1import pytest
2
3
4def test_default_monitor(hlwm):
5    assert hlwm.get_attr('monitors.count') == '1'
6    assert hlwm.get_attr('monitors.focus.name') == ''
7    assert hlwm.get_attr('monitors.focus.index') == '0'
8
9
10def test_add_monitor_requires_unfocused_tag(hlwm):
11    hlwm.call_xfail('add_monitor 800x600+40+40 default monitor2')
12
13    assert hlwm.get_attr('monitors.count') == '1'
14    assert hlwm.get_attr('monitors.focus.name') == ''
15    assert hlwm.get_attr('monitors.focus.index') == '0'
16
17
18def test_add_monitor(hlwm):
19    hlwm.call('add tag2')
20
21    hlwm.call('add_monitor 800x600+40+40 tag2 monitor2')
22
23    assert hlwm.get_attr('monitors.count') == '2'
24    assert hlwm.get_attr('monitors.1.name') == 'monitor2'
25
26
27def test_add_monitor_with_no_name(hlwm):
28    hlwm.call('add tag2')
29
30    hlwm.call('add_monitor 800x600+40+40 tag2')
31
32    assert hlwm.get_attr('monitors.count') == '2'
33    assert hlwm.get_attr('monitors.1.name') == ''
34
35
36def test_cannot_add_monitor_without_free_tag(hlwm):
37    call = hlwm.call_xfail('add_monitor 800x600+40+40')
38    assert call.stderr == 'add_monitor: There are not enough free tags\n'
39    assert hlwm.get_attr('monitors.count') == '1'
40
41
42def test_cannot_add_monitor_with_nonexistent_tag(hlwm):
43    call = hlwm.call_xfail('add_monitor 800x600+40+40 derp')
44    assert call.stderr == 'add_monitor: Tag "derp" does not exist\n'
45    assert hlwm.get_attr('monitors.count') == '1'
46
47
48def test_cannot_add_monitor_with_already_viewed_tag(hlwm):
49    hlwm.call('add tag2')
50    call = hlwm.call_xfail('add_monitor 800x600+40+40 default')
51    assert call.stderr == 'add_monitor: Tag "default" is already being viewed on a monitor\n'
52    assert hlwm.get_attr('monitors.count') == '1'
53
54
55def test_cannot_add_monitor_with_numeric_name(hlwm):
56    hlwm.call('add tag2')
57    call = hlwm.call_xfail('add_monitor 800x600+40+40 tag2 123foo')
58    assert call.stderr == 'add_monitor: Invalid name "123foo": The monitor name may not start with a number\n'
59    assert hlwm.get_attr('monitors.count') == '1'
60
61
62def test_cannot_add_monitor_with_empty_name(hlwm):
63    hlwm.call('add tag2')
64    call = hlwm.call_xfail('add_monitor 800x600+40+40 tag2 ""')
65    assert call.stderr == 'add_monitor: An empty monitor name is not permitted\n'
66    assert hlwm.get_attr('monitors.count') == '1'
67
68
69def test_cannot_add_monitor_with_existing_name(hlwm):
70    hlwm.call('rename_monitor 0 mon1')
71    hlwm.call('add tag2')
72    call = hlwm.call_xfail('add_monitor 800x600+40+40 tag2 mon1')
73    assert call.stderr == 'add_monitor: A monitor with the name "mon1" already exists\n'
74    assert hlwm.get_attr('monitors.count') == '1'
75
76
77def test_remove_monitor(hlwm):
78    hlwm.call('add tag2')
79    hlwm.call('add_monitor 800x600+40+40 tag2 monitor2')
80
81    hlwm.call('remove_monitor 0')
82
83    assert hlwm.get_attr('monitors.0.index') == '0'
84    assert hlwm.get_attr('monitors.count') == '1'
85    assert hlwm.get_attr('monitors.focus.name') == 'monitor2'
86
87
88def test_cannot_remove_nonexistent_monitor(hlwm):
89    call = hlwm.call_xfail('remove_monitor 1')
90    assert call.stderr == 'remove_monitor: Monitor "1" not found!\n'
91    assert hlwm.get_attr('monitors.count') == '1'
92
93
94def test_cannot_remove_last_monitor(hlwm):
95    call = hlwm.call_xfail('remove_monitor 0')
96    assert call.stderr == 'remove_monitor: Can\'t remove the last monitor\n'
97    assert hlwm.get_attr('monitors.count') == '1'
98
99
100def test_move_monitor(hlwm):
101    r = [8, 4, 400, 300]  # x,y,width,height
102    hlwm.call('move_monitor \"0\" %dx%d%+d%+d' % (r[2], r[3], r[0], r[1]))
103    assert hlwm.call('monitor_rect \"\"').stdout == ' '.join(map(str, r))
104
105
106def test_focus_monitor(hlwm):
107    hlwm.call('add tag2')
108    hlwm.call('add_monitor 800x600+40+40')
109    assert hlwm.get_attr('monitors.focus.index') == '0'
110    assert hlwm.get_attr('tags.focus.index') == '0'
111
112    hlwm.call('focus_monitor 1')
113
114    assert hlwm.get_attr('monitors.focus.index') == '1'
115    assert hlwm.get_attr('tags.focus.index') == '1'
116
117
118def test_new_clients_appear_in_focused_monitor(hlwm):
119    hlwm.call('add tag2')
120    hlwm.call('add_monitor 800x600+40+40 tag2 monitor2')
121    hlwm.call('focus_monitor monitor2')
122
123    winid, _ = hlwm.create_client()
124
125    assert hlwm.get_attr('tags.by-name.tag2.client_count') == '1'
126    assert hlwm.get_attr('tags.by-name.default.client_count') == '0'
127    assert hlwm.get_attr('clients', winid, 'tag') == 'tag2'
128
129
130@pytest.mark.parametrize("arg", ['-l', '--list-all', '--no-disjoin'])
131def test_detect_monitors_does_not_crash(hlwm, arg):
132    # I don't know how to test detect_monitors properly, so just check that
133    # it does not crash at least
134    hlwm.call(['detect_monitors', arg])
135
136
137def test_detect_monitors_affects_list_monitors(hlwm):
138    list_monitors_before = hlwm.call('list_monitors').stdout
139    hlwm.call('add othertag')
140    hlwm.call('set_monitors 80x80+5+5 80x80+85+5')
141    assert hlwm.get_attr('monitors.count') == '2'
142    assert list_monitors_before != hlwm.call('list_monitors').stdout
143
144    # check that detect monitors restores the one monitor
145    hlwm.call('detect_monitors')
146
147    assert hlwm.get_attr('monitors.count') == '1'
148    assert list_monitors_before == hlwm.call('list_monitors').stdout
149
150
151def test_rename_monitor(hlwm):
152    hlwm.call('rename_monitor 0 foo')
153
154    assert hlwm.get_attr('monitors.focus.name') == 'foo'
155    assert hlwm.list_children('monitors.by-name') == ['foo']
156
157
158def test_rename_monitor_no_name(hlwm):
159    hlwm.call('rename_monitor 0 foo')
160
161    hlwm.call('rename_monitor foo ""')
162
163    assert hlwm.get_attr('monitors.focus.name') == ''
164    assert hlwm.list_children('monitors.by-name') == []
165
166
167@pytest.mark.parametrize("tag_count", [1, 2, 3, 4])
168@pytest.mark.parametrize("monitor_count", [1, 2, 3, 4])
169def test_set_monitors_create_monitors(hlwm, tag_count, monitor_count):
170    for i in range(1, tag_count):
171        hlwm.call('add tag{}'.format(i))
172    rects = ['100x200+{}+0'.format(100 * i) for i in range(0, monitor_count)]
173
174    if tag_count < monitor_count:
175        hlwm.call_xfail(['set_monitors'] + rects) \
176            .expect_stderr('There are not enough free tags')
177    else:
178        hlwm.call(['set_monitors'] + rects)
179
180        assert int(hlwm.get_attr('monitors.count')) == monitor_count
181        monitors = hlwm.call('list_monitors').stdout.splitlines()
182        assert rects == [s.split(' ')[1] for s in monitors]
183
184
185@pytest.mark.parametrize("monitor_count_before", [1, 2, 3])
186def test_set_monitors_removes_monitors(hlwm, monitor_count_before):
187    for i in range(1, monitor_count_before):
188        hlwm.call('add tag{}'.format(i))
189        hlwm.call('add_monitor 100x200+{}+0'.format(100 * i))
190    # focus the last monitor
191    hlwm.call('focus_monitor {}'.format(monitor_count_before - 1))
192
193    hlwm.call(['set_monitors', '100x200+100+0'])
194
195    assert hlwm.get_attr('monitors.count') == '1'
196    monitors = hlwm.call('list_monitors').stdout
197    assert monitors == '0: 100x200+100+0 with tag "default" [FOCUS]\n'
198
199
200def test_raise_monitor_completion(hlwm):
201    hlwm.call('add tag2')
202    hlwm.call('add_monitor 800x600+40+40 tag2 monitor2')
203
204    expected = ['']
205    expected += '-1 +0 +1 0 1 monitor2'.split(' ')
206    expected.sort()
207    assert hlwm.complete('raise_monitor') == expected
208
209
210def test_use_previous_on_tag_stealing_monitor(hlwm):
211    hlwm.call('add tag2')
212    hlwm.call('add tag3')
213    hlwm.call('add_monitor 800x600+40+40 tag3 monitor2')
214
215    hlwm.call('use tag2')
216    hlwm.call('use tag3')  # steal it from monitor2
217    hlwm.call('use_previous')
218
219    assert hlwm.get_attr('tags.focus.name') == 'tag2'
220
221
222def test_use_previous_on_stolen_monitor(hlwm):
223    hlwm.call('add tag2')
224    hlwm.call('add tag3')
225    hlwm.call('add tag4')
226    hlwm.call('add_monitor 800x600+40+40 tag4 monitor2')
227    hlwm.call('focus_monitor monitor2')
228    hlwm.call('use tag3')
229    hlwm.call('focus_monitor 0')
230
231    hlwm.call('use tag2')
232    hlwm.call('use tag3')  # steal it from monitor2
233    hlwm.call('focus_monitor monitor2')
234    hlwm.call('use_previous')
235
236    assert hlwm.get_attr('tags.focus.name') == 'tag3'
237
238
239@pytest.mark.parametrize("two_monitors", [True, False])
240def test_initial_client_position(hlwm, x11, two_monitors):
241    # create two monitors side by side (with a little y-offset)
242    if two_monitors:
243        hlwm.call('add other')
244        hlwm.call('set_monitors 173x174+4+5 199x198+200+100')
245        hlwm.call('focus_monitor 1')
246    hlwm.call('set_attr theme.border_width 0')  # disable border
247    # add pad to the focused monitor and set its tag to floating
248    hlwm.call('pad +0 12 13 14 15')
249    hlwm.call('floating on')
250
251    # create a new window in the area of the second monitor.
252    g = (250, 160, 81, 82)  # x, y, width, height
253    w, winid = x11.create_client(geometry=g)
254    assert int(hlwm.get_attr('tags.focus.client_count')) == 1
255
256    # check that the new client has the desired geometry
257    win_geo = w.get_geometry()
258    assert (win_geo.width, win_geo.height) == (g[2], g[3])
259    x, y = x11.get_absolute_top_left(w)
260    assert (x, y) == (g[0], g[1])
261
262
263def test_shift_to_monitor(hlwm):
264    hlwm.call('add tag2')
265    hlwm.call('set_monitors 80x80+0+0 80x80+80+0')
266    winid, _ = hlwm.create_client()
267    oldtag = hlwm.get_attr('monitors.0.tag')
268    assert hlwm.get_attr(f'clients.{winid}.tag') == oldtag
269
270    hlwm.call('shift_to_monitor 1')
271
272    newtag = hlwm.get_attr('monitors.1.tag')
273    assert oldtag != newtag
274    assert hlwm.get_attr(f'clients.{winid}.tag') == newtag
275
276
277def test_shift_to_monitor_invalid_mon(hlwm):
278    winid, _ = hlwm.create_client()
279    hlwm.call_xfail('shift_to_monitor 34') \
280        .expect_stderr('Invalid monitor')
281
282
283def test_shift_to_monitor_no_client(hlwm):
284    hlwm.call('add tag2')
285    hlwm.call('set_monitors 80x80+0+0 80x80+80+0')
286
287    # there is no error message at the moment, so we only
288    # check that it does not crash
289    hlwm.call('shift_to_monitor 1')
290
291
292def test_invalid_monitor_name(hlwm):
293    cmds = [
294        'list_padding', 'move_monitor',
295        'rename_monitor', 'lock_tag', 'unlock_tag'
296    ]
297    for command in cmds:
298        hlwm.call_xfail([command, 'thismonitordoesnotexist']) \
299            .expect_stderr('Monitor "thismonitordoesnotexist" not found')
300
301
302def test_list_padding(hlwm):
303    hlwm.call('add othertag')
304    hlwm.call('add_monitor 800x600+600+0')
305    pad0 = '5 20 3 30'
306    pad1 = '1 2 4 8'
307    hlwm.call('pad 0 ' + pad0)
308    hlwm.call('pad 1 ' + pad1)
309
310    # this is a very primitive command, so we directly test multiple things at once
311    assert hlwm.call('list_padding 0').stdout == pad0 + '\n'
312    assert hlwm.call('list_padding 1').stdout == pad1 + '\n'
313
314    assert hlwm.call('list_padding').stdout == pad0 + '\n'
315    hlwm.call('focus_monitor 1')
316    assert hlwm.call('list_padding').stdout == pad1 + '\n'
317
318
319def test_list_padding_invalid_monitor(hlwm):
320    hlwm.call_xfail('list_padding 23') \
321        .expect_stderr('Monitor.*not found')
322
323
324@pytest.mark.parametrize("mon_num,focus_idx", [
325    (num, focus) for num in [1, 2, 3, 4, 5] for focus in [0, num - 1]])
326@pytest.mark.parametrize("delta", ['-1', '+1'])
327@pytest.mark.parametrize("command", ['cycle_monitor', 'focus_monitor'])
328def test_cycle_monitor(hlwm, mon_num, focus_idx, delta, command):
329    """the present test also tests the MOD() function in utility.cpp"""
330    for i in range(1, mon_num):
331        hlwm.call('add tag' + str(i))
332        hlwm.call('add_monitor 800x600+' + str(i * 10))
333    hlwm.call(['focus_monitor', str(focus_idx)])
334    assert hlwm.get_attr('monitors.focus.index') == str(focus_idx)
335    assert hlwm.get_attr('monitors.count') == str(mon_num)
336
337    hlwm.call([command, delta])
338
339    new_index = (focus_idx + int(delta) + mon_num) % mon_num
340    assert hlwm.get_attr('monitors.focus.index') == str(new_index)
341
342
343@pytest.mark.parametrize("lock_tag_cmd", [
344    lambda index: ['lock_tag', str(index)],
345    lambda index: ['set_attr', f'monitors.{index}.lock_tag', 'on']
346])
347def test_lock_tag_switch_away(hlwm, lock_tag_cmd):
348    hlwm.call('add tag1')
349    hlwm.call('add tag2')
350    hlwm.call('set_monitors 800x600+0+0 800x600+800+0')
351    hlwm.call(lock_tag_cmd(0))
352    assert hlwm.attr.monitors[0].lock_tag() == hlwm.bool(True)
353
354    hlwm.call('focus_monitor 0')
355
356    # can not switch to another tag on the locked monitor
357    hlwm.call_xfail('use tag2') \
358        .expect_stderr('Could not change .*monitor 0 is locked')
359
360
361@pytest.mark.parametrize("locked", [True, False])
362def test_lock_tag_switch_to_locked(hlwm, locked):
363    hlwm.call('add tag1')
364    hlwm.call('add tag2')
365    hlwm.call('set_monitors 800x600+0+0 800x600+800+0')
366    hlwm.call('set swap_monitors_to_get_tag on')
367
368    hlwm.attr.monitors[0].lock_tag = hlwm.bool(locked)
369    hlwm.call('focus_monitor 1')
370
371    # being on monitor 1, try to focus the tag on monitor 0
372    tag_on_locked_monitor = hlwm.attr.monitors[0].tag()
373    hlwm.call(['use', tag_on_locked_monitor])
374
375    if locked:
376        # if the monitor was locked, then the tag stays
377        # on monitor 0
378        expected_monitor_index = '0'
379    else:
380        # otherwise, the tag is moved to monitor 1 because
381        # of swap_monitors_to_get_tag
382        expected_monitor_index = '1'
383    assert hlwm.attr.tags.focus.name() == tag_on_locked_monitor
384    assert hlwm.attr.monitors.focus.index() == expected_monitor_index
385
386
387def test_lock_tag_command_vs_attribute(hlwm):
388    hlwm.call('add anothertag')
389    hlwm.call('set_monitors 800x600+0+0 800x600+800+0')
390    hlwm.call('focus_monitor 1')
391
392    # no argument modifies the attribute of the focused monitor
393    hlwm.call('lock_tag')
394    assert hlwm.attr.monitors[1].lock_tag() == hlwm.bool(True)
395    hlwm.call('unlock_tag')
396    assert hlwm.attr.monitors[1].lock_tag() == hlwm.bool(False)
397
398    hlwm.call('lock_tag 0')
399    assert hlwm.attr.monitors[0].lock_tag() == hlwm.bool(True)
400    hlwm.call('unlock_tag 0')
401    assert hlwm.attr.monitors[0].lock_tag() == hlwm.bool(False)
402
403
404def test_monitor_rect_too_small(hlwm):
405    hlwm.call_xfail('attr monitors.0.geometry 20x300+0+0') \
406        .expect_stderr('too small.*wide')
407    hlwm.call_xfail('attr monitors.0.geometry 400x30+0+0') \
408        .expect_stderr('too small.*high')
409
410
411def test_monitor_rect_is_updated(hlwm):
412    for rect in ['500x300+30+40', '500x300-30+40', '500x300+30-40', '500x300-30-40']:
413        hlwm.call(['move_monitor', 0, rect])
414        assert hlwm.attr.monitors[0].geometry() == rect
415
416
417def test_monitor_rect_parser(hlwm):
418    for rect in [(400, 800, a * 40, b * 60) for a in [1, -1] for b in [1, -1]]:
419
420        hlwm.attr.monitors[0].geometry = '%dx%d%+d%+d' % rect
421
422        expected_output = ' '.join([str(rect[i]) for i in [2, 3, 0, 1]])
423        assert hlwm.call('monitor_rect 0').stdout.strip() == expected_output
424
425
426def test_monitor_rect_apply_layout(hlwm, x11):
427    winhandle, winid = x11.create_client()
428    hlwm.attr.clients[winid].fullscreen = 'on'
429    for expected_geometry in [(600, 700, 40, 50), (400, 800, -10, 0)]:
430        hlwm.attr.monitors[0].geometry = '%dx%d%+d%+d' % expected_geometry
431
432        x11.display.sync()
433        geom = x11.get_absolute_geometry(winhandle)
434        assert (geom.width, geom.height, geom.x, geom.y) == expected_geometry
435