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