]> mj.ucw.cz Git - osdd.git/commitdiff
Bits of new display code
authorMartin Mares <mj@ucw.cz>
Sat, 12 Oct 2013 20:58:45 +0000 (22:58 +0200)
committerMartin Mares <mj@ucw.cz>
Sat, 12 Oct 2013 20:58:45 +0000 (22:58 +0200)
Makefile
display.c [new file with mode: 0644]
display.h [new file with mode: 0644]
osdd.c
test.c

index 31eeafbb92aca633f1b1fe85ea5b34c6cb1b17af..3e7319cac0f97094ee837791a7517db2a833012a 100644 (file)
--- 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 (file)
index 0000000..1294db7
--- /dev/null
+++ b/display.c
@@ -0,0 +1,382 @@
+/*
+ *     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;
+}
diff --git a/display.h b/display.h
new file mode 100644 (file)
index 0000000..fba43df
--- /dev/null
+++ b/display.h
@@ -0,0 +1,41 @@
+/*
+ *     On-screen Display
+ *
+ *     (c) 2013 Martin Mares <mj@ucw.cz>
+ */
+
+#include <stdbool.h>
+#include <X11/Xlib.h>
+
+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 f37efde933c234ff680ac57f0e804ad000a75847..8626cbd8f96ea276a7a793e84937784bc271498c 100644 (file)
--- a/osdd.c
+++ b/osdd.c
@@ -1,7 +1,7 @@
 /*
  *     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;
@@ -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; i<num_lines; i++)
-    xosd_display(osd, i, XOSD_string, "");
-  xosd_hide(osd);
+  osd_hide(osd);
+  // FIXME: Reset the osd state
   free(msg);
 }
 
@@ -256,13 +259,8 @@ main(int argc, char **argv)
   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),
@@ -302,6 +300,8 @@ main(int argc, char **argv)
            {
              XEvent ev;
              XNextEvent(dpy, &ev);
+             if (osd_handle_event(osd, &ev))
+               continue;
              if (ev.type != PropertyNotify)
                continue;
              XPropertyEvent *p = &ev.xproperty;
diff --git a/test.c b/test.c
index 5d48d8d0f726f74148ec73eec028676369baefa0..ced576fe564ce03b98d6e32ccad76ad5d61d0b68 100644 (file)
--- a/test.c
+++ b/test.c
@@ -155,7 +155,7 @@ main(int argc, char **argv)
   osd->font = 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)