--- /dev/null
+/*
+ * On-screen Display Daemon
+ *
+ * (c) 2010 Martin Mares <mj@ucw.cz>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <sys/time.h>
+#include <xosd.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#define DEBUG
+#include "util.h"
+
+static xosd *osd;
+#define MAX_LINES 4
+
+typedef uint64_t timestamp_t;
+static timestamp_t now;
+
+/*** Displaying of messages ***/
+
+struct msg {
+ struct msg *next;
+ timestamp_t min_light, max_light;
+ char text[1];
+};
+
+static void
+display_msg(struct msg *msg)
+{
+ msg->min_light = msg->max_light = now + 1000;
+
+ char *line = msg->text;
+ int row = 0;
+ while (*line)
+ {
+ // The parser it destructive, but it does not 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;
+ }
+
+ if (!key[0])
+ {
+ if (row < MAX_LINES)
+ xosd_display(osd, row++, XOSD_string, val);
+ }
+ else if (!strcmp(key, "duration"))
+ msg->max_light = now + atoi(val);
+ else if (!strcmp(key, "min-duration"))
+ msg->min_light = now + atoi(val);
+
+ line = nl;
+ }
+
+ if (msg->min_light > msg->max_light)
+ msg->min_light = msg->max_light;
+}
+
+static void
+hide_msg(struct msg *msg)
+{
+ xosd_scroll(osd, MAX_LINES);
+ xosd_hide(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("[%.*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;
+
+ 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;
+ }
+}
+
+/*** Main loop ***/
+
+int
+main(void)
+{
+ 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");
+
+ XSelectInput(dpy, win, PropertyChangeMask);
+ XDeleteProperty(dpy, win, pty);
+ XFlush(dpy);
+
+ osd = xosd_create(4);
+ if (!osd)
+ die("Cannot initialize OSD");
+ xosd_set_font(osd, "-bitstream-bitstream vera sans-bold-r-normal-*-*-320-*-*-p-*-*");
+ xosd_set_outline_offset(osd, 2);
+ xosd_set_outline_colour(osd, "black");
+ xosd_set_pos(osd, XOSD_middle);
+ xosd_set_align(osd, XOSD_center);
+
+ struct pollfd pfd = {
+ .fd = ConnectionNumber(dpy),
+ .events = POLLIN,
+ };
+
+ for (;;)
+ {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ now = (timestamp_t) tv.tv_sec * 1000 + tv.tv_usec / 1000;
+
+ 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;
+ }
+ }
+
+ 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 (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)
+ {
+ DBG("Received: <%.*s>\n", (int) pty_items, pty_buf);
+ parse_input(pty_buf, pty_items);
+ }
+ if (pty_buf)
+ XFree(pty_buf);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * On-screen Display Client -- Sending Messages
+ *
+ * (c) 2010 Martin Mares <mj@ucw.cz>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include "util.h"
+#include "send.h"
+
+static Display *dpy;
+static Atom pty;
+
+#define MAX_MSG_SIZE 1024
+
+struct osd_msg {
+ int cnt;
+ char buf[MAX_MSG_SIZE];
+};
+
+void
+osd_init(void)
+{
+ dpy = XOpenDisplay(NULL);
+ if (!dpy)
+ die("Cannot open display");
+
+ pty = XInternAtom(dpy, "OSD_QUEUE", False);
+ if (!pty)
+ die("Cannot intern OSD_QUEUE atom");
+}
+
+struct osd_msg *
+osd_new_msg(void)
+{
+ struct osd_msg *msg = xmalloc(sizeof(*msg));
+ msg->cnt = 0;
+ return msg;
+}
+
+void
+osd_add_line(struct osd_msg *msg, char *key, char *val)
+{
+ if (!key)
+ key = "";
+ msg->cnt += snprintf(msg->buf + msg->cnt, MAX_MSG_SIZE - msg->cnt - 1, "%s:%s\n", key, val);
+ if (msg->cnt > MAX_MSG_SIZE - 1)
+ die("OSD message too long (at most %d bytes)", MAX_MSG_SIZE);
+}
+
+void
+osd_send(struct osd_msg *msg)
+{
+ if (!dpy)
+ osd_init();
+
+ msg->buf[msg->cnt++] = '\n';
+ if (!XChangeProperty(dpy, DefaultRootWindow(dpy), pty, XA_STRING, 8, PropModeAppend, (unsigned char *) msg->buf, msg->cnt))
+ die("XChangeProperty failed");
+ XFlush(dpy);
+ free(msg);
+}