i3 - improved tiling WM


introduced i3 command for showing/hiding/toggling i3bar

Patch status: needinfo

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.

append: immediately hide i3bar by all means when set to forcehide

fixes #833

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

b/i3bar/include/config.h

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

b/i3bar/src/config.c

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

b/i3bar/src/ipc.c

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

b/i3bar/src/xcb.c

139
@@ -198,7 +198,7 @@ void refresh_statusline(void) {
140
  *
141
  */
142
 void hide_bars(void) {
143
-    if (!config.hide_on_modifier) {
144
+    if (!config.hide_on_modifier || config.state == S_SHOW) {
145
         return;
146
     }
147
 
148
@@ -217,7 +217,7 @@ void hide_bars(void) {
149
  *
150
  */
151
 void unhide_bars(void) {
152
-    if (!config.hide_on_modifier) {
153
+    if (!config.hide_on_modifier || config.state == S_FORCEHIDE) {
154
         return;
155
     }
156
 
157
@@ -1716,11 +1716,14 @@ void draw_bars(bool unhide) {
158
         i = 0;
159
     }
160
 
161
+    /* Assure the bar is hidden/unhidden according to the specified state */
162
+    bool state_unhide = config.state == S_SHOW || (unhide && config.state == S_HIDE);
163
+    bool state_hide = config.state == S_FORCEHIDE;
164
+
165
     if (!mod_pressed) {
166
-        if (unhide) {
167
-            /* The urgent-hint should get noticed, so we unhide the bars shortly */
168
+        if ((unhide && !state_hide) || state_unhide) {
169
             unhide_bars();
170
-        } else if (walks_away) {
171
+        } else if (walks_away || state_hide) {
172
             FREE(last_urgent_ws);
173
             hide_bars();
174
         }

b/include/commands.h

179
@@ -265,4 +265,10 @@ void cmd_scratchpad_show(I3_CMD);
180
  */
181
 void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name);
182
 
183
+/**
184
+ * Implementation of 'bar [<bar_id>] hide|show|forcehide'
185
+ *
186
+ */
187
+void cmd_bar(I3_CMD, char* bar_id, char *state_str);
188
+
189
 #endif

b/include/config.h

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

b/include/config_directives.h

246
@@ -62,6 +62,8 @@ CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *ke
247
 
248
 CFGFUN(bar_font, const char *font);
249
 CFGFUN(bar_mode, const char *mode);
250
+CFGFUN(bar_state, const char *state);
251
+CFGFUN(bar_id, const char *bar_id);
252
 CFGFUN(bar_output, const char *output);
253
 CFGFUN(bar_verbose, const char *verbose);
254
 CFGFUN(bar_modifier, const char *modifier);

b/include/i3/ipc.h

259
@@ -99,4 +99,7 @@ typedef struct i3_ipc_header {
260
 /* The window event will be triggered upon window changes */
261
 #define I3_IPC_EVENT_WINDOW                     (I3_IPC_EVENT_MASK | 3)
262
 
263
+/** Bar config update will be triggered to update the bar config */
264
+#define I3_IPC_EVENT_BARCONFIG_UPDATE           (I3_IPC_EVENT_MASK | 4)
265
+
266
 #endif

b/parser-specs/commands.spec

271
@@ -35,6 +35,7 @@ state INITIAL:
272
   'nop' -> NOP
273
   'scratchpad' -> SCRATCHPAD
274
   'mode' -> MODE
275
+  'bar' -> BAR
276
 
277
 state CRITERIA:
278
   ctype = 'class' -> CRITERION
279
@@ -319,3 +320,14 @@ state NOP:
280
 state SCRATCHPAD:
281
   'show'
282
       -> call cmd_scratchpad_show()
283
+
284
+# bar [<bar_id>] hide|show|forcehide|toggle
285
+state BAR:
286
+  bar_state = 'hide', 'show', 'forcehide', 'toggle'
287
+      -> call cmd_bar(NULL, $bar_state)
288
+  bar_id = word
289
+      -> BAR_W_ID
290
+
291
+state BAR_W_ID:
292
+  bar_state = 'hide', 'show', 'forcehide', 'toggle'
293
+      -> call cmd_bar($bar_id, $bar_state)

b/parser-specs/config.spec

298
@@ -349,6 +349,8 @@ state BAR:
299
   'status_command'    -> BAR_STATUS_COMMAND
300
   'socket_path'       -> BAR_SOCKET_PATH
301
   'mode'              -> BAR_MODE
302
+  'state'             -> BAR_STATE
303
+  'id'                -> BAR_ID
304
   'modifier'          -> BAR_MODIFIER
305
   'position'          -> BAR_POSITION
306
   'output'            -> BAR_OUTPUT
307
@@ -381,6 +383,14 @@ state BAR_MODE:
308
   mode = 'dock', 'hide'
309
       -> call cfg_bar_mode($mode); BAR
310
 
311
+state BAR_STATE:
312
+  state = 'hide', 'show', 'forcehide'
313
+      -> call cfg_bar_state($state); BAR
314
+
315
+state BAR_ID:
316
+  bar_id = word
317
+      -> call cfg_bar_id($bar_id); BAR
318
+
319
 state BAR_MODIFIER:
320
   modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift'
321
       -> call cfg_bar_modifier($modifier); BAR

b/src/commands.c

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

b/src/config.c

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

b/src/config_directives.c

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

b/src/ipc.c

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