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