i3 - improved tiling WM


Add optional bidirectional interface to i3bar. (ver.2)

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))

The second version of this patch mainly introduced a better API for sending
commands.

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

b/docs/i3bar-protocol

30
@@ -51,7 +51,7 @@ consists of a single JSON hash:
31
 
32
 *All features example*:
33
 ------------------------------
34
-{ "version": 1, "stop_signal": 10, "cont_signal": 12 }
35
+{ "version": 1, "stop_signal": 10, "cont_signal": 12, "bidirectional": 1 }
36
 ------------------------------
37
 
38
 (Note that before i3 v4.3 the precise format had to be +{"version":1}+,
39
@@ -110,6 +110,9 @@ cont_signal::
40
 	Specify to i3bar the signal (as an integer)to send to continue your
41
 	processing.
42
 	The default value (if none is specified) is SIGCONT.
43
+bidirectional::
44
+	If specified and 1 i3bar will write a infinite array (same as above)
45
+	to your stdin.
46
 
47
 === Blocks in detail
48
 
49
@@ -183,3 +186,20 @@ An example of a block which uses all possible entries follows:
50
  "instance": "eth0"
51
 }
52
 ------------------------------------------
53
+
54
+=== Bidirectional communication
55
+
56
+If enabled i3bar will send you notifications about certain events, currently
57
+only one such notification is implmented: block_clicked.
58
+It is send if the user clicks on a block and looks like this:
59
+
60
+*Example*:
61
+------------------------------------------
62
+{
63
+ "command": "block_clicked",
64
+ "name": "ethernet",
65
+ "instance": "eth0",
66
+ "x": 1320,
67
+ "y": 1400
68
+}
69
+------------------------------------------

b/i3bar/include/child.h

74
@@ -33,6 +33,12 @@ typedef struct {
75
      * The signal requested by the client to inform it of theun hidden state of i3bar
76
      */
77
     int cont_signal;
78
+
79
+    /**
80
+     * Enable bi-directional communication, i.e. on-click events
81
+     */
82
+    bool bidirectional;
83
+    int bidirectional_init;
84
 } i3bar_child;
85
 
86
 /*
87
@@ -68,4 +74,10 @@ void stop_child(void);
88
  */
89
 void cont_child(void);
90
 
91
+/*
92
+ * ends the block_clicked command to the child
93
+ *
94
+ */
95
+void send_block_clicked(const char *name, const char *instance, int x, int y);
96
+
97
 #endif

b/i3bar/include/common.h

102
@@ -50,6 +50,10 @@ struct status_block {
103
     uint32_t x_offset;
104
     uint32_t x_append;
105
 
106
+    /* Optional */
107
+    char *name;
108
+    char *instance;
109
+
110
     TAILQ_ENTRY(status_block) blocks;
111
 };
112
 

b/i3bar/src/child.c

117
@@ -21,6 +21,7 @@
118
 #include <yajl/yajl_common.h>
119
 #include <yajl/yajl_parse.h>
120
 #include <yajl/yajl_version.h>
121
+#include <yajl/yajl_gen.h>
122
 
123
 #include "common.h"
124
 
125
@@ -35,6 +36,9 @@ ev_child *child_sig;
126
 yajl_callbacks callbacks;
127
 yajl_handle parser;
128
 
129
+/* JSON generator for stdout */
130
+yajl_gen gen;
131
+
132
 typedef struct parser_ctx {
133
     /* True if one of the parsed blocks was urgent */
134
     bool has_urgent;
135
@@ -141,6 +145,18 @@ static int stdin_string(void *context, const unsigned char *val, unsigned int le
136
             ctx->block.align = ALIGN_CENTER;
137
         }
138
     }
139
+    if (strcasecmp(ctx->last_map_key, "name") == 0) {
140
+        char *copy = (char*)malloc(len+1);
141
+        strncpy(copy, (const char *)val, len);
142
+        copy[len] = 0;
143
+        ctx->block.name = copy;
144
+    }
145
+    if (strcasecmp(ctx->last_map_key, "instance") == 0) {
146
+        char *copy = (char*)malloc(len+1);
147
+        strncpy(copy, (const char *)val, len);
148
+        copy[len] = 0;
149
+        ctx->block.instance = copy;
150
+    }
151
     return 1;
152
 }
153
 
154
@@ -322,6 +338,18 @@ void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
155
     cleanup();
156
 }
157
 
158
+void child_write_output(void) {
159
+    if(child.bidirectional) {
160
+        const unsigned char *output;
161
+        size_t size;
162
+        yajl_gen_get_buf(gen, &output, &size);
163
+        fwrite(output, 1, size, stdout);
164
+        fwrite("\n", 1, 1, stdout);
165
+        fflush(stdout);
166
+        yajl_gen_clear(gen);    
167
+    }
168
+}
169
+
170
 /*
171
  * Start a child-process with the specified command and reroute stdin.
172
  * We actually start a $SHELL to execute the command so we don't have to care
173
@@ -347,10 +375,14 @@ void start_child(char *command) {
174
     parser = yajl_alloc(&callbacks, NULL, &parser_context);
175
 #endif
176
 
177
+    gen = yajl_gen_alloc(NULL);
178
+
179
     if (command != NULL) {
180
-        int fd[2];
181
+        int fd[4];
182
         if (pipe(fd) == -1)
183
             err(EXIT_FAILURE, "pipe(fd)");
184
+        if (pipe(&fd[2]) == -1)
185
+            err(EXIT_FAILURE, "pipe(fd)");
186
 
187
         child.pid = fork();
188
         switch (child.pid) {
189
@@ -358,10 +390,10 @@ void start_child(char *command) {
190
                 ELOG("Couldn't fork(): %s\n", strerror(errno));
191
                 exit(EXIT_FAILURE);
192
             case 0:
193
-                /* Child-process. Reroute stdout and start shell */
194
-                close(fd[0]);
195
+                /* Child-process. Reroute streams and start shell */
196
 
197
                 dup2(fd[1], STDOUT_FILENO);
198
+                dup2(fd[2], STDIN_FILENO);
199
 
200
                 static const char *shell = NULL;
201
 
202
@@ -371,9 +403,9 @@ void start_child(char *command) {
203
                 execl(shell, shell, "-c", command, (char*) NULL);
204
                 return;
205
             default:
206
-                /* Parent-process. Rerout stdin */
207
-                close(fd[1]);
208
+                /* Parent-process. Reroute streams */
209
 
210
+                dup2(fd[3], STDOUT_FILENO);
211
                 dup2(fd[0], STDIN_FILENO);
212
 
213
                 break;
214
@@ -396,6 +428,66 @@ void start_child(char *command) {
215
 }
216
 
217
 /*
218
+ * Internal helper functions for bidirectional comms
219
+ *
220
+ */
221
+void child_bidi_initialize(void) {
222
+    if(!child.bidirectional_init) {
223
+        yajl_gen_array_open(gen);
224
+        child_write_output();
225
+        child.bidirectional_init = 1;
226
+    }    
227
+}
228
+
229
+void child_bidi_key(const char *key) {
230
+    yajl_gen_string(gen, (const unsigned char *)key, strlen(key));
231
+}
232
+
233
+void child_bidi_open(const char *command) {
234
+    child_bidi_initialize();
235
+
236
+    yajl_gen_map_open(gen);
237
+
238
+    child_bidi_key("command");
239
+    yajl_gen_string(gen, (const unsigned char *)command, strlen(command));
240
+}
241
+
242
+void child_bidi_close(void) {
243
+    yajl_gen_map_close(gen);
244
+    child_write_output();
245
+}
246
+
247
+/*
248
+ * sends the block_clicked command to the child
249
+ *
250
+ */
251
+void send_block_clicked(const char *name, const char *instance, int x, int y) {
252
+    if(child.bidirectional) {
253
+        child_bidi_open("block_clicked");
254
+
255
+        if(name) {
256
+            child_bidi_key("name");
257
+            yajl_gen_string(gen, (const unsigned char *)name, strlen(name));
258
+        }
259
+
260
+        if(instance) {
261
+            child_bidi_key("instance");
262
+            yajl_gen_string(gen, (const unsigned char *)instance, strlen(instance));
263
+        }
264
+
265
+        child_bidi_key("x");
266
+        yajl_gen_integer(gen, x);
267
+
268
+        child_bidi_key("y");
269
+        yajl_gen_integer(gen, y);
270
+
271
+        yajl_gen_map_close(gen);
272
+
273
+        child_write_output();
274
+    }
275
+}
276
+
277
+/*
278
  * kill()s the child-process (if any). Called when exit()ing.
279
  *
280
  */

b/i3bar/src/parse_json_header.c

285
@@ -31,6 +31,7 @@ static enum {
286
     KEY_VERSION,
287
     KEY_STOP_SIGNAL,
288
     KEY_CONT_SIGNAL,
289
+    KEY_BIDIRECTIONAL,
290
     NO_KEY
291
 } current_key;
292
 
293
@@ -51,6 +52,8 @@ static int header_integer(void *ctx, long val) {
294
         case KEY_CONT_SIGNAL:
295
             child->cont_signal = val;
296
             break;
297
+        case KEY_BIDIRECTIONAL:
298
+            child->bidirectional = val;
299
         default:
300
             break;
301
     }
302
@@ -71,6 +74,8 @@ static int header_map_key(void *ctx, const unsigned char *stringval, unsigned in
303
         current_key = KEY_STOP_SIGNAL;
304
     } else if (CHECK_KEY("cont_signal")) {
305
         current_key = KEY_CONT_SIGNAL;
306
+    } else if (CHECK_KEY("bidirectional")) {
307
+        current_key = KEY_BIDIRECTIONAL;
308
     }
309
     return 1;
310
 }

b/i3bar/src/xcb.c

315
@@ -160,7 +160,7 @@ void refresh_statusline(void) {
316
     xcb_poly_fill_rectangle(xcb_connection, statusline_pm, statusline_clear, 1, &rect);
317
 
318
     /* Draw the text of each block. */
319
-    uint32_t x = 0;
320
+    uint32_t x = 0; 
321
     TAILQ_FOREACH(block, &statusline_head, blocks) {
322
         if (i3string_get_num_bytes(block->full_text) == 0)
323
             continue;
324
@@ -306,6 +306,7 @@ void handle_button(xcb_button_press_event_t *event) {
325
     }
326
 
327
     int32_t x = event->event_x >= 0 ? event->event_x : 0;
328
+    int32_t original_x = x;
329
 
330
     DLOG("Got Button %d\n", event->detail);
331
 
332
@@ -321,6 +322,36 @@ void handle_button(xcb_button_press_event_t *event) {
333
                 x -= cur_ws->name_width + 11;
334
             }
335
             if (cur_ws == NULL) {
336
+                /* No workspace button was pressed.
337
+                 * Check if a status block has been clicked.
338
+                 * This of course only has an effect,
339
+                 * if the child reported bidirectional protocol usage. */
340
+
341
+                /* First calculate width of tray area */
342
+                trayclient *trayclient;
343
+                int tray_width = 0;
344
+                TAILQ_FOREACH_REVERSE(trayclient, walk->trayclients, tc_head, tailq) {
345
+                    if (!trayclient->mapped)
346
+                        continue;
347
+                    tray_width += (font.height + 2);
348
+                }
349
+
350
+                int block_x = 0, last_block_x;
351
+                int offset = (walk->rect.w - (statusline_width + tray_width)) - 10;
352
+                
353
+                x = original_x - offset > 0 ? original_x - offset : 0;
354
+
355
+                struct status_block *block;
356
+
357
+                TAILQ_FOREACH(block, &statusline_head, blocks) {
358
+                    last_block_x = block_x;
359
+                    block_x += block->width + block->x_offset + block->x_append;
360
+
361
+                    if(x <= block_x && x >= last_block_x) {
362
+                        send_block_clicked(block->name, block->instance, event->event_x, event->event_y);
363
+                        return;
364
+                    }
365
+                }
366
                 return;
367
             }
368
             break;