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 show|hide|forcehide|toggle [<bar_id>]

    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

append 2: resolved comments from Michael + initially hide i3bar when
switching to state hide + send barconfig_update event on reload just in
case it changed.

fixes #833

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

b/docs/ipc

55
@@ -626,6 +626,8 @@ mode (2)::
56
 window (3)::
57
 	Sent when a client's window is successfully reparented (that is when i3
58
 	has finished fitting it into a container).
59
+barconfig_update_<bar_id> (4)::
60
+    Sent when the state field in the barconfig of the specified bar_id changes.
61
 
62
 *Example:*
63
 --------------------------------------------------------------------
64
@@ -723,6 +725,18 @@ window title as "urxvt").
65
 }
66
 ---------------------------
67
 
68
+=== barconfig_update_<bar_id> event
69
+
70
+This event consists of a single serialized map reporting on options from the
71
+barconfig of the specified bar_id that were updated in i3. Currently, the map
72
+consists of a single property +state (string)+, which indicates the hidden state
73
+of an i3bar instance (the value can be "hide|show|forcehide").
74
+
75
+*Example:*
76
+---------------------------
77
+{ "state": "hide" }
78
+---------------------------
79
+
80
 == See also (existing libraries)
81
 
82
 [[libraries]]

b/i3bar/include/config.h

87
@@ -31,6 +31,18 @@ typedef struct config_t {
88
     char         *tray_output;
89
     int          num_outputs;
90
     char         **outputs;
91
+
92
+    /* The current state of the bar, which indicates whether it is hidden/shown/forcehidden */
93
+    enum {
94
+        /* bar is hidden, but can be unhidden by the bar modifier or urgency hints, etc. */
95
+        S_HIDE = 0,
96
+
97
+        /* always show the bar */
98
+        S_SHOW = 1,
99
+
100
+        /* always hide the bar */
101
+        S_FORCEHIDE = 2,
102
+    } state;
103
 } config_t;
104
 
105
 config_t config;

b/i3bar/src/config.c

110
@@ -77,6 +77,14 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
111
         return 1;
112
     }
113
 
114
+    if (!strcmp(cur_key, "state")) {
115
+        DLOG("state = %.*s, len = %d\n", len, val, len);
116
+        config.state = (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")) ? S_HIDE
117
+                        : (len == 4 && !strncmp((const char*)val, "show", strlen("show")) ? S_SHOW
118
+                            : S_FORCEHIDE));
119
+        return 1;
120
+    }
121
+
122
     if (!strcmp(cur_key, "modifier")) {
123
         DLOG("modifier = %.*s\n", len, val);
124
         if (len == 5 && !strncmp((const char*)val, "shift", strlen("shift"))) {

b/i3bar/src/ipc.c

129
@@ -107,6 +107,23 @@ void got_bar_config(char *reply) {
130
     FREE(config.command);
131
 }
132
 
133
+/*
134
+ * Called when we get a configuration update for our bar instance
135
+ *
136
+ */
137
+void got_bar_config_update(char *reply) {
138
+    DLOG("Received bar config update \"%s\"\n", reply);
139
+    parse_config_json(reply);
140
+    if (config.state == S_HIDE) {
141
+        /* Initially hide, when switch to state hide */
142
+        config.state = S_FORCEHIDE;
143
+        draw_bars(false);
144
+        config.state = S_HIDE;
145
+    } else {
146
+        draw_bars(false);
147
+    }
148
+}
149
+
150
 /* Data-structure to easily call the reply-handlers later */
151
 handler_t reply_handlers[] = {
152
     &got_command_reply,
153
@@ -154,7 +171,9 @@ void got_mode_event(char *event) {
154
 handler_t event_handlers[] = {
155
     &got_workspace_event,
156
     &got_output_event,
157
-    &got_mode_event
158
+    &got_mode_event,
159
+    NULL,
160
+    &got_bar_config_update,
161
 };
162
 
163
 /*
164
@@ -309,9 +328,12 @@ void destroy_connection(void) {
165
  *
166
  */
167
 void subscribe_events(void) {
168
+    char *events;
169
     if (config.disable_ws) {
170
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\" ]");
171
+        sasprintf(&events, "[ \"output\", \"mode\", \"barconfig_update_%s\" ]", config.bar_id);
172
     } else {
173
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\" ]");
174
+        sasprintf(&events, "[ \"workspace\", \"output\", \"mode\", \"barconfig_update_%s\" ]", config.bar_id);
175
     }
176
+
177
+    i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, events);
178
 }

b/i3bar/src/xcb.c

183
@@ -198,7 +198,7 @@ void refresh_statusline(void) {
184
  *
185
  */
186
 void hide_bars(void) {
187
-    if (!config.hide_on_modifier) {
188
+    if (!config.hide_on_modifier || config.state == S_SHOW) {
189
         return;
190
     }
191
 
192
@@ -217,7 +217,7 @@ void hide_bars(void) {
193
  *
194
  */
195
 void unhide_bars(void) {
196
-    if (!config.hide_on_modifier) {
197
+    if (!config.hide_on_modifier || config.state == S_FORCEHIDE) {
198
         return;
199
     }
200
 
201
@@ -1716,11 +1716,14 @@ void draw_bars(bool unhide) {
202
         i = 0;
203
     }
204
 
205
+    /* Assure the bar is hidden/unhidden according to the specified state */
206
+    bool state_unhide = (config.state == S_SHOW || (unhide && config.state == S_HIDE));
207
+    bool state_hide = (config.state == S_FORCEHIDE);
208
+
209
     if (!mod_pressed) {
210
-        if (unhide) {
211
-            /* The urgent-hint should get noticed, so we unhide the bars shortly */
212
+        if ((unhide && !state_hide) || state_unhide) {
213
             unhide_bars();
214
-        } else if (walks_away) {
215
+        } else if (walks_away || state_hide) {
216
             FREE(last_urgent_ws);
217
             hide_bars();
218
         }

b/include/commands.h

223
@@ -265,4 +265,10 @@ void cmd_scratchpad_show(I3_CMD);
224
  */
225
 void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name);
226
 
227
+/**
228
+ * Implementation of 'bar hide|show|forcehide|toggle [<bar_id>]'
229
+ *
230
+ */
231
+void cmd_bar(I3_CMD, char *bar_id, char *state_str);
232
+
233
 #endif

b/include/config.h

238
@@ -199,6 +199,9 @@ struct Config {
239
         /* just ignore the popup, that is, don’t map it */
240
         PDF_IGNORE = 2,
241
     } popup_during_fullscreen;
242
+
243
+    /* The number of currently parsed barconfigs */
244
+    int number_barconfigs;
245
 };
246
 
247
 /**
248
@@ -229,6 +232,18 @@ struct Barconfig {
249
     /** Bar display mode (hide unless modifier is pressed or show in dock mode) */
250
     enum { M_DOCK = 0, M_HIDE = 1 } mode;
251
 
252
+    /* The current state of the bar, which indicates whether it is hidden/shown/forcehidden */
253
+    enum {
254
+        /* bar is hidden, but can be unhidden by the bar modifier or urgency hints, etc. */
255
+        S_HIDE = 0,
256
+
257
+        /* always show the bar */
258
+        S_SHOW = 1,
259
+
260
+        /* always hide the bar */
261
+        S_FORCEHIDE = 2,
262
+    } state;
263
+
264
     /** Bar modifier (to show bar when in hide mode). */
265
     enum {
266
         M_NONE = 0,
267
@@ -323,6 +338,18 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
268
  */
269
 void switch_mode(const char *new_mode);
270
 
271
+/*
272
+ * Sends a bar state update to the barconfig listener with the corresponding bar_id
273
+ *
274
+ */
275
+void update_barconfig_state(char *bar_id);
276
+
277
+/*
278
+ * Sends a bar state update to all barconfig listeners
279
+ *
280
+ */
281
+void update_barconfig_state_all();
282
+
283
 /**
284
  * Returns a pointer to the Binding with the specified modifiers and keycode
285
  * or NULL if no such binding exists.

b/include/config_directives.h

290
@@ -62,6 +62,8 @@ CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *ke
291
 
292
 CFGFUN(bar_font, const char *font);
293
 CFGFUN(bar_mode, const char *mode);
294
+CFGFUN(bar_state, const char *state);
295
+CFGFUN(bar_id, const char *bar_id);
296
 CFGFUN(bar_output, const char *output);
297
 CFGFUN(bar_verbose, const char *verbose);
298
 CFGFUN(bar_modifier, const char *modifier);

b/include/i3/ipc.h

303
@@ -99,4 +99,7 @@ typedef struct i3_ipc_header {
304
 /* The window event will be triggered upon window changes */
305
 #define I3_IPC_EVENT_WINDOW                     (I3_IPC_EVENT_MASK | 3)
306
 
307
+/** Bar config update will be triggered to update the bar config */
308
+#define I3_IPC_EVENT_BARCONFIG_UPDATE           (I3_IPC_EVENT_MASK | 4)
309
+
310
 #endif

b/parser-specs/commands.spec

315
@@ -35,6 +35,7 @@ state INITIAL:
316
   'nop' -> NOP
317
   'scratchpad' -> SCRATCHPAD
318
   'mode' -> MODE
319
+  'bar' -> BAR
320
 
321
 state CRITERIA:
322
   ctype = 'class' -> CRITERION
323
@@ -319,3 +320,14 @@ state NOP:
324
 state SCRATCHPAD:
325
   'show'
326
       -> call cmd_scratchpad_show()
327
+
328
+# bar hide|show|forcehide|toggle [<bar_id>]
329
+state BAR:
330
+  bar_state = 'hide', 'show', 'forcehide', 'toggle'
331
+      -> BAR_W_ID
332
+
333
+state BAR_W_ID:
334
+  bar_id = word
335
+      -> call cmd_bar($bar_id, $bar_state)
336
+  end
337
+      -> call cmd_bar(NULL, $bar_state)

b/parser-specs/config.spec

342
@@ -349,6 +349,8 @@ state BAR:
343
   'status_command'    -> BAR_STATUS_COMMAND
344
   'socket_path'       -> BAR_SOCKET_PATH
345
   'mode'              -> BAR_MODE
346
+  'state'             -> BAR_STATE
347
+  'id'                -> BAR_ID
348
   'modifier'          -> BAR_MODIFIER
349
   'position'          -> BAR_POSITION
350
   'output'            -> BAR_OUTPUT
351
@@ -381,6 +383,14 @@ state BAR_MODE:
352
   mode = 'dock', 'hide'
353
       -> call cfg_bar_mode($mode); BAR
354
 
355
+state BAR_STATE:
356
+  state = 'hide', 'show', 'forcehide'
357
+      -> call cfg_bar_state($state); BAR
358
+
359
+state BAR_ID:
360
+  bar_id = word
361
+      -> call cfg_bar_id($bar_id); BAR
362
+
363
 state BAR_MODIFIER:
364
   modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift'
365
       -> call cfg_bar_modifier($modifier); BAR

b/src/commands.c

370
@@ -1615,6 +1615,8 @@ void cmd_reload(I3_CMD) {
371
     x_set_i3_atoms();
372
     /* Send an IPC event just in case the ws names have changed */
373
     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
374
+    /* Update the state in the barconfig just in case it has changed */
375
+    update_barconfig_state_all();
376
 
377
     // XXX: default reply for now, make this a better reply
378
     ysuccess(true);
379
@@ -1898,3 +1900,54 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
380
 
381
     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}");
382
 }
383
+
384
+/*
385
+ * Implementation of 'bar hide|show|forcehide|toggle [<bar_id>]'
386
+ *
387
+ */
388
+void cmd_bar(I3_CMD, char *bar_id, char *state_str) {
389
+    int state;
390
+    bool toggle = false;
391
+    if (strcmp(state_str, "hide") == 0)
392
+        state = S_HIDE;
393
+    else if (strcmp(state_str, "show") == 0)
394
+        state = S_SHOW;
395
+    else if (strcmp(state_str, "forcehide") == 0)
396
+        state = S_FORCEHIDE;
397
+    else if (strcmp(state_str, "toggle") == 0)
398
+        toggle = true;
399
+    else {
400
+        ELOG("Unknown bar state \"%s\", this is a mismatch between code and parser spec.\n", state_str);
401
+        return;
402
+    }
403
+
404
+    bool changed_state = false;
405
+    Barconfig *current = NULL;
406
+    TAILQ_FOREACH(current, &barconfigs, configs) {
407
+        if (bar_id && strcmp(current->id, bar_id) != 0)
408
+            continue;
409
+
410
+        if (toggle)
411
+            state = (current->state + 1) % 2;
412
+
413
+        DLOG("Changing bar state of bar_id %s to %s (%d)\n", bar_id, state_str, state);
414
+        current->state = state;
415
+        changed_state = true;
416
+
417
+        if (bar_id)
418
+             break;
419
+    }
420
+
421
+    if (bar_id && !changed_state) {
422
+        DLOG("Changing bar state of bar_id %s failed, bar_id not found.\n", bar_id);
423
+        ysuccess(false);
424
+        return;
425
+    }
426
+
427
+    ysuccess(true);
428
+
429
+    if (bar_id)
430
+        update_barconfig_state(bar_id);
431
+    else
432
+        update_barconfig_state_all();
433
+}

b/src/config.c

438
@@ -211,6 +211,71 @@ void switch_mode(const char *new_mode) {
439
 }
440
 
441
 /*
442
+ * Sends a bar state update to a specific barconfig listener
443
+ *
444
+ */
445
+void update_barconfig_state_on_config(Barconfig *config) {
446
+    /* Build json message */
447
+    char *state;
448
+    switch (config->state) {
449
+        case S_SHOW:
450
+            state ="show";
451
+            break;
452
+        case S_FORCEHIDE:
453
+            state ="forcehide";
454
+            break;
455
+        case S_HIDE:
456
+        default:
457
+            state = "hide";
458
+            break;
459
+    }
460
+
461
+    char *event_msg;
462
+    sasprintf(&event_msg, "{\"state\":\"%s\"}", state);
463
+
464
+    /* Send event to the bar with the specified bar_id */
465
+    char *event_id;
466
+    sasprintf(&event_id, "barconfig_update_%s", config->id);
467
+
468
+    ipc_send_event(event_id, I3_IPC_EVENT_BARCONFIG_UPDATE, event_msg);
469
+}
470
+
471
+/*
472
+ * Sends a bar state update to the barconfig listener with the corresponding bar_id
473
+ *
474
+ */
475
+void update_barconfig_state(char *bar_id) {
476
+    /* Get the corresponding barconfig */
477
+    Barconfig *current, *config;
478
+    TAILQ_FOREACH(current, &barconfigs, configs) {
479
+        if (strcmp(current->id, bar_id) != 0)
480
+            continue;
481
+
482
+        config = current;
483
+        break;
484
+    }
485
+
486
+    if (!config) {
487
+        /* We could not find a barconfig for the specified bar_id */
488
+        DLOG("Sending bar state update failed, specified bar_id %s not found.\n", bar_id);
489
+        return;
490
+    }
491
+
492
+    update_barconfig_state_on_config(config);
493
+}
494
+
495
+/*
496
+ * Sends a bar state update to all barconfig listeners
497
+ *
498
+ */
499
+void update_barconfig_state_all() {
500
+    Barconfig *current;
501
+    TAILQ_FOREACH(current, &barconfigs, configs) {
502
+        update_barconfig_state_on_config(current);
503
+    }
504
+}
505
+
506
+/*
507
  * Get the path of the first configuration file found. If override_configpath
508
  * is specified, that path is returned and saved for further calls. Otherwise,
509
  * checks the home directory first, then the system directory first, always

b/src/config_directives.c

514
@@ -455,6 +455,14 @@ CFGFUN(bar_mode, const char *mode) {
515
     current_bar.mode = (strcmp(mode, "hide") == 0 ? M_HIDE : M_DOCK);
516
 }
517
 
518
+CFGFUN(bar_state, const char *state) {
519
+    current_bar.state = (strcmp(state, "hide") == 0 ? S_HIDE : (strcmp(state, "show") == 0 ? S_SHOW : S_FORCEHIDE));
520
+}
521
+
522
+CFGFUN(bar_id, const char *bar_id) {
523
+    current_bar.id = sstrdup(bar_id);
524
+}
525
+
526
 CFGFUN(bar_output, const char *output) {
527
     int new_outputs = current_bar.num_outputs + 1;
528
     current_bar.outputs = srealloc(current_bar.outputs, sizeof(char*) * new_outputs);
529
@@ -548,15 +556,11 @@ CFGFUN(bar_workspace_buttons, const char *value) {
530
 
531
 CFGFUN(bar_finish) {
532
     DLOG("\t new bar configuration finished, saving.\n");
533
-    /* Generate a unique ID for this bar */
534
-    current_bar.id = sstrdup("bar-XXXXXX");
535
-    /* This works similar to mktemp in that it replaces the last six X with
536
-     * random letters, but without the restriction that the given buffer
537
-     * has to contain a valid path name. */
538
-    char *x = current_bar.id + strlen("bar-");
539
-    while (*x != '\0') {
540
-        *(x++) = (rand() % 26) + 'a';
541
-    }
542
+    /* Generate a unique ID for this bar if not already configured */
543
+    if (!current_bar.id)
544
+        sasprintf(&current_bar.id, "bar-%d", config.number_barconfigs);
545
+
546
+    config.number_barconfigs++;
547
 
548
     /* If no font was explicitly set, we use the i3 font as default */
549
     if (!current_bar.font && font_pattern)

b/src/ipc.c

554
@@ -625,6 +625,20 @@ IPC_HANDLER(get_bar_config) {
555
             ystr("hide");
556
         else ystr("dock");
557
 
558
+        ystr("state");
559
+        switch (config->state) {
560
+            case S_SHOW:
561
+                ystr("show");
562
+                break;
563
+            case S_FORCEHIDE:
564
+                ystr("forcehide");
565
+                break;
566
+            case S_HIDE:
567
+            default:
568
+                ystr("hide");
569
+                break;
570
+        }
571
+
572
         ystr("modifier");
573
         switch (config->modifier) {
574
             case M_CONTROL:
575
@@ -710,6 +724,8 @@ IPC_HANDLER(get_bar_config) {
576
     y(free);
577
 }
578
 
579
+#undef YSTR_WRITE_STATE
580
+
581
 /*
582
  * Callback for the YAJL parser (will be called when a string is parsed).
583
  *