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