aioxlib 0.8.0__tar.gz → 0.9.0__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.8.0 → aioxlib-0.9.0}/PKG-INFO +3 -3
- {aioxlib-0.8.0 → aioxlib-0.9.0}/README.md +2 -2
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/__init__.py +1 -1
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/api.py +21 -1
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/constants.py +17 -1
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/display.py +104 -25
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/protocol.py +121 -1
- aioxlib-0.9.0/aioxlib/stream.py +145 -0
- aioxlib-0.9.0/aioxlib/widgets.py +428 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib.egg-info/PKG-INFO +3 -3
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib.egg-info/SOURCES.txt +1 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_display.py +1 -1
- aioxlib-0.8.0/aioxlib/widgets.py +0 -211
- {aioxlib-0.8.0 → aioxlib-0.9.0}/LICENSE +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/_extensions.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/authority.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/bigreq.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/connection.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/dbe.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/dri3.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/glx.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/present.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/randr.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/render.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib/shm.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib.egg-info/dependency_links.txt +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/aioxlib.egg-info/top_level.txt +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/pyproject.toml +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/setup.cfg +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_api.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_authority.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_bigreq.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_connection.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_constants.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_dbe.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_dri3.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_examples.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_extensions.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_glx.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_present.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_protocol.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_randr.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_render.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_shm.py +0 -0
- {aioxlib-0.8.0 → aioxlib-0.9.0}/tests/test_widgets.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aioxlib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Human friendly interface to XLib subsystems using asyncio python
|
|
5
5
|
Author-email: Jose Tiago Macara Coutinho <coutinhotiago@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -38,9 +38,9 @@ Open displays, create windows, draw with GCs and the RENDER extension, and strea
|
|
|
38
38
|
events — without blocking the event loop and without linking to libX11 or xcb.
|
|
39
39
|
|
|
40
40
|
<p align="center">
|
|
41
|
-
<img src="docs/images/game_of_life.gif" style="height: 300px;" alt="Conway's Game of Life on X11 via aioxlib"/>
|
|
41
|
+
<img src="docs/images/game_of_life.gif" style="height: 300px;" height="300" alt="Conway's Game of Life on X11 via aioxlib"/>
|
|
42
42
|
|
|
43
|
-
<img src="docs/images/3d_cube.gif" style="height: 300px;" alt="Rotating 3D wireframe via aioxlib"/>
|
|
43
|
+
<img src="docs/images/3d_cube.gif" style="height: 300px;" height="300" alt="Rotating 3D wireframe via aioxlib"/>
|
|
44
44
|
</p>
|
|
45
45
|
|
|
46
46
|
<p align="center"><em>Examples shipped with the library: Game of Life · 3D wireframe</em></p>
|
|
@@ -12,9 +12,9 @@ Open displays, create windows, draw with GCs and the RENDER extension, and strea
|
|
|
12
12
|
events — without blocking the event loop and without linking to libX11 or xcb.
|
|
13
13
|
|
|
14
14
|
<p align="center">
|
|
15
|
-
<img src="docs/images/game_of_life.gif" style="height: 300px;" alt="Conway's Game of Life on X11 via aioxlib"/>
|
|
15
|
+
<img src="docs/images/game_of_life.gif" style="height: 300px;" height="300" alt="Conway's Game of Life on X11 via aioxlib"/>
|
|
16
16
|
|
|
17
|
-
<img src="docs/images/3d_cube.gif" style="height: 300px;" alt="Rotating 3D wireframe via aioxlib"/>
|
|
17
|
+
<img src="docs/images/3d_cube.gif" style="height: 300px;" height="300" alt="Rotating 3D wireframe via aioxlib"/>
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
20
|
<p align="center"><em>Examples shipped with the library: Game of Life · 3D wireframe</em></p>
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
from .constants import (
|
|
10
10
|
Allocate,
|
|
11
11
|
AnyPropertyType,
|
|
12
|
+
CoordinateMode,
|
|
12
13
|
PropMode,
|
|
13
14
|
)
|
|
14
15
|
from .display import Display
|
|
@@ -307,6 +308,17 @@ async def open_font(name=None, display: Display | None = None, **kwargs):
|
|
|
307
308
|
return await d.open_font(name=name, **kwargs)
|
|
308
309
|
|
|
309
310
|
|
|
311
|
+
async def query_text_extents(font, string, display: Display | None = None):
|
|
312
|
+
"""QueryTextExtents — bounding box of *string* in *font* (or GC).
|
|
313
|
+
|
|
314
|
+
Returns a dict with draw_direction, font_ascent, font_descent,
|
|
315
|
+
overall_ascent, overall_descent, overall_width, overall_left,
|
|
316
|
+
overall_right.
|
|
317
|
+
"""
|
|
318
|
+
d = await _resolve_display(display)
|
|
319
|
+
return await d.query_text_extents(font, string)
|
|
320
|
+
|
|
321
|
+
|
|
310
322
|
async def tree(wnd, display: Display | None = None):
|
|
311
323
|
"""Query the window tree for *wnd*."""
|
|
312
324
|
d = await _resolve_display(display)
|
|
@@ -331,12 +343,18 @@ async def poly_segments(drawable, gc, segments, display: Display | None = None):
|
|
|
331
343
|
return await d.poly_segments(drawable, gc, segments)
|
|
332
344
|
|
|
333
345
|
|
|
334
|
-
async def poly_points(drawable, gc, points, mode, display: Display | None = None):
|
|
346
|
+
async def poly_points(drawable, gc, points, mode=CoordinateMode.Origin, display: Display | None = None):
|
|
335
347
|
"""PolyPoint on *drawable*."""
|
|
336
348
|
d = await _resolve_display(display)
|
|
337
349
|
return await d.poly_points(drawable, gc, points, mode)
|
|
338
350
|
|
|
339
351
|
|
|
352
|
+
async def poly_lines(drawable, gc, points, mode=CoordinateMode.Origin, display: Display | None = None):
|
|
353
|
+
"""PolyLine on *drawable*."""
|
|
354
|
+
d = await _resolve_display(display)
|
|
355
|
+
return await d.poly_lines(drawable, gc, points, mode)
|
|
356
|
+
|
|
357
|
+
|
|
340
358
|
async def clear_area(wnd, x, y, w, h, exposure=False, display: Display | None = None):
|
|
341
359
|
"""ClearArea on *wnd*."""
|
|
342
360
|
d = await _resolve_display(display)
|
|
@@ -421,11 +439,13 @@ __all__ = [
|
|
|
421
439
|
"delete_property",
|
|
422
440
|
"open_font_name",
|
|
423
441
|
"open_font",
|
|
442
|
+
"query_text_extents",
|
|
424
443
|
"tree",
|
|
425
444
|
"poly_fill_rectangles",
|
|
426
445
|
"poly_fill_arcs",
|
|
427
446
|
"poly_segments",
|
|
428
447
|
"poly_points",
|
|
448
|
+
"poly_lines",
|
|
429
449
|
"clear_area",
|
|
430
450
|
"set_input_focus",
|
|
431
451
|
"get_geometry",
|
|
@@ -282,9 +282,25 @@ class Command(enum.IntEnum):
|
|
|
282
282
|
FreeColormap = 79
|
|
283
283
|
QueryExtension = 98
|
|
284
284
|
ListExtensions = 99
|
|
285
|
-
|
|
285
|
+
ChangeKeyboardMapping = 100
|
|
286
|
+
GetKeyboardMapping = 101
|
|
287
|
+
ChangeKeyboardControl = 102
|
|
288
|
+
GetKeyboardControl = 103
|
|
289
|
+
Bell = 104
|
|
290
|
+
ChangePointerControl = 105
|
|
291
|
+
GetPointerControl = 106
|
|
286
292
|
SetScreenSaver = 107
|
|
287
293
|
GetScreenSaver = 108
|
|
294
|
+
ChangeHosts = 109
|
|
295
|
+
ListHosts = 110
|
|
296
|
+
SetAccessControl = 111
|
|
297
|
+
SetCloseDownMode = 112
|
|
298
|
+
KillClient = 113
|
|
299
|
+
RotateProperties = 114
|
|
300
|
+
ForceScreenSaver = 115
|
|
301
|
+
SetPointerMapping = 116
|
|
302
|
+
GetPointerMapping = 117
|
|
303
|
+
SetModifierMapping = 118
|
|
288
304
|
GetModifierMapping = 119
|
|
289
305
|
NoOperation = 127
|
|
290
306
|
|
|
@@ -17,6 +17,7 @@ import asyncio
|
|
|
17
17
|
import logging
|
|
18
18
|
import os
|
|
19
19
|
|
|
20
|
+
from . import protocol
|
|
20
21
|
from .connection import open_connection
|
|
21
22
|
from .constants import (
|
|
22
23
|
BYTE_ORDER_TEXT,
|
|
@@ -28,6 +29,7 @@ from .constants import (
|
|
|
28
29
|
AnyPropertyType,
|
|
29
30
|
Atom,
|
|
30
31
|
Command,
|
|
32
|
+
CoordinateMode,
|
|
31
33
|
CopyFromParent,
|
|
32
34
|
ErrorCode,
|
|
33
35
|
EventType,
|
|
@@ -59,6 +61,7 @@ from .protocol import (
|
|
|
59
61
|
pack_get_atom_name,
|
|
60
62
|
pack_get_font_path,
|
|
61
63
|
pack_get_geometry,
|
|
64
|
+
pack_get_keyboard_mapping,
|
|
62
65
|
pack_get_property,
|
|
63
66
|
pack_image,
|
|
64
67
|
pack_image_text,
|
|
@@ -72,10 +75,15 @@ from .protocol import (
|
|
|
72
75
|
pack_poly_fill_raw_arcs,
|
|
73
76
|
pack_poly_fill_raw_rectangles,
|
|
74
77
|
pack_poly_fill_rectangles,
|
|
78
|
+
pack_poly_lines,
|
|
75
79
|
pack_poly_points,
|
|
80
|
+
pack_poly_raw_lines,
|
|
81
|
+
pack_poly_raw_rectangles,
|
|
76
82
|
pack_poly_raw_segments,
|
|
83
|
+
pack_poly_rectangles,
|
|
77
84
|
pack_poly_segments,
|
|
78
85
|
pack_query_extension,
|
|
86
|
+
pack_query_text_extents,
|
|
79
87
|
pack_query_tree,
|
|
80
88
|
pack_reparent_window,
|
|
81
89
|
pack_set_font_path,
|
|
@@ -84,15 +92,16 @@ from .protocol import (
|
|
|
84
92
|
pack_unmap_window,
|
|
85
93
|
unpack_error,
|
|
86
94
|
unpack_event,
|
|
87
|
-
unpack_get_atom_name,
|
|
88
95
|
unpack_get_font_path,
|
|
89
96
|
unpack_get_geometry,
|
|
97
|
+
unpack_get_keyboard_mapping,
|
|
90
98
|
unpack_get_property,
|
|
91
99
|
unpack_intern_atom,
|
|
92
100
|
unpack_list_extensions,
|
|
93
101
|
unpack_list_fonts,
|
|
94
102
|
unpack_list_properties,
|
|
95
103
|
unpack_query_extension,
|
|
104
|
+
unpack_query_text_extents,
|
|
96
105
|
unpack_query_tree,
|
|
97
106
|
unpack_setup,
|
|
98
107
|
unpack_setup_error,
|
|
@@ -101,7 +110,11 @@ from .protocol import (
|
|
|
101
110
|
log = logging.getLogger("aioxlib")
|
|
102
111
|
|
|
103
112
|
|
|
104
|
-
|
|
113
|
+
def unpack_get_atom_name(data, offset=0):
|
|
114
|
+
reply, offset = protocol.unpack_get_atom_name(data, offset)
|
|
115
|
+
return reply["name"], offset
|
|
116
|
+
|
|
117
|
+
|
|
105
118
|
class Extension:
|
|
106
119
|
"""Base class for X protocol extensions."""
|
|
107
120
|
|
|
@@ -117,7 +130,6 @@ class Extension:
|
|
|
117
130
|
self.commands = {}
|
|
118
131
|
|
|
119
132
|
|
|
120
|
-
# --- resource ---
|
|
121
133
|
class Resource:
|
|
122
134
|
"""Base class for server-side resources identified by an XID."""
|
|
123
135
|
|
|
@@ -186,6 +198,10 @@ class Drawable(Resource):
|
|
|
186
198
|
"""Poly fill rectangles."""
|
|
187
199
|
return await self.display.poly_fill_rectangles(self, gc, rectangles)
|
|
188
200
|
|
|
201
|
+
async def poly_rectangles(self, gc, rectangles):
|
|
202
|
+
"""Poly rectangles."""
|
|
203
|
+
return await self.display.poly_rectangles(self, gc, rectangles)
|
|
204
|
+
|
|
189
205
|
async def poly_fill_arcs(self, gc, arcs):
|
|
190
206
|
"""Fill elliptical arcs on this drawable.
|
|
191
207
|
|
|
@@ -202,6 +218,10 @@ class Drawable(Resource):
|
|
|
202
218
|
"""Poly points."""
|
|
203
219
|
return await self.display.poly_points(self, gc, points, mode)
|
|
204
220
|
|
|
221
|
+
async def poly_lines(self, gc, points, mode=CoordinateMode.Origin):
|
|
222
|
+
"""Poly lines."""
|
|
223
|
+
return await self.display.poly_lines(self, gc, points, mode)
|
|
224
|
+
|
|
205
225
|
async def put_text(self, gc, x, y, text, drawable=None):
|
|
206
226
|
"""Put text."""
|
|
207
227
|
drawable = drawable or self
|
|
@@ -242,7 +262,6 @@ class ResourceManager:
|
|
|
242
262
|
return self.base | i
|
|
243
263
|
|
|
244
264
|
|
|
245
|
-
# --- gc ---
|
|
246
265
|
class GC(Resource):
|
|
247
266
|
"""Graphics context resource used by core drawing requests."""
|
|
248
267
|
|
|
@@ -256,6 +275,11 @@ class GC(Resource):
|
|
|
256
275
|
drawable = drawable or self.drawable
|
|
257
276
|
return await self.display.poly_fill_rectangles(drawable, self, rectangles)
|
|
258
277
|
|
|
278
|
+
async def poly_rectangles(self, rectangles, drawable=None):
|
|
279
|
+
"""Poly rectangles."""
|
|
280
|
+
drawable = drawable or self.drawable
|
|
281
|
+
return await self.display.poly_rectangles(drawable, self, rectangles)
|
|
282
|
+
|
|
259
283
|
async def poly_fill_arcs(self, arcs, drawable=None):
|
|
260
284
|
"""Poly fill arcs."""
|
|
261
285
|
if drawable is None:
|
|
@@ -267,11 +291,16 @@ class GC(Resource):
|
|
|
267
291
|
drawable = drawable or self.drawable
|
|
268
292
|
return await self.display.poly_segments(drawable, self, segments)
|
|
269
293
|
|
|
270
|
-
async def poly_points(self, points, mode, drawable=None):
|
|
294
|
+
async def poly_points(self, points, mode=CoordinateMode.Origin, drawable=None):
|
|
271
295
|
"""Poly points."""
|
|
272
296
|
drawable = drawable or self.drawable
|
|
273
297
|
return await self.display.poly_points(drawable, self, points, mode)
|
|
274
298
|
|
|
299
|
+
async def poly_lines(self, points, mode=CoordinateMode.Origin, drawable=None):
|
|
300
|
+
"""Poly points."""
|
|
301
|
+
drawable = drawable or self.drawable
|
|
302
|
+
return await self.display.poly_lines(drawable, self, points, mode)
|
|
303
|
+
|
|
275
304
|
async def put_text(self, x, y, text, drawable=None):
|
|
276
305
|
"""Put text."""
|
|
277
306
|
if drawable is None:
|
|
@@ -279,16 +308,22 @@ class GC(Resource):
|
|
|
279
308
|
payload = pack_image_text(drawable, self, x, y, text)
|
|
280
309
|
self.display.connection.write(payload)
|
|
281
310
|
|
|
311
|
+
async def query_text_extents(self, string, font=None):
|
|
312
|
+
return await self.display.query_text_extents(font or self, string)
|
|
313
|
+
|
|
282
314
|
|
|
283
|
-
# --- window ---
|
|
284
315
|
class Window(Drawable):
|
|
285
316
|
"""X window resource with mapping, properties, and drawing helpers."""
|
|
286
317
|
|
|
318
|
+
def __init__(self, display, wid, depth):
|
|
319
|
+
super().__init__(display, wid)
|
|
320
|
+
self.depth = depth
|
|
321
|
+
|
|
287
322
|
async def create_window(
|
|
288
323
|
self,
|
|
289
324
|
width,
|
|
290
325
|
height,
|
|
291
|
-
depth,
|
|
326
|
+
depth=None,
|
|
292
327
|
x=0,
|
|
293
328
|
y=0,
|
|
294
329
|
border_width=0,
|
|
@@ -301,6 +336,8 @@ class Window(Drawable):
|
|
|
301
336
|
) -> Window:
|
|
302
337
|
"""Create window."""
|
|
303
338
|
wid = self.display.resources.allocate()
|
|
339
|
+
if depth is None:
|
|
340
|
+
depth = self.depth
|
|
304
341
|
payload = pack_create_window(
|
|
305
342
|
depth=depth,
|
|
306
343
|
wid=wid,
|
|
@@ -326,12 +363,16 @@ class Window(Drawable):
|
|
|
326
363
|
wm_protocols = display.WM_PROTOCOLS
|
|
327
364
|
payload += pack_change_property(wid, wm_protocols, Atom.ATOM, 32, protocols, PropMode.Replace)
|
|
328
365
|
self.display.connection.write(payload)
|
|
329
|
-
return Window(self.display, wid)
|
|
366
|
+
return Window(self.display, wid, depth)
|
|
330
367
|
|
|
331
368
|
async def set_name(self, name):
|
|
332
369
|
"""Set name."""
|
|
333
370
|
await self.change_text_property(Atom.WM_NAME, name)
|
|
334
371
|
|
|
372
|
+
async def get_name(self):
|
|
373
|
+
value = await self.get_property(Atom.WM_NAME)
|
|
374
|
+
return value["value"]
|
|
375
|
+
|
|
335
376
|
async def set_motif_hints(self, functions=None, decorations=None, input_mode=None, status=None):
|
|
336
377
|
data = build_motif_wm_hints(
|
|
337
378
|
functions=functions,
|
|
@@ -601,7 +642,7 @@ class Screen:
|
|
|
601
642
|
"""Initialize a new Screen instance."""
|
|
602
643
|
self.display = display
|
|
603
644
|
self.info = info
|
|
604
|
-
self.root = Window(display, info["root"])
|
|
645
|
+
self.root = Window(display, info["root"], self.root_depth)
|
|
605
646
|
|
|
606
647
|
def __getattr__(self, name):
|
|
607
648
|
return self.info[name]
|
|
@@ -626,8 +667,6 @@ class Screen:
|
|
|
626
667
|
parent = self.root
|
|
627
668
|
if depth is None:
|
|
628
669
|
depth = self.root_depth
|
|
629
|
-
if background_pixel is None:
|
|
630
|
-
background_pixel = self.black_pixel
|
|
631
670
|
return await parent.create_window(
|
|
632
671
|
x=x,
|
|
633
672
|
y=y,
|
|
@@ -642,8 +681,11 @@ class Screen:
|
|
|
642
681
|
**attrs,
|
|
643
682
|
)
|
|
644
683
|
|
|
684
|
+
async def create_gc(self, drawable=None, **kwargs) -> GC:
|
|
685
|
+
drawable = drawable or self.root
|
|
686
|
+
return await drawable.create_gc(**kwargs)
|
|
687
|
+
|
|
645
688
|
|
|
646
|
-
# --- display ---
|
|
647
689
|
class Pipeline:
|
|
648
690
|
"""Context manager that batches write and write_read requests before flush.
|
|
649
691
|
|
|
@@ -705,7 +747,7 @@ class Display:
|
|
|
705
747
|
self._events = {}
|
|
706
748
|
self._atom_cache = {}
|
|
707
749
|
self._extensions_cache = None
|
|
708
|
-
self.
|
|
750
|
+
self._event_task = None
|
|
709
751
|
self._pipeline = None
|
|
710
752
|
|
|
711
753
|
async def __aenter__(self):
|
|
@@ -722,19 +764,19 @@ class Display:
|
|
|
722
764
|
|
|
723
765
|
def close(self):
|
|
724
766
|
"""Close."""
|
|
725
|
-
if self.
|
|
726
|
-
self.
|
|
767
|
+
if self._event_task:
|
|
768
|
+
self._event_task.cancel()
|
|
727
769
|
self.connection.close()
|
|
728
770
|
|
|
729
771
|
async def aclose(self):
|
|
730
772
|
"""Aclose."""
|
|
731
|
-
if self.
|
|
732
|
-
self.
|
|
773
|
+
if self._event_task:
|
|
774
|
+
self._event_task.cancel()
|
|
733
775
|
try:
|
|
734
|
-
await self.
|
|
776
|
+
await self._event_task
|
|
735
777
|
except asyncio.CancelledError:
|
|
736
778
|
pass
|
|
737
|
-
self.
|
|
779
|
+
self._event_task = None
|
|
738
780
|
await self.connection.aclose()
|
|
739
781
|
|
|
740
782
|
def pipeline(self):
|
|
@@ -785,8 +827,9 @@ class Display:
|
|
|
785
827
|
|
|
786
828
|
async def setup(self):
|
|
787
829
|
"""Perform the X11 connection handshake and populate screens."""
|
|
788
|
-
if self.
|
|
789
|
-
self.
|
|
830
|
+
if self._event_task is None:
|
|
831
|
+
self._event_queue = asyncio.Queue()
|
|
832
|
+
self._event_task = asyncio.create_task(self._loop())
|
|
790
833
|
entry = self.connection.authority()
|
|
791
834
|
payload = pack_setup(entry["name"], entry["data"])
|
|
792
835
|
info = await self.write_read(payload, unpack_setup)
|
|
@@ -801,11 +844,10 @@ class Display:
|
|
|
801
844
|
reply = await self.write_read(payload, unpack_intern_atom)
|
|
802
845
|
return reply["atom"]
|
|
803
846
|
|
|
804
|
-
|
|
847
|
+
def get_atom_name(self, atom):
|
|
805
848
|
"""Return the string name for *atom*."""
|
|
806
849
|
payload = pack_get_atom_name(atom)
|
|
807
|
-
|
|
808
|
-
return reply["name"]
|
|
850
|
+
return self.write_read(payload, unpack_get_atom_name)
|
|
809
851
|
|
|
810
852
|
async def _list_extensions(self):
|
|
811
853
|
payload = pack_list_extensions()
|
|
@@ -955,6 +997,16 @@ class Display:
|
|
|
955
997
|
font = build_font(**kwargs) if name is None else name
|
|
956
998
|
return await self.open_font_name(font)
|
|
957
999
|
|
|
1000
|
+
async def query_text_extents(self, font, string):
|
|
1001
|
+
"""QueryTextExtents — bounding box of *string* in *font* (or GC).
|
|
1002
|
+
|
|
1003
|
+
Returns a dict with draw_direction, font_ascent, font_descent,
|
|
1004
|
+
overall_ascent, overall_descent, overall_width, overall_left,
|
|
1005
|
+
overall_right.
|
|
1006
|
+
"""
|
|
1007
|
+
payload = pack_query_text_extents(font, string)
|
|
1008
|
+
return await self.write_read(payload, unpack_query_text_extents)
|
|
1009
|
+
|
|
958
1010
|
async def tree(self, wnd):
|
|
959
1011
|
"""Tree."""
|
|
960
1012
|
payload = pack_query_tree(wnd)
|
|
@@ -969,6 +1021,14 @@ class Display:
|
|
|
969
1021
|
payload = pack_poly_fill_rectangles(drawable, gc, rectangles)
|
|
970
1022
|
self.write(payload)
|
|
971
1023
|
|
|
1024
|
+
async def poly_rectangles(self, drawable, gc, rectangles):
|
|
1025
|
+
"""Poly rectangles."""
|
|
1026
|
+
if isinstance(rectangles, bytes | bytearray):
|
|
1027
|
+
payload = pack_poly_raw_rectangles(drawable, gc, rectangles)
|
|
1028
|
+
else:
|
|
1029
|
+
payload = pack_poly_rectangles(drawable, gc, rectangles)
|
|
1030
|
+
self.write(payload)
|
|
1031
|
+
|
|
972
1032
|
async def poly_fill_arcs(self, drawable, gc, arcs):
|
|
973
1033
|
"""PolyFillArc — fill elliptical arcs (or full ellipses).
|
|
974
1034
|
|
|
@@ -997,6 +1057,14 @@ class Display:
|
|
|
997
1057
|
payload = pack_poly_points(drawable, gc, points, mode)
|
|
998
1058
|
self.write(payload)
|
|
999
1059
|
|
|
1060
|
+
async def poly_lines(self, drawable, gc, points, mode=CoordinateMode.Origin):
|
|
1061
|
+
"""Poly lines."""
|
|
1062
|
+
if isinstance(points, bytes):
|
|
1063
|
+
payload = pack_poly_raw_lines(drawable, gc, points, mode)
|
|
1064
|
+
else:
|
|
1065
|
+
payload = pack_poly_lines(drawable, gc, points, mode)
|
|
1066
|
+
self.write(payload)
|
|
1067
|
+
|
|
1000
1068
|
async def clear_area(self, wnd, x, y, w, h, exposure=False):
|
|
1001
1069
|
"""Clear area."""
|
|
1002
1070
|
payload = pack_clear_area(wnd, x, y, w, h, exposure)
|
|
@@ -1046,6 +1114,12 @@ class Display:
|
|
|
1046
1114
|
payload = pack_reparent_window(window, parent, x, y)
|
|
1047
1115
|
self.write(payload)
|
|
1048
1116
|
|
|
1117
|
+
async def get_keyboard_mapping(self, start=None, count=None):
|
|
1118
|
+
start = start or self.info["min_keycode"]
|
|
1119
|
+
count = count or (self.info["max_keycode"] - start)
|
|
1120
|
+
payload = pack_get_keyboard_mapping(start, count)
|
|
1121
|
+
return await self.write_read(payload, unpack_get_keyboard_mapping)
|
|
1122
|
+
|
|
1049
1123
|
def add_listener(self, listener):
|
|
1050
1124
|
"""Add listener."""
|
|
1051
1125
|
self.listeners.add(listener)
|
|
@@ -1116,7 +1190,12 @@ class Display:
|
|
|
1116
1190
|
# An event
|
|
1117
1191
|
if len(buffer) < 32:
|
|
1118
1192
|
return True, buffer, current_decoder
|
|
1119
|
-
|
|
1193
|
+
try:
|
|
1194
|
+
event, offset = unpack_event(self._events, buffer)
|
|
1195
|
+
except ValueError:
|
|
1196
|
+
log.exception("discarded buffer: %s", buffer)
|
|
1197
|
+
return False, b"", current_decoder
|
|
1198
|
+
|
|
1120
1199
|
for listener in self.listeners:
|
|
1121
1200
|
await listener.put(event)
|
|
1122
1201
|
return False, buffer[offset:], current_decoder
|
|
@@ -26,6 +26,7 @@ from .constants import (
|
|
|
26
26
|
Atom,
|
|
27
27
|
Command,
|
|
28
28
|
ConfigureWindowAttributes,
|
|
29
|
+
CoordinateMode,
|
|
29
30
|
CreateWindowAttributes,
|
|
30
31
|
EventType,
|
|
31
32
|
InitialState,
|
|
@@ -444,6 +445,15 @@ def pack_poly_fill_rectangles(drawable, gc, rectangles) -> bytes:
|
|
|
444
445
|
return struct.pack(fmt, Command.PolyFillRectangle, length, drawable, gc, *coords)
|
|
445
446
|
|
|
446
447
|
|
|
448
|
+
def pack_poly_rectangles(drawable, gc, rectangles) -> bytes:
|
|
449
|
+
"""Pack a wire-protocol poly rectangles request into ``bytes``."""
|
|
450
|
+
n = len(rectangles)
|
|
451
|
+
length = 3 + 2 * n
|
|
452
|
+
fmt = f"BxHII{4 * n}h"
|
|
453
|
+
coords = (c for rect in rectangles for c in rect)
|
|
454
|
+
return struct.pack(fmt, Command.PolyRectangle, length, drawable, gc, *coords)
|
|
455
|
+
|
|
456
|
+
|
|
447
457
|
def pack_poly_fill_arcs(drawable, gc, arcs) -> bytes:
|
|
448
458
|
"""PolyFillArc: fill elliptical arcs (or full ellipses).
|
|
449
459
|
|
|
@@ -481,6 +491,15 @@ def pack_poly_fill_raw_rectangles(drawable, gc, rectangles: bytes) -> bytes:
|
|
|
481
491
|
return struct.pack(fmt, Command.PolyFillRectangle, length, drawable, gc, rectangles)
|
|
482
492
|
|
|
483
493
|
|
|
494
|
+
def pack_poly_raw_rectangles(drawable, gc, rectangles: bytes) -> bytes:
|
|
495
|
+
"""Pack a wire-protocol poly fill raw rectangles request into ``bytes``."""
|
|
496
|
+
size = len(rectangles)
|
|
497
|
+
n = size // 8
|
|
498
|
+
length = 3 + 2 * n
|
|
499
|
+
fmt = f"BxHII{size}s"
|
|
500
|
+
return struct.pack(fmt, Command.PolyRectangle, length, drawable, gc, rectangles)
|
|
501
|
+
|
|
502
|
+
|
|
484
503
|
def pack_poly_segments(drawable, gc, segments):
|
|
485
504
|
"""Pack a wire-protocol poly segments request into ``bytes``."""
|
|
486
505
|
n = len(segments)
|
|
@@ -499,7 +518,7 @@ def pack_poly_raw_segments(drawable, gc, segments: bytes) -> bytes:
|
|
|
499
518
|
return struct.pack(fmt, Command.PolySegment, length, drawable, gc, segments)
|
|
500
519
|
|
|
501
520
|
|
|
502
|
-
def pack_poly_points(drawable, gc, points, mode):
|
|
521
|
+
def pack_poly_points(drawable, gc, points, mode=CoordinateMode.Origin):
|
|
503
522
|
"""Pack a wire-protocol poly points request into ``bytes``."""
|
|
504
523
|
n = len(points)
|
|
505
524
|
length = 3 + n
|
|
@@ -508,6 +527,22 @@ def pack_poly_points(drawable, gc, points, mode):
|
|
|
508
527
|
return struct.pack(fmt, Command.PolyPoint, mode, length, drawable, gc, *coords)
|
|
509
528
|
|
|
510
529
|
|
|
530
|
+
def pack_poly_lines(drawable, gc, points, mode=CoordinateMode.Origin):
|
|
531
|
+
n = len(points)
|
|
532
|
+
length = 3 + n
|
|
533
|
+
fmt = f"BBHII{2 * n}h"
|
|
534
|
+
coords = (coord for point in points for coord in point)
|
|
535
|
+
return struct.pack(fmt, Command.PolyLine, mode, length, drawable, gc, *coords)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def pack_poly_raw_lines(drawable, gc, points, mode=CoordinateMode.Origin):
|
|
539
|
+
size = len(points)
|
|
540
|
+
n = size // 4
|
|
541
|
+
length = 3 + n
|
|
542
|
+
fmt = f"BBHII{size}s"
|
|
543
|
+
return struct.pack(fmt, Command.PolyLine, mode, length, drawable, gc, points)
|
|
544
|
+
|
|
545
|
+
|
|
511
546
|
def pack_open_font(fid, name, encoding=DEFAULT_STRING_ENCODING):
|
|
512
547
|
"""Pack a wire-protocol open font request into ``bytes``."""
|
|
513
548
|
name = name.encode(encoding)
|
|
@@ -530,6 +565,77 @@ def pack_close_font(fid):
|
|
|
530
565
|
return struct.pack("BxHI", Command.CloseFont, 8 // 4, fid)
|
|
531
566
|
|
|
532
567
|
|
|
568
|
+
def pack_query_text_extents(font, string, encoding=DEFAULT_STRING_ENCODING):
|
|
569
|
+
"""Pack a QueryTextExtents request.
|
|
570
|
+
|
|
571
|
+
*font* is a Font or GContext id (Fontable).
|
|
572
|
+
*string* is a str or bytes (8-bit characters). Each character is sent as
|
|
573
|
+
a CHAR2B with the high byte zero, matching the classic Xlib behaviour of
|
|
574
|
+
XQueryTextExtents for Latin-1 / ASCII text.
|
|
575
|
+
"""
|
|
576
|
+
if isinstance(string, str):
|
|
577
|
+
string = string.encode(encoding)
|
|
578
|
+
nchars = len(string)
|
|
579
|
+
# 2 bytes per character (CHAR2B)
|
|
580
|
+
nbytes = nchars * 2
|
|
581
|
+
p = pad(nbytes)
|
|
582
|
+
# request length in 4-byte units: header (2) + string(+pad)
|
|
583
|
+
length = 2 + (nbytes + p) // 4
|
|
584
|
+
odd = nchars & 1
|
|
585
|
+
# Build CHAR2B list: for each byte c -> (0, c)
|
|
586
|
+
chars = bytearray()
|
|
587
|
+
for c in string:
|
|
588
|
+
chars.append(0)
|
|
589
|
+
chars.append(c)
|
|
590
|
+
chars.extend(b"\x00" * p)
|
|
591
|
+
return struct.pack(
|
|
592
|
+
f"BBHI{nbytes + p}s",
|
|
593
|
+
Command.QueryTextExtents,
|
|
594
|
+
odd,
|
|
595
|
+
length,
|
|
596
|
+
int(font),
|
|
597
|
+
bytes(chars),
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def unpack_query_text_extents(data, offset=0):
|
|
602
|
+
"""Unpack a QueryTextExtents reply.
|
|
603
|
+
|
|
604
|
+
Returns a dict with:
|
|
605
|
+
draw_direction, font_ascent, font_descent,
|
|
606
|
+
overall_ascent, overall_descent, overall_width,
|
|
607
|
+
overall_left, overall_right
|
|
608
|
+
"""
|
|
609
|
+
result, offset = unpack_reply_head(data, offset)
|
|
610
|
+
assert result["reply"] == 1
|
|
611
|
+
# after the 8-byte reply head:
|
|
612
|
+
# BYTE draw_direction was already consumed as result["n"]
|
|
613
|
+
result["draw_direction"] = result.pop("n")
|
|
614
|
+
fmt = "hhhhiii4x"
|
|
615
|
+
(
|
|
616
|
+
font_ascent,
|
|
617
|
+
font_descent,
|
|
618
|
+
overall_ascent,
|
|
619
|
+
overall_descent,
|
|
620
|
+
overall_width,
|
|
621
|
+
overall_left,
|
|
622
|
+
overall_right,
|
|
623
|
+
) = struct.unpack_from(fmt, data, offset)
|
|
624
|
+
offset += struct.calcsize(fmt)
|
|
625
|
+
result.update(
|
|
626
|
+
{
|
|
627
|
+
"font_ascent": font_ascent,
|
|
628
|
+
"font_descent": font_descent,
|
|
629
|
+
"overall_ascent": overall_ascent,
|
|
630
|
+
"overall_descent": overall_descent,
|
|
631
|
+
"overall_width": overall_width,
|
|
632
|
+
"overall_left": overall_left,
|
|
633
|
+
"overall_right": overall_right,
|
|
634
|
+
}
|
|
635
|
+
)
|
|
636
|
+
return result, offset
|
|
637
|
+
|
|
638
|
+
|
|
533
639
|
def unpack_reply_head(data: bytes, offset: int = 0) -> tuple[dict, int]:
|
|
534
640
|
"""Unpack a reply head reply or event from *data* starting at *offset*."""
|
|
535
641
|
fmt = "BBHI"
|
|
@@ -1573,6 +1679,20 @@ def pack_reparent_window(window, parent, x, y) -> bytes:
|
|
|
1573
1679
|
)
|
|
1574
1680
|
|
|
1575
1681
|
|
|
1682
|
+
def pack_get_keyboard_mapping(start, count):
|
|
1683
|
+
return struct.pack("BxHBB2x", Command.GetKeyboardMapping, 2, start, count)
|
|
1684
|
+
|
|
1685
|
+
|
|
1686
|
+
def unpack_get_keyboard_mapping(data, offset=0):
|
|
1687
|
+
result, offset = unpack_reply_head(data, offset)
|
|
1688
|
+
offset += 24
|
|
1689
|
+
n = result["n"]
|
|
1690
|
+
nm = result["length"]
|
|
1691
|
+
values = struct.unpack_from(f"{nm}I", data, offset)
|
|
1692
|
+
result["values"] = list(zip(*[iter(values)] * n, strict=True))
|
|
1693
|
+
return result, offset + 4 * nm
|
|
1694
|
+
|
|
1695
|
+
|
|
1576
1696
|
def _gl_pad4(n: int) -> int:
|
|
1577
1697
|
return (4 - (n & 3)) & 3
|
|
1578
1698
|
|