]> git.sur5r.net Git - i3/i3/blob - testcases/t/185-scratchpad.t
Merge branch 'master' into next
[i3/i3] / testcases / t / 185-scratchpad.t
1 #!perl
2 # vim:ts=4:sw=4:expandtab
3 #
4 # Please read the following documents before working on tests:
5 # • http://build.i3wm.org/docs/testsuite.html
6 #   (or docs/testsuite)
7 #
8 # • http://build.i3wm.org/docs/lib-i3test.html
9 #   (alternatively: perldoc ./testcases/lib/i3test.pm)
10 #
11 # • http://build.i3wm.org/docs/ipc.html
12 #   (or docs/ipc)
13 #
14 # • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
15 #   (unless you are already familiar with Perl)
16 #
17 # Tests for the scratchpad functionality.
18 #
19 use i3test;
20 use List::Util qw(first);
21
22 my $i3 = i3(get_socket_path());
23 my $tmp = fresh_workspace;
24
25 ################################################################################
26 # 1: Verify that the __i3 output contains the __i3_scratch workspace and that
27 # it’s empty initially. Also, __i3 should not show up in GET_OUTPUTS so that
28 # tools like i3bar will not handle it. Similarly, __i3_scratch should not show
29 # up in GET_WORKSPACES. After all, you should not be able to switch to it.
30 ################################################################################
31
32 my $tree = $i3->get_tree->recv;
33 is($tree->{name}, 'root', 'root node is the first thing we get');
34
35 my @__i3 = grep { $_->{name} eq '__i3' } @{$tree->{nodes}};
36 is(scalar @__i3, 1, 'output __i3 found');
37
38 my $content = first { $_->{type} == 2 } @{$__i3[0]->{nodes}};
39 my @workspaces = @{$content->{nodes}};
40 my @workspace_names = map { $_->{name} } @workspaces;
41 ok('__i3_scratch' ~~ @workspace_names, '__i3_scratch workspace found');
42
43 my $get_outputs = $i3->get_outputs->recv;
44 my $get_ws = $i3->get_workspaces->recv;
45 my @output_names = map { $_->{name} } @$get_outputs;
46 my @ws_names = map { $_->{name} } @$get_ws;
47
48 ok(!('__i3' ~~ @output_names), '__i3 not in GET_OUTPUTS');
49 ok(!('__i3_scratch' ~~ @ws_names), '__i3_scratch ws not in GET_WORKSPACES');
50
51 ################################################################################
52 # 2: Verify that you cannot switch to the __i3_scratch workspace and moving
53 # windows to __i3_scratch does not work (users should be aware of the different
54 # behavior and acknowledge that by using the scratchpad commands).
55 ################################################################################
56
57 # Try focusing the workspace.
58 my $__i3_scratch = get_ws('__i3_scratch');
59 is($__i3_scratch->{focused}, 0, '__i3_scratch ws not focused');
60
61 cmd 'workspace __i3_scratch';
62
63 $__i3_scratch = get_ws('__i3_scratch');
64 is($__i3_scratch->{focused}, 0, '__i3_scratch ws still not focused');
65
66
67 # Try moving a window to it.
68 is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');
69
70 my $window = open_window;
71 cmd 'move workspace __i3_scratch';
72
73 $__i3_scratch = get_ws('__i3_scratch');
74 is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');
75
76
77 # Try moving the window with the 'output <direction>' command.
78 # We hardcode output left since the pseudo-output will be initialized before
79 # every other output, so it will always be the first one.
80 cmd 'move output left';
81
82 $__i3_scratch = get_ws('__i3_scratch');
83 is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');
84
85
86 # Try moving the window with the 'output <name>' command.
87 cmd 'move output __i3';
88
89 $__i3_scratch = get_ws('__i3_scratch');
90 is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');
91
92
93 ################################################################################
94 # 3: Verify that 'scratchpad toggle' sends a window to the __i3_scratch
95 # workspace and sets the scratchpad flag to SCRATCHPAD_FRESH. The window’s size
96 # and position will be changed (once!) on the next 'scratchpad show' and the
97 # flag will be changed to SCRATCHPAD_CHANGED.
98 ################################################################################
99
100 my ($nodes, $focus) = get_ws_content($tmp);
101 is(scalar @$nodes, 1, 'precisely one window on current ws');
102 is($nodes->[0]->{scratchpad_state}, 'none', 'scratchpad_state none');
103
104 cmd 'move scratchpad';
105
106 $__i3_scratch = get_ws('__i3_scratch');
107 my @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
108 is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
109 ($nodes, $focus) = get_ws_content($tmp);
110 is(scalar @$nodes, 0, 'no window on current ws anymore');
111
112 is($scratch_nodes[0]->{scratchpad_state}, 'fresh', 'scratchpad_state fresh');
113
114 $tree = $i3->get_tree->recv;
115 my $__i3 = first { $_->{name} eq '__i3' } @{$tree->{nodes}};
116 isnt($tree->{focus}->[0], $__i3->{id}, '__i3 output not focused');
117
118 $get_outputs = $i3->get_outputs->recv;
119 $get_ws = $i3->get_workspaces->recv;
120 @output_names = map { $_->{name} } @$get_outputs;
121 @ws_names = map { $_->{name} } @$get_ws;
122
123 ok(!('__i3' ~~ @output_names), '__i3 not in GET_OUTPUTS');
124 ok(!('__i3_scratch' ~~ @ws_names), '__i3_scratch ws not in GET_WORKSPACES');
125
126 ################################################################################
127 # 4: Verify that 'scratchpad show' makes the window visible.
128 ################################################################################
129
130 # Open another window so that we can check if focus is on the scratchpad window
131 # after showing it.
132 my $second_window = open_window;
133 my $old_focus = get_focused($tmp);
134
135 cmd 'scratchpad show';
136
137 isnt(get_focused($tmp), $old_focus, 'focus changed');
138
139 $__i3_scratch = get_ws('__i3_scratch');
140 @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
141 is(scalar @scratch_nodes, 0, '__i3_scratch is now empty');
142
143 my $ws = get_ws($tmp);
144 my $output = $tree->{nodes}->[1];
145 my $scratchrect = $ws->{floating_nodes}->[0]->{rect};
146 my $outputrect = $output->{rect};
147
148 is($scratchrect->{width}, $outputrect->{width} * 0.5, 'scratch width is 50%');
149 is($scratchrect->{height}, $outputrect->{height} * 0.75, 'scratch height is 75%');
150 is($scratchrect->{x},
151    ($outputrect->{width} / 2) - ($scratchrect->{width} / 2),
152    'scratch window centered horizontally');
153 is($scratchrect->{y},
154    ($outputrect->{height} / 2 ) - ($scratchrect->{height} / 2),
155    'scratch window centered vertically');
156
157 ################################################################################
158 # 5: Another 'scratchpad show' should make that window go to the scratchpad
159 # again.
160 ################################################################################
161
162 cmd 'scratchpad show';
163
164 $__i3_scratch = get_ws('__i3_scratch');
165 @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
166 is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
167
168 is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed');
169
170 ################################################################################
171 # 6: Verify that repeated 'scratchpad show' cycle through the stack, that is,
172 # toggling a visible window should insert it at the bottom of the stack of the
173 # __i3_scratch workspace.
174 ################################################################################
175
176 my $third_window = open_window(name => 'scratch-match');
177 cmd 'move scratchpad';
178
179 $__i3_scratch = get_ws('__i3_scratch');
180 @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
181 is(scalar @scratch_nodes, 2, '__i3_scratch contains both windows');
182
183 is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'changed window first');
184 is($scratch_nodes[1]->{scratchpad_state}, 'fresh', 'fresh window is second');
185
186 my $changed_id = $scratch_nodes[0]->{nodes}->[0]->{id};
187 my $fresh_id = $scratch_nodes[1]->{nodes}->[0]->{id};
188 is($scratch_nodes[0]->{id}, $__i3_scratch->{focus}->[0], 'changed window first');
189 is($scratch_nodes[1]->{id}, $__i3_scratch->{focus}->[1], 'fresh window second');
190
191 # Repeatedly use 'scratchpad show' and check that the windows are different.
192 cmd 'scratchpad show';
193
194 is(get_focused($tmp), $changed_id, 'focus changed');
195
196 $ws = get_ws($tmp);
197 $scratchrect = $ws->{floating_nodes}->[0]->{rect};
198 is($scratchrect->{width}, $outputrect->{width} * 0.5, 'scratch width is 50%');
199 is($scratchrect->{height}, $outputrect->{height} * 0.75, 'scratch height is 75%');
200 is($scratchrect->{x},
201    ($outputrect->{width} / 2) - ($scratchrect->{width} / 2),
202    'scratch window centered horizontally');
203 is($scratchrect->{y},
204    ($outputrect->{height} / 2 ) - ($scratchrect->{height} / 2),
205    'scratch window centered vertically');
206
207 cmd 'scratchpad show';
208
209 isnt(get_focused($tmp), $changed_id, 'focus changed');
210
211 cmd 'scratchpad show';
212
213 is(get_focused($tmp), $fresh_id, 'focus changed');
214
215 cmd 'scratchpad show';
216
217 isnt(get_focused($tmp), $fresh_id, 'focus changed');
218
219 ################################################################################
220 # 8: Show it, move it around, hide it. Verify that the position is retained
221 # when showing it again.
222 ################################################################################
223
224 cmd '[title="scratch-match"] scratchpad show';
225
226 isnt(get_focused($tmp), $old_focus, 'scratchpad window shown');
227
228 my $oldrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};
229
230 cmd 'move left';
231
232 $scratchrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};
233 isnt($scratchrect->{x}, $oldrect->{x}, 'x position changed');
234 $oldrect = $scratchrect;
235
236 # hide it, then show it again
237 cmd '[title="scratch-match"] scratchpad show';
238 cmd '[title="scratch-match"] scratchpad show';
239
240 # verify the position is still the same
241 $scratchrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};
242
243 is_deeply($scratchrect, $oldrect, 'position/size the same');
244
245 # hide it again for the next test
246 cmd '[title="scratch-match"] scratchpad show';
247
248 is(get_focused($tmp), $old_focus, 'scratchpad window hidden');
249
250 is(scalar @{get_ws($tmp)->{nodes}}, 1, 'precisely one window on current ws');
251
252 ################################################################################
253 # 9: restart i3 and verify that the scratchpad show still works
254 ################################################################################
255
256 $__i3_scratch = get_ws('__i3_scratch');
257 my $old_nodes = scalar @{$__i3_scratch->{nodes}};
258 my $old_floating_nodes = scalar @{$__i3_scratch->{floating_nodes}};
259
260 cmd 'restart';
261
262 does_i3_live;
263
264 $__i3_scratch = get_ws('__i3_scratch');
265 is(scalar @{$__i3_scratch->{nodes}}, $old_nodes, "number of nodes matches ($old_nodes)");
266 is(scalar @{$__i3_scratch->{floating_nodes}}, $old_floating_nodes, "number of floating nodes matches ($old_floating_nodes)");
267
268 is(scalar @{get_ws($tmp)->{nodes}}, 1, 'still precisely one window on current ws');
269 is(scalar @{get_ws($tmp)->{floating_nodes}}, 0, 'still no floating windows on current ws');
270
271 # verify that we can display the scratchpad window
272 cmd '[title="scratch-match"] scratchpad show';
273
274 $ws = get_ws($tmp);
275 is(scalar @{$ws->{nodes}}, 1, 'still precisely one window on current ws');
276 is(scalar @{$ws->{floating_nodes}}, 1, 'precisely one floating windows on current ws');
277 is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'changed', 'scratchpad_state is "changed"');
278
279 ################################################################################
280 # 10: on an empty workspace, ensure the 'move scratchpad' command does nothing
281 ################################################################################
282
283 $tmp = fresh_workspace;
284
285 cmd 'move scratchpad';
286
287 does_i3_live;
288
289 ################################################################################
290 # 11: focus a workspace and move all of its children to the scratchpad area
291 ################################################################################
292
293 sub verify_scratchpad_move_multiple_win {
294     my $floating = shift;
295
296     my $first = open_window;
297     my $second = open_window;
298
299     if ($floating) {
300         cmd 'floating toggle';
301         cmd 'focus tiling';
302     }
303
304     cmd 'focus parent';
305     cmd 'move scratchpad';
306
307     does_i3_live;
308
309     $ws = get_ws($tmp);
310     is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
311     is(scalar @{$ws->{floating_nodes}}, 0, 'no floating windows on ws');
312
313     # show the first window.
314     cmd 'scratchpad show';
315
316     $ws = get_ws($tmp);
317     is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
318     is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
319
320     $old_focus = get_focused($tmp);
321
322     cmd 'scratchpad show';
323
324     # show the second window.
325     cmd 'scratchpad show';
326
327     $ws = get_ws($tmp);
328     is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
329     is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
330
331     isnt(get_focused($tmp), $old_focus, 'focus changed');
332 }
333
334 $tmp = fresh_workspace;
335 verify_scratchpad_move_multiple_win(0);
336 $tmp = fresh_workspace;
337 verify_scratchpad_move_multiple_win(1);
338
339 ################################################################################
340 # 12: open a scratchpad window on a workspace, switch to another workspace and
341 # call 'scratchpad show' again
342 ################################################################################
343
344 sub verify_scratchpad_move_with_visible_scratch_con {
345     my ($first, $second, $cross_output) = @_;
346
347     cmd "workspace $first";
348
349     my $window1 = open_window;
350     cmd 'move scratchpad';
351
352     my $window2 = open_window;
353     cmd 'move scratchpad';
354
355     # this should bring up window 1
356     cmd 'scratchpad show';
357
358     my $ws = get_ws($first);
359     is(scalar @{$ws->{floating_nodes}}, 1, 'one floating node on ws1');
360     is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
361
362     # this should bring up window 1
363     cmd "workspace $second";
364     cmd 'scratchpad show';
365     is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
366
367     my $ws2 = get_ws($second);
368     is(scalar @{$ws2->{floating_nodes}}, 1, 'one floating node on ws2');
369     unless ($cross_output) {
370         ok(!workspace_exists($first), 'ws1 was empty and therefore closed');
371     } else {
372         $ws = get_ws($first);
373         is(scalar @{$ws->{floating_nodes}}, 0, 'ws1 has no floating nodes');
374     }
375
376     # hide window 1 again
377     cmd 'move scratchpad';
378
379     # this should bring up window 2
380     cmd "workspace $first";
381     cmd 'scratchpad show';
382     is($x->input_focus, $window2->id, "showed the correct scratchpad window");
383 }
384
385 # let's clear the scratchpad first
386 sub clear_scratchpad {
387     while (scalar @{get_ws('__i3_scratch')->{floating_nodes}}) {
388         cmd 'scratchpad show';
389         cmd 'kill';
390     }
391 }
392
393 clear_scratchpad;
394 is (scalar @{get_ws('__i3_scratch')->{floating_nodes}}, 0, "scratchpad is empty");
395
396 my ($first, $second);
397 $first = fresh_workspace;
398 $second = fresh_workspace;
399
400 verify_scratchpad_move_with_visible_scratch_con($first, $second, 0);
401 does_i3_live;
402
403 # TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint
404
405 done_testing;