4 * (c) 2013--2014 Martin Mares <mj@ucw.cz>
6 * This code is heavily inspired by the libxosd library,
7 * which is (c) 2000, 2001 Andre Renaud <andre@ignavus.net>.
15 #include <X11/Xatom.h>
16 #include <X11/extensions/shape.h>
17 #include <X11/extensions/render.h>
18 #include <X11/Xft/Xft.h>
24 #define SLIDERS_WITH_BRACKETS
27 // Basic characteristics of current display and screen
51 // Contents of the display
52 struct osd_line *lines;
60 // Used temporarily when drawing a line
66 stay_on_top(struct osd_state *osd)
69 unsigned long nitems, bytes_after;
70 unsigned char *prop = NULL;
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 &&
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;
85 e.message_type = XInternAtom(osd->dpy, "_WIN_LAYER", False);
87 e.data.l[0] = 6; // WIN_LAYER_ONTOP */
88 XSendEvent(osd->dpy, osd->root, False, SubstructureNotifyMask, (XEvent *) &e);
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 &&
99 DBG("stay_on_top: NetWM mode\n");
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);
114 DBG("stay_on_top: WM does not support any known protocol\n");
117 struct osd_state *osd_new(Display *dpy)
119 struct osd_state *osd = xmalloc(sizeof(*osd));
120 memset(osd, 0, sizeof(*osd));
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);
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);
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");
139 osd->lines = xmalloc(sizeof(struct osd_line) * osd->max_lines);
144 void osd_free(struct osd_state *osd)
149 XftFontClose(osd->dpy, osd->font);
155 void osd_set_font(struct osd_state *osd, char *font_name, double line_spacing)
158 XftFontClose(osd->dpy, osd->font);
160 DBG("Using font %s\n", font_name);
161 osd->font = XftFontOpenName(osd->dpy, osd->screen, font_name);
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);
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);
172 struct osd_line *osd_add_line(struct osd_state *osd, enum osd_line_type type)
174 if (osd->num_lines >= osd->max_lines)
176 osd->max_lines = 2 * osd->max_lines;
177 osd->lines = xrealloc(osd->lines, sizeof(struct osd_line) * osd->max_lines);
180 struct osd_line *l = &osd->lines[osd->num_lines++];
182 l->fg_color = "green";
183 l->outline_color = "black";
184 l->outline_width = 0;
191 case OSD_TYPE_PERCENTAGE:
192 case OSD_TYPE_SLIDER:
196 die("osd_add_line: unknown type %d", type);
202 static void osd_prepare_line(struct osd_state *osd, int i)
204 struct osd_line *line = &osd->lines[i];
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;
216 case OSD_TYPE_PERCENTAGE:
217 case OSD_TYPE_SLIDER:
219 #ifdef SLIDERS_WITH_BRACKETS
220 line->slider_unit = osd->line_height / 5;
221 line->slider_space = osd->line_height / 5;
223 line->slider_unit = osd->line_height / 3;
224 line->slider_space = osd->line_height / 6;
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;
238 die("osd_recalc_line: unknown type %d", line->type);
241 DBG("Line #%d: Width %d (outline %d)\n", i, line->width, line->outline_width);
244 static void osd_justify_line(struct osd_state *osd, int i)
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);
252 static void osd_prepare(struct osd_state *osd)
256 for (int i=0; i < osd->num_lines; i++)
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;
264 osd->win_height += osd->line_skip;
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);
274 for (int i=0; i < osd->num_lines; i++)
276 struct osd_line *line = &osd->lines[i];
280 osd_justify_line(osd, i);
285 static void osd_draw_box(struct osd_state *osd, struct osd_line *line, int x, int y, int w, int h)
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);
293 static void osd_draw_line(struct osd_state *osd, int i)
295 struct osd_line *line = &osd->lines[i];
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");
305 // Draw background in outline color
306 XftDrawRect(osd->image_draw, &outline_color, 0, line->y_pos, osd->win_width, line->height);
312 int x = line->x_pos + line->outline_width;
313 int y = line->y_pos + line->outline_width + osd->line_height;
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);
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);
328 case OSD_TYPE_PERCENTAGE:
329 case OSD_TYPE_SLIDER:
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
338 int hu = osd->line_height / 5;
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);
345 int hu = osd->line_height / 3;
349 if (line->type == OSD_TYPE_PERCENTAGE)
352 max = (units+1) * line->u.percent / 100 - 1;
355 min = max = (units-1) * line->u.percent / 100;
356 for (int i=0; i < units; i++)
358 if (i >= min && i <= max)
359 osd_draw_box(osd, line, x, y, su, osd->line_height);
361 osd_draw_box(osd, line, x, y + hd, su, hu);
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);
372 die("osd_draw_line: unknown type %d", line->type);
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);
380 void osd_show(struct osd_state *osd)
386 XSetWindowAttributes win_attr = {
387 .override_redirect = 1,
389 osd->win = XCreateWindow(osd->dpy,
391 (osd->screen_width - osd->win_width) / 2, (osd->screen_height - osd->win_height) / 2,
392 osd->win_width, osd->win_height,
399 XStoreName(osd->dpy, osd->win, "OSD");
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);
406 .graphics_exposures = 0,
408 osd->gc = XCreateGC(osd->dpy, osd->win, GCGraphicsExposures, &gcv);
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);
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);
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");
425 // Draw individial lines
426 for (int i=0; i < osd->num_lines; i++)
427 osd_draw_line(osd, i);
429 XShapeCombineMask(osd->dpy, osd->win, ShapeBounding, 0, 0, osd->mask_bitmap, ShapeSet);
431 XSelectInput(osd->dpy, osd->win, ExposureMask);
432 XMapRaised(osd->dpy, osd->win);
438 void osd_hide(struct osd_state *osd)
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);
455 void osd_clear(struct osd_state *osd)
461 bool osd_handle_event(struct osd_state *osd, XEvent *ev)
466 if (ev->type == Expose)
468 XExposeEvent *ex = &ev->xexpose;
469 if (ex->window == osd->win)
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);