i3 - improved tiling WM


Add optional bidirectional interface to i3bar.

Patch status: superseded

Patch by enkore

Long description:

If the child specifies bidirectional:1 in the protocl header,
a JSON array will be streamed to it's stdin.
It consists of maps with at least one key (command).

Such a map is emitted if the user clicks on a status block. i.e.
{"command":"block_clicked","name":"some_block_name","instance":"optional_instance"}

The basic output format is like the rest of the i3bar protocol (i.e.
a new line after each element (map in this case))

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

b/i3bar/include/child.h

26
@@ -33,6 +33,12 @@ typedef struct {
27
      * The signal requested by the client to inform it of theun hidden state of i3bar
28
      */
29
     int cont_signal;
30
+
31
+    /**
32
+     * Enable bi-directional communication, i.e. on-click events
33
+     */
34
+    bool bidirectional;
35
+    int bidirectional_init;
36
 } i3bar_child;
37
 
38
 /*
39
@@ -68,4 +74,10 @@ void stop_child(void);
40
  */
41
 void cont_child(void);
42
 
43
+/*
44
+ * sends a command to the child, if bidirectional communication is enabled.
45
+ *
46
+ */
47
+void send_child_command(const char *command, const char *name, const char *instance);
48
+
49
 #endif

b/i3bar/include/common.h

54
@@ -50,6 +50,10 @@ struct status_block {
55
     uint32_t x_offset;
56
     uint32_t x_append;
57
 
58
+    /* Optional */
59
+    char *name;
60
+    char *instance;
61
+
62
     TAILQ_ENTRY(status_block) blocks;
63
 };
64
 

b/i3bar/src/child.c

69
@@ -21,6 +21,7 @@
70
 #include <yajl/yajl_common.h>
71
 #include <yajl/yajl_parse.h>
72
 #include <yajl/yajl_version.h>
73
+#include <yajl/yajl_gen.h>
74
 
75
 #include "common.h"
76
 
77
@@ -35,6 +36,9 @@ ev_child *child_sig;
78
 yajl_callbacks callbacks;
79
 yajl_handle parser;
80
 
81
+/* JSON generator for stdout */
82
+yajl_gen gen;
83
+
84
 typedef struct parser_ctx {
85
     /* True if one of the parsed blocks was urgent */
86
     bool has_urgent;
87
@@ -141,6 +145,18 @@ static int stdin_string(void *context, const unsigned char *val, unsigned int le
88
             ctx->block.align = ALIGN_CENTER;
89
         }
90
     }
91
+    if (strcasecmp(ctx->last_map_key, "name") == 0) {
92
+        char *copy = (char*)malloc(len+1);
93
+        strncpy(copy, (const char *)val, len);
94
+        copy[len] = 0;
95
+        ctx->block.name = copy;
96
+    }
97
+    if (strcasecmp(ctx->last_map_key, "instance") == 0) {
98
+        char *copy = (char*)malloc(len+1);
99
+        strncpy(copy, (const char *)val, len);
100
+        copy[len] = 0;
101
+        ctx->block.instance = copy;
102
+    }
103
     return 1;
104
 }
105
 
106
@@ -322,6 +338,18 @@ void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
107
     cleanup();
108
 }
109
 
110
+void child_write_output(void) {
111
+    if(child.bidirectional) {
112
+        const unsigned char *output;
113
+        size_t size;
114
+        yajl_gen_get_buf(gen, &output, &size);
115
+        fwrite(output, 1, size, stdout);
116
+        fwrite("\n", 1, 1, stdout);
117
+        fflush(stdout);
118
+        yajl_gen_clear(gen);    
119
+    }
120
+}
121
+
122
 /*
123
  * Start a child-process with the specified command and reroute stdin.
124
  * We actually start a $SHELL to execute the command so we don't have to care
125
@@ -347,10 +375,14 @@ void start_child(char *command) {
126
     parser = yajl_alloc(&callbacks, NULL, &parser_context);
127
 #endif
128
 
129
+    gen = yajl_gen_alloc(NULL);
130
+
131
     if (command != NULL) {
132
-        int fd[2];
133
+        int fd[4];
134
         if (pipe(fd) == -1)
135
             err(EXIT_FAILURE, "pipe(fd)");
136
+        if (pipe(&fd[2]) == -1)
137
+            err(EXIT_FAILURE, "pipe(fd)");
138
 
139
         child.pid = fork();
140
         switch (child.pid) {
141
@@ -358,10 +390,10 @@ void start_child(char *command) {
142
                 ELOG("Couldn't fork(): %s\n", strerror(errno));
143
                 exit(EXIT_FAILURE);
144
             case 0:
145
-                /* Child-process. Reroute stdout and start shell */
146
-                close(fd[0]);
147
+                /* Child-process. Reroute streams and start shell */
148
 
149
                 dup2(fd[1], STDOUT_FILENO);
150
+                dup2(fd[2], STDIN_FILENO);
151
 
152
                 static const char *shell = NULL;
153
 
154
@@ -371,9 +403,9 @@ void start_child(char *command) {
155
                 execl(shell, shell, "-c", command, (char*) NULL);
156
                 return;
157
             default:
158
-                /* Parent-process. Rerout stdin */
159
-                close(fd[1]);
160
+                /* Parent-process. Reroute streams */
161
 
162
+                dup2(fd[3], STDOUT_FILENO);
163
                 dup2(fd[0], STDIN_FILENO);
164
 
165
                 break;
166
@@ -396,6 +428,38 @@ void start_child(char *command) {
167
 }
168
 
169
 /*
170
+ * sends a command to the child, if bidirectional communication is enabled.
171
+ *
172
+ */
173
+void send_child_command(const char *command, const char *name, const char *instance) {
174
+    if(child.bidirectional) {
175
+        if(!child.bidirectional_init) {
176
+            yajl_gen_array_open(gen);
177
+            child_write_output();
178
+            child.bidirectional_init = 1;
179
+        }
180
+        yajl_gen_map_open(gen);
181
+
182
+        yajl_gen_string(gen, (const unsigned char *)"command", strlen("command"));
183
+        yajl_gen_string(gen, (const unsigned char *)command, strlen(command));
184
+
185
+        if(name) {
186
+            yajl_gen_string(gen, (const unsigned char *)"name", strlen("name"));
187
+            yajl_gen_string(gen, (const unsigned char *)name, strlen(name));
188
+        }
189
+
190
+        if(instance) {
191
+            yajl_gen_string(gen, (const unsigned char *)"instance", strlen("instance"));
192
+            yajl_gen_string(gen, (const unsigned char *)instance, strlen(instance));
193
+        }
194
+
195
+        yajl_gen_map_close(gen);
196
+
197
+        child_write_output();
198
+    }
199
+}
200
+
201
+/*
202
  * kill()s the child-process (if any). Called when exit()ing.
203
  *
204
  */

b/i3bar/src/parse_json_header.c

209
@@ -31,6 +31,7 @@ static enum {
210
     KEY_VERSION,
211
     KEY_STOP_SIGNAL,
212
     KEY_CONT_SIGNAL,
213
+    KEY_BIDIRECTIONAL,
214
     NO_KEY
215
 } current_key;
216
 
217
@@ -51,6 +52,8 @@ static int header_integer(void *ctx, long val) {
218
         case KEY_CONT_SIGNAL:
219
             child->cont_signal = val;
220
             break;
221
+        case KEY_BIDIRECTIONAL:
222
+            child->bidirectional = val;
223
         default:
224
             break;
225
     }
226
@@ -71,6 +74,8 @@ static int header_map_key(void *ctx, const unsigned char *stringval, unsigned in
227
         current_key = KEY_STOP_SIGNAL;
228
     } else if (CHECK_KEY("cont_signal")) {
229
         current_key = KEY_CONT_SIGNAL;
230
+    } else if (CHECK_KEY("bidirectional")) {
231
+        current_key = KEY_BIDIRECTIONAL;
232
     }
233
     return 1;
234
 }

b/i3bar/src/xcb.c

239
@@ -160,7 +160,7 @@ void refresh_statusline(void) {
240
     xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
241
 
242
     /* Draw the text of each block. */
243
-    uint32_t x = 0;
244
+    uint32_t x = 0; 
245
     TAILQ_FOREACH(block, &statusline_head, blocks) {
246
         if (i3string_get_num_bytes(block->full_text) == 0)
247
             continue;
248
@@ -306,6 +306,7 @@ void handle_button(xcb_button_press_event_t *event) {
249
     }
250
 
251
     int32_t x = event->event_x >= 0 ? event->event_x : 0;
252
+    int32_t original_x = x;
253
 
254
     DLOG("Got Button %d\n", event->detail);
255
 
256
@@ -321,6 +322,40 @@ void handle_button(xcb_button_press_event_t *event) {
257
                 x -= cur_ws->name_width + 11;
258
             }
259
             if (cur_ws == NULL) {
260
+                /* No workspace button was pressed.
261
+                 * Check if a status block has been clicked.
262
+                 * This of course only has an effect,
263
+                 * if the child reported bidirectional protocol usage. */
264
+
265
+                /* First calculate width of tray area */
266
+                trayclient *trayclient;
267
+                int tray_width = 0;
268
+                TAILQ_FOREACH_REVERSE(trayclient, walk->trayclients, tc_head, tailq) {
269
+                    if (!trayclient->mapped)
270
+                        continue;
271
+                    tray_width += (font.height + 2);
272
+                }
273
+
274
+                /* Since we iterated over *all* buttons above (else we won't be here)
275
+                   we can calculate this width easily */
276
+                int workspace_button_width = (original_x - x);
277
+
278
+                int block_x = 0, last_block_x;
279
+                int offset = (walk->rect.w - (statusline_width + tray_width)) - 10;
280
+                
281
+                x = original_x - offset > 0 ? original_x - offset : 0;
282
+
283
+                struct status_block *block;
284
+
285
+                TAILQ_FOREACH(block, &statusline_head, blocks) {
286
+                    last_block_x = block_x;
287
+                    block_x += block->width + block->x_offset + block->x_append;
288
+
289
+                    if(x <= block_x && x >= last_block_x) {
290
+                        send_child_command("block_clicked", block->name, block->instance);
291
+                        break;
292
+                    }
293
+                }
294
                 return;
295
             }
296
             break;