i3 - improved tiling WM


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

Patch status: needinfo

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

b/i3bar/include/config.h

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

b/i3bar/src/config.c

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

b/i3bar/src/ipc.c

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

b/i3bar/src/xcb.c

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

b/include/commands.h

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

b/include/config.h

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

b/include/config_directives.h

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

b/include/i3/ipc.h

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

b/parser-specs/commands.spec

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

b/parser-specs/config.spec

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

b/src/commands.c

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

b/src/config.c

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

b/src/config_directives.c

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

b/src/ipc.c

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

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

907
@@ -144,7 +144,7 @@ is(parser_calls("\nworkspace test"),
908
 ################################################################################
909
 
910
 is(parser_calls('unknown_literal'),
911
-   "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" .
912
+   "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" .
913
    "ERROR: Your command: unknown_literal\n" .
914
    "ERROR:               ^^^^^^^^^^^^^^^",
915
    'error for unknown literal ok');

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

920
@@ -627,7 +627,7 @@ EOT
921
 
922
 $expected = <<'EOT';
923
 cfg_bar_output(LVDS-1)
924
-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', '}'
925
+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', '}'
926
 ERROR: CONFIG: (in file <stdin>)
927
 ERROR: CONFIG: Line   1: bar {
928
 ERROR: CONFIG: Line   2:     output LVDS-1