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/146/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 == M_DOCK ? 0 : 1);
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 == M_DOCK ? 0 : 1);
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,27 @@ 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
+                cont_child();
452
+                map_cookie = xcb_map_window_checked(xcb_connection, walk->bar);
453
+            }
454
+
455
+            if (config.hide_on_modifier == M_HIDE) {
456
+                /* Switching to hide mode, register for keyevents */
457
+                register_xkb_keyevents();
458
+            } else {
459
+                /* Switching to dock/invisible mode, deregister from keyevents */
460
+                deregister_xkb_keyevents();
461
             }
462
-            if (xcb_request_failed(pm_cookie,  "Could not create pixmap")) {
463
+
464
+            if (xcb_request_failed(cfg_cookie, "Could not reconfigure window") ||
465
+                xcb_request_failed(chg_cookie, "Could not change window") ||
466
+                xcb_request_failed(pm_cookie,  "Could not create pixmap") ||
467
+                xcb_request_failed(umap_cookie,  "Could not unmap window") ||
468
+                ((config.hide_on_modifier == M_DOCK) && xcb_request_failed(map_cookie, "Could not map window"))) {
469
                 exit(EXIT_FAILURE);
470
             }
471
         }
472
@@ -1718,11 +1764,14 @@ void draw_bars(bool unhide) {
473
         i = 0;
474
     }
475
 
476
+    /* Assure the bar is hidden/unhidden according to the specified state */
477
+    bool should_unhide = (config.state == S_SHOW || (unhide && config.state == S_HIDE));
478
+    bool should_hide = (config.hide_on_modifier == M_INVISIBLE);
479
+
480
     if (!mod_pressed) {
481
-        if (unhide) {
482
-            /* The urgent-hint should get noticed, so we unhide the bars shortly */
483
+        if ((unhide || should_unhide) && !should_hide) {
484
             unhide_bars();
485
-        } else if (walks_away) {
486
+        } else if (walks_away || should_hide) {
487
             FREE(last_urgent_ws);
488
             hide_bars();
489
         }

b/include/commands.h

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

b/include/config.h

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

b/include/config_directives.h

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

b/include/i3/ipc.h

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

b/parser-specs/commands.spec

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

b/parser-specs/config.spec

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

b/src/commands.c

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

b/src/config.c

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

b/src/config_directives.c

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

b/src/ipc.c

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

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

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

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

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