]> git.sur5r.net Git - i3/i3/blob - i3bar/src/child.c
Add click events to i3bar
[i3/i3] / i3bar / src / child.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3bar - an xcb-based status- and ws-bar for i3
5  * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
6  *
7  * child.c: Getting Input for the statusline
8  *
9  */
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <sys/types.h>
13 #include <sys/wait.h>
14 #include <signal.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <err.h>
20 #include <ev.h>
21 #include <yajl/yajl_common.h>
22 #include <yajl/yajl_parse.h>
23 #include <yajl/yajl_version.h>
24 #include <yajl/yajl_gen.h>
25
26 #include "common.h"
27
28 /* Global variables for child_*() */
29 i3bar_child child = { 0 };
30
31 /* stdin- and sigchild-watchers */
32 ev_io    *stdin_io;
33 ev_child *child_sig;
34
35 /* JSON parser for stdin */
36 yajl_callbacks callbacks;
37 yajl_handle parser;
38
39 /* JSON generator for stdout */
40 yajl_gen gen;
41
42 typedef struct parser_ctx {
43     /* True if one of the parsed blocks was urgent */
44     bool has_urgent;
45
46     /* A copy of the last JSON map key. */
47     char *last_map_key;
48
49     /* The current block. Will be filled, then copied and put into the list of
50      * blocks. */
51     struct status_block block;
52 } parser_ctx;
53
54 parser_ctx parser_context;
55
56 /* The buffer statusline points to */
57 struct statusline_head statusline_head = TAILQ_HEAD_INITIALIZER(statusline_head);
58 char *statusline_buffer = NULL;
59
60 /*
61  * Stop and free() the stdin- and sigchild-watchers
62  *
63  */
64 void cleanup(void) {
65     if (stdin_io != NULL) {
66         ev_io_stop(main_loop, stdin_io);
67         FREE(stdin_io);
68         FREE(statusline_buffer);
69         /* statusline pointed to memory within statusline_buffer */
70         statusline = NULL;
71     }
72
73     if (child_sig != NULL) {
74         ev_child_stop(main_loop, child_sig);
75         FREE(child_sig);
76     }
77
78     memset(&child, 0, sizeof(i3bar_child));
79 }
80
81 /*
82  * The start of a new array is the start of a new status line, so we clear all
83  * previous entries.
84  *
85  */
86 static int stdin_start_array(void *context) {
87     struct status_block *first;
88     while (!TAILQ_EMPTY(&statusline_head)) {
89         first = TAILQ_FIRST(&statusline_head);
90         I3STRING_FREE(first->full_text);
91         FREE(first->color);
92         FREE(first->name);
93         FREE(first->instance);
94         TAILQ_REMOVE(&statusline_head, first, blocks);
95         free(first);
96     }
97     return 1;
98 }
99
100 /*
101  * The start of a map is the start of a single block of the status line.
102  *
103  */
104 static int stdin_start_map(void *context) {
105     parser_ctx *ctx = context;
106     memset(&(ctx->block), '\0', sizeof(struct status_block));
107
108     /* Default width of the separator block. */
109     ctx->block.sep_block_width = 9;
110
111     return 1;
112 }
113
114 #if YAJL_MAJOR >= 2
115 static int stdin_map_key(void *context, const unsigned char *key, size_t len) {
116 #else
117 static int stdin_map_key(void *context, const unsigned char *key, unsigned int len) {
118 #endif
119     parser_ctx *ctx = context;
120     FREE(ctx->last_map_key);
121     sasprintf(&(ctx->last_map_key), "%.*s", len, key);
122     return 1;
123 }
124
125 static int stdin_boolean(void *context, int val) {
126     parser_ctx *ctx = context;
127     if (strcasecmp(ctx->last_map_key, "urgent") == 0) {
128         ctx->block.urgent = val;
129     }
130     if (strcasecmp(ctx->last_map_key, "separator") == 0) {
131         ctx->block.no_separator = !val;
132     }
133     return 1;
134 }
135
136 #if YAJL_MAJOR >= 2
137 static int stdin_string(void *context, const unsigned char *val, size_t len) {
138 #else
139 static int stdin_string(void *context, const unsigned char *val, unsigned int len) {
140 #endif
141     parser_ctx *ctx = context;
142     if (strcasecmp(ctx->last_map_key, "full_text") == 0) {
143         ctx->block.full_text = i3string_from_utf8_with_length((const char *)val, len);
144     }
145     if (strcasecmp(ctx->last_map_key, "color") == 0) {
146         sasprintf(&(ctx->block.color), "%.*s", len, val);
147     }
148     if (strcasecmp(ctx->last_map_key, "align") == 0) {
149         if (len == strlen("left") && !strncmp((const char*)val, "left", strlen("left"))) {
150             ctx->block.align = ALIGN_LEFT;
151         } else if (len == strlen("right") && !strncmp((const char*)val, "right", strlen("right"))) {
152             ctx->block.align = ALIGN_RIGHT;
153         } else {
154             ctx->block.align = ALIGN_CENTER;
155         }
156     } else if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
157         i3String *text = i3string_from_utf8_with_length((const char *)val, len);
158         ctx->block.min_width = (uint32_t)predict_text_width(text);
159         i3string_free(text);
160     }
161     if (strcasecmp(ctx->last_map_key, "name") == 0) {
162         char *copy = (char*)malloc(len+1);
163         strncpy(copy, (const char *)val, len);
164         copy[len] = 0;
165         ctx->block.name = copy;
166     }
167     if (strcasecmp(ctx->last_map_key, "instance") == 0) {
168         char *copy = (char*)malloc(len+1);
169         strncpy(copy, (const char *)val, len);
170         copy[len] = 0;
171         ctx->block.instance = copy;
172     }
173     return 1;
174 }
175
176 #if YAJL_MAJOR >= 2
177 static int stdin_integer(void *context, long long val) {
178 #else
179 static int stdin_integer(void *context, long val) {
180 #endif
181     parser_ctx *ctx = context;
182     if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
183         ctx->block.min_width = (uint32_t)val;
184     }
185     if (strcasecmp(ctx->last_map_key, "separator_block_width") == 0) {
186         ctx->block.sep_block_width = (uint32_t)val;
187     }
188     return 1;
189 }
190
191 static int stdin_end_map(void *context) {
192     parser_ctx *ctx = context;
193     struct status_block *new_block = smalloc(sizeof(struct status_block));
194     memcpy(new_block, &(ctx->block), sizeof(struct status_block));
195     /* Ensure we have a full_text set, so that when it is missing (or null),
196      * i3bar doesn’t crash and the user gets an annoying message. */
197     if (!new_block->full_text)
198         new_block->full_text = i3string_from_utf8("SPEC VIOLATION (null)");
199     if (new_block->urgent)
200         ctx->has_urgent = true;
201     TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
202     return 1;
203 }
204
205 static int stdin_end_array(void *context) {
206     DLOG("dumping statusline:\n");
207     struct status_block *current;
208     TAILQ_FOREACH(current, &statusline_head, blocks) {
209         DLOG("full_text = %s\n", i3string_as_utf8(current->full_text));
210         DLOG("color = %s\n", current->color);
211     }
212     DLOG("end of dump\n");
213     return 1;
214 }
215
216 /*
217  * Helper function to read stdin
218  *
219  */
220 static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
221     int fd = watcher->fd;
222     int n = 0;
223     int rec = 0;
224     int buffer_len = STDIN_CHUNK_SIZE;
225     unsigned char *buffer = smalloc(buffer_len+1);
226     buffer[0] = '\0';
227     while(1) {
228         n = read(fd, buffer + rec, buffer_len - rec);
229         if (n == -1) {
230             if (errno == EAGAIN) {
231                 /* finish up */
232                 break;
233             }
234             ELOG("read() failed!: %s\n", strerror(errno));
235             exit(EXIT_FAILURE);
236         }
237         if (n == 0) {
238             /* end of file, kill the watcher */
239             ELOG("stdin: received EOF\n");
240             cleanup();
241             draw_bars(false);
242             *ret_buffer_len = -1;
243             return NULL;
244         }
245         rec += n;
246
247         if (rec == buffer_len) {
248             buffer_len += STDIN_CHUNK_SIZE;
249             buffer = srealloc(buffer, buffer_len);
250         }
251     }
252     if (*buffer == '\0') {
253         FREE(buffer);
254         rec = -1;
255     }
256     *ret_buffer_len = rec;
257     return buffer;
258 }
259
260 static void read_flat_input(char *buffer, int length) {
261     struct status_block *first = TAILQ_FIRST(&statusline_head);
262     /* Clear the old buffer if any. */
263     I3STRING_FREE(first->full_text);
264     /* Remove the trailing newline and terminate the string at the same
265      * time. */
266     if (buffer[length-1] == '\n' || buffer[length-1] == '\r')
267         buffer[length-1] = '\0';
268     else buffer[length] = '\0';
269     first->full_text = i3string_from_utf8(buffer);
270 }
271
272 static bool read_json_input(unsigned char *input, int length) {
273     yajl_status status = yajl_parse(parser, input, length);
274     bool has_urgent = false;
275 #if YAJL_MAJOR >= 2
276     if (status != yajl_status_ok) {
277 #else
278     if (status != yajl_status_ok && status != yajl_status_insufficient_data) {
279 #endif
280         fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n",
281                 status, length, input);
282     } else if (parser_context.has_urgent) {
283         has_urgent = true;
284     }
285     return has_urgent;
286 }
287
288 /*
289  * Callbalk for stdin. We read a line from stdin and store the result
290  * in statusline
291  *
292  */
293 void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
294     int rec;
295     unsigned char *buffer = get_buffer(watcher, &rec);
296     if (buffer == NULL)
297         return;
298     bool has_urgent = false;
299     if (child.version > 0) {
300         has_urgent = read_json_input(buffer, rec);
301     } else {
302         read_flat_input((char*)buffer, rec);
303     }
304     free(buffer);
305     draw_bars(has_urgent);
306 }
307
308 /*
309  * Callbalk for stdin first line. We read the first line to detect
310  * whether this is JSON or plain text
311  *
312  */
313 void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
314     int rec;
315     unsigned char *buffer = get_buffer(watcher, &rec);
316     if (buffer == NULL)
317         return;
318     DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
319     /* Detect whether this is JSON or plain text. */
320     unsigned int consumed = 0;
321     /* At the moment, we don’t care for the version. This might change
322      * in the future, but for now, we just discard it. */
323     parse_json_header(&child, buffer, rec, &consumed);
324     if (child.version > 0) {
325         /* If hide-on-modifier is set, we start of by sending the
326          * child a SIGSTOP, because the bars aren't mapped at start */
327         if (config.hide_on_modifier) {
328             stop_child();
329         }
330         read_json_input(buffer + consumed, rec - consumed);
331     } else {
332         /* In case of plaintext, we just add a single block and change its
333          * full_text pointer later. */
334         struct status_block *new_block = scalloc(sizeof(struct status_block));
335         TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
336         read_flat_input((char*)buffer, rec);
337     }
338     free(buffer);
339     ev_io_stop(main_loop, stdin_io);
340     ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
341     ev_io_start(main_loop, stdin_io);
342 }
343
344 /*
345  * We received a sigchild, meaning, that the child-process terminated.
346  * We simply free the respective data-structures and don't care for input
347  * anymore
348  *
349  */
350 void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
351     ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
352            child.pid,
353            watcher->rstatus);
354     cleanup();
355 }
356
357 void child_write_output(void) {
358     if (child.click_events) {
359         const unsigned char *output;
360         size_t size;
361         yajl_gen_get_buf(gen, &output, &size);
362         fwrite(output, 1, size, stdout);
363         fwrite("\n", 1, 1, stdout);
364         fflush(stdout);
365         yajl_gen_clear(gen);
366     }
367 }
368
369 /*
370  * Start a child-process with the specified command and reroute stdin.
371  * We actually start a $SHELL to execute the command so we don't have to care
372  * about arguments and such
373  *
374  */
375 void start_child(char *command) {
376     /* Allocate a yajl parser which will be used to parse stdin. */
377     memset(&callbacks, '\0', sizeof(yajl_callbacks));
378     callbacks.yajl_map_key = stdin_map_key;
379     callbacks.yajl_boolean = stdin_boolean;
380     callbacks.yajl_string = stdin_string;
381     callbacks.yajl_integer = stdin_integer;
382     callbacks.yajl_start_array = stdin_start_array;
383     callbacks.yajl_end_array = stdin_end_array;
384     callbacks.yajl_start_map = stdin_start_map;
385     callbacks.yajl_end_map = stdin_end_map;
386 #if YAJL_MAJOR < 2
387     yajl_parser_config parse_conf = { 0, 0 };
388
389     parser = yajl_alloc(&callbacks, &parse_conf, NULL, (void*)&parser_context);
390 #else
391     parser = yajl_alloc(&callbacks, NULL, &parser_context);
392 #endif
393
394     gen = yajl_gen_alloc(NULL);
395
396     if (command != NULL) {
397         int pipe_in[2]; /* pipe we read from */
398         int pipe_out[2]; /* pipe we write to */
399
400         if (pipe(pipe_in) == -1)
401             err(EXIT_FAILURE, "pipe(pipe_in)");
402         if (pipe(pipe_out) == -1)
403             err(EXIT_FAILURE, "pipe(pipe_out)");
404
405         child.pid = fork();
406         switch (child.pid) {
407             case -1:
408                 ELOG("Couldn't fork(): %s\n", strerror(errno));
409                 exit(EXIT_FAILURE);
410             case 0:
411                 /* Child-process. Reroute streams and start shell */
412
413                 close(pipe_in[0]);
414                 close(pipe_out[1]);
415
416                 dup2(pipe_in[1], STDOUT_FILENO);
417                 dup2(pipe_out[0], STDIN_FILENO);
418
419                 static const char *shell = NULL;
420
421                 if ((shell = getenv("SHELL")) == NULL)
422                     shell = "/bin/sh";
423
424                 execl(shell, shell, "-c", command, (char*) NULL);
425                 return;
426             default:
427                 /* Parent-process. Reroute streams */
428
429                 close(pipe_in[1]);
430                 close(pipe_out[0]);
431
432                 dup2(pipe_in[0], STDIN_FILENO);
433                 dup2(pipe_out[1], STDOUT_FILENO);
434
435                 break;
436         }
437     }
438
439     /* We set O_NONBLOCK because blocking is evil in event-driven software */
440     fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
441
442     stdin_io = smalloc(sizeof(ev_io));
443     ev_io_init(stdin_io, &stdin_io_first_line_cb, STDIN_FILENO, EV_READ);
444     ev_io_start(main_loop, stdin_io);
445
446     /* We must cleanup, if the child unexpectedly terminates */
447     child_sig = smalloc(sizeof(ev_child));
448     ev_child_init(child_sig, &child_sig_cb, child.pid, 0);
449     ev_child_start(main_loop, child_sig);
450
451     atexit(kill_child_at_exit);
452 }
453
454 void child_click_events_initialize(void) {
455     if (!child.click_events_init) {
456         yajl_gen_array_open(gen);
457         child_write_output();
458         child.click_events_init = true;
459     }
460 }
461
462 void child_click_events_key(const char *key) {
463     yajl_gen_string(gen, (const unsigned char *)key, strlen(key));
464 }
465
466 /*
467  * Generates a click event, if enabled.
468  *
469  */
470 void send_block_clicked(int button, const char *name, const char *instance, int x, int y) {
471     if (child.click_events) {
472         child_click_events_initialize();
473
474         yajl_gen_map_open(gen);
475
476         if (name) {
477             child_click_events_key("name");
478             yajl_gen_string(gen, (const unsigned char *)name, strlen(name));
479         }
480
481         if (instance) {
482             child_click_events_key("instance");
483             yajl_gen_string(gen, (const unsigned char *)instance, strlen(instance));
484         }
485
486         child_click_events_key("button");
487         yajl_gen_integer(gen, button);
488
489         child_click_events_key("x");
490         yajl_gen_integer(gen, x);
491
492         child_click_events_key("y");
493         yajl_gen_integer(gen, y);
494
495         yajl_gen_map_close(gen);
496         child_write_output();
497     }
498 }
499
500 /*
501  * kill()s the child-process (if any). Called when exit()ing.
502  *
503  */
504 void kill_child_at_exit(void) {
505     if (child.pid > 0) {
506         if (child.cont_signal > 0 && child.stopped)
507             kill(child.pid, child.cont_signal);
508         kill(child.pid, SIGTERM);
509     }
510 }
511
512 /*
513  * kill()s the child-process (if existent) and closes and
514  * free()s the stdin- and sigchild-watchers
515  *
516  */
517 void kill_child(void) {
518     if (child.pid > 0) {
519         if (child.cont_signal > 0 && child.stopped)
520             kill(child.pid, child.cont_signal);
521         kill(child.pid, SIGTERM);
522         int status;
523         waitpid(child.pid, &status, 0);
524         cleanup();
525     }
526 }
527
528 /*
529  * Sends a SIGSTOP to the child-process (if existent)
530  *
531  */
532 void stop_child(void) {
533     if (child.stop_signal > 0 && !child.stopped) {
534         child.stopped = true;
535         kill(child.pid, child.stop_signal);
536     }
537 }
538
539 /*
540  * Sends a SIGCONT to the child-process (if existent)
541  *
542  */
543 void cont_child(void) {
544     if (child.cont_signal > 0 && child.stopped) {
545         child.stopped = false;
546         kill(child.pid, child.cont_signal);
547     }
548 }