aioxlib 0.0.1__py3-none-any.whl
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/__init__.py +1571 -0
- aioxlib-0.0.1.dist-info/METADATA +24 -0
- aioxlib-0.0.1.dist-info/RECORD +5 -0
- aioxlib-0.0.1.dist-info/WHEEL +5 -0
- aioxlib-0.0.1.dist-info/top_level.txt +1 -0
aioxlib/__init__.py
ADDED
|
@@ -0,0 +1,1571 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is part of the aioxlib project
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2026 Tiago Coutinho
|
|
5
|
+
# Distributed under the GPLv3 license. See LICENSE for more info.
|
|
6
|
+
|
|
7
|
+
__version__ = "0.0.1"
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import enum
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import socket
|
|
15
|
+
import struct
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
log = logging.getLogger("aioxlib")
|
|
19
|
+
|
|
20
|
+
BIG_ENDIAN = 0x42
|
|
21
|
+
LITTLE_ENDIAN = 0x6C
|
|
22
|
+
BYTE_ORDER = LITTLE_ENDIAN if sys.byteorder == "little" else BIG_ENDIAN
|
|
23
|
+
|
|
24
|
+
PROTOCOL_MAJOR = 11
|
|
25
|
+
PROTOCOL_MINOR = 0
|
|
26
|
+
|
|
27
|
+
TYPE = b"MIT-MAGIC-COOKIE-1"
|
|
28
|
+
SUPPORTED_PROTOCOLS = "unix", "tcp"
|
|
29
|
+
DISPLAY_RE = re.compile(r"^((?P<protocol>tcp|unix)/)?(?P<host>[-a-zA-Z0-9._]*):(?P<port>\d+)(\.(?P<screen>\d+))?$")
|
|
30
|
+
|
|
31
|
+
BASE_TCP_PORT = 6000
|
|
32
|
+
UNIX_PATH_TEMPLATE = "/tmp/.X11-unix/X{}"
|
|
33
|
+
|
|
34
|
+
DEFAULT_STRING_ENCODING = "ISO-8859-1"
|
|
35
|
+
UTF8_STRING_ENCODING = "UTF-8"
|
|
36
|
+
|
|
37
|
+
NONE = 0
|
|
38
|
+
|
|
39
|
+
ParentRelative = 1 # background pixmap in CreateWindow
|
|
40
|
+
# and ChangeWindowAttributes
|
|
41
|
+
|
|
42
|
+
CopyFromParent = 0 # border pixmap in CreateWindow
|
|
43
|
+
# and ChangeWindowAttributes
|
|
44
|
+
# special VisualID and special window
|
|
45
|
+
# class passed to CreateWindow
|
|
46
|
+
|
|
47
|
+
PointerWindow = 0 # destination window in SendEvent
|
|
48
|
+
InputFocus = 1 # destination window in SendEvent
|
|
49
|
+
PointerRoot = 1 # focus window in SetInputFocus
|
|
50
|
+
AnyPropertyType = 0 # special Atom, passed to GetProperty
|
|
51
|
+
AnyKey = 0 # special Key Code, passed to GrabKey
|
|
52
|
+
AnyButton = 0 # special Button Code, passed to GrabButton
|
|
53
|
+
AllTemporary = 0 # special Resource ID passed to KillClient
|
|
54
|
+
CurrentTime = 0 # special Time
|
|
55
|
+
NoSymbol = 0 # special KeySym
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class EventMask(enum.IntFlag):
|
|
59
|
+
NoEvent = 0
|
|
60
|
+
KeyPress = 1 << 0
|
|
61
|
+
KeyRelease = 1 << 1
|
|
62
|
+
ButtonPress = 1 << 2
|
|
63
|
+
ButtonRelease = 1 << 3
|
|
64
|
+
EnterWindow = 1 << 4
|
|
65
|
+
LeaveWindow = 1 << 5
|
|
66
|
+
PointerMotion = 1 << 6
|
|
67
|
+
PointerMotionHint = 1 << 7
|
|
68
|
+
Button1Motion = 1 << 8
|
|
69
|
+
Button2Motion = 1 << 9
|
|
70
|
+
Button3Motion = 1 << 10
|
|
71
|
+
Button4Motion = 1 << 11
|
|
72
|
+
Button5Motion = 1 << 12
|
|
73
|
+
ButtonMotion = 1 << 13
|
|
74
|
+
KeymapState = 1 << 14
|
|
75
|
+
Exposure = 1 << 15
|
|
76
|
+
VisibilityChange = 1 << 16
|
|
77
|
+
StructureNotify = 1 << 17
|
|
78
|
+
ResizeRedirect = 1 << 18
|
|
79
|
+
SubstructureNotify = 1 << 19
|
|
80
|
+
SubstructureRedirect = 1 << 20
|
|
81
|
+
FocusChange = 1 << 21
|
|
82
|
+
PropertyChange = 1 << 22
|
|
83
|
+
ColormapChange = 1 << 23
|
|
84
|
+
OwnerGrabButton = 1 << 24
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class PacketType(enum.IntEnum):
|
|
88
|
+
Error = 0
|
|
89
|
+
Reply = 1
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Event(enum.IntEnum):
|
|
93
|
+
KeyPress = 2
|
|
94
|
+
KeyRelease = 3
|
|
95
|
+
ButtonPress = 4
|
|
96
|
+
ButtonRelease = 5
|
|
97
|
+
MotionNotify = 6
|
|
98
|
+
EnterNotify = 7
|
|
99
|
+
LeaveNotify = 8
|
|
100
|
+
FocusIn = 9
|
|
101
|
+
FocusOut = 10
|
|
102
|
+
KeymapNotify = 11
|
|
103
|
+
Expose = 12
|
|
104
|
+
GraphicsExpose = 13
|
|
105
|
+
NoExpose = 14
|
|
106
|
+
VisibilityNotify = 15
|
|
107
|
+
CreateNotify = 16
|
|
108
|
+
DestroyNotify = 17
|
|
109
|
+
UnmapNotify = 18
|
|
110
|
+
MapNotify = 19
|
|
111
|
+
MapRequest = 20
|
|
112
|
+
ReparentNotify = 21
|
|
113
|
+
ConfigureNotify = 22
|
|
114
|
+
ConfigureRequest = 23
|
|
115
|
+
GravityNotify = 24
|
|
116
|
+
ResizeRequest = 25
|
|
117
|
+
CirculateNotify = 26
|
|
118
|
+
CirculateRequest = 27
|
|
119
|
+
PropertyNotify = 28
|
|
120
|
+
SelectionClear = 29
|
|
121
|
+
SelectionRequest = 30
|
|
122
|
+
SelectionNotify = 31
|
|
123
|
+
ColormapNotify = 32
|
|
124
|
+
ClientMessage = 33
|
|
125
|
+
MappingNotify = 34
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class KeyMask(enum.IntFlag):
|
|
129
|
+
Shift = 1 << 0
|
|
130
|
+
Lock = 1 << 1
|
|
131
|
+
Control = 1 << 2
|
|
132
|
+
Mod1 = 1 << 3
|
|
133
|
+
Mod2 = 1 << 4
|
|
134
|
+
Mod3 = 1 << 5
|
|
135
|
+
Mod4 = 1 << 6
|
|
136
|
+
Mod5 = 1 << 7
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class Modifier(enum.IntEnum):
|
|
140
|
+
ShiftMapIndex = 0
|
|
141
|
+
LockMapIndex = 1
|
|
142
|
+
ControlMapIndex = 2
|
|
143
|
+
Mod1MapIndex = 3
|
|
144
|
+
Mod2MapIndex = 4
|
|
145
|
+
Mod3MapIndex = 5
|
|
146
|
+
Mod4MapIndex = 6
|
|
147
|
+
Mod5MapIndex = 7
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class ButtonMask(enum.IntFlag):
|
|
151
|
+
Button1Mask = 1 << 8
|
|
152
|
+
Button2Mask = 1 << 9
|
|
153
|
+
Button3Mask = 1 << 10
|
|
154
|
+
Button4Mask = 1 << 11
|
|
155
|
+
Button5Mask = 1 << 12
|
|
156
|
+
AnyModifier = 1 << 15 # used in GrabButton, GrabKey
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class Button(enum.IntEnum):
|
|
160
|
+
Button1 = 1
|
|
161
|
+
Button2 = 2
|
|
162
|
+
Button3 = 3
|
|
163
|
+
Button4 = 4
|
|
164
|
+
Button5 = 5
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class Family(enum.IntEnum):
|
|
168
|
+
Internet = 0
|
|
169
|
+
DECnet = 1
|
|
170
|
+
Chaos = 2
|
|
171
|
+
ServerInterpreted = 5
|
|
172
|
+
InternetV6 = 6
|
|
173
|
+
Local = 256
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class Status(enum.IntEnum):
|
|
177
|
+
Failed = 0
|
|
178
|
+
Success = 1
|
|
179
|
+
Authenticate = 2
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class Command(enum.IntEnum):
|
|
183
|
+
CreateWindow = 1
|
|
184
|
+
ChangeWindowAttributes = 2
|
|
185
|
+
GetWindowAttributes = 3
|
|
186
|
+
DestroyWindow = 4
|
|
187
|
+
DestroySubwindows = 5
|
|
188
|
+
ChangeSaveSet = 6
|
|
189
|
+
ReparentWindow = 7
|
|
190
|
+
MapWindow = 8
|
|
191
|
+
MapSubwindows = 9
|
|
192
|
+
UnmapWindow = 10
|
|
193
|
+
UnmapSubwindows = 11
|
|
194
|
+
ConfigureWindow = 12
|
|
195
|
+
CirculateWindow = 13
|
|
196
|
+
GetGeometry = 14
|
|
197
|
+
QueryTree = 15
|
|
198
|
+
InternAtom = 16
|
|
199
|
+
GetAtomName = 17
|
|
200
|
+
ChangeProperty = 18
|
|
201
|
+
DeleteProperty = 19
|
|
202
|
+
GetProperty = 20
|
|
203
|
+
ListProperties = 21
|
|
204
|
+
SetSelectionOwner = 22
|
|
205
|
+
GetSelectionOwner = 23
|
|
206
|
+
ConvertSelection = 24
|
|
207
|
+
SendEvent = 25
|
|
208
|
+
GrabPointer = 26
|
|
209
|
+
UngrabPointer = 27
|
|
210
|
+
GrabButton = 28
|
|
211
|
+
UngrabButton = 29
|
|
212
|
+
ChangeActivePointerGrab = 30
|
|
213
|
+
GrabKeyboard = 31
|
|
214
|
+
UngrabKeyboard = 32
|
|
215
|
+
GrabKey = 33
|
|
216
|
+
UngrabKey = 34
|
|
217
|
+
AllowEvents = 35
|
|
218
|
+
GrabServer = 36
|
|
219
|
+
UngrabServer = 37
|
|
220
|
+
QueryPointer = 38
|
|
221
|
+
GetMotionEvents = 39
|
|
222
|
+
TranslateCoordinates = 40
|
|
223
|
+
WarpPointer = 41
|
|
224
|
+
SetInputFocus = 42
|
|
225
|
+
GetInputFocus = 43
|
|
226
|
+
QueryKeymap = 44
|
|
227
|
+
OpenFont = 45
|
|
228
|
+
CloseFont = 46
|
|
229
|
+
QueryFont = 47
|
|
230
|
+
QueryTextExtents = 48
|
|
231
|
+
ListFonts = 49
|
|
232
|
+
ListFontsWithInfo = 50
|
|
233
|
+
SetFontPath = 51
|
|
234
|
+
GetFontPath = 52
|
|
235
|
+
CreatePixmap = 53
|
|
236
|
+
FreePixmap = 54
|
|
237
|
+
CreateGC = 55
|
|
238
|
+
ChangeGC = 56
|
|
239
|
+
CopyGC = 57
|
|
240
|
+
SetDashes = 58
|
|
241
|
+
SetClipRectangles = 59
|
|
242
|
+
FreeGC = 60
|
|
243
|
+
ClearArea = 61
|
|
244
|
+
CopyArea = 62
|
|
245
|
+
CopyPlane = 63
|
|
246
|
+
PolyPoint = 64
|
|
247
|
+
PolyLine = 65
|
|
248
|
+
PolySegment = 66
|
|
249
|
+
PolyRectangle = 67
|
|
250
|
+
PolyArc = 68
|
|
251
|
+
FillPoly = 69
|
|
252
|
+
PolyFillRectangle = 70
|
|
253
|
+
PolyFillArc = 71
|
|
254
|
+
PutImage = 72
|
|
255
|
+
GetImage = 73
|
|
256
|
+
PolyText8 = 74
|
|
257
|
+
PolyText16 = 75
|
|
258
|
+
ImageText8 = 76
|
|
259
|
+
ImageText16 = 77
|
|
260
|
+
QueryExtension = 98
|
|
261
|
+
ListExtensions = 99
|
|
262
|
+
|
|
263
|
+
SetScreenSaver = 107
|
|
264
|
+
GetScreenSaver = 108
|
|
265
|
+
GetModifierMapping = 119
|
|
266
|
+
NoOperation = 127
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# -----------------------------------------------------------------------
|
|
270
|
+
# XXX These still need documentation -- for now, read <X11/X.h>
|
|
271
|
+
#
|
|
272
|
+
#
|
|
273
|
+
NotifyNormal = 0
|
|
274
|
+
NotifyGrab = 1
|
|
275
|
+
NotifyUngrab = 2
|
|
276
|
+
NotifyWhileGrabbed = 3
|
|
277
|
+
NotifyHint = 1
|
|
278
|
+
NotifyAncestor = 0
|
|
279
|
+
NotifyVirtual = 1
|
|
280
|
+
NotifyInferior = 2
|
|
281
|
+
NotifyNonlinear = 3
|
|
282
|
+
NotifyNonlinearVirtual = 4
|
|
283
|
+
NotifyPointer = 5
|
|
284
|
+
NotifyPointerRoot = 6
|
|
285
|
+
NotifyDetailNone = 7
|
|
286
|
+
VisibilityUnobscured = 0
|
|
287
|
+
VisibilityPartiallyObscured = 1
|
|
288
|
+
VisibilityFullyObscured = 2
|
|
289
|
+
PlaceOnTop = 0
|
|
290
|
+
PlaceOnBottom = 1
|
|
291
|
+
|
|
292
|
+
PropertyNewValue = 0
|
|
293
|
+
PropertyDelete = 1
|
|
294
|
+
ColormapUninstalled = 0
|
|
295
|
+
ColormapInstalled = 1
|
|
296
|
+
GrabModeSync = 0
|
|
297
|
+
GrabModeAsync = 1
|
|
298
|
+
GrabSuccess = 0
|
|
299
|
+
AlreadyGrabbed = 1
|
|
300
|
+
GrabInvalidTime = 2
|
|
301
|
+
GrabNotViewable = 3
|
|
302
|
+
GrabFrozen = 4
|
|
303
|
+
AsyncPointer = 0
|
|
304
|
+
SyncPointer = 1
|
|
305
|
+
ReplayPointer = 2
|
|
306
|
+
AsyncKeyboard = 3
|
|
307
|
+
SyncKeyboard = 4
|
|
308
|
+
ReplayKeyboard = 5
|
|
309
|
+
AsyncBoth = 6
|
|
310
|
+
SyncBoth = 7
|
|
311
|
+
RevertToNone = 0
|
|
312
|
+
RevertToPointerRoot = PointerRoot
|
|
313
|
+
RevertToParent = 2
|
|
314
|
+
Success = 0
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class ErrorCode(enum.IntEnum):
|
|
318
|
+
BadRequest = 1
|
|
319
|
+
BadValue = 2
|
|
320
|
+
BadWindow = 3
|
|
321
|
+
BadPixmap = 4
|
|
322
|
+
BadAtom = 5
|
|
323
|
+
BadCursor = 6
|
|
324
|
+
BadFont = 7
|
|
325
|
+
BadMatch = 8
|
|
326
|
+
BadDrawable = 9
|
|
327
|
+
BadAccess = 10
|
|
328
|
+
BadAlloc = 11
|
|
329
|
+
BadColor = 12
|
|
330
|
+
BadGC = 13
|
|
331
|
+
BadIDChoice = 14
|
|
332
|
+
BadName = 15
|
|
333
|
+
BadLength = 16
|
|
334
|
+
BadImplementation = 17
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class PropMode(enum.IntEnum):
|
|
338
|
+
Replace = 0
|
|
339
|
+
Prepend = 1
|
|
340
|
+
Append = 2
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class Atom(enum.IntEnum):
|
|
344
|
+
PRIMARY = 1
|
|
345
|
+
SECONDARY = 2
|
|
346
|
+
ARC = 3
|
|
347
|
+
ATOM = 4
|
|
348
|
+
BITMAP = 5
|
|
349
|
+
CARDINAL = 6
|
|
350
|
+
COLORMAP = 7
|
|
351
|
+
CURSOR = 8
|
|
352
|
+
CUT_BUFFER0 = 9
|
|
353
|
+
CUT_BUFFER1 = 10
|
|
354
|
+
CUT_BUFFER2 = 11
|
|
355
|
+
CUT_BUFFER3 = 12
|
|
356
|
+
CUT_BUFFER4 = 13
|
|
357
|
+
CUT_BUFFER5 = 14
|
|
358
|
+
CUT_BUFFER6 = 15
|
|
359
|
+
CUT_BUFFER7 = 16
|
|
360
|
+
DRAWABLE = 17
|
|
361
|
+
FONT = 18
|
|
362
|
+
INTEGER = 19
|
|
363
|
+
PIXMAP = 20
|
|
364
|
+
POINT = 21
|
|
365
|
+
RECTANGLE = 22
|
|
366
|
+
RESOURCE_MANAGER = 23
|
|
367
|
+
RGB_COLOR_MAP = 24
|
|
368
|
+
RGB_BEST_MAP = 25
|
|
369
|
+
RGB_BLUE_MAP = 26
|
|
370
|
+
RGB_DEFAULT_MAP = 27
|
|
371
|
+
RGB_GRAY_MAP = 28
|
|
372
|
+
RGB_GREEN_MAP = 29
|
|
373
|
+
RGB_RED_MAP = 30
|
|
374
|
+
STRING = 31
|
|
375
|
+
VISUALID = 32
|
|
376
|
+
WINDOW = 33
|
|
377
|
+
WM_COMMAND = 34
|
|
378
|
+
WM_HINTS = 35
|
|
379
|
+
WM_CLIENT_MACHINE = 36
|
|
380
|
+
WM_ICON_NAME = 37
|
|
381
|
+
WM_ICON_SIZE = 38
|
|
382
|
+
WM_NAME = 39
|
|
383
|
+
WM_NORMAL_HINTS = 40
|
|
384
|
+
WM_SIZE_HINTS = 41
|
|
385
|
+
WM_ZOOM_HINTS = 42
|
|
386
|
+
MIN_SPACE = 43
|
|
387
|
+
NORM_SPACE = 44
|
|
388
|
+
MAX_SPACE = 45
|
|
389
|
+
END_SPACE = 46
|
|
390
|
+
SUPERSCRIPT_X = 47
|
|
391
|
+
SUPERSCRIPT_Y = 48
|
|
392
|
+
SUBSCRIPT_X = 49
|
|
393
|
+
SUBSCRIPT_Y = 50
|
|
394
|
+
UNDERLINE_POSITION = 51
|
|
395
|
+
UNDERLINE_THICKNESS = 52
|
|
396
|
+
STRIKEOUT_ASCENT = 53
|
|
397
|
+
STRIKEOUT_DESCENT = 54
|
|
398
|
+
ITALIC_ANGLE = 55
|
|
399
|
+
X_HEIGHT = 56
|
|
400
|
+
QUAD_WIDTH = 57
|
|
401
|
+
WEIGHT = 58
|
|
402
|
+
POINT_SIZE = 59
|
|
403
|
+
RESOLUTION = 60
|
|
404
|
+
COPYRIGHT = 61
|
|
405
|
+
NOTICE = 62
|
|
406
|
+
FONT_NAME = 63
|
|
407
|
+
FAMILY_NAME = 64
|
|
408
|
+
FULL_NAME = 65
|
|
409
|
+
CAP_HEIGHT = 66
|
|
410
|
+
WM_CLASS = 67
|
|
411
|
+
WM_TRANSIENT_FOR = 68
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class ImageFormat(enum.IntEnum):
|
|
415
|
+
XYBitmap = 0
|
|
416
|
+
XYPixmap = 1
|
|
417
|
+
ZPixmap = 2
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
FirstExtensionError = 128
|
|
421
|
+
LastExtensionError = 255
|
|
422
|
+
InputOutput = 1
|
|
423
|
+
InputOnly = 2
|
|
424
|
+
CWBackPixmap = 1 << 0
|
|
425
|
+
CWBackPixel = 1 << 1
|
|
426
|
+
CWBorderPixmap = 1 << 2
|
|
427
|
+
CWBorderPixel = 1 << 3
|
|
428
|
+
CWBitGravity = 1 << 4
|
|
429
|
+
CWWinGravity = 1 << 5
|
|
430
|
+
CWBackingStore = 1 << 6
|
|
431
|
+
CWBackingPlanes = 1 << 7
|
|
432
|
+
CWBackingPixel = 1 << 8
|
|
433
|
+
CWOverrideRedirect = 1 << 9
|
|
434
|
+
CWSaveUnder = 1 << 10
|
|
435
|
+
CWEventMask = 1 << 11
|
|
436
|
+
CWDontPropagate = 1 << 12
|
|
437
|
+
CWColormap = 1 << 13
|
|
438
|
+
CWCursor = 1 << 14
|
|
439
|
+
CWX = 1 << 0
|
|
440
|
+
CWY = 1 << 1
|
|
441
|
+
CWWidth = 1 << 2
|
|
442
|
+
CWHeight = 1 << 3
|
|
443
|
+
CWBorderWidth = 1 << 4
|
|
444
|
+
CWSibling = 1 << 5
|
|
445
|
+
CWStackMode = 1 << 6
|
|
446
|
+
ForgetGravity = 0
|
|
447
|
+
NorthWestGravity = 1
|
|
448
|
+
NorthGravity = 2
|
|
449
|
+
NorthEastGravity = 3
|
|
450
|
+
WestGravity = 4
|
|
451
|
+
CenterGravity = 5
|
|
452
|
+
EastGravity = 6
|
|
453
|
+
SouthWestGravity = 7
|
|
454
|
+
SouthGravity = 8
|
|
455
|
+
SouthEastGravity = 9
|
|
456
|
+
StaticGravity = 10
|
|
457
|
+
UnmapGravity = 0
|
|
458
|
+
NotUseful = 0
|
|
459
|
+
WhenMapped = 1
|
|
460
|
+
Always = 2
|
|
461
|
+
IsUnmapped = 0
|
|
462
|
+
IsUnviewable = 1
|
|
463
|
+
IsViewable = 2
|
|
464
|
+
SetModeInsert = 0
|
|
465
|
+
SetModeDelete = 1
|
|
466
|
+
DestroyAll = 0
|
|
467
|
+
RetainPermanent = 1
|
|
468
|
+
RetainTemporary = 2
|
|
469
|
+
Above = 0
|
|
470
|
+
Below = 1
|
|
471
|
+
TopIf = 2
|
|
472
|
+
BottomIf = 3
|
|
473
|
+
Opposite = 4
|
|
474
|
+
RaiseLowest = 0
|
|
475
|
+
LowerHighest = 1
|
|
476
|
+
|
|
477
|
+
GXclear = 0x0
|
|
478
|
+
GXand = 0x1
|
|
479
|
+
GXandReverse = 0x2
|
|
480
|
+
GXcopy = 0x3
|
|
481
|
+
GXandInverted = 0x4
|
|
482
|
+
GXnoop = 0x5
|
|
483
|
+
GXxor = 0x6
|
|
484
|
+
GXor = 0x7
|
|
485
|
+
GXnor = 0x8
|
|
486
|
+
GXequiv = 0x9
|
|
487
|
+
GXinvert = 0xA
|
|
488
|
+
GXorReverse = 0xB
|
|
489
|
+
GXcopyInverted = 0xC
|
|
490
|
+
GXorInverted = 0xD
|
|
491
|
+
GXnand = 0xE
|
|
492
|
+
GXset = 0xF
|
|
493
|
+
LineSolid = 0
|
|
494
|
+
LineOnOffDash = 1
|
|
495
|
+
LineDoubleDash = 2
|
|
496
|
+
CapNotLast = 0
|
|
497
|
+
CapButt = 1
|
|
498
|
+
CapRound = 2
|
|
499
|
+
CapProjecting = 3
|
|
500
|
+
JoinMiter = 0
|
|
501
|
+
JoinRound = 1
|
|
502
|
+
JoinBevel = 2
|
|
503
|
+
FillSolid = 0
|
|
504
|
+
FillTiled = 1
|
|
505
|
+
FillStippled = 2
|
|
506
|
+
FillOpaqueStippled = 3
|
|
507
|
+
EvenOddRule = 0
|
|
508
|
+
WindingRule = 1
|
|
509
|
+
ClipByChildren = 0
|
|
510
|
+
IncludeInferiors = 1
|
|
511
|
+
Unsorted = 0
|
|
512
|
+
YSorted = 1
|
|
513
|
+
YXSorted = 2
|
|
514
|
+
YXBanded = 3
|
|
515
|
+
CoordModeOrigin = 0
|
|
516
|
+
CoordModePrevious = 1
|
|
517
|
+
Complex = 0
|
|
518
|
+
Nonconvex = 1
|
|
519
|
+
Convex = 2
|
|
520
|
+
ArcChord = 0
|
|
521
|
+
ArcPieSlice = 1
|
|
522
|
+
GCFunction = 1 << 0
|
|
523
|
+
GCPlaneMask = 1 << 1
|
|
524
|
+
GCForeground = 1 << 2
|
|
525
|
+
GCBackground = 1 << 3
|
|
526
|
+
GCLineWidth = 1 << 4
|
|
527
|
+
GCLineStyle = 1 << 5
|
|
528
|
+
GCCapStyle = 1 << 6
|
|
529
|
+
GCJoinStyle = 1 << 7
|
|
530
|
+
GCFillStyle = 1 << 8
|
|
531
|
+
GCFillRule = 1 << 9
|
|
532
|
+
GCTile = 1 << 10
|
|
533
|
+
GCStipple = 1 << 11
|
|
534
|
+
GCTileStipXOrigin = 1 << 12
|
|
535
|
+
GCTileStipYOrigin = 1 << 13
|
|
536
|
+
GCFont = 1 << 14
|
|
537
|
+
GCSubwindowMode = 1 << 15
|
|
538
|
+
GCGraphicsExposures = 1 << 16
|
|
539
|
+
GCClipXOrigin = 1 << 17
|
|
540
|
+
GCClipYOrigin = 1 << 18
|
|
541
|
+
GCClipMask = 1 << 19
|
|
542
|
+
GCDashOffset = 1 << 20
|
|
543
|
+
GCDashList = 1 << 21
|
|
544
|
+
GCArcMode = 1 << 22
|
|
545
|
+
GCLastBit = 22
|
|
546
|
+
FontLeftToRight = 0
|
|
547
|
+
FontRightToLeft = 1
|
|
548
|
+
FontChange = 255
|
|
549
|
+
|
|
550
|
+
AllocNone = 0
|
|
551
|
+
AllocAll = 1
|
|
552
|
+
DoRed = 1 << 0
|
|
553
|
+
DoGreen = 1 << 1
|
|
554
|
+
DoBlue = 1 << 2
|
|
555
|
+
CursorShape = 0
|
|
556
|
+
TileShape = 1
|
|
557
|
+
StippleShape = 2
|
|
558
|
+
AutoRepeatModeOff = 0
|
|
559
|
+
AutoRepeatModeOn = 1
|
|
560
|
+
AutoRepeatModeDefault = 2
|
|
561
|
+
LedModeOff = 0
|
|
562
|
+
LedModeOn = 1
|
|
563
|
+
KBKeyClickPercent = 1 << 0
|
|
564
|
+
KBBellPercent = 1 << 1
|
|
565
|
+
KBBellPitch = 1 << 2
|
|
566
|
+
KBBellDuration = 1 << 3
|
|
567
|
+
KBLed = 1 << 4
|
|
568
|
+
KBLedMode = 1 << 5
|
|
569
|
+
KBKey = 1 << 6
|
|
570
|
+
KBAutoRepeatMode = 1 << 7
|
|
571
|
+
MappingSuccess = 0
|
|
572
|
+
MappingBusy = 1
|
|
573
|
+
MappingFailed = 2
|
|
574
|
+
MappingModifier = 0
|
|
575
|
+
MappingKeyboard = 1
|
|
576
|
+
MappingPointer = 2
|
|
577
|
+
DontPreferBlanking = 0
|
|
578
|
+
PreferBlanking = 1
|
|
579
|
+
DefaultBlanking = 2
|
|
580
|
+
DisableScreenSaver = 0
|
|
581
|
+
DisableScreenInterval = 0
|
|
582
|
+
DontAllowExposures = 0
|
|
583
|
+
AllowExposures = 1
|
|
584
|
+
DefaultExposures = 2
|
|
585
|
+
ScreenSaverReset = 0
|
|
586
|
+
ScreenSaverActive = 1
|
|
587
|
+
HostInsert = 0
|
|
588
|
+
HostDelete = 1
|
|
589
|
+
EnableAccess = 1
|
|
590
|
+
DisableAccess = 0
|
|
591
|
+
StaticGray = 0
|
|
592
|
+
GrayScale = 1
|
|
593
|
+
StaticColor = 2
|
|
594
|
+
PseudoColor = 3
|
|
595
|
+
TrueColor = 4
|
|
596
|
+
DirectColor = 5
|
|
597
|
+
LSBFirst = 0
|
|
598
|
+
MSBFirst = 1
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
GenericEventCode = 35
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def pad(n: int) -> int:
|
|
605
|
+
return (4 - n % 4) % 4
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def round_bytes(s: bytes) -> tuple[bytes, int, int]:
|
|
609
|
+
n = len(s)
|
|
610
|
+
r = pad(n)
|
|
611
|
+
return s + r * b"\0", n, n + r
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def pack_setup(auth_name, auth_data, byte_order=BYTE_ORDER, protocol=(PROTOCOL_MAJOR, PROTOCOL_MINOR)):
|
|
615
|
+
auth_name, n1, n2 = round_bytes(auth_name)
|
|
616
|
+
auth_data, d1, d2 = round_bytes(auth_data)
|
|
617
|
+
fmt = f"=BxHHHH2x{n2}s{d2}s"
|
|
618
|
+
return struct.pack(fmt, byte_order, *protocol, n1, d1, auth_name, auth_data)
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
async def read_setup(reader):
|
|
622
|
+
data = await reader.readexactly(1)
|
|
623
|
+
status = Status(ord(data))
|
|
624
|
+
if status == Status.Success:
|
|
625
|
+
head = "BxHHH"
|
|
626
|
+
head_len = struct.calcsize(head)
|
|
627
|
+
data += await reader.readexactly(head_len - 1)
|
|
628
|
+
status, proto_major, proto_minor, length = struct.unpack_from(head, data)
|
|
629
|
+
assert proto_major >= 11
|
|
630
|
+
length *= 4
|
|
631
|
+
data = await reader.readexactly(length)
|
|
632
|
+
return unpack_successfull_setup(data)[0]
|
|
633
|
+
elif status == Status.Failed:
|
|
634
|
+
head = "BBHHH"
|
|
635
|
+
head_len = struct.calcsize(head)
|
|
636
|
+
data += await reader.readexactly(head_len - 1)
|
|
637
|
+
status, _, proto_major, proto_minor, length = struct.unpack_from(head, data)
|
|
638
|
+
length *= 4
|
|
639
|
+
reason = (await reader.readexactly(length)).decode()
|
|
640
|
+
raise RuntimeError(reason)
|
|
641
|
+
elif status == Status.Authenticate:
|
|
642
|
+
head = "BxH"
|
|
643
|
+
head_len = struct.calcsize(head)
|
|
644
|
+
data = await reader.readexactly(head_len - 1)
|
|
645
|
+
status, length = struct.unpack_from(head, data)
|
|
646
|
+
n = ord(data[1]) * 4
|
|
647
|
+
reason = (await reader.readexactly(n)).decode()
|
|
648
|
+
raise ValueError(reason)
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def unpack_successfull_setup(data, offset=0):
|
|
652
|
+
fmt = "4I2H8B4x"
|
|
653
|
+
fields = (
|
|
654
|
+
"release",
|
|
655
|
+
"resource_id_base",
|
|
656
|
+
"resource_id_mask",
|
|
657
|
+
"motion_buffer_size",
|
|
658
|
+
"vendor_length",
|
|
659
|
+
"max_request_length",
|
|
660
|
+
"root_length",
|
|
661
|
+
"pixmap_length",
|
|
662
|
+
"image_byte_order",
|
|
663
|
+
"bitmap_format_bit_order",
|
|
664
|
+
"bitmap_format_scanline_unit",
|
|
665
|
+
"bitmap_format_scanline_pad",
|
|
666
|
+
"min_keycode",
|
|
667
|
+
"max_keycode",
|
|
668
|
+
)
|
|
669
|
+
setup = dict(zip(fields, struct.unpack_from(fmt, data, offset), strict=True))
|
|
670
|
+
offset += struct.calcsize(fmt)
|
|
671
|
+
vendor_length = setup["vendor_length"]
|
|
672
|
+
setup["vendor"] = struct.unpack_from(f"{vendor_length}s", data, offset)[0].decode()
|
|
673
|
+
offset += vendor_length
|
|
674
|
+
setup["pixmap_formats"] = pixmap_formats = []
|
|
675
|
+
for _ in range(setup["pixmap_length"]):
|
|
676
|
+
pixmap_format, offset = unpack_pixmap_format(data, offset)
|
|
677
|
+
pixmap_formats.append(pixmap_format)
|
|
678
|
+
|
|
679
|
+
setup["screens"] = screens = []
|
|
680
|
+
for _ in range(setup["root_length"]):
|
|
681
|
+
screen, offset = unpack_screen(data, offset)
|
|
682
|
+
screens.append(screen)
|
|
683
|
+
return setup, offset
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def unpack_pixmap_format(data, offset) -> tuple[dict, int]:
|
|
687
|
+
fmt = "BBB5x"
|
|
688
|
+
fields = "depth", "bits_per_pixel", "pad"
|
|
689
|
+
pixel_format = dict(zip(fields, struct.unpack_from(fmt, data, offset), strict=True))
|
|
690
|
+
offset += struct.calcsize(fmt)
|
|
691
|
+
return pixel_format, offset
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def unpack_screen(data, offset) -> tuple[dict, int]:
|
|
695
|
+
fields = (
|
|
696
|
+
"root",
|
|
697
|
+
"colormap",
|
|
698
|
+
"white_pixel",
|
|
699
|
+
"black_pixel",
|
|
700
|
+
"current_input_mask",
|
|
701
|
+
"width",
|
|
702
|
+
"height",
|
|
703
|
+
"width_in_mms",
|
|
704
|
+
"height_in_mms",
|
|
705
|
+
"min_installed_maps",
|
|
706
|
+
"max_installed_maps",
|
|
707
|
+
"root_visual",
|
|
708
|
+
"backing_store",
|
|
709
|
+
"save_unders",
|
|
710
|
+
"root_depth",
|
|
711
|
+
"depths_length",
|
|
712
|
+
)
|
|
713
|
+
fmt = "5I6HI4B"
|
|
714
|
+
screen = dict(zip(fields, struct.unpack_from(fmt, data, offset), strict=True))
|
|
715
|
+
offset += struct.calcsize(fmt)
|
|
716
|
+
screen["depths"] = depths = []
|
|
717
|
+
for _ in range(screen["depths_length"]):
|
|
718
|
+
depth, offset = unpack_depth(data, offset)
|
|
719
|
+
depths.append(depth)
|
|
720
|
+
return screen, offset
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def unpack_depth(data, offset) -> tuple[dict, int]:
|
|
724
|
+
fmt = "BxH4x"
|
|
725
|
+
fields = "depth", "nb_visuals"
|
|
726
|
+
depth = dict(zip(fields, struct.unpack_from(fmt, data, offset), strict=True))
|
|
727
|
+
offset += struct.calcsize(fmt)
|
|
728
|
+
depth["visuals"] = visuals = []
|
|
729
|
+
for _ in range(depth["nb_visuals"]):
|
|
730
|
+
visual, offset = unpack_visual(data, offset)
|
|
731
|
+
visuals.append(visual)
|
|
732
|
+
return depth, offset
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def unpack_visual(data, offset) -> tuple[dict, int]:
|
|
736
|
+
fmt = "IBBHIII4x"
|
|
737
|
+
fields = (
|
|
738
|
+
"visual_id",
|
|
739
|
+
"visual_class",
|
|
740
|
+
"bits_per_rgb_value",
|
|
741
|
+
"colormap_entries",
|
|
742
|
+
"red_mask",
|
|
743
|
+
"green_mask",
|
|
744
|
+
"blue_mask",
|
|
745
|
+
)
|
|
746
|
+
visual = dict(zip(fields, struct.unpack_from(fmt, data, offset), strict=True))
|
|
747
|
+
offset += struct.calcsize(fmt)
|
|
748
|
+
return visual, offset
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def pack_create_window(*args, **kwargs) -> bytes:
|
|
752
|
+
from Xlib.protocol.request import CreateWindow
|
|
753
|
+
|
|
754
|
+
return CreateWindow._request.to_binary(*args, **kwargs)
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def pack_map_window(wid) -> bytes:
|
|
758
|
+
from Xlib.protocol.request import MapWindow
|
|
759
|
+
|
|
760
|
+
return MapWindow._request.to_binary(wid)
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def pack_destroy_window(wid) -> bytes:
|
|
764
|
+
from Xlib.protocol.request import DestroyWindow
|
|
765
|
+
|
|
766
|
+
return DestroyWindow._request.to_binary(wid)
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def pack_intern_atom(name, only_if_exists=False):
|
|
770
|
+
from Xlib.protocol.request import InternAtom
|
|
771
|
+
|
|
772
|
+
return InternAtom._request.to_binary(name=name, only_if_exists=only_if_exists)
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def pack_poly_fill_rectangles(wid, gc, rectangles) -> bytes:
|
|
776
|
+
from Xlib.protocol.request import PolyFillRectangle
|
|
777
|
+
|
|
778
|
+
rectangles = [
|
|
779
|
+
r if isinstance(r, dict) else dict(zip(['x', 'y', 'width', 'height'], r))
|
|
780
|
+
for r in rectangles
|
|
781
|
+
]
|
|
782
|
+
|
|
783
|
+
return PolyFillRectangle._request.to_binary(wid, gc, rectangles)
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def unpack_reply_head(data: bytes, offset: int = 0) -> tuple[dict, int]:
|
|
788
|
+
fmt = "BBHI"
|
|
789
|
+
reply = struct.unpack_from(fmt, data, offset)
|
|
790
|
+
fields = "reply", "n", "sequence_number", "length"
|
|
791
|
+
return dict(zip(fields, reply, strict=True)), offset + struct.calcsize(fmt)
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def unpack_intern_atom(data: bytes, offset: int = 0) -> tuple[dict, int]:
|
|
795
|
+
result, offset = unpack_reply_head(data, offset)
|
|
796
|
+
assert result["reply"] == 1
|
|
797
|
+
fmt = "I20x"
|
|
798
|
+
(atom,) = struct.unpack_from(fmt, data, offset)
|
|
799
|
+
offset += struct.calcsize(fmt)
|
|
800
|
+
result["atom"] = atom
|
|
801
|
+
return result, offset
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def unpack_error(data: bytes, offset: int = 0):
|
|
805
|
+
fmt = "BBHIHB21x"
|
|
806
|
+
fields = "error", "code", "sequence_number", "value", "minor_opcode", "major_opcode"
|
|
807
|
+
reply = struct.unpack_from(fmt, data, offset)
|
|
808
|
+
offset += struct.calcsize(fmt)
|
|
809
|
+
result = dict(zip(fields, reply, strict=True))
|
|
810
|
+
result["code"] = ErrorCode(result["code"])
|
|
811
|
+
return result, offset
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
def unpack_event(event_map, data: bytes, offset: int = 0):
|
|
815
|
+
raw_event_type = data[0]
|
|
816
|
+
# Skip bit 8, that is set if this event came from an SendEvent
|
|
817
|
+
from_send = bool(raw_event_type & 0x80)
|
|
818
|
+
event_code = raw_event_type & 0x7F
|
|
819
|
+
if handler := event_map.get(event_code):
|
|
820
|
+
result, offset = handler(data, offset)
|
|
821
|
+
else:
|
|
822
|
+
fmt = "BBHIIII12x"
|
|
823
|
+
fields = "raw_code", "detail", "sequence_number", "time", "root", "event", "child"
|
|
824
|
+
reply = struct.unpack_from(fmt, data, offset)
|
|
825
|
+
offset += struct.calcsize(fmt)
|
|
826
|
+
result = dict(zip(fields, reply, strict=True))
|
|
827
|
+
result["name"] = Event(event_code).name if event_code in Event else event_code
|
|
828
|
+
result["code"] = event_code
|
|
829
|
+
result["send_event"] = from_send
|
|
830
|
+
return result, offset
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
def unpack_client_message(data, offset):
|
|
834
|
+
format = data[1]
|
|
835
|
+
fmt = "BBHII"
|
|
836
|
+
fields = "code", "format", "sequence_number", "window", "type"
|
|
837
|
+
reply = struct.unpack_from(fmt, data, offset)
|
|
838
|
+
offset += struct.calcsize(fmt)
|
|
839
|
+
result = dict(zip(fields, reply, strict=True))
|
|
840
|
+
data_fmt = "20B"
|
|
841
|
+
if format == 16:
|
|
842
|
+
data_fmt = "10H"
|
|
843
|
+
elif format == 32:
|
|
844
|
+
data_fmt = "5I"
|
|
845
|
+
result["data"] = struct.unpack_from(data_fmt, data, offset)
|
|
846
|
+
result["name"] = "ClientMessage"
|
|
847
|
+
offset += struct.calcsize(data_fmt)
|
|
848
|
+
return result, offset
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def pack_gc(cid, drawable, attrs):
|
|
852
|
+
from Xlib.protocol.request import CreateGC
|
|
853
|
+
|
|
854
|
+
return CreateGC._request.to_binary(cid=cid, drawable=drawable, attrs=attrs)
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def pack_change_property(wid, property, property_type, format, data, mode):
|
|
858
|
+
from Xlib.protocol.request import ChangeProperty
|
|
859
|
+
|
|
860
|
+
return ChangeProperty._request.to_binary(
|
|
861
|
+
mode=mode, window=wid, property=property, type=property_type, data=(format, data)
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def pack_list_extensions():
|
|
866
|
+
from Xlib.protocol.request import ListExtensions
|
|
867
|
+
|
|
868
|
+
return ListExtensions._request.to_binary()
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def unpack_list_extensions(data: bytes, offset: int = 0):
|
|
872
|
+
result, offset = unpack_reply_head(data, offset)
|
|
873
|
+
n = result["n"]
|
|
874
|
+
assert result["reply"] == 1
|
|
875
|
+
offset += 24
|
|
876
|
+
result["extensions"] = extensions = []
|
|
877
|
+
for _ in range(n):
|
|
878
|
+
(size,) = struct.unpack_from("B", data, offset)
|
|
879
|
+
offset += 1
|
|
880
|
+
(extension,) = struct.unpack_from(f"{size}s", data, offset)
|
|
881
|
+
offset += size
|
|
882
|
+
extensions.append(extension.decode())
|
|
883
|
+
return result, offset + 32 + result["length"] * 4
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def pack_query_extension(name: bytes) -> bytes:
|
|
887
|
+
from Xlib.protocol.request import QueryExtension
|
|
888
|
+
|
|
889
|
+
return QueryExtension._request.to_binary(name)
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
def unpack_query_extension(data: bytes, offset: int = 0):
|
|
893
|
+
result, offset = unpack_reply_head(data, offset)
|
|
894
|
+
assert result["reply"] == 1
|
|
895
|
+
fmt = "BBBB20x"
|
|
896
|
+
fields = "present", "opcode", "base_event", "base_error"
|
|
897
|
+
result.update(zip(fields, struct.unpack_from(fmt, data, offset), strict=True))
|
|
898
|
+
offset += struct.calcsize(fmt)
|
|
899
|
+
return result, offset
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
def pack_image(format, drawable, gc, w, h, x, y, left_pad, depth, data):
|
|
903
|
+
fmt = "BBHIIHHhhBB2x"
|
|
904
|
+
n = len(data)
|
|
905
|
+
p = pad(n)
|
|
906
|
+
length = 6 + (n + p) // 4
|
|
907
|
+
head = struct.pack(fmt, 72, format, length, drawable, gc, w, h, x, y, left_pad, depth)
|
|
908
|
+
return head, data, p * b"\x00"
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
def get_auth_config(filename: str) -> list[dict]:
|
|
912
|
+
with open(filename, "rb") as f:
|
|
913
|
+
raw = f.read()
|
|
914
|
+
|
|
915
|
+
offset, entries = 0, []
|
|
916
|
+
while offset < len(raw):
|
|
917
|
+
family, addrlen = struct.unpack_from(">HH", raw, offset)
|
|
918
|
+
offset += 4
|
|
919
|
+
addr = raw[offset : offset + addrlen]
|
|
920
|
+
offset += addrlen
|
|
921
|
+
(numlen,) = struct.unpack_from(">H", raw, offset)
|
|
922
|
+
offset += 2
|
|
923
|
+
num = raw[offset : offset + numlen]
|
|
924
|
+
offset += numlen
|
|
925
|
+
(namelen,) = struct.unpack_from(">H", raw, offset)
|
|
926
|
+
offset += 2
|
|
927
|
+
name = raw[offset : offset + namelen]
|
|
928
|
+
offset += namelen
|
|
929
|
+
(datalen,) = struct.unpack_from(">H", raw, offset)
|
|
930
|
+
offset += 2
|
|
931
|
+
data = raw[offset : offset + datalen]
|
|
932
|
+
offset += datalen
|
|
933
|
+
entries.append({"family": family, "address": addr, "port": num, "name": name, "data": data})
|
|
934
|
+
return entries
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def get_auth_filename(filename: str | None = None) -> str:
|
|
938
|
+
if filename is None:
|
|
939
|
+
if (filename := os.getenv("XAUTHORITY")) is None:
|
|
940
|
+
filename = f"{os.getenv('HOME')}/.Xauthority"
|
|
941
|
+
return filename
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
class Screen:
|
|
945
|
+
def __init__(self, display, info):
|
|
946
|
+
self.display = display
|
|
947
|
+
self.info = info
|
|
948
|
+
self.root = Window(display, info["root"])
|
|
949
|
+
|
|
950
|
+
def __getattr__(self, name):
|
|
951
|
+
return self.info[name]
|
|
952
|
+
|
|
953
|
+
async def create_window(
|
|
954
|
+
self,
|
|
955
|
+
x,
|
|
956
|
+
y,
|
|
957
|
+
width,
|
|
958
|
+
height,
|
|
959
|
+
border_width,
|
|
960
|
+
parent=None,
|
|
961
|
+
depth=None,
|
|
962
|
+
window_class=CopyFromParent,
|
|
963
|
+
visual=CopyFromParent,
|
|
964
|
+
**attrs,
|
|
965
|
+
):
|
|
966
|
+
if parent is None:
|
|
967
|
+
parent = self.root
|
|
968
|
+
if depth is None:
|
|
969
|
+
depth = self.root_depth
|
|
970
|
+
return await parent.create_window(x, y, width, height, border_width, depth, window_class, visual, **attrs)
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
class Resource:
|
|
974
|
+
def __init__(self, display, rid):
|
|
975
|
+
self.display = display
|
|
976
|
+
self.id = rid
|
|
977
|
+
|
|
978
|
+
def __int__(self):
|
|
979
|
+
return self.id
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
class Drawable(Resource):
|
|
983
|
+
async def create_gc(self, **kwargs) -> "GC":
|
|
984
|
+
cid = self.display.resources.allocate()
|
|
985
|
+
payload = pack_gc(cid=cid, drawable=self.id, attrs=kwargs)
|
|
986
|
+
self.display.connection.write(payload)
|
|
987
|
+
return GC(self.display, cid, self)
|
|
988
|
+
|
|
989
|
+
async def put_image(self, gc, x, y, w, h, depth, data, format=ImageFormat.ZPixmap, left_pad=0, drawable=None):
|
|
990
|
+
if drawable is None:
|
|
991
|
+
drawable = self
|
|
992
|
+
payloads = pack_image(format, int(drawable), int(gc), w, h, x, y, left_pad, depth, data)
|
|
993
|
+
payload = b"".join(payloads)
|
|
994
|
+
self.display.connection.write(payload)
|
|
995
|
+
|
|
996
|
+
async def draw_image(self, gc, x, y, w, h, depth, data, drawable=None):
|
|
997
|
+
max_size = self.display.info["max_request_length"]
|
|
998
|
+
nb_pixels = w * h
|
|
999
|
+
n = len(data)
|
|
1000
|
+
pixel_size = n // nb_pixels
|
|
1001
|
+
nb_bytes_per_line = w * pixel_size
|
|
1002
|
+
lines_per_packet = (max_size - 64) // nb_bytes_per_line
|
|
1003
|
+
data = memoryview(data)
|
|
1004
|
+
y1 = 0
|
|
1005
|
+
while y1 < h:
|
|
1006
|
+
start = y1 * nb_bytes_per_line
|
|
1007
|
+
end = (y1 + lines_per_packet) * nb_bytes_per_line
|
|
1008
|
+
d = data[start:end]
|
|
1009
|
+
lines = min(lines_per_packet, h - y1)
|
|
1010
|
+
await self.put_image(gc, x, y, w, lines, depth, d, drawable=drawable)
|
|
1011
|
+
y1 += lines_per_packet
|
|
1012
|
+
y += lines_per_packet
|
|
1013
|
+
|
|
1014
|
+
async def poly_fill_rectangles(self, gc, rectangles):
|
|
1015
|
+
payload = pack_poly_fill_rectangles(int(self), int(gc), rectangles)
|
|
1016
|
+
self.display.connection.write(payload)
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
class Window(Drawable):
|
|
1020
|
+
async def create_window(
|
|
1021
|
+
self,
|
|
1022
|
+
x,
|
|
1023
|
+
y,
|
|
1024
|
+
width,
|
|
1025
|
+
height,
|
|
1026
|
+
border_width,
|
|
1027
|
+
depth,
|
|
1028
|
+
window_class=CopyFromParent,
|
|
1029
|
+
visual=CopyFromParent,
|
|
1030
|
+
**attrs,
|
|
1031
|
+
):
|
|
1032
|
+
wid = self.display.resources.allocate()
|
|
1033
|
+
payload = pack_create_window(
|
|
1034
|
+
depth=depth,
|
|
1035
|
+
wid=wid,
|
|
1036
|
+
parent=self.id,
|
|
1037
|
+
x=x,
|
|
1038
|
+
y=y,
|
|
1039
|
+
width=width,
|
|
1040
|
+
height=height,
|
|
1041
|
+
border_width=border_width,
|
|
1042
|
+
window_class=window_class,
|
|
1043
|
+
visual=visual,
|
|
1044
|
+
attrs=attrs,
|
|
1045
|
+
)
|
|
1046
|
+
self.display.connection.write(payload)
|
|
1047
|
+
return Window(self.display, wid)
|
|
1048
|
+
|
|
1049
|
+
async def set_name(self, name):
|
|
1050
|
+
await self.change_text_property(Atom.WM_NAME, name)
|
|
1051
|
+
|
|
1052
|
+
async def change_text_property(self, property, data, property_type=Atom.STRING, mode=PropMode.Replace):
|
|
1053
|
+
if not isinstance(data, bytes):
|
|
1054
|
+
if property_type == Atom.STRING:
|
|
1055
|
+
data = data.encode(DEFAULT_STRING_ENCODING)
|
|
1056
|
+
elif property_type == self.display.get_atom("UTF8_STRING"):
|
|
1057
|
+
data = data.encode()
|
|
1058
|
+
await self.change_property(property, property_type, 8, data, mode)
|
|
1059
|
+
|
|
1060
|
+
async def map(self):
|
|
1061
|
+
payload = pack_map_window(self.id)
|
|
1062
|
+
self.display.connection.write(payload)
|
|
1063
|
+
|
|
1064
|
+
async def destroy(self):
|
|
1065
|
+
payload = pack_destroy_window(self.id)
|
|
1066
|
+
self.display.connection.write(payload)
|
|
1067
|
+
|
|
1068
|
+
async def change_property(self, property, property_type, format, data, mode=PropMode.Replace):
|
|
1069
|
+
payload = pack_change_property(self.id, property, property_type, format, data, mode)
|
|
1070
|
+
self.display.connection.write(payload)
|
|
1071
|
+
|
|
1072
|
+
async def set_wm_protocols(self, protocols):
|
|
1073
|
+
id = await self.display.get_atom("WM_PROTOCOLS")
|
|
1074
|
+
await self.change_property(id, Atom.ATOM, 32, protocols)
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
class GC(Resource):
|
|
1078
|
+
def __init__(self, display, rid, drawable):
|
|
1079
|
+
super().__init__(display, rid)
|
|
1080
|
+
self.drawable = drawable
|
|
1081
|
+
|
|
1082
|
+
|
|
1083
|
+
class LogBytes:
|
|
1084
|
+
__slots__ = ["data"]
|
|
1085
|
+
Limit = 32
|
|
1086
|
+
|
|
1087
|
+
def __init__(self, data):
|
|
1088
|
+
self.data = data
|
|
1089
|
+
|
|
1090
|
+
def __str__(self):
|
|
1091
|
+
if len(self.data) > self.Limit:
|
|
1092
|
+
return " ".join(f"{b:02X}" for b in self.data[: self.Limit]) + " [...]"
|
|
1093
|
+
return " ".join(f"{b:02X}" for b in self.data)
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
class Connection:
|
|
1097
|
+
def __init__(self, reader, writer, config):
|
|
1098
|
+
self.reader = reader
|
|
1099
|
+
self.writer = writer
|
|
1100
|
+
self.config = config
|
|
1101
|
+
|
|
1102
|
+
def close(self):
|
|
1103
|
+
self.writer.close()
|
|
1104
|
+
|
|
1105
|
+
async def aclose(self):
|
|
1106
|
+
self.writer.close()
|
|
1107
|
+
await self.writer.wait_closed()
|
|
1108
|
+
|
|
1109
|
+
def get_extra_info(self, item):
|
|
1110
|
+
return self.writer.get_extra_info(item)
|
|
1111
|
+
|
|
1112
|
+
@property
|
|
1113
|
+
def socket(self):
|
|
1114
|
+
return self.get_extra_info("socket")
|
|
1115
|
+
|
|
1116
|
+
@property
|
|
1117
|
+
def xfamily(self) -> Family | None:
|
|
1118
|
+
family = self.socket.family
|
|
1119
|
+
if family == socket.AF_UNIX:
|
|
1120
|
+
return Family.Local
|
|
1121
|
+
elif family == socket.AF_INET:
|
|
1122
|
+
return Family.Internet
|
|
1123
|
+
elif family == socket.AF_INET6:
|
|
1124
|
+
return Family.InternetV6
|
|
1125
|
+
|
|
1126
|
+
def write(self, payload):
|
|
1127
|
+
self.writer.write(payload)
|
|
1128
|
+
|
|
1129
|
+
def writelines(self, lines):
|
|
1130
|
+
l0 = lines[0]
|
|
1131
|
+
code = l0[0]
|
|
1132
|
+
if code in {LITTLE_ENDIAN, BIG_ENDIAN}:
|
|
1133
|
+
cmd_name = "Setup"
|
|
1134
|
+
else:
|
|
1135
|
+
cmd_name = Command(code).name
|
|
1136
|
+
log.debug("WRITEL(%3d): %s %s", len(lines), cmd_name, LogBytes(l0))
|
|
1137
|
+
self.writer.writelines(lines)
|
|
1138
|
+
|
|
1139
|
+
async def flush(self):
|
|
1140
|
+
return await self.writer.drain()
|
|
1141
|
+
|
|
1142
|
+
async def read(self, n=-1):
|
|
1143
|
+
return await self.reader.read(n)
|
|
1144
|
+
|
|
1145
|
+
async def readexactly(self, n):
|
|
1146
|
+
return await self.reader.readexactly(n)
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
async def open_connection(config: dict) -> Connection:
|
|
1150
|
+
protocol = config["protocol"]
|
|
1151
|
+
if protocol == "unix":
|
|
1152
|
+
path = UNIX_PATH_TEMPLATE.format(config["port"])
|
|
1153
|
+
r, w = await asyncio.open_unix_connection(path)
|
|
1154
|
+
elif protocol == "tcp":
|
|
1155
|
+
port = BASE_TCP_PORT + config["port"]
|
|
1156
|
+
r, w = await asyncio.open_connection(config["host"], port)
|
|
1157
|
+
else:
|
|
1158
|
+
raise ValueError(f"Unsupported protocol {protocol!r}")
|
|
1159
|
+
return Connection(r, w, config)
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
class Xauthority:
|
|
1163
|
+
def __init__(self, filename: str | None = None):
|
|
1164
|
+
self.filename = get_auth_filename(filename)
|
|
1165
|
+
self.entries = get_auth_config(self.filename)
|
|
1166
|
+
|
|
1167
|
+
def __call__(self, connection: Connection, types=(TYPE,)) -> dict:
|
|
1168
|
+
family = connection.xfamily
|
|
1169
|
+
if connection.config["protocol"] == "tcp":
|
|
1170
|
+
if connection.config["host"] == "localhost":
|
|
1171
|
+
family = Family.Local
|
|
1172
|
+
address = socket.gethostname().encode()
|
|
1173
|
+
else:
|
|
1174
|
+
if family == Family.Internet:
|
|
1175
|
+
octets = connection.socket.getpeername()[0].split(".")
|
|
1176
|
+
address = bytearray(int(x) for x in octets)
|
|
1177
|
+
elif family == Family.InternetV6:
|
|
1178
|
+
address = socket.gethostname().encode()
|
|
1179
|
+
else:
|
|
1180
|
+
address = socket.gethostname().encode()
|
|
1181
|
+
|
|
1182
|
+
port = connection.config["port"]
|
|
1183
|
+
bport = f"{port}".encode()
|
|
1184
|
+
|
|
1185
|
+
matches = {}
|
|
1186
|
+
for entry in self.entries:
|
|
1187
|
+
ename, eport = entry["name"], entry["port"]
|
|
1188
|
+
if not eport and entry["name"] not in matches:
|
|
1189
|
+
eport = bport
|
|
1190
|
+
if entry["family"] == family and entry["address"] == address and eport == bport:
|
|
1191
|
+
matches[ename] = entry
|
|
1192
|
+
for type_ in types:
|
|
1193
|
+
try:
|
|
1194
|
+
return matches[type_]
|
|
1195
|
+
except KeyError:
|
|
1196
|
+
pass
|
|
1197
|
+
raise ValueError("Cannot find auth")
|
|
1198
|
+
|
|
1199
|
+
|
|
1200
|
+
class ResourceManager:
|
|
1201
|
+
def __init__(self, base, mask):
|
|
1202
|
+
self.base = base
|
|
1203
|
+
self.mask = mask
|
|
1204
|
+
self.last = 0
|
|
1205
|
+
self.ids = set()
|
|
1206
|
+
|
|
1207
|
+
def allocate(self):
|
|
1208
|
+
i = self.last
|
|
1209
|
+
while i in self.ids:
|
|
1210
|
+
i = i + 1
|
|
1211
|
+
if i > self.mask:
|
|
1212
|
+
i = 0
|
|
1213
|
+
if i == self.last:
|
|
1214
|
+
raise RuntimeError("out of resource ids")
|
|
1215
|
+
self.ids.add(i)
|
|
1216
|
+
self.last = i
|
|
1217
|
+
return self.base | i
|
|
1218
|
+
|
|
1219
|
+
|
|
1220
|
+
class SharedMemoryOperation(enum.IntEnum):
|
|
1221
|
+
QueryVersion = 0
|
|
1222
|
+
Attach = 1
|
|
1223
|
+
Detach = 2
|
|
1224
|
+
PutImage = 3
|
|
1225
|
+
GetImage = 4
|
|
1226
|
+
CreatePixmap = 5
|
|
1227
|
+
AttachFd = 6
|
|
1228
|
+
CreateSegment = 7
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
def pack_shm_attach(opcode, rid, shmid, read_only=True):
|
|
1232
|
+
fmt = "BBHIIB3x"
|
|
1233
|
+
length = struct.calcsize(fmt) // 4
|
|
1234
|
+
return struct.pack(fmt, opcode, SharedMemoryOperation.Attach, length, rid, shmid, 1 if read_only else 0)
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
def pack_shm_attach_fd(opcode, fd, read_only=True):
|
|
1238
|
+
fmt = "BBHIB3x"
|
|
1239
|
+
length = struct.calcsize(fmt) // 4
|
|
1240
|
+
return struct.pack(fmt, opcode, SharedMemoryOperation.AttachFd, length, fd, 1 if read_only else 0)
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
def pack_shm_version(opcode):
|
|
1244
|
+
fmt = "BBH"
|
|
1245
|
+
length = struct.calcsize(fmt) // 4
|
|
1246
|
+
return struct.pack(fmt, opcode, SharedMemoryOperation.QueryVersion, length)
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
def unpack_shm_version(data, offset=0):
|
|
1250
|
+
result, offset = unpack_reply_head(data, offset)
|
|
1251
|
+
fmt = "HHHHB15x"
|
|
1252
|
+
fields = "major_version", "minor_version", "uid", "gid", "pixmap_format"
|
|
1253
|
+
result.update(zip(fields, struct.unpack_from(fmt, data, offset), strict=True))
|
|
1254
|
+
offset += struct.calcsize(fmt)
|
|
1255
|
+
result["shared_pixmaps"] = bool(result.pop("n"))
|
|
1256
|
+
return result, offset
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
def pack_shm_put_image(
|
|
1260
|
+
opcode,
|
|
1261
|
+
wnd,
|
|
1262
|
+
gc,
|
|
1263
|
+
total_width,
|
|
1264
|
+
total_height,
|
|
1265
|
+
src_x,
|
|
1266
|
+
src_y,
|
|
1267
|
+
src_width,
|
|
1268
|
+
src_height,
|
|
1269
|
+
dst_x,
|
|
1270
|
+
dst_y,
|
|
1271
|
+
depth,
|
|
1272
|
+
send_event,
|
|
1273
|
+
rid,
|
|
1274
|
+
offset,
|
|
1275
|
+
):
|
|
1276
|
+
fmt = "BBHIIHHHHHHhhBBBBII"
|
|
1277
|
+
length = struct.calcsize(fmt) // 4
|
|
1278
|
+
return struct.pack(
|
|
1279
|
+
fmt,
|
|
1280
|
+
opcode,
|
|
1281
|
+
SharedMemoryOperation.PutImage,
|
|
1282
|
+
length,
|
|
1283
|
+
wnd,
|
|
1284
|
+
gc,
|
|
1285
|
+
total_width,
|
|
1286
|
+
total_height,
|
|
1287
|
+
src_x,
|
|
1288
|
+
src_y,
|
|
1289
|
+
src_width,
|
|
1290
|
+
src_height,
|
|
1291
|
+
dst_x,
|
|
1292
|
+
dst_y,
|
|
1293
|
+
depth,
|
|
1294
|
+
ImageFormat.ZPixmap,
|
|
1295
|
+
send_event,
|
|
1296
|
+
0,
|
|
1297
|
+
rid,
|
|
1298
|
+
offset,
|
|
1299
|
+
)
|
|
1300
|
+
|
|
1301
|
+
|
|
1302
|
+
def unpack_shm_complete(data, offset=0):
|
|
1303
|
+
fmt = "BxHIHBxII12x"
|
|
1304
|
+
fields = "code", "sequence_number", "window", "minor", "major", "shm_id", "offset"
|
|
1305
|
+
size = struct.calcsize(fmt)
|
|
1306
|
+
result = dict(zip(fields, struct.unpack_from(fmt, data, offset), strict=True))
|
|
1307
|
+
result["name"] = "ShmComplete"
|
|
1308
|
+
return result, offset + size
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
class Extension:
|
|
1312
|
+
def __init__(self, display, info):
|
|
1313
|
+
self.display = display
|
|
1314
|
+
self.info = info
|
|
1315
|
+
self.code = info["opcode"]
|
|
1316
|
+
self.events = {}
|
|
1317
|
+
self.errors = {}
|
|
1318
|
+
self.commands = {}
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
class SharedMemory(Extension):
|
|
1322
|
+
BaseShmCompletion = 0
|
|
1323
|
+
BaseBadShmSeg = 0
|
|
1324
|
+
|
|
1325
|
+
def __init__(self, display, info):
|
|
1326
|
+
super().__init__(display, info)
|
|
1327
|
+
self.ShmCompletion = info["base_event"] + self.BaseShmCompletion
|
|
1328
|
+
self.BadShmSeg = info["base_error"] + self.BaseBadShmSeg
|
|
1329
|
+
self.events = {self.ShmCompletion: unpack_shm_complete}
|
|
1330
|
+
self.errors = {self.BadShmSeg: "BadShmSeg"}
|
|
1331
|
+
self.commands = {self.code: "Shm"}
|
|
1332
|
+
|
|
1333
|
+
async def version(self):
|
|
1334
|
+
payload = pack_shm_version(self.code)
|
|
1335
|
+
reply = await self.display.write_read(payload, unpack_shm_version)
|
|
1336
|
+
return reply
|
|
1337
|
+
|
|
1338
|
+
async def attach(self, shm_id, read_only=True):
|
|
1339
|
+
rid = self.display.resources.allocate()
|
|
1340
|
+
payload = pack_shm_attach(self.code, rid, shm_id, read_only)
|
|
1341
|
+
self.display.write(payload)
|
|
1342
|
+
return rid
|
|
1343
|
+
|
|
1344
|
+
async def attach_fd(self, fd, read_only=True):
|
|
1345
|
+
rid = self.display.resources.allocate()
|
|
1346
|
+
payload = pack_shm_attach_fd(self.code, rid, read_only)
|
|
1347
|
+
sock = self.display.connection.socket._sock
|
|
1348
|
+
socket.send_fds(sock, (payload,), (fd,))
|
|
1349
|
+
return rid
|
|
1350
|
+
|
|
1351
|
+
async def put_image(
|
|
1352
|
+
self,
|
|
1353
|
+
wnd,
|
|
1354
|
+
gc,
|
|
1355
|
+
total_width,
|
|
1356
|
+
total_height,
|
|
1357
|
+
src_x,
|
|
1358
|
+
src_y,
|
|
1359
|
+
src_width,
|
|
1360
|
+
src_height,
|
|
1361
|
+
dst_x,
|
|
1362
|
+
dst_y,
|
|
1363
|
+
depth,
|
|
1364
|
+
send_event,
|
|
1365
|
+
rid,
|
|
1366
|
+
offset,
|
|
1367
|
+
):
|
|
1368
|
+
payload = pack_shm_put_image(
|
|
1369
|
+
self.code,
|
|
1370
|
+
int(wnd),
|
|
1371
|
+
int(gc),
|
|
1372
|
+
total_width,
|
|
1373
|
+
total_height,
|
|
1374
|
+
src_x,
|
|
1375
|
+
src_y,
|
|
1376
|
+
src_width,
|
|
1377
|
+
src_height,
|
|
1378
|
+
dst_x,
|
|
1379
|
+
dst_y,
|
|
1380
|
+
depth,
|
|
1381
|
+
send_event,
|
|
1382
|
+
rid,
|
|
1383
|
+
offset,
|
|
1384
|
+
)
|
|
1385
|
+
self.display.write(payload)
|
|
1386
|
+
|
|
1387
|
+
|
|
1388
|
+
EXTENSIONS = {"MIT-SHM": SharedMemory}
|
|
1389
|
+
|
|
1390
|
+
|
|
1391
|
+
class Display:
|
|
1392
|
+
def __init__(self, connection, config):
|
|
1393
|
+
self.connection = connection
|
|
1394
|
+
self.config = config
|
|
1395
|
+
self.info = None
|
|
1396
|
+
self.resources = None
|
|
1397
|
+
self.screens = None
|
|
1398
|
+
self.events = {Event.ClientMessage: unpack_client_message}
|
|
1399
|
+
self.commands = {cmd: cmd.name for cmd in Command}
|
|
1400
|
+
self.requests = asyncio.Queue()
|
|
1401
|
+
self.listeners = set()
|
|
1402
|
+
self._atom_cache = {}
|
|
1403
|
+
self._extensions_cache = None
|
|
1404
|
+
self._task = None
|
|
1405
|
+
|
|
1406
|
+
async def __aenter__(self):
|
|
1407
|
+
await self.setup()
|
|
1408
|
+
return self
|
|
1409
|
+
|
|
1410
|
+
async def __aexit__(self, *args):
|
|
1411
|
+
await self.aclose()
|
|
1412
|
+
|
|
1413
|
+
def close(self):
|
|
1414
|
+
if self._task:
|
|
1415
|
+
self._task.cancel()
|
|
1416
|
+
self.connection.close()
|
|
1417
|
+
|
|
1418
|
+
async def aclose(self):
|
|
1419
|
+
if self._task:
|
|
1420
|
+
self._task.cancel()
|
|
1421
|
+
try:
|
|
1422
|
+
await self._task
|
|
1423
|
+
except asyncio.CancelledError:
|
|
1424
|
+
pass
|
|
1425
|
+
self._task = None
|
|
1426
|
+
await self.connection.aclose()
|
|
1427
|
+
|
|
1428
|
+
def write(self, payload):
|
|
1429
|
+
code = payload[0]
|
|
1430
|
+
cmd_name = code
|
|
1431
|
+
if code in {LITTLE_ENDIAN, BIG_ENDIAN}:
|
|
1432
|
+
cmd_name = "Setup"
|
|
1433
|
+
else:
|
|
1434
|
+
cmd_name = self.commands.get(code)
|
|
1435
|
+
log.debug("WRITE: %s", cmd_name)
|
|
1436
|
+
self.connection.write(payload)
|
|
1437
|
+
|
|
1438
|
+
async def read(self, n=-1):
|
|
1439
|
+
return await self.connection.read(n)
|
|
1440
|
+
|
|
1441
|
+
async def readexactly(self, n):
|
|
1442
|
+
payload = await self.connection.readexactly(n)
|
|
1443
|
+
log.debug("READ: %d", len(payload))
|
|
1444
|
+
return payload
|
|
1445
|
+
|
|
1446
|
+
def write_read(self, payload, callback):
|
|
1447
|
+
future = asyncio.Future()
|
|
1448
|
+
code = payload[0]
|
|
1449
|
+
cmd = Command(code).name if code in Command else code
|
|
1450
|
+
self.requests.put_nowait((callback, future, cmd))
|
|
1451
|
+
self.write(payload)
|
|
1452
|
+
return future
|
|
1453
|
+
|
|
1454
|
+
async def flush(self):
|
|
1455
|
+
await self.connection.flush()
|
|
1456
|
+
|
|
1457
|
+
async def setup(self):
|
|
1458
|
+
entry = auth(self.connection)
|
|
1459
|
+
req = pack_setup(entry["name"], entry["data"])
|
|
1460
|
+
self.write(req)
|
|
1461
|
+
await self.flush()
|
|
1462
|
+
info = await read_setup(self)
|
|
1463
|
+
self.info = info
|
|
1464
|
+
self.resources = ResourceManager(info["resource_id_base"], info["resource_id_mask"])
|
|
1465
|
+
self.screens = [Screen(self, sinfo) for sinfo in info["screens"]]
|
|
1466
|
+
self._task = asyncio.create_task(self._loop())
|
|
1467
|
+
return info
|
|
1468
|
+
|
|
1469
|
+
async def intern_atom(self, name, only_if_exists=False):
|
|
1470
|
+
payload = pack_intern_atom(name, only_if_exists)
|
|
1471
|
+
reply = await self.write_read(payload, unpack_intern_atom)
|
|
1472
|
+
return reply["atom"]
|
|
1473
|
+
|
|
1474
|
+
async def _list_extensions(self):
|
|
1475
|
+
payload = pack_list_extensions()
|
|
1476
|
+
reply = await self.write_read(payload, unpack_list_extensions)
|
|
1477
|
+
return reply["extensions"]
|
|
1478
|
+
|
|
1479
|
+
async def _query_extension(self, name):
|
|
1480
|
+
payload = pack_query_extension(name)
|
|
1481
|
+
reply = await self.write_read(payload, unpack_query_extension)
|
|
1482
|
+
return reply
|
|
1483
|
+
|
|
1484
|
+
async def list_extensions(self):
|
|
1485
|
+
if self._extensions_cache is None:
|
|
1486
|
+
self._extensions_cache = dict.fromkeys(await self._list_extensions())
|
|
1487
|
+
return set(self._extensions_cache)
|
|
1488
|
+
|
|
1489
|
+
async def query_extension(self, name):
|
|
1490
|
+
extensions = await self.list_extensions()
|
|
1491
|
+
if name in extensions:
|
|
1492
|
+
if ext := self._extensions_cache[name]:
|
|
1493
|
+
return ext
|
|
1494
|
+
ext = await self._query_extension(name)
|
|
1495
|
+
if klass := EXTENSIONS.get(name):
|
|
1496
|
+
ext = klass(self, ext)
|
|
1497
|
+
self._extensions_cache[name] = ext
|
|
1498
|
+
self.events.update(ext.events)
|
|
1499
|
+
self.commands.update(ext.commands)
|
|
1500
|
+
return ext
|
|
1501
|
+
|
|
1502
|
+
async def get_atom(self, name, only_if_exists=False):
|
|
1503
|
+
if name in self._atom_cache:
|
|
1504
|
+
return self._atom_cache[name]
|
|
1505
|
+
|
|
1506
|
+
atom = await self.intern_atom(name, only_if_exists)
|
|
1507
|
+
if atom != NONE: # don't cache NONE responses in case someone creates this later
|
|
1508
|
+
self._atom_cache[name] = atom
|
|
1509
|
+
return atom
|
|
1510
|
+
|
|
1511
|
+
def default_screen(self) -> Screen:
|
|
1512
|
+
return self.screens[self.config["screen"]]
|
|
1513
|
+
|
|
1514
|
+
async def create_window(self, *args, **kwargs) -> Window:
|
|
1515
|
+
parent = self.default_screen().root
|
|
1516
|
+
return await parent.create_window(*args, **kwargs)
|
|
1517
|
+
|
|
1518
|
+
def add_listener(self, listener):
|
|
1519
|
+
self.listeners.add(listener)
|
|
1520
|
+
|
|
1521
|
+
async def _loop(self):
|
|
1522
|
+
buffer = b""
|
|
1523
|
+
while True:
|
|
1524
|
+
if not (data := await self.read(16 * 1024)):
|
|
1525
|
+
log.info("Connection closed")
|
|
1526
|
+
return
|
|
1527
|
+
buffer += data
|
|
1528
|
+
packet_type = buffer[0]
|
|
1529
|
+
if packet_type in PacketType:
|
|
1530
|
+
if packet_type == PacketType.Reply:
|
|
1531
|
+
decode, future, cmd = await self.requests.get()
|
|
1532
|
+
reply, offset = decode(buffer, 0)
|
|
1533
|
+
log.debug("REPLY: %s", cmd)
|
|
1534
|
+
future.set_result(reply)
|
|
1535
|
+
elif packet_type == PacketType.Error:
|
|
1536
|
+
error, offset = unpack_error(buffer)
|
|
1537
|
+
log.warning("ERROR: %s", error)
|
|
1538
|
+
else:
|
|
1539
|
+
event, offset = unpack_event(self.events, buffer)
|
|
1540
|
+
for listener in self.listeners:
|
|
1541
|
+
await listener.put(event)
|
|
1542
|
+
log.debug("EVENT: %s", event["name"])
|
|
1543
|
+
buffer = buffer[offset:]
|
|
1544
|
+
|
|
1545
|
+
|
|
1546
|
+
def get_display_config(uri: str) -> dict:
|
|
1547
|
+
if (match := DISPLAY_RE.match(uri)) is None:
|
|
1548
|
+
raise ValueError(f"Invalid DISPLAY {uri!r}")
|
|
1549
|
+
display = match.groupdict()
|
|
1550
|
+
protocol = display.get("protocol")
|
|
1551
|
+
if protocol:
|
|
1552
|
+
if protocol == "unix" and display["host"]:
|
|
1553
|
+
raise ValueError(f"Invalid DISPLAY {uri!r}")
|
|
1554
|
+
else:
|
|
1555
|
+
protocol = "tcp" if display["host"] else "unix"
|
|
1556
|
+
display["protocol"] = protocol
|
|
1557
|
+
display["port"] = int(display["port"])
|
|
1558
|
+
display["uri"] = uri
|
|
1559
|
+
display["screen"] = display["screen"] or 0
|
|
1560
|
+
return display
|
|
1561
|
+
|
|
1562
|
+
|
|
1563
|
+
async def get_display(uri: str | None = None) -> Display:
|
|
1564
|
+
uri = uri or os.environ["DISPLAY"]
|
|
1565
|
+
config = get_display_config(uri)
|
|
1566
|
+
connection = await open_connection(config)
|
|
1567
|
+
display = Display(connection, config)
|
|
1568
|
+
return display
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
auth = Xauthority()
|