1
# This file is part of Parti.
2
# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
3
# Parti is released under the terms of the GNU GPL v2, or, at your option, any
4
# later version. See the file COPYING for details.
6
from wimpiggy.test import *
7
import wimpiggy.lowlevel as l
9
from wimpiggy.error import *
11
class TestLowlevel(TestWithSession):
12
def root(self, disp=None):
15
return disp.get_default_screen().get_root_window()
17
def window(self, disp=None):
20
win = gtk.gdk.Window(self.root(disp), width=10, height=10,
21
# WINDOW_CHILD is sort of bogus, but it reduces
22
# the amount of magic that GDK will do (e.g.,
23
# WINDOW_TOPLEVELs automatically get a child
24
# window to be used in focus management).
25
window_type=gtk.gdk.WINDOW_CHILD,
26
wclass=gtk.gdk.INPUT_OUTPUT,
30
class TestLowlevelMisc(TestLowlevel):
31
def test_get_xwindow_pywindow(self):
32
d2 = self.clone_display()
36
assert l.get_xwindow(r1) == l.get_xwindow(r2)
38
assert l.get_xwindow(r1) != l.get_xwindow(win)
39
assert l.get_pywindow(r2, l.get_xwindow(r1)) is r2
41
assert_raises(l.XError, l.get_pywindow, self.display, 0)
43
# This is necessary to stop some mysterious failure (perhaps d2 being
44
# garbage collected before r2):
47
def test_get_display_for(self):
48
assert l.get_display_for(self.display) is self.display
50
assert l.get_display_for(win) is self.display
51
assert_raises(TypeError, l.get_display_for, None)
53
assert l.get_display_for(widg) is self.display
54
clipboard = gtk.Clipboard(self.display, "PRIMARY")
55
assert l.get_display_for(clipboard) is self.display
57
def test_get_xatom_pyatom(self):
58
d2 = self.clone_display()
59
asdf1 = l.get_xatom(self.display, "ASDF")
60
asdf2 = l.get_xatom(d2, "ASDF")
61
ghjk1 = l.get_xatom(self.display, "GHJK")
62
ghjk2 = l.get_xatom(d2, "GHJK")
65
assert l.get_pyatom(self.display, asdf2) == "ASDF"
66
assert l.get_pyatom(d2, ghjk1) == "GHJK"
68
def test_property(self):
70
data = "\x01\x02\x03\x04\x05\x06\x07\x08"
71
assert_raises(l.NoSuchProperty,
72
l.XGetWindowProperty, r, "ASDF", "ASDF")
73
l.XChangeProperty(r, "ASDF", ("GHJK", 32, data))
74
assert_raises(l.BadPropertyType,
75
l.XGetWindowProperty, r, "ASDF", "ASDF")
79
l.XChangeProperty(r, "ASDF", ("GHJK", n, data))
80
assert l.XGetWindowProperty(r, "ASDF", "GHJK") == data
82
l.XDeleteProperty(r, "ASDF")
83
assert_raises(l.NoSuchProperty,
84
l.XGetWindowProperty, r, "ASDF", "GHJK")
86
badwin = self.window()
88
assert_raises((l.PropertyError, XError),
89
trap.call, l.XGetWindowProperty, badwin, "ASDF", "ASDF")
91
# Giant massive property
92
l.XChangeProperty(r, "ASDF",
93
("GHJK", 32, "\x00" * 512 * (2 ** 10)))
94
assert_raises(l.PropertyOverflow,
95
l.XGetWindowProperty, r, "ASDF", "GHJK")
97
def test_BadProperty_on_empty(self):
99
l.XChangeProperty(win, "ASDF", ("GHJK", 32, ""))
100
assert l.XGetWindowProperty(win, "ASDF", "GHJK") == ""
101
assert_raises(l.BadPropertyType,
102
l.XGetWindowProperty, win, "ASDF", "ASDF")
104
def test_get_children_and_get_parent_and_reparent(self):
105
d2 = self.clone_display()
106
w1 = self.window(self.display)
110
assert not l.get_children(w1)
111
children = l.get_children(self.root())
112
xchildren = map(l.get_xwindow, children)
113
xwins = map(l.get_xwindow, [w1, w2])
114
# GDK creates an invisible child of the root window on each
115
# connection, so there are some windows we don't know about:
117
assert known in xchildren
118
assert l.get_parent(w1) == w1.get_parent()
120
w1.reparent(l.get_pywindow(w1, l.get_xwindow(w2)), 0, 0)
122
assert map(l.get_xwindow, l.get_children(w2)) == [l.get_xwindow(w1)]
123
assert l.get_parent(w1).xid == w2.xid
125
def test_get_parent_of_root(self):
126
assert l.get_parent(self.root()) is None
128
def test_save_set(self):
129
w1 = self.window(self.display)
130
w2 = self.window(self.display)
131
w3 = self.window(self.display)
135
def do_child(disp_name, xwindow1, xwindow2, xwindow3):
136
print "child: in do_child"
137
d2 = gtk.gdk.Display(disp_name)
138
w1on2 = l.get_pywindow(d2, xwindow1)
139
w2on2 = l.get_pywindow(d2, xwindow2)
140
w3on2 = l.get_pywindow(d2, xwindow3)
141
mywin = self.window(d2)
142
print "child: mywin == %s" % l.get_xwindow(mywin)
143
w1on2.reparent(mywin, 0, 0)
144
w2on2.reparent(mywin, 0, 0)
145
w3on2.reparent(mywin, 0, 0)
148
l.XAddToSaveSet(w1on2)
150
# w3 is saved, but then unsaved (to test RemoveFromSaveSet):
151
l.XAddToSaveSet(w3on2)
152
l.XRemoveFromSaveSet(w3on2)
154
print "child: finished"
155
print "prefork: ", os.getpid()
160
print "child: pid ", os.getpid()
161
name = self.display.get_name()
162
# This is very important, though I don't know why. If we
163
# don't close this display then something inside
164
# xcb_wait_for_reply gets Very Confused and the *parent*
165
# process gets a spurious IO error with nonsense errno
166
# (because errno is not actually being set, because there is
167
# no IO error, just something going weird inside xcb). I'm
168
# not even sure that this actually fixes the underlying
169
# problem, but it makes the test pass, so...
178
print "parent: ", os.getpid()
179
print "parent: child is ", pid
180
print "parent: waiting for child"
182
print "parent: child exited"
183
# Is there a race condition here, where the child exits but the X
184
# server doesn't notice until after we send our commands?
185
print map(l.get_xwindow, [w1, w2, w3])
186
print map(l.get_xwindow, l.get_children(self.root()))
187
assert w1 in l.get_children(self.root())
188
assert w2 not in l.get_children(self.root())
189
assert w3 not in l.get_children(self.root())
191
def test_is_mapped(self):
194
assert not l.is_mapped(win)
197
assert l.is_mapped(win)
199
def test_show_unraised_without_extra_stupid_stuff(self):
202
assert not l.is_mapped(win)
203
l.show_unraised_without_extra_stupid_stuff(win)
205
assert l.is_mapped(win)
207
def test_is_override_redirect(self):
210
assert not l.is_override_redirect(win)
211
win.set_override_redirect(True)
213
assert l.is_override_redirect(win)
215
class _EventRoutingReceiver(MockEventReceiver):
216
def __init__(self, tag, store_in):
217
MockEventReceiver.__init__(self)
219
self.store_in = store_in
220
def do_wimpiggy_map_event(self, event):
221
print "map_event in %s" % self.tag
222
self.store_in.add(("map", self.tag))
224
def do_wimpiggy_child_map_event(self, event):
225
print "child_map_event in %s" % self.tag
226
self.store_in.add(("child-map", self.tag))
229
class TestEventRouting(TestLowlevel):
230
def test_event_routing(self):
233
w2.reparent(w1, 0, 0)
235
r1 = _EventRoutingReceiver(1, results)
236
r2 = _EventRoutingReceiver(2, results)
237
r3 = _EventRoutingReceiver(3, results)
238
r4 = _EventRoutingReceiver(4, results)
239
l.add_event_receiver(w1, r1)
240
l.add_event_receiver(w1, r2)
241
l.add_event_receiver(w2, r3)
242
l.add_event_receiver(w2, r4)
244
w1.set_events(gtk.gdk.SUBSTRUCTURE_MASK)
245
w2.set_events(gtk.gdk.STRUCTURE_MASK)
247
while len(results) != 4:
250
assert results == set([("child-map", 1), ("child-map", 2),
251
("map", 3), ("map", 4)])
252
l.remove_event_receiver(w1, r2)
253
l.remove_event_receiver(w2, r4)
256
while len(results) != 2:
258
assert results == set([("child-map", 1), ("map", 3)])
260
class TestUnmapWithSerial(TestLowlevel, MockEventReceiver):
261
def do_wimpiggy_map_event(self, event):
264
def do_wimpiggy_unmap_event(self, event):
269
def test_unmap_with_serial(self):
273
l.add_event_receiver(w, self)
274
serial = l.unmap_with_serial(w)
277
assert self._event is not None
278
assert self._event.serial == serial
280
class TestFocusStuff(TestLowlevel, MockEventReceiver):
281
def do_wimpiggy_focus_in_event(self, event):
282
if event.window is self.w1:
283
assert self.w1_got is None
286
assert self.w2_got is None
289
def do_wimpiggy_focus_out_event(self, event):
290
if event.window is self.w1:
291
assert self.w1_lost is None
294
assert self.w2_lost is None
297
def test_focus_stuff(self):
298
self.w1 = self.window()
300
self.w2 = self.window()
303
self.w1_got, self.w2_got = None, None
304
self.w1_lost, self.w2_lost = None, None
305
l.selectFocusChange(self.w1)
306
l.selectFocusChange(self.w2)
307
l.add_event_receiver(self.w1, self)
308
l.add_event_receiver(self.w2, self)
311
l.XSetInputFocus(self.w1)
314
assert self.w1_got is not None
315
assert self.w1_got.window is self.w1
316
assert self.w1_got.mode == l.const["NotifyNormal"]
317
assert self.w1_got.detail == l.const["NotifyNonlinear"]
319
assert self.w2_got is None
320
assert self.w1_lost is None
321
assert self.w2_lost is None
323
l.XSetInputFocus(self.w2)
327
assert self.w1_got is None
328
assert self.w2_got is not None
329
assert self.w2_got.window is self.w2
330
assert self.w2_got.mode == l.const["NotifyNormal"]
331
assert self.w2_got.detail == l.const["NotifyNonlinear"]
333
assert self.w1_lost is not None
334
assert self.w1_lost.window is self.w1
335
assert self.w1_lost.mode == l.const["NotifyNormal"]
336
assert self.w1_lost.detail == l.const["NotifyNonlinear"]
338
assert self.w2_lost is None
340
l.XSetInputFocus(self.root())
343
assert self.w1_got is None
344
assert self.w2_got is None
345
assert self.w1_lost is None
346
assert self.w2_lost is not None
347
assert self.w2_lost.window is self.w2
348
assert self.w2_lost.mode == l.const["NotifyNormal"]
349
assert self.w2_lost.detail == l.const["NotifyAncestor"]
352
class TestClientMessageAndXSelectInputStuff(TestLowlevel, MockEventReceiver):
353
def do_wimpiggy_client_message_event(self, event):
354
print "got clientmessage"
355
self.evs.append(event)
358
def test_select_clientmessage_and_xselectinput(self):
360
self.w = self.window()
363
l.add_event_receiver(self.w, self)
364
l.add_event_receiver(self.root(), self)
366
data = (0x01020304, 0x05060708, 0x090a0b0c, 0x0d0e0f10, 0x11121314)
367
l.sendClientMessage(self.root(), False, 0, "NOMASK", *data)
368
l.sendClientMessage(self.w, False, 0, "NOMASK", *data)
370
# Should have gotten message to w, not to root
371
assert len(self.evs) == 1
373
assert ev.window is self.w
374
assert ev.message_type == "NOMASK"
375
assert ev.format == 32
376
assert ev.data == data
379
l.sendClientMessage(self.root(), False, l.const["Button1MotionMask"],
381
l.addXSelectInput(self.root(), l.const["Button1MotionMask"])
382
l.sendClientMessage(self.root(), False, l.const["Button1MotionMask"],
385
assert len(self.evs) == 1
387
assert ev.window is self.root()
388
assert ev.message_type == "GOOD"
389
assert ev.format == 32
390
assert ev.data == data
392
def test_send_wm_take_focus(self):
395
l.add_event_receiver(win, self)
398
l.send_wm_take_focus(win, 1234)
400
assert len(self.evs) == 1
402
assert event is not None
403
assert event.window is win
404
assert event.message_type == "WM_PROTOCOLS"
405
assert event.format == 32
406
assert event.data == (l.get_xatom(win, "WM_TAKE_FOCUS"),
409
# myGetSelectionOwner gets tested in test_selection.py
411
class TestSendConfigureNotify(TestLowlevel):
412
# This test stomps on Wimpiggy's global event handler, so make sure not to
413
# try receiving wimpiggy events in it.
414
def test_sendConfigureNotify(self):
415
# GDK discards ConfigureNotify's sent to child windows, so we can't
417
w1 = gtk.gdk.Window(self.root(), width=10, height=10,
418
window_type=gtk.gdk.WINDOW_TOPLEVEL,
419
wclass=gtk.gdk.INPUT_OUTPUT,
420
event_mask=gtk.gdk.ALL_EVENTS_MASK)
422
def myfilter(ev, data=None):
423
print "ev %s" % (ev.type,)
424
if ev.type == gtk.gdk.CONFIGURE:
427
gtk.main_do_event(ev)
428
gtk.gdk.event_handler_set(myfilter)
432
l.sendConfigureNotify(w1)
435
assert self.ev is not None
436
assert self.ev.type == gtk.gdk.CONFIGURE
437
assert self.ev.window == w1
438
assert self.ev.send_event
439
assert self.ev.x == 0
440
assert self.ev.y == 0
441
assert self.ev.width == 10
442
assert self.ev.height == 10
444
# We have to create w2 on a separate connection, because if we just
445
# did w1.reparent(w2, ...), then GDK would magically convert w1 from a
446
# TOPLEVEL window into a CHILD window.
447
# Have to hold onto a reference to d2, so it doesn't get garbage
448
# collected and kill the connection:
449
d2 = self.clone_display()
452
w2on1 = l.get_pywindow(w1, l.get_xwindow(w2))
453
# Doesn't generate an event, because event mask is zeroed out.
455
# Reparenting doesn't trigger a ConfigureNotify.
456
w1.reparent(w2on1, 13, 14)
457
# To double-check that it's still a TOPLEVEL:
458
print w1.get_window_type()
462
# w1 in root coordinates is now at (24, 26)
464
l.sendConfigureNotify(w1)
467
assert self.ev is not None
468
assert self.ev.type == gtk.gdk.CONFIGURE
469
assert self.ev.window == w1
470
assert self.ev.send_event
471
assert self.ev.x == 24
472
assert self.ev.y == 26
473
assert self.ev.width == 15
474
assert self.ev.height == 16
476
class TestSubstructureRedirect(TestLowlevel, MockEventReceiver):
477
def do_child_map_request_event(self, event):
478
print "do_child_map_request_event"
481
def do_child_configure_request_event(self, event):
482
print "do_child_configure_request_event"
486
def test_substructure_redirect(self):
490
d2 = self.clone_display()
493
w1 = l.get_pywindow(self.display, l.get_xwindow(w2))
495
l.add_event_receiver(root, self)
496
l.substructureRedirect(root)
499
# gdk_window_show does both a map and a configure (to raise the
503
# Can't just call gtk.main() twice, the two events may be delivered
504
# together and processed in a single mainloop iteration.
505
while None in (self.map_ev, self.conf_ev):
507
assert self.map_ev.delivered_to is root
508
assert self.map_ev.window is w1
510
assert self.conf_ev.delivered_to is root
511
assert self.conf_ev.window is w1
512
for field in ("x", "y", "width", "height",
513
"border_width", "above", "detail", "value_mask"):
515
assert hasattr(self.conf_ev, field)
520
w2.move_resize(1, 2, 3, 4)
522
assert self.map_ev is None
523
assert self.conf_ev is not None
524
assert self.conf_ev.delivered_to is root
525
assert self.conf_ev.window is w1
526
assert self.conf_ev.x == 1
527
assert self.conf_ev.y == 2
528
assert self.conf_ev.width == 3
529
assert self.conf_ev.height == 4
530
assert self.conf_ev.value_mask == (l.const["CWX"]
533
| l.const["CWHeight"])
539
assert self.map_ev is None
540
assert self.conf_ev.x == 5
541
assert self.conf_ev.y == 6
542
assert self.conf_ev.value_mask == (l.const["CWX"] | l.const["CWY"])
548
assert self.map_ev is None
549
assert self.conf_ev.detail == l.const["Above"]
550
assert self.conf_ev.value_mask == l.const["CWStackMode"]
552
def test_configureAndNotify(self):
554
l.substructureRedirect(self.root())
555
l.add_event_receiver(self.root(), self)
556
# Need to hold onto a handle to this, so connection doesn't get
558
client = self.clone_display()
559
w1_client = self.window(client)
561
w1_wm = l.get_pywindow(self.display, l.get_xwindow(w1_client))
563
l.configureAndNotify(w1_client, 11, 12, 13, 14)
566
assert self.conf_ev is not None
567
assert self.conf_ev.delivered_to is self.root()
568
assert self.conf_ev.window is w1_wm
569
assert self.conf_ev.x == 11
570
assert self.conf_ev.y == 12
571
assert self.conf_ev.width == 13
572
assert self.conf_ev.height == 14
573
assert self.conf_ev.border_width == 0
574
assert self.conf_ev.value_mask == (l.const["CWX"]
577
| l.const["CWHeight"]
578
| l.const["CWBorderWidth"])
580
partial_mask = l.const["CWWidth"] | l.const["CWStackMode"]
581
l.configureAndNotify(w1_client, 11, 12, 13, 14, partial_mask)
584
assert self.conf_ev is not None
585
assert self.conf_ev.delivered_to is self.root()
586
assert self.conf_ev.window is w1_wm
587
assert self.conf_ev.width == 13
588
assert self.conf_ev.border_width == 0
589
assert self.conf_ev.value_mask == (l.const["CWWidth"]
590
| l.const["CWBorderWidth"])
594
class TestGeometryConstraints(object):
595
# This doesn't actually need a session to play with...
596
def test_calc_constrained_size(self):
599
return repr(self.__dict__)
602
for k in ("max_size", "min_size", "base_size", "resize_inc",
603
"min_aspect", "max_aspect"):
605
for k, v in args.iteritems():
608
def t(w, h, hints, exp_w, exp_h, exp_vw, exp_vh):
609
got = l.calc_constrained_size(w, h, hints)
611
assert got == (exp_w, exp_h, exp_vw, exp_vh)
612
t(150, 100, None, 150, 100, 150, 100)
613
t(150, 100, hints(), 150, 100, 150, 100)
614
t(150, 100, hints(max_size=(90, 150)), 90, 100, 90, 100)
615
t(150, 100, hints(max_size=(200, 90)), 150, 90, 150, 90)
616
t(150, 100, hints(min_size=(90, 150)), 150, 150, 150, 150)
617
t(150, 100, hints(min_size=(200, 90)), 200, 100, 200, 100)
618
t(150, 100, hints(min_size=(182, 17), max_size=(182, 17)),
621
t(150, 100, hints(base_size=(3, 4), resize_inc=(10, 10)),
624
t(150, 100, hints(base_size=(3, 4), resize_inc=(10, 10),
625
max_size=(100, 150), min_size=(0, 140)),
627
except AssertionError:
628
print ("Assertion Failed! But *cough* *cough* actually gdk "
629
+ "(and apparently every wm ever) has a bug here. "
630
+ "and it's trivial and I'm ignoring it for now. "
631
+ "(see http://bugzilla.gnome.org/show_bug.cgi?id=492961)")
633
raise AssertionError, "Dude look at this, gtk+ fixed bug#492961"
634
# FIXME: this is wrong (see above), but it is what it actually
635
# returns, and is not so bad as all that:
636
t(150, 100, hints(base_size=(3, 4), resize_inc=(10, 10),
637
max_size=(100, 150), min_size=(0, 140)),
640
# Behavior in this case is basically undefined, so *shrug*:
641
t(150, 100, hints(base_size=(3, 4), resize_inc=(10, 10),
642
max_size=(100, 100), min_size=(100, 100)),
645
t(150, 100, hints(min_aspect=1, max_aspect=1), 100, 100, 100, 100)
646
t(100, 150, hints(min_aspect=1, max_aspect=1), 100, 100, 100, 100)
648
t(100, 150, hints(min_aspect=1, max_aspect=1,
649
base_size=(3, 3), resize_inc=(10, 10)),
652
# Also undefined, but (93, 94) is good enough:
653
t(100, 150, hints(min_aspect=1, max_aspect=1,
654
base_size=(3, 4), resize_inc=(10, 10)),
658
class TestRegion(object):
659
def test_get_rectangle_from_region(self):
661
region = gtk.gdk.Region()
662
assert_raises(ValueError, l.get_rectangle_from_region, region)
664
rect1 = gtk.gdk.Rectangle(1, 2, 3, 4)
665
region = gtk.gdk.region_rectangle(rect1)
666
(x, y, w, h) = l.get_rectangle_from_region(region)
667
assert (x, y, w, h) == (1, 2, 3, 4)
669
region.union_with_rect(gtk.gdk.Rectangle(10, 11, 12, 13))
670
(x, y, w, h) = l.get_rectangle_from_region(region)
671
assert (x, y, w, h) in [(1, 2, 3, 4), (10, 11, 12, 13)]
673
region.subtract(gtk.gdk.region_rectangle(rect1))
674
(x, y, w, h) = l.get_rectangle_from_region(region)
675
assert (x, y, w, h) == (10, 11, 12, 13)
678
class TestImageOptimizations(object):
679
def test_premultiply_argb_in_place(self):
681
ar = array.array("I", [0x80ffffff, 0x306090b0])
682
l.premultiply_argb_in_place(ar)
683
# Got each byte by calculating e.g.:
684
# hex(int(0xb0 * (0x30 / 255.))) == 0x21
685
assert ar[0] == 0x80808080
686
assert ar[1] == 0x30121b21