]> mj.ucw.cz Git - osdd.git/blob - display.c
New display code supports sliders and percentages
[osdd.git] / display.c
1 /*
2  *      On-screen Display
3  *
4  *      (c) 2013--2014 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 #define SLIDERS_WITH_BRACKETS
26
27 struct osd_state {
28   // Basic characteristics of current display and screen
29   Display *dpy;
30   int screen;
31   Visual *visual;
32   Colormap cmap;
33   Window root;
34   int depth;
35   int screen_width;
36   int screen_height;
37
38   // Our window
39   Window win;
40   int win_width;
41   int win_height;
42   Pixmap mask_bitmap;
43   Pixmap image_pixmap;
44   GC gc;
45   GC mask_gc;
46
47   // Xft state
48   XftFont *font;
49   XftDraw *mask_draw;
50   XftDraw *image_draw;
51
52   // Contents of the display
53   struct osd_line *lines;
54   int num_lines;
55   int max_lines;
56   int line_distance;
57   int line_height;
58   int line_skip;
59   bool visible;
60
61   // Used temporarily when drawing a line
62   XftColor fg_color;
63   XftColor mask_color;
64 };
65
66 static void
67 stay_on_top(struct osd_state *osd)
68 {
69   int format;
70   unsigned long nitems, bytes_after;
71   unsigned char *prop = NULL;
72   Atom type;
73
74   // Gnome-compliant way
75   Atom gnome = XInternAtom(osd->dpy, "_WIN_SUPPORTING_WM_CHECK", False);
76   if (XGetWindowProperty(osd->dpy, osd->root, gnome, 0, 16384, False, AnyPropertyType,
77                          &type, &format, &nitems, &bytes_after, &prop) == Success &&
78       nitems > 0)
79     {
80       DBG("stay_on_top: Gnome mode\n");
81       // FIXME: Check capabilities
82       XClientMessageEvent e;
83       memset(&e, 0, sizeof(e));
84       e.type = ClientMessage;
85       e.window = osd->win;
86       e.message_type = XInternAtom(osd->dpy, "_WIN_LAYER", False);
87       e.format = 32;
88       e.data.l[0] = 6;  // WIN_LAYER_ONTOP */
89       XSendEvent(osd->dpy, osd->root, False, SubstructureNotifyMask, (XEvent *) &e);
90       XFree(prop);
91       return;
92     }
93
94   // NetWM-compliant way
95   Atom net_wm = XInternAtom(osd->dpy, "_NET_SUPPORTED", False);
96   if (XGetWindowProperty(osd->dpy, osd->root, net_wm, 0, 16384, False, AnyPropertyType,
97                          &type, &format, &nitems, &bytes_after, &prop) == Success &&
98       nitems > 0)
99     {
100       DBG("stay_on_top: NetWM mode\n");
101       XEvent e;
102       memset(&e, 0, sizeof(e));
103       e.xclient.type = ClientMessage;
104       e.xclient.message_type = XInternAtom(osd->dpy, "_NET_WM_STATE", False);
105       e.xclient.display = osd->dpy;
106       e.xclient.window = osd->win;
107       e.xclient.format = 32;
108       e.xclient.data.l[0] = 1;  // _NET_WM_STATE_ADD
109       e.xclient.data.l[1] = XInternAtom(osd->dpy, "_NET_WM_STATE_STAYS_ON_TOP", False);
110       XSendEvent(osd->dpy, osd->root, False, SubstructureRedirectMask, &e);
111       XFree(prop);
112       return;
113     }
114
115   DBG("stay_on_top: WM does not support any known protocol\n");
116 }
117
118 struct osd_state *osd_new(Display *dpy)
119 {
120   struct osd_state *osd = xmalloc(sizeof(*osd));
121   memset(osd, 0, sizeof(*osd));
122
123   osd->dpy = dpy;
124   osd->screen = XDefaultScreen(osd->dpy);
125   osd->visual = XDefaultVisual(osd->dpy, osd->screen);
126   osd->cmap = DefaultColormap(osd->dpy, osd->screen);
127   osd->root = DefaultRootWindow(osd->dpy);
128
129   // FIXME: These can change. And what about Xinerama?
130   osd->depth = XDefaultDepth(osd->dpy, osd->screen);
131   osd->screen_width = XDisplayWidth(osd->dpy, osd->screen);
132   osd->screen_height = XDisplayHeight(osd->dpy, osd->screen);
133   DBG("Screen: %dx%d depth %d\n", osd->screen_width, osd->screen_height, osd->depth);
134
135   int event_basep, error_basep;
136   if (!XShapeQueryExtension(osd->dpy, &event_basep, &error_basep))
137     die("XShape extension not supported by X server, giving up");
138
139   osd->max_lines = 4;
140   osd->lines = xmalloc(sizeof(struct osd_line) * osd->max_lines);
141
142   return osd;
143 }
144
145 void osd_free(struct osd_state *osd)
146 {
147   osd_hide(osd);
148
149   if (osd->font)
150     XftFontClose(osd->dpy, osd->font);
151
152   free(osd->lines);
153   free(osd);
154 }
155
156 void osd_set_font(struct osd_state *osd, char *font_name, double line_spacing)
157 {
158   if (osd->font)
159     XftFontClose(osd->dpy, osd->font);
160
161   DBG("Using font %s\n", font_name);
162   osd->font = XftFontOpenName(osd->dpy, osd->screen, font_name);
163   if (!osd->font)
164     die("Cannot open font %s", font_name);
165   DBG("Font: asc=%d desc=%d ht=%d\n", osd->font->ascent, osd->font->descent, osd->font->height);
166
167   osd->line_distance = osd->font->height;
168   osd->line_height = osd->font->ascent;
169   osd->line_skip = osd->line_distance * line_spacing;
170   DBG("Line: distance=%d height=%d skip=%d\n", osd->line_distance, osd->line_height, osd->line_skip);
171 }
172
173 struct osd_line *osd_add_line(struct osd_state *osd, enum osd_line_type type)
174 {
175   if (osd->num_lines >= osd->max_lines)
176     {
177       osd->max_lines = 2 * osd->max_lines;
178       osd->lines = xrealloc(osd->lines, sizeof(struct osd_line) * osd->max_lines);
179     }
180
181   struct osd_line *l = &osd->lines[osd->num_lines++];
182   l->type = type;
183   l->fg_color = "green";
184   l->outline_color = "black";
185   l->outline_width = 0;
186
187   switch (l->type)
188     {
189     case OSD_TYPE_TEXT:
190       l->u.text[0] = 0;
191       break;
192     case OSD_TYPE_PERCENTAGE:
193     case OSD_TYPE_SLIDER:
194       l->u.percent = 0;
195       break;
196     default:
197       die("osd_add_line: unknown type %d", type);
198     }
199
200   return l;
201 }
202
203 static void osd_prepare_line(struct osd_state *osd, int i)
204 {
205   struct osd_line *line = &osd->lines[i];
206   switch (line->type)
207     {
208     case OSD_TYPE_TEXT:
209       {
210         XGlyphInfo gi;
211         XftTextExtentsUtf8(osd->dpy, osd->font, (unsigned char *) line->u.text, strlen(line->u.text), &gi);
212         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);
213         line->width = gi.width + 2*line->outline_width;
214         line->height = osd->line_distance + 2*line->outline_width;
215         break;
216       }
217     case OSD_TYPE_PERCENTAGE:
218     case OSD_TYPE_SLIDER:
219       {
220 #ifdef SLIDERS_WITH_BRACKETS
221         line->slider_unit = osd->line_height / 5;
222         line->slider_space = osd->line_height / 5;
223 #else
224         line->slider_unit = osd->line_height / 3;
225         line->slider_space = osd->line_height / 6;
226 #endif
227         if (!line->slider_space)
228           line->slider_space = line->slider_unit = 1;
229         int use_width = osd->screen_width * 4 / 5;
230         int u = line->slider_unit + line->slider_space;
231         line->slider_units = (use_width + line->slider_space) / u;
232         if (line->slider_units < 3)
233           line->slider_units = 3;
234         line->width = line->slider_units*u - line->slider_space + 2*line->outline_width;
235         line->height = osd->line_height + 2*line->outline_width;
236         break;
237       }
238     default:
239       die("osd_recalc_line: unknown type %d", line->type);
240     }
241
242   DBG("Line #%d: Width %d (outline %d)\n", i, line->width, line->outline_width);
243 }
244
245 static void osd_justify_line(struct osd_state *osd, int i)
246 {
247   // FIXME: Support more modes of justification
248   struct osd_line *line = &osd->lines[i];
249   line->x_pos = (osd->win_width - line->width) / 2;
250   DBG("Line #%d: Position (%d,%d)\n", i, line->x_pos, line->y_pos);
251 }
252
253 static void osd_prepare(struct osd_state *osd)
254 {
255   osd->win_width = 0;
256   osd->win_height = 0;
257   for (int i=0; i < osd->num_lines; i++)
258     {
259       struct osd_line *line = &osd->lines[i];
260       osd_prepare_line(osd, i);
261       if (line->width > osd->win_width)
262         osd->win_width = line->width;
263       osd->win_height += line->height;
264       if (i)
265         osd->win_height += osd->line_skip;
266     }
267
268   if (osd->win_width > osd->screen_width)
269     osd->win_width = osd->screen_width;
270   if (osd->win_height > osd->screen_height)
271     osd->win_height = osd->screen_height;
272   DBG("Window size set to %dx%d\n", osd->win_width, osd->win_height);
273
274   int y = 0;
275   for (int i=0; i < osd->num_lines; i++)
276     {
277       struct osd_line *line = &osd->lines[i];
278       if (i)
279         y += osd->line_skip;
280       line->y_pos = y;
281       osd_justify_line(osd, i);
282       y += line->height;
283     }
284 }
285
286 static void osd_draw_box(struct osd_state *osd, struct osd_line *line, int x, int y, int w, int h)
287 {
288   XftDrawRect(osd->mask_draw, &osd->mask_color,
289     x - line->outline_width, y - line->outline_width,
290     w + 2*line->outline_width, h + 2*line->outline_width);
291   XftDrawRect(osd->image_draw, &osd->fg_color, x, y, w, h);
292 }
293
294 static void osd_draw_line(struct osd_state *osd, int i)
295 {
296   struct osd_line *line = &osd->lines[i];
297
298   // Allocate colors
299   XftColor outline_color;
300   XRenderColor mask_rc = { .red = 0xffff, .green = 0xffff, .blue = 0xffff, .alpha = 0xffff };
301   if (!XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->fg_color, &osd->fg_color) ||
302       !XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->outline_color, &outline_color) ||
303       !XftColorAllocValue(osd->dpy, osd->visual, osd->cmap, &mask_rc, &osd->mask_color))
304     die("Cannot allocate colors");
305
306   // Draw background in outline color
307   XftDrawRect(osd->image_draw, &outline_color, 0, line->y_pos, osd->win_width, line->height);
308
309   switch (line->type)
310     {
311     case OSD_TYPE_TEXT:
312       {
313         int x = line->x_pos + line->outline_width;
314         int y = line->y_pos + line->outline_width + osd->line_height;
315
316         unsigned char *text = (unsigned char *) line->u.text;
317         int text_len = strlen(line->u.text);
318         XftDrawStringUtf8(osd->image_draw, &osd->fg_color, osd->font, x, y, text, text_len);
319
320         // This is slow, but unlike the method used by libxosd, the result isn't ugly.
321         int outline = line->outline_width;
322         for (int dx = -outline; dx <= outline; dx++)
323           for (int dy = -outline; dy <= outline; dy++)
324             if (dx*dx + dy*dy <= outline*outline)
325               XftDrawStringUtf8(osd->mask_draw, &osd->mask_color, osd->font, x + dx, y + dy, text, text_len);
326
327         break;
328       }
329     case OSD_TYPE_PERCENTAGE:
330     case OSD_TYPE_SLIDER:
331       {
332         int x = line->x_pos + line->outline_width;
333         int y = line->y_pos + line->outline_width;
334         int su = line->slider_unit;
335         int advance = su + line->slider_space;
336         int units = line->slider_units;
337 #ifdef SLIDERS_WITH_BRACKETS
338         units -= 2;
339         int hu = osd->line_height / 5;
340         int hd = 2*hu;
341         osd_draw_box(osd, line, x, y, su - su/3, 5*hu);
342         osd_draw_box(osd, line, x, y, su, hu);
343         osd_draw_box(osd, line, x, y + 4*hu, su, hu);
344         x += advance;
345 #else
346         int hu = osd->line_height / 3;
347         int hd = hu;
348 #endif
349         int min, max;
350         if (line->type == OSD_TYPE_PERCENTAGE)
351           {
352             min = 0;
353             max = (units+1) * line->u.percent / 100 - 1;
354           }
355         else
356           min = max = (units-1) * line->u.percent / 100;
357         for (int i=0; i < units; i++)
358           {
359             if (i >= min && i <= max)
360               osd_draw_box(osd, line, x, y, su, osd->line_height);
361             else
362               osd_draw_box(osd, line, x, y + hd, su, hu);
363             x += advance;
364           }
365 #ifdef SLIDERS_WITH_BRACKETS
366         osd_draw_box(osd, line, x + su/3, y, su - su/3, 5*hu);
367         osd_draw_box(osd, line, x, y, su, hu);
368         osd_draw_box(osd, line, x, y + 4*hu, su, hu);
369 #endif
370         break;
371       }
372     default:
373       die("osd_draw_line: unknown type %d", line->type);
374     }
375
376   XftColorFree(osd->dpy, osd->visual, osd->cmap, &osd->fg_color);
377   XftColorFree(osd->dpy, osd->visual, osd->cmap, &outline_color);
378   XftColorFree(osd->dpy, osd->visual, osd->cmap, &osd->mask_color);
379 }
380
381 void osd_show(struct osd_state *osd)
382 {
383   osd_hide(osd);
384   osd_prepare(osd);
385
386   // Create our window
387   XSetWindowAttributes win_attr = {
388     .override_redirect = 1,
389   };
390   osd->win = XCreateWindow(osd->dpy,
391         osd->root,
392         (osd->screen_width - osd->win_width) / 2, (osd->screen_height - osd->win_height) / 2,
393         osd->win_width, osd->win_height,
394         0,
395         osd->depth,
396         CopyFromParent,
397         osd->visual,
398         CWOverrideRedirect,
399         &win_attr);
400   XStoreName(osd->dpy, osd->win, "OSD");
401   stay_on_top(osd);
402
403   // Create image pixmap and its graphic context
404   // osd->gc can be used for both osd->win and osd->image_bitmap as they have the same root and depth
405   osd->image_pixmap = XCreatePixmap(osd->dpy, osd->win, osd->win_width, osd->win_height, osd->depth);
406   XGCValues gcv = {
407     .graphics_exposures = 0,
408   };
409   osd->gc = XCreateGC(osd->dpy, osd->win, GCGraphicsExposures, &gcv);
410
411   // Create mask bitmap and its GC
412   osd->mask_bitmap = XCreatePixmap(osd->dpy, osd->win, osd->win_width, osd->win_height, 1);
413   osd->mask_gc = XCreateGC(osd->dpy, osd->mask_bitmap, GCGraphicsExposures, &gcv);
414
415   // Clear the mask bitmap
416   XSetBackground(osd->dpy, osd->mask_gc, WhitePixel(osd->dpy, osd->screen));
417   XSetForeground(osd->dpy, osd->mask_gc, BlackPixel(osd->dpy, osd->screen));
418   XFillRectangle(osd->dpy, osd->mask_bitmap, osd->mask_gc, 0, 0, osd->win_width, osd->win_height);
419
420   // Create XftDraw for mask and image
421   osd->mask_draw = XftDrawCreateBitmap(osd->dpy, osd->mask_bitmap);
422   osd->image_draw = XftDrawCreate(osd->dpy, osd->image_pixmap, osd->visual, osd->cmap);
423   if (!osd->mask_draw || !osd->image_draw)
424     die("Cannot create XftDraw");
425
426   // Draw individial lines
427   for (int i=0; i < osd->num_lines; i++)
428     osd_draw_line(osd, i);
429
430   XShapeCombineMask(osd->dpy, osd->win, ShapeBounding, 0, 0, osd->mask_bitmap, ShapeSet);
431
432   XSelectInput(osd->dpy, osd->win, ExposureMask);
433   XMapRaised(osd->dpy, osd->win);
434   XFlush(osd->dpy);
435
436   osd->visible = 1;
437 }
438
439 void osd_hide(struct osd_state *osd)
440 {
441   if (!osd->visible)
442     return;
443
444   XftDrawDestroy(osd->image_draw);
445   XftDrawDestroy(osd->mask_draw);
446   XFreeGC(osd->dpy, osd->gc);
447   XFreeGC(osd->dpy, osd->mask_gc);
448   XFreePixmap(osd->dpy, osd->image_pixmap);
449   XFreePixmap(osd->dpy, osd->mask_bitmap);
450   XDestroyWindow(osd->dpy, osd->win);
451   XFlush(osd->dpy);
452
453   osd->visible = 0;
454 }
455
456 void osd_clear(struct osd_state *osd)
457 {
458   osd_hide(osd);
459   osd->num_lines = 0;
460 }
461
462 bool osd_handle_event(struct osd_state *osd, XEvent *ev)
463 {
464   if (!osd->visible)
465     return 0;
466
467   if (ev->type == Expose)
468     {
469       XExposeEvent *ex = &ev->xexpose;
470       if (ex->window == osd->win)
471         {
472           DBG("Expose cnt=%d (%d,%d)+(%d,%d)\n", ex->count, ex->x, ex->y, ex->width, ex->height);
473           XCopyArea(osd->dpy, osd->image_pixmap, osd->win, osd->gc, ex->x, ex->y, ex->width, ex->height, ex->x, ex->y);
474           return 1;
475         }
476     }
477
478   return 0;
479 }