Codebase list i3-gaps / 36cd905 testcases / t / 185-scratchpad.t
36cd905

Tree @36cd905 (Download .tar.gz)

185-scratchpad.t @36cd905raw · history · blame

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
#!perl
# vim:ts=4:sw=4:expandtab
#
# Please read the following documents before working on tests:
# • https://build.i3wm.org/docs/testsuite.html
#   (or docs/testsuite)
#
# • https://build.i3wm.org/docs/lib-i3test.html
#   (alternatively: perldoc ./testcases/lib/i3test.pm)
#
# • https://build.i3wm.org/docs/ipc.html
#   (or docs/ipc)
#
# • http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
#   (unless you are already familiar with Perl)
#
# Tests for the scratchpad functionality.
#
use i3test;
use List::Util qw(first);

my $i3 = i3(get_socket_path());
my $tmp = fresh_workspace;

################################################################################
# 1: Verify that the __i3 output contains the __i3_scratch workspace and that
# it’s empty initially. Also, __i3 should not show up in GET_OUTPUTS so that
# tools like i3bar will not handle it. Similarly, __i3_scratch should not show
# up in GET_WORKSPACES. After all, you should not be able to switch to it.
################################################################################

my $tree = $i3->get_tree->recv;
is($tree->{name}, 'root', 'root node is the first thing we get');

my @__i3 = grep { $_->{name} eq '__i3' } @{$tree->{nodes}};
is(scalar @__i3, 1, 'output __i3 found');

my $content = first { $_->{type} eq 'con' } @{$__i3[0]->{nodes}};
my @workspaces = @{$content->{nodes}};
my @workspace_names = map { $_->{name} } @workspaces;
ok('__i3_scratch' ~~ @workspace_names, '__i3_scratch workspace found');

my $get_outputs = $i3->get_outputs->recv;
my $get_ws = $i3->get_workspaces->recv;
my @output_names = map { $_->{name} } @$get_outputs;
my @ws_names = map { $_->{name} } @$get_ws;

ok(!('__i3' ~~ @output_names), '__i3 not in GET_OUTPUTS');
ok(!('__i3_scratch' ~~ @ws_names), '__i3_scratch ws not in GET_WORKSPACES');

################################################################################
# 2: Verify that you cannot switch to the __i3_scratch workspace and moving
# windows to __i3_scratch does not work (users should be aware of the different
# behavior and acknowledge that by using the scratchpad commands).
################################################################################

# Try focusing the workspace.
my $__i3_scratch = get_ws('__i3_scratch');
is($__i3_scratch->{focused}, 0, '__i3_scratch ws not focused');

cmd 'workspace __i3_scratch';

$__i3_scratch = get_ws('__i3_scratch');
is($__i3_scratch->{focused}, 0, '__i3_scratch ws still not focused');


# Try moving a window to it.
is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');

my $window = open_window;
cmd 'move workspace __i3_scratch';

$__i3_scratch = get_ws('__i3_scratch');
is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');


# Try moving the window with the 'output <direction>' command.
# We hardcode output left since the pseudo-output will be initialized before
# every other output, so it will always be the first one.
cmd 'move output left';

$__i3_scratch = get_ws('__i3_scratch');
is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');


# Try moving the window with the 'output <name>' command.
cmd 'move output __i3';

$__i3_scratch = get_ws('__i3_scratch');
is(scalar @{$__i3_scratch->{floating_nodes}}, 0, '__i3_scratch ws empty');


################################################################################
# 3: Verify that 'scratchpad toggle' sends a window to the __i3_scratch
# workspace and sets the scratchpad flag to SCRATCHPAD_FRESH. The window’s size
# and position will be changed on the next 'scratchpad show'.
################################################################################

my ($nodes, $focus) = get_ws_content($tmp);
is(scalar @$nodes, 1, 'precisely one window on current ws');
is($nodes->[0]->{scratchpad_state}, 'none', 'scratchpad_state none');

cmd 'move scratchpad';

$__i3_scratch = get_ws('__i3_scratch');
my @scratch_nodes = @{$__i3_scratch->{floating_nodes}};
is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
($nodes, $focus) = get_ws_content($tmp);
is(scalar @$nodes, 0, 'no window on current ws anymore');

is($scratch_nodes[0]->{scratchpad_state}, 'fresh', 'scratchpad_state fresh');

$tree = $i3->get_tree->recv;
my $__i3 = first { $_->{name} eq '__i3' } @{$tree->{nodes}};
isnt($tree->{focus}->[0], $__i3->{id}, '__i3 output not focused');

$get_outputs = $i3->get_outputs->recv;
$get_ws = $i3->get_workspaces->recv;
@output_names = map { $_->{name} } @$get_outputs;
@ws_names = map { $_->{name} } @$get_ws;

ok(!('__i3' ~~ @output_names), '__i3 not in GET_OUTPUTS');
ok(!('__i3_scratch' ~~ @ws_names), '__i3_scratch ws not in GET_WORKSPACES');

################################################################################
# 4: Verify that 'scratchpad show' makes the window visible.
################################################################################

# Open another window so that we can check if focus is on the scratchpad window
# after showing it.
my $second_window = open_window;
my $old_focus = get_focused($tmp);

cmd 'scratchpad show';

isnt(get_focused($tmp), $old_focus, 'focus changed');

$__i3_scratch = get_ws('__i3_scratch');
@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
is(scalar @scratch_nodes, 0, '__i3_scratch is now empty');

my $ws = get_ws($tmp);
my $output = $tree->{nodes}->[1];
my $scratchrect = $ws->{floating_nodes}->[0]->{rect};
my $outputrect = $output->{rect};

is($scratchrect->{width}, $outputrect->{width} * 0.5, 'scratch width is 50%');
is($scratchrect->{height}, $outputrect->{height} * 0.75, 'scratch height is 75%');
is($scratchrect->{x},
   ($outputrect->{width} / 2) - ($scratchrect->{width} / 2),
   'scratch window centered horizontally');
is($scratchrect->{y},
   ($outputrect->{height} / 2 ) - ($scratchrect->{height} / 2),
   'scratch window centered vertically');

################################################################################
# 5: Another 'scratchpad show' should make that window go to the scratchpad
# again.
################################################################################

cmd 'scratchpad show';

$__i3_scratch = get_ws('__i3_scratch');
@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');

################################################################################
# 6: Resizing the window should disable auto centering on scratchpad show
################################################################################

cmd 'scratchpad show';

$ws = get_ws($tmp);
is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'fresh',
   'scratchpad_state fresh');

cmd 'resize grow width 10 px';
cmd 'scratchpad show';
cmd 'scratchpad show';

$ws = get_ws($tmp);
$scratchrect = $ws->{floating_nodes}->[0]->{rect};
$outputrect = $output->{rect};

is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'changed',
   'scratchpad_state changed');
is($scratchrect->{width}, $outputrect->{width} * 0.5 + 10, 'scratch width is 50% + 10px');

cmd 'resize shrink width 10 px';
cmd 'scratchpad show';

################################################################################
# 7: Verify that repeated 'scratchpad show' cycle through the stack, that is,
# toggling a visible window should insert it at the bottom of the stack of the
# __i3_scratch workspace.
################################################################################

my $third_window = open_window(name => 'scratch-match');
cmd 'move scratchpad';

$__i3_scratch = get_ws('__i3_scratch');
@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
is(scalar @scratch_nodes, 2, '__i3_scratch contains both windows');

is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'changed window first');
is($scratch_nodes[1]->{scratchpad_state}, 'fresh', 'fresh window is second');

my $changed_id = $scratch_nodes[0]->{nodes}->[0]->{id};
my $fresh_id = $scratch_nodes[1]->{nodes}->[0]->{id};
is($scratch_nodes[0]->{id}, $__i3_scratch->{focus}->[0], 'changed window first');
is($scratch_nodes[1]->{id}, $__i3_scratch->{focus}->[1], 'fresh window second');

# Repeatedly use 'scratchpad show' and check that the windows are different.
cmd 'scratchpad show';

is(get_focused($tmp), $changed_id, 'focus changed');

$ws = get_ws($tmp);
$scratchrect = $ws->{floating_nodes}->[0]->{rect};
is($scratchrect->{width}, $outputrect->{width} * 0.5, 'scratch width is 50%');
is($scratchrect->{height}, $outputrect->{height} * 0.75, 'scratch height is 75%');
is($scratchrect->{x},
   ($outputrect->{width} / 2) - ($scratchrect->{width} / 2),
   'scratch window centered horizontally');
is($scratchrect->{y},
   ($outputrect->{height} / 2 ) - ($scratchrect->{height} / 2),
   'scratch window centered vertically');

cmd 'scratchpad show';

isnt(get_focused($tmp), $changed_id, 'focus changed');

cmd 'scratchpad show';

is(get_focused($tmp), $fresh_id, 'focus changed');

cmd 'scratchpad show';

isnt(get_focused($tmp), $fresh_id, 'focus changed');

################################################################################
# 8: Show it, move it around, hide it. Verify that the position is retained
# when showing it again.
################################################################################

cmd '[title="scratch-match"] scratchpad show';

isnt(get_focused($tmp), $old_focus, 'scratchpad window shown');

my $oldrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};

cmd 'move left';

$scratchrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};
isnt($scratchrect->{x}, $oldrect->{x}, 'x position changed');
$oldrect = $scratchrect;

# hide it, then show it again
cmd '[title="scratch-match"] scratchpad show';
cmd '[title="scratch-match"] scratchpad show';

# verify the position is still the same
$scratchrect = get_ws($tmp)->{floating_nodes}->[0]->{rect};

is_deeply($scratchrect, $oldrect, 'position/size the same');

# hide it again for the next test
cmd '[title="scratch-match"] scratchpad show';

is(get_focused($tmp), $old_focus, 'scratchpad window hidden');

is(scalar @{get_ws($tmp)->{nodes}}, 1, 'precisely one window on current ws');

################################################################################
# 9: restart i3 and verify that the scratchpad show still works
################################################################################

$__i3_scratch = get_ws('__i3_scratch');
my $old_nodes = scalar @{$__i3_scratch->{nodes}};
my $old_floating_nodes = scalar @{$__i3_scratch->{floating_nodes}};

cmd 'restart';

does_i3_live;

$__i3_scratch = get_ws('__i3_scratch');
is(scalar @{$__i3_scratch->{nodes}}, $old_nodes, "number of nodes matches ($old_nodes)");
is(scalar @{$__i3_scratch->{floating_nodes}}, $old_floating_nodes, "number of floating nodes matches ($old_floating_nodes)");

is(scalar @{get_ws($tmp)->{nodes}}, 1, 'still precisely one window on current ws');
is(scalar @{get_ws($tmp)->{floating_nodes}}, 0, 'still no floating windows on current ws');

# verify that we can display the scratchpad window
cmd '[title="scratch-match"] scratchpad show';

$ws = get_ws($tmp);
is(scalar @{$ws->{nodes}}, 1, 'still precisely one window on current ws');
is(scalar @{$ws->{floating_nodes}}, 1, 'precisely one floating windows on current ws');
is($ws->{floating_nodes}->[0]->{scratchpad_state}, 'changed', 'scratchpad_state is "changed"');

################################################################################
# 10: on an empty workspace, ensure the 'move scratchpad' command does nothing
################################################################################

$tmp = fresh_workspace;

cmd 'move scratchpad';

does_i3_live;

################################################################################
# 11: focus a workspace and move all of its children to the scratchpad area
################################################################################

sub verify_scratchpad_move_multiple_win {
    my $floating = shift;

    my $first = open_window;
    my $second = open_window;

    if ($floating) {
        cmd 'floating toggle';
        cmd 'focus tiling';
    }

    cmd 'focus parent';
    cmd 'move scratchpad';

    does_i3_live;

    $ws = get_ws($tmp);
    is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
    is(scalar @{$ws->{floating_nodes}}, 0, 'no floating windows on ws');

    # show the first window.
    cmd 'scratchpad show';

    $ws = get_ws($tmp);
    is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
    is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');

    $old_focus = get_focused($tmp);

    cmd 'scratchpad show';

    # show the second window.
    cmd 'scratchpad show';

    $ws = get_ws($tmp);
    is(scalar @{$ws->{nodes}}, 0, 'no windows on ws');
    is(scalar @{$ws->{floating_nodes}}, 1, 'one floating windows on ws');

    isnt(get_focused($tmp), $old_focus, 'focus changed');
}

$tmp = fresh_workspace;
verify_scratchpad_move_multiple_win(0);
$tmp = fresh_workspace;
verify_scratchpad_move_multiple_win(1);

################################################################################
# 12: open a scratchpad window on a workspace, switch to another workspace and
# call 'scratchpad show' again
################################################################################

sub verify_scratchpad_move_with_visible_scratch_con {
    my ($first, $second, $cross_output) = @_;

    cmd "workspace $first";

    my $window1 = open_window;
    cmd 'move scratchpad';

    my $window2 = open_window;
    cmd 'move scratchpad';

    # this should bring up window 1
    cmd 'scratchpad show';

    $ws = get_ws($first);
    is(scalar @{$ws->{floating_nodes}}, 1, 'one floating node on ws1');
    is($x->input_focus, $window1->id, "showed the correct scratchpad window1");

    # this should bring up window 1
    cmd "workspace $second";
    cmd 'scratchpad show';
    is($x->input_focus, $window1->id, "showed the correct scratchpad window1");

    my $ws2 = get_ws($second);
    is(scalar @{$ws2->{floating_nodes}}, 1, 'one floating node on ws2');
    unless ($cross_output) {
        ok(!workspace_exists($first), 'ws1 was empty and therefore closed');
    } else {
        $ws = get_ws($first);
        is(scalar @{$ws->{floating_nodes}}, 0, 'ws1 has no floating nodes');
    }

    # hide window 1 again
    cmd 'move scratchpad';

    # this should bring up window 2
    cmd "workspace $first";
    cmd 'scratchpad show';
    is($x->input_focus, $window2->id, "showed the correct scratchpad window");
}

# let's clear the scratchpad first
sub clear_scratchpad {
    while (scalar @{get_ws('__i3_scratch')->{floating_nodes}}) {
        cmd 'scratchpad show';
        cmd 'kill';
    }
}

clear_scratchpad;
is (scalar @{get_ws('__i3_scratch')->{floating_nodes}}, 0, "scratchpad is empty");

my ($first, $second);
$first = fresh_workspace;
$second = fresh_workspace;

verify_scratchpad_move_with_visible_scratch_con($first, $second, 0);
does_i3_live;


################################################################################
# 13: Test whether scratchpad show moves focus to the scratchpad window
# when another window on the same workspace has focus
################################################################################

clear_scratchpad;
$ws = fresh_workspace;

open_window;
my $scratch = get_focused($ws);
cmd 'move scratchpad';
cmd 'scratchpad show';

open_window;
my $not_scratch = get_focused($ws);
is(get_focused($ws), $not_scratch, 'not scratch window has focus');

cmd 'scratchpad show';

is(get_focused($ws), $scratch, 'scratchpad is focused');

# TODO: make i3bar display *something* when a window on the scratchpad has the urgency hint

################################################################################
# 14: Verify that 'move scratchpad' sends floating containers to scratchpad but
# does not resize/resposition the container on the next 'scratchpad show', i.e.,
# i3 sets the scratchpad flag to SCRATCHPAD_CHANGED
################################################################################

clear_scratchpad;
$tmp = fresh_workspace;
open_window;

($nodes, $focus) = get_ws_content($tmp);
is(scalar @$nodes, 1, 'precisely one window on current ws');
is($nodes->[0]->{scratchpad_state}, 'none', 'scratchpad_state none');

cmd 'floating toggle';
cmd 'move scratchpad';

$__i3_scratch = get_ws('__i3_scratch');
@scratch_nodes = @{$__i3_scratch->{floating_nodes}};
is(scalar @scratch_nodes, 1, '__i3_scratch contains our window');
($nodes, $focus) = get_ws_content($tmp);
is(scalar @$nodes, 0, 'no window on current ws anymore');

is($scratch_nodes[0]->{scratchpad_state}, 'changed', 'scratchpad_state changed');

################################################################################
# 15: Verify that 'scratchpad show' returns correct info.
################################################################################

kill_all_windows;

my $result = cmd 'scratchpad show';
is($result->[0]->{success}, 0, 'no scratchpad window and call to scratchpad failed');

open_window;
cmd 'move scratchpad';
$result = cmd 'scratchpad show';
is($result->[0]->{success}, 1, 'call to scratchpad succeeded');
$result = cmd 'scratchpad show';
is($result->[0]->{success}, 1, 'call to scratchpad succeeded');

kill_all_windows;
$result = cmd 'scratchpad show';
is($result->[0]->{success}, 0, 'call to scratchpad failed');

################################################################################
# 16: Verify that 'scratchpad show' with the criteria returns correct info.
################################################################################

open_window(name => "scratch-match");
cmd 'move scratchpad';

$result = cmd '[title="scratch-match"] scratchpad show';
is($result->[0]->{success}, 1, 'call to scratchpad with the criteria succeeded');

$result = cmd '[title="nomatch"] scratchpad show';
is($result->[0]->{success}, 0, 'call to scratchpad with non-matching criteria failed');

################################################################################
# 17: Open a scratchpad window on a workspace, switch to another workspace and
# call 'scratchpad show' again. Verify that it returns correct info.
################################################################################

fresh_workspace;
open_window;
cmd 'move scratchpad';

fresh_workspace;
$result = cmd 'scratchpad show';
is($result->[0]->{success}, 1, 'call to scratchpad in another workspace succeeded');

################################################################################
# 18: Disabling floating for a scratchpad window should not work.
################################################################################

kill_all_windows;

$ws = fresh_workspace;
$window = open_window;
cmd 'move scratchpad';
cmd '[id=' . $window->id . '] floating disable';

is(scalar @{get_ws_content($ws)}, 0, 'no window in workspace');
cmd 'scratchpad show';
is($x->input_focus, $window->id, 'scratchpad window shown');

################################################################################
# 19: move position commands do not show scratchpad window
# See issue #3832
################################################################################

kill_all_windows;

fresh_workspace;
$first = open_window;
$second = open_window;

cmd '[id=' . $first->id . '] move to scratchpad, move position 100 100';
is ($x->input_focus, $second->id, 'moving scratchpad window does not show it');
cmd '[id=' . $first->id . '] move position center';
is ($x->input_focus, $second->id, 'centering scratchpad window does not show it');

###################################################################################
# Verify that a scratchpad container with child containers that was open in
# another workspace is moved to the current workspace (with all its children)
# after a scratchpad show.
################################################################################

kill_all_windows;
open_window;
open_window;
# This is to dodge the edge case were the whole workspace is moved
# window-by-window into the scratchpad.
cmd 'layout tabbed';
cmd 'focus parent';
cmd 'move to scratchpad';
$ws = fresh_workspace;
cmd 'scratchpad show';
# Case 1: a parent node in the scratchpad does not lose children
# Note on the layout: there should be a floating tabbed container, which is
# represented as follows:
#    [workspace object] -> [floating_nodes] -> [tabbed node container] -> [the 2 children we expect]
is(scalar @{get_ws($ws)->{floating_nodes}->[0]->{nodes}->[0]->{nodes}}, 2, 'both windows moved from scratchpad to this workspace');

# Case 2: a parent node in the scratchpad from another workspace does not lose children
$ws = fresh_workspace;
cmd 'scratchpad show';
is(scalar @{get_ws($ws)->{floating_nodes}->[0]->{nodes}->[0]->{nodes}}, 2, 'both windows moved from scratchpad focused on other workspace to this workspace');

done_testing;