From: Martin Mares Date: Sat, 12 Oct 2013 20:58:45 +0000 (+0200) Subject: Bits of new display code X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=2ab1559fea8926db5550be13cba7e4dc3c9ac92d;p=osdd.git Bits of new display code --- diff --git a/Makefile b/Makefile index 31eeafb..3e7319c 100644 --- a/Makefile +++ b/Makefile @@ -3,16 +3,14 @@ ARCHIVE=osdd-$(VERSION).tar.gz CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 -# all: osdd osdc osd-batt osd-alsa -all: test +all: osdd osdc osd-batt osd-alsa -osdd: osdd.o util.o +osdd: osdd.o util.o display.o osdc: osdc.o util.o client.o osd-batt: osd-batt.o util.o client.o osd-alsa: osd-alsa.o util.o client.o -osdd.o: CFLAGS+=$(shell xosd-config --cflags) -osdd: LDLIBS+=$(shell xosd-config --libs) +osdd: LDLIBS+=$(shell pkg-config --libs xft) -l Xext osdc: LDLIBS+=-lX11 osd-batt: LDLIBS+=-lX11 @@ -20,9 +18,7 @@ osd-batt: LDLIBS+=-lX11 osd-alsa.o: CFLAGS+=$(shell pkg-config --cflags alsa) osd-alsa: LDLIBS+=$(shell pkg-config --libs alsa) -lX11 -test.o: CFLAGS+=$(shell pkg-config --cflags xft) -test: LDFLAGS+=$(shell pkg-config --libs xft) -l Xext -test: test.o util.o +display.o: CFLAGS+=$(shell pkg-config --cflags xft) clean: rm -f *~ *.o TAGS core osdd osdc osd-batt osd-alsa diff --git a/display.c b/display.c new file mode 100644 index 0000000..1294db7 --- /dev/null +++ b/display.c @@ -0,0 +1,382 @@ +/* + * On-screen Display + * + * (c) 2013 Martin Mares + * + * This code is heavily inspired by the libxosd library, + * which is (c) 2000, 2001 Andre Renaud . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/display.h b/display.h new file mode 100644 index 0000000..fba43df --- /dev/null +++ b/display.h @@ -0,0 +1,41 @@ +/* + * On-screen Display + * + * (c) 2013 Martin Mares + */ + +#include +#include + +struct osd_state; + +#define OSD_MAX_LINE_LEN 256 + +enum osd_line_type { + OSD_TYPE_TEXT, + OSD_TYPE_PERCENTAGE, + OSD_TYPE_SLIDER, +}; + +struct osd_line { + enum osd_line_type type; + char *fg_color; + char *outline_color; + int outline_width; + union { // Data dependent on type + char text[OSD_MAX_LINE_LEN]; // in UTF-8 + } u; + + int width; + int height; + int x_pos; + int y_pos; +}; + +struct osd_state *osd_new(Display *dpy); +void osd_free(struct osd_state *osd); +void osd_set_font(struct osd_state *osd, char *font_name); +struct osd_line *osd_add_line(struct osd_state *osd, enum osd_line_type type); +void osd_show(struct osd_state *osd); +void osd_hide(struct osd_state *osd); +bool osd_handle_event(struct osd_state *osd, XEvent *ev); diff --git a/osdd.c b/osdd.c index f37efde..8626cbd 100644 --- a/osdd.c +++ b/osdd.c @@ -1,7 +1,7 @@ /* * On-screen Display Daemon * - * (c) 2010 Martin Mares + * (c) 2010--2013 Martin Mares */ #include @@ -19,14 +19,14 @@ #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; @@ -114,11 +114,10 @@ display_msg(struct msg *msg) 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. @@ -139,31 +138,36 @@ display_msg(struct msg *msg) } 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; } @@ -176,9 +180,8 @@ static void hide_msg(struct msg *msg) { DBG("## Hiding message\n"); - for (int i=0; ifont = XftFontOpenName(osd->dpy, osd->screen, "times-64"); if (!osd->font) die("Cannot open font"); - DBG("Font: asc=%d desc=%d ht=%d", osd->font->ascent, osd->font->descent, osd->font->height); + DBG("Font: asc=%d desc=%d ht=%d\n", osd->font->ascent, osd->font->descent, osd->font->height); osd->mask_draw = XftDrawCreateBitmap(osd->dpy, osd->mask_bitmap); if (!osd->mask_draw)