i3 - improved tiling WM


introduced i3 command for changing the hidden state and the mode of i3bar

Patch status: superseded

Patch by jj

Long description:

The state and mode of each i3bar instance can now be controlled from within i3.
Therefore, two new i3 command were introduced:
    _
    bar state show|hide|toggle [<bar_id>]

    show: always show the bar
    hide: normal hide mode
    toggle: toggle between show and hide (individually for each bar)
    _
    bar mode dock|hide|invisible|toggle [<bar_id>]

    hide,dock: like before
    invisible: always keep the bar hidden
    toggle: toggle between dock and hide (individually for each bar)

This patch introduces a state ("state hide|show") in the barconfig,
which indicates the current state of each i3bar instance. It only affects the
bar when in hide mode. Additionally, a new invisible mode was introduced. In
order to change the state or mode of the bar from i3, a barconfig-update event
was introduced, for which a bar can subscribe and the bar then gets notified
about the currently set state and mode 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, #651

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

b/docs/ipc

61
@@ -626,6 +626,9 @@ mode (2)::
62
 window (3)::
63
 	Sent when a client's window is successfully reparented (that is when i3
64
 	has finished fitting it into a container).
65
+barconfig_update (4)::
66
+    Sent when the state or mode field in the barconfig of any bar instance was
67
+    updated.
68
 
69
 *Example:*
70
 --------------------------------------------------------------------
71
@@ -723,6 +726,24 @@ window title as "urxvt").
72
 }
73
 ---------------------------
74
 
75
+=== barconfig_update event
76
+
77
+This event consists of a single serialized map reporting on options from the
78
+barconfig of the specified bar_id that were updated in i3. The map always
79
+consists of a property +id (string)+, which specifies to which bar instance the
80
+send config update belongs, a property +state (string)+, which indicates the
81
+hidden state of an i3bar instance, and a property +mode (string)+, which
82
+corresponds to current mode.
83
+
84
+*Example:*
85
+---------------------------
86
+{
87
+    "id": "bar-0",
88
+    "state": "hide"
89
+    "mode": "hide"
90
+}
91
+---------------------------
92
+
93
 == See also (existing libraries)
94
 
95
 [[libraries]]

b/docs/userguide

100
@@ -997,19 +997,31 @@ bar {
101
 === Display mode
102
 
103
 You can have i3bar either be visible permanently at one edge of the screen
104
-(+dock+ mode) or make it show up when you press your modifier key (+hide+
105
+(+dock+ mode) or make it show up when you press your modifier key (+hide+ mode),
106
+i3bar can also be forced to be hidden until the mode changes again (+invisible+
107
 mode). The modifier key can be configured using the +modifier+ option.
108
 
109
 The hide mode maximizes screen space that can be used for actual windows. Also,
110
 i3bar sends the +SIGSTOP+ and +SIGCONT+ signals to the statusline process to
111
 save battery power.
112
 
113
-The default is dock mode; in hide mode, the default modifier is Mod4 (usually
114
-the windows key).
115
+The state option for i3bar is only relevant for hide mode and has no effect in
116
+dock mode or invisible mode. It indicates the current state of the bar: (1) The
117
+bar acts like in normal hide mode, it is hidden and is only unhidden in case of
118
+urgency hints or by pressing the modifier key (+hide+ state), or (2) it is
119
+drawn on top of the currently visible workspace (+show+ state).
120
+
121
+Both, the state and the mode option can be changed during runtime through the
122
+corresponding bar commands and will be reverted to the configured values on
123
+reload.
124
+
125
+The default mode is dock mode; in hide mode, the default modifier is Mod4 (usually
126
+the windows key). The default value for the state is hide.
127
 
128
 *Syntax*:
129
 ----------------
130
-mode <dock|hide>
131
+mode <dock|hide|invisible>
132
+state <hide|show>
133
 modifier <Modifier>
134
 ----------------
135
 
136
@@ -1017,12 +1029,31 @@ modifier <Modifier>
137
 ----------------
138
 bar {
139
     mode hide
140
+    state hide
141
     modifier Mod1
142
 }
143
 ----------------
144
 
145
 Available modifiers are Mod1-Mod5, Shift, Control (see +xmodmap(1)+).
146
 
147
+=== Bar ID
148
+
149
+Specifies the bar ID for the configured bar instance. If this option is missing,
150
+the ID is set to 'bar-x', where x corresponds to the position of the embedding
151
+bar block in the config file ('bar-0', 'bar-1', ...).
152
+
153
+*Syntax*:
154
+---------------------
155
+id <bar_id>
156
+---------------------
157
+
158
+*Example*:
159
+---------------------
160
+bar {
161
+    id bar-1
162
+}
163
+---------------------
164
+
165
 [[i3bar_position]]
166
 === Position
167
 
168
@@ -1775,6 +1806,35 @@ bindsym $mod+minus scratchpad show
169
 bindsym mod4+s [title="^Sup ::"] scratchpad show
170
 ------------------------------------------------
171
 
172
+=== i3bar control
173
+
174
+There are two options in the configuration of each i3bar instance that can be
175
+changed during runtime by invoking a command through i3. The commands +bar
176
+state+ and +bar mode+ allow setting the current state respectively mode option
177
+of each bar. It is also possible to toggle between hide state and show state as
178
+well as between dock mode and hide mode. Each i3bar instance can be controlled
179
+individually by specifying a bar_id, if none is given, the command is executed
180
+for all bar instances.
181
+
182
+*Syntax*:
183
+---------------
184
+bar state hide|show|toggle [<bar_id>]
185
+
186
+bar mode dock|hide|invisible|toggle [<bar_id>]
187
+---------------
188
+
189
+*Examples*:
190
+------------------------------------------------
191
+# Toggle between hide state and show state
192
+bindsym $mod+b bar state toggle
193
+
194
+# Set the bar instance with id 'bar-1' to stay hidden
195
+bindsym $mod+Shift+b bar mode invisible bar-1
196
+
197
+# Toggle between dock mode and hide mode
198
+bindsym $mod+n bar mode toggle
199
+------------------------------------------------
200
+
201
 [[multi_monitor]]
202
 
203
 == Multiple monitors

b/i3bar/include/config.h

208
@@ -19,7 +19,6 @@ typedef enum {
209
 } position_t;
210
 
211
 typedef struct config_t {
212
-    int          hide_on_modifier;
213
     int          modifier;
214
     position_t   position;
215
     int          verbose;
216
@@ -31,6 +30,12 @@ typedef struct config_t {
217
     char         *tray_output;
218
     int          num_outputs;
219
     char         **outputs;
220
+
221
+    /* Bar display mode (hide unless modifier is pressed or show in dock mode or always hide in invisible mode) */
222
+    enum { M_DOCK = 0, M_HIDE = 1, M_INVISIBLE = 2 } hide_on_modifier;
223
+
224
+    /* The current state of the bar, which indicates whether it is hidden or shown */
225
+    enum { S_HIDE = 0, S_SHOW = 1 } state;
226
 } config_t;
227
 
228
 config_t config;

b/i3bar/src/config.c

233
@@ -73,7 +73,15 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
234
 
235
     if (!strcmp(cur_key, "mode")) {
236
         DLOG("mode = %.*s, len = %d\n", len, val, len);
237
-        config.hide_on_modifier = (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")));
238
+        config.hide_on_modifier = (len == 4 && !strncmp((const char*)val, "dock", strlen("dock")) ? M_DOCK
239
+            : (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")) ? M_HIDE
240
+                : M_INVISIBLE));
241
+        return 1;
242
+    }
243
+
244
+    if (!strcmp(cur_key, "state")) {
245
+        DLOG("state = %.*s, len = %d\n", len, val, len);
246
+        config.state = (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW);
247
         return 1;
248
     }
249
 

b/i3bar/src/ipc.c

254
@@ -149,12 +149,36 @@ void got_mode_event(char *event) {
255
     draw_bars(false);
256
 }
257
 
258
+/*
259
+ * Called, when a barconfig_update event arrives (i.e. i3 changed the bar state)
260
+ *
261
+ */
262
+void got_bar_config_update(char *event) {
263
+    /* check whether this affect this bar instance by checking the bar_id */
264
+    char* expected_id;
265
+    sasprintf(&expected_id, "\"id\":\"%s\"", config.bar_id);
266
+    if(strstr(event, expected_id) == NULL)
267
+       return;
268
+    FREE(expected_id);
269
+
270
+    /* update the configuration with the received settings */
271
+    DLOG("Received bar config update \"%s\"\n", event);
272
+    int old_mode = config.hide_on_modifier;
273
+    parse_config_json(event);
274
+    if (old_mode != config.hide_on_modifier) {
275
+        reconfig_windows();
276
+    }
277
 
278
-/* Data-structure to easily call the reply-handlers later */
279
+    draw_bars(false);
280
+}
281
+
282
+/* Data-structure to easily call the event-handlers later */
283
 handler_t event_handlers[] = {
284
     &got_workspace_event,
285
     &got_output_event,
286
-    &got_mode_event
287
+    &got_mode_event,
288
+    NULL,
289
+    &got_bar_config_update,
290
 };
291
 
292
 /*
293
@@ -310,8 +334,8 @@ void destroy_connection(void) {
294
  */
295
 void subscribe_events(void) {
296
     if (config.disable_ws) {
297
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\" ]");
298
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
299
     } else {
300
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\" ]");
301
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\", \"barconfig_update\" ]");
302
     }
303
 }

b/i3bar/src/xcb.c

308
@@ -198,7 +198,7 @@ void refresh_statusline(void) {
309
  *
310
  */
311
 void hide_bars(void) {
312
-    if (!config.hide_on_modifier) {
313
+    if ((config.hide_on_modifier == M_DOCK) || (config.state == S_SHOW)) {
314
         return;
315
     }
316
 
317
@@ -217,7 +217,7 @@ void hide_bars(void) {
318
  *
319
  */
320
 void unhide_bars(void) {
321
-    if (!config.hide_on_modifier) {
322
+    if (config.hide_on_modifier != M_HIDE) {
323
         return;
324
     }
325
 
326
@@ -988,25 +988,13 @@ char *init_xcb_early() {
327
 }
328
 
329
 /*
330
- * Initialization which depends on 'config' being usable. Called after the
331
- * configuration has arrived.
332
+ * Register for xkb keyevents. To grab modifiers without blocking other applications from receiving key-events
333
+ * involving that modifier, we sadly have to use xkb which is not yet fully supported
334
+ * in xcb.
335
  *
336
  */
337
-void init_xcb_late(char *fontname) {
338
-    if (fontname == NULL)
339
-        fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
340
-
341
-    /* Load the font */
342
-    font = load_font(fontname, true);
343
-    set_font(&font);
344
-    DLOG("Calculated Font-height: %d\n", font.height);
345
-
346
-    xcb_flush(xcb_connection);
347
-
348
-    /* To grab modifiers without blocking other applications from receiving key-events
349
-     * involving that modifier, we sadly have to use xkb which is not yet fully supported
350
-     * in xcb */
351
-    if (config.hide_on_modifier) {
352
+void register_xkb_keyevents() {
353
+    if (xkb_dpy == NULL) {
354
         int xkb_major, xkb_minor, xkb_errbase, xkb_err;
355
         xkb_major = XkbMajorVersion;
356
         xkb_minor = XkbMinorVersion;
357
@@ -1047,6 +1035,39 @@ void init_xcb_late(char *fontname) {
358
 }
359
 
360
 /*
361
+ * Deregister from xkb keyevents.
362
+ *
363
+ */
364
+void deregister_xkb_keyevents() {
365
+    if (xkb_dpy != NULL) {
366
+        ev_io_stop (main_loop, xkb_io);
367
+        close(xkb_io->fd);
368
+        FREE(xkb_io);
369
+        FREE(xkb_dpy);
370
+    }
371
+}
372
+
373
+/*
374
+ * Initialization which depends on 'config' being usable. Called after the
375
+ * configuration has arrived.
376
+ *
377
+ */
378
+void init_xcb_late(char *fontname) {
379
+    if (fontname == NULL)
380
+        fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
381
+
382
+    /* Load the font */
383
+    font = load_font(fontname, true);
384
+    set_font(&font);
385
+    DLOG("Calculated Font-height: %d\n", font.height);
386
+
387
+    xcb_flush(xcb_connection);
388
+
389
+    if (config.hide_on_modifier == M_HIDE)
390
+        register_xkb_keyevents();
391
+}
392
+
393
+/*
394
  * Inform clients waiting for a new _NET_SYSTEM_TRAY that we took the
395
  * selection.
396
  *
397
@@ -1368,8 +1389,8 @@ void reconfig_windows(void) {
398
             mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
399
             /* Black background */
400
             values[0] = colors.bar_bg;
401
-            /* If hide_on_modifier is set, i3 is not supposed to manage our bar-windows */
402
-            values[1] = config.hide_on_modifier;
403
+            /* If hide_on_modifier is set to hide or invisible mode, i3 is not supposed to manage our bar-windows */
404
+            values[1] = (config.hide_on_modifier ? 1 : 0);
405
             /* We enable the following EventMask fields:
406
              * EXPOSURE, to get expose events (we have to re-draw then)
407
              * SUBSTRUCTURE_REDIRECT, to get ConfigureRequests when the tray
408
@@ -1490,7 +1511,7 @@ void reconfig_windows(void) {
409
 
410
             /* We finally map the bar (display it on screen), unless the modifier-switch is on */
411
             xcb_void_cookie_t map_cookie;
412
-            if (!config.hide_on_modifier) {
413
+            if (config.hide_on_modifier == M_DOCK) {
414
                 map_cookie = xcb_map_window_checked(xcb_connection, walk->bar);
415
             }
416
 
417
@@ -1501,7 +1522,7 @@ void reconfig_windows(void) {
418
                 xcb_request_failed(name_cookie,  "Could not set WM_NAME")   ||
419
                 xcb_request_failed(strut_cookie, "Could not set strut")     ||
420
                 xcb_request_failed(gc_cookie,    "Could not create graphical context") ||
421
-                (!config.hide_on_modifier && xcb_request_failed(map_cookie, "Could not map window"))) {
422
+                ((config.hide_on_modifier == M_DOCK) && xcb_request_failed(map_cookie, "Could not map window"))) {
423
                 exit(EXIT_FAILURE);
424
             }
425
 
426
@@ -1533,6 +1554,14 @@ void reconfig_windows(void) {
427
                                                                         mask,
428
                                                                         values);
429
 
430
+            mask = XCB_CW_OVERRIDE_REDIRECT;
431
+            values[0] = (config.hide_on_modifier ? 1 : 0);
432
+            DLOG("Changing Window attribute override_redirect for output %s to %d\n", walk->name, values[0]);
433
+            xcb_void_cookie_t chg_cookie = xcb_change_window_attributes(xcb_connection,
434
+                                                                        walk->bar,
435
+                                                                        mask,
436
+                                                                        values);
437
+
438
             DLOG("Recreating buffer for output %s\n", walk->name);
439
             xcb_void_cookie_t pm_cookie = xcb_create_pixmap_checked(xcb_connection,
440
                                                                     root_screen->root_depth,
441
@@ -1541,10 +1570,26 @@ void reconfig_windows(void) {
442
                                                                     walk->rect.w,
443
                                                                     walk->rect.h);
444
 
445
-            if (xcb_request_failed(cfg_cookie, "Could not reconfigure window")) {
446
-                exit(EXIT_FAILURE);
447
+            /* Unmap the window, and draw it again when in dock mode */
448
+            xcb_void_cookie_t umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar);
449
+            xcb_void_cookie_t map_cookie;
450
+            if (config.hide_on_modifier == M_DOCK) {
451
+                map_cookie = xcb_map_window_checked(xcb_connection, walk->bar);
452
+            }
453
+
454
+            if (config.hide_on_modifier == M_HIDE) {
455
+                /* Switching to hide mode, register for keyevents */
456
+                register_xkb_keyevents();
457
+            } else {
458
+                /* Switching to dock/invisible mode, deregister from keyevents */
459
+                deregister_xkb_keyevents();
460
             }
461
-            if (xcb_request_failed(pm_cookie,  "Could not create pixmap")) {
462
+
463
+            if (xcb_request_failed(cfg_cookie, "Could not reconfigure window") ||
464
+                xcb_request_failed(chg_cookie, "Could not change window") ||
465
+                xcb_request_failed(pm_cookie,  "Could not create pixmap") ||
466
+                xcb_request_failed(umap_cookie,  "Could not unmap window") ||
467
+                ((config.hide_on_modifier == M_DOCK) && xcb_request_failed(map_cookie, "Could not map window"))) {
468
                 exit(EXIT_FAILURE);
469
             }
470
         }
471
@@ -1718,11 +1763,14 @@ void draw_bars(bool unhide) {
472
         i = 0;
473
     }
474
 
475
+    /* Assure the bar is hidden/unhidden according to the specified state */
476
+    bool should_unhide = (config.state == S_SHOW || (unhide && config.state == S_HIDE));
477
+    bool should_hide = (config.hide_on_modifier == M_INVISIBLE);
478
+
479
     if (!mod_pressed) {
480
-        if (unhide) {
481
-            /* The urgent-hint should get noticed, so we unhide the bars shortly */
482
+        if ((unhide || should_unhide) && !should_hide) {
483
             unhide_bars();
484
-        } else if (walks_away) {
485
+        } else if (walks_away || should_hide) {
486
             FREE(last_urgent_ws);
487
             hide_bars();
488
         }

b/include/commands.h

493
@@ -265,4 +265,10 @@ void cmd_scratchpad_show(I3_CMD);
494
  */
495
 void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name);
496
 
497
+/**
498
+ * Implementation of 'bar (state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]'
499
+ *
500
+ */
501
+void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id);
502
+
503
 #endif

b/include/config.h

508
@@ -199,6 +199,9 @@ struct Config {
509
         /* just ignore the popup, that is, don’t map it */
510
         PDF_IGNORE = 2,
511
     } popup_during_fullscreen;
512
+
513
+    /* The number of currently parsed barconfigs */
514
+    int number_barconfigs;
515
 };
516
 
517
 /**
518
@@ -226,8 +229,11 @@ struct Barconfig {
519
      * root window! */
520
     char *socket_path;
521
 
522
-    /** Bar display mode (hide unless modifier is pressed or show in dock mode) */
523
-    enum { M_DOCK = 0, M_HIDE = 1 } mode;
524
+    /** Bar display mode (hide unless modifier is pressed or show in dock mode or always hide in invisible mode) */
525
+    enum { M_DOCK = 0, M_HIDE = 1, M_INVISIBLE = 2 } mode;
526
+
527
+    /* The current state of the bar, which indicates whether it is hidden or shown */
528
+    enum { S_HIDE = 0, S_SHOW = 1 } state;
529
 
530
     /** Bar modifier (to show bar when in hide mode). */
531
     enum {
532
@@ -324,6 +330,12 @@ void grab_all_keys(xcb_connection_t *conn, bool bind_mode_switch);
533
 void switch_mode(const char *new_mode);
534
 
535
 /**
536
+ * Sends the current bar configuration as an event to all barconfig_update listeners.
537
+ * This update mechnism currently only includes the state and the mode in the config.
538
+ *
539
+ */void update_barconfig();
540
+
541
+/**
542
  * Returns a pointer to the Binding with the specified modifiers and keycode
543
  * or NULL if no such binding exists.
544
  *

b/include/config_directives.h

549
@@ -62,6 +62,8 @@ CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *ke
550
 
551
 CFGFUN(bar_font, const char *font);
552
 CFGFUN(bar_mode, const char *mode);
553
+CFGFUN(bar_state, const char *state);
554
+CFGFUN(bar_id, const char *bar_id);
555
 CFGFUN(bar_output, const char *output);
556
 CFGFUN(bar_verbose, const char *verbose);
557
 CFGFUN(bar_modifier, const char *modifier);

b/include/i3/ipc.h

562
@@ -99,4 +99,7 @@ typedef struct i3_ipc_header {
563
 /* The window event will be triggered upon window changes */
564
 #define I3_IPC_EVENT_WINDOW                     (I3_IPC_EVENT_MASK | 3)
565
 
566
+/** Bar config update will be triggered to update the bar config */
567
+#define I3_IPC_EVENT_BARCONFIG_UPDATE           (I3_IPC_EVENT_MASK | 4)
568
+
569
 #endif

b/parser-specs/commands.spec

574
@@ -35,6 +35,7 @@ state INITIAL:
575
   'nop' -> NOP
576
   'scratchpad' -> SCRATCHPAD
577
   'mode' -> MODE
578
+  'bar' -> BAR
579
 
580
 state CRITERIA:
581
   ctype = 'class' -> CRITERION
582
@@ -319,3 +320,24 @@ state NOP:
583
 state SCRATCHPAD:
584
   'show'
585
       -> call cmd_scratchpad_show()
586
+
587
+# bar (state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]
588
+state BAR:
589
+  bar_type = 'state'
590
+      -> BAR_STATE
591
+  bar_type = 'mode'
592
+      -> BAR_MODE
593
+
594
+state BAR_STATE:
595
+  bar_value = 'hide', 'show', 'toggle'
596
+      -> BAR_W_ID
597
+
598
+state BAR_MODE:
599
+  bar_value = 'dock', 'hide', 'invisible', 'toggle'
600
+      -> BAR_W_ID
601
+
602
+state BAR_W_ID:
603
+  bar_id = word
604
+      ->
605
+  end
606
+      -> call cmd_bar($bar_type, $bar_value, $bar_id)

b/parser-specs/config.spec

611
@@ -349,6 +349,8 @@ state BAR:
612
   'status_command'    -> BAR_STATUS_COMMAND
613
   'socket_path'       -> BAR_SOCKET_PATH
614
   'mode'              -> BAR_MODE
615
+  'state'             -> BAR_STATE
616
+  'id'                -> BAR_ID
617
   'modifier'          -> BAR_MODIFIER
618
   'position'          -> BAR_POSITION
619
   'output'            -> BAR_OUTPUT
620
@@ -378,9 +380,17 @@ state BAR_SOCKET_PATH:
621
       -> call cfg_bar_socket_path($path); BAR
622
 
623
 state BAR_MODE:
624
-  mode = 'dock', 'hide'
625
+  mode = 'dock', 'hide', 'invisible'
626
       -> call cfg_bar_mode($mode); BAR
627
 
628
+state BAR_STATE:
629
+  state = 'hide', 'show'
630
+      -> call cfg_bar_state($state); BAR
631
+
632
+state BAR_ID:
633
+  bar_id = word
634
+      -> call cfg_bar_id($bar_id); BAR
635
+
636
 state BAR_MODIFIER:
637
   modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift'
638
       -> call cfg_bar_modifier($modifier); BAR

b/src/commands.c

643
@@ -1632,6 +1632,8 @@ void cmd_reload(I3_CMD) {
644
     x_set_i3_atoms();
645
     /* Send an IPC event just in case the ws names have changed */
646
     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"reload\"}");
647
+    /* Send an update event for the barconfig just in case it has changed */
648
+    update_barconfig();
649
 
650
     // XXX: default reply for now, make this a better reply
651
     ysuccess(true);
652
@@ -1915,3 +1917,113 @@ void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name) {
653
 
654
     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"rename\"}");
655
 }
656
+
657
+/*
658
+ * Implementation of 'bar mode dock|hide|invisible|toggle [<bar_id>]'
659
+ *
660
+ */
661
+bool cmd_bar_mode(char *bar_mode, char *bar_id) {
662
+    int mode;
663
+    bool toggle = false;
664
+    if (strcmp(bar_mode, "dock") == 0)
665
+        mode = M_DOCK;
666
+    else if (strcmp(bar_mode, "hide") == 0)
667
+        mode = M_HIDE;
668
+    else if (strcmp(bar_mode, "invisible") == 0)
669
+        mode = M_INVISIBLE;
670
+    else if (strcmp(bar_mode, "toggle") == 0)
671
+        toggle = true;
672
+    else {
673
+        ELOG("Unknown bar mode \"%s\", this is a mismatch between code and parser spec.\n", bar_mode);
674
+        return false;
675
+    }
676
+
677
+    bool changed_sth = false;
678
+    Barconfig *current = NULL;
679
+    TAILQ_FOREACH(current, &barconfigs, configs) {
680
+        if (bar_id && strcmp(current->id, bar_id) != 0)
681
+            continue;
682
+
683
+        if (toggle)
684
+            mode = (current->mode + 1) % 2;
685
+
686
+        DLOG("Changing bar mode of bar_id '%s' to '%s (%d)'\n", current->id, bar_mode, mode);
687
+        current->mode = mode;
688
+        changed_sth = true;
689
+
690
+        if (bar_id)
691
+             break;
692
+    }
693
+
694
+    if (bar_id && !changed_sth) {
695
+        DLOG("Changing bar mode of bar_id %s failed, bar_id not found.\n", bar_id);
696
+        return false;
697
+    }
698
+
699
+    return true;
700
+}
701
+
702
+/*
703
+ * Implementation of 'bar state hide|show|toggle [<bar_id>]'
704
+ *
705
+ */
706
+bool cmd_bar_state(char *bar_state, char *bar_id) {
707
+    int state;
708
+    bool toggle = false;
709
+    if (strcmp(bar_state, "hide") == 0)
710
+        state = S_HIDE;
711
+    else if (strcmp(bar_state, "show") == 0)
712
+        state = S_SHOW;
713
+    else if (strcmp(bar_state, "toggle") == 0)
714
+        toggle = true;
715
+    else {
716
+        ELOG("Unknown bar state \"%s\", this is a mismatch between code and parser spec.\n", bar_state);
717
+        return false;
718
+    }
719
+
720
+    bool changed_sth = false;
721
+    Barconfig *current = NULL;
722
+    TAILQ_FOREACH(current, &barconfigs, configs) {
723
+        if (bar_id && strcmp(current->id, bar_id) != 0)
724
+            continue;
725
+
726
+        if (toggle)
727
+            state = (current->state + 1) % 2;
728
+
729
+        DLOG("Changing bar state of bar_id '%s' to '%s (%d)'\n", current->id, bar_state, state);
730
+        current->state = state;
731
+        changed_sth = true;
732
+
733
+        if (bar_id)
734
+             break;
735
+    }
736
+
737
+    if (bar_id && !changed_sth) {
738
+        DLOG("Changing bar state of bar_id %s failed, bar_id not found.\n", bar_id);
739
+        return false;
740
+    }
741
+
742
+    return true;
743
+}
744
+
745
+/*
746
+ * Implementation of 'bar (state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]'
747
+ *
748
+ */
749
+void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id) {
750
+    bool ret;
751
+    if (strcmp(bar_type, "mode") == 0)
752
+        ret = cmd_bar_mode(bar_value, bar_id);
753
+    else if (strcmp(bar_type, "state") == 0)
754
+        ret = cmd_bar_state(bar_value, bar_id);
755
+    else {
756
+        ELOG("Unknown bar option type \"%s\", this is a mismatch between code and parser spec.\n", bar_type);
757
+        ret = false;
758
+    }
759
+
760
+    ysuccess(ret);
761
+    if (!ret)
762
+        return;
763
+
764
+    update_barconfig();
765
+}

b/src/config.c

770
@@ -211,6 +211,49 @@ void switch_mode(const char *new_mode) {
771
 }
772
 
773
 /*
774
+ * Sends the current bar configuration as an event to all barconfig_update listeners.
775
+ * This update mechnism currently only includes the state and the mode in the config.
776
+ *
777
+ */
778
+void update_barconfig() {
779
+    Barconfig *current;
780
+    TAILQ_FOREACH(current, &barconfigs, configs) {
781
+        /* Build json message */
782
+        char *state;
783
+        switch (current->state) {
784
+            case S_SHOW:
785
+                state ="show";
786
+                break;
787
+            case S_HIDE:
788
+            default:
789
+                state = "hide";
790
+                break;
791
+        }
792
+
793
+        char *mode;
794
+        switch (current->mode) {
795
+            case M_HIDE:
796
+                mode ="hide";
797
+                break;
798
+            case M_INVISIBLE:
799
+                mode ="invisible";
800
+                break;
801
+            case M_DOCK:
802
+            default:
803
+                mode = "dock";
804
+                break;
805
+        }
806
+
807
+        /* Send an event to all barconfig listeners*/
808
+        char *event_msg;
809
+        sasprintf(&event_msg, "{ \"id\":\"%s\", \"state\":\"%s\", \"mode\":\"%s\" }", current->id, state, mode);
810
+
811
+        ipc_send_event("barconfig_update", I3_IPC_EVENT_BARCONFIG_UPDATE, event_msg);
812
+        FREE(event_msg);
813
+    }
814
+}
815
+
816
+/*
817
  * Get the path of the first configuration file found. If override_configpath
818
  * is specified, that path is returned and saved for further calls. Otherwise,
819
  * checks the home directory first, then the system directory first, always

b/src/config_directives.c

824
@@ -452,7 +452,15 @@ CFGFUN(bar_font, const char *font) {
825
 }
826
 
827
 CFGFUN(bar_mode, const char *mode) {
828
-    current_bar.mode = (strcmp(mode, "hide") == 0 ? M_HIDE : M_DOCK);
829
+    current_bar.mode = (strcmp(mode, "dock") == 0 ? M_DOCK : (strcmp(mode, "hide") == 0 ? M_HIDE : M_INVISIBLE));
830
+}
831
+
832
+CFGFUN(bar_state, const char *state) {
833
+    current_bar.state = (strcmp(state, "hide") == 0 ? S_HIDE : S_SHOW);
834
+}
835
+
836
+CFGFUN(bar_id, const char *bar_id) {
837
+    current_bar.id = sstrdup(bar_id);
838
 }
839
 
840
 CFGFUN(bar_output, const char *output) {
841
@@ -548,15 +556,11 @@ CFGFUN(bar_workspace_buttons, const char *value) {
842
 
843
 CFGFUN(bar_finish) {
844
     DLOG("\t new bar configuration finished, saving.\n");
845
-    /* Generate a unique ID for this bar */
846
-    current_bar.id = sstrdup("bar-XXXXXX");
847
-    /* This works similar to mktemp in that it replaces the last six X with
848
-     * random letters, but without the restriction that the given buffer
849
-     * has to contain a valid path name. */
850
-    char *x = current_bar.id + strlen("bar-");
851
-    while (*x != '\0') {
852
-        *(x++) = (rand() % 26) + 'a';
853
-    }
854
+    /* Generate a unique ID for this bar if not already configured */
855
+    if (!current_bar.id)
856
+        sasprintf(&current_bar.id, "bar-%d", config.number_barconfigs);
857
+
858
+    config.number_barconfigs++;
859
 
860
     /* If no font was explicitly set, we use the i3 font as default */
861
     if (!current_bar.font && font_pattern)

b/src/ipc.c

866
@@ -621,9 +621,29 @@ IPC_HANDLER(get_bar_config) {
867
         YSTR_IF_SET(socket_path);
868
 
869
         ystr("mode");
870
-        if (config->mode == M_HIDE)
871
-            ystr("hide");
872
-        else ystr("dock");
873
+        switch (config->mode) {
874
+            case M_HIDE:
875
+                ystr("hide");
876
+                break;
877
+            case M_INVISIBLE:
878
+                ystr("invisible");
879
+                break;
880
+            case M_DOCK:
881
+            default:
882
+                ystr("dock");
883
+                break;
884
+        }
885
+
886
+        ystr("state");
887
+        switch (config->state) {
888
+            case S_SHOW:
889
+                ystr("show");
890
+                break;
891
+            case S_HIDE:
892
+            default:
893
+                ystr("hide");
894
+                break;
895
+        }
896
 
897
         ystr("modifier");
898
         switch (config->modifier) {

b/testcases/t/187-commands-parser.t

903
@@ -144,7 +144,7 @@ is(parser_calls("\nworkspace test"),
904
 ################################################################################
905
 
906
 is(parser_calls('unknown_literal'),
907
-   "ERROR: Expected one of these tokens: <end>, '[', 'move', 'exec', 'exit', 'restart', 'reload', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'resize', 'rename', 'nop', 'scratchpad', 'mode'\n" .
908
+   "ERROR: Expected one of these tokens: <end>, '[', 'move', 'exec', 'exit', 'restart', 'reload', 'border', 'layout', 'append_layout', 'workspace', 'focus', 'kill', 'open', 'fullscreen', 'split', 'floating', 'mark', 'resize', 'rename', 'nop', 'scratchpad', 'mode', 'bar'\n" .
909
    "ERROR: Your command: unknown_literal\n" .
910
    "ERROR:               ^^^^^^^^^^^^^^^",
911
    'error for unknown literal ok');

b/testcases/t/201-config-parser.t

916
@@ -627,7 +627,7 @@ EOT
917
 
918
 $expected = <<'EOT';
919
 cfg_bar_output(LVDS-1)
920
-ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'modifier', 'position', 'output', 'tray_output', 'font', 'workspace_buttons', 'verbose', 'colors', '}'
921
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'workspace_buttons', 'verbose', 'colors', '}'
922
 ERROR: CONFIG: (in file <stdin>)
923
 ERROR: CONFIG: Line   1: bar {
924
 ERROR: CONFIG: Line   2:     output LVDS-1