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