i3 - improved tiling WM


i3bar: Print error message when status command fails

Patch status: needinfo

Patch by Tony Crisci

Long description:

Add a function to i3bar to print an error message in the status line
when the child process invoked by the status command fails to provide
input that can be displayed as a statusline.

When the child provides JSON that cannot be parsed, alert the user and
convey a short message provided by yajl communicating the specific
problem.

When the child (or the shell executing the status command) exits
unexpectedly, alert the user and display the exit code. The cases where
the status command is not executable or not found in the user's PATH are
treated specially.

fixes #1130

To apply this patch, use:
curl http://cr.i3wm.org/patch/346/raw.patch | git am

b/i3bar/src/child.c

27
@@ -13,6 +13,7 @@
28
 #include <sys/wait.h>
29
 #include <signal.h>
30
 #include <stdio.h>
31
+#include <stdarg.h>
32
 #include <fcntl.h>
33
 #include <string.h>
34
 #include <errno.h>
35
@@ -61,6 +62,52 @@ char *statusline_buffer = NULL;
36
 int child_stdin;
37
 
38
 /*
39
+ * Clears all blocks from the statusline structure in memory and frees their
40
+ * associated resources.
41
+ */
42
+static void clear_status_blocks() {
43
+    struct status_block *first;
44
+    while (!TAILQ_EMPTY(&statusline_head)) {
45
+        first = TAILQ_FIRST(&statusline_head);
46
+        I3STRING_FREE(first->full_text);
47
+        TAILQ_REMOVE(&statusline_head, first, blocks);
48
+        free(first);
49
+    }
50
+}
51
+
52
+/*
53
+ * Replaces the statusline in memory with an error message. Pass a format
54
+ * string and format parameters as you would in `printf'. The next time
55
+ * `draw_bars' is called, the error message text will be drawn on the bar in
56
+ * the space allocated for the statusline.
57
+ */
58
+static void set_statusline_error(char *format, ...) {
59
+    clear_status_blocks();
60
+
61
+    char buffer[256];
62
+    va_list args;
63
+    va_start(args, format);
64
+    vsnprintf(buffer, 255, format, args);
65
+
66
+    struct status_block *err_block = scalloc(sizeof(struct status_block));
67
+    err_block->full_text = i3string_from_utf8("Error: ");
68
+    err_block->name = "error";
69
+    err_block->color = "red";
70
+    err_block->no_separator = true;
71
+
72
+    struct status_block *message_block = scalloc(sizeof(struct status_block));
73
+    message_block->full_text = i3string_from_utf8(buffer);
74
+    message_block->name = "error_message";
75
+    message_block->no_separator = true;
76
+    message_block->color = "red";
77
+
78
+    TAILQ_INSERT_HEAD(&statusline_head, err_block, blocks);
79
+    TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
80
+
81
+    va_end(args);
82
+}
83
+
84
+/*
85
  * Stop and free() the stdin- and sigchild-watchers
86
  *
87
  */
88
@@ -241,6 +288,7 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
89
             /* end of file, kill the watcher */
90
             ELOG("stdin: received EOF\n");
91
             cleanup();
92
+            set_statusline_error("Received EOF from statusline process");
93
             draw_bars(false);
94
             *ret_buffer_len = -1;
95
             return NULL;
96
@@ -280,8 +328,17 @@ static bool read_json_input(unsigned char *input, int length) {
97
 #else
98
     if (status != yajl_status_ok && status != yajl_status_insufficient_data) {
99
 #endif
100
-        fprintf(stderr, "[i3bar] Could not parse JSON input (code %d): %.*s\n",
101
-                status, length, input);
102
+        char *message = (char *)yajl_get_error(parser, 0, input, length);
103
+
104
+        /* strip the newline yajl adds to the error message */
105
+        if (message[strlen(message) - 1] == '\n')
106
+            message[strlen(message) - 1] = '\0';
107
+
108
+        fprintf(stderr, "[i3bar] Could not parse JSON input (code = %d, message = %s): %.*s\n",
109
+                status, message, length, input);
110
+
111
+        set_statusline_error("Could not parse JSON (%s)", message);
112
+        draw_bars(false);
113
     } else if (parser_context.has_urgent) {
114
         has_urgent = true;
115
     }
116
@@ -351,10 +408,23 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
117
  *
118
  */
119
 void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
120
+    int exit_status = WEXITSTATUS(watcher->rstatus);
121
+
122
     ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
123
            child.pid,
124
-           watcher->rstatus);
125
+           exit_status);
126
+
127
+    /* this error is most likely caused by a user giving a nonexecutable or
128
+     * nonexistent file, so we will handle those cases separately. */
129
+    if (exit_status == 126)
130
+        set_statusline_error("Status command is not executable (exit %d)", exit_status);
131
+    else if (exit_status == 127)
132
+        set_statusline_error("Status command not found (exit %d)", exit_status);
133
+    else
134
+        set_statusline_error("Statusline process exited unexpectedly (exit %d)", exit_status);
135
+
136
     cleanup();
137
+    draw_bars(false);
138
 }
139
 
140
 void child_write_output(void) {