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; |