]> git.sur5r.net Git - i3/i3/blob - testcases/t/185-scratchpad.t
fd3827f740c16de5b6526b237d74466c9c944f32
[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 # • https://build.i3wm.org/docs/testsuite.html
6 #   (or docs/testsuite)
7 #
8 # • https://build.i3wm.org/docs/lib-i3test.html
9 #   (alternatively: perldoc ./testcases/lib/i3test.pm)
10 #
11 # • https://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} eq 'con' } @{$__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 on the next 'scratchpad show'.
97 ################################################################################
98
99 my ($nodes, $focus) = get_ws_content($tmp);
100 is(scalar @$nodes, 1, 'precisely one window on current ws');
101 is($nodes->[0]->{scratchpad_state}, 'none', 'scratchpad_state none');
102
103 cmd 'move scratchpad';
104
105 $__i3_scratch = get_ws('__i3_scratch');
106 my @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
107 is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
108 ($nodes, $focus) = get_ws_content($tmp);
109 is(scalar @$nodes, 0, 'no window on current ws anymore');
110
111 is($scratch_nodes[0]->{scratchpad_state}, 'fresh', 'scratchpad_state fresh');
112
113 $tree = $i3->get_tree->recv;
114 my $__i3 = first { $_->{name} eq '__i3' } @{$tree->{nodes}};
115 isnt($tree->{focus}->[0], $__i3->{id}, '__i3 output not focused');
116
117 $get_outputs = $i3->get_outputs->recv;
118 $get_ws = $i3->get_workspaces->recv;
119 @output_names = map { $_->{name} } @$get_outputs;
120 @ws_names = map { $_->{name} } @$get_ws;
121
122 ok(!('__i3' ~~ @output_names), '__i3 not in GET_OUTPUTS');
123 ok(!('__i3_scratch' ~~ @ws_names), '__i3_scratch ws not in GET_WORKSPACES');
124
125 ################################################################################
126 # 4: Verify that 'scratchpad show' makes the window visible.
127 ################################################################################
128
129 # Open another window so that we can check if focus is on the scratchpad window
130 # after showing it.
131 my $second_window = open_window;
132 my $old_focus = get_focused($tmp);
133
134 cmd 'scratchpad show';
135
136 isnt(get_focused($tmp), $old_focus, 'focus changed');
137
138 $__i3_scratch = get_ws('__i3_scratch');
139 @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
140 is(scalar @scratch_nodes, 0, '__i3_scratch is now empty');
141
142 my $ws = get_ws($tmp);
143 my $output = $tree->{nodes}->[1];
144 my $scratchrect = $ws->{floating_nodes}->[0]->{rect};
145 my $outputrect = $output->{rect};
146
147 is($scratchrect->{width}, $outputrect->{width} * 0.5, 'scratch width is 50%');
148 is($scratchrect->{height}, $outputrect->{height} * 0.75, 'scratch height is 75%');
149 is($scratchrect->{x},
150    ($outputrect->{width} / 2) - ($scratchrect->{width} / 2),
151    'scratch window centered horizontally');
152 is($scratchrect->{y},
153    ($outputrect->{height} / 2 ) - ($scratchrect->{height} / 2),
154    'scratch window centered vertically');
155
156 ################################################################################
157 # 5: Another 'scratchpad show' should make that window go to the scratchpad
158 # again.
159 ################################################################################
160
161 cmd 'scratchpad show';
162
163 $__i3_scratch = get_ws('__i3_scratch');
164 @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
165 is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
166
167 ################################################################################
168 # 6: Resizing the window should disable auto centering on scratchpad show
169 ################################################################################
170
171 cmd 'scratchpad show';
172
173 $ws = get_ws($tmp);
174 is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'fresh',
175    'scratchpad_state fresh');
176
177 cmd 'resize grow width 10 px';
178 cmd 'scratchpad show';
179 cmd 'scratchpad show';
180
181 $ws = get_ws($tmp);
182 $scratchrect = $ws->{floating_nodes}->[0]->{rect};
183 $outputrect = $output->{rect};
184
185 is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'changed',
186    'scratchpad_state changed');
187 is($scratchrect->{width}, $outputrect->{width} * 0.5 + 10, 'scratch width is 50% + 10px');
188
189 cmd 'resize shrink width 10 px';
190 cmd 'scratchpad show';
191
192 ################################################################################
193 # 7: Verify that repeated 'scratchpad show' cycle through the stack, that is,
194 # toggling a visible window should insert it at the bottom of the stack of the
195 # __i3_scratch workspace.
196 ################################################################################
197
198 my $third_window = open_window(name => 'scratch-match');
199 cmd 'move scratchpad';
200
201 $__i3_scratch = get_ws('__i3_scratch');
202 @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
203 is(scalar @scratch_nodes, 2, '__i3_scratch contains both windows');
204
205 is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'changed window first');
206 is($scratch_nodes[1]->{scratchpad_state}, 'fresh', 'fresh window is second');
207
208 my $changed_id = $scratch_nodes[0]->{nodes}->[0]->{id};
209 my $fresh_id = $scratch_nodes[1]->{nodes}->[0]->{id};
210 is($scratch_nodes[0]->{id}, $__i3_scratch->{focus}->[0], 'changed window first');
211 is($scratch_nodes[1]->{id}, $__i3_scratch->{focus}->[1], 'fresh window second');
212
213 # Repeatedly use 'scratchpad show' and check that the windows are different.
214 cmd 'scratchpad show';
215
216 is(get_focused($tmp), $changed_id, 'focus changed');
217
218 $ws = get_ws($tmp);
219 $scratchrect = $ws->{floating_nodes}->[0]->{rect};
220 is($scratchrect->{width}, $outputrect->{width} * 0.5, 'scratch width is 50%');
221 is($scratchrect->{height}, $outputrect->{height} * 0.75, 'scratch height is 75%');
222 is($scratchrect->{x},
223    ($outputrect->{width} / 2) - ($scratchrect->{width} / 2),
224    'scratch window centered horizontally');
225 is($scratchrect->{y},
226    ($outputrect->{height} / 2 ) - ($scratchrect->{height} / 2),
227    'scratch window centered vertically');
228
229 cmd 'scratchpad show';
230
231 isnt(get_focused($tmp), $changed_id, 'focus changed');
232
233 cmd 'scratchpad show';
234
235 is(get_focused($tmp), $fresh_id, 'focus changed');
236
237 cmd 'scratchpad show';
238
239 isnt(get_focused($tmp), $fresh_id, 'focus changed');
240
241 ################################################################################
242 # 8: Show it, move it around, hide it. Verify that the position is retained
243 # when showing it again.
244 ################################################################################
245
246 cmd '[title="scratch-match"] scratchpad show';
247
248 isnt(get_focused($tmp), $old_focus, 'scratchpad window shown');
249
250 my $oldrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};
251
252 cmd 'move left';
253
254 $scratchrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};
255 isnt($scratchrect->{x}, $oldrect->{x}, 'x position changed');
256 $oldrect = $scratchrect;
257
258 # hide it, then show it again
259 cmd '[title="scratch-match"] scratchpad show';
260 cmd '[title="scratch-match"] scratchpad show';
261
262 # verify the position is still the same
263 $scratchrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};
264
265 is_deeply($scratchrect, $oldrect, 'position/size the same');
266
267 # hide it again for the next test
268 cmd '[title="scratch-match"] scratchpad show';
269
270 is(get_focused($tmp), $old_focus, 'scratchpad window hidden');
271
272 is(scalar @{get_ws($tmp)->{nodes}}, 1, 'precisely one window on current ws');
273
274 ################################################################################
275 # 9: restart i3 and verify that the scratchpad show still works
276 ################################################################################
277
278 $__i3_scratch = get_ws('__i3_scratch');
279 my $old_nodes = scalar @{$__i3_scratch->{nodes}};
280 my $old_floating_nodes = scalar @{$__i3_scratch->{floating_nodes}};
281
282 cmd 'restart';
283
284 does_i3_live;
285
286 $__i3_scratch = get_ws('__i3_scratch');
287 is(scalar @{$__i3_scratch->{nodes}}, $old_nodes, "number of nodes matches ($old_nodes)");
288 is(scalar @{$__i3_scratch->{floating_nodes}}, $old_floating_nodes, "number of floating nodes matches ($old_floating_nodes)");
289
290 is(scalar @{get_ws($tmp)->{nodes}}, 1, 'still precisely one window on current ws');
291 is(scalar @{get_ws($tmp)->{floating_nodes}}, 0, 'still no floating windows on current ws');
292
293 # verify that we can display the scratchpad window
294 cmd '[title="scratch-match"] scratchpad show';
295
296 $ws = get_ws($tmp);
297 is(scalar @{$ws->{nodes}}, 1, 'still precisely one window on current ws');
298 is(scalar @{$ws->{floating_nodes}}, 1, 'precisely one floating windows on current ws');
299 is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'changed', 'scratchpad_state is "changed"');
300
301 ################################################################################
302 # 10: on an empty workspace, ensure the 'move scratchpad' command does nothing
303 ################################################################################
304
305 $tmp = fresh_workspace;
306
307 cmd 'move scratchpad';
308
309 does_i3_live;
310
311 ################################################################################
312 # 11: focus a workspace and move all of its children to the scratchpad area
313 ################################################################################
314
315 sub verify_scratchpad_move_multiple_win {
316     my $floating = shift;
317
318     my $first = open_window;
319     my $second = open_window;
320
321     if ($floating) {
322         cmd 'floating toggle';
323         cmd 'focus tiling';
324     }
325
326     cmd 'focus parent';
327     cmd 'move scratchpad';
328
329     does_i3_live;
330
331     $ws = get_ws($tmp);
332     is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
333     is(scalar @{$ws->{floating_nodes}}, 0, 'no floating windows on ws');
334
335     # show the first window.
336     cmd 'scratchpad show';
337
338     $ws = get_ws($tmp);
339     is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
340     is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
341
342     $old_focus = get_focused($tmp);
343
344     cmd 'scratchpad show';
345
346     # show the second window.
347     cmd 'scratchpad show';
348
349     $ws = get_ws($tmp);
350     is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
351     is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');
352
353     isnt(get_focused($tmp), $old_focus, 'focus changed');
354 }
355
356 $tmp = fresh_workspace;
357 verify_scratchpad_move_multiple_win(0);
358 $tmp = fresh_workspace;
359 verify_scratchpad_move_multiple_win(1);
360
361 ################################################################################
362 # 12: open a scratchpad window on a workspace, switch to another workspace and
363 # call 'scratchpad show' again
364 ################################################################################
365
366 sub verify_scratchpad_move_with_visible_scratch_con {
367     my ($first, $second, $cross_output) = @_;
368
369     cmd "workspace $first";
370
371     my $window1 = open_window;
372     cmd 'move scratchpad';
373
374     my $window2 = open_window;
375     cmd 'move scratchpad';
376
377     # this should bring up window 1
378     cmd 'scratchpad show';
379
380     $ws = get_ws($first);
381     is(scalar @{$ws->{floating_nodes}}, 1, 'one floating node on ws1');
382     is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
383
384     # this should bring up window 1
385     cmd "workspace $second";
386     cmd 'scratchpad show';
387     is($x->input_focus, $window1->id, "showed the correct scratchpad window1");
388
389     my $ws2 = get_ws($second);
390     is(scalar @{$ws2->{floating_nodes}}, 1, 'one floating node on ws2');
391     unless ($cross_output) {
392         ok(!workspace_exists($first), 'ws1 was empty and therefore closed');
393     } else {
394         $ws = get_ws($first);
395         is(scalar @{$ws->{floating_nodes}}, 0, 'ws1 has no floating nodes');
396     }
397
398     # hide window 1 again
399     cmd 'move scratchpad';
400
401     # this should bring up window 2
402     cmd "workspace $first";
403     cmd 'scratchpad show';
404     is($x->input_focus, $window2->id, "showed the correct scratchpad window");
405 }
406
407 # let's clear the scratchpad first
408 sub clear_scratchpad {
409     while (scalar @{get_ws('__i3_scratch')->{floating_nodes}}) {
410         cmd 'scratchpad show';
411         cmd 'kill';
412     }
413 }
414
415 clear_scratchpad;
416 is (scalar @{get_ws('__i3_scratch')->{floating_nodes}}, 0, "scratchpad is empty");
417
418 my ($first, $second);
419 $first = fresh_workspace;
420 $second = fresh_workspace;
421
422 verify_scratchpad_move_with_visible_scratch_con($first, $second, 0);
423 does_i3_live;
424
425
426 ################################################################################
427 # 13: Test whether scratchpad show moves focus to the scratchpad window
428 # when another window on the same workspace has focus
429 ################################################################################
430
431 clear_scratchpad;
432 $ws = fresh_workspace;
433
434 open_window;
435 my $scratch = get_focused($ws);
436 cmd 'move scratchpad';
437 cmd 'scratchpad show';
438
439 open_window;
440 my $not_scratch = get_focused($ws);
441 is(get_focused($ws), $not_scratch, 'not scratch window has focus');
442
443 cmd 'scratchpad show';
444
445 is(get_focused($ws), $scratch, 'scratchpad is focused');
446
447 # TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint
448
449 ################################################################################
450 # 14: Verify that 'move scratchpad' sends floating containers to scratchpad but
451 # does not resize/resposition the container on the next 'scratchpad show', i.e.,
452 # i3 sets the scratchpad flag to SCRATCHPAD_CHANGED
453 ################################################################################
454
455 clear_scratchpad;
456 $tmp = fresh_workspace;
457 open_window;
458
459 ($nodes, $focus) = get_ws_content($tmp);
460 is(scalar @$nodes, 1, 'precisely one window on current ws');
461 is($nodes->[0]->{scratchpad_state}, 'none', 'scratchpad_state none');
462
463 cmd 'floating toggle';
464 cmd 'move scratchpad';
465
466 $__i3_scratch = get_ws('__i3_scratch');
467 @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
468 is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
469 ($nodes, $focus) = get_ws_content($tmp);
470 is(scalar @$nodes, 0, 'no window on current ws anymore');
471
472 is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed');
473
474 ################################################################################
475 # 15: Verify that 'scratchpad show' returns correct info.
476 ################################################################################
477
478 kill_all_windows;
479
480 my $result = cmd 'scratchpad show';
481 is($result->[0]->{success}, 0, 'no scratchpad window and call to scratchpad failed');
482
483 open_window;
484 cmd 'move scratchpad';
485 $result = cmd 'scratchpad show';
486 is($result->[0]->{success}, 1, 'call to scratchpad succeeded');
487 $result = cmd 'scratchpad show';
488 is($result->[0]->{success}, 1, 'call to scratchpad succeeded');
489
490 kill_all_windows;
491 $result = cmd 'scratchpad show';
492 is($result->[0]->{success}, 0, 'call to scratchpad failed');
493
494 ################################################################################
495 # 16: Verify that 'scratchpad show' with the criteria returns correct info.
496 ################################################################################
497
498 open_window(name => "scratch-match");
499 cmd 'move scratchpad';
500
501 $result = cmd '[title="scratch-match"] scratchpad show';
502 is($result->[0]->{success}, 1, 'call to scratchpad with the criteria succeeded');
503
504 $result = cmd '[title="nomatch"] scratchpad show';
505 is($result->[0]->{success}, 0, 'call to scratchpad with non-matching criteria failed');
506
507 ################################################################################
508 # 17: Open a scratchpad window on a workspace, switch to another workspace and
509 # call 'scratchpad show' again. Verify that it returns correct info.
510 ################################################################################
511
512 fresh_workspace;
513 open_window;
514 cmd 'move scratchpad';
515
516 fresh_workspace;
517 $result = cmd 'scratchpad show';
518 is($result->[0]->{success}, 1, 'call to scratchpad in another workspace succeeded');
519
520 ################################################################################
521 # 18: Disabling floating for a scratchpad window should not work.
522 ################################################################################
523
524 kill_all_windows;
525
526 $ws = fresh_workspace;
527 $window = open_window;
528 cmd 'move scratchpad';
529 cmd '[id=' . $window->id . '] floating disable';
530
531 is(scalar @{get_ws_content($ws)}, 0, 'no window in workspace');
532 cmd 'scratchpad show';
533 is($x->input_focus, $window->id, 'scratchpad window shown');
534
535
536 done_testing;