]> mj.ucw.cz Git - osdd.git/blob - display.c
Bits of new display code
[osdd.git] / display.c
1 /*
2  *      On-screen Display
3  *
4  *      (c) 2013 Martin Mares <mj@ucw.cz>
5  *
6  *      This code is heavily inspired by the libxosd library,
7  *      which is (c) 2000, 2001 Andre Renaud <andre@ignavus.net>.
8  */
9
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <stdbool.h>
13 #include <string.h>
14 #include <xosd.h>
15 #include <X11/Xlib.h>
16 #include <X11/Xatom.h>
17 #include <X11/extensions/shape.h>
18 #include <X11/extensions/render.h>
19 #include <X11/Xft/Xft.h>
20
21 #undef DEBUG
22 #include "util.h"
23 #include "display.h"
24
25 struct osd_state {
26   // Basic characteristics of current display and screen
27   Display *dpy;
28   int screen;
29   Visual *visual;
30   Colormap cmap;
31   Window root;
32   int depth;
33   int screen_width;
34   int screen_height;
35
36   // Our window
37   Window win;
38   int win_width;
39   int win_height;
40   Pixmap mask_bitmap;
41   Pixmap image_pixmap;
42   GC gc;
43   GC mask_gc;
44
45   // Xft state
46   XftFont *font;
47   XftDraw *mask_draw;
48   XftDraw *image_draw;
49
50   // Contents of the display
51   struct osd_line *lines;
52   int num_lines;
53   int max_lines;
54   int line_distance;
55   int line_height;
56   bool visible;
57 };
58
59 static void
60 stay_on_top(struct osd_state *osd)
61 {
62   int format;
63   unsigned long nitems, bytes_after;
64   unsigned char *prop = NULL;
65   Atom type;
66
67   // Gnome-compliant way
68   Atom gnome = XInternAtom(osd->dpy, "_WIN_SUPPORTING_WM_CHECK", False);
69   if (XGetWindowProperty(osd->dpy, osd->root, gnome, 0, 16384, False, AnyPropertyType,
70                          &type, &format, &nitems, &bytes_after, &prop) == Success &&
71       nitems > 0)
72     {
73       DBG("stay_on_top: Gnome mode\n");
74       // FIXME: check capabilities
75       XClientMessageEvent e;
76       memset(&e, 0, sizeof(e));
77       e.type = ClientMessage;
78       e.window = osd->win;
79       e.message_type = XInternAtom(osd->dpy, "_WIN_LAYER", False);
80       e.format = 32;
81       e.data.l[0] = 6;  // WIN_LAYER_ONTOP */
82       XSendEvent(osd->dpy, osd->root, False, SubstructureNotifyMask, (XEvent *) &e);
83       XFree(prop);
84       return;
85     }
86
87   // NetWM-compliant way
88   Atom net_wm = XInternAtom(osd->dpy, "_NET_SUPPORTED", False);
89   if (XGetWindowProperty(osd->dpy, osd->root, net_wm, 0, 16384, False, AnyPropertyType,
90                          &type, &format, &nitems, &bytes_after, &prop) == Success &&
91       nitems > 0)
92     {
93       DBG("stay_on_top: NetWM mode\n");
94       XEvent e;
95       memset(&e, 0, sizeof(e));
96       e.xclient.type = ClientMessage;
97       e.xclient.message_type = XInternAtom(osd->dpy, "_NET_WM_STATE", False);
98       e.xclient.display = osd->dpy;
99       e.xclient.window = osd->win;
100       e.xclient.format = 32;
101       e.xclient.data.l[0] = 1;  // _NET_WM_STATE_ADD
102       e.xclient.data.l[1] = XInternAtom(osd->dpy, "_NET_WM_STATE_STAYS_ON_TOP", False);
103       XSendEvent(osd->dpy, osd->root, False, SubstructureRedirectMask, &e);
104       XFree(prop);
105       return;
106     }
107
108   DBG("stay_on_top: WM does not support any known protocol\n");
109 }
110
111 struct osd_state *osd_new(Display *dpy)
112 {
113   struct osd_state *osd = xmalloc(sizeof(*osd));
114   memset(osd, 0, sizeof(*osd));
115
116   osd->dpy = dpy;
117   osd->screen = XDefaultScreen(osd->dpy);
118   osd->visual = XDefaultVisual(osd->dpy, osd->screen);
119   osd->cmap = DefaultColormap(osd->dpy, osd->screen);
120   osd->root = DefaultRootWindow(osd->dpy);
121
122   // FIXME: These can change. And what about Xinerama?
123   osd->depth = XDefaultDepth(osd->dpy, osd->screen);
124   osd->screen_width = XDisplayWidth(osd->dpy, osd->screen);
125   osd->screen_height = XDisplayHeight(osd->dpy, osd->screen);
126   DBG("Screen: %dx%d depth %d\n", osd->screen_width, osd->screen_height, osd->depth);
127
128   int event_basep, error_basep;
129   if (!XShapeQueryExtension(osd->dpy, &event_basep, &error_basep))
130     die("XShape extension not supported by X server, giving up");
131
132   osd->max_lines = 2;   // FIXME
133   osd->lines = xmalloc(sizeof(struct osd_line) * osd->max_lines);
134
135   return osd;
136 }
137
138 void osd_free(struct osd_state *osd)
139 {
140   osd_hide(osd);
141
142   if (osd->font)
143     XftFontClose(osd->dpy, osd->font);
144
145   free(osd->lines);
146   free(osd);
147 }
148
149 void osd_set_font(struct osd_state *osd, char *font_name)
150 {
151   if (osd->font)
152     XftFontClose(osd->dpy, osd->font);
153
154   DBG("Using font %s", font_name);
155   osd->font = XftFontOpenName(osd->dpy, osd->screen, font_name);
156   if (!osd->font)
157     die("Cannot open font %s", font_name);
158   DBG("Font: asc=%d desc=%d ht=%d", osd->font->ascent, osd->font->descent, osd->font->height);
159
160   osd->line_distance = osd->font->height;
161   osd->line_height = osd->font->ascent;
162 }
163
164 struct osd_line *osd_add_line(struct osd_state *osd, enum osd_line_type type)
165 {
166   if (osd->num_lines >= osd->max_lines)
167     {
168       osd->max_lines = 2 * osd->max_lines;
169       osd->lines = xrealloc(osd->lines, sizeof(struct osd_line) * osd->max_lines);
170     }
171
172   struct osd_line *l = &osd->lines[osd->num_lines++];
173   l->type = type;
174   l->fg_color = "green";
175   l->outline_color = "yellow";
176   l->outline_width = 0;
177   // FIXME: Colors, alignment etc.
178
179   switch (l->type)
180     {
181     case OSD_TYPE_TEXT:
182       l->u.text[0] = 0;
183       break;
184     default:
185       die("osd_add_line: unknown type %d", type);
186     }
187
188   return l;
189 }
190
191 static void osd_prepare_line(struct osd_state *osd, int i)
192 {
193   struct osd_line *line = &osd->lines[i];
194   switch (line->type)
195     {
196     case OSD_TYPE_TEXT:
197       {
198         XGlyphInfo gi;
199         XftTextExtentsUtf8(osd->dpy, osd->font, (unsigned char *) line->u.text, strlen(line->u.text), &gi);
200         DBG("Line #%d: Glyph info: (%d,%d)+(%d,%d) off (%d,%d)\n", i, gi.x, gi.y, gi.width, gi.height, gi.xOff, gi.yOff);
201         line->width = gi.width + 2*line->outline_width;
202         line->height = osd->line_distance + 2*line->outline_width;
203         break;
204       }
205     default:
206       die("osd_recalc_line: unknown type %d", line->type);
207     }
208
209   DBG("Line #%d: Width %d (outline %d)\n", i, line->width, line->outline_width);
210 }
211
212 static void osd_justify_line(struct osd_state *osd, int i)
213 {
214   // FIXME: Support more modes of justification
215   struct osd_line *line = &osd->lines[i];
216   line->x_pos = (osd->win_width - line->width) / 2;
217   DBG("Line #%d: Position (%d,%d)\n", i, line->x_pos, line->y_pos);
218 }
219
220 static void osd_prepare(struct osd_state *osd)
221 {
222   osd->win_width = 0;
223   osd->win_height = 0;
224   for (int i=0; i < osd->num_lines; i++)
225     {
226       struct osd_line *line = &osd->lines[i];
227       osd_prepare_line(osd, i);
228       if (line->width > osd->win_width)
229         osd->win_width = line->width;
230       osd->win_height += line->height;
231     }
232
233   // FIXME: Check clipping
234   if (osd->win_width > osd->screen_width)
235     osd->win_width = osd->screen_width;
236   if (osd->win_height > osd->screen_height)
237     osd->win_height = osd->screen_height;
238   DBG("Window size set to %dx%d\n", osd->win_width, osd->win_height);
239
240   int y = 0;
241   for (int i=0; i < osd->num_lines; i++)
242     {
243       struct osd_line *line = &osd->lines[i];
244       line->y_pos = y;
245       osd_justify_line(osd, i);
246       y += line->height;
247     }
248 }
249
250 static void osd_draw_line(struct osd_state *osd, int i)
251 {
252   struct osd_line *line = &osd->lines[i];
253
254   // Allocate colors
255   XftColor fg_color, outline_color, mask_color;
256   XRenderColor mask_rc = { .red = 0xffff, .green = 0xffff, .blue = 0xffff, .alpha = 0xffff };
257   if (!XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->fg_color, &fg_color) ||
258       !XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->outline_color, &outline_color) ||
259       !XftColorAllocValue(osd->dpy, osd->visual, osd->cmap, &mask_rc, &mask_color))
260     die("Cannot allocate colors");
261
262   // Draw background in outline color
263   XftDrawRect(osd->image_draw, &outline_color, 0, line->y_pos, osd->win_width, line->height);
264
265   switch (line->type)
266     {
267     case OSD_TYPE_TEXT:
268       {
269         unsigned char *text = (unsigned char *) line->u.text;
270         int text_len = strlen(line->u.text);
271         XftDrawStringUtf8(osd->image_draw, &fg_color, osd->font, line->x_pos + line->outline_width, line->y_pos + line->outline_width, text, text_len);
272
273         // This is slow, but unlike the method used by libxosd, the result isn't ugly.
274         int outline = line->outline_width;
275         for (int dx = -outline; dx <= outline; dx++)
276           for (int dy = -outline; dy <= outline; dy++)
277             if (dx*dx + dy*dy <= outline*outline)
278               XftDrawStringUtf8(osd->mask_draw, &mask_color, osd->font, 100 + dx, 100 + dy, text, text_len);
279
280         break;
281       }
282     default:
283       die("osd_draw_line: unknown type %d", line->type);
284     }
285
286   XftColorFree(osd->dpy, osd->visual, osd->cmap, &fg_color);
287   XftColorFree(osd->dpy, osd->visual, osd->cmap, &outline_color);
288   XftColorFree(osd->dpy, osd->visual, osd->cmap, &mask_color);
289 }
290
291 void osd_show(struct osd_state *osd)
292 {
293   osd_hide(osd);
294   osd_prepare(osd);
295
296   // Create our window
297   XSetWindowAttributes win_attr = {
298     .override_redirect = 1,
299   };
300   osd->win = XCreateWindow(osd->dpy,
301         osd->root,
302         0, 0,
303         osd->win_width, osd->win_height,
304         0,
305         osd->depth,
306         CopyFromParent,
307         osd->visual,
308         CWOverrideRedirect,
309         &win_attr);
310   XStoreName(osd->dpy, osd->win, "OSD");
311   stay_on_top(osd);
312
313   // Create image pixmap and its graphic context
314   // osd->gc can be used for both osd->win and osd->image_bitmap as they have the same root and depth
315   osd->image_pixmap = XCreatePixmap(osd->dpy, osd->win, osd->win_width, osd->win_height, osd->depth);
316   XGCValues gcv = {
317     .graphics_exposures = 0,
318   };
319   osd->gc = XCreateGC(osd->dpy, osd->win, GCGraphicsExposures, &gcv);
320
321   // Create mask bitmap and its GC
322   osd->mask_bitmap = XCreatePixmap(osd->dpy, osd->win, osd->win_width, osd->win_height, 1);
323   osd->mask_gc = XCreateGC(osd->dpy, osd->mask_bitmap, GCGraphicsExposures, &gcv);
324
325   // Clear the mask bitmap
326   XSetBackground(osd->dpy, osd->mask_gc, WhitePixel(osd->dpy, osd->screen));
327   XSetForeground(osd->dpy, osd->mask_gc, BlackPixel(osd->dpy, osd->screen));
328   XFillRectangle(osd->dpy, osd->mask_bitmap, osd->mask_gc, 0, 0, osd->win_width, osd->win_height);
329
330   // Create XftDraw for mask and image
331   osd->mask_draw = XftDrawCreateBitmap(osd->dpy, osd->mask_bitmap);
332   osd->image_draw = XftDrawCreate(osd->dpy, osd->image_pixmap, osd->visual, osd->cmap);
333   if (!osd->mask_draw || !osd->image_draw)
334     die("Cannot create XftDraw");
335
336   // Draw individial lines
337   for (int i=0; i < osd->num_lines; i++)
338     osd_draw_line(osd, i);
339
340   XShapeCombineMask(osd->dpy, osd->win, ShapeBounding, 0, 0, osd->mask_bitmap, ShapeSet);
341
342   XSelectInput(osd->dpy, osd->win, ExposureMask);
343   XMapRaised(osd->dpy, osd->win);
344   XFlush(osd->dpy);
345
346   osd->visible = 1;
347 }
348
349 void osd_hide(struct osd_state *osd)
350 {
351   if (!osd->visible)
352     return;
353
354   XftDrawDestroy(osd->image_draw);
355   XftDrawDestroy(osd->mask_draw);
356   XFreeGC(osd->dpy, osd->gc);
357   XFreeGC(osd->dpy, osd->mask_gc);
358   XFreePixmap(osd->dpy, osd->image_pixmap);
359   XFreePixmap(osd->dpy, osd->mask_bitmap);
360   XDestroyWindow(osd->dpy, osd->win);
361
362   osd->visible = 0;
363 }
364
365 bool osd_handle_event(struct osd_state *osd, XEvent *ev)
366 {
367   if (!osd->visible)
368     return 0;
369
370   if (ev->type == Expose)
371     {
372       XExposeEvent *ex = &ev->xexpose;
373       if (ex->window == osd->win)
374         {
375           DBG("Expose cnt=%d (%d,%d)+(%d,%d)\n", ex->count, ex->x, ex->y, ex->width, ex->height);
376           XCopyArea(osd->dpy, osd->image_pixmap, osd->win, osd->gc, ex->x, ex->y, ex->width, ex->height, ex->x, ex->y);
377           return 1;
378         }
379     }
380
381   return 0;
382 }