i3 - improved tiling WM


Extend the fullscreen command

Patch status: merged

Patch by Mats

Long description:

Rather than just toggling the fullscreen modes, allow to set them
directly with:

    fullscreen enable|toggle [global]
    fullscreen disable

For compatibility, retain the previous command and its toggling behavior:

    fullscreen [global]

fixes #1120

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

b/docs/userguide

34
@@ -91,7 +91,7 @@ To display a window in fullscreen mode or to go out of fullscreen mode again,
35
 press +$mod+f+.
36
 
37
 There is also a global fullscreen mode in i3 in which the client will span all
38
-available outputs (the command is +fullscreen global+).
39
+available outputs (the command is +fullscreen toggle global+).
40
 
41
 === Opening other applications
42
 
43
@@ -367,7 +367,7 @@ bindcode [--release] [Modifiers+]keycode command
44
 *Examples*:
45
 --------------------------------
46
 # Fullscreen
47
-bindsym $mod+f fullscreen
48
+bindsym $mod+f fullscreen toggle
49
 
50
 # Restart
51
 bindsym $mod+Shift+r restart
52
@@ -1502,9 +1502,13 @@ Use +layout toggle split+, +layout stacking+, +layout tabbed+, +layout splitv+
53
 or +layout splith+ to change the current container layout to splith/splitv,
54
 stacking, tabbed layout, splitv or splith, respectively.
55
 
56
-To make the current window (!) fullscreen, use +fullscreen+, to make
57
-it floating (or tiling again) use +floating enable+ respectively +floating disable+
58
-(or +floating toggle+):
59
+To make the current window (!) fullscreen, use +fullscreen enable+ (or
60
++fullscreen enable global+ for the global mode), to leave either fullscreen
61
+mode use +fullscreen disable+, and to toggle between these two states use
62
++fullscreen toggle+ (or +fullscreen toggle global+).
63
+
64
+Likewise, to make the current window floating (or tiling again) use +floating
65
+enable+ respectively +floating disable+ (or +floating toggle+):
66
 
67
 *Syntax*:
68
 --------------
69
@@ -1525,7 +1529,7 @@ bindsym $mod+x layout toggle
70
 bindsym $mod+x layout toggle all
71
 
72
 # Toggle fullscreen
73
-bindsym $mod+f fullscreen
74
+bindsym $mod+f fullscreen toggle
75
 
76
 # Toggle floating/tiling
77
 bindsym $mod+t floating toggle

b/i3.config

82
@@ -75,7 +75,7 @@ bindsym Mod1+h split h
83
 bindsym Mod1+v split v
84
 
85
 # enter fullscreen mode for the focused container
86
-bindsym Mod1+f fullscreen
87
+bindsym Mod1+f fullscreen toggle
88
 
89
 # change container layout (stacked, tabbed, toggle split)
90
 bindsym Mod1+s layout stacking

b/i3.config.keycodes

95
@@ -69,7 +69,7 @@ bindcode $mod+43 split h
96
 bindcode $mod+55 split v
97
 
98
 # enter fullscreen mode for the focused container
99
-bindcode $mod+41 fullscreen
100
+bindcode $mod+41 fullscreen toggle
101
 
102
 # change container layout (stacked, tabbed, toggle split)
103
 bindcode $mod+39 layout stacking

b/include/commands.h

108
@@ -187,10 +187,10 @@ void cmd_focus_level(I3_CMD, char *level);
109
 void cmd_focus(I3_CMD);
110
 
111
 /**
112
- * Implementation of 'fullscreen [global]'.
113
+ * Implementation of 'fullscreen [enable|disable|toggle] [global]'.
114
  *
115
  */
116
-void cmd_fullscreen(I3_CMD, char *fullscreen_mode);
117
+void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode);
118
 
119
 /**
120
  * Implementation of 'move <direction> [<pixels> [px]]'.

b/include/con.h

125
@@ -173,6 +173,18 @@ void con_fix_percent(Con *con);
126
 void con_toggle_fullscreen(Con *con, int fullscreen_mode);
127
 
128
 /**
129
+ * Enables fullscreen mode for the given container, if necessary.
130
+ *
131
+ */
132
+void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode);
133
+
134
+/**
135
+ * Disables fullscreen mode for the given container, if necessary.
136
+ *
137
+ */
138
+void con_disable_fullscreen(Con *con);
139
+
140
+/**
141
  * Moves the given container to the currently focused container on the given
142
  * workspace.
143
  *

b/man/i3.man

148
@@ -230,7 +230,7 @@ bindsym Mod1+h split h
149
 bindsym Mod1+v split v
150
 
151
 # enter fullscreen mode for the focused container
152
-bindsym Mod1+f fullscreen
153
+bindsym Mod1+f fullscreen toggle
154
 
155
 # change container layout (stacked, tabbed, default)
156
 bindsym Mod1+s layout stacking

b/parser-specs/commands.spec

161
@@ -156,12 +156,28 @@ state KILL:
162
   end
163
       -> call cmd_kill($kill_mode)
164
 
165
+# fullscreen enable|toggle [global]
166
+# fullscreen disable
167
 # fullscreen [global]
168
 state FULLSCREEN:
169
-  fullscreen_mode = 'global'
170
-      -> call cmd_fullscreen($fullscreen_mode)
171
+  action = 'disable'
172
+      -> call cmd_fullscreen($action, "output")
173
+  action = 'enable', 'toggle'
174
+      -> FULLSCREEN_MODE
175
+  action = ''
176
+      -> FULLSCREEN_COMPAT
177
+
178
+state FULLSCREEN_MODE:
179
+  mode = 'global'
180
+      -> call cmd_fullscreen($action, $mode)
181
   end
182
-      -> call cmd_fullscreen($fullscreen_mode)
183
+      -> call cmd_fullscreen($action, "output")
184
+
185
+state FULLSCREEN_COMPAT:
186
+  mode = 'global'
187
+      -> call cmd_fullscreen("toggle", $mode)
188
+  end
189
+      -> call cmd_fullscreen("toggle", "output")
190
 
191
 # split v|h|vertical|horizontal
192
 state SPLIT:

b/src/commands.c

197
@@ -1574,20 +1574,26 @@ void cmd_focus(I3_CMD) {
198
 }
199
 
200
 /*
201
- * Implementation of 'fullscreen [global]'.
202
+ * Implementation of 'fullscreen enable|toggle [global]' and
203
+ *                   'fullscreen disable'
204
  *
205
  */
206
-void cmd_fullscreen(I3_CMD, char *fullscreen_mode) {
207
-    if (fullscreen_mode == NULL)
208
-        fullscreen_mode = "output";
209
-    DLOG("toggling fullscreen, mode = %s\n", fullscreen_mode);
210
+void cmd_fullscreen(I3_CMD, char *action, char *fullscreen_mode) {
211
+    fullscreen_mode_t mode = strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT;
212
+    DLOG("%s fullscreen, mode = %s\n", action, fullscreen_mode);
213
     owindow *current;
214
 
215
     HANDLE_EMPTY_MATCH;
216
 
217
     TAILQ_FOREACH(current, &owindows, owindows) {
218
         DLOG("matching: %p / %s\n", current->con, current->con->name);
219
-        con_toggle_fullscreen(current->con, (strcmp(fullscreen_mode, "global") == 0 ? CF_GLOBAL : CF_OUTPUT));
220
+        if (strcmp(action, "toggle") == 0) {
221
+            con_toggle_fullscreen(current->con, mode);
222
+        } else if (strcmp(action, "enable") == 0) {
223
+            con_enable_fullscreen(current->con, mode);
224
+        } else if (strcmp(action, "disable") == 0) {
225
+            con_disable_fullscreen(current->con);
226
+        }
227
     }
228
 
229
     cmd_output->needs_tree_render = true;

b/src/con.c

234
@@ -565,37 +565,27 @@ void con_fix_percent(Con *con) {
235
  *
236
  */
237
 void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
238
-    Con *workspace, *fullscreen;
239
-
240
     if (con->type == CT_WORKSPACE) {
241
         DLOG("You cannot make a workspace fullscreen.\n");
242
         return;
243
     }
244
 
245
     DLOG("toggling fullscreen for %p / %s\n", con, con->name);
246
-    if (con->fullscreen_mode == CF_NONE) {
247
-        /* 1: check if there already is a fullscreen con */
248
-        if (fullscreen_mode == CF_GLOBAL)
249
-            fullscreen = con_get_fullscreen_con(croot, CF_GLOBAL);
250
-        else {
251
-            workspace = con_get_workspace(con);
252
-            fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
253
-        }
254
-        if (fullscreen != NULL) {
255
-            /* Disable fullscreen for the currently fullscreened
256
-             * container and enable it for the one the user wants
257
-             * to have in fullscreen mode. */
258
-            LOG("Disabling fullscreen for (%p/%s) upon user request\n",
259
-                fullscreen, fullscreen->name);
260
-            fullscreen->fullscreen_mode = CF_NONE;
261
-        }
262
 
263
-        /* 2: enable fullscreen */
264
-        con->fullscreen_mode = fullscreen_mode;
265
-    } else {
266
-        /* 1: disable fullscreen */
267
-        con->fullscreen_mode = CF_NONE;
268
-    }
269
+    if (con->fullscreen_mode == CF_NONE)
270
+        con_enable_fullscreen(con, fullscreen_mode);
271
+    else
272
+        con_disable_fullscreen(con);
273
+}
274
+
275
+/*
276
+ * Sets the specified fullscreen mode for the given container, sends the
277
+ * “fullscreen_mode” event and changes the XCB fullscreen property of the
278
+ * container’s window, if any.
279
+ *
280
+ */
281
+static void con_set_fullscreen_mode(Con *con, fullscreen_mode_t fullscreen_mode) {
282
+    con->fullscreen_mode = fullscreen_mode;
283
 
284
     DLOG("mode now: %d\n", con->fullscreen_mode);
285
 
286
@@ -619,6 +609,79 @@ void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
287
 }
288
 
289
 /*
290
+ * Enables fullscreen mode for the given container, if necessary.
291
+ *
292
+ * If the container’s mode is already CF_OUTPUT or CF_GLOBAL, the container is
293
+ * kept fullscreen but its mode is set to CF_GLOBAL and CF_OUTPUT,
294
+ * respectively.
295
+ *
296
+ * Other fullscreen containers will be disabled first, if they hide the new
297
+ * one.
298
+ *
299
+ */
300
+void con_enable_fullscreen(Con *con, fullscreen_mode_t fullscreen_mode) {
301
+    if (con->type == CT_WORKSPACE) {
302
+        DLOG("You cannot make a workspace fullscreen.\n");
303
+        return;
304
+    }
305
+
306
+    assert(fullscreen_mode == CF_GLOBAL || fullscreen_mode == CF_OUTPUT);
307
+
308
+    if (fullscreen_mode == CF_GLOBAL)
309
+        DLOG("enabling global fullscreen for %p / %s\n", con, con->name);
310
+    else
311
+        DLOG("enabling fullscreen for %p / %s\n", con, con->name);
312
+
313
+    if (con->fullscreen_mode == fullscreen_mode) {
314
+        DLOG("fullscreen already enabled for %p / %s\n", con, con->name);
315
+        return;
316
+    }
317
+
318
+    Con *con_ws = con_get_workspace(con);
319
+
320
+    /* Disable any fullscreen container that would conflict the new one. */
321
+    Con *fullscreen = con_get_fullscreen_con(croot, CF_GLOBAL);
322
+    if (fullscreen == NULL)
323
+        fullscreen = con_get_fullscreen_con(con_ws, CF_OUTPUT);
324
+    if (fullscreen != NULL)
325
+        con_disable_fullscreen(fullscreen);
326
+
327
+    /* Set focus to new fullscreen container. Unless in global fullscreen mode
328
+     * and on another workspace restore focus afterwards.
329
+     * Switch to the container’s workspace if mode is global. */
330
+    Con *cur_ws = con_get_workspace(focused);
331
+    Con *old_focused = focused;
332
+    if (fullscreen_mode == CF_GLOBAL && cur_ws != con_ws)
333
+        workspace_show(con_ws);
334
+    con_focus(con);
335
+    if (fullscreen_mode != CF_GLOBAL && cur_ws != con_ws)
336
+        con_focus(old_focused);
337
+
338
+    con_set_fullscreen_mode(con, fullscreen_mode);
339
+}
340
+
341
+/*
342
+ * Disables fullscreen mode for the given container regardless of the mode, if
343
+ * necessary.
344
+ *
345
+ */
346
+void con_disable_fullscreen(Con *con) {
347
+    if (con->type == CT_WORKSPACE) {
348
+        DLOG("You cannot make a workspace fullscreen.\n");
349
+        return;
350
+    }
351
+
352
+    DLOG("disabling fullscreen for %p / %s\n", con, con->name);
353
+
354
+    if (con->fullscreen_mode == CF_NONE) {
355
+        DLOG("fullscreen already disabled for %p / %s\n", con, con->name);
356
+        return;
357
+    }
358
+
359
+    con_set_fullscreen_mode(con, CF_NONE);
360
+}
361
+
362
+/*
363
  * Moves the given container to the currently focused container on the given
364
  * workspace.
365
  *

b/testcases/i3-test.config

370
@@ -20,7 +20,7 @@ bindsym Mod1+h split h
371
 bindsym Mod1+v split v
372
 
373
 # Fullscreen (Mod1+f)
374
-bindsym Mod1+f fullscreen
375
+bindsym Mod1+f fullscreen toggle
376
 
377
 # Stacking (Mod1+s)
378
 bindsym Mod1+s layout stacking

b/testcases/t/100-fullscreen.t

383
@@ -235,4 +235,131 @@ $swindow = open_window({
384
 is(fullscreen_windows($tmp), 1, 'one fullscreen window on ws');
385
 is($x->input_focus, $swindow->id, 'fullscreen window focused');
386
 
387
+################################################################################
388
+# Verify that command ‘fullscreen enable’ works and is idempotent.
389
+################################################################################
390
+
391
+$tmp = fresh_workspace;
392
+
393
+$window = open_window;
394
+is($x->input_focus, $window->id, 'window focused');
395
+is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
396
+
397
+cmd 'fullscreen enable';
398
+is($x->input_focus, $window->id, 'window still focused');
399
+is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
400
+
401
+cmd 'fullscreen enable';
402
+is($x->input_focus, $window->id, 'window still focused');
403
+is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace');
404
+
405
+$window->fullscreen(0);
406
+sync_with_i3;
407
+is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
408
+
409
+################################################################################
410
+# Verify that command ‘fullscreen enable global’ works and is idempotent.
411
+################################################################################
412
+
413
+$tmp = fresh_workspace;
414
+
415
+$window = open_window;
416
+is($x->input_focus, $window->id, 'window focused');
417
+is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
418
+
419
+cmd 'fullscreen enable global';
420
+is($x->input_focus, $window->id, 'window still focused');
421
+is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
422
+
423
+cmd 'fullscreen enable global';
424
+is($x->input_focus, $window->id, 'window still focused');
425
+is(fullscreen_windows($tmp), 1, 'still one fullscreen window on workspace');
426
+
427
+$window->fullscreen(0);
428
+sync_with_i3;
429
+is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
430
+
431
+################################################################################
432
+# Verify that command ‘fullscreen disable’ works and is idempotent.
433
+################################################################################
434
+
435
+$tmp = fresh_workspace;
436
+
437
+$window = open_window;
438
+is($x->input_focus, $window->id, 'window focused');
439
+is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
440
+
441
+$window->fullscreen(1);
442
+sync_with_i3;
443
+is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
444
+
445
+cmd 'fullscreen disable';
446
+is($x->input_focus, $window->id, 'window still focused');
447
+is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
448
+
449
+cmd 'fullscreen disable';
450
+is($x->input_focus, $window->id, 'window still focused');
451
+is(fullscreen_windows($tmp), 0, 'still no fullscreen window on workspace');
452
+
453
+################################################################################
454
+# Verify that command ‘fullscreen toggle’ works.
455
+################################################################################
456
+
457
+$tmp = fresh_workspace;
458
+
459
+$window = open_window;
460
+is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
461
+
462
+cmd 'fullscreen toggle';
463
+is($x->input_focus, $window->id, 'window still focused');
464
+is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
465
+
466
+cmd 'fullscreen toggle';
467
+is($x->input_focus, $window->id, 'window still focused');
468
+is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
469
+
470
+################################################################################
471
+# Verify that a window’s fullscreen is disabled when another one is enabled
472
+# on the same workspace. The new fullscreen window should be focused.
473
+################################################################################
474
+
475
+$tmp = fresh_workspace;
476
+
477
+$window = open_window;
478
+$other = open_window;
479
+
480
+is($x->input_focus, $other->id, 'other window focused');
481
+is(fullscreen_windows($tmp), 0, 'no fullscreen window on workspace');
482
+
483
+cmd 'fullscreen enable';
484
+is($x->input_focus, $other->id, 'other window focused');
485
+is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
486
+
487
+cmd '[id="' . $window->id . '"] fullscreen enable';
488
+is($x->input_focus, $window->id, 'window focused');
489
+is(fullscreen_windows($tmp), 1, 'one fullscreen window on workspace');
490
+
491
+################################################################################
492
+# Verify that when a global fullscreen is enabled the window is focused and
493
+# it’s workspace is selected, so that disabling the fullscreen keeps the window
494
+# focused and visible.
495
+################################################################################
496
+
497
+$tmp = fresh_workspace;
498
+
499
+$window = open_window;
500
+
501
+is($x->input_focus, $window->id, 'window focused');
502
+
503
+cmd 'workspace ' . get_unused_workspace;
504
+isnt($x->input_focus, $window->id, 'window not focused');
505
+isnt(focused_ws(), $tmp, 'workspace not selected');
506
+
507
+cmd '[id="' . $window->id . '"] fullscreen enable global';
508
+is($x->input_focus, $window->id, 'window focused');
509
+
510
+cmd 'fullscreen disable';
511
+is($x->input_focus, $window->id, 'window still focused');
512
+is(focused_ws(), $tmp, 'workspace selected');
513
+
514
 done_testing;