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