i3 - improved tiling WM


introduced i3 command for showing/hiding/toggling i3bar

Patch status: superseded

Patch by jj

Long description:

The state of each i3bar instance can now be controlled from within i3.
Therefore, a new i3 command was introduced, which only affects bars in mode "hide":
    bar [<bar_id>] show|hide|forcehide|toggle

    show: always show the bar
    hide: normal hide mode
    forcehide: always hide the bar
    toggle: toggle between show and hide (individually for each bar)

This patch introduces a state ("state hide|show|forcehide") in the barconfig, which 
indicates the current state of each i3bar instance. In order to change the state of the 
bar from i3, a barconfig-update event was introduced, for which a bar can subsribe and 
the bar then gets notified about a state change in its barconfig.

For convenience, an id field ("id <bar_id>") was added to the barconfig, where one can 
set the desired id for the corresponding bar. If the id is not specified, i3 will 
deterministically choose an id; otherwise, with the previous random approach for finding
a new id, which is actually not shared with i3bar, as it would determine its id on 
startup, the event-subscription would be destroyed on reload. Still, this issue remains
when manually changing the bar_id in the config and then reloading.

fixes #833

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

b/i3bar/include/config.h

49
@@ -31,6 +31,18 @@ typedef struct config_t {
50
     char         *tray_output;
51
     int          num_outputs;
52
     char         **outputs;
53
+
54
+    /* The current state of the bar, which indicates whether it is hidden/shown/forcehidden */
55
+    enum {
56
+        /* bar is hidden, but can be unhidden by the bar modifier or urgency hints, etc. */
57
+        S_HIDE = 0,
58
+
59
+        /* always show the bar */
60
+        S_SHOW = 1,
61
+
62
+        /* always hide the bar */
63
+        S_FORCEHIDE = 2,
64
+    } state;
65
 } config_t;
66
 
67
 config_t config;

b/i3bar/src/config.c

72
@@ -77,6 +77,14 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
73
         return 1;
74
     }
75
 
76
+    if (!strcmp(cur_key, "state")) {
77
+        DLOG("state = %.*s, len = %d\n", len, val, len);
78
+        config.state = (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")) ? S_HIDE
79
+                        : (len == 4 && !strncmp((const char*)val, "show", strlen("show")) ? S_SHOW
80
+                            : S_FORCEHIDE));
81
+        return 1;
82
+    }
83
+
84
     if (!strcmp(cur_key, "modifier")) {
85
         DLOG("modifier = %.*s\n", len, val);
86
         if (len == 5 && !strncmp((const char*)val, "shift", strlen("shift"))) {

b/i3bar/src/ipc.c

91
@@ -107,6 +107,16 @@ void got_bar_config(char *reply) {
92
     FREE(config.command);
93
 }
94
 
95
+/*
96
+ * Called when we get a configuration update for our bar instance
97
+ *
98
+ */
99
+void got_bar_config_update(char *reply) {
100
+    DLOG("Received bar config update \"%s\"\n", reply);
101
+    parse_config_json(reply);
102
+    draw_bars(false);
103
+}
104
+
105
 /* Data-structure to easily call the reply-handlers later */
106
 handler_t reply_handlers[] = {
107
     &got_command_reply,
108
@@ -154,7 +164,9 @@ void got_mode_event(char *event) {
109
 handler_t event_handlers[] = {
110
     &got_workspace_event,
111
     &got_output_event,
112
-    &got_mode_event
113
+    &got_mode_event,
114
+    NULL,
115
+    &got_bar_config_update,
116
 };
117
 
118
 /*
119
@@ -309,9 +321,12 @@ void destroy_connection(void) {
120
  *
121
  */
122
 void subscribe_events(void) {
123
+    char *events;
124
     if (config.disable_ws) {
125
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\" ]");
126
+        sasprintf(&events, "[ \"output\", \"mode\", \"barconfig_update_%s\" ]", config.bar_id);
127
     } else {
128
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\" ]");
129
+        sasprintf(&events, "[ \"workspace\", \"output\", \"mode\", \"barconfig_update_%s\" ]", config.bar_id);
130
     }
131
+
132
+    i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, events);
133
 }

b/i3bar/src/xcb.c

138
@@ -198,7 +198,7 @@ void refresh_statusline(void) {
139
  *
140
  */
141
 void hide_bars(void) {
142
-    if (!config.hide_on_modifier) {
143
+    if (!config.hide_on_modifier || config.state == S_SHOW) {
144
         return;
145
     }
146
 
147
@@ -217,7 +217,7 @@ void hide_bars(void) {
148
  *
149
  */
150
 void unhide_bars(void) {
151
-    if (!config.hide_on_modifier) {
152
+    if (!config.hide_on_modifier || config.state == S_FORCEHIDE) {
153
         return;
154
     }
155
 
156
@@ -1716,6 +1716,9 @@ void draw_bars(bool unhide) {
157
         i = 0;
158
     }
159
 
160
+    /* Assure the bar is unhidden when configured to always show */
161
+    unhide |= config.state == S_SHOW;
162
+
163
     if (!mod_pressed) {
164
         if (unhide) {
165
             /* The urgent-hint should get noticed, so we unhide the bars shortly */

b/include/commands.h

170
@@ -265,4 +265,10 @@ void cmd_scratchpad_show(I3_CMD);
171
  */
172
 void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name);
173
 
174
+/**
175
+ * Implementation of 'bar [<bar_id>] hide|show|forcehide'
176
+ *
177
+ */
178
+void cmd_bar(I3_CMD, char* bar_id, char *state_str);
179
+
180
 #endif

b/include/config.h

185
@@ -199,6 +199,9 @@ struct Config {
186
         /* just ignore the popup, that is, don’t map it */
187
         PDF_IGNORE = 2,
188
     } popup_during_fullscreen;
189
+
190
+    /* The number of currently parsed barconfigs */
191
+    int number_barconfigs;
192
 };
193
 
194
 /**
195
@@ -229,6 +232,18 @@ struct Barconfig {
196
     /** Bar display mode (hide unless modifier is pressed or show in dock mode) */
197
     enum { M_DOCK = 0, M_HIDE = 1 } mode;
198
 
199
+    /* The current state of the bar, which indicates whether it is hidden/shown/forcehidden */
200
+    enum {
201
+        /* bar is hidden, but can be unhidden by the bar modifier or urgency hints, etc. */
202
+        S_HIDE = 0,
203
+
204
+        /* always show the bar */
205
+        S_SHOW = 1,
206
+
207
+        /* always hide the bar */
208
+        S_FORCEHIDE = 2,
209
+    } state;
210
+
211
     /** Bar modifier (to show bar when in hide mode). */
212
     enum {
213
         M_NONE = 0,
214
@@ -323,6 +338,18 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
215
  */
216
 void switch_mode(const char *new_mode);
217
 
218
+/*
219
+ * Sends a bar state update to the barconfig listener with the corresponding bar_id
220
+ *
221
+ */
222
+void update_barconfig_state(char* bar_id);
223
+
224
+/*
225
+ * Sends a bar state update to all barconfig listeners
226
+ *
227
+ */
228
+void update_barconfig_state_all();
229
+
230
 /**
231
  * Returns a pointer to the Binding with the specified modifiers and keycode
232
  * or NULL if no such binding exists.

b/include/config_directives.h

237
@@ -62,6 +62,8 @@ CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *ke
238
 
239
 CFGFUN(bar_font, const char *font);
240
 CFGFUN(bar_mode, const char *mode);
241
+CFGFUN(bar_state, const char *state);
242
+CFGFUN(bar_id, const char *bar_id);
243
 CFGFUN(bar_output, const char *output);
244
 CFGFUN(bar_verbose, const char *verbose);
245
 CFGFUN(bar_modifier, const char *modifier);

b/include/i3/ipc.h

250
@@ -99,4 +99,7 @@ typedef struct i3_ipc_header {
251
 /* The window event will be triggered upon window changes */
252
 #define I3_IPC_EVENT_WINDOW                     (I3_IPC_EVENT_MASK | 3)
253
 
254
+/** Bar config update will be triggered to update the bar config */
255
+#define I3_IPC_EVENT_BARCONFIG_UPDATE           (I3_IPC_EVENT_MASK | 4)
256
+
257
 #endif

b/parser-specs/commands.spec

262
@@ -35,6 +35,7 @@ state INITIAL:
263
   'nop' -> NOP
264
   'scratchpad' -> SCRATCHPAD
265
   'mode' -> MODE
266
+  'bar' -> BAR
267
 
268
 state CRITERIA:
269
   ctype = 'class' -> CRITERION
270
@@ -319,3 +320,14 @@ state NOP:
271
 state SCRATCHPAD:
272
   'show'
273
       -> call cmd_scratchpad_show()
274
+
275
+# bar [<bar_id>] hide|show|forcehide|toggle
276
+state BAR:
277
+  bar_state = 'hide', 'show', 'forcehide', 'toggle'
278
+      -> call cmd_bar(NULL, $bar_state)
279
+  bar_id = word
280
+      -> BAR_W_ID
281
+
282
+state BAR_W_ID:
283
+  bar_state = 'hide', 'show', 'forcehide', 'toggle'
284
+      -> call cmd_bar($bar_id, $bar_state)

b/parser-specs/config.spec

289
@@ -349,6 +349,8 @@ state BAR:
290
   'status_command'    -> BAR_STATUS_COMMAND
291
   'socket_path'       -> BAR_SOCKET_PATH
292
   'mode'              -> BAR_MODE
293
+  'state'             -> BAR_STATE
294
+  'id'                -> BAR_ID
295
   'modifier'          -> BAR_MODIFIER
296
   'position'          -> BAR_POSITION
297
   'output'            -> BAR_OUTPUT
298
@@ -381,6 +383,14 @@ state BAR_MODE:
299
   mode = 'dock', 'hide'
300
       -> call cfg_bar_mode($mode); BAR
301
 
302
+state BAR_STATE:
303
+  state = 'hide', 'show', 'forcehide'
304
+      -> call cfg_bar_state($state); BAR
305
+
306
+state BAR_ID:
307
+  bar_id = word
308
+      -> call cfg_bar_id($bar_id); BAR
309
+
310
 state BAR_MODIFIER:
311
   modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift'
312
       -> call cfg_bar_modifier($modifier); BAR

b/src/commands.c

317
@@ -1898,3 +1898,54 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
318
 
319
     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}");
320
 }
321
+
322
+/*
323
+ * Implementation of 'bar [<bar_id>] hide|show|forcehide|toggle'
324
+ *
325
+ */
326
+void cmd_bar(I3_CMD, char* bar_id, char *state_str) {
327
+    int state;
328
+    bool toggle = false;
329
+    if (strcmp(state_str, "hide") == 0)
330
+        state = S_HIDE;
331
+    else if (strcmp(state_str, "show") == 0)
332
+        state = S_SHOW;
333
+    else if (strcmp(state_str, "forcehide") == 0)
334
+        state = S_FORCEHIDE;
335
+    else if (strcmp(state_str, "toggle") == 0)
336
+        toggle = true;
337
+    else {
338
+        ELOG("Unknown bar state \"%s\", this is a mismatch between code and parser spec.\n", state_str);
339
+        return;
340
+    }
341
+
342
+    bool changed_state = false;
343
+    Barconfig *current = NULL;
344
+    TAILQ_FOREACH(current, &barconfigs, configs) {
345
+        if (bar_id && strcmp(current->id, bar_id) != 0)
346
+            continue;
347
+
348
+        if (toggle)
349
+            state = (current->state + 1) % 2;
350
+
351
+        DLOG("Changing bar state of bar_id %s to %s (%d)\n", bar_id, state_str, state);
352
+        current->state = state;
353
+        changed_state = true;
354
+
355
+        if (bar_id)
356
+             break;
357
+    }
358
+
359
+    if (bar_id && !changed_state) {
360
+        DLOG("Changing bar state of bar_id %s failed, bar_id not found.\n", bar_id);
361
+        ysuccess(false);
362
+        return;
363
+    }
364
+
365
+    ysuccess(true);
366
+
367
+    if (bar_id)
368
+        update_barconfig_state(bar_id);
369
+    else
370
+        update_barconfig_state_all();
371
+}

b/src/config.c

376
@@ -211,6 +211,71 @@ void switch_mode(const char *new_mode) {
377
 }
378
 
379
 /*
380
+ * Sends a bar state update to a specific barconfig listener
381
+ *
382
+ */
383
+void update_barconfig_state_on_config(Barconfig* config) {
384
+    /* Build json message */
385
+    char *state;
386
+    switch (config->state) {
387
+        case S_SHOW:
388
+            state ="show";
389
+            break;
390
+        case S_FORCEHIDE:
391
+            state ="forcehide";
392
+            break;
393
+        case S_HIDE:
394
+        default:
395
+            state = "hide";
396
+            break;
397
+    }
398
+
399
+    char *event_msg;
400
+    sasprintf(&event_msg, "{\"state\":\"%s\"}", state);
401
+
402
+    /* Send event to the bar with the specified bar_id */
403
+    char *event_id;
404
+    sasprintf(&event_id, "barconfig_update_%s", config->id);
405
+
406
+    ipc_send_event(event_id, I3_IPC_EVENT_BARCONFIG_UPDATE, event_msg);
407
+}
408
+
409
+/*
410
+ * Sends a bar state update to the barconfig listener with the corresponding bar_id
411
+ *
412
+ */
413
+void update_barconfig_state(char* bar_id) {
414
+    /* Get the corresponding barconfig */
415
+    Barconfig *current, *config;
416
+    TAILQ_FOREACH(current, &barconfigs, configs) {
417
+        if (strcmp(current->id, bar_id) != 0)
418
+            continue;
419
+
420
+        config = current;
421
+        break;
422
+    }
423
+
424
+    if (!config) {
425
+        /* We could not find a barconfig for the specified bar_id */
426
+        DLOG("Sending bar state update failed, specified bar_id %s not found.\n", bar_id);
427
+        return;
428
+    }
429
+
430
+    update_barconfig_state_on_config(config);
431
+}
432
+
433
+/*
434
+ * Sends a bar state update to all barconfig listeners
435
+ *
436
+ */
437
+void update_barconfig_state_all() {
438
+    Barconfig *current;
439
+    TAILQ_FOREACH(current, &barconfigs, configs) {
440
+        update_barconfig_state_on_config(current);
441
+    }
442
+}
443
+
444
+/*
445
  * Get the path of the first configuration file found. If override_configpath
446
  * is specified, that path is returned and saved for further calls. Otherwise,
447
  * checks the home directory first, then the system directory first, always

b/src/config_directives.c

452
@@ -455,6 +455,14 @@ CFGFUN(bar_mode, const char *mode) {
453
     current_bar.mode = (strcmp(mode, "hide") == 0 ? M_HIDE : M_DOCK);
454
 }
455
 
456
+CFGFUN(bar_state, const char *state) {
457
+    current_bar.state = (strcmp(state, "hide") == 0 ? S_HIDE : (strcmp(state, "show") == 0 ? S_SHOW : S_FORCEHIDE));
458
+}
459
+
460
+CFGFUN(bar_id, const char *bar_id) {
461
+    current_bar.id = sstrdup(bar_id);
462
+}
463
+
464
 CFGFUN(bar_output, const char *output) {
465
     int new_outputs = current_bar.num_outputs + 1;
466
     current_bar.outputs = srealloc(current_bar.outputs, sizeof(char*) * new_outputs);
467
@@ -548,16 +556,15 @@ CFGFUN(bar_workspace_buttons, const char *value) {
468
 
469
 CFGFUN(bar_finish) {
470
     DLOG("\t new bar configuration finished, saving.\n");
471
-    /* Generate a unique ID for this bar */
472
-    current_bar.id = sstrdup("bar-XXXXXX");
473
-    /* This works similar to mktemp in that it replaces the last six X with
474
-     * random letters, but without the restriction that the given buffer
475
-     * has to contain a valid path name. */
476
-    char *x = current_bar.id + strlen("bar-");
477
-    while (*x != '\0') {
478
-        *(x++) = (rand() % 26) + 'a';
479
+    /* Generate a unique ID for this bar if not already configured */
480
+    if (!current_bar.id) {
481
+        char* buf;
482
+        sasprintf(&buf, "bar-%d", config.number_barconfigs);
483
+        current_bar.id = sstrdup(buf);
484
     }
485
 
486
+    config.number_barconfigs++;
487
+
488
     /* If no font was explicitly set, we use the i3 font as default */
489
     if (!current_bar.font && font_pattern)
490
         current_bar.font = sstrdup(font_pattern);

b/src/ipc.c

495
@@ -625,6 +625,20 @@ IPC_HANDLER(get_bar_config) {
496
             ystr("hide");
497
         else ystr("dock");
498
 
499
+        ystr("state");
500
+        switch (config->state) {
501
+            case S_SHOW:
502
+                ystr("show");
503
+                break;
504
+            case S_FORCEHIDE:
505
+                ystr("forcehide");
506
+                break;
507
+            case S_HIDE:
508
+            default:
509
+                ystr("hide");
510
+                break;
511
+        }
512
+
513
         ystr("modifier");
514
         switch (config->modifier) {
515
             case M_CONTROL:
516
@@ -710,6 +724,8 @@ IPC_HANDLER(get_bar_config) {
517
     y(free);
518
 }
519
 
520
+#undef YSTR_WRITE_STATE
521
+
522
 /*
523
  * Callback for the YAJL parser (will be called when a string is parsed).
524
  *