i3 - improved tiling WM


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

Patch status: merged

Patch by jj

Long description:

The hidden_state and mode of each i3bar instance can now be controlled from within i3.
Therefore, two new i3 command were introduced:
    _
    bar hidden_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 hidden_state ("hidden_state hide|show") in the
barconfig, which indicates the current hidden_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 hidden_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 hidden_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/168/raw.patch | git am

b/docs/ipc

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

b/docs/userguide

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

b/i3bar/include/config.h

221
@@ -19,7 +19,6 @@ typedef enum {
222
 } position_t;
223
 
224
 typedef struct config_t {
225
-    int          hide_on_modifier;
226
     int          modifier;
227
     position_t   position;
228
     int          verbose;
229
@@ -31,6 +30,12 @@ typedef struct config_t {
230
     char         *tray_output;
231
     int          num_outputs;
232
     char         **outputs;
233
+
234
+    /* Bar display mode (hide unless modifier is pressed or show in dock mode or always hide in invisible mode) */
235
+    enum { M_DOCK = 0, M_HIDE = 1, M_INVISIBLE = 2 } hide_on_modifier;
236
+
237
+    /* The current hidden_state of the bar, which indicates whether it is hidden or shown */
238
+    enum { S_HIDE = 0, S_SHOW = 1 } hidden_state;
239
 } config_t;
240
 
241
 config_t config;

b/i3bar/src/config.c

246
@@ -73,7 +73,15 @@ static int config_string_cb(void *params_, const unsigned char *val, unsigned in
247
 
248
     if (!strcmp(cur_key, "mode")) {
249
         DLOG("mode = %.*s, len = %d\n", len, val, len);
250
-        config.hide_on_modifier = (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")));
251
+        config.hide_on_modifier = (len == 4 && !strncmp((const char*)val, "dock", strlen("dock")) ? M_DOCK
252
+            : (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")) ? M_HIDE
253
+                : M_INVISIBLE));
254
+        return 1;
255
+    }
256
+
257
+    if (!strcmp(cur_key, "hidden_state")) {
258
+        DLOG("hidden_state = %.*s, len = %d\n", len, val, len);
259
+        config.hidden_state = (len == 4 && !strncmp((const char*)val, "hide", strlen("hide")) ? S_HIDE : S_SHOW);
260
         return 1;
261
     }
262
 

b/i3bar/src/ipc.c

267
@@ -149,12 +149,37 @@ void got_mode_event(char *event) {
268
     draw_bars(false);
269
 }
270
 
271
+/*
272
+ * Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode)
273
+ *
274
+ */
275
+void got_bar_config_update(char *event) {
276
+    /* check whether this affect this bar instance by checking the bar_id */
277
+    char *expected_id;
278
+    sasprintf(&expected_id, "\"id\":\"%s\"", config.bar_id);
279
+    char *found_id = strstr(event, expected_id);
280
+    FREE(expected_id);
281
+    if (found_id == NULL)
282
+       return;
283
+
284
+    /* update the configuration with the received settings */
285
+    DLOG("Received bar config update \"%s\"\n", event);
286
+    int old_mode = config.hide_on_modifier;
287
+    parse_config_json(event);
288
+    if (old_mode != config.hide_on_modifier) {
289
+        reconfig_windows();
290
+    }
291
 
292
-/* Data-structure to easily call the reply-handlers later */
293
+    draw_bars(false);
294
+}
295
+
296
+/* Data-structure to easily call the event-handlers later */
297
 handler_t event_handlers[] = {
298
     &got_workspace_event,
299
     &got_output_event,
300
-    &got_mode_event
301
+    &got_mode_event,
302
+    NULL,
303
+    &got_bar_config_update,
304
 };
305
 
306
 /*
307
@@ -310,8 +335,8 @@ void destroy_connection(void) {
308
  */
309
 void subscribe_events(void) {
310
     if (config.disable_ws) {
311
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\" ]");
312
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
313
     } else {
314
-        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\" ]");
315
+        i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\", \"barconfig_update\" ]");
316
     }
317
 }

b/i3bar/src/xcb.c

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

b/include/commands.h

508
@@ -265,4 +265,10 @@ void cmd_scratchpad_show(I3_CMD);
509
  */
510
 void cmd_rename_workspace(I3_CMD, char *old_name, char *new_name);
511
 
512
+/**
513
+ * Implementation of 'bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]'
514
+ *
515
+ */
516
+void cmd_bar(I3_CMD, char *bar_type, char *bar_value, char *bar_id);
517
+
518
 #endif

b/include/config.h

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

b/include/config_directives.h

564
@@ -62,6 +62,8 @@ CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *ke
565
 
566
 CFGFUN(bar_font, const char *font);
567
 CFGFUN(bar_mode, const char *mode);
568
+CFGFUN(bar_hidden_state, const char *hidden_state);
569
+CFGFUN(bar_id, const char *bar_id);
570
 CFGFUN(bar_output, const char *output);
571
 CFGFUN(bar_verbose, const char *verbose);
572
 CFGFUN(bar_modifier, const char *modifier);

b/include/i3/ipc.h

577
@@ -99,4 +99,7 @@ typedef struct i3_ipc_header {
578
 /* The window event will be triggered upon window changes */
579
 #define I3_IPC_EVENT_WINDOW                     (I3_IPC_EVENT_MASK | 3)
580
 
581
+/** Bar config update will be triggered to update the bar config */
582
+#define I3_IPC_EVENT_BARCONFIG_UPDATE           (I3_IPC_EVENT_MASK | 4)
583
+
584
 #endif

b/parser-specs/commands.spec

589
@@ -35,6 +35,7 @@ state INITIAL:
590
   'nop' -> NOP
591
   'scratchpad' -> SCRATCHPAD
592
   'mode' -> MODE
593
+  'bar' -> BAR
594
 
595
 state CRITERIA:
596
   ctype = 'class' -> CRITERION
597
@@ -319,3 +320,24 @@ state NOP:
598
 state SCRATCHPAD:
599
   'show'
600
       -> call cmd_scratchpad_show()
601
+
602
+# bar (hidden_state hide|show|toggle)|(mode dock|hide|invisible|toggle) [<bar_id>]
603
+state BAR:
604
+  bar_type = 'hidden_state'
605
+      -> BAR_HIDDEN_STATE
606
+  bar_type = 'mode'
607
+      -> BAR_MODE
608
+
609
+state BAR_HIDDEN_STATE:
610
+  bar_value = 'hide', 'show', 'toggle'
611
+      -> BAR_W_ID
612
+
613
+state BAR_MODE:
614
+  bar_value = 'dock', 'hide', 'invisible', 'toggle'
615
+      -> BAR_W_ID
616
+
617
+state BAR_W_ID:
618
+  bar_id = word
619
+      ->
620
+  end
621
+      -> call cmd_bar($bar_type, $bar_value, $bar_id)

b/parser-specs/config.spec

626
@@ -349,6 +349,8 @@ state BAR:
627
   'status_command'    -> BAR_STATUS_COMMAND
628
   'socket_path'       -> BAR_SOCKET_PATH
629
   'mode'              -> BAR_MODE
630
+  'hidden_state'      -> BAR_HIDDEN_STATE
631
+  'id'                -> BAR_ID
632
   'modifier'          -> BAR_MODIFIER
633
   'position'          -> BAR_POSITION
634
   'output'            -> BAR_OUTPUT
635
@@ -378,9 +380,17 @@ state BAR_SOCKET_PATH:
636
       -> call cfg_bar_socket_path($path); BAR
637
 
638
 state BAR_MODE:
639
-  mode = 'dock', 'hide'
640
+  mode = 'dock', 'hide', 'invisible'
641
       -> call cfg_bar_mode($mode); BAR
642
 
643
+state BAR_HIDDEN_STATE:
644
+  hidden_state = 'hide', 'show'
645
+      -> call cfg_bar_hidden_state($hidden_state); BAR
646
+
647
+state BAR_ID:
648
+  bar_id = word
649
+      -> call cfg_bar_id($bar_id); BAR
650
+
651
 state BAR_MODIFIER:
652
   modifier = 'Mod1', 'Mod2', 'Mod3', 'Mod4', 'Mod5', 'Control', 'Ctrl', 'Shift'
653
       -> call cfg_bar_modifier($modifier); BAR

b/src/commands.c

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

b/src/config.c

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

b/src/config_directives.c

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

b/src/ipc.c

881
@@ -621,9 +621,29 @@ IPC_HANDLER(get_bar_config) {
882
         YSTR_IF_SET(socket_path);
883
 
884
         ystr("mode");
885
-        if (config->mode == M_HIDE)
886
-            ystr("hide");
887
-        else ystr("dock");
888
+        switch (config->mode) {
889
+            case M_HIDE:
890
+                ystr("hide");
891
+                break;
892
+            case M_INVISIBLE:
893
+                ystr("invisible");
894
+                break;
895
+            case M_DOCK:
896
+            default:
897
+                ystr("dock");
898
+                break;
899
+        }
900
+
901
+        ystr("hidden_state");
902
+        switch (config->hidden_state) {
903
+            case S_SHOW:
904
+                ystr("show");
905
+                break;
906
+            case S_HIDE:
907
+            default:
908
+                ystr("hide");
909
+                break;
910
+        }
911
 
912
         ystr("modifier");
913
         switch (config->modifier) {

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

918
@@ -144,7 +144,7 @@ is(parser_calls("\nworkspace test"),
919
 ################################################################################
920
 
921
 is(parser_calls('unknown_literal'),
922
-   "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" .
923
+   "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" .
924
    "ERROR: Your command: unknown_literal\n" .
925
    "ERROR:               ^^^^^^^^^^^^^^^",
926
    'error for unknown literal ok');

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

931
@@ -627,7 +627,7 @@ EOT
932
 
933
 $expected = <<'EOT';
934
 cfg_bar_output(LVDS-1)
935
-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', '}'
936
+ERROR: CONFIG: Expected one of these tokens: <end>, '#', 'set', 'i3bar_command', 'status_command', 'socket_path', 'mode', 'hidden_state', 'id', 'modifier', 'position', 'output', 'tray_output', 'font', 'workspace_buttons', 'verbose', 'colors', '}'
937
 ERROR: CONFIG: (in file <stdin>)
938
 ERROR: CONFIG: Line   1: bar {
939
 ERROR: CONFIG: Line   2:     output LVDS-1