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