]> mj.ucw.cz Git - osdd.git/blob - osdd.c
New display code is able to show text messages
[osdd.git] / osdd.c
1 /*
2  *      On-screen Display Daemon
3  *
4  *      (c) 2010--2013 Martin Mares <mj@ucw.cz>
5  */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <poll.h>
12 #include <getopt.h>
13 #include <locale.h>
14 #include <xosd.h>
15 #include <X11/Xlib.h>
16 #include <X11/Xatom.h>
17
18 #undef DEBUG
19 #include "util.h"
20 #include "display.h"
21
22 static struct osd_state *osd;
23
24 static timestamp_t now;
25
26 /*** Options ***/
27
28 static char *font_name = "times-64";
29 static char *default_color = "green";
30 static char *default_outline_color = "black";
31 static int default_outline_width = 2;
32 static int default_duration = 1000;
33 static int default_min_duration = 250;
34 static int debug_mode;
35 static int test_mode;
36 static double line_spacing = 0.2;
37
38 static const char short_opts[] = "c:d:Df:m:o:O:s:";
39
40 enum long_opt {
41   OPT_TEST = 256,
42 };
43
44 static const struct option long_opts[] = {
45   { "color",            required_argument,      NULL,   'c' },
46   { "debug",            no_argument,            NULL,   'D' },
47   { "duration",         required_argument,      NULL,   'd' },
48   { "font",             required_argument,      NULL,   'f' },
49   { "min-duration",     required_argument,      NULL,   'm' },
50   { "outline-color",    required_argument,      NULL,   'o' },
51   { "outline-width",    required_argument,      NULL,   'O' },
52   { "line-spacing",     required_argument,      NULL,   's' },
53   { "test",             no_argument,            NULL,   OPT_TEST },     // Undocumented test mode
54   { NULL,               0,                      NULL,   0   },
55 };
56
57 static void NONRET
58 usage(void)
59 {
60   fprintf(stderr, "Usage: osdd <options>\n\n\
61 Options:\n\
62 -c, --color=<c>\t\tDefault color (#rgb, #rrggbb or a name from rgb.txt)\n\
63 -D, --debug\t\tDebugging mode (do not detach from the terminal)\n\
64 -d, --duration=<ms>\tDefault message duration in milliseconds\n\
65 -f, --font=<f>\t\tFont to use for the OSD\n\
66 -m, --min-duration=<ms>\tDefault minimum message duration in milliseconds\n\
67 -o, --outline-color=<c>\tDefault outline color\n\
68 -O, --outline-width=<n>\tDefault outline width (default=2)\n\
69 -s, --line-spacing=<n>\tSet line spacing factor (decimal fraction, default=0.2)\n\
70 ");
71   exit(1);
72 }
73
74 static void
75 parse_opts(int argc, char **argv)
76 {
77   int opt;
78   while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
79     switch (opt)
80       {
81       case 'c':
82         default_color = optarg;
83         break;
84       case 'd':
85         default_duration = atoi(optarg);
86         break;
87       case 'D':
88         debug_mode = 1;
89         break;
90       case 'f':
91         font_name = optarg;
92         break;
93       case 'l':
94         line_spacing = atof(optarg);
95         break;
96       case 'm':
97         default_min_duration = atoi(optarg);
98         break;
99       case 'o':
100         default_outline_color = optarg;
101         break;
102       case 'O':
103         default_outline_width = atoi(optarg);
104         break;
105       case OPT_TEST:
106         test_mode = 1;
107         debug_mode = 1;
108         break;
109       default:
110         usage();
111       }
112
113   if (optind < argc)
114     usage();
115 }
116
117 /*** Displaying of messages ***/
118
119 struct msg {
120   struct msg *next;
121   timestamp_t min_light, max_light;
122   char text[1];
123 };
124
125 static void
126 display_msg(struct msg *msg)
127 {
128   DBG("## Displaying message\n");
129   msg->min_light = now + default_min_duration;
130   msg->max_light = now + default_duration;
131   char *fg_color = default_color;
132   char *outline_color = default_outline_color;
133   int outline_width = default_outline_width;
134
135   char *line = msg->text;
136   while (*line)
137     {
138       // The parser it destructive, but it does not do any harm, since we display each message only once.
139       char *nl = strchr(line, '\n');
140       *nl++ = 0;
141
142       char *key;
143       char *val = strchr(line, ':');
144       if (val)
145         {
146           key = line;
147           *val++ = 0;
148         }
149       else
150         {
151           key = "";
152           val = line;
153         }
154       DBG("\t%s:%s\n", key, val);
155
156       struct osd_line *l = NULL;
157       if (!key[0])
158         {
159           l = osd_add_line(osd, OSD_TYPE_TEXT);
160           sprintf(l->u.text, "%.*s", OSD_MAX_LINE_LEN, val);
161         }
162       else if (!strcmp(key, "percentage") || !strcmp(key, "percent"))
163         {
164           // FIXME
165           // xosd_display(osd, row++, XOSD_percentage, atoi(val));
166         }
167       else if (!strcmp(key, "slider"))
168         {
169           // FIXME
170           // xosd_display(osd, row++, XOSD_slider, atoi(val));
171         }
172       else if (!strcmp(key, "duration"))
173         msg->max_light = now + atoi(val);
174       else if (!strcmp(key, "min-duration"))
175         msg->min_light = now + atoi(val);
176       else if (!strcmp(key, "color"))
177         fg_color = val;
178       else if (!strcmp(key, "outline-color"))
179         outline_color = val;
180       else if (!strcmp(key, "outline-width"))
181         outline_width = atoi(val);
182
183       if (l)
184         {
185           l->fg_color = fg_color;
186           l->outline_color = outline_color;
187           l->outline_width = outline_width;
188         }
189
190       line = nl;
191     }
192
193   if (msg->min_light > msg->max_light)
194     msg->min_light = msg->max_light;
195
196   osd_show(osd);
197 }
198
199 static void
200 hide_msg(struct msg *msg)
201 {
202   DBG("## Hiding message\n");
203   osd_clear(osd);
204   free(msg);
205 }
206
207 /*** The message queue ***/
208
209 static struct msg *current_msg, *first_msg, *last_msg;
210
211 static void
212 enqueue_msg(unsigned char *buf, int len)
213 {
214   DBG("Received: [%.*s]\n", len, buf);
215   if (!len || buf[len-1] != '\n')
216     return;
217
218   struct msg *msg = xmalloc(sizeof(*msg) + len);
219   memcpy(msg->text, buf, len);
220   msg->text[len] = 0;
221
222   if (first_msg)
223     last_msg->next = msg;
224   else
225     first_msg = msg;
226   last_msg = msg;
227   msg->next = NULL;
228 }
229
230 static void
231 parse_input(unsigned char *buf, int len)
232 {
233   /* The property might contain several messages concatenated. Split them. */
234   while (len > 0)
235     {
236       if (buf[0] == '\n')
237         {
238           buf++, len--;
239           continue;
240         }
241       int i = 0;
242       while (i < len && (buf[i] != '\n' || (i && buf[i-1] != '\n')))
243         i++;
244       enqueue_msg(buf, i);
245       buf += i, len -= i;
246     }
247 }
248
249 static void
250 do_test(void)
251 {
252   unsigned char buf[4096];
253   int len = 0;
254   int c;
255
256   while ((c = read(0, buf + len, 4096 - len)) > 0)
257     len += c;
258   if (len)
259     enqueue_msg(buf, len);
260 }
261
262 /*** Main loop ***/
263
264 int
265 main(int argc, char **argv)
266 {
267   parse_opts(argc, argv);
268   setlocale(LC_CTYPE, "");
269   XInitThreads();
270
271   Display *dpy = XOpenDisplay(NULL);
272   if (!dpy)
273     die("Cannot open display");
274   Window win = DefaultRootWindow(dpy);
275
276   Atom pty = XInternAtom(dpy, "OSD_QUEUE", False);
277   if (!pty)
278     die("Cannot intern OSD_QUEUE atom");
279
280   if (!debug_mode)
281     {
282       pid_t pid = fork();
283       if (pid < 0)
284         die("Cannot fork: %m");
285       if (pid > 0)
286         return 0;
287       setsid();
288     }
289
290   if (test_mode)
291     {
292       do_test();
293       pty = 0;
294     }
295   else
296     {
297       XSelectInput(dpy, win, PropertyChangeMask);
298       XDeleteProperty(dpy, win, pty);
299       XFlush(dpy);
300     }
301
302   osd = osd_new(dpy);
303   osd_set_font(osd, font_name, line_spacing);
304
305   struct pollfd pfd = {
306     .fd = ConnectionNumber(dpy),
307     .events = POLLIN,
308   };
309
310   for (;;)
311     {
312       now = get_current_time();
313
314       timestamp_t wait_until = now - 1;
315       if (!current_msg && first_msg)
316         {
317           current_msg = first_msg;
318           first_msg = first_msg->next;
319           display_msg(current_msg);
320         }
321       if (current_msg)
322         {
323           if (first_msg)
324             wait_until = current_msg->min_light;
325           else
326             wait_until = current_msg->max_light;
327           if (wait_until <= now)
328             {
329               hide_msg(current_msg);
330               current_msg = NULL;
331               continue;
332             }
333         }
334       if (test_mode && !current_msg)
335         break;
336
337       DBG("... waiting for %d ms\n", (int)(wait_until - now));
338       poll(&pfd, 1, wait_until - now);
339       if (pfd.revents & POLLIN)
340         {
341           while (XPending(dpy))
342             {
343               XEvent ev;
344               XNextEvent(dpy, &ev);
345               if (osd_handle_event(osd, &ev))
346                 continue;
347               if (ev.type != PropertyNotify)
348                 continue;
349               XPropertyEvent *p = &ev.xproperty;
350               if (p->window == win && p->atom == pty)
351                 {
352                   Atom pty_type;
353                   int pty_fmt;
354                   unsigned long pty_items, pty_remains;
355                   unsigned char *pty_buf = NULL;
356                   XGetWindowProperty(dpy, win, pty, 0, 4096, True, XA_STRING, &pty_type, &pty_fmt, &pty_items, &pty_remains, &pty_buf);
357                   if (pty_type == XA_STRING && pty_fmt == 8 && pty_items)
358                     parse_input(pty_buf, pty_items);
359                   if (pty_buf)
360                     XFree(pty_buf);
361                 }
362             }
363         }
364     }
365 }