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