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