aioxlib 0.3.0__tar.gz → 0.5.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.3.0 → aioxlib-0.5.0}/PKG-INFO +9 -3
- {aioxlib-0.3.0 → aioxlib-0.5.0}/README.md +8 -2
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/__init__.py +4 -7
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/_extensions.py +2 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/api.py +8 -2
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/connection.py +0 -4
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/display.py +97 -41
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/glx.py +96 -22
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/protocol.py +84 -6
- aioxlib-0.5.0/aioxlib/randr.py +1350 -0
- aioxlib-0.5.0/aioxlib/widgets.py +211 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib.egg-info/PKG-INFO +9 -3
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib.egg-info/SOURCES.txt +2 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_connection.py +0 -16
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_glx.py +4 -4
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/authority.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/bigreq.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/constants.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/dbe.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/dri3.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/present.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/render.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/shm.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib.egg-info/dependency_links.txt +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib.egg-info/top_level.txt +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/pyproject.toml +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/setup.cfg +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_api.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_authority.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_bigreq.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_constants.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_dbe.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_display.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_dri3.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_examples.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_present.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_protocol.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_render.py +0 -0
- {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_shm.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aioxlib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -36,10 +36,12 @@ asyncio—without blocking the event loop and without linking to libX11.
|
|
|
36
36
|
## Documentation
|
|
37
37
|
|
|
38
38
|
- Online: [aioxlib.readthedocs.io](https://aioxlib.readthedocs.io)
|
|
39
|
-
- Local: `pip install
|
|
39
|
+
- Local: `pip install --group docs && mkdocs serve`
|
|
40
40
|
|
|
41
41
|
## Installation
|
|
42
42
|
|
|
43
|
+
From within your favorite python environment:
|
|
44
|
+
|
|
43
45
|
```bash
|
|
44
46
|
pip install aioxlib
|
|
45
47
|
```
|
|
@@ -52,6 +54,7 @@ The **root package** exposes the functional API and `get_display` only:
|
|
|
52
54
|
import asyncio
|
|
53
55
|
import aioxlib
|
|
54
56
|
|
|
57
|
+
|
|
55
58
|
async def main():
|
|
56
59
|
wnd = await aioxlib.create_window(640, 480, title="Hello from aioxlib")
|
|
57
60
|
await wnd.show()
|
|
@@ -60,6 +63,7 @@ async def main():
|
|
|
60
63
|
if wnd.is_delete_window(event):
|
|
61
64
|
break
|
|
62
65
|
|
|
66
|
+
|
|
63
67
|
asyncio.run(main())
|
|
64
68
|
```
|
|
65
69
|
|
|
@@ -69,6 +73,7 @@ asyncio.run(main())
|
|
|
69
73
|
import asyncio
|
|
70
74
|
import aioxlib
|
|
71
75
|
|
|
76
|
+
|
|
72
77
|
async def main():
|
|
73
78
|
display = await aioxlib.get_display()
|
|
74
79
|
async with display:
|
|
@@ -80,6 +85,7 @@ async def main():
|
|
|
80
85
|
if wnd.is_delete_window(event):
|
|
81
86
|
break
|
|
82
87
|
|
|
88
|
+
|
|
83
89
|
asyncio.run(main())
|
|
84
90
|
```
|
|
85
91
|
|
|
@@ -134,5 +140,5 @@ GPLv3 or later — see [LICENSE](LICENSE).
|
|
|
134
140
|
|
|
135
141
|
## Author
|
|
136
142
|
|
|
137
|
-
José Tiago Macara Coutinho
|
|
143
|
+
José Tiago Macara Coutinho
|
|
138
144
|
<https://codeberg.org/tiagocoutinho>
|
|
@@ -12,10 +12,12 @@ asyncio—without blocking the event loop and without linking to libX11.
|
|
|
12
12
|
## Documentation
|
|
13
13
|
|
|
14
14
|
- Online: [aioxlib.readthedocs.io](https://aioxlib.readthedocs.io)
|
|
15
|
-
- Local: `pip install
|
|
15
|
+
- Local: `pip install --group docs && mkdocs serve`
|
|
16
16
|
|
|
17
17
|
## Installation
|
|
18
18
|
|
|
19
|
+
From within your favorite python environment:
|
|
20
|
+
|
|
19
21
|
```bash
|
|
20
22
|
pip install aioxlib
|
|
21
23
|
```
|
|
@@ -28,6 +30,7 @@ The **root package** exposes the functional API and `get_display` only:
|
|
|
28
30
|
import asyncio
|
|
29
31
|
import aioxlib
|
|
30
32
|
|
|
33
|
+
|
|
31
34
|
async def main():
|
|
32
35
|
wnd = await aioxlib.create_window(640, 480, title="Hello from aioxlib")
|
|
33
36
|
await wnd.show()
|
|
@@ -36,6 +39,7 @@ async def main():
|
|
|
36
39
|
if wnd.is_delete_window(event):
|
|
37
40
|
break
|
|
38
41
|
|
|
42
|
+
|
|
39
43
|
asyncio.run(main())
|
|
40
44
|
```
|
|
41
45
|
|
|
@@ -45,6 +49,7 @@ asyncio.run(main())
|
|
|
45
49
|
import asyncio
|
|
46
50
|
import aioxlib
|
|
47
51
|
|
|
52
|
+
|
|
48
53
|
async def main():
|
|
49
54
|
display = await aioxlib.get_display()
|
|
50
55
|
async with display:
|
|
@@ -56,6 +61,7 @@ async def main():
|
|
|
56
61
|
if wnd.is_delete_window(event):
|
|
57
62
|
break
|
|
58
63
|
|
|
64
|
+
|
|
59
65
|
asyncio.run(main())
|
|
60
66
|
```
|
|
61
67
|
|
|
@@ -110,5 +116,5 @@ GPLv3 or later — see [LICENSE](LICENSE).
|
|
|
110
116
|
|
|
111
117
|
## Author
|
|
112
118
|
|
|
113
|
-
José Tiago Macara Coutinho
|
|
119
|
+
José Tiago Macara Coutinho
|
|
114
120
|
<https://codeberg.org/tiagocoutinho>
|
|
@@ -16,19 +16,16 @@ Types and protocol helpers live in submodules::
|
|
|
16
16
|
from aioxlib.protocol import pack_create_window
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
__version__ = "0.
|
|
19
|
+
__version__ = "0.5.0"
|
|
20
20
|
|
|
21
|
+
from . import api, constants
|
|
21
22
|
from .api import * # noqa: F403
|
|
22
|
-
from .api import __all__ as _api_all
|
|
23
23
|
from .constants import * # noqa: F403
|
|
24
|
-
from .constants import __all__ as _constants_all
|
|
25
24
|
from .display import get_display
|
|
26
25
|
|
|
27
26
|
__all__ = [
|
|
28
27
|
"__version__",
|
|
29
28
|
"get_display",
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
*constants.__all__,
|
|
30
|
+
*api.__all__,
|
|
32
31
|
]
|
|
33
|
-
|
|
34
|
-
del _api_all, _constants_all
|
|
@@ -11,6 +11,7 @@ from .dbe import DoubleBuffer
|
|
|
11
11
|
from .dri3 import DRI3
|
|
12
12
|
from .glx import GLX
|
|
13
13
|
from .present import Present
|
|
14
|
+
from .randr import RandR
|
|
14
15
|
from .render import Render
|
|
15
16
|
from .shm import SharedMemory
|
|
16
17
|
|
|
@@ -22,4 +23,5 @@ EXTENSIONS = {
|
|
|
22
23
|
"DRI3": DRI3,
|
|
23
24
|
"Present": Present,
|
|
24
25
|
"RENDER": Render,
|
|
26
|
+
"RANDR": RandR,
|
|
25
27
|
}
|
|
@@ -207,6 +207,11 @@ async def get_font_path(display: Display | None = None):
|
|
|
207
207
|
return await d.get_font_path()
|
|
208
208
|
|
|
209
209
|
|
|
210
|
+
async def set_font_path(path, display: Display | None = None):
|
|
211
|
+
d = await _resolve_display(display)
|
|
212
|
+
return await d.set_font_path(path)
|
|
213
|
+
|
|
214
|
+
|
|
210
215
|
async def get_atom(name, only_if_exists=False, display: Display | None = None):
|
|
211
216
|
"""Intern *name* (with cache) on the default (or given) display."""
|
|
212
217
|
d = await _resolve_display(display)
|
|
@@ -296,10 +301,10 @@ async def open_font_name(name, display: Display | None = None):
|
|
|
296
301
|
return await d.open_font_name(name)
|
|
297
302
|
|
|
298
303
|
|
|
299
|
-
async def open_font(display: Display | None = None, **kwargs):
|
|
304
|
+
async def open_font(name=None, display: Display | None = None, **kwargs):
|
|
300
305
|
"""Open a font built from keyword XLFD fields."""
|
|
301
306
|
d = await _resolve_display(display)
|
|
302
|
-
return await d.open_font(**kwargs)
|
|
307
|
+
return await d.open_font(name=name, **kwargs)
|
|
303
308
|
|
|
304
309
|
|
|
305
310
|
async def tree(wnd, display: Display | None = None):
|
|
@@ -402,6 +407,7 @@ __all__ = [
|
|
|
402
407
|
"query_extension",
|
|
403
408
|
"list_fonts",
|
|
404
409
|
"get_font_path",
|
|
410
|
+
"set_font_path",
|
|
405
411
|
"get_atom",
|
|
406
412
|
"default_screen",
|
|
407
413
|
"create_window",
|
|
@@ -41,12 +41,15 @@ from .protocol import (
|
|
|
41
41
|
pack_change_property,
|
|
42
42
|
pack_clear_area,
|
|
43
43
|
pack_configure_window,
|
|
44
|
+
pack_copy_area,
|
|
44
45
|
pack_create_colormap,
|
|
45
46
|
pack_create_gc,
|
|
47
|
+
pack_create_pixmap,
|
|
46
48
|
pack_create_window,
|
|
47
49
|
pack_delete_property,
|
|
48
50
|
pack_destroy_window,
|
|
49
51
|
pack_free_colormap,
|
|
52
|
+
pack_free_pixmap,
|
|
50
53
|
pack_get_atom_name,
|
|
51
54
|
pack_get_font_path,
|
|
52
55
|
pack_get_geometry,
|
|
@@ -69,12 +72,14 @@ from .protocol import (
|
|
|
69
72
|
pack_query_extension,
|
|
70
73
|
pack_query_tree,
|
|
71
74
|
pack_reparent_window,
|
|
75
|
+
pack_set_font_path,
|
|
72
76
|
pack_set_input_focus,
|
|
73
77
|
pack_setup,
|
|
74
78
|
pack_unmap_window,
|
|
75
79
|
unpack_client_message,
|
|
76
80
|
unpack_error,
|
|
77
81
|
unpack_event,
|
|
82
|
+
unpack_expose_event,
|
|
78
83
|
unpack_get_atom_name,
|
|
79
84
|
unpack_get_font_path,
|
|
80
85
|
unpack_get_geometry,
|
|
@@ -155,6 +160,7 @@ class Drawable(Resource):
|
|
|
155
160
|
|
|
156
161
|
async def draw_image(self, gc, x, y, w, h, depth, data, drawable=None):
|
|
157
162
|
"""Draw image."""
|
|
163
|
+
drawable = drawable or self
|
|
158
164
|
max_size = self.display.info["max_request_length"]
|
|
159
165
|
nb_pixels = w * h
|
|
160
166
|
n = len(data)
|
|
@@ -194,8 +200,7 @@ class Drawable(Resource):
|
|
|
194
200
|
|
|
195
201
|
async def put_text(self, gc, x, y, text, drawable=None):
|
|
196
202
|
"""Put text."""
|
|
197
|
-
|
|
198
|
-
drawable = self
|
|
203
|
+
drawable = drawable or self
|
|
199
204
|
payload = pack_image_text(drawable, gc, x, y, text)
|
|
200
205
|
self.display.connection.write(payload)
|
|
201
206
|
|
|
@@ -203,6 +208,11 @@ class Drawable(Resource):
|
|
|
203
208
|
"""Get geometry."""
|
|
204
209
|
return await self.display.get_geometry(self)
|
|
205
210
|
|
|
211
|
+
async def create_pixmap(self, width, height, depth=None, drawable=None):
|
|
212
|
+
"""Create a pixmap. *drawable* defaults to the root window."""
|
|
213
|
+
drawable = drawable or self
|
|
214
|
+
return await self.display.create_pixmap(width, height, depth, drawable)
|
|
215
|
+
|
|
206
216
|
|
|
207
217
|
class ResourceManager:
|
|
208
218
|
"""Allocates resource IDs from the server base and mask."""
|
|
@@ -284,7 +294,7 @@ class Window(Drawable):
|
|
|
284
294
|
delete_on_close=False,
|
|
285
295
|
take_focus=False,
|
|
286
296
|
**attrs,
|
|
287
|
-
):
|
|
297
|
+
) -> Window:
|
|
288
298
|
"""Create window."""
|
|
289
299
|
wid = self.display.resources.allocate()
|
|
290
300
|
payload = pack_create_window(
|
|
@@ -482,7 +492,7 @@ class Screen:
|
|
|
482
492
|
background_pixel=None,
|
|
483
493
|
delete_on_close=True,
|
|
484
494
|
**attrs,
|
|
485
|
-
):
|
|
495
|
+
) -> Window:
|
|
486
496
|
"""Create window."""
|
|
487
497
|
if parent is None:
|
|
488
498
|
parent = self.root
|
|
@@ -538,7 +548,10 @@ class Display:
|
|
|
538
548
|
self.commands = {cmd: cmd.name for cmd in Command}
|
|
539
549
|
self.requests = asyncio.Queue()
|
|
540
550
|
self.listeners = set()
|
|
541
|
-
self._events = {
|
|
551
|
+
self._events = {
|
|
552
|
+
EventType.ClientMessage: unpack_client_message,
|
|
553
|
+
EventType.Expose: unpack_expose_event,
|
|
554
|
+
}
|
|
542
555
|
self._atom_cache = {}
|
|
543
556
|
self._extensions_cache = None
|
|
544
557
|
self._task = None
|
|
@@ -670,6 +683,16 @@ class Display:
|
|
|
670
683
|
reply = await self.write_read(payload, unpack_get_font_path)
|
|
671
684
|
return reply["path"]
|
|
672
685
|
|
|
686
|
+
async def set_font_path(self, paths=()):
|
|
687
|
+
"""Set the font search path.
|
|
688
|
+
|
|
689
|
+
*paths* is a sequence of directory strings. Pass an empty sequence
|
|
690
|
+
(or call with no arguments) to restore the server default path.
|
|
691
|
+
This is a write-only request (no reply).
|
|
692
|
+
"""
|
|
693
|
+
payload = pack_set_font_path(paths)
|
|
694
|
+
self.write(payload)
|
|
695
|
+
|
|
673
696
|
async def get_atom(self, name, only_if_exists=False):
|
|
674
697
|
"""Intern *name* and cache the atom id."""
|
|
675
698
|
if name in self._atom_cache:
|
|
@@ -759,9 +782,9 @@ class Display:
|
|
|
759
782
|
self.write(payload)
|
|
760
783
|
return fid
|
|
761
784
|
|
|
762
|
-
async def open_font(self, **kwargs):
|
|
785
|
+
async def open_font(self, name=None, **kwargs):
|
|
763
786
|
"""Open font."""
|
|
764
|
-
font = build_font(**kwargs)
|
|
787
|
+
font = build_font(**kwargs) if name is None else name
|
|
765
788
|
return await self.open_font_name(font)
|
|
766
789
|
|
|
767
790
|
async def tree(self, wnd):
|
|
@@ -772,7 +795,7 @@ class Display:
|
|
|
772
795
|
|
|
773
796
|
async def poly_fill_rectangles(self, drawable, gc, rectangles):
|
|
774
797
|
"""Poly fill rectangles."""
|
|
775
|
-
if isinstance(rectangles,
|
|
798
|
+
if isinstance(rectangles, bytes | bytearray):
|
|
776
799
|
payload = pack_poly_fill_raw_rectangles(drawable, gc, rectangles)
|
|
777
800
|
else:
|
|
778
801
|
payload = pack_poly_fill_rectangles(drawable, gc, rectangles)
|
|
@@ -784,7 +807,7 @@ class Display:
|
|
|
784
807
|
*arcs* is a sequence of ``(x, y, width, height, angle1, angle2)``
|
|
785
808
|
with angles in 1/64 degree units, or a pre-packed ``bytes`` LISTofARC.
|
|
786
809
|
"""
|
|
787
|
-
if isinstance(arcs,
|
|
810
|
+
if isinstance(arcs, bytes | bytearray):
|
|
788
811
|
payload = pack_poly_fill_raw_arcs(drawable, gc, arcs)
|
|
789
812
|
else:
|
|
790
813
|
payload = pack_poly_fill_arcs(drawable, gc, arcs)
|
|
@@ -811,6 +834,24 @@ class Display:
|
|
|
811
834
|
payload = pack_clear_area(wnd, x, y, w, h, exposure)
|
|
812
835
|
self.write(payload)
|
|
813
836
|
|
|
837
|
+
async def copy_area(self, src, dst, gc, src_x, src_y, width, height, dst_x=0, dst_y=0):
|
|
838
|
+
"""Copy a rectangle from *src* to *dst*."""
|
|
839
|
+
self.write(pack_copy_area(src, dst, gc, src_x, src_y, width, height, dst_x, dst_y))
|
|
840
|
+
|
|
841
|
+
async def create_pixmap(self, width, height, depth=None, drawable=None):
|
|
842
|
+
"""Create a pixmap. *drawable* defaults to the root window."""
|
|
843
|
+
if drawable is None:
|
|
844
|
+
drawable = self.default_screen().root
|
|
845
|
+
if depth is None:
|
|
846
|
+
depth = self.default_screen().root_depth
|
|
847
|
+
pid = self.resources.allocate()
|
|
848
|
+
self.write(pack_create_pixmap(pid, drawable, width, height, depth))
|
|
849
|
+
return pid
|
|
850
|
+
|
|
851
|
+
async def free_pixmap(self, pixmap):
|
|
852
|
+
"""Free a pixmap."""
|
|
853
|
+
self.write(pack_free_pixmap(pixmap))
|
|
854
|
+
|
|
814
855
|
async def set_input_focus(self, focus, revert_to=1, time=0):
|
|
815
856
|
"""Set keyboard input focus to *focus* (window id or resource)."""
|
|
816
857
|
self.write(pack_set_input_focus(focus, revert_to=revert_to, time=time))
|
|
@@ -871,42 +912,57 @@ class Display:
|
|
|
871
912
|
break
|
|
872
913
|
return error
|
|
873
914
|
|
|
915
|
+
async def _handle_buffer(self, buffer, current_decoder=None):
|
|
916
|
+
packet_type = buffer[0]
|
|
917
|
+
if packet_type == Status.Success:
|
|
918
|
+
if current_decoder is None:
|
|
919
|
+
current_decoder = await self.requests.get()
|
|
920
|
+
decode, future = current_decoder
|
|
921
|
+
if decode == unpack_setup:
|
|
922
|
+
size = 8 + 4 * int.from_bytes(buffer[6:8], BYTE_ORDER_TEXT)
|
|
923
|
+
else:
|
|
924
|
+
size = 32 + 4 * int.from_bytes(buffer[4:8], BYTE_ORDER_TEXT)
|
|
925
|
+
if len(buffer) < size:
|
|
926
|
+
return True, buffer, current_decoder
|
|
927
|
+
try:
|
|
928
|
+
reply, offset = decode(buffer, 0)
|
|
929
|
+
future.set_result(reply)
|
|
930
|
+
except Exception as error:
|
|
931
|
+
future.set_exception(error)
|
|
932
|
+
return False, buffer[offset:], None
|
|
933
|
+
elif packet_type == Status.Failed and not self.requests.empty():
|
|
934
|
+
decode, future = await self.requests.get()
|
|
935
|
+
if decode == unpack_setup:
|
|
936
|
+
# todo: check if buffer is complete
|
|
937
|
+
error, offset = unpack_setup_error(buffer)
|
|
938
|
+
future.set_exception(RuntimeError(error))
|
|
939
|
+
else:
|
|
940
|
+
if len(buffer) < 32:
|
|
941
|
+
return True, buffer, current_decoder
|
|
942
|
+
error, offset = unpack_error(buffer)
|
|
943
|
+
error = self._handle_error(error)
|
|
944
|
+
log.warning("ERROR: %s", error)
|
|
945
|
+
future.set_exception(X11Error(error))
|
|
946
|
+
return False, buffer[offset:], None
|
|
947
|
+
|
|
948
|
+
# An event
|
|
949
|
+
if len(buffer) < 32:
|
|
950
|
+
return True, buffer, current_decoder
|
|
951
|
+
event, offset = unpack_event(self._events, buffer)
|
|
952
|
+
for listener in self.listeners:
|
|
953
|
+
await listener.put(event)
|
|
954
|
+
return False, buffer[offset:], current_decoder
|
|
955
|
+
|
|
874
956
|
async def _loop(self):
|
|
875
|
-
buffer,
|
|
957
|
+
buffer, context = b"", None
|
|
876
958
|
while True:
|
|
877
|
-
if not (data := await self.read(
|
|
959
|
+
if not (data := await self.read(4096)):
|
|
878
960
|
log.info("Connection closed")
|
|
879
961
|
return
|
|
880
962
|
buffer += data
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
decoder = await self.requests.get()
|
|
885
|
-
decode, future = decoder
|
|
886
|
-
if decode == unpack_setup:
|
|
887
|
-
size = 8 + 4 * int.from_bytes(buffer[6:8], BYTE_ORDER_TEXT)
|
|
888
|
-
else:
|
|
889
|
-
size = 32 + 4 * int.from_bytes(buffer[4:8], BYTE_ORDER_TEXT)
|
|
890
|
-
if len(buffer) < size:
|
|
891
|
-
continue
|
|
892
|
-
reply, offset = decode(buffer, 0)
|
|
893
|
-
future.set_result(reply)
|
|
894
|
-
decoder = None
|
|
895
|
-
elif packet_type == Status.Failed and not self.requests.empty():
|
|
896
|
-
decode, future = await self.requests.get()
|
|
897
|
-
if decode == unpack_setup:
|
|
898
|
-
error, offset = unpack_setup_error(buffer)
|
|
899
|
-
future.set_exception(RuntimeError(error))
|
|
900
|
-
else:
|
|
901
|
-
error, offset = unpack_error(buffer)
|
|
902
|
-
error = self._handle_error(error)
|
|
903
|
-
log.warning("ERROR: %s", error)
|
|
904
|
-
future.set_exception(X11Error(error))
|
|
905
|
-
else:
|
|
906
|
-
event, offset = unpack_event(self._events, buffer)
|
|
907
|
-
for listener in self.listeners:
|
|
908
|
-
await listener.put(event)
|
|
909
|
-
buffer = buffer[offset:]
|
|
963
|
+
need_more = False
|
|
964
|
+
while not need_more and buffer:
|
|
965
|
+
need_more, buffer, context = await self._handle_buffer(buffer, context)
|
|
910
966
|
|
|
911
967
|
async def events(self):
|
|
912
968
|
"""Async iterator yielding event dicts from the server."""
|
|
@@ -933,7 +989,7 @@ def get_display_config(uri: str) -> dict:
|
|
|
933
989
|
display["protocol"] = protocol
|
|
934
990
|
display["port"] = int(display["port"])
|
|
935
991
|
display["uri"] = uri
|
|
936
|
-
display["screen"] = display["screen"] or 0
|
|
992
|
+
display["screen"] = int(display["screen"] or 0)
|
|
937
993
|
return display
|
|
938
994
|
|
|
939
995
|
|
|
@@ -613,7 +613,7 @@ def pack_glx_is_direct(opcode, context):
|
|
|
613
613
|
def unpack_glx_is_direct(data, offset=0):
|
|
614
614
|
"""Unpack a glx is direct reply or event from *data* starting at *offset*."""
|
|
615
615
|
result, offset = unpack_reply_head(data, offset)
|
|
616
|
-
result["is_direct"] = bool(
|
|
616
|
+
result["is_direct"] = bool(struct.unpack_from("B", data, offset)[0])
|
|
617
617
|
offset += 24
|
|
618
618
|
return result, offset
|
|
619
619
|
|
|
@@ -754,17 +754,22 @@ def pack_glx_get_fb_configs(opcode, screen):
|
|
|
754
754
|
|
|
755
755
|
def unpack_glx_get_fb_configs(data, offset=0):
|
|
756
756
|
"""Unpack a glx get fb configs reply or event from *data* starting at *offset*."""
|
|
757
|
+
start = offset
|
|
757
758
|
result, offset = unpack_reply_head(data, offset)
|
|
759
|
+
# Fixed fields at bytes 8..31 of the reply
|
|
758
760
|
n_cfgs, n_props = struct.unpack_from("II", data, offset)
|
|
759
|
-
offset +=
|
|
761
|
+
offset += struct.calcsize("II") + 16
|
|
760
762
|
total = n_cfgs * n_props
|
|
761
|
-
|
|
762
|
-
|
|
763
|
+
if total:
|
|
764
|
+
props = list(struct.unpack_from(f"{total}I", data, offset))
|
|
765
|
+
else:
|
|
766
|
+
props = []
|
|
763
767
|
configs = [props[i * n_props : (i + 1) * n_props] for i in range(n_cfgs)]
|
|
764
768
|
result["num_fb_configs"] = n_cfgs
|
|
765
769
|
result["num_properties"] = n_props
|
|
766
770
|
result["fb_configs"] = configs
|
|
767
|
-
|
|
771
|
+
# Always consume the full reply so the connection buffer stays aligned
|
|
772
|
+
return result, start + 32 + 4 * result["length"]
|
|
768
773
|
|
|
769
774
|
|
|
770
775
|
def pack_glx_create_new_context(
|
|
@@ -793,32 +798,51 @@ def pack_glx_create_new_context(
|
|
|
793
798
|
|
|
794
799
|
|
|
795
800
|
def pack_glx_make_context_current(opcode, drawable, read_drawable, context, old_context_tag=0):
|
|
796
|
-
"""Pack a wire-protocol glx make context current request into ``bytes``.
|
|
801
|
+
"""Pack a wire-protocol glx make context current request into ``bytes``.
|
|
802
|
+
|
|
803
|
+
Wire order (glxproto.h)::
|
|
804
|
+
|
|
805
|
+
oldContextTag, drawable, readdrawable, context
|
|
806
|
+
"""
|
|
797
807
|
return struct.pack(
|
|
798
808
|
"BBHIIII",
|
|
799
809
|
opcode,
|
|
800
810
|
GLXCommand.MakeContextCurrent,
|
|
801
811
|
5,
|
|
812
|
+
int(old_context_tag),
|
|
802
813
|
int(drawable),
|
|
803
814
|
int(read_drawable),
|
|
804
815
|
int(context),
|
|
805
|
-
int(old_context_tag),
|
|
806
816
|
)
|
|
807
817
|
|
|
808
818
|
|
|
809
|
-
def pack_glx_create_pbuffer(
|
|
810
|
-
|
|
819
|
+
def pack_glx_create_pbuffer(
|
|
820
|
+
opcode,
|
|
821
|
+
pbuffer,
|
|
822
|
+
fbconfig,
|
|
823
|
+
screen=0,
|
|
824
|
+
attrs: list[tuple[int, int]] | None = None,
|
|
825
|
+
):
|
|
826
|
+
"""Pack a wire-protocol glx create pbuffer request into ``bytes``.
|
|
827
|
+
|
|
828
|
+
Wire order (xcb)::
|
|
829
|
+
|
|
830
|
+
screen, fbconfig, pbuffer, num_attribs, attribs[num_attribs*2]
|
|
831
|
+
|
|
832
|
+
Fixed header ``BBHIIII`` is 5 units plus 2 per attribute pair.
|
|
833
|
+
"""
|
|
811
834
|
attrs = attrs or []
|
|
812
835
|
n = len(attrs)
|
|
813
|
-
length =
|
|
836
|
+
length = 5 + 2 * n
|
|
814
837
|
parts = [
|
|
815
838
|
struct.pack(
|
|
816
|
-
"
|
|
839
|
+
"BBHIIII",
|
|
817
840
|
opcode,
|
|
818
841
|
GLXCommand.CreatePbuffer,
|
|
819
842
|
length,
|
|
820
|
-
int(
|
|
843
|
+
int(screen),
|
|
821
844
|
int(fbconfig),
|
|
845
|
+
int(pbuffer),
|
|
822
846
|
n,
|
|
823
847
|
)
|
|
824
848
|
]
|
|
@@ -832,20 +856,36 @@ def pack_glx_destroy_pbuffer(opcode, pbuffer):
|
|
|
832
856
|
return struct.pack("BBHI", opcode, GLXCommand.DestroyPbuffer, 2, int(pbuffer))
|
|
833
857
|
|
|
834
858
|
|
|
835
|
-
def pack_glx_create_window(
|
|
836
|
-
|
|
859
|
+
def pack_glx_create_window(
|
|
860
|
+
opcode,
|
|
861
|
+
glx_window,
|
|
862
|
+
fbconfig,
|
|
863
|
+
x_window,
|
|
864
|
+
screen=0,
|
|
865
|
+
attrs: list[tuple[int, int]] | None = None,
|
|
866
|
+
):
|
|
867
|
+
"""Pack a wire-protocol glx create window request into ``bytes``.
|
|
868
|
+
|
|
869
|
+
Wire order (GLX 1.3 / xcb)::
|
|
870
|
+
|
|
871
|
+
screen, fbconfig, x_window, glx_window, num_attribs, attribs[num_attribs*2]
|
|
872
|
+
|
|
873
|
+
Request length is in 4-byte units: fixed header ``BBHIIIII`` is 6 units
|
|
874
|
+
plus 2 units per attribute/value pair.
|
|
875
|
+
"""
|
|
837
876
|
attrs = attrs or []
|
|
838
877
|
n = len(attrs)
|
|
839
|
-
length =
|
|
878
|
+
length = 6 + 2 * n
|
|
840
879
|
parts = [
|
|
841
880
|
struct.pack(
|
|
842
|
-
"
|
|
881
|
+
"=BBHIIIII",
|
|
843
882
|
opcode,
|
|
844
883
|
GLXCommand.CreateWindow,
|
|
845
884
|
length,
|
|
846
|
-
int(
|
|
885
|
+
int(screen),
|
|
847
886
|
int(fbconfig),
|
|
848
887
|
int(x_window),
|
|
888
|
+
int(glx_window),
|
|
849
889
|
n,
|
|
850
890
|
)
|
|
851
891
|
]
|
|
@@ -1395,22 +1435,56 @@ class GLX(Extension):
|
|
|
1395
1435
|
)
|
|
1396
1436
|
return context
|
|
1397
1437
|
|
|
1398
|
-
async def create_window(self, fbconfig, x_window, attrs=None, window=None):
|
|
1399
|
-
"""Create window.
|
|
1438
|
+
async def create_window(self, fbconfig, x_window, screen=None, attrs=None, window=None):
|
|
1439
|
+
"""Create a GLX window wrapping an X11 window for the given FBConfig.
|
|
1440
|
+
|
|
1441
|
+
Parameters
|
|
1442
|
+
----------
|
|
1443
|
+
fbconfig:
|
|
1444
|
+
FBConfig id (from :meth:`get_fb_configs`).
|
|
1445
|
+
x_window:
|
|
1446
|
+
Existing X11 window XID (must use a visual compatible with *fbconfig*).
|
|
1447
|
+
screen:
|
|
1448
|
+
Screen number (default: display default screen).
|
|
1449
|
+
attrs:
|
|
1450
|
+
Optional list of ``(attribute, value)`` pairs.
|
|
1451
|
+
window:
|
|
1452
|
+
Optional GLX window id; allocated if omitted (must differ from *x_window*).
|
|
1453
|
+
"""
|
|
1454
|
+
x_window = int(x_window)
|
|
1455
|
+
fbconfig = int(fbconfig)
|
|
1456
|
+
if screen is None:
|
|
1457
|
+
screen = self.display.config.get("screen", 0)
|
|
1458
|
+
screen = int(screen)
|
|
1400
1459
|
if window is None:
|
|
1401
1460
|
window = self.display.resources.allocate()
|
|
1402
|
-
|
|
1461
|
+
window = int(window)
|
|
1462
|
+
# GLX window XID must be a fresh client ID, not the X11 window
|
|
1463
|
+
if window == x_window or window == 0:
|
|
1464
|
+
window = int(self.display.resources.allocate())
|
|
1465
|
+
self.display.write(
|
|
1466
|
+
pack_glx_create_window(
|
|
1467
|
+
self.code,
|
|
1468
|
+
window,
|
|
1469
|
+
fbconfig,
|
|
1470
|
+
x_window,
|
|
1471
|
+
screen=screen,
|
|
1472
|
+
attrs=attrs,
|
|
1473
|
+
)
|
|
1474
|
+
)
|
|
1403
1475
|
return window
|
|
1404
1476
|
|
|
1405
1477
|
async def destroy_window(self, window):
|
|
1406
1478
|
"""Destroy window."""
|
|
1407
1479
|
self.display.write(pack_glx_destroy_window(self.code, window))
|
|
1408
1480
|
|
|
1409
|
-
async def create_pbuffer(self, fbconfig, attrs=None, pbuffer=None):
|
|
1481
|
+
async def create_pbuffer(self, fbconfig, attrs=None, pbuffer=None, screen=None):
|
|
1410
1482
|
"""Create pbuffer."""
|
|
1411
1483
|
if pbuffer is None:
|
|
1412
1484
|
pbuffer = self.display.resources.allocate()
|
|
1413
|
-
|
|
1485
|
+
if screen is None:
|
|
1486
|
+
screen = self.display.config.get("screen", 0)
|
|
1487
|
+
self.display.write(pack_glx_create_pbuffer(self.code, pbuffer, fbconfig, screen=screen, attrs=attrs))
|
|
1414
1488
|
return pbuffer
|
|
1415
1489
|
|
|
1416
1490
|
async def destroy_pbuffer(self, pbuffer):
|