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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aioxlib
3
- Version: 0.0.7
3
+ Version: 0.0.8
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
@@ -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"
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
- Above = 0
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
- async def read_setup(reader):
781
- data = await reader.readexactly(1)
782
- status = Status(ord(data))
783
- if status == Status.Success:
784
- head = "BxHHH"
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, mask_format=0
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, mask_format=0
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
- req = pack_setup(entry["name"], entry["data"])
4860
- self.write(req)
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(16 * 1024)):
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 PacketType:
5041
- if packet_type == PacketType.Reply:
5042
- size = 32 + 4 * int.from_bytes(buffer[4:8], BYTE_ORDER_TEXT)
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 == PacketType.Error:
5049
- error, offset = unpack_error(buffer)
5050
- error = self._handle_error(error)
5051
- log.warning("ERROR: %s", error)
5052
- if self.requests.empty():
5053
- continue
5054
- _, future = self.requests.get_nowait()
5055
- future.set_exception(X11Error(error))
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aioxlib
3
- Version: 0.0.7
3
+ Version: 0.0.8
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
@@ -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