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

osdd.c

Blame
  • osdd.c 8.78 KiB
    /*
     *	On-screen Display Daemon
     *
     *	(c) 2010--2014 Martin Mares <mj@ucw.cz>
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <poll.h>
    #include <getopt.h>
    #include <locale.h>
    #include <X11/Xlib.h>
    #include <X11/Xatom.h>
    
    #undef DEBUG
    #include "util.h"
    #include "display.h"
    
    static struct osd_state *osd;
    
    static timestamp_t now;
    
    /*** Options ***/
    
    static char *font_name = "times-64:bold";
    static char *default_color = "green";
    static char *default_outline_color = "black";
    static int default_outline_width = 2;
    static int default_duration = 1000;
    static int default_min_duration = 250;
    static int debug_mode;
    static int test_mode;
    static double line_spacing = 0.2;
    
    static const char short_opts[] = "c:d:Df:m:o:O:s:";
    
    enum long_opt {
      OPT_TEST = 256,
    };
    
    static const struct option long_opts[] = {
      { "color",		required_argument,	NULL,	'c' },
      { "debug",		no_argument,		NULL,	'D' },
      { "duration",		required_argument,	NULL,	'd' },
      { "font",		required_argument,	NULL,	'f' },
      { "min-duration",	required_argument,	NULL,	'm' },
      { "outline-color",	required_argument,	NULL,	'o' },
      { "outline-width",	required_argument,	NULL,	'O' },
      { "line-spacing",	required_argument,	NULL,	's' },
      { "test",		no_argument,		NULL,	OPT_TEST },	// Undocumented test mode
      { NULL,		0,			NULL,	0   },
    };
    
    static void NONRET
    usage(void)
    {
      fprintf(stderr, "Usage: osdd <options>\n\n\
    Options:\n\
    -c, --color=<c>\t\tDefault color (#rgb, #rrggbb or a name from rgb.txt)\n\
    -D, --debug\t\tDebugging mode (do not detach from the terminal)\n\
    -d, --duration=<ms>\tDefault message duration in milliseconds\n\
    -f, --font=<f>\t\tFont to use for the OSD\n\
    -m, --min-duration=<ms>\tDefault minimum message duration in milliseconds\n\
    -o, --outline-color=<c>\tDefault outline color\n\
    -O, --outline-width=<n>\tDefault outline width (default=2)\n\
    -s, --line-spacing=<n>\tSet line spacing factor (decimal fraction, default=0.2)\n\
    ");
      exit(1);
    }
    
    static void
    parse_opts(int argc, char **argv)
    {
      int opt;
      while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
        switch (opt)
          {
          case 'c':
    	default_color = optarg;
    	break;
          case 'd':
    	default_duration = atoi(optarg);
    	break;
          case 'D':
    	debug_mode = 1;
    	break;
          case 'f':
    	font_name = optarg;
    	break;
          case 'l':
    	line_spacing = atof(optarg);
    	break;
          case 'm':
    	default_min_duration = atoi(optarg);
    	break;
          case 'o':
    	default_outline_color = optarg;
    	break;
          case 'O':
    	default_outline_width = atoi(optarg);
    	break;
          case OPT_TEST:
    	test_mode = 1;
    	debug_mode = 1;
    	break;
          default:
    	usage();
          }
    
      if (optind < argc)
        usage();
    }
    
    /*** Displaying of messages ***/
    
    struct msg {
      struct msg *next;
      timestamp_t min_light, max_light;
      char text[1];
    };
    
    static void
    display_msg(struct msg *msg)
    {
      DBG("## Displaying message\n");
      msg->min_light = now + default_min_duration;
      msg->max_light = now + default_duration;
      char *fg_color = default_color;
      char *outline_color = default_outline_color;
      int outline_width = default_outline_width;
    
      char *line = msg->text;
      while (*line)
        {
          // The parser it destructive, but it does not do any harm, since we display each message only once.
          char *nl = strchr(line, '\n');
          *nl++ = 0;
    
          char *key;
          char *val = strchr(line, ':');
          if (val)
    	{
    	  key = line;
    	  *val++ = 0;
    	}
          else
    	{
    	  key = "";
    	  val = line;
    	}
          DBG("\t%s:%s\n", key, val);
    
          struct osd_line *l = NULL;
          if (!key[0])
    	{
    	  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"))
    	{
    	  l = osd_add_line(osd, OSD_TYPE_PERCENTAGE);
    	  l->u.percent = atoi(val);
    	}
          else if (!strcmp(key, "slider"))
    	{
    	  l = osd_add_line(osd, OSD_TYPE_SLIDER);
    	  l->u.percent = 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"))
    	fg_color = val;
          else if (!strcmp(key, "outline-color"))
    	outline_color = val;
          else if (!strcmp(key, "outline-width"))
    	outline_width = atoi(val);
          if (l)
    	{
    	  l->fg_color = fg_color;
    	  l->outline_color = outline_color;
    	  l->outline_width = outline_width;
    	}
    
          line = nl;
        }
    
      if (msg->min_light > msg->max_light)
        msg->min_light = msg->max_light;
    
      osd_show(osd);
    }
    
    static void
    msg_to_stdout(struct msg *msg)
    {
      DBG("## Printing message to stdout\n");
    
      char *line = msg->text;
      int out_parametr_index=0;
      while (*line)
        {
          // The parser it destructive, but it does not do any harm, since we display each message only once.
          char *nl = strchr(line, '\n');
          *nl++ = 0;
    
          char *key;
          char *val = strchr(line, ':');
          if (val)
    	{
    	  key = line;
    	  *val++ = 0;
    	}
          else
    	{
    	  key = "";
    	  val = line;
    	}
          DBG("\t%s:%s\n", key, val);
    
          if (!key[0])
    	{
    		printf(out_parametr_index++?" %s":"%s",val);
    	}
    
          line = nl;
        }
    	printf("\n");
    	fflush(stdout);
    }
    
    static void
    hide_msg(struct msg *msg)
    {
      DBG("## Hiding message\n");
      osd_clear(osd);
      free(msg);
    }
    
    /*** The message queue ***/
    
    static struct msg *current_msg, *first_msg, *last_msg;
    
    static void
    enqueue_msg(unsigned char *buf, int len)
    {
      DBG("Received: [%.*s]\n", len, buf);
      if (!len || buf[len-1] != '\n')
        return;
    
      struct msg *msg = xmalloc(sizeof(*msg) + len);
      memcpy(msg->text, buf, len);
      msg->text[len] = 0;
    
      char to[123];
      *to=0;
    
      char *line = msg->text;
      while (*line)
        {
          char *nl = strchr(line, '\n');
          *nl++=0;
    
          char *key;
          char *val = strchr(line, ':');
          if (val)
    	{
    	  key = line;
    	  *val++ = 0;
    	}
          else
    	{
    	  key = "";
    	  val = line;
    	}
          DBG("\t%s:%s\n", key, val);
    
          if (!strcmp(key, "to"))
          {
    	      if(strlen(val)<120)
    		      strcpy(to,val);
          }
    
          val[-1]=':';
          nl[-1]=10;
          line = nl;
        }
      if(!strcmp(to,"stdout"))
      {
    	  msg_to_stdout(msg);
      }
      else
      {
    	  if (first_msg)
    	    last_msg->next = msg;
    	  else
    	    first_msg = msg;
    	  last_msg = msg;
    	  msg->next = NULL;
      }
    }
    
    static void
    parse_input(unsigned char *buf, int len)
    {
      /* The property might contain several messages concatenated. Split them. */
      while (len > 0)
        {
          if (buf[0] == '\n')
    	{
    	  buf++, len--;
    	  continue;
    	}
          int i = 0;
          while (i < len && (buf[i] != '\n' || (i && buf[i-1] != '\n')))
    	i++;
          enqueue_msg(buf, i);
          buf += i, len -= i;
        }
    }
    
    static void
    do_test(void)
    {
      unsigned char buf[4096];
      int len = 0;
      int c;
    
      while ((c = read(0, buf + len, 4096 - len)) > 0)
        len += c;
      if (len)
        enqueue_msg(buf, len);
    }
    
    /*** Main loop ***/
    
    int
    main(int argc, char **argv)
    {
      parse_opts(argc, argv);
      setlocale(LC_CTYPE, "");
      XInitThreads();
    
      Display *dpy = XOpenDisplay(NULL);
      if (!dpy)
        die("Cannot open display");
      Window win = DefaultRootWindow(dpy);
    
      Atom pty = XInternAtom(dpy, "OSD_QUEUE", False);
      if (!pty)
        die("Cannot intern OSD_QUEUE atom");
    
      if (!debug_mode)
        {
          pid_t pid = fork();
          if (pid < 0)
    	die("Cannot fork: %m");
          if (pid > 0)
            return 0;
          setsid();
        }
    
      if (test_mode)
        {
          do_test();
          pty = 0;
        }
      else
        {
          XSelectInput(dpy, win, PropertyChangeMask);
          XDeleteProperty(dpy, win, pty);
          XFlush(dpy);
        }
    
      osd = osd_new(dpy);
      osd_set_font(osd, font_name, line_spacing);
    
      struct pollfd pfd = {
        .fd = ConnectionNumber(dpy),
        .events = POLLIN,
      };
    
      for (;;)
        {
          now = get_current_time();
    
          timestamp_t wait_until = now - 1;
          if (!current_msg && first_msg)
    	{
    	  current_msg = first_msg;
    	  first_msg = first_msg->next;
    	  display_msg(current_msg);
    	}
          if (current_msg)
    	{
    	  if (first_msg)
    	    wait_until = current_msg->min_light;
    	  else
    	    wait_until = current_msg->max_light;
    	  if (wait_until <= now)
    	    {
    	      hide_msg(current_msg);
    	      current_msg = NULL;
    	      continue;
    	    }
    	}
          if (test_mode && !current_msg)
    	break;
    
          DBG("... waiting for %d ms\n", (int)(wait_until - now));
          poll(&pfd, 1, wait_until - now);
          if (pfd.revents & POLLIN)
    	{
    	  while (XPending(dpy))
    	    {
    	      XEvent ev;
    	      XNextEvent(dpy, &ev);
    	      if (osd_handle_event(osd, &ev))
    		continue;
    	      if (ev.type != PropertyNotify)
    		continue;
    	      XPropertyEvent *p = &ev.xproperty;
    	      if (p->window == win && p->atom == pty)
    		{
    		  Atom pty_type;
    		  int pty_fmt;
    		  unsigned long pty_items, pty_remains;
    		  unsigned char *pty_buf = NULL;
    		  XGetWindowProperty(dpy, win, pty, 0, 4096, True, XA_STRING, &pty_type, &pty_fmt, &pty_items, &pty_remains, &pty_buf);
    		  if (pty_type == XA_STRING && pty_fmt == 8 && pty_items)
    		    parse_input(pty_buf, pty_items);
    		  if (pty_buf)
    		    XFree(pty_buf);
    		}
    	    }
    	}
        }
    }