]> git.sur5r.net Git - i3/i3/blob - i3bar/src/child.c
Added background and border keys to the i3bar protocol.
[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 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 <stdarg.h>
17 #include <fcntl.h>
18 #include <string.h>
19 #include <errno.h>
20 #include <err.h>
21 #include <ev.h>
22 #include <yajl/yajl_common.h>
23 #include <yajl/yajl_parse.h>
24 #include <yajl/yajl_version.h>
25 #include <yajl/yajl_gen.h>
26 #include <paths.h>
27
28 #include "common.h"
29
30 /* Global variables for child_*() */
31 i3bar_child child;
32
33 /* stdin- and SIGCHLD-watchers */
34 ev_io *stdin_io;
35 ev_child *child_sig;
36
37 /* JSON parser for stdin */
38 yajl_handle parser;
39
40 /* JSON generator for stdout */
41 yajl_gen gen;
42
43 typedef struct parser_ctx {
44     /* True if one of the parsed blocks was urgent */
45     bool has_urgent;
46
47     /* A copy of the last JSON map key. */
48     char *last_map_key;
49
50     /* The current block. Will be filled, then copied and put into the list of
51      * blocks. */
52     struct status_block block;
53 } parser_ctx;
54
55 parser_ctx parser_context;
56
57 struct statusline_head statusline_head = TAILQ_HEAD_INITIALIZER(statusline_head);
58 /* Used temporarily while reading a statusline */
59 struct statusline_head statusline_buffer = TAILQ_HEAD_INITIALIZER(statusline_buffer);
60
61 int child_stdin;
62
63 /*
64  * Remove all blocks from the given statusline.
65  * If free_resources is set, the fields of each status block will be free'd.
66  */
67 static void clear_statusline(struct statusline_head *head, bool free_resources) {
68     struct status_block *first;
69     while (!TAILQ_EMPTY(head)) {
70         first = TAILQ_FIRST(head);
71         if (free_resources) {
72             I3STRING_FREE(first->full_text);
73             I3STRING_FREE(first->short_text);
74             FREE(first->color);
75             FREE(first->name);
76             FREE(first->instance);
77             FREE(first->min_width_str);
78             FREE(first->background);
79             FREE(first->border);
80         }
81
82         TAILQ_REMOVE(head, first, blocks);
83         free(first);
84     }
85 }
86
87 static void copy_statusline(struct statusline_head *from, struct statusline_head *to) {
88     struct status_block *current;
89     TAILQ_FOREACH(current, from, blocks) {
90         struct status_block *new_block = smalloc(sizeof(struct status_block));
91         memcpy(new_block, current, sizeof(struct status_block));
92         TAILQ_INSERT_TAIL(to, new_block, blocks);
93     }
94 }
95
96 /*
97  * Replaces the statusline in memory with an error message. Pass a format
98  * string and format parameters as you would in `printf'. The next time
99  * `draw_bars' is called, the error message text will be drawn on the bar in
100  * the space allocated for the statusline.
101  */
102 __attribute__((format(printf, 1, 2))) static void set_statusline_error(const char *format, ...) {
103     clear_statusline(&statusline_head, true);
104
105     char *message;
106     va_list args;
107     va_start(args, format);
108     (void)vasprintf(&message, format, args);
109
110     struct status_block *err_block = scalloc(1, sizeof(struct status_block));
111     err_block->full_text = i3string_from_utf8("Error: ");
112     err_block->name = sstrdup("error");
113     err_block->color = sstrdup("red");
114     err_block->no_separator = true;
115
116     struct status_block *message_block = scalloc(1, sizeof(struct status_block));
117     message_block->full_text = i3string_from_utf8(message);
118     message_block->name = sstrdup("error_message");
119     message_block->color = sstrdup("red");
120     message_block->no_separator = true;
121
122     TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks);
123     TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
124
125     FREE(message);
126     va_end(args);
127 }
128
129 /*
130  * Stop and free() the stdin- and SIGCHLD-watchers
131  *
132  */
133 void cleanup(void) {
134     if (stdin_io != NULL) {
135         ev_io_stop(main_loop, stdin_io);
136         FREE(stdin_io);
137     }
138
139     if (child_sig != NULL) {
140         ev_child_stop(main_loop, child_sig);
141         FREE(child_sig);
142     }
143
144     memset(&child, 0, sizeof(i3bar_child));
145 }
146
147 /*
148  * The start of a new array is the start of a new status line, so we clear all
149  * previous entries from the buffer.
150  */
151 static int stdin_start_array(void *context) {
152     // the blocks are still used by statusline_head, so we won't free the
153     // resources here.
154     clear_statusline(&statusline_buffer, false);
155     return 1;
156 }
157
158 /*
159  * The start of a map is the start of a single block of the status line.
160  *
161  */
162 static int stdin_start_map(void *context) {
163     parser_ctx *ctx = context;
164     memset(&(ctx->block), '\0', sizeof(struct status_block));
165
166     /* Default width of the separator block. */
167     if (config.separator_symbol == NULL)
168         ctx->block.sep_block_width = logical_px(9);
169     else
170         ctx->block.sep_block_width = logical_px(8) + separator_symbol_width;
171
172     return 1;
173 }
174
175 static int stdin_map_key(void *context, const unsigned char *key, size_t len) {
176     parser_ctx *ctx = context;
177     FREE(ctx->last_map_key);
178     sasprintf(&(ctx->last_map_key), "%.*s", len, key);
179     return 1;
180 }
181
182 static int stdin_boolean(void *context, int val) {
183     parser_ctx *ctx = context;
184     if (strcasecmp(ctx->last_map_key, "urgent") == 0) {
185         ctx->block.urgent = val;
186         return 1;
187     }
188     if (strcasecmp(ctx->last_map_key, "separator") == 0) {
189         ctx->block.no_separator = !val;
190         return 1;
191     }
192
193     return 1;
194 }
195
196 static int stdin_string(void *context, const unsigned char *val, size_t len) {
197     parser_ctx *ctx = context;
198     if (strcasecmp(ctx->last_map_key, "full_text") == 0) {
199         ctx->block.full_text = i3string_from_markup_with_length((const char *)val, len);
200         return 1;
201     }
202     if (strcasecmp(ctx->last_map_key, "short_text") == 0) {
203         ctx->block.short_text = i3string_from_markup_with_length((const char *)val, len);
204         return 1;
205     }
206     if (strcasecmp(ctx->last_map_key, "color") == 0) {
207         sasprintf(&(ctx->block.color), "%.*s", len, val);
208         return 1;
209     }
210     if (strcasecmp(ctx->last_map_key, "background") == 0) {
211         sasprintf(&(ctx->block.background), "%.*s", len, val);
212         return 1;
213     }
214     if (strcasecmp(ctx->last_map_key, "border") == 0) {
215         sasprintf(&(ctx->block.border), "%.*s", len, val);
216         return 1;
217     }
218     if (strcasecmp(ctx->last_map_key, "markup") == 0) {
219         ctx->block.pango_markup = (len == strlen("pango") && !strncasecmp((const char *)val, "pango", strlen("pango")));
220         return 1;
221     }
222     if (strcasecmp(ctx->last_map_key, "align") == 0) {
223         if (len == strlen("center") && !strncmp((const char *)val, "center", strlen("center"))) {
224             ctx->block.align = ALIGN_CENTER;
225         } else if (len == strlen("right") && !strncmp((const char *)val, "right", strlen("right"))) {
226             ctx->block.align = ALIGN_RIGHT;
227         } else {
228             ctx->block.align = ALIGN_LEFT;
229         }
230         return 1;
231     }
232     if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
233         sasprintf(&(ctx->block.min_width_str), "%.*s", len, val);
234         return 1;
235     }
236     if (strcasecmp(ctx->last_map_key, "name") == 0) {
237         sasprintf(&(ctx->block.name), "%.*s", len, val);
238         return 1;
239     }
240     if (strcasecmp(ctx->last_map_key, "instance") == 0) {
241         sasprintf(&(ctx->block.instance), "%.*s", len, val);
242         return 1;
243     }
244
245     return 1;
246 }
247
248 static int stdin_integer(void *context, long long val) {
249     parser_ctx *ctx = context;
250     if (strcasecmp(ctx->last_map_key, "min_width") == 0) {
251         ctx->block.min_width = (uint32_t)val;
252         return 1;
253     }
254     if (strcasecmp(ctx->last_map_key, "separator_block_width") == 0) {
255         ctx->block.sep_block_width = (uint32_t)val;
256         return 1;
257     }
258
259     return 1;
260 }
261
262 /*
263  * When a map is finished, we have an entire status block.
264  * Move it from the parser's context to the statusline buffer.
265  */
266 static int stdin_end_map(void *context) {
267     parser_ctx *ctx = context;
268     struct status_block *new_block = smalloc(sizeof(struct status_block));
269     memcpy(new_block, &(ctx->block), sizeof(struct status_block));
270     /* Ensure we have a full_text set, so that when it is missing (or null),
271      * i3bar doesn’t crash and the user gets an annoying message. */
272     if (!new_block->full_text)
273         new_block->full_text = i3string_from_utf8("SPEC VIOLATION: full_text is NULL!");
274     if (new_block->urgent)
275         ctx->has_urgent = true;
276
277     if (new_block->min_width_str) {
278         i3String *text = i3string_from_utf8(new_block->min_width_str);
279         i3string_set_markup(text, new_block->pango_markup);
280         new_block->min_width = (uint32_t)predict_text_width(text);
281         i3string_free(text);
282     }
283
284     i3string_set_markup(new_block->full_text, new_block->pango_markup);
285
286     if (new_block->short_text != NULL)
287         i3string_set_markup(new_block->short_text, new_block->pango_markup);
288
289     TAILQ_INSERT_TAIL(&statusline_buffer, new_block, blocks);
290     return 1;
291 }
292
293 /*
294  * When an array is finished, we have an entire statusline.
295  * Copy it from the buffer to the actual statusline.
296  */
297 static int stdin_end_array(void *context) {
298     DLOG("copying statusline_buffer to statusline_head\n");
299     clear_statusline(&statusline_head, true);
300     copy_statusline(&statusline_buffer, &statusline_head);
301
302     DLOG("dumping statusline:\n");
303     struct status_block *current;
304     TAILQ_FOREACH(current, &statusline_head, blocks) {
305         DLOG("full_text = %s\n", i3string_as_utf8(current->full_text));
306         DLOG("short_text = %s\n", (current->short_text == NULL ? NULL : i3string_as_utf8(current->short_text)));
307         DLOG("color = %s\n", current->color);
308     }
309     DLOG("end of dump\n");
310     return 1;
311 }
312
313 /*
314  * Helper function to read stdin
315  *
316  * Returns NULL on EOF.
317  *
318  */
319 static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
320     int fd = watcher->fd;
321     int n = 0;
322     int rec = 0;
323     int buffer_len = STDIN_CHUNK_SIZE;
324     unsigned char *buffer = smalloc(buffer_len + 1);
325     buffer[0] = '\0';
326     while (1) {
327         n = read(fd, buffer + rec, buffer_len - rec);
328         if (n == -1) {
329             if (errno == EAGAIN) {
330                 /* finish up */
331                 break;
332             }
333             ELOG("read() failed!: %s\n", strerror(errno));
334             exit(EXIT_FAILURE);
335         }
336         if (n == 0) {
337             ELOG("stdin: received EOF\n");
338             *ret_buffer_len = -1;
339             return NULL;
340         }
341         rec += n;
342
343         if (rec == buffer_len) {
344             buffer_len += STDIN_CHUNK_SIZE;
345             buffer = srealloc(buffer, buffer_len);
346         }
347     }
348     if (*buffer == '\0') {
349         FREE(buffer);
350         rec = -1;
351     }
352     *ret_buffer_len = rec;
353     return buffer;
354 }
355
356 static void read_flat_input(char *buffer, int length) {
357     struct status_block *first = TAILQ_FIRST(&statusline_head);
358     /* Clear the old buffer if any. */
359     I3STRING_FREE(first->full_text);
360     /* Remove the trailing newline and terminate the string at the same
361      * time. */
362     if (buffer[length - 1] == '\n' || buffer[length - 1] == '\r')
363         buffer[length - 1] = '\0';
364     else
365         buffer[length] = '\0';
366     first->full_text = i3string_from_markup(buffer);
367 }
368
369 static bool read_json_input(unsigned char *input, int length) {
370     yajl_status status = yajl_parse(parser, input, length);
371     bool has_urgent = false;
372     if (status != yajl_status_ok) {
373         char *message = (char *)yajl_get_error(parser, 0, input, length);
374
375         /* strip the newline yajl adds to the error message */
376         if (message[strlen(message) - 1] == '\n')
377             message[strlen(message) - 1] = '\0';
378
379         fprintf(stderr, "[i3bar] Could not parse JSON input (code = %d, message = %s): %.*s\n",
380                 status, message, length, input);
381
382         set_statusline_error("Could not parse JSON (%s)", message);
383         yajl_free_error(parser, (unsigned char *)message);
384         draw_bars(false);
385     } else if (parser_context.has_urgent) {
386         has_urgent = true;
387     }
388     return has_urgent;
389 }
390
391 /*
392  * Callbalk for stdin. We read a line from stdin and store the result
393  * in statusline
394  *
395  */
396 void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
397     int rec;
398     unsigned char *buffer = get_buffer(watcher, &rec);
399     if (buffer == NULL)
400         return;
401     bool has_urgent = false;
402     if (child.version > 0) {
403         has_urgent = read_json_input(buffer, rec);
404     } else {
405         read_flat_input((char *)buffer, rec);
406     }
407     free(buffer);
408     draw_bars(has_urgent);
409 }
410
411 /*
412  * Callbalk for stdin first line. We read the first line to detect
413  * whether this is JSON or plain text
414  *
415  */
416 void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
417     int rec;
418     unsigned char *buffer = get_buffer(watcher, &rec);
419     if (buffer == NULL)
420         return;
421     DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
422     /* Detect whether this is JSON or plain text. */
423     unsigned int consumed = 0;
424     /* At the moment, we don’t care for the version. This might change
425      * in the future, but for now, we just discard it. */
426     parse_json_header(&child, buffer, rec, &consumed);
427     if (child.version > 0) {
428         /* If hide-on-modifier is set, we start of by sending the
429          * child a SIGSTOP, because the bars aren't mapped at start */
430         if (config.hide_on_modifier) {
431             stop_child();
432         }
433         draw_bars(read_json_input(buffer + consumed, rec - consumed));
434     } else {
435         /* In case of plaintext, we just add a single block and change its
436          * full_text pointer later. */
437         struct status_block *new_block = scalloc(1, sizeof(struct status_block));
438         TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks);
439         read_flat_input((char *)buffer, rec);
440     }
441     free(buffer);
442     ev_io_stop(main_loop, stdin_io);
443     ev_io_init(stdin_io, &stdin_io_cb, STDIN_FILENO, EV_READ);
444     ev_io_start(main_loop, stdin_io);
445 }
446
447 /*
448  * We received a SIGCHLD, meaning, that the child process terminated.
449  * We simply free the respective data structures and don't care for input
450  * anymore
451  *
452  */
453 void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
454     int exit_status = WEXITSTATUS(watcher->rstatus);
455
456     ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
457          child.pid,
458          exit_status);
459
460     /* this error is most likely caused by a user giving a nonexecutable or
461      * nonexistent file, so we will handle those cases separately. */
462     if (exit_status == 126)
463         set_statusline_error("status_command is not executable (exit %d)", exit_status);
464     else if (exit_status == 127)
465         set_statusline_error("status_command not found or is missing a library dependency (exit %d)", exit_status);
466     else
467         set_statusline_error("status_command process exited unexpectedly (exit %d)", exit_status);
468
469     cleanup();
470     draw_bars(false);
471 }
472
473 void child_write_output(void) {
474     if (child.click_events) {
475         const unsigned char *output;
476         size_t size;
477         ssize_t n;
478
479         yajl_gen_get_buf(gen, &output, &size);
480
481         n = writeall(child_stdin, output, size);
482         if (n != -1)
483             n = writeall(child_stdin, "\n", 1);
484
485         yajl_gen_clear(gen);
486
487         if (n == -1) {
488             child.click_events = false;
489             kill_child();
490             set_statusline_error("child_write_output failed");
491             draw_bars(false);
492         }
493     }
494 }
495
496 /*
497  * Start a child process with the specified command and reroute stdin.
498  * We actually start a $SHELL to execute the command so we don't have to care
499  * about arguments and such.
500  *
501  * If `command' is NULL, such as in the case when no `status_command' is given
502  * in the bar config, no child will be started.
503  *
504  */
505 void start_child(char *command) {
506     if (command == NULL)
507         return;
508
509     /* Allocate a yajl parser which will be used to parse stdin. */
510     static yajl_callbacks callbacks = {
511         .yajl_boolean = stdin_boolean,
512         .yajl_integer = stdin_integer,
513         .yajl_string = stdin_string,
514         .yajl_start_map = stdin_start_map,
515         .yajl_map_key = stdin_map_key,
516         .yajl_end_map = stdin_end_map,
517         .yajl_start_array = stdin_start_array,
518         .yajl_end_array = stdin_end_array,
519     };
520     parser = yajl_alloc(&callbacks, NULL, &parser_context);
521
522     gen = yajl_gen_alloc(NULL);
523
524     int pipe_in[2];  /* pipe we read from */
525     int pipe_out[2]; /* pipe we write to */
526
527     if (pipe(pipe_in) == -1)
528         err(EXIT_FAILURE, "pipe(pipe_in)");
529     if (pipe(pipe_out) == -1)
530         err(EXIT_FAILURE, "pipe(pipe_out)");
531
532     child.pid = fork();
533     switch (child.pid) {
534         case -1:
535             ELOG("Couldn't fork(): %s\n", strerror(errno));
536             exit(EXIT_FAILURE);
537         case 0:
538             /* Child-process. Reroute streams and start shell */
539
540             close(pipe_in[0]);
541             close(pipe_out[1]);
542
543             dup2(pipe_in[1], STDOUT_FILENO);
544             dup2(pipe_out[0], STDIN_FILENO);
545
546             setpgid(child.pid, 0);
547             execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL);
548             return;
549         default:
550             /* Parent-process. Reroute streams */
551
552             close(pipe_in[1]);
553             close(pipe_out[0]);
554
555             dup2(pipe_in[0], STDIN_FILENO);
556             child_stdin = pipe_out[1];
557
558             break;
559     }
560
561     /* We set O_NONBLOCK because blocking is evil in event-driven software */
562     fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
563
564     stdin_io = smalloc(sizeof(ev_io));
565     ev_io_init(stdin_io, &stdin_io_first_line_cb, STDIN_FILENO, EV_READ);
566     ev_io_start(main_loop, stdin_io);
567
568     /* We must cleanup, if the child unexpectedly terminates */
569     child_sig = smalloc(sizeof(ev_child));
570     ev_child_init(child_sig, &child_sig_cb, child.pid, 0);
571     ev_child_start(main_loop, child_sig);
572
573     atexit(kill_child_at_exit);
574 }
575
576 void child_click_events_initialize(void) {
577     if (!child.click_events_init) {
578         yajl_gen_array_open(gen);
579         child_write_output();
580         child.click_events_init = true;
581     }
582 }
583
584 void child_click_events_key(const char *key) {
585     yajl_gen_string(gen, (const unsigned char *)key, strlen(key));
586 }
587
588 /*
589  * Generates a click event, if enabled.
590  *
591  */
592 void send_block_clicked(int button, const char *name, const char *instance, int x, int y) {
593     if (!child.click_events) {
594         return;
595     }
596
597     child_click_events_initialize();
598
599     yajl_gen_map_open(gen);
600
601     if (name) {
602         child_click_events_key("name");
603         yajl_gen_string(gen, (const unsigned char *)name, strlen(name));
604     }
605
606     if (instance) {
607         child_click_events_key("instance");
608         yajl_gen_string(gen, (const unsigned char *)instance, strlen(instance));
609     }
610
611     child_click_events_key("button");
612     yajl_gen_integer(gen, button);
613
614     child_click_events_key("x");
615     yajl_gen_integer(gen, x);
616
617     child_click_events_key("y");
618     yajl_gen_integer(gen, y);
619
620     yajl_gen_map_close(gen);
621     child_write_output();
622 }
623
624 /*
625  * kill()s the child process (if any). Called when exit()ing.
626  *
627  */
628 void kill_child_at_exit(void) {
629     if (child.pid > 0) {
630         if (child.cont_signal > 0 && child.stopped)
631             killpg(child.pid, child.cont_signal);
632         killpg(child.pid, SIGTERM);
633     }
634 }
635
636 /*
637  * kill()s the child process (if existent) and closes and
638  * free()s the stdin- and SIGCHLD-watchers
639  *
640  */
641 void kill_child(void) {
642     if (child.pid > 0) {
643         if (child.cont_signal > 0 && child.stopped)
644             killpg(child.pid, child.cont_signal);
645         killpg(child.pid, SIGTERM);
646         int status;
647         waitpid(child.pid, &status, 0);
648         cleanup();
649     }
650 }
651
652 /*
653  * Sends a SIGSTOP to the child process (if existent)
654  *
655  */
656 void stop_child(void) {
657     if (child.stop_signal > 0 && !child.stopped) {
658         child.stopped = true;
659         killpg(child.pid, child.stop_signal);
660     }
661 }
662
663 /*
664  * Sends a SIGCONT to the child process (if existent)
665  *
666  */
667 void cont_child(void) {
668     if (child.cont_signal > 0 && child.stopped) {
669         child.stopped = false;
670         killpg(child.pid, child.cont_signal);
671     }
672 }
673
674 /*
675  * Whether or not the child want click events
676  *
677  */
678 bool child_want_click_events(void) {
679     return child.click_events;
680 }