From 2700ac9a690c473f362f9816b54f256ecb122bd4 Mon Sep 17 00:00:00 2001 From: mTvare Date: Thu, 3 Sep 2026 19:46:25 +0530 Subject: [PATCH] x11: fix crash when XInput 2 is unavailable EventLoop::new() currently panics while handling the XIQueryVersion reply on servers that only support XI1, such as x2goagent. Propagate XInput initialization failures through the existing error path and return NotSupported when the server does not support XI2. --- winit-x11/src/event_loop.rs | 40 +++++++++++++++++++++---------- winit-x11/src/xdisplay.rs | 32 +++++++++++++++++++++---- winit/src/changelog/unreleased.md | 2 ++ 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/winit-x11/src/event_loop.rs b/winit-x11/src/event_loop.rs index 78c2c0acb..71eab87a7 100644 --- a/winit-x11/src/event_loop.rs +++ b/winit-x11/src/event_loop.rs @@ -33,7 +33,7 @@ use winit_core::window::{Theme, Window as CoreWindow, WindowAttributes, WindowId use x11rb::connection::RequestConnection; use x11rb::errors::{ConnectError, ConnectionError, IdsExhausted, ReplyError}; use x11rb::protocol::xinput::{self, ConnectionExt as _}; -use x11rb::protocol::{xkb, xproto}; +use x11rb::protocol::{ErrorKind, xkb, xproto}; use x11rb::x11_utils::X11Error as LogicalError; use x11rb::xcb_ffi::ReplyOrIdError; @@ -221,6 +221,9 @@ impl EventLoop { let xconn = match X11_BACKEND.lock().unwrap_or_else(|e| e.into_inner()).as_ref() { Ok(xconn) => xconn.clone(), + Err(XNotSupported::ExtensionNotSupported(reason)) => { + return Err(NotSupportedError::new(reason).into()); + }, Err(err) => return Err(os_error!(err.clone()).into()), }; @@ -268,27 +271,40 @@ impl EventLoop { let ime = ime.ok().map(RefCell::new); - let randr_event_offset = - xconn.select_xrandr_input(root).expect("Failed to query XRandR extension"); + let randr_event_offset = xconn.select_xrandr_input(root).map_err(|err| match err { + X11Error::MissingExtension(_) => EventLoopError::NotSupported(NotSupportedError::new( + "the X11 backend requires XRandR 1.2 or newer", + )), + error => os_error!(error).into(), + })?; let xi2ext = xconn .xcb_connection() .extension_information(xinput::X11_EXTENSION_NAME) - .expect("Failed to query XInput extension") - .expect("X server missing XInput extension"); + .map_err(|err| os_error!(X11Error::from(err)))? + .ok_or_else(|| { + NotSupportedError::new("the X11 backend requires XInput 2.0 or newer") + })?; let xkbext = xconn .xcb_connection() .extension_information(xkb::X11_EXTENSION_NAME) - .expect("Failed to query XKB extension") - .expect("X server missing XKB extension"); + .map_err(|err| os_error!(X11Error::from(err)))? + .ok_or_else(|| NotSupportedError::new("the X11 backend requires XKB 1.0 or newer"))?; // Check for XInput2 support. xconn .xcb_connection() .xinput_xi_query_version(2, 3) - .expect("Failed to send XInput2 query version request") + .map_err(|err| os_error!(X11Error::from(err)))? .reply() - .expect("Error while checking for XInput2 query version reply"); + .map_err(|err| match err { + ReplyError::X11Error(error) if error.error_kind == ErrorKind::Request => { + EventLoopError::NotSupported(NotSupportedError::new( + "the X11 backend requires XInput 2.0 or newer", + )) + }, + error => os_error!(X11Error::from(error)).into(), + })?; xconn.update_cached_wm_info(root); @@ -338,8 +354,8 @@ impl EventLoop { .expect("Failed to register the event loop waker source"); let event_loop_proxy = EventLoopProxy::new(user_waker); - let xkb_context = - Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()).unwrap(); + let xkb_context = Context::from_x11_xkb(xconn.xcb_connection().get_raw_xcb_connection()) + .map_err(|_| NotSupportedError::new("the X11 backend requires XKB 1.0 or newer"))?; let mut xmodmap = util::ModifierKeymap::new(); xmodmap.reload_from_x_connection(&xconn); @@ -411,7 +427,7 @@ impl EventLoop { | xkb::EventType::MAP_NOTIFY | xkb::EventType::STATE_NOTIFY, ) - .unwrap(); + .map_err(|err| os_error!(err))?; event_processor.init_device(ALL_DEVICES); diff --git a/winit-x11/src/xdisplay.rs b/winit-x11/src/xdisplay.rs index 9eb190bc7..33d6a8d83 100644 --- a/winit-x11/src/xdisplay.rs +++ b/winit-x11/src/xdisplay.rs @@ -7,10 +7,11 @@ use std::{fmt, ptr}; use rwh_06::HasDisplayHandle; use winit_core::cursor::CursorIcon; -use x11rb::connection::Connection; +use x11rb::connection::{Connection, RequestConnection}; +use x11rb::errors::ReplyError; use x11rb::protocol::randr::ConnectionExt as _; -use x11rb::protocol::render; use x11rb::protocol::xproto::{self, ConnectionExt}; +use x11rb::protocol::{ErrorKind, randr, render}; use x11rb::resource_manager; use x11rb::xcb_ffi::XCBConnection; @@ -119,11 +120,28 @@ impl XConnection { .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?; // Load the RandR version. + xcb.extension_information(randr::X11_EXTENSION_NAME) + .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))? + .ok_or(XNotSupported::ExtensionNotSupported( + "the X11 backend requires XRandR 1.2 or newer", + ))?; let randr_version = xcb .randr_query_version(1, 3) - .expect("failed to request XRandR version") + .map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))? .reply() - .expect("failed to query XRandR version"); + .map_err(|e| match e { + ReplyError::X11Error(error) if error.error_kind == ErrorKind::Request => { + XNotSupported::ExtensionNotSupported( + "the X11 backend requires XRandR 1.2 or newer", + ) + }, + error => XNotSupported::XcbConversionError(Arc::new(error)), + })?; + if (randr_version.major_version, randr_version.minor_version) < (1, 2) { + return Err(XNotSupported::ExtensionNotSupported( + "the X11 backend requires XRandR 1.2 or newer", + )); + } let xsettings_screen = Self::new_xsettings_screen(&xcb, default_screen); if xsettings_screen.is_none() { @@ -338,7 +356,7 @@ impl fmt::Display for XError { } } -/// Error returned if this system doesn't have XLib or can't create an X connection. +/// Error returned if this system doesn't support the X11 backend. #[derive(Clone, Debug)] pub enum XNotSupported { /// Failed to load one or several shared libraries. @@ -349,6 +367,9 @@ pub enum XNotSupported { /// We encountered an error while converting the connection to XCB. XcbConversionError(Arc), + + /// A required X11 extension is not supported. + ExtensionNotSupported(&'static str), } impl From for XNotSupported { @@ -364,6 +385,7 @@ impl XNotSupported { XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries", XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server", XNotSupported::XcbConversionError(_) => "Failed to convert Xlib connection to XCB", + XNotSupported::ExtensionNotSupported(reason) => reason, } } } diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index 5381bd83e..390a69d4c 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -111,6 +111,8 @@ changelog entry. ### Fixed +- On X11, return `NotSupported` instead of panicking when XInput 2.0, XRandR + 1.2, or XKB 1.0 is unavailable. - On Windows, fix a freeze that occurs when the keyboard layout is switched by tools such as Punto Switcher. The `WM_INPUTLANGCHANGE` message is now handled to refresh the cached keyboard layout, while still deferring to