44
55from __future__ import annotations
66
7+ import locale
78import os
89from contextlib import suppress
910from ctypes import (
1011 CFUNCTYPE ,
1112 POINTER ,
1213 Structure ,
14+ _Pointer ,
1315 byref ,
1416 c_char_p ,
1517 c_int ,
16- c_int32 ,
17- c_long ,
1818 c_short ,
1919 c_ubyte ,
2020 c_uint ,
21- c_uint32 ,
2221 c_ulong ,
2322 c_ushort ,
2423 c_void_p ,
4039__all__ = ("MSS" ,)
4140
4241
42+ X_FIRST_EXTENSION_OPCODE = 128
4343PLAINMASK = 0x00FFFFFF
4444ZPIXMAP = 2
4545BITS_PER_PIXELS_32 = 32
4848}
4949
5050
51+ class XID (c_ulong ):
52+ """X11 generic resource ID
53+ https://tronche.com/gui/x/xlib/introduction/generic.html
54+ https://gitlab.freedesktop.org/xorg/proto/xorgproto/-/blob/master/include/X11/X.h#L66
55+ """
56+
57+
58+ class XStatus (c_int ):
59+ """Xlib common return code type
60+ This is Status in Xlib, but XStatus here to prevent ambiguity.
61+ Zero is an error, non-zero is success.
62+ https://tronche.com/gui/x/xlib/introduction/errors.html
63+ https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/include/X11/Xlib.h#L79
64+ """
65+
66+
67+ class XBool (c_int ):
68+ """Xlib boolean type
69+ This is Bool in Xlib, but XBool here to prevent ambiguity.
70+ 0 is False, 1 is True.
71+ https://tronche.com/gui/x/xlib/introduction/generic.html
72+ https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/include/X11/Xlib.h#L78
73+ """
74+
75+
5176class Display (Structure ):
5277 """Structure that serves as the connection to the X server
5378 and that contains all the information about that X server.
79+ The contents of this structure are implementation dependent.
80+ A Display should be treated as opaque by application code.
81+ https://tronche.com/gui/x/xlib/display/display-macros.html
82+ https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/include/X11/Xlib.h#L477
5483 https://github.com/garrybodsworth/pyxlib-ctypes/blob/master/pyxlib/xlib.py#L831.
5584 """
5685
86+ # Opaque data
87+
88+
89+ class Visual (Structure ):
90+ """Visual structure; contains information about colormapping possible.
91+ https://tronche.com/gui/x/xlib/window/visual-types.html
92+ https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/include/X11/Xlib.hheads#L220
93+ https://github.com/garrybodsworth/pyxlib-ctypes/blob/master/pyxlib/xlib.py#302.
94+ """
95+
96+ # Opaque data (per Tronche)
97+
98+
99+ class Screen (Structure ):
100+ """Information about the screen.
101+ The contents of this structure are implementation dependent. A
102+ Screen should be treated as opaque by application code.
103+ https://tronche.com/gui/x/xlib/display/screen-information.html
104+ https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/include/X11/Xlib.h#L253
105+ """
106+
107+ # Opaque data
108+
57109
58110class XErrorEvent (Structure ):
59111 """XErrorEvent to debug eventual errors.
60112 https://tronche.com/gui/x/xlib/event-handling/protocol-errors/default-handlers.html.
113+ https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/include/X11/Xlib.h#L920
61114 """
62115
63116 _fields_ = (
64117 ("type" , c_int ),
65118 ("display" , POINTER (Display )), # Display the event was read from
119+ ("resourceid" , XID ), # resource ID
66120 ("serial" , c_ulong ), # serial number of failed request
67121 ("error_code" , c_ubyte ), # error code of failed request
68122 ("request_code" , c_ubyte ), # major op-code of failed request
69123 ("minor_code" , c_ubyte ), # minor op-code of failed request
70- ("resourceid" , c_void_p ), # resource ID
71124 )
72125
73126
@@ -93,7 +146,8 @@ class XFixesCursorImage(Structure):
93146
94147class XImage (Structure ):
95148 """Description of an image as it exists in the client's memory.
96- https://tronche.com/gui/x/xlib/graphics/images.html.
149+ https://tronche.com/gui/x/xlib/graphics/images.html
150+ https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/include/X11/Xlib.h#L353
97151 """
98152
99153 _fields_ = (
@@ -113,6 +167,7 @@ class XImage(Structure):
113167 ("green_mask" , c_ulong ), # bits in z arrangement
114168 ("blue_mask" , c_ulong ), # bits in z arrangement
115169 )
170+ # Other opaque fields follow for Xlib's internal use.
116171
117172
118173class XRRCrtcInfo (Structure ):
@@ -126,19 +181,21 @@ class XRRCrtcInfo(Structure):
126181 ("y" , c_int ),
127182 ("width" , c_uint ),
128183 ("height" , c_uint ),
129- ("mode" , c_long ),
130- ("rotation" , c_int ),
184+ ("mode" , XID ),
185+ ("rotation" , c_ushort ),
131186 ("noutput" , c_int ),
132- ("outputs" , POINTER (c_long )),
187+ ("outputs" , POINTER (XID )),
133188 ("rotations" , c_ushort ),
134189 ("npossible" , c_int ),
135- ("possible" , POINTER (c_long )),
190+ ("possible" , POINTER (XID )),
136191 )
137192
138193
139194class XRRModeInfo (Structure ):
140195 """https://gitlab.freedesktop.org/xorg/lib/libxrandr/-/blob/master/include/X11/extensions/Xrandr.h#L248."""
141196
197+ # The fields aren't needed
198+
142199
143200class XRRScreenResources (Structure ):
144201 """Structure that contains arrays of XIDs that point to the
@@ -150,41 +207,44 @@ class XRRScreenResources(Structure):
150207 ("timestamp" , c_ulong ),
151208 ("configTimestamp" , c_ulong ),
152209 ("ncrtc" , c_int ),
153- ("crtcs" , POINTER (c_long )),
210+ ("crtcs" , POINTER (XID )),
154211 ("noutput" , c_int ),
155- ("outputs" , POINTER (c_long )),
212+ ("outputs" , POINTER (XID )),
156213 ("nmode" , c_int ),
157214 ("modes" , POINTER (XRRModeInfo )),
158215 )
159216
160217
161218class XWindowAttributes (Structure ):
162- """Attributes for the specified window."""
219+ """Attributes for the specified window.
220+ https://tronche.com/gui/x/xlib/window-information/XGetWindowAttributes.html
221+ https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/include/X11/Xlib.h#L304
222+ """
163223
164224 _fields_ = (
165- ("x" , c_int32 ), # location of window
166- ("y" , c_int32 ), # location of window
167- ("width" , c_int32 ), # width of window
168- ("height" , c_int32 ), # height of window
169- ("border_width" , c_int32 ), # border width of window
170- ("depth" , c_int32 ), # depth of window
171- ("visual" , c_ulong ), # the associated visual structure
172- ("root" , c_ulong ), # root of screen containing window
173- ("class" , c_int32 ), # InputOutput, InputOnly
174- ("bit_gravity" , c_int32 ), # one of bit gravity values
175- ("win_gravity" , c_int32 ), # one of the window gravity values
176- ("backing_store" , c_int32 ), # NotUseful, WhenMapped, Always
225+ ("x" , c_int ), # location of window
226+ ("y" , c_int ), # location of window
227+ ("width" , c_int ), # width of window
228+ ("height" , c_int ), # height of window
229+ ("border_width" , c_int ), # border width of window
230+ ("depth" , c_int ), # depth of window
231+ ("visual" , POINTER ( Visual ) ), # the associated visual structure
232+ ("root" , XID ), # root of screen containing window
233+ ("class" , c_int ), # InputOutput, InputOnly
234+ ("bit_gravity" , c_int ), # one of bit gravity values
235+ ("win_gravity" , c_int ), # one of the window gravity values
236+ ("backing_store" , c_int ), # NotUseful, WhenMapped, Always
177237 ("backing_planes" , c_ulong ), # planes to be preserved if possible
178238 ("backing_pixel" , c_ulong ), # value to be used when restoring planes
179- ("save_under" , c_int32 ), # boolean, should bits under be saved?
180- ("colormap" , c_ulong ), # color map to be associated with window
181- ("mapinstalled" , c_uint32 ), # boolean, is color map currently installed
182- ("map_state" , c_uint32 ), # IsUnmapped, IsUnviewable, IsViewable
239+ ("save_under" , XBool ), # boolean, should bits under be saved?
240+ ("colormap" , XID ), # color map to be associated with window
241+ ("mapinstalled" , XBool ), # boolean, is color map currently installed
242+ ("map_state" , c_uint ), # IsUnmapped, IsUnviewable, IsViewable
183243 ("all_event_masks" , c_ulong ), # set of events all people have interest in
184244 ("your_event_mask" , c_ulong ), # my event mask
185245 ("do_not_propagate_mask" , c_ulong ), # set of events that should not propagate
186- ("override_redirect" , c_int32 ), # boolean value for override-redirect
187- ("screen" , c_ulong ), # back pointer to correct screen
246+ ("override_redirect" , XBool ), # boolean value for override-redirect
247+ ("screen" , POINTER ( Screen ) ), # back pointer to correct screen
188248 )
189249
190250
@@ -194,6 +254,29 @@ class XWindowAttributes(Structure):
194254_XRANDR = find_library ("Xrandr" )
195255
196256
257+ class XError (ScreenShotError ):
258+ def __str__ (self ) -> str :
259+ msg = super ().__str__ ()
260+ # The details only get populated if the X11 error handler is invoked, but not if a function simply returns
261+ # a failure status.
262+ if self .details :
263+ # We use something similar to the default Xlib error handler's format, since that's quite well-understood.
264+ # The original code is in
265+ # https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/src/XlibInt.c?ref_type=heads#L1313
266+ # but we don't try to implement most of it.
267+ msg += (
268+ f"\n X Error of failed request: { self .details ['error' ]} "
269+ f"\n Major opcode of failed request: { self .details ['request_code' ]} ({ self .details ['request' ]} )"
270+ )
271+ if self .details ["request_code" ] >= X_FIRST_EXTENSION_OPCODE :
272+ msg += f"\n Minor opcode of failed request: { self .details ['minor_code' ]} "
273+ msg += (
274+ f"\n Resource id in failed request: { self .details ['resourceid' ]} "
275+ f"\n Serial number of failed request: { self .details ['serial' ]} "
276+ )
277+ return msg
278+
279+
197280@CFUNCTYPE (c_int , POINTER (Display ), POINTER (XErrorEvent ))
198281def _error_handler (display : Display , event : XErrorEvent ) -> int :
199282 """Specifies the program's supplied error handler."""
@@ -202,32 +285,65 @@ def _error_handler(display: Display, event: XErrorEvent) -> int:
202285 get_error = xlib .XGetErrorText
203286 get_error .argtypes = [POINTER (Display ), c_int , c_char_p , c_int ]
204287 get_error .restype = c_void_p
288+ get_error_database = xlib .XGetErrorDatabaseText
289+ get_error_database .argtypes = [POINTER (Display ), c_char_p , c_char_p , c_char_p , c_char_p , c_int ]
290+ get_error_database .restype = c_int
205291
206292 evt = event .contents
207293 error = create_string_buffer (1024 )
208294 get_error (display , evt .error_code , error , len (error ))
209-
295+ request = create_string_buffer (1024 )
296+ get_error_database (display , b"XRequest" , b"%i" % evt .request_code , b"Extension-specific" , request , len (request ))
297+ # We don't try to get the string forms of the extension name or minor code currently. Those are important
298+ # fields for debugging, but getting the strings is difficult. The call stack of the exception gives pretty
299+ # useful similar information, though; most of the requests we use are synchronous, so the failing request is
300+ # usually the function being called.
301+
302+ encoding = (
303+ locale .getencoding () if hasattr (locale , "getencoding" ) else locale .getpreferredencoding (do_setlocale = False )
304+ )
210305 _ERROR [current_thread ()] = {
211- "error" : error .value .decode ("utf-8 " ),
306+ "error" : error .value .decode (encoding , errors = "replace " ),
212307 "error_code" : evt .error_code ,
213308 "minor_code" : evt .minor_code ,
309+ "request" : request .value .decode (encoding , errors = "replace" ),
214310 "request_code" : evt .request_code ,
215311 "serial" : evt .serial ,
312+ "resourceid" : evt .resourceid ,
216313 "type" : evt .type ,
217314 }
218315
219316 return 0
220317
221318
222- def _validate (retval : int , func : Any , args : tuple [Any , Any ], / ) -> tuple [Any , Any ]:
223- """Validate the returned value of a C function call."""
319+ def _validate_x11 (
320+ retval : _Pointer | None | XBool | XStatus | XID | int , func : Any , args : tuple [Any , Any ], /
321+ ) -> tuple [Any , Any ]:
224322 thread = current_thread ()
225- if retval != 0 and thread not in _ERROR :
323+
324+ if retval is None :
325+ # A void return is always ok.
326+ is_ok = True
327+ elif isinstance (retval , (_Pointer , XBool , XStatus , XID )):
328+ # A pointer should be non-NULL. A boolean should be true. An Xlib Status should be non-zero.
329+ # An XID should not be None, which is a reserved ID used for certain APIs.
330+ is_ok = bool (retval )
331+ elif isinstance (retval , int ):
332+ # There are currently two functions we call that return ints. XDestroyImage returns 1 always, and
333+ # XCloseDisplay returns 0 always. Neither can fail. Other Xlib functions might return ints with other
334+ # interpretations. If we didn't get an X error from the server, then we'll assume that they worked.
335+ is_ok = True
336+ else :
337+ msg = f"Internal error: cannot check return type { type (retval )} "
338+ raise AssertionError (msg )
339+
340+ # Regardless of the return value, raise an error if the thread got an Xlib error (possibly from an earlier call).
341+ if is_ok and thread not in _ERROR :
226342 return args
227343
228344 details = _ERROR .pop (thread , {})
229345 msg = f"{ func .__name__ } () failed"
230- raise ScreenShotError (msg , details = details )
346+ raise XError (msg , details = details )
231347
232348
233349# C functions that will be initialised later.
@@ -238,24 +354,24 @@ def _validate(retval: int, func: Any, args: tuple[Any, Any], /) -> tuple[Any, An
238354# Note: keep it sorted by cfunction.
239355CFUNCTIONS : CFunctions = {
240356 # Syntax: cfunction: (attr, argtypes, restype)
241- "XCloseDisplay" : ("xlib" , [POINTER (Display )], c_void_p ),
242- "XDefaultRootWindow" : ("xlib" , [POINTER (Display )], POINTER ( XWindowAttributes ) ),
243- "XDestroyImage" : ("xlib" , [POINTER (XImage )], c_void_p ),
357+ "XCloseDisplay" : ("xlib" , [POINTER (Display )], c_int ),
358+ "XDefaultRootWindow" : ("xlib" , [POINTER (Display )], XID ),
359+ "XDestroyImage" : ("xlib" , [POINTER (XImage )], c_int ),
244360 "XFixesGetCursorImage" : ("xfixes" , [POINTER (Display )], POINTER (XFixesCursorImage )),
245361 "XGetImage" : (
246362 "xlib" ,
247- [POINTER (Display ), POINTER ( Display ) , c_int , c_int , c_uint , c_uint , c_ulong , c_int ],
363+ [POINTER (Display ), XID , c_int , c_int , c_uint , c_uint , c_ulong , c_int ],
248364 POINTER (XImage ),
249365 ),
250- "XGetWindowAttributes" : ("xlib" , [POINTER (Display ), POINTER ( XWindowAttributes ) , POINTER (XWindowAttributes )], c_int ),
366+ "XGetWindowAttributes" : ("xlib" , [POINTER (Display ), XID , POINTER (XWindowAttributes )], XStatus ),
251367 "XOpenDisplay" : ("xlib" , [c_char_p ], POINTER (Display )),
252- "XQueryExtension" : ("xlib" , [POINTER (Display ), c_char_p , POINTER (c_int ), POINTER (c_int ), POINTER (c_int )], c_uint ),
253- "XRRQueryVersion" : ("xrandr" , [POINTER (Display ), POINTER (c_int ), POINTER (c_int )], c_int ),
254- "XRRFreeCrtcInfo" : ("xrandr" , [POINTER (XRRCrtcInfo )], c_void_p ),
255- "XRRFreeScreenResources" : ("xrandr" , [POINTER (XRRScreenResources )], c_void_p ),
256- "XRRGetCrtcInfo" : ("xrandr" , [POINTER (Display ), POINTER (XRRScreenResources ), c_long ], POINTER (XRRCrtcInfo )),
257- "XRRGetScreenResources" : ("xrandr" , [POINTER (Display ), POINTER ( Display ) ], POINTER (XRRScreenResources )),
258- "XRRGetScreenResourcesCurrent" : ("xrandr" , [POINTER (Display ), POINTER ( Display ) ], POINTER (XRRScreenResources )),
368+ "XQueryExtension" : ("xlib" , [POINTER (Display ), c_char_p , POINTER (c_int ), POINTER (c_int ), POINTER (c_int )], XBool ),
369+ "XRRQueryVersion" : ("xrandr" , [POINTER (Display ), POINTER (c_int ), POINTER (c_int )], XStatus ),
370+ "XRRFreeCrtcInfo" : ("xrandr" , [POINTER (XRRCrtcInfo )], None ),
371+ "XRRFreeScreenResources" : ("xrandr" , [POINTER (XRRScreenResources )], None ),
372+ "XRRGetCrtcInfo" : ("xrandr" , [POINTER (Display ), POINTER (XRRScreenResources ), XID ], POINTER (XRRCrtcInfo )),
373+ "XRRGetScreenResources" : ("xrandr" , [POINTER (Display ), XID ], POINTER (XRRScreenResources )),
374+ "XRRGetScreenResourcesCurrent" : ("xrandr" , [POINTER (Display ), XID ], POINTER (XRRScreenResources )),
259375 "XSetErrorHandler" : ("xlib" , [c_void_p ], c_void_p ),
260376}
261377
@@ -323,11 +439,7 @@ def __init__(self, /, **kwargs: Any) -> None:
323439 msg = "Xrandr not enabled."
324440 raise ScreenShotError (msg )
325441
326- self ._handles .root = self .xlib .XDefaultRootWindow (self ._handles .display )
327-
328- # Fix for XRRGetScreenResources and XGetImage:
329- # expected LP_Display instance instead of LP_XWindowAttributes
330- self ._handles .drawable = cast (self ._handles .root , POINTER (Display ))
442+ self ._handles .drawable = self ._handles .root = self .xlib .XDefaultRootWindow (self ._handles .display )
331443
332444 def close (self ) -> None :
333445 # Clean-up
@@ -380,7 +492,7 @@ def _set_cfunctions(self) -> None:
380492 }
381493 for func , (attr , argtypes , restype ) in CFUNCTIONS .items ():
382494 with suppress (AttributeError ):
383- errcheck = None if func == "XSetErrorHandler" else _validate
495+ errcheck = None if func == "XSetErrorHandler" else _validate_x11
384496 cfactory (attrs [attr ], func , argtypes , restype , errcheck = errcheck )
385497
386498 def _monitors_impl (self ) -> None :
0 commit comments