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.
Files changed (39) hide show
  1. {aioxlib-0.3.0 → aioxlib-0.5.0}/PKG-INFO +9 -3
  2. {aioxlib-0.3.0 → aioxlib-0.5.0}/README.md +8 -2
  3. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/__init__.py +4 -7
  4. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/_extensions.py +2 -0
  5. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/api.py +8 -2
  6. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/connection.py +0 -4
  7. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/display.py +97 -41
  8. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/glx.py +96 -22
  9. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/protocol.py +84 -6
  10. aioxlib-0.5.0/aioxlib/randr.py +1350 -0
  11. aioxlib-0.5.0/aioxlib/widgets.py +211 -0
  12. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib.egg-info/PKG-INFO +9 -3
  13. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib.egg-info/SOURCES.txt +2 -0
  14. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_connection.py +0 -16
  15. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_glx.py +4 -4
  16. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/authority.py +0 -0
  17. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/bigreq.py +0 -0
  18. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/constants.py +0 -0
  19. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/dbe.py +0 -0
  20. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/dri3.py +0 -0
  21. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/present.py +0 -0
  22. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/render.py +0 -0
  23. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib/shm.py +0 -0
  24. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib.egg-info/dependency_links.txt +0 -0
  25. {aioxlib-0.3.0 → aioxlib-0.5.0}/aioxlib.egg-info/top_level.txt +0 -0
  26. {aioxlib-0.3.0 → aioxlib-0.5.0}/pyproject.toml +0 -0
  27. {aioxlib-0.3.0 → aioxlib-0.5.0}/setup.cfg +0 -0
  28. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_api.py +0 -0
  29. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_authority.py +0 -0
  30. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_bigreq.py +0 -0
  31. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_constants.py +0 -0
  32. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_dbe.py +0 -0
  33. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_display.py +0 -0
  34. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_dri3.py +0 -0
  35. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_examples.py +0 -0
  36. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_present.py +0 -0
  37. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_protocol.py +0 -0
  38. {aioxlib-0.3.0 → aioxlib-0.5.0}/tests/test_render.py +0 -0
  39. {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.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 -r docs/requirements.txt && mkdocs serve`
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 -r docs/requirements.txt && mkdocs serve`
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.3.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
- *_constants_all,
31
- *_api_all,
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",
@@ -97,10 +97,6 @@ class Connection:
97
97
  """Write."""
98
98
  self.writer.write(payload)
99
99
 
100
- def writelines(self, lines):
101
- """Writelines."""
102
- self.writer.writelines(lines)
103
-
104
100
  async def flush(self):
105
101
  """Flush."""
106
102
  return await self.writer.drain()
@@ -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
- if drawable is None:
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 = {EventType.ClientMessage: unpack_client_message}
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, (bytes, bytearray)):
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, (bytes, bytearray)):
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, decoder = b"", None
957
+ buffer, context = b"", None
876
958
  while True:
877
- if not (data := await self.read(4 * 1024)):
959
+ if not (data := await self.read(4096)):
878
960
  log.info("Connection closed")
879
961
  return
880
962
  buffer += data
881
- packet_type = buffer[0]
882
- if packet_type == Status.Success:
883
- if decoder is None:
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(result["n"])
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 += 8 + 16
761
+ offset += struct.calcsize("II") + 16
760
762
  total = n_cfgs * n_props
761
- props = list(struct.unpack_from(f"{total}I".format(total=total), data, offset)) if total else []
762
- offset += total * 4
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
- return result, offset
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(opcode, pbuffer, fbconfig, attrs: list[tuple[int, int]] | None = None):
810
- """Pack a wire-protocol glx create pbuffer request into ``bytes``."""
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 = 3 + 2 * n
836
+ length = 5 + 2 * n
814
837
  parts = [
815
838
  struct.pack(
816
- "BBHIII",
839
+ "BBHIIII",
817
840
  opcode,
818
841
  GLXCommand.CreatePbuffer,
819
842
  length,
820
- int(pbuffer),
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(opcode, window, fbconfig, x_window, attrs: list[tuple[int, int]] | None = None):
836
- """Pack a wire-protocol glx create window request into ``bytes``."""
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 = 4 + 2 * n
878
+ length = 6 + 2 * n
840
879
  parts = [
841
880
  struct.pack(
842
- "BBHIIII",
881
+ "=BBHIIIII",
843
882
  opcode,
844
883
  GLXCommand.CreateWindow,
845
884
  length,
846
- int(window),
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
- self.display.write(pack_glx_create_window(self.code, window, fbconfig, x_window, attrs))
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
- self.display.write(pack_glx_create_pbuffer(self.code, pbuffer, fbconfig, attrs))
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):