--- /dev/null
+/*
+ * On-screen Display
+ *
+ * (c) 2013 Martin Mares <mj@ucw.cz>
+ *
+ * This code is heavily inspired by the libxosd library,
+ * which is (c) 2000, 2001 Andre Renaud <andre@ignavus.net>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <xosd.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/shape.h>
+#include <X11/extensions/render.h>
+#include <X11/Xft/Xft.h>
+
+#undef DEBUG
+#include "util.h"
+#include "display.h"
+
+struct osd_state {
+ // Basic characteristics of current display and screen
+ Display *dpy;
+ int screen;
+ Visual *visual;
+ Colormap cmap;
+ Window root;
+ int depth;
+ int screen_width;
+ int screen_height;
+
+ // Our window
+ Window win;
+ int win_width;
+ int win_height;
+ Pixmap mask_bitmap;
+ Pixmap image_pixmap;
+ GC gc;
+ GC mask_gc;
+
+ // Xft state
+ XftFont *font;
+ XftDraw *mask_draw;
+ XftDraw *image_draw;
+
+ // Contents of the display
+ struct osd_line *lines;
+ int num_lines;
+ int max_lines;
+ int line_distance;
+ int line_height;
+ bool visible;
+};
+
+static void
+stay_on_top(struct osd_state *osd)
+{
+ int format;
+ unsigned long nitems, bytes_after;
+ unsigned char *prop = NULL;
+ Atom type;
+
+ // Gnome-compliant way
+ Atom gnome = XInternAtom(osd->dpy, "_WIN_SUPPORTING_WM_CHECK", False);
+ if (XGetWindowProperty(osd->dpy, osd->root, gnome, 0, 16384, False, AnyPropertyType,
+ &type, &format, &nitems, &bytes_after, &prop) == Success &&
+ nitems > 0)
+ {
+ DBG("stay_on_top: Gnome mode\n");
+ // FIXME: check capabilities
+ XClientMessageEvent e;
+ memset(&e, 0, sizeof(e));
+ e.type = ClientMessage;
+ e.window = osd->win;
+ e.message_type = XInternAtom(osd->dpy, "_WIN_LAYER", False);
+ e.format = 32;
+ e.data.l[0] = 6; // WIN_LAYER_ONTOP */
+ XSendEvent(osd->dpy, osd->root, False, SubstructureNotifyMask, (XEvent *) &e);
+ XFree(prop);
+ return;
+ }
+
+ // NetWM-compliant way
+ Atom net_wm = XInternAtom(osd->dpy, "_NET_SUPPORTED", False);
+ if (XGetWindowProperty(osd->dpy, osd->root, net_wm, 0, 16384, False, AnyPropertyType,
+ &type, &format, &nitems, &bytes_after, &prop) == Success &&
+ nitems > 0)
+ {
+ DBG("stay_on_top: NetWM mode\n");
+ XEvent e;
+ memset(&e, 0, sizeof(e));
+ e.xclient.type = ClientMessage;
+ e.xclient.message_type = XInternAtom(osd->dpy, "_NET_WM_STATE", False);
+ e.xclient.display = osd->dpy;
+ e.xclient.window = osd->win;
+ e.xclient.format = 32;
+ e.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
+ e.xclient.data.l[1] = XInternAtom(osd->dpy, "_NET_WM_STATE_STAYS_ON_TOP", False);
+ XSendEvent(osd->dpy, osd->root, False, SubstructureRedirectMask, &e);
+ XFree(prop);
+ return;
+ }
+
+ DBG("stay_on_top: WM does not support any known protocol\n");
+}
+
+struct osd_state *osd_new(Display *dpy)
+{
+ struct osd_state *osd = xmalloc(sizeof(*osd));
+ memset(osd, 0, sizeof(*osd));
+
+ osd->dpy = dpy;
+ osd->screen = XDefaultScreen(osd->dpy);
+ osd->visual = XDefaultVisual(osd->dpy, osd->screen);
+ osd->cmap = DefaultColormap(osd->dpy, osd->screen);
+ osd->root = DefaultRootWindow(osd->dpy);
+
+ // FIXME: These can change. And what about Xinerama?
+ osd->depth = XDefaultDepth(osd->dpy, osd->screen);
+ osd->screen_width = XDisplayWidth(osd->dpy, osd->screen);
+ osd->screen_height = XDisplayHeight(osd->dpy, osd->screen);
+ DBG("Screen: %dx%d depth %d\n", osd->screen_width, osd->screen_height, osd->depth);
+
+ int event_basep, error_basep;
+ if (!XShapeQueryExtension(osd->dpy, &event_basep, &error_basep))
+ die("XShape extension not supported by X server, giving up");
+
+ osd->max_lines = 2; // FIXME
+ osd->lines = xmalloc(sizeof(struct osd_line) * osd->max_lines);
+
+ return osd;
+}
+
+void osd_free(struct osd_state *osd)
+{
+ osd_hide(osd);
+
+ if (osd->font)
+ XftFontClose(osd->dpy, osd->font);
+
+ free(osd->lines);
+ free(osd);
+}
+
+void osd_set_font(struct osd_state *osd, char *font_name)
+{
+ if (osd->font)
+ XftFontClose(osd->dpy, osd->font);
+
+ DBG("Using font %s", font_name);
+ osd->font = XftFontOpenName(osd->dpy, osd->screen, font_name);
+ if (!osd->font)
+ die("Cannot open font %s", font_name);
+ DBG("Font: asc=%d desc=%d ht=%d", osd->font->ascent, osd->font->descent, osd->font->height);
+
+ osd->line_distance = osd->font->height;
+ osd->line_height = osd->font->ascent;
+}
+
+struct osd_line *osd_add_line(struct osd_state *osd, enum osd_line_type type)
+{
+ if (osd->num_lines >= osd->max_lines)
+ {
+ osd->max_lines = 2 * osd->max_lines;
+ osd->lines = xrealloc(osd->lines, sizeof(struct osd_line) * osd->max_lines);
+ }
+
+ struct osd_line *l = &osd->lines[osd->num_lines++];
+ l->type = type;
+ l->fg_color = "green";
+ l->outline_color = "yellow";
+ l->outline_width = 0;
+ // FIXME: Colors, alignment etc.
+
+ switch (l->type)
+ {
+ case OSD_TYPE_TEXT:
+ l->u.text[0] = 0;
+ break;
+ default:
+ die("osd_add_line: unknown type %d", type);
+ }
+
+ return l;
+}
+
+static void osd_prepare_line(struct osd_state *osd, int i)
+{
+ struct osd_line *line = &osd->lines[i];
+ switch (line->type)
+ {
+ case OSD_TYPE_TEXT:
+ {
+ XGlyphInfo gi;
+ XftTextExtentsUtf8(osd->dpy, osd->font, (unsigned char *) line->u.text, strlen(line->u.text), &gi);
+ 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);
+ line->width = gi.width + 2*line->outline_width;
+ line->height = osd->line_distance + 2*line->outline_width;
+ break;
+ }
+ default:
+ die("osd_recalc_line: unknown type %d", line->type);
+ }
+
+ DBG("Line #%d: Width %d (outline %d)\n", i, line->width, line->outline_width);
+}
+
+static void osd_justify_line(struct osd_state *osd, int i)
+{
+ // FIXME: Support more modes of justification
+ struct osd_line *line = &osd->lines[i];
+ line->x_pos = (osd->win_width - line->width) / 2;
+ DBG("Line #%d: Position (%d,%d)\n", i, line->x_pos, line->y_pos);
+}
+
+static void osd_prepare(struct osd_state *osd)
+{
+ osd->win_width = 0;
+ osd->win_height = 0;
+ for (int i=0; i < osd->num_lines; i++)
+ {
+ struct osd_line *line = &osd->lines[i];
+ osd_prepare_line(osd, i);
+ if (line->width > osd->win_width)
+ osd->win_width = line->width;
+ osd->win_height += line->height;
+ }
+
+ // FIXME: Check clipping
+ if (osd->win_width > osd->screen_width)
+ osd->win_width = osd->screen_width;
+ if (osd->win_height > osd->screen_height)
+ osd->win_height = osd->screen_height;
+ DBG("Window size set to %dx%d\n", osd->win_width, osd->win_height);
+
+ int y = 0;
+ for (int i=0; i < osd->num_lines; i++)
+ {
+ struct osd_line *line = &osd->lines[i];
+ line->y_pos = y;
+ osd_justify_line(osd, i);
+ y += line->height;
+ }
+}
+
+static void osd_draw_line(struct osd_state *osd, int i)
+{
+ struct osd_line *line = &osd->lines[i];
+
+ // Allocate colors
+ XftColor fg_color, outline_color, mask_color;
+ XRenderColor mask_rc = { .red = 0xffff, .green = 0xffff, .blue = 0xffff, .alpha = 0xffff };
+ if (!XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->fg_color, &fg_color) ||
+ !XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->outline_color, &outline_color) ||
+ !XftColorAllocValue(osd->dpy, osd->visual, osd->cmap, &mask_rc, &mask_color))
+ die("Cannot allocate colors");
+
+ // Draw background in outline color
+ XftDrawRect(osd->image_draw, &outline_color, 0, line->y_pos, osd->win_width, line->height);
+
+ switch (line->type)
+ {
+ case OSD_TYPE_TEXT:
+ {
+ unsigned char *text = (unsigned char *) line->u.text;
+ int text_len = strlen(line->u.text);
+ XftDrawStringUtf8(osd->image_draw, &fg_color, osd->font, line->x_pos + line->outline_width, line->y_pos + line->outline_width, text, text_len);
+
+ // This is slow, but unlike the method used by libxosd, the result isn't ugly.
+ int outline = line->outline_width;
+ for (int dx = -outline; dx <= outline; dx++)
+ for (int dy = -outline; dy <= outline; dy++)
+ if (dx*dx + dy*dy <= outline*outline)
+ XftDrawStringUtf8(osd->mask_draw, &mask_color, osd->font, 100 + dx, 100 + dy, text, text_len);
+
+ break;
+ }
+ default:
+ die("osd_draw_line: unknown type %d", line->type);
+ }
+
+ XftColorFree(osd->dpy, osd->visual, osd->cmap, &fg_color);
+ XftColorFree(osd->dpy, osd->visual, osd->cmap, &outline_color);
+ XftColorFree(osd->dpy, osd->visual, osd->cmap, &mask_color);
+}
+
+void osd_show(struct osd_state *osd)
+{
+ osd_hide(osd);
+ osd_prepare(osd);
+
+ // Create our window
+ XSetWindowAttributes win_attr = {
+ .override_redirect = 1,
+ };
+ osd->win = XCreateWindow(osd->dpy,
+ osd->root,
+ 0, 0,
+ osd->win_width, osd->win_height,
+ 0,
+ osd->depth,
+ CopyFromParent,
+ osd->visual,
+ CWOverrideRedirect,
+ &win_attr);
+ XStoreName(osd->dpy, osd->win, "OSD");
+ stay_on_top(osd);
+
+ // Create image pixmap and its graphic context
+ // osd->gc can be used for both osd->win and osd->image_bitmap as they have the same root and depth
+ osd->image_pixmap = XCreatePixmap(osd->dpy, osd->win, osd->win_width, osd->win_height, osd->depth);
+ XGCValues gcv = {
+ .graphics_exposures = 0,
+ };
+ osd->gc = XCreateGC(osd->dpy, osd->win, GCGraphicsExposures, &gcv);
+
+ // Create mask bitmap and its GC
+ osd->mask_bitmap = XCreatePixmap(osd->dpy, osd->win, osd->win_width, osd->win_height, 1);
+ osd->mask_gc = XCreateGC(osd->dpy, osd->mask_bitmap, GCGraphicsExposures, &gcv);
+
+ // Clear the mask bitmap
+ XSetBackground(osd->dpy, osd->mask_gc, WhitePixel(osd->dpy, osd->screen));
+ XSetForeground(osd->dpy, osd->mask_gc, BlackPixel(osd->dpy, osd->screen));
+ XFillRectangle(osd->dpy, osd->mask_bitmap, osd->mask_gc, 0, 0, osd->win_width, osd->win_height);
+
+ // Create XftDraw for mask and image
+ osd->mask_draw = XftDrawCreateBitmap(osd->dpy, osd->mask_bitmap);
+ osd->image_draw = XftDrawCreate(osd->dpy, osd->image_pixmap, osd->visual, osd->cmap);
+ if (!osd->mask_draw || !osd->image_draw)
+ die("Cannot create XftDraw");
+
+ // Draw individial lines
+ for (int i=0; i < osd->num_lines; i++)
+ osd_draw_line(osd, i);
+
+ XShapeCombineMask(osd->dpy, osd->win, ShapeBounding, 0, 0, osd->mask_bitmap, ShapeSet);
+
+ XSelectInput(osd->dpy, osd->win, ExposureMask);
+ XMapRaised(osd->dpy, osd->win);
+ XFlush(osd->dpy);
+
+ osd->visible = 1;
+}
+
+void osd_hide(struct osd_state *osd)
+{
+ if (!osd->visible)
+ return;
+
+ XftDrawDestroy(osd->image_draw);
+ XftDrawDestroy(osd->mask_draw);
+ XFreeGC(osd->dpy, osd->gc);
+ XFreeGC(osd->dpy, osd->mask_gc);
+ XFreePixmap(osd->dpy, osd->image_pixmap);
+ XFreePixmap(osd->dpy, osd->mask_bitmap);
+ XDestroyWindow(osd->dpy, osd->win);
+
+ osd->visible = 0;
+}
+
+bool osd_handle_event(struct osd_state *osd, XEvent *ev)
+{
+ if (!osd->visible)
+ return 0;
+
+ if (ev->type == Expose)
+ {
+ XExposeEvent *ex = &ev->xexpose;
+ if (ex->window == osd->win)
+ {
+ DBG("Expose cnt=%d (%d,%d)+(%d,%d)\n", ex->count, ex->x, ex->y, ex->width, ex->height);
+ XCopyArea(osd->dpy, osd->image_pixmap, osd->win, osd->gc, ex->x, ex->y, ex->width, ex->height, ex->x, ex->y);
+ return 1;
+ }
+ }
+
+ return 0;
+}
/*
* On-screen Display Daemon
*
- * (c) 2010 Martin Mares <mj@ucw.cz>
+ * (c) 2010--2013 Martin Mares <mj@ucw.cz>
*/
#include <stdio.h>
#include "util.h"
#include "display.h"
-static xosd *osd;
+static struct osd_state *osd;
static timestamp_t now;
/*** Options ***/
-static int num_lines = 4;
-static char *font_name = "-bitstream-bitstream vera sans-bold-r-normal-*-*-320-*-*-p-*-*";
+static int num_lines = 4; // FIXME
+static char *font_name = "-bitstream-bitstream vera sans-bold-r-normal-*-*-320-*-*-p-*-*"; // FIXME
static char *default_color = "green";
static char *default_outline_color = "black";
static int default_duration = 1000;
DBG("## Displaying message\n");
msg->min_light = now + default_min_duration;
msg->max_light = now + default_duration;
- xosd_set_colour(osd, default_color);
- xosd_set_outline_colour(osd, default_outline_color);
+ char *fg_color = default_color;
+ char *outline_color = default_outline_color;
char *line = msg->text;
- int row = 0;
while (*line)
{
// The parser it destructive, but it does not do any harm, since we display each message only once.
}
DBG("\t%s:%s\n", key, val);
+ struct osd_line *l = NULL;
if (!key[0])
{
- if (row < num_lines)
- xosd_display(osd, row++, XOSD_string, val);
+ l = osd_add_line(osd, OSD_TYPE_TEXT);
+ sprintf(l->u.text, "%.*s", OSD_MAX_LINE_LEN, val);
}
else if (!strcmp(key, "percentage") || !strcmp(key, "percent"))
{
- if (row < num_lines)
- xosd_display(osd, row++, XOSD_percentage, atoi(val));
+ // FIXME
+ // xosd_display(osd, row++, XOSD_percentage, atoi(val));
}
else if (!strcmp(key, "slider"))
{
- if (row < num_lines)
- xosd_display(osd, row++, XOSD_slider, atoi(val));
+ // FIXME
+ // xsd_display(osd, row++, XOSD_slider, atoi(val));
}
else if (!strcmp(key, "duration"))
msg->max_light = now + atoi(val);
else if (!strcmp(key, "min-duration"))
msg->min_light = now + atoi(val);
else if (!strcmp(key, "color"))
- xosd_set_colour(osd, val);
+ fg_color = val; // FIXME: Need copying!
else if (!strcmp(key, "outline-color"))
- xosd_set_outline_colour(osd, val);
- else
- xosd_display(osd, (row < num_lines ? row++ : num_lines-1), XOSD_string, "PARSE ERROR");
+ outline_color = val; // FIXME: Need copying!
+
+ if (l)
+ {
+ l->fg_color = fg_color;
+ l->outline_color = outline_color;
+ }
line = nl;
}
hide_msg(struct msg *msg)
{
DBG("## Hiding message\n");
- for (int i=0; i<num_lines; i++)
- xosd_display(osd, i, XOSD_string, "");
- xosd_hide(osd);
+ osd_hide(osd);
+ // FIXME: Reset the osd state
free(msg);
}
XDeleteProperty(dpy, win, pty);
XFlush(dpy);
- osd = xosd_create(num_lines);
- if (!osd)
- die("Cannot initialize OSD");
- xosd_set_font(osd, font_name);
- xosd_set_outline_offset(osd, 2);
- xosd_set_pos(osd, XOSD_middle);
- xosd_set_align(osd, XOSD_center);
+ osd = osd_new(dpy);
+ osd_set_font(osd, font_name);
struct pollfd pfd = {
.fd = ConnectionNumber(dpy),
{
XEvent ev;
XNextEvent(dpy, &ev);
+ if (osd_handle_event(osd, &ev))
+ continue;
if (ev.type != PropertyNotify)
continue;
XPropertyEvent *p = &ev.xproperty;