]> mj.ucw.cz Git - osdd.git/blob - osdd.c
Report parse errors
[osdd.git] / osdd.c
1 /*
2  *      On-screen Display Daemon
3  *
4  *      (c) 2010 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <inttypes.h>
11 #include <poll.h>
12 #include <getopt.h>
13 #include <sys/time.h>
14 #include <xosd.h>
15 #include <X11/Xlib.h>
16 #include <X11/Xatom.h>
17
18 #define DEBUG
19 #include "util.h"
20
21 static xosd *osd;
22
23 typedef uint64_t timestamp_t;
24 static timestamp_t now;
25
26 /*** Options ***/
27
28 static int num_lines = 4;
29 static char *font_name = "-bitstream-bitstream vera sans-bold-r-normal-*-*-320-*-*-p-*-*";
30 static char *default_color = "green";
31 static char *default_outline_color = "black";
32 static int default_duration = 1000;
33 static int default_min_duration = 1000;
34
35 static const char short_opts[] = "c:d:f:l:m:o:";
36
37 static const struct option long_opts[] = {
38   { "color",            required_argument,      NULL,   'c' },
39   { "duration",         required_argument,      NULL,   'd' },
40   { "font",             required_argument,      NULL,   'f' },
41   { "lines",            required_argument,      NULL,   'l' },
42   { "min-duration",     required_argument,      NULL,   'm' },
43   { "outline-color",    required_argument,      NULL,   'o' },
44   { NULL,       0,                      NULL,   0   },
45 };
46
47 static void NONRET
48 usage(void)
49 {
50   fprintf(stderr, "Usage: osdd <options>\n\n\
51 Options:\n\
52 -c, --color=<c>\t\tDefault color (#rgb, #rrggbb or a name from rgb.txt)\n\
53 -d, --duration=<ms>\tDefault message duration in milliseconds\n\
54 -f, --font=<f>\t\tFont to use for the OSD\n\
55 -l, --lines=<n>\t\tNumber of lines of the OSD\n\
56 -m, --min-duration=<ms>\tDefault minimum message duration in milliseconds\n\
57 -o, --outline-color=<c>\tDefault outline color\n\
58 ");
59   exit(1);
60 }
61
62 static void
63 parse_opts(int argc, char **argv)
64 {
65   int opt;
66   while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
67     switch (opt)
68       {
69       case 'c':
70         default_color = optarg;
71         break;
72       case 'd':
73         default_duration = atoi(optarg);
74         break;
75       case 'f':
76         font_name = optarg;
77         break;
78       case 'l':
79         num_lines = atoi(optarg);
80         if (num_lines < 1)
81           usage();
82         break;
83       case 'm':
84         default_min_duration = atoi(optarg);
85         break;
86       case 'o':
87         default_outline_color = optarg;
88         break;
89       default:
90         usage();
91       }
92
93   if (optind < argc)
94     usage();
95 }
96
97 /*** Displaying of messages ***/
98
99 struct msg {
100   struct msg *next;
101   timestamp_t min_light, max_light;
102   char text[1];
103 };
104
105 static void
106 display_msg(struct msg *msg)
107 {
108   DBG("## Displaying message\n");
109   msg->min_light = now + default_min_duration;
110   msg->max_light = now + default_duration;
111   xosd_set_colour(osd, default_color);
112   xosd_set_outline_colour(osd, default_outline_color);
113
114   char *line = msg->text;
115   int row = 0;
116   while (*line)
117     {
118       // The parser it destructive, but it does not do any harm, since we display each message only once.
119       char *nl = strchr(line, '\n');
120       *nl++ = 0;
121
122       char *key;
123       char *val = strchr(line, ':');
124       if (val)
125         {
126           key = line;
127           *val++ = 0;
128         }
129       else
130         {
131           key = "";
132           val = line;
133         }
134       DBG("\t%s:%s\n", key, val);
135
136       if (!key[0])
137         {
138           if (row < num_lines)
139             xosd_display(osd, row++, XOSD_string, val);
140         }
141       else if (!strcmp(key, "percentage") || !strcmp(key, "percent"))
142         {
143           if (row < num_lines)
144             xosd_display(osd, row++, XOSD_percentage, atoi(val));
145         }
146       else if (!strcmp(key, "slider"))
147         {
148           if (row < num_lines)
149             xosd_display(osd, row++, XOSD_slider, atoi(val));
150         }
151       else if (!strcmp(key, "duration"))
152         msg->max_light = now + atoi(val);
153       else if (!strcmp(key, "min-duration"))
154         msg->min_light = now + atoi(val);
155       else if (!strcmp(key, "color"))
156         xosd_set_colour(osd, val);
157       else if (!strcmp(key, "outline-color"))
158         xosd_set_outline_colour(osd, val);
159       else
160         xosd_display(osd, (row < num_lines ? row++ : num_lines-1), XOSD_string, "PARSE ERROR");
161
162       line = nl;
163     }
164
165   if (msg->min_light > msg->max_light)
166     msg->min_light = msg->max_light;
167 }
168
169 static void
170 hide_msg(struct msg *msg)
171 {
172   DBG("## Hiding message\n");
173   for (int i=0; i<num_lines; i++)
174     xosd_display(osd, i, XOSD_string, "");
175   xosd_hide(osd);
176   free(msg);
177 }
178
179 /*** The message queue ***/
180
181 static struct msg *current_msg, *first_msg, *last_msg;
182
183 static void
184 enqueue_msg(unsigned char *buf, int len)
185 {
186   DBG("Received: [%.*s]\n", len, buf);
187   if (!len || buf[len-1] != '\n')
188     return;
189
190   struct msg *msg = xmalloc(sizeof(*msg) + len);
191   memcpy(msg->text, buf, len);
192   msg->text[len] = 0;
193
194   if (first_msg)
195     last_msg->next = msg;
196   else
197     first_msg = msg;
198   last_msg = msg;
199   msg->next = NULL;
200 }
201
202 static void
203 parse_input(unsigned char *buf, int len)
204 {
205   /* The property might contain several messages concatenated. Split them. */
206   while (len > 0)
207     {
208       if (buf[0] == '\n')
209         {
210           buf++, len--;
211           continue;
212         }
213       int i = 0;
214       while (i < len && (buf[i] != '\n' || (i && buf[i-1] != '\n')))
215         i++;
216       enqueue_msg(buf, i);
217       buf += i, len -= i;
218     }
219 }
220
221 /*** Main loop ***/
222
223 int
224 main(int argc, char **argv)
225 {
226   parse_opts(argc, argv);
227   XInitThreads();
228
229   Display *dpy = XOpenDisplay(NULL);
230   if (!dpy)
231     die("Cannot open display");
232   Window win = DefaultRootWindow(dpy);
233
234   Atom pty = XInternAtom(dpy, "OSD_QUEUE", False);
235   if (!pty)
236     die("Cannot intern OSD_QUEUE atom");
237
238   XSelectInput(dpy, win, PropertyChangeMask);
239   XDeleteProperty(dpy, win, pty);
240   XFlush(dpy);
241
242   osd = xosd_create(num_lines);
243   if (!osd)
244     die("Cannot initialize OSD");
245   xosd_set_font(osd, font_name);
246   xosd_set_outline_offset(osd, 2);
247   xosd_set_pos(osd, XOSD_middle);
248   xosd_set_align(osd, XOSD_center);
249
250   struct pollfd pfd = {
251     .fd = ConnectionNumber(dpy),
252     .events = POLLIN,
253   };
254
255   for (;;)
256     {
257       struct timeval tv;
258       gettimeofday(&tv, NULL);
259       now = (timestamp_t) tv.tv_sec * 1000 + tv.tv_usec / 1000;
260
261       timestamp_t wait_until = now - 1;
262       if (!current_msg && first_msg)
263         {
264           current_msg = first_msg;
265           first_msg = first_msg->next;
266           display_msg(current_msg);
267         }
268       if (current_msg)
269         {
270           if (first_msg)
271             wait_until = current_msg->min_light;
272           else
273             wait_until = current_msg->max_light;
274           if (wait_until <= now)
275             {
276               hide_msg(current_msg);
277               current_msg = NULL;
278               continue;
279             }
280         }
281
282       DBG("... waiting for %d ms\n", (int)(wait_until - now));
283       poll(&pfd, 1, wait_until - now);
284       if (pfd.revents & POLLIN)
285         {
286           while (XPending(dpy))
287             {
288               XEvent ev;
289               XNextEvent(dpy, &ev);
290               if (ev.type != PropertyNotify)
291                 continue;
292               XPropertyEvent *p = &ev.xproperty;
293               if (p->window == win && p->atom == pty)
294                 {
295                   Atom pty_type;
296                   int pty_fmt;
297                   unsigned long pty_items, pty_remains;
298                   unsigned char *pty_buf = NULL;
299                   XGetWindowProperty(dpy, win, pty, 0, 4096, True, XA_STRING, &pty_type, &pty_fmt, &pty_items, &pty_remains, &pty_buf);
300                   if (pty_type == XA_STRING && pty_fmt == 8 && pty_items)
301                     parse_input(pty_buf, pty_items);
302                   if (pty_buf)
303                     XFree(pty_buf);
304                 }
305             }
306         }
307     }
308 }