aioxlib 0.0.7__tar.gz → 0.0.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aioxlib-0.0.7 → aioxlib-0.0.8}/PKG-INFO +1 -1
- {aioxlib-0.0.7 → aioxlib-0.0.8}/aioxlib/__init__.py +182 -144
- {aioxlib-0.0.7 → aioxlib-0.0.8}/aioxlib.egg-info/PKG-INFO +1 -1
- {aioxlib-0.0.7 → aioxlib-0.0.8}/tests/test_aioxlib.py +0 -180
- {aioxlib-0.0.7 → aioxlib-0.0.8}/README.md +0 -0
- {aioxlib-0.0.7 → aioxlib-0.0.8}/aioxlib.egg-info/SOURCES.txt +0 -0
- {aioxlib-0.0.7 → aioxlib-0.0.8}/aioxlib.egg-info/dependency_links.txt +0 -0
- {aioxlib-0.0.7 → aioxlib-0.0.8}/aioxlib.egg-info/top_level.txt +0 -0
- {aioxlib-0.0.7 → aioxlib-0.0.8}/pyproject.toml +0 -0
- {aioxlib-0.0.7 → aioxlib-0.0.8}/setup.cfg +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Copyright (c) 2026 Tiago Coutinho
|
|
5
5
|
# Distributed under the GPLv3 license. See LICENSE for more info.
|
|
6
6
|
|
|
7
|
-
__version__ = "0.0.
|
|
7
|
+
__version__ = "0.0.8"
|
|
8
8
|
|
|
9
9
|
import asyncio
|
|
10
10
|
import enum
|
|
@@ -85,11 +85,6 @@ class EventMask(enum.IntFlag):
|
|
|
85
85
|
OwnerGrabButton = 1 << 24
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
class PacketType(enum.IntEnum):
|
|
89
|
-
Error = 0
|
|
90
|
-
Reply = 1
|
|
91
|
-
|
|
92
|
-
|
|
93
88
|
class Event(enum.IntEnum):
|
|
94
89
|
KeyPress = 2
|
|
95
90
|
KeyRelease = 3
|
|
@@ -456,18 +451,20 @@ WINDOW_ATTR_ORDER = (
|
|
|
456
451
|
("cursor", CreateWindowAttributes.Cursor),
|
|
457
452
|
)
|
|
458
453
|
|
|
454
|
+
|
|
455
|
+
class StackMode(enum.IntEnum):
|
|
456
|
+
Above = 0
|
|
457
|
+
Below = 1
|
|
458
|
+
TopIf = 2
|
|
459
|
+
BottomIf = 3
|
|
460
|
+
Opposite = 4
|
|
461
|
+
|
|
462
|
+
|
|
459
463
|
FirstExtensionError = 128
|
|
460
464
|
LastExtensionError = 255
|
|
461
465
|
InputOutput = 1
|
|
462
466
|
InputOnly = 2
|
|
463
467
|
|
|
464
|
-
CWX = 1 << 0
|
|
465
|
-
CWY = 1 << 1
|
|
466
|
-
CWWidth = 1 << 2
|
|
467
|
-
CWHeight = 1 << 3
|
|
468
|
-
CWBorderWidth = 1 << 4
|
|
469
|
-
CWSibling = 1 << 5
|
|
470
|
-
CWStackMode = 1 << 6
|
|
471
468
|
|
|
472
469
|
ForgetGravity = 0
|
|
473
470
|
NorthWestGravity = 1
|
|
@@ -492,11 +489,7 @@ SetModeDelete = 1
|
|
|
492
489
|
DestroyAll = 0
|
|
493
490
|
RetainPermanent = 1
|
|
494
491
|
RetainTemporary = 2
|
|
495
|
-
|
|
496
|
-
Below = 1
|
|
497
|
-
TopIf = 2
|
|
498
|
-
BottomIf = 3
|
|
499
|
-
Opposite = 4
|
|
492
|
+
|
|
500
493
|
RaiseLowest = 0
|
|
501
494
|
LowerHighest = 1
|
|
502
495
|
|
|
@@ -604,16 +597,38 @@ GC_ATTR_ORDER = (
|
|
|
604
597
|
("arc_mode", GCAttributes.ArcMode, "B3x"),
|
|
605
598
|
)
|
|
606
599
|
|
|
607
|
-
FontLeftToRight = 0
|
|
608
|
-
FontRightToLeft = 1
|
|
609
|
-
FontChange = 255
|
|
610
|
-
|
|
611
600
|
|
|
612
601
|
class Allocate(enum.IntEnum):
|
|
613
602
|
None_ = 0
|
|
614
603
|
All = 1
|
|
615
604
|
|
|
616
605
|
|
|
606
|
+
class ConfigureWindowAttributes(enum.IntFlag):
|
|
607
|
+
"""Value-mask bits for ConfigureWindow (same as CW* constants)."""
|
|
608
|
+
|
|
609
|
+
X = 1 << 0
|
|
610
|
+
Y = 1 << 1
|
|
611
|
+
Width = 1 << 2
|
|
612
|
+
Height = 1 << 3
|
|
613
|
+
BorderWidth = 1 << 4
|
|
614
|
+
Sibling = 1 << 5
|
|
615
|
+
StackMode = 1 << 6
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
CONFIGURE_WINDOW_ORDER = (
|
|
619
|
+
("x", ConfigureWindowAttributes.X),
|
|
620
|
+
("y", ConfigureWindowAttributes.Y),
|
|
621
|
+
("width", ConfigureWindowAttributes.Width),
|
|
622
|
+
("height", ConfigureWindowAttributes.Height),
|
|
623
|
+
("border_width", ConfigureWindowAttributes.BorderWidth),
|
|
624
|
+
("sibling", ConfigureWindowAttributes.Sibling),
|
|
625
|
+
("stack_mode", ConfigureWindowAttributes.StackMode),
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
ontLeftToRight = 0
|
|
629
|
+
FontRightToLeft = 1
|
|
630
|
+
FontChange = 255
|
|
631
|
+
|
|
617
632
|
DoRed = 1 << 0
|
|
618
633
|
DoGreen = 1 << 1
|
|
619
634
|
DoBlue = 1 << 2
|
|
@@ -777,34 +792,11 @@ def pack_setup(
|
|
|
777
792
|
return struct.pack(fmt, byte_order, *protocol, n1, d1, auth_name, auth_data)
|
|
778
793
|
|
|
779
794
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
head_len = struct.calcsize(head)
|
|
786
|
-
data += await reader.readexactly(head_len - 1)
|
|
787
|
-
status, proto_major, proto_minor, length = struct.unpack_from(head, data)
|
|
788
|
-
assert proto_major >= 11
|
|
789
|
-
length *= 4
|
|
790
|
-
data = await reader.readexactly(length)
|
|
791
|
-
return unpack_successfull_setup(data)[0]
|
|
792
|
-
elif status == Status.Failed:
|
|
793
|
-
head = "BBHHH"
|
|
794
|
-
head_len = struct.calcsize(head)
|
|
795
|
-
data += await reader.readexactly(head_len - 1)
|
|
796
|
-
status, _, proto_major, proto_minor, length = struct.unpack_from(head, data)
|
|
797
|
-
length *= 4
|
|
798
|
-
reason = (await reader.readexactly(length)).decode()
|
|
799
|
-
raise RuntimeError(reason)
|
|
800
|
-
elif status == Status.Authenticate:
|
|
801
|
-
head = "BxH"
|
|
802
|
-
head_len = struct.calcsize(head)
|
|
803
|
-
data = await reader.readexactly(head_len - 1)
|
|
804
|
-
status, length = struct.unpack_from(head, data)
|
|
805
|
-
n = ord(data[1]) * 4
|
|
806
|
-
reason = (await reader.readexactly(n)).decode()
|
|
807
|
-
raise ValueError(reason)
|
|
795
|
+
def unpack_setup(data, offset=0):
|
|
796
|
+
_, proto_major, _, _ = struct.unpack_from("BxHHH", data, offset)
|
|
797
|
+
offset += 8
|
|
798
|
+
assert proto_major == PROTOCOL_MAJOR
|
|
799
|
+
return unpack_successfull_setup(data, offset)
|
|
808
800
|
|
|
809
801
|
|
|
810
802
|
def unpack_successfull_setup(data, offset=0):
|
|
@@ -977,6 +969,35 @@ def pack_destroy_subwindows(wid) -> bytes:
|
|
|
977
969
|
return struct.pack("BxHI", Command.DestroySubwindows, 2, wid)
|
|
978
970
|
|
|
979
971
|
|
|
972
|
+
def pack_configure_window(window, **attrs) -> bytes:
|
|
973
|
+
"""ConfigureWindow: change geometry and/or stacking of *window*.
|
|
974
|
+
|
|
975
|
+
Accepted keyword attributes (any subset)::
|
|
976
|
+
|
|
977
|
+
x, y, width, height, border_width, sibling, stack_mode
|
|
978
|
+
|
|
979
|
+
Values are sent in protocol order; each is a CARD32 (16-bit geometry
|
|
980
|
+
fields occupy the low 16 bits).
|
|
981
|
+
"""
|
|
982
|
+
mask = ConfigureWindowAttributes(0)
|
|
983
|
+
values = []
|
|
984
|
+
for name, bit in CONFIGURE_WINDOW_ORDER:
|
|
985
|
+
if (val := attrs.get(name)) is None:
|
|
986
|
+
continue
|
|
987
|
+
mask |= bit
|
|
988
|
+
values.append(int(val))
|
|
989
|
+
length = 3 + len(values)
|
|
990
|
+
header = struct.pack(
|
|
991
|
+
"=BxHIH2x",
|
|
992
|
+
Command.ConfigureWindow,
|
|
993
|
+
length,
|
|
994
|
+
int(window),
|
|
995
|
+
int(mask),
|
|
996
|
+
)
|
|
997
|
+
body = b"".join(struct.pack("=I", v) for v in values)
|
|
998
|
+
return header + body
|
|
999
|
+
|
|
1000
|
+
|
|
980
1001
|
def pack_list_properties(wid) -> bytes:
|
|
981
1002
|
return struct.pack("BxHI", Command.ListProperties, 2, wid)
|
|
982
1003
|
|
|
@@ -1121,6 +1142,14 @@ def unpack_error(data: bytes, offset: int = 0):
|
|
|
1121
1142
|
return result, offset
|
|
1122
1143
|
|
|
1123
1144
|
|
|
1145
|
+
def unpack_setup_error(data, offset=0):
|
|
1146
|
+
*_, length = struct.unpack_from("BBHHH", data, offset)
|
|
1147
|
+
length *= 4
|
|
1148
|
+
offset += 8
|
|
1149
|
+
error = data[offset : offset + length].strip(b"\x00").decode()
|
|
1150
|
+
return error, offset + length
|
|
1151
|
+
|
|
1152
|
+
|
|
1124
1153
|
def unpack_event(event_map, data: bytes, offset: int = 0):
|
|
1125
1154
|
raw_event_type = data[0]
|
|
1126
1155
|
# Skip bit 8, that is set if this event came from an SendEvent
|
|
@@ -1336,6 +1365,19 @@ def unpack_get_geometry(data, offset=0):
|
|
|
1336
1365
|
return result, offset + 32
|
|
1337
1366
|
|
|
1338
1367
|
|
|
1368
|
+
def pack_reparent_window(window, parent, x, y) -> bytes:
|
|
1369
|
+
"""ReparentWindow: move *window* under *parent* at (x, y) in parent coords."""
|
|
1370
|
+
return struct.pack(
|
|
1371
|
+
"BxHIIhh",
|
|
1372
|
+
Command.ReparentWindow,
|
|
1373
|
+
4,
|
|
1374
|
+
window,
|
|
1375
|
+
parent,
|
|
1376
|
+
x,
|
|
1377
|
+
y,
|
|
1378
|
+
)
|
|
1379
|
+
|
|
1380
|
+
|
|
1339
1381
|
def get_auth_config(filename: str) -> list[dict]:
|
|
1340
1382
|
with open(filename, "rb") as f:
|
|
1341
1383
|
raw = f.read()
|
|
@@ -1409,7 +1451,7 @@ class Screen:
|
|
|
1409
1451
|
|
|
1410
1452
|
|
|
1411
1453
|
class Resource:
|
|
1412
|
-
def __init__(self, display, rid):
|
|
1454
|
+
def __init__(self, display: "Display", rid):
|
|
1413
1455
|
self.display = display
|
|
1414
1456
|
self.id = rid
|
|
1415
1457
|
|
|
@@ -1559,6 +1601,33 @@ class Window(Drawable):
|
|
|
1559
1601
|
"""Allocate a colormap for *visual* on the screen of *window* (default: root)."""
|
|
1560
1602
|
return await self.display.create_colormap(visual, window or self, alloc)
|
|
1561
1603
|
|
|
1604
|
+
async def reparent(self, parent, x=0, y=0):
|
|
1605
|
+
"""Reparent this window under *parent* at (*x*, *y*) in parent coordinates.
|
|
1606
|
+
|
|
1607
|
+
The window should typically be unmapped first (X servers often require
|
|
1608
|
+
it). After reparenting, map it again if it should be visible.
|
|
1609
|
+
"""
|
|
1610
|
+
return await self.display.reparent_window(self, parent, x, y)
|
|
1611
|
+
|
|
1612
|
+
async def configure(self, **attrs):
|
|
1613
|
+
return await self.display.configure_window(self, **attrs)
|
|
1614
|
+
|
|
1615
|
+
async def move(self, x, y):
|
|
1616
|
+
"""Move this window to (*x*, *y*) in parent coordinates."""
|
|
1617
|
+
return await self.configure(x=x, y=y)
|
|
1618
|
+
|
|
1619
|
+
async def resize(self, width, height):
|
|
1620
|
+
"""Resize this window to *width* × *height*."""
|
|
1621
|
+
return await self.configure(width=width, height=height)
|
|
1622
|
+
|
|
1623
|
+
async def raise_(self):
|
|
1624
|
+
"""Raise this window to the top of the stack in its parent."""
|
|
1625
|
+
return await self.configure(stack_mode=StackMode.Above)
|
|
1626
|
+
|
|
1627
|
+
async def lower(self):
|
|
1628
|
+
"""Lower this window to the bottom of the stack in its parent."""
|
|
1629
|
+
return await self.configure(stack_mode=StackMode.Below)
|
|
1630
|
+
|
|
1562
1631
|
|
|
1563
1632
|
class GC(Resource):
|
|
1564
1633
|
def __init__(self, display, rid, drawable):
|
|
@@ -1626,9 +1695,6 @@ class Connection:
|
|
|
1626
1695
|
async def read(self, n=-1):
|
|
1627
1696
|
return await self.reader.read(n)
|
|
1628
1697
|
|
|
1629
|
-
async def readexactly(self, n):
|
|
1630
|
-
return await self.reader.readexactly(n)
|
|
1631
|
-
|
|
1632
1698
|
|
|
1633
1699
|
async def open_connection(config: dict) -> Connection:
|
|
1634
1700
|
protocol = config["protocol"]
|
|
@@ -4073,7 +4139,7 @@ def unpack_render_query_pict_formats(data, offset=0):
|
|
|
4073
4139
|
d["visuals"] = visuals[vi : vi + d["n_visuals"]]
|
|
4074
4140
|
vi += d["n_visuals"]
|
|
4075
4141
|
di = 0
|
|
4076
|
-
for s, n in zip(screens, depth_headers):
|
|
4142
|
+
for s, n in zip(screens, depth_headers, strict=True):
|
|
4077
4143
|
s["depths"] = depths[di : di + n]
|
|
4078
4144
|
di += n
|
|
4079
4145
|
|
|
@@ -4144,9 +4210,7 @@ def pack_render_set_picture_clip_rectangles(opcode, picture, x_origin, y_origin,
|
|
|
4144
4210
|
|
|
4145
4211
|
|
|
4146
4212
|
def pack_render_free_picture(opcode, picture):
|
|
4147
|
-
return struct.pack(
|
|
4148
|
-
"BBHI", opcode, RenderCommand.FreePicture, 2, int(picture)
|
|
4149
|
-
)
|
|
4213
|
+
return struct.pack("BBHI", opcode, RenderCommand.FreePicture, 2, int(picture))
|
|
4150
4214
|
|
|
4151
4215
|
|
|
4152
4216
|
def pack_render_composite(
|
|
@@ -4200,9 +4264,7 @@ def pack_render_fill_rectangles(opcode, op, dst, color, rects):
|
|
|
4200
4264
|
0,
|
|
4201
4265
|
dst,
|
|
4202
4266
|
)
|
|
4203
|
-
body = pack_render_color(*color) + b"".join(
|
|
4204
|
-
struct.pack("hhHH", *r) for r in rects
|
|
4205
|
-
)
|
|
4267
|
+
body = pack_render_color(*color) + b"".join(struct.pack("hhHH", *r) for r in rects)
|
|
4206
4268
|
return header + body
|
|
4207
4269
|
|
|
4208
4270
|
|
|
@@ -4242,9 +4304,7 @@ def pack_render_set_picture_filter(opcode, picture, name, values=()):
|
|
|
4242
4304
|
|
|
4243
4305
|
|
|
4244
4306
|
def pack_render_query_filters(opcode, drawable):
|
|
4245
|
-
return struct.pack(
|
|
4246
|
-
"BBHI", opcode, RenderCommand.QueryFilters, 2, int(drawable)
|
|
4247
|
-
)
|
|
4307
|
+
return struct.pack("BBHI", opcode, RenderCommand.QueryFilters, 2, int(drawable))
|
|
4248
4308
|
|
|
4249
4309
|
|
|
4250
4310
|
def unpack_render_query_filters(data, offset=0):
|
|
@@ -4299,9 +4359,7 @@ def pack_render_create_linear_gradient(opcode, pid, p1, p2, stops):
|
|
|
4299
4359
|
return header + body
|
|
4300
4360
|
|
|
4301
4361
|
|
|
4302
|
-
def pack_render_create_radial_gradient(
|
|
4303
|
-
opcode, pid, inner, outer, inner_radius, outer_radius, stops
|
|
4304
|
-
):
|
|
4362
|
+
def pack_render_create_radial_gradient(opcode, pid, inner, outer, inner_radius, outer_radius, stops):
|
|
4305
4363
|
n = len(stops)
|
|
4306
4364
|
length = 9 + n + n * 2
|
|
4307
4365
|
header = struct.pack(
|
|
@@ -4424,9 +4482,7 @@ def pack_render_create_glyphset(opcode, gsid, format):
|
|
|
4424
4482
|
|
|
4425
4483
|
|
|
4426
4484
|
def pack_render_free_glyphset(opcode, gsid):
|
|
4427
|
-
return struct.pack(
|
|
4428
|
-
"BBHI", opcode, RenderCommand.FreeGlyphSet, 2, gsid
|
|
4429
|
-
)
|
|
4485
|
+
return struct.pack("BBHI", opcode, RenderCommand.FreeGlyphSet, 2, gsid)
|
|
4430
4486
|
|
|
4431
4487
|
|
|
4432
4488
|
def pack_render_add_glyphs(opcode, gsid, glyph_ids, glyph_infos, data):
|
|
@@ -4436,14 +4492,9 @@ def pack_render_add_glyphs(opcode, gsid, glyph_ids, glyph_infos, data):
|
|
|
4436
4492
|
n = len(glyph_ids)
|
|
4437
4493
|
if n != len(glyph_infos):
|
|
4438
4494
|
raise ValueError("glyph_ids and glyph_infos length mismatch")
|
|
4439
|
-
info_bytes = b"".join(
|
|
4440
|
-
struct.pack("HHhh hh", *info) for info in glyph_infos
|
|
4441
|
-
)
|
|
4495
|
+
info_bytes = b"".join(struct.pack("HHhh hh", *info) for info in glyph_infos)
|
|
4442
4496
|
# each GlyphInfo is 12 bytes
|
|
4443
|
-
info_bytes = b"".join(
|
|
4444
|
-
struct.pack("HHhhhh", w, h, x, y, xo, yo)
|
|
4445
|
-
for (w, h, x, y, xo, yo) in glyph_infos
|
|
4446
|
-
)
|
|
4497
|
+
info_bytes = b"".join(struct.pack("HHhhhh", w, h, x, y, xo, yo) for (w, h, x, y, xo, yo) in glyph_infos)
|
|
4447
4498
|
ids_bytes = b"".join(struct.pack("I", int(g)) for g in glyph_ids)
|
|
4448
4499
|
# pad image data to 32-bit
|
|
4449
4500
|
pad = (4 - (len(data) % 4)) % 4
|
|
@@ -4463,16 +4514,12 @@ def pack_render_add_glyphs(opcode, gsid, glyph_ids, glyph_infos, data):
|
|
|
4463
4514
|
def pack_render_free_glyphs(opcode, gsid, glyph_ids):
|
|
4464
4515
|
n = len(glyph_ids)
|
|
4465
4516
|
length = 2 + n
|
|
4466
|
-
header = struct.pack(
|
|
4467
|
-
"BBHI", opcode, RenderCommand.FreeGlyphs, length, int(gsid)
|
|
4468
|
-
)
|
|
4517
|
+
header = struct.pack("BBHI", opcode, RenderCommand.FreeGlyphs, length, int(gsid))
|
|
4469
4518
|
body = b"".join(struct.pack("I", int(g)) for g in glyph_ids)
|
|
4470
4519
|
return header + body
|
|
4471
4520
|
|
|
4472
4521
|
|
|
4473
|
-
def pack_render_composite_glyphs8(
|
|
4474
|
-
opcode, op, src, dst, mask_format, glyphset, src_x, src_y, elements
|
|
4475
|
-
):
|
|
4522
|
+
def pack_render_composite_glyphs8(opcode, op, src, dst, mask_format, glyphset, src_x, src_y, elements):
|
|
4476
4523
|
"""elements: sequence of (delta_x, delta_y, glyphs_bytes_or_str).
|
|
4477
4524
|
|
|
4478
4525
|
Each element becomes an xGlyphElt: 1-byte length + deltas + glyph 8-bit ids,
|
|
@@ -4552,9 +4599,7 @@ class Render(Extension):
|
|
|
4552
4599
|
|
|
4553
4600
|
async def query_pict_formats(self):
|
|
4554
4601
|
payload = pack_render_query_pict_formats(self.code)
|
|
4555
|
-
reply = await self.display.write_read(
|
|
4556
|
-
payload, unpack_render_query_pict_formats
|
|
4557
|
-
)
|
|
4602
|
+
reply = await self.display.write_read(payload, unpack_render_query_pict_formats)
|
|
4558
4603
|
self._formats = reply
|
|
4559
4604
|
return reply
|
|
4560
4605
|
|
|
@@ -4584,9 +4629,7 @@ class Render(Extension):
|
|
|
4584
4629
|
"""Create a Picture for *drawable* using PictFormat *format*."""
|
|
4585
4630
|
if pid is None:
|
|
4586
4631
|
pid = self.display.resources.allocate()
|
|
4587
|
-
payload = pack_render_create_picture(
|
|
4588
|
-
self.code, pid, drawable, format, **attrs
|
|
4589
|
-
)
|
|
4632
|
+
payload = pack_render_create_picture(self.code, pid, drawable, format, **attrs)
|
|
4590
4633
|
self.display.write(payload)
|
|
4591
4634
|
return pid
|
|
4592
4635
|
|
|
@@ -4595,9 +4638,7 @@ class Render(Extension):
|
|
|
4595
4638
|
self.display.write(payload)
|
|
4596
4639
|
|
|
4597
4640
|
async def set_clip_rectangles(self, picture, x_origin, y_origin, rects):
|
|
4598
|
-
payload = pack_render_set_picture_clip_rectangles(
|
|
4599
|
-
self.code, picture, x_origin, y_origin, rects
|
|
4600
|
-
)
|
|
4641
|
+
payload = pack_render_set_picture_clip_rectangles(self.code, picture, x_origin, y_origin, rects)
|
|
4601
4642
|
self.display.write(payload)
|
|
4602
4643
|
|
|
4603
4644
|
async def free_picture(self, picture):
|
|
@@ -4651,9 +4692,7 @@ class Render(Extension):
|
|
|
4651
4692
|
|
|
4652
4693
|
async def set_filter(self, picture, name, values=()):
|
|
4653
4694
|
"""Set filter name (e.g. ``\"bilinear\"``, ``\"nearest\"``) and params."""
|
|
4654
|
-
payload = pack_render_set_picture_filter(
|
|
4655
|
-
self.code, picture, name, values
|
|
4656
|
-
)
|
|
4695
|
+
payload = pack_render_set_picture_filter(self.code, picture, name, values)
|
|
4657
4696
|
self.display.write(payload)
|
|
4658
4697
|
|
|
4659
4698
|
async def query_filters(self, drawable):
|
|
@@ -4670,46 +4709,30 @@ class Render(Extension):
|
|
|
4670
4709
|
async def create_linear_gradient(self, p1, p2, stops, pid=None):
|
|
4671
4710
|
if pid is None:
|
|
4672
4711
|
pid = self.display.resources.allocate()
|
|
4673
|
-
payload = pack_render_create_linear_gradient(
|
|
4674
|
-
self.code, pid, p1, p2, stops
|
|
4675
|
-
)
|
|
4712
|
+
payload = pack_render_create_linear_gradient(self.code, pid, p1, p2, stops)
|
|
4676
4713
|
self.display.write(payload)
|
|
4677
4714
|
return pid
|
|
4678
4715
|
|
|
4679
|
-
async def create_radial_gradient(
|
|
4680
|
-
self, inner, outer, inner_radius, outer_radius, stops, pid=None
|
|
4681
|
-
):
|
|
4716
|
+
async def create_radial_gradient(self, inner, outer, inner_radius, outer_radius, stops, pid=None):
|
|
4682
4717
|
if pid is None:
|
|
4683
4718
|
pid = self.display.resources.allocate()
|
|
4684
|
-
payload = pack_render_create_radial_gradient(
|
|
4685
|
-
self.code, pid, inner, outer, inner_radius, outer_radius, stops
|
|
4686
|
-
)
|
|
4719
|
+
payload = pack_render_create_radial_gradient(self.code, pid, inner, outer, inner_radius, outer_radius, stops)
|
|
4687
4720
|
self.display.write(payload)
|
|
4688
4721
|
return pid
|
|
4689
4722
|
|
|
4690
4723
|
async def create_conical_gradient(self, center, angle, stops, pid=None):
|
|
4691
4724
|
if pid is None:
|
|
4692
4725
|
pid = self.display.resources.allocate()
|
|
4693
|
-
payload = pack_render_create_conical_gradient(
|
|
4694
|
-
self.code, pid, center, angle, stops
|
|
4695
|
-
)
|
|
4726
|
+
payload = pack_render_create_conical_gradient(self.code, pid, center, angle, stops)
|
|
4696
4727
|
self.display.write(payload)
|
|
4697
4728
|
return pid
|
|
4698
4729
|
|
|
4699
|
-
async def trapezoids(
|
|
4700
|
-
self, op, src, dst, src_x, src_y, traps
|
|
4701
|
-
):
|
|
4702
|
-
payload = pack_render_trapezoids(
|
|
4703
|
-
self.code, op, src, dst, mask_format, src_x, src_y, traps
|
|
4704
|
-
)
|
|
4730
|
+
async def trapezoids(self, op, src, dst, src_x, src_y, traps, mask_format=0):
|
|
4731
|
+
payload = pack_render_trapezoids(self.code, op, src, dst, mask_format, src_x, src_y, traps)
|
|
4705
4732
|
self.display.write(payload)
|
|
4706
4733
|
|
|
4707
|
-
async def triangles(
|
|
4708
|
-
self, op, src, dst, src_x, src_y, tris
|
|
4709
|
-
):
|
|
4710
|
-
payload = pack_render_triangles(
|
|
4711
|
-
self.code, op, src, dst, mask_format, src_x, src_y, tris
|
|
4712
|
-
)
|
|
4734
|
+
async def triangles(self, op, src, dst, src_x, src_y, tris, mask_format=0):
|
|
4735
|
+
payload = pack_render_triangles(self.code, op, src, dst, mask_format, src_x, src_y, tris)
|
|
4713
4736
|
self.display.write(payload)
|
|
4714
4737
|
|
|
4715
4738
|
async def create_glyphset(self, format, gsid=None):
|
|
@@ -4723,9 +4746,7 @@ class Render(Extension):
|
|
|
4723
4746
|
self.display.write(pack_render_free_glyphset(self.code, gsid))
|
|
4724
4747
|
|
|
4725
4748
|
async def add_glyphs(self, gsid, glyph_ids, glyph_infos, data):
|
|
4726
|
-
payload = pack_render_add_glyphs(
|
|
4727
|
-
self.code, gsid, glyph_ids, glyph_infos, data
|
|
4728
|
-
)
|
|
4749
|
+
payload = pack_render_add_glyphs(self.code, gsid, glyph_ids, glyph_infos, data)
|
|
4729
4750
|
self.display.write(payload)
|
|
4730
4751
|
|
|
4731
4752
|
async def free_glyphs(self, gsid, glyph_ids):
|
|
@@ -4842,9 +4863,6 @@ class Display:
|
|
|
4842
4863
|
async def read(self, n=-1):
|
|
4843
4864
|
return await self.connection.read(n)
|
|
4844
4865
|
|
|
4845
|
-
async def readexactly(self, n):
|
|
4846
|
-
return await self.connection.readexactly(n)
|
|
4847
|
-
|
|
4848
4866
|
def write_read(self, payload, callback):
|
|
4849
4867
|
future = asyncio.Future()
|
|
4850
4868
|
self.requests.put_nowait((callback, future))
|
|
@@ -4855,15 +4873,14 @@ class Display:
|
|
|
4855
4873
|
await self.connection.flush()
|
|
4856
4874
|
|
|
4857
4875
|
async def setup(self):
|
|
4876
|
+
if self._task is None:
|
|
4877
|
+
self._task = asyncio.create_task(self._loop())
|
|
4858
4878
|
entry = auth(self.connection)
|
|
4859
|
-
|
|
4860
|
-
self.
|
|
4861
|
-
await self.flush()
|
|
4862
|
-
info = await read_setup(self)
|
|
4879
|
+
payload = pack_setup(entry["name"], entry["data"])
|
|
4880
|
+
info = await self.write_read(payload, unpack_setup)
|
|
4863
4881
|
self.info = info
|
|
4864
4882
|
self.resources = ResourceManager(info["resource_id_base"], info["resource_id_mask"])
|
|
4865
4883
|
self.screens = [Screen(self, sinfo) for sinfo in info["screens"]]
|
|
4866
|
-
self._task = asyncio.create_task(self._loop())
|
|
4867
4884
|
return info
|
|
4868
4885
|
|
|
4869
4886
|
async def intern_atom(self, name, only_if_exists=False):
|
|
@@ -4937,6 +4954,15 @@ class Display:
|
|
|
4937
4954
|
payload = pack_destroy_window(wnd)
|
|
4938
4955
|
self.write(payload)
|
|
4939
4956
|
|
|
4957
|
+
async def configure_window(self, window, **attrs):
|
|
4958
|
+
"""Configure geometry and/or stacking of *window*.
|
|
4959
|
+
|
|
4960
|
+
Keyword attributes: ``x``, ``y``, ``width``, ``height``,
|
|
4961
|
+
``border_width``, ``sibling``, ``stack_mode``.
|
|
4962
|
+
"""
|
|
4963
|
+
payload = pack_configure_window(window, **attrs)
|
|
4964
|
+
self.write(payload)
|
|
4965
|
+
|
|
4940
4966
|
async def list_properties(self, wnd):
|
|
4941
4967
|
payload = pack_list_properties(wnd)
|
|
4942
4968
|
reply = await self.write_read(payload, unpack_list_properties)
|
|
@@ -4997,6 +5023,11 @@ class Display:
|
|
|
4997
5023
|
async def free_colormap(self, cid):
|
|
4998
5024
|
self.write(pack_free_colormap(cid))
|
|
4999
5025
|
|
|
5026
|
+
async def reparent_window(self, window, parent, x=0, y=0):
|
|
5027
|
+
"""Reparent *window* under *parent* at (*x*, *y*) in parent coordinates."""
|
|
5028
|
+
payload = pack_reparent_window(window, parent, x, y)
|
|
5029
|
+
self.write(payload)
|
|
5030
|
+
|
|
5000
5031
|
def add_listener(self, listener):
|
|
5001
5032
|
self.listeners.add(listener)
|
|
5002
5033
|
|
|
@@ -5030,29 +5061,36 @@ class Display:
|
|
|
5030
5061
|
return error
|
|
5031
5062
|
|
|
5032
5063
|
async def _loop(self):
|
|
5033
|
-
buffer = b""
|
|
5064
|
+
buffer, decoder = b"", None
|
|
5034
5065
|
while True:
|
|
5035
|
-
if not (data := await self.read(
|
|
5066
|
+
if not (data := await self.read(4 * 1024)):
|
|
5036
5067
|
log.info("Connection closed")
|
|
5037
5068
|
return
|
|
5038
5069
|
buffer += data
|
|
5039
5070
|
packet_type = buffer[0]
|
|
5040
|
-
if packet_type in
|
|
5041
|
-
if
|
|
5042
|
-
|
|
5071
|
+
if packet_type in Status:
|
|
5072
|
+
if decoder is None:
|
|
5073
|
+
decoder = await self.requests.get()
|
|
5074
|
+
decode, future = decoder
|
|
5075
|
+
if packet_type == Status.Success:
|
|
5076
|
+
if decode == unpack_setup:
|
|
5077
|
+
size = 8 + 4 * int.from_bytes(buffer[6:8], BYTE_ORDER_TEXT)
|
|
5078
|
+
else:
|
|
5079
|
+
size = 32 + 4 * int.from_bytes(buffer[4:8], BYTE_ORDER_TEXT)
|
|
5043
5080
|
if len(buffer) < size:
|
|
5044
5081
|
continue
|
|
5045
|
-
decode, future = await self.requests.get()
|
|
5046
5082
|
reply, offset = decode(buffer, 0)
|
|
5047
5083
|
future.set_result(reply)
|
|
5048
|
-
elif packet_type ==
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5084
|
+
elif packet_type == Status.Failed:
|
|
5085
|
+
if decode == unpack_setup:
|
|
5086
|
+
error, offset = unpack_setup_error(buffer)
|
|
5087
|
+
future.set_exception(RuntimeError(error))
|
|
5088
|
+
else:
|
|
5089
|
+
error, offset = unpack_error(buffer)
|
|
5090
|
+
error = self._handle_error(error)
|
|
5091
|
+
log.warning("ERROR: %s", error)
|
|
5092
|
+
future.set_exception(X11Error(error))
|
|
5093
|
+
decoder = None
|
|
5056
5094
|
else:
|
|
5057
5095
|
event, offset = unpack_event(self._events, buffer)
|
|
5058
5096
|
for listener in self.listeners:
|
|
@@ -96,7 +96,6 @@ from aioxlib import (
|
|
|
96
96
|
pack_unmap_window,
|
|
97
97
|
pad,
|
|
98
98
|
parse_font_name,
|
|
99
|
-
read_setup,
|
|
100
99
|
round_bytes,
|
|
101
100
|
unpack_client_message,
|
|
102
101
|
unpack_dbe_get_back_buffer_attributes,
|
|
@@ -1319,114 +1318,6 @@ def _make_reader(data: bytes) -> asyncio.StreamReader:
|
|
|
1319
1318
|
return reader
|
|
1320
1319
|
|
|
1321
1320
|
|
|
1322
|
-
class TestReadSetup:
|
|
1323
|
-
@pytest.mark.asyncio
|
|
1324
|
-
async def test_success(self):
|
|
1325
|
-
reply = build_success_reply()
|
|
1326
|
-
reader = _make_reader(reply)
|
|
1327
|
-
setup = await read_setup(reader)
|
|
1328
|
-
assert setup["vendor"] == "XOrg"
|
|
1329
|
-
assert setup["root_length"] == 1
|
|
1330
|
-
assert len(setup["screens"]) == 1
|
|
1331
|
-
assert setup["screens"][0]["width"] == 1920
|
|
1332
|
-
|
|
1333
|
-
@pytest.mark.asyncio
|
|
1334
|
-
async def test_success_custom_body(self):
|
|
1335
|
-
body = build_success_setup_body(vendor="XOrg", release=42)
|
|
1336
|
-
reply = build_success_reply(body)
|
|
1337
|
-
setup = await read_setup(_make_reader(reply))
|
|
1338
|
-
assert setup["release"] == 42
|
|
1339
|
-
assert setup["vendor"] == "XOrg"
|
|
1340
|
-
|
|
1341
|
-
@pytest.mark.asyncio
|
|
1342
|
-
async def test_failed(self):
|
|
1343
|
-
reason = "No protocol specified"
|
|
1344
|
-
reason_b = reason.encode("latin-1")
|
|
1345
|
-
# pad reason to 4-byte boundary for length field
|
|
1346
|
-
padded = reason_b + b"\0" * pad(len(reason_b))
|
|
1347
|
-
length_units = len(padded) // 4
|
|
1348
|
-
# status(B) n(B) major(H) minor(H) length(H) then reason
|
|
1349
|
-
header = struct.pack(
|
|
1350
|
-
"=BBHHH",
|
|
1351
|
-
Status.Failed,
|
|
1352
|
-
len(reason_b), # n = length of reason string (unpadded)
|
|
1353
|
-
PROTOCOL_MAJOR,
|
|
1354
|
-
PROTOCOL_MINOR,
|
|
1355
|
-
length_units,
|
|
1356
|
-
)
|
|
1357
|
-
reply = header + padded
|
|
1358
|
-
reader = _make_reader(reply)
|
|
1359
|
-
with pytest.raises(RuntimeError, match="No protocol specified"):
|
|
1360
|
-
await read_setup(reader)
|
|
1361
|
-
|
|
1362
|
-
@pytest.mark.asyncio
|
|
1363
|
-
async def test_authenticate(self):
|
|
1364
|
-
"""Authenticate branch of read_setup.
|
|
1365
|
-
|
|
1366
|
-
Note: the current implementation replaces ``data`` with only
|
|
1367
|
-
``head_len - 1`` (3) bytes, then calls ``struct.unpack_from("BxH",
|
|
1368
|
-
data)`` which needs 4 bytes. That is a library bug; this test
|
|
1369
|
-
documents the actual behaviour (struct.error). When the library
|
|
1370
|
-
is fixed to append/read 4 bytes, switch the expectation to
|
|
1371
|
-
ValueError with the reason string.
|
|
1372
|
-
"""
|
|
1373
|
-
reason = "Authorization required"
|
|
1374
|
-
reason_b = reason.encode("latin-1")
|
|
1375
|
-
padded = reason_b + b"\0" * pad(len(reason_b))
|
|
1376
|
-
n_byte = len(padded) // 4
|
|
1377
|
-
# status + 3-byte partial header + reason (matches current read size)
|
|
1378
|
-
partial = bytes([0, n_byte, 0])
|
|
1379
|
-
reply = bytes([Status.Authenticate]) + partial + padded
|
|
1380
|
-
reader = _make_reader(reply)
|
|
1381
|
-
with pytest.raises(struct.error):
|
|
1382
|
-
await read_setup(reader)
|
|
1383
|
-
|
|
1384
|
-
@pytest.mark.asyncio
|
|
1385
|
-
async def test_authenticate_with_enough_header_bytes(self):
|
|
1386
|
-
"""If the reader supplies a full 4-byte header after status, the
|
|
1387
|
-
branch can still raise ValueError once unpack succeeds.
|
|
1388
|
-
|
|
1389
|
-
Current code: readexactly(3) only — so we cannot reach ValueError
|
|
1390
|
-
without changing the library. This test keeps a clear target for
|
|
1391
|
-
a future fix by using a custom reader that yields 4 bytes.
|
|
1392
|
-
"""
|
|
1393
|
-
reason = "Authorization required"
|
|
1394
|
-
reason_b = reason.encode("latin-1")
|
|
1395
|
-
padded = reason_b + b"\0" * pad(len(reason_b))
|
|
1396
|
-
n_byte = len(padded) // 4
|
|
1397
|
-
|
|
1398
|
-
class FakeReader:
|
|
1399
|
-
def __init__(self):
|
|
1400
|
-
self._chunks = [
|
|
1401
|
-
bytes([Status.Authenticate]),
|
|
1402
|
-
# pretend the library asked for 4 bytes (fixed behaviour)
|
|
1403
|
-
bytes([0, n_byte, 0, 0]),
|
|
1404
|
-
padded,
|
|
1405
|
-
]
|
|
1406
|
-
|
|
1407
|
-
async def readexactly(self, n):
|
|
1408
|
-
chunk = self._chunks.pop(0)
|
|
1409
|
-
if len(chunk) != n:
|
|
1410
|
-
# emulate real StreamReader: return exactly n if available
|
|
1411
|
-
if len(chunk) > n:
|
|
1412
|
-
self._chunks.insert(0, chunk[n:])
|
|
1413
|
-
return chunk[:n]
|
|
1414
|
-
raise asyncio.IncompleteReadError(chunk, n)
|
|
1415
|
-
return chunk
|
|
1416
|
-
|
|
1417
|
-
# With the *current* library (reads 3 bytes), FakeReader still fails
|
|
1418
|
-
# the same way. Assert that we get an error either way.
|
|
1419
|
-
with pytest.raises((struct.error, ValueError, asyncio.IncompleteReadError)):
|
|
1420
|
-
await read_setup(FakeReader())
|
|
1421
|
-
|
|
1422
|
-
@pytest.mark.asyncio
|
|
1423
|
-
async def test_success_then_eof_is_ok(self):
|
|
1424
|
-
reply = build_success_reply(build_success_setup_body(vendor="XOrg"))
|
|
1425
|
-
reader = _make_reader(reply)
|
|
1426
|
-
setup = await read_setup(reader)
|
|
1427
|
-
assert "screens" in setup
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
1321
|
# ---------------------------------------------------------------------------
|
|
1431
1322
|
# End-to-end: pack client setup ↔ shape of a server Success body
|
|
1432
1323
|
# ---------------------------------------------------------------------------
|
|
@@ -1460,7 +1351,6 @@ def mock_connection():
|
|
|
1460
1351
|
conn.write = MagicMock()
|
|
1461
1352
|
conn.flush = AsyncMock()
|
|
1462
1353
|
conn.read = AsyncMock(return_value=b"")
|
|
1463
|
-
conn.readexactly = AsyncMock()
|
|
1464
1354
|
conn.aclose = AsyncMock()
|
|
1465
1355
|
conn.close = MagicMock()
|
|
1466
1356
|
conn.config = {"protocol": "unix", "port": 0, "screen": 0, "uri": ":0"}
|
|
@@ -2105,56 +1995,6 @@ class TestDisplay:
|
|
|
2105
1995
|
display._task.cancel.assert_called_once()
|
|
2106
1996
|
display.connection.close.assert_called_once()
|
|
2107
1997
|
|
|
2108
|
-
@pytest.mark.asyncio
|
|
2109
|
-
async def test_setup_wires_resources_and_screens(self, mock_connection):
|
|
2110
|
-
from aioxlib import unpack_successfull_setup
|
|
2111
|
-
|
|
2112
|
-
body = build_success_setup_body(vendor="XOrg")
|
|
2113
|
-
length_units = len(body) // 4
|
|
2114
|
-
header = struct.pack("=BxHHH", 1, 11, 0, length_units)
|
|
2115
|
-
reply = header + body
|
|
2116
|
-
|
|
2117
|
-
buf = reply
|
|
2118
|
-
|
|
2119
|
-
async def readexactly(n):
|
|
2120
|
-
nonlocal buf
|
|
2121
|
-
if len(buf) < n:
|
|
2122
|
-
raise asyncio.IncompleteReadError(buf, n)
|
|
2123
|
-
chunk, buf = buf[:n], buf[n:]
|
|
2124
|
-
return chunk
|
|
2125
|
-
|
|
2126
|
-
mock_connection.readexactly = readexactly
|
|
2127
|
-
mock_connection.read = AsyncMock(return_value=b"") # _loop exits immediately
|
|
2128
|
-
mock_connection.flush = AsyncMock()
|
|
2129
|
-
mock_connection.write = MagicMock()
|
|
2130
|
-
mock_connection.config = {
|
|
2131
|
-
"protocol": "unix",
|
|
2132
|
-
"port": 0,
|
|
2133
|
-
"screen": 0,
|
|
2134
|
-
"uri": ":0",
|
|
2135
|
-
"host": "",
|
|
2136
|
-
}
|
|
2137
|
-
|
|
2138
|
-
d = Display(mock_connection, mock_connection.config)
|
|
2139
|
-
|
|
2140
|
-
with patch("aioxlib.auth") as mock_auth:
|
|
2141
|
-
mock_auth.return_value = {
|
|
2142
|
-
"name": b"MIT-MAGIC-COOKIE-1",
|
|
2143
|
-
"data": b"\x00" * 16,
|
|
2144
|
-
}
|
|
2145
|
-
info = await d.setup()
|
|
2146
|
-
|
|
2147
|
-
assert info is d.info
|
|
2148
|
-
assert d.resources is not None
|
|
2149
|
-
assert len(d.screens) >= 1
|
|
2150
|
-
assert isinstance(d.screens[0], Screen)
|
|
2151
|
-
assert d._task is not None
|
|
2152
|
-
# clean up background _loop task
|
|
2153
|
-
await d.aclose()
|
|
2154
|
-
rid = d.resources.allocate()
|
|
2155
|
-
expected = unpack_successfull_setup(body)[0]
|
|
2156
|
-
assert rid == expected["resource_id_base"]
|
|
2157
|
-
|
|
2158
1998
|
|
|
2159
1999
|
# ---------------------------------------------------------------------------
|
|
2160
2000
|
# Pipeline
|
|
@@ -2394,13 +2234,6 @@ class TestConnection:
|
|
|
2394
2234
|
assert await conn.read(3) == b"xyz"
|
|
2395
2235
|
reader.read.assert_awaited_with(3)
|
|
2396
2236
|
|
|
2397
|
-
@pytest.mark.asyncio
|
|
2398
|
-
async def test_readexactly(self):
|
|
2399
|
-
reader = MagicMock()
|
|
2400
|
-
reader.readexactly = AsyncMock(return_value=b"ab")
|
|
2401
|
-
conn = Connection(reader, MagicMock(), {})
|
|
2402
|
-
assert await conn.readexactly(2) == b"ab"
|
|
2403
|
-
|
|
2404
2237
|
def test_socket_and_xfamily_unix(self):
|
|
2405
2238
|
sock = MagicMock()
|
|
2406
2239
|
sock.family = socket.AF_UNIX
|
|
@@ -3112,7 +2945,6 @@ def display_full():
|
|
|
3112
2945
|
conn.write = MagicMock()
|
|
3113
2946
|
conn.flush = AsyncMock()
|
|
3114
2947
|
conn.read = AsyncMock(return_value=b"")
|
|
3115
|
-
conn.readexactly = AsyncMock()
|
|
3116
2948
|
conn.aclose = AsyncMock()
|
|
3117
2949
|
conn.close = MagicMock()
|
|
3118
2950
|
conn.config = {"protocol": "unix", "port": 0, "screen": 0, "uri": ":0", "host": ""}
|
|
@@ -3296,11 +3128,6 @@ class TestDisplayExtended:
|
|
|
3296
3128
|
display_full.connection.read = AsyncMock(return_value=b"data")
|
|
3297
3129
|
assert await display_full.read(4) == b"data"
|
|
3298
3130
|
|
|
3299
|
-
@pytest.mark.asyncio
|
|
3300
|
-
async def test_readexactly_delegates(self, display_full):
|
|
3301
|
-
display_full.connection.readexactly = AsyncMock(return_value=b"xy")
|
|
3302
|
-
assert await display_full.readexactly(2) == b"xy"
|
|
3303
|
-
|
|
3304
3131
|
@pytest.mark.asyncio
|
|
3305
3132
|
async def test_loop_dispatches_reply(self, display_full):
|
|
3306
3133
|
# Build a minimal reply packet (32 bytes, length=0)
|
|
@@ -3314,13 +3141,6 @@ class TestDisplayExtended:
|
|
|
3314
3141
|
assert fut.done()
|
|
3315
3142
|
assert fut.result() == {"ok": True}
|
|
3316
3143
|
|
|
3317
|
-
@pytest.mark.asyncio
|
|
3318
|
-
async def test_loop_dispatches_error(self, display_full):
|
|
3319
|
-
err = struct.pack("=BBHIHB21x", 0, 3, 1, 0, 0, 1) # BadWindow
|
|
3320
|
-
display_full.connection.read = AsyncMock(side_effect=[err, b""])
|
|
3321
|
-
# should not raise — just log
|
|
3322
|
-
await display_full._loop()
|
|
3323
|
-
|
|
3324
3144
|
@pytest.mark.asyncio
|
|
3325
3145
|
async def test_loop_dispatches_event_to_listeners(self, display_full):
|
|
3326
3146
|
evt = _generic_event(Event.Expose)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|