i3 - improved tiling WM


Send IPC workspace empty event after last window closed

Patch status: needinfo

Patch by Marco Hunsicker

Long description:

This patch sends the workspace empty event after the last container in
a workspace has been closed and the workspace becomes empty again.

Additionally this patch adjusts the prior behavior that the workspace
empty event was send when switching *from* an empty workspace. Now it
is send when switching *to* an empty workspace (as client now know
already about the old empty workspace).

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

b/src/tree.c

23
@@ -229,6 +229,9 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
24
         return false;
25
     }
26
 
27
+    Con *ws = con_get_workspace(con);
28
+    bool ws_empty = !ws || (TAILQ_EMPTY(&(ws->nodes_head)) && TAILQ_EMPTY(&(ws->floating_head)));
29
+
30
     if (con->window != NULL) {
31
         if (kill_window != DONT_KILL_WINDOW) {
32
             x_window_kill(con->window->id, kill_window);
33
@@ -264,8 +267,6 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
34
         FREE(con->window);
35
     }
36
 
37
-    Con *ws = con_get_workspace(con);
38
-
39
     /* Figure out which container to focus next before detaching 'con'. */
40
     if (con_is_floating(con)) {
41
         if (con == focused) {
42
@@ -321,6 +322,12 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool
43
     TAILQ_REMOVE(&all_cons, con, all_cons);
44
     free(con);
45
 
46
+    /* if the workspace is now empty, notify any interested parties. */
47
+    if (ws && !ws_empty && TAILQ_EMPTY(&(ws->nodes_head)) && TAILQ_EMPTY(&(ws->floating_head))) {
48
+        DLOG("Issue IPC workspace empty event for workspace %s\n", ws->name);
49
+        ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
50
+    }
51
+
52
     /* in the case of floating windows, we already focused another container
53
      * when closing the parent, so we can exit now. */
54
     if (!next) {

b/src/workspace.c

59
@@ -420,10 +420,15 @@ static void _workspace_show(Con *workspace) {
60
         if (!workspace_is_visible(old)) {
61
             LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
62
             tree_close(old, DONT_KILL_WINDOW, false, false);
63
-            ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
64
         }
65
     }
66
 
67
+    /* if the new workspace is empty, notify any interested parties. */
68
+    if (TAILQ_EMPTY(&(workspace->nodes_head)) && TAILQ_EMPTY(&(workspace->floating_head))) {
69
+        DLOG("Issue IPC workspace empty event for workspace %s\n", workspace->name);
70
+        ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
71
+    }
72
+
73
     workspace->fullscreen_mode = CF_OUTPUT;
74
     LOG("focused now = %p / %s\n", focused, focused->name);
75
 

b/testcases/t/223-ipc-empty-workspace.t

81
@@ -0,0 +1,105 @@
82
+#!perl
83
+# vim:ts=4:sw=4:expandtab
84
+#
85
+# Please read the following documents before working on tests:
86
+# • http://build.i3wm.org/docs/testsuite.html
87
+#   (or docs/testsuite)
88
+#
89
+# • http://build.i3wm.org/docs/lib-i3test.html
90
+#   (alternatively: perldoc ./testcases/lib/i3test.pm)
91
+#
92
+# • http://build.i3wm.org/docs/ipc.html
93
+#   (or docs/ipc)
94
+#
95
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
96
+#   (unless you are already familiar with Perl)
97
+#
98
+# Check if the workspace empty event is correctly send after window close.
99
+#
100
+use i3test i3_autostart => 0;
101
+use List::Util qw(first);
102
+
103
+SKIP: {
104
+
105
+    skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15;
106
+
107
+my $config = <<EOT;
108
+# i3 config file (v4)
109
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
110
+
111
+bar {
112
+    # Start a default instance of i3bar which provides workspace buttons.
113
+    # Additionally, i3status will provide a statusline.
114
+    status_command i3status --foo
115
+}
116
+EOT
117
+
118
+my $pid = launch_with_config($config);
119
+
120
+my $i3 = i3(get_socket_path(0));
121
+$i3->connect()->recv;
122
+
123
+##############################################################
124
+# check that the empty event is send when last window closes
125
+##############################################################
126
+
127
+my $tmp = fresh_workspace;
128
+
129
+my $bars = $i3->get_bar_config()->recv;
130
+is(@$bars, 1, 'one bar configured');
131
+
132
+my $cond = AnyEvent->condvar;
133
+
134
+$i3->subscribe({
135
+    workspace => sub {
136
+        my ($event) = @_;
137
+        $cond->send($event);
138
+    }
139
+})->recv;
140
+
141
+my $w1 = open_window();
142
+
143
+cmd '[id="' . $w1->id . '"] kill';
144
+sync_with_i3;
145
+
146
+my $event = $cond->recv;
147
+is($event->{change}, 'empty', 'Empty workspace event received after last window close');
148
+
149
+
150
+##############################################################
151
+# check that no empty event is send when workspace is not
152
+# empty after window close
153
+##############################################################
154
+
155
+$tmp = fresh_workspace;
156
+
157
+$cond = AnyEvent->condvar;
158
+
159
+$i3->subscribe({
160
+    workspace => sub {
161
+        my ($event) = @_;
162
+        ok($event->{change} ne 'empty', 'No empty workspace event received');
163
+        $cond->send($event);
164
+    },
165
+    window => sub {
166
+        my ($event) = @_;
167
+        $cond->send($event);
168
+    }
169
+})->recv;
170
+
171
+$w1 = open_window(name => 'Window 1');
172
+my $w2 = open_window(name => 'Window 2');
173
+
174
+cmd '[id="' . $w2->id . '"] kill';
175
+sync_with_i3;
176
+
177
+$event = $cond->recv;
178
+
179
+is($event->{change}, 'focus', 'Window focus event received');
180
+is($event->{container}->{name}, 'Window 1', 'Window 1 focused');
181
+
182
+exit_gracefully($pid);
183
+
184
+}
185
+
186
+done_testing;