@@ -171,36 +171,65 @@ class XError(ScreenShotError):
171171class XProtoError (XError ):
172172 """Exception indicating server-reported errors."""
173173
174- def __init__ (self , _xcb_conn : XcbConnection , xcb_err : XcbGenericErrorStructure ) -> None :
174+ def __init__ (self , xcb_conn : XcbConnection , xcb_err : XcbGenericErrorStructure ) -> None :
175175 if isinstance (xcb_err , _Pointer ):
176176 xcb_err = xcb_err .contents
177- # I would verify isinstance(xcb_err,
178- # XcbGenericErrorStructure), but ruff doesn't really give me a
179- # great way to throw an appropriate exception here.
180- # FIXME Get the text descriptions, if possible. That's why we
181- # got the connection handle: that can be necessary to get error
182- # descriptions for extensions.
183- super ().__init__ (
184- "X11 Protocol Error" ,
185- details = {
186- "error_code" : xcb_err .error_code ,
187- "sequence" : xcb_err .sequence ,
188- "resource_id" : xcb_err .resource_id ,
189- "minor_code" : xcb_err .minor_code ,
190- "major_code" : xcb_err .major_code ,
191- "full_sequence" : xcb_err .full_sequence ,
192- },
193- )
177+ assert isinstance (xcb_err , XcbGenericErrorStructure ) # noqa: S101
178+
179+ details = {
180+ "error_code" : xcb_err .error_code ,
181+ "sequence" : xcb_err .sequence ,
182+ "resource_id" : xcb_err .resource_id ,
183+ "minor_code" : xcb_err .minor_code ,
184+ "major_code" : xcb_err .major_code ,
185+ "full_sequence" : xcb_err .full_sequence ,
186+ }
187+
188+ # xcb-errors is a library to get descriptive error strings, instead of reporting the raw codes. This is not
189+ # installed by default on most systems, but is quite helpful for developers. We use it if it exists, but don't
190+ # force the matter. We can't do this when we format the error message, since the XCB connection may be gone.
191+ if xcb_errors :
192+ # We don't try to reuse the error context, since it's per-connection, and probably will only be used once.
193+ ctx = POINTER (XcbErrorsContext )()
194+ ctx_new_setup = xcb_errors .xcb_errors_context_new (xcb_conn , ctx )
195+ if ctx_new_setup == 0 :
196+ try :
197+ # Some of these may return NULL, but some are guaranteed.
198+ ext_name = POINTER (c_char_p )()
199+ error_name = xcb_errors .xcb_errors_get_name_for_error (ctx , xcb_err .error_code , ext_name )
200+ details ["error" ] = error_name .decode ("ascii" , errors = "replace" )
201+ if ext_name :
202+ details ["extension" ] = ext_name .decode ("ascii" , errors = "replace" )
203+ major_name = xcb_errors .xcb_errors_get_name_for_major_code (ctx , xcb_err .major_code )
204+ details ["major_name" ] = major_name .decode ("ascii" , errors = "replace" )
205+ minor_name = xcb_errors .xcb_errors_get_name_for_minor_code (
206+ ctx , xcb_err .major_code , xcb_err .minor_code
207+ )
208+ if minor_name :
209+ details ["minor_name" ] = minor_name .decode ("ascii" , errors = "replace" )
210+ finally :
211+ xcb_errors .xcb_errors_context_free (ctx )
212+
213+ super ().__init__ ("X11 Protocol Error" , details = details )
194214
195215 def __str__ (self ) -> str :
196216 msg = super ().__str__ ()
197- err = self .details
217+ details = self .details
218+ error_desc = f"{ details ['error_code' ]} ({ details ['error' ]} )" if "error" in details else details ["error_code" ]
219+ major_desc = (
220+ f"{ details ['major_code' ]} ({ details ['major_name' ]} )" if "major_name" in details else details ["major_code" ]
221+ )
222+ minor_desc = (
223+ f"{ details ['minor_code' ]} ({ details ['minor_name' ]} )" if "minor_name" in details else details ["minor_code" ]
224+ )
225+ ext_desc = f"\n Extension: { details ['ext_name' ]} " if "ext_desc" in details else ""
198226 msg += (
199- f"\n X Error of failed request: { err ['error_code' ]} "
200- f"\n Major opcode of failed request: { err ['major_code' ]} "
201- f"\n Minor opcode of failed request: { err ['minor_code' ]} "
202- f"\n Resource id in failed request: { err ['resource_id' ]} "
203- f"\n Serial number of failed request: { err ['full_sequence' ]} "
227+ f"\n X Error of failed request: { error_desc } "
228+ f"\n Major opcode of failed request: { major_desc } "
229+ f"{ ext_desc } "
230+ f"\n Minor opcode of failed request: { minor_desc } "
231+ f"\n Resource id in failed request: { details ['resource_id' ]} "
232+ f"\n Serial number of failed request: { details ['full_sequence' ]} "
204233 )
205234 return msg
206235
@@ -234,7 +263,6 @@ def check(self, xcb_conn: XcbConnection) -> None:
234263
235264 This will raise an exception if there is an error.
236265 """
237- self ._xcb_handled_ = True
238266 err_p = libxcb .xcb_request_check (xcb_conn , self )
239267 if not err_p :
240268 return
@@ -555,11 +583,30 @@ class XcbQueryExtensionReply(Structure):
555583# for many core X requests.
556584XCB_NONE = XID (0 )
557585XCB_ATOM_WINDOW = XcbAtom (33 )
586+ XCB_CONN_ERROR = 1
587+ XCB_CONN_CLOSED_EXT_NOTSUPPORTED = 2
588+ XCB_CONN_CLOSED_MEM_INSUFFICIENT = 3
589+ XCB_CONN_CLOSED_REQ_LEN_EXCEED = 4
590+ XCB_CONN_CLOSED_PARSE_ERR = 5
591+ XCB_CONN_CLOSED_INVALID_SCREEN = 6
592+ XCB_CONN_CLOSED_FDPASSING_FAILED = 7
558593XCB_IMAGE_FORMAT_Z_PIXMAP = 2
559594XCB_IMAGE_ORDER_LSB_FIRST = 0
560595XCB_VISUAL_CLASS_TRUE_COLOR = 4
561596XCB_VISUAL_CLASS_DIRECT_COLOR = 5
562597
598+ # I don't know of error descriptions for the XCB connection errors being accessible through a library (a la strerror),
599+ # and the ones in xcb.h's comments aren't too great, so I wrote these.
600+ XCB_CONN_ERRMSG = {
601+ XCB_CONN_ERROR : "connection lost or could not be established" ,
602+ XCB_CONN_CLOSED_EXT_NOTSUPPORTED : "extension not supported" ,
603+ XCB_CONN_CLOSED_MEM_INSUFFICIENT : "memory exhausted" ,
604+ XCB_CONN_CLOSED_REQ_LEN_EXCEED : "request length longer than server accepts" ,
605+ XCB_CONN_CLOSED_PARSE_ERR : "display is unset or invalid (check $DISPLAY)" ,
606+ XCB_CONN_CLOSED_INVALID_SCREEN : "server does not have a screen matching the requested display" ,
607+ XCB_CONN_CLOSED_FDPASSING_FAILED : "could not pass file descriptor" ,
608+ }
609+
563610# randr
564611
565612
@@ -769,6 +816,19 @@ class XcbXfixesGetCursorImageReply(Structure):
769816XCB_XFIXES_MAJOR_VERSION = 6
770817XCB_XFIXES_MINOR_VERSION = 0
771818
819+ # xcb-errors
820+
821+
822+ class XcbErrorsContext (Structure ):
823+ """A context for using this library.
824+
825+ Create a context with xcb_errors_context_new() and destroy it with xcb_errors_context_free(). Except for
826+ xcb_errors_context_free(), all functions in this library are thread-safe and can be called from multiple threads at
827+ the same time, even on the same context.
828+ """
829+
830+ # Opaque
831+
772832
773833#### ctypes initialization
774834
@@ -784,7 +844,7 @@ def initialize() -> None:
784844 if _INITIALIZE_DONE :
785845 return
786846
787- global libc , libxcb , randr , xcb_randr_id , render , xcb_render_id , xfixes , xcb_xfixes_id
847+ global libc , libxcb , randr , xcb_randr_id , render , xcb_render_id , xfixes , xcb_xfixes_id , xcb_errors
788848
789849 # We don't use the cached versions that ctypes.cdll exposes as
790850 # attributes, since other libraries may be doing their own
@@ -958,6 +1018,24 @@ def initialize() -> None:
9581018 xfixes .xcb_xfixes_get_cursor_image_cursor_image_length .argtypes = [POINTER (XcbXfixesGetCursorImageReply )]
9591019 xfixes .xcb_xfixes_get_cursor_image_cursor_image_length .restype = c_int
9601020
1021+ # xcb_errors is an optional library, mostly only useful to developers.
1022+ # We use the qualified .so name, since it's subject to change.
1023+ try :
1024+ xcb_errors = cdll .LoadLibrary ("libxcb-errors.so.0" )
1025+ except Exception : # noqa: BLE001
1026+ xcb_errors = None
1027+ else :
1028+ xcb_errors .xcb_errors_context_new .argtypes = [POINTER (XcbConnection ), POINTER (POINTER (XcbErrorsContext ))]
1029+ xcb_errors .xcb_errors_context_new .restype = c_int
1030+ xcb_errors .xcb_errors_context_free .argtypes = [POINTER (XcbErrorsContext )]
1031+ xcb_errors .xcb_errors_context_free .restype = None
1032+ xcb_errors .xcb_errors_get_name_for_major_code .argtypes = [POINTER (XcbErrorsContext ), c_uint8 ]
1033+ xcb_errors .xcb_errors_get_name_for_major_code .restype = c_char_p
1034+ xcb_errors .xcb_errors_get_name_for_minor_code .argtypes = [POINTER (XcbErrorsContext ), c_uint8 , c_uint16 ]
1035+ xcb_errors .xcb_errors_get_name_for_minor_code .restype = c_char_p
1036+ xcb_errors .xcb_errors_get_name_for_error .argtypes = [POINTER (XcbErrorsContext ), c_uint8 , POINTER (c_char_p )]
1037+ xcb_errors .xcb_errors_get_name_for_error .restype = c_char_p
1038+
9611039 _INITIALIZE_DONE = True
9621040
9631041
@@ -1290,22 +1368,32 @@ def connect(display: str | bytes | None = None) -> tuple[XcbConnection, int]:
12901368
12911369 pref_screen_num = c_int ()
12921370 conn_p = libxcb .xcb_connect (display , pref_screen_num )
1293- if libxcb .xcb_connection_has_error (conn_p ) != 0 :
1294- # FIXME Get the error information
1295- msg = "Cannot connect to display"
1371+
1372+ # We still get a connection object even if the connection fails.
1373+ conn_err = libxcb .xcb_connection_has_error (conn_p )
1374+ if conn_err != 0 :
1375+ msg = "Cannot connect to display: "
1376+ conn_errmsg = XCB_CONN_ERRMSG .get (conn_err )
1377+ if conn_errmsg :
1378+ msg += conn_errmsg
1379+ else :
1380+ msg += f"error code { conn_err } "
12961381 raise XError (msg )
12971382
12981383 return conn_p .contents , pref_screen_num .value
12991384
13001385
13011386def disconnect (conn : XcbConnection ) -> None :
1302- error_status = libxcb .xcb_connection_has_error (conn )
1303- # XCB won't free its connection structures until we disconnect,
1304- # even in the event of an error.
1387+ conn_err = libxcb .xcb_connection_has_error (conn )
1388+ # XCB won't free its connection structures until we disconnect, even in the event of an error.
13051389 libxcb .xcb_disconnect (conn )
1306- if error_status != 0 :
1307- # FIXME Get the error information
1308- msg = "Connection closed due to errors"
1390+ if conn_err != 0 :
1391+ msg = "Connection to X server closed: "
1392+ conn_errmsg = XCB_CONN_ERRMSG .get (conn_err )
1393+ if conn_errmsg :
1394+ msg += conn_errmsg
1395+ else :
1396+ msg += f"error code { conn_err } "
13091397 raise XError (msg )
13101398
13111399
0 commit comments