Send IPC workspace blank event when workspace becomes empty
Patch status: rejected
Patch by Marco Hunsicker
Long description:
This patch adds a new workspace "blank" event that is send after the last application window in a workspace has been closed and the workspace becomes empty.
To apply this patch, use:
curl http://cr.i3wm.org/patch/499/raw.patch | git am
b/docs/ipc
20 |
@@ -624,8 +624,9 @@ strip the highest bit first). |
21 |
|
22 |
workspace (0):: |
23 |
Sent when the user switches to a different workspace, when a new |
24 |
- workspace is initialized or when a workspace is removed (because the |
25 |
- last client vanished). |
26 |
+ workspace is initialized, when a workspace becomes empty (because the last |
27 |
+ application window has been closed) or when a workspace is removed (because |
28 |
+ the last client vanished). |
29 |
output (1):: |
30 |
Sent when RandR issues a change notification (of either screens, |
31 |
outputs, CRTCs or output properties). |
32 |
@@ -661,7 +662,7 @@ if ($is_event) { |
33 |
|
34 |
This event consists of a single serialized map containing a property |
35 |
+change (string)+ which indicates the type of the change ("focus", "init", |
36 |
-"empty", "urgent"). |
37 |
+"blank", "empty", "urgent"). |
38 |
|
39 |
Moreover, when the change is "focus", an +old (object)+ and a +current |
40 |
(object)+ properties will be present with the previous and current |
41 |
@@ -671,6 +672,11 @@ workspace, and the +old+ property will be set to +null+. Also note |
42 |
that if the previous is empty it will get destroyed when switching, |
43 |
but will still be present in the "old" property. |
44 |
|
45 |
+Please note that for historic reasons, the change "blank" indicates that |
46 |
+a workspace became empty (because the last application window has been |
47 |
+closed), while the change "empty" indicates that a workspace has been |
48 |
+removed (because it was empty). |
49 |
+ |
50 |
*Example:* |
51 |
--------------------- |
52 |
{ |
b/src/tree.c
57 |
@@ -229,6 +229,9 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool |
58 |
return false; |
59 |
} |
60 |
|
61 |
+ Con *ws = con_get_workspace(con); |
62 |
+ bool ws_was_empty = !ws || (TAILQ_EMPTY(&(ws->nodes_head)) && TAILQ_EMPTY(&(ws->floating_head))); |
63 |
+ |
64 |
if (con->window != NULL) { |
65 |
if (kill_window != DONT_KILL_WINDOW) { |
66 |
x_window_kill(con->window->id, kill_window); |
67 |
@@ -264,8 +267,6 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool |
68 |
FREE(con->window); |
69 |
} |
70 |
|
71 |
- Con *ws = con_get_workspace(con); |
72 |
- |
73 |
/* Figure out which container to focus next before detaching 'con'. */ |
74 |
if (con_is_floating(con)) { |
75 |
if (con == focused) { |
76 |
@@ -321,6 +322,12 @@ bool tree_close(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool |
77 |
TAILQ_REMOVE(&all_cons, con, all_cons); |
78 |
free(con); |
79 |
|
80 |
+ /* if the workspace became empty, notify any interested parties. */ |
81 |
+ if (!ws_was_empty && TAILQ_EMPTY(&(ws->nodes_head)) && TAILQ_EMPTY(&(ws->floating_head))) { |
82 |
+ DLOG("Issue IPC workspace blank event for workspace %s\n", ws->name); |
83 |
+ ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"blank\"}"); |
84 |
+ } |
85 |
+ |
86 |
/* in the case of floating windows, we already focused another container |
87 |
* when closing the parent, so we can exit now. */ |
88 |
if (!next) { |
b/src/workspace.c
93 |
@@ -424,6 +424,12 @@ static void _workspace_show(Con *workspace) { |
94 |
} |
95 |
} |
96 |
|
97 |
+ /* if the new workspace is empty, notify any interested parties. */ |
98 |
+ if (TAILQ_EMPTY(&(workspace->nodes_head)) && TAILQ_EMPTY(&(workspace->floating_head))) { |
99 |
+ DLOG("Issue IPC workspace blank event for workspace %s\n", workspace->name); |
100 |
+ ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"blank\"}"); |
101 |
+ } |
102 |
+ |
103 |
workspace->fullscreen_mode = CF_OUTPUT; |
104 |
LOG("focused now = %p / %s\n", focused, focused->name); |
105 |
|
b/testcases/t/223-ipc-workspace-blank.t
111 |
@@ -0,0 +1,158 @@ |
112 |
+#!perl |
113 |
+# vim:ts=4:sw=4:expandtab |
114 |
+# |
115 |
+# Please read the following documents before working on tests: |
116 |
+# • http://build.i3wm.org/docs/testsuite.html |
117 |
+# (or docs/testsuite) |
118 |
+# |
119 |
+# • http://build.i3wm.org/docs/lib-i3test.html |
120 |
+# (alternatively: perldoc ./testcases/lib/i3test.pm) |
121 |
+# |
122 |
+# • http://build.i3wm.org/docs/ipc.html |
123 |
+# (or docs/ipc) |
124 |
+# |
125 |
+# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf |
126 |
+# (unless you are already familiar with Perl) |
127 |
+# |
128 |
+# Checks the workspace "blank" event semantics. |
129 |
+# |
130 |
+use i3test; |
131 |
+ |
132 |
+SKIP: { |
133 |
+ |
134 |
+ skip "AnyEvent::I3 too old (need >= 0.15)", 1 if $AnyEvent::I3::VERSION < 0.15; |
135 |
+ |
136 |
+################################################################################ |
137 |
+# check that the workspace blank event is send when the last window was closed |
138 |
+################################################################################ |
139 |
+subtest 'Workspace blank event', sub { |
140 |
+ fresh_workspace; |
141 |
+ |
142 |
+ my $cond = AnyEvent->condvar; |
143 |
+ my $client = i3(get_socket_path(0)); |
144 |
+ $client->connect()->recv; |
145 |
+ $client->subscribe({ |
146 |
+ workspace => sub { |
147 |
+ my ($event) = @_; |
148 |
+ $cond->send($event); |
149 |
+ } |
150 |
+ })->recv; |
151 |
+ |
152 |
+ my $w1 = open_window(); |
153 |
+ |
154 |
+ cmd '[id="' . $w1->id . '"] kill'; |
155 |
+ sync_with_i3; |
156 |
+ |
157 |
+ my $event = $cond->recv; |
158 |
+ is($event->{change}, 'blank', '"Blank" event received after last window close'); |
159 |
+}; |
160 |
+ |
161 |
+################################################################################ |
162 |
+# check that no blank workspace event is send when the workspace is not empty |
163 |
+# when a window was closed |
164 |
+################################################################################ |
165 |
+subtest 'No workspace blank event', sub { |
166 |
+ my $ws1 = fresh_workspace; |
167 |
+ my $w1 = open_window(); |
168 |
+ my $w2 = open_window(); |
169 |
+ my $ws2 = fresh_workspace; |
170 |
+ my $w3 = open_window(); |
171 |
+ |
172 |
+ is(focused_ws(), $ws2, 'Focused workspace is ws2'); |
173 |
+ |
174 |
+ my @events; |
175 |
+ my $cond = AnyEvent->condvar; |
176 |
+ my $client = i3(get_socket_path(0)); |
177 |
+ $client->connect()->recv; |
178 |
+ $client->subscribe({ |
179 |
+ workspace => sub { |
180 |
+ my ($event) = @_; |
181 |
+ push @events, $event; |
182 |
+ } |
183 |
+ })->recv; |
184 |
+ |
185 |
+ # Wait for the workspace event on a new connection. Events will be delivered |
186 |
+ # to older connections earlier, so by the time it arrives here, it should be |
187 |
+ # in @events already. |
188 |
+ my $ws_event_block_conn = i3(get_socket_path(0)); |
189 |
+ $ws_event_block_conn->connect()->recv; |
190 |
+ $ws_event_block_conn->subscribe({ workspace => sub { $cond->send(1) }}); |
191 |
+ |
192 |
+ cmd "workspace $ws1"; |
193 |
+ cmd 'open'; |
194 |
+ |
195 |
+ is(focused_ws(), $ws1, 'Focused workspace is ws1'); |
196 |
+ |
197 |
+ cmd '[id="' . $w1->id . '"] kill'; |
198 |
+ sync_with_i3; |
199 |
+ |
200 |
+ my @expected_events = grep { $_->{change} eq 'focus' } @events; |
201 |
+ my @blank_events = grep { $_->{change} eq 'blank' } @events; |
202 |
+ is(@expected_events, 1, '"Focus" event received'); |
203 |
+ is(@blank_events, 0, 'No "blank" events received'); |
204 |
+}; |
205 |
+ |
206 |
+################################################################################ |
207 |
+# check that blank workspace event is send when switching to an empty workspace |
208 |
+################################################################################ |
209 |
+subtest 'Workspace blank event when switching', sub { |
210 |
+ my $ws2 = fresh_workspace; |
211 |
+ my $ws1 = fresh_workspace; |
212 |
+ my $w1 = open_window(); |
213 |
+ |
214 |
+ is(focused_ws(), $ws1, 'Focused workspace is ws1'); |
215 |
+ |
216 |
+ my $cond = AnyEvent->condvar; |
217 |
+ my $client = i3(get_socket_path()); |
218 |
+ $client->connect()->recv; |
219 |
+ $client->subscribe({ |
220 |
+ workspace => sub { |
221 |
+ my ($event) = @_; |
222 |
+ $cond->send($event); |
223 |
+ } |
224 |
+ })->recv; |
225 |
+ |
226 |
+ cmd "workspace $ws2"; |
227 |
+ cmd 'open'; |
228 |
+ |
229 |
+ is(focused_ws(), $ws2, 'Focused workspace is ws2'); |
230 |
+ |
231 |
+ my $event = $cond->recv; |
232 |
+ is($event->{change}, 'blank', '"Blank" event received upon workspace switch'); |
233 |
+}; |
234 |
+ |
235 |
+################################################################################ |
236 |
+# check that no workspace blank event is send when switching from an empty |
237 |
+# workspace |
238 |
+################################################################################ |
239 |
+subtest 'No workspace blank event when switching', sub { |
240 |
+ my $ws1 = fresh_workspace; |
241 |
+ my $window1 = open_window(); |
242 |
+ my $ws2 = fresh_workspace; |
243 |
+ |
244 |
+ is(focused_ws(), $ws2, 'Focused workspace is ws2'); |
245 |
+ |
246 |
+ my @events; |
247 |
+ my $client = i3(get_socket_path()); |
248 |
+ $client->connect()->recv; |
249 |
+ $client->subscribe({ |
250 |
+ workspace => sub { |
251 |
+ my ($event) = @_; |
252 |
+ push @events, $event; |
253 |
+ } |
254 |
+ })->recv; |
255 |
+ |
256 |
+ cmd "workspace $ws1"; |
257 |
+ cmd 'open'; |
258 |
+ |
259 |
+ is(focused_ws(), $ws1, 'Focused workspace is ws1'); |
260 |
+ |
261 |
+ my @focus_events = grep { $_->{change} eq 'focus' } @events; |
262 |
+ my @blank_events = grep { $_->{change} eq 'blank' } @events; |
263 |
+ is(@focus_events, 1, '"Focus" event received'); |
264 |
+ is(@blank_events, 0, 'No "blank" events received'); |
265 |
+}; |
266 |
+ |
267 |
+} |
268 |
+ |
269 |
+done_testing; |