Skip to content
Snippets Groups Projects
Select Git revision
  • 2ab1559fea8926db5550be13cba7e4dc3c9ac92d
  • jk default protected
2 results

display.c

Blame
  • display.c 10.78 KiB
    /*
     *	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;
    }