x11: Improve error handling (#32913)

Continuing this work from a while back in #21079, now greatly aided by
agent + sonnet 4. With this change, there are now only a few spots that
explicitly panic, though errors during initialization will panic.

Motivation was this recent user panic in `handle_event`, figured fixing
all this use of unwrap was a great use of the agent.

> called `Result::unwrap()` on an `Err` value: X11 GetProperty for
_NET_WM_STATE failed.

Release Notes:

- N/A
This commit is contained in:
Michael Sloan 2025-06-17 19:40:17 -06:00 committed by GitHub
parent 90aa99bb14
commit aa1b2d74ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 407 additions and 304 deletions

View file

@ -93,6 +93,9 @@ pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
#[cfg(any(target_os = "linux", target_os = "freebsd"))] #[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> { pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
#[cfg(feature = "x11")]
use anyhow::Context as _;
if headless { if headless {
return Rc::new(HeadlessClient::new()); return Rc::new(HeadlessClient::new());
} }
@ -102,7 +105,11 @@ pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
"Wayland" => Rc::new(WaylandClient::new()), "Wayland" => Rc::new(WaylandClient::new()),
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
"X11" => Rc::new(X11Client::new()), "X11" => Rc::new(
X11Client::new()
.context("Failed to initialize X11 client.")
.unwrap(),
),
"Headless" => Rc::new(HeadlessClient::new()), "Headless" => Rc::new(HeadlessClient::new()),
_ => unreachable!(), _ => unreachable!(),

View file

@ -17,6 +17,7 @@ use calloop::{
use collections::HashMap; use collections::HashMap;
use futures::channel::oneshot; use futures::channel::oneshot;
use http_client::Url; use http_client::Url;
use log::Level;
use smallvec::SmallVec; use smallvec::SmallVec;
use util::ResultExt; use util::ResultExt;
@ -41,12 +42,12 @@ use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSIO
use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask, STATE_LAYOUT_EFFECTIVE}; use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask, STATE_LAYOUT_EFFECTIVE};
use super::{ use super::{
ButtonOrScroll, ScrollDirection, button_or_scroll_from_event_detail, ButtonOrScroll, ScrollDirection, X11Display, X11WindowStatePtr, XcbAtoms, XimCallbackEvent,
XimHandler, button_or_scroll_from_event_detail, check_reply,
clipboard::{self, Clipboard}, clipboard::{self, Clipboard},
get_valuator_axis_index, modifiers_from_state, pressed_button_from_mask, get_reply, get_valuator_axis_index, handle_connection_error, modifiers_from_state,
pressed_button_from_mask,
}; };
use super::{X11Display, X11WindowStatePtr, XcbAtoms};
use super::{XimCallbackEvent, XimHandler};
use crate::platform::{ use crate::platform::{
LinuxCommon, PlatformWindow, LinuxCommon, PlatformWindow,
@ -230,12 +231,14 @@ pub struct X11ClientState {
pub struct X11ClientStatePtr(pub Weak<RefCell<X11ClientState>>); pub struct X11ClientStatePtr(pub Weak<RefCell<X11ClientState>>);
impl X11ClientStatePtr { impl X11ClientStatePtr {
fn get_client(&self) -> X11Client { fn get_client(&self) -> Option<X11Client> {
X11Client(self.0.upgrade().expect("client already dropped")) self.0.upgrade().map(X11Client)
} }
pub fn drop_window(&self, x_window: u32) { pub fn drop_window(&self, x_window: u32) {
let client = self.get_client(); let Some(client) = self.get_client() else {
return;
};
let mut state = client.0.borrow_mut(); let mut state = client.0.borrow_mut();
if let Some(window_ref) = state.windows.remove(&x_window) { if let Some(window_ref) = state.windows.remove(&x_window) {
@ -262,14 +265,23 @@ impl X11ClientStatePtr {
} }
pub fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) { pub fn update_ime_position(&self, bounds: Bounds<ScaledPixels>) {
let client = self.get_client(); let Some(client) = self.get_client() else {
return;
};
let mut state = client.0.borrow_mut(); let mut state = client.0.borrow_mut();
if state.composing || state.ximc.is_none() { if state.composing || state.ximc.is_none() {
return; return;
} }
let mut ximc = state.ximc.take().unwrap(); let Some(mut ximc) = state.ximc.take() else {
let xim_handler = state.xim_handler.take().unwrap(); log::error!("bug: xim connection not set");
return;
};
let Some(xim_handler) = state.xim_handler.take() else {
log::error!("bug: xim handler not set");
state.ximc = Some(ximc);
return;
};
let ic_attributes = ximc let ic_attributes = ximc
.build_ic_attributes() .build_ic_attributes()
.push( .push(
@ -300,8 +312,8 @@ impl X11ClientStatePtr {
pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>); pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>);
impl X11Client { impl X11Client {
pub(crate) fn new() -> Self { pub(crate) fn new() -> anyhow::Result<Self> {
let event_loop = EventLoop::try_new().unwrap(); let event_loop = EventLoop::try_new()?;
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal()); let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
@ -321,39 +333,34 @@ impl X11Client {
} }
} }
}) })
.unwrap(); .map_err(|err| {
anyhow!("Failed to initialize event loop handling of foreground tasks: {err:?}")
})?;
let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap(); let (xcb_connection, x_root_index) = XCBConnection::connect(None)?;
xcb_connection xcb_connection.prefetch_extension_information(xkb::X11_EXTENSION_NAME)?;
.prefetch_extension_information(xkb::X11_EXTENSION_NAME) xcb_connection.prefetch_extension_information(randr::X11_EXTENSION_NAME)?;
.unwrap(); xcb_connection.prefetch_extension_information(render::X11_EXTENSION_NAME)?;
xcb_connection xcb_connection.prefetch_extension_information(xinput::X11_EXTENSION_NAME)?;
.prefetch_extension_information(randr::X11_EXTENSION_NAME)
.unwrap();
xcb_connection
.prefetch_extension_information(render::X11_EXTENSION_NAME)
.unwrap();
xcb_connection
.prefetch_extension_information(xinput::X11_EXTENSION_NAME)
.unwrap();
// Announce to X server that XInput up to 2.1 is supported. To increase this to 2.2 and // Announce to X server that XInput up to 2.1 is supported. To increase this to 2.2 and
// beyond, support for touch events would need to be added. // beyond, support for touch events would need to be added.
let xinput_version = xcb_connection let xinput_version = get_reply(
.xinput_xi_query_version(2, 1) || "XInput XiQueryVersion failed",
.unwrap() xcb_connection.xinput_xi_query_version(2, 1),
.reply() )?;
.unwrap();
// XInput 1.x is not supported.
assert!( assert!(
xinput_version.major_version >= 2, xinput_version.major_version >= 2,
"XInput version >= 2 required." "XInput version >= 2 required."
); );
let pointer_device_states = let pointer_device_states =
get_new_pointer_device_states(&xcb_connection, &BTreeMap::new()); current_pointer_device_states(&xcb_connection, &BTreeMap::new()).unwrap_or_default();
let atoms = XcbAtoms::new(&xcb_connection).unwrap().reply().unwrap(); let atoms = XcbAtoms::new(&xcb_connection)
.context("Failed to get XCB atoms")?
.reply()
.context("Failed to get XCB atoms")?;
let root = xcb_connection.setup().roots[0].root; let root = xcb_connection.setup().roots[0].root;
let compositor_present = check_compositor_present(&xcb_connection, root); let compositor_present = check_compositor_present(&xcb_connection, root);
@ -366,11 +373,11 @@ impl X11Client {
gtk_frame_extents_supported gtk_frame_extents_supported
); );
let xkb = xcb_connection let xkb = get_reply(
.xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION) || "Failed to initialize XKB extension",
.unwrap() xcb_connection
.reply() .xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION),
.unwrap(); )?;
let events = xkb::EventType::STATE_NOTIFY let events = xkb::EventType::STATE_NOTIFY
| xkb::EventType::MAP_NOTIFY | xkb::EventType::MAP_NOTIFY
@ -383,16 +390,17 @@ impl X11Client {
| xkb::MapPart::KEY_BEHAVIORS | xkb::MapPart::KEY_BEHAVIORS
| xkb::MapPart::VIRTUAL_MODS | xkb::MapPart::VIRTUAL_MODS
| xkb::MapPart::VIRTUAL_MOD_MAP; | xkb::MapPart::VIRTUAL_MOD_MAP;
xcb_connection check_reply(
.xkb_select_events( || "Failed to select XKB events",
xcb_connection.xkb_select_events(
xkb::ID::USE_CORE_KBD.into(), xkb::ID::USE_CORE_KBD.into(),
0u8.into(), 0u8.into(),
events, events,
map_notify_parts, map_notify_parts,
map_notify_parts, map_notify_parts,
&xkb::SelectEventsAux::new(), &xkb::SelectEventsAux::new(),
) ),
.unwrap(); )?;
assert!(xkb.supported); assert!(xkb.supported);
let xkb_context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS); let xkb_context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS);
@ -414,9 +422,10 @@ impl X11Client {
.to_string(); .to_string();
let keyboard_layout = LinuxKeyboardLayout::new(layout_name.into()); let keyboard_layout = LinuxKeyboardLayout::new(layout_name.into());
let gpu_context = BladeContext::new().expect("Unable to init GPU context"); let gpu_context = BladeContext::new().context("Unable to init GPU context")?;
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap(); let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection)
.context("Failed to create resource database")?;
let scale_factor = resource_database let scale_factor = resource_database
.get_value("Xft.dpi", "Xft.dpi") .get_value("Xft.dpi", "Xft.dpi")
.ok() .ok()
@ -424,11 +433,11 @@ impl X11Client {
.map(|dpi: f32| dpi / 96.0) .map(|dpi: f32| dpi / 96.0)
.unwrap_or(1.0); .unwrap_or(1.0);
let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database) let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database)
.unwrap() .context("Failed to initialize cursor theme handler")?
.reply() .reply()
.unwrap(); .context("Failed to initialize cursor theme handler")?;
let clipboard = Clipboard::new().unwrap(); let clipboard = Clipboard::new().context("Failed to initialize clipboard")?;
let xcb_connection = Rc::new(xcb_connection); let xcb_connection = Rc::new(xcb_connection);
@ -457,7 +466,7 @@ impl X11Client {
} }
}, },
) )
.expect("Failed to initialize x11 event source"); .map_err(|err| anyhow!("Failed to initialize X11 event source: {err:?}"))?;
handle handle
.insert_source(XDPEventSource::new(&common.background_executor), { .insert_source(XDPEventSource::new(&common.background_executor), {
@ -473,9 +482,9 @@ impl X11Client {
} }
} }
}) })
.unwrap(); .map_err(|err| anyhow!("Failed to initialize XDP event source: {err:?}"))?;
X11Client(Rc::new(RefCell::new(X11ClientState { Ok(X11Client(Rc::new(RefCell::new(X11ClientState {
modifiers: Modifiers::default(), modifiers: Modifiers::default(),
capslock: Capslock::default(), capslock: Capslock::default(),
last_modifiers_changed_event: Modifiers::default(), last_modifiers_changed_event: Modifiers::default(),
@ -520,7 +529,7 @@ impl X11Client {
clipboard, clipboard,
clipboard_item: None, clipboard_item: None,
xdnd_state: Xdnd::default(), xdnd_state: Xdnd::default(),
}))) }))))
} }
pub fn process_x11_events( pub fn process_x11_events(
@ -606,8 +615,9 @@ impl X11Client {
Ok(None) => { Ok(None) => {
break; break;
} }
Err(e) => { Err(err) => {
log::warn!("error polling for X11 events: {e:?}"); let err = handle_connection_error(err);
log::warn!("error while polling for X11 events: {err:?}");
break; break;
} }
} }
@ -633,14 +643,15 @@ impl X11Client {
for event in events.into_iter() { for event in events.into_iter() {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
if state.ximc.is_none() || state.xim_handler.is_none() { if !state.has_xim() {
drop(state); drop(state);
self.handle_event(event); self.handle_event(event);
continue; continue;
} }
let mut ximc = state.ximc.take().unwrap(); let Some((mut ximc, mut xim_handler)) = state.take_xim() else {
let mut xim_handler = state.xim_handler.take().unwrap(); continue;
};
let xim_connected = xim_handler.connected; let xim_connected = xim_handler.connected;
drop(state); drop(state);
@ -654,8 +665,7 @@ impl X11Client {
let xim_callback_event = xim_handler.last_callback_event.take(); let xim_callback_event = xim_handler.last_callback_event.take();
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
state.ximc = Some(ximc); state.restore_xim(ximc, xim_handler);
state.xim_handler = Some(xim_handler);
drop(state); drop(state);
if let Some(event) = xim_callback_event { if let Some(event) = xim_callback_event {
@ -678,12 +688,13 @@ impl X11Client {
pub fn enable_ime(&self) { pub fn enable_ime(&self) {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
if state.ximc.is_none() { if !state.has_xim() {
return; return;
} }
let mut ximc = state.ximc.take().unwrap(); let Some((mut ximc, mut xim_handler)) = state.take_xim() else {
let mut xim_handler = state.xim_handler.take().unwrap(); return;
};
let mut ic_attributes = ximc let mut ic_attributes = ximc
.build_ic_attributes() .build_ic_attributes()
.push(AttributeName::InputStyle, InputStyle::PREEDIT_CALLBACKS) .push(AttributeName::InputStyle, InputStyle::PREEDIT_CALLBACKS)
@ -693,7 +704,13 @@ impl X11Client {
let window_id = state.keyboard_focused_window; let window_id = state.keyboard_focused_window;
drop(state); drop(state);
if let Some(window_id) = window_id { if let Some(window_id) = window_id {
let window = self.get_window(window_id).unwrap(); let Some(window) = self.get_window(window_id) else {
log::error!("Failed to get window for IME positioning");
let mut state = self.0.borrow_mut();
state.ximc = Some(ximc);
state.xim_handler = Some(xim_handler);
return;
};
if let Some(area) = window.get_ime_area() { if let Some(area) = window.get_ime_area() {
ic_attributes = ic_attributes =
ic_attributes.nested_list(xim::AttributeName::PreeditAttributes, |b| { ic_attributes.nested_list(xim::AttributeName::PreeditAttributes, |b| {
@ -709,17 +726,19 @@ impl X11Client {
} }
ximc.create_ic(xim_handler.im_id, ic_attributes.build()) ximc.create_ic(xim_handler.im_id, ic_attributes.build())
.ok(); .ok();
state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
state.xim_handler = Some(xim_handler); state.restore_xim(ximc, xim_handler);
state.ximc = Some(ximc);
} }
pub fn reset_ime(&self) { pub fn reset_ime(&self) {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
state.composing = false; state.composing = false;
if let Some(mut ximc) = state.ximc.take() { if let Some(mut ximc) = state.ximc.take() {
let xim_handler = state.xim_handler.as_ref().unwrap(); if let Some(xim_handler) = state.xim_handler.as_ref() {
ximc.reset_ic(xim_handler.im_id, xim_handler.ic_id).ok(); ximc.reset_ic(xim_handler.im_id, xim_handler.ic_id).ok();
} else {
log::error!("bug: xim handler not set in reset_ime");
}
state.ximc = Some(ximc); state.ximc = Some(ximc);
} }
} }
@ -799,26 +818,25 @@ impl X11Client {
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Exited {})); window.handle_input(PlatformInput::FileDrop(FileDropEvent::Exited {}));
self.0.borrow_mut().xdnd_state = Xdnd::default(); self.0.borrow_mut().xdnd_state = Xdnd::default();
} else if event.type_ == state.atoms.XdndPosition { } else if event.type_ == state.atoms.XdndPosition {
if let Ok(pos) = state if let Ok(pos) = get_reply(
.xcb_connection || "Failed to query pointer position",
.query_pointer(event.window) state.xcb_connection.query_pointer(event.window),
.unwrap() ) {
.reply()
{
state.xdnd_state.position = state.xdnd_state.position =
Point::new(Pixels(pos.win_x as f32), Pixels(pos.win_y as f32)); Point::new(Pixels(pos.win_x as f32), Pixels(pos.win_y as f32));
} }
if !state.xdnd_state.retrieved { if !state.xdnd_state.retrieved {
state check_reply(
.xcb_connection || "Failed to convert selection for drag and drop",
.convert_selection( state.xcb_connection.convert_selection(
event.window, event.window,
state.atoms.XdndSelection, state.atoms.XdndSelection,
state.xdnd_state.drag_type, state.xdnd_state.drag_type,
state.atoms.XDND_DATA, state.atoms.XDND_DATA,
arg3, arg3,
),
) )
.unwrap(); .log_err();
} }
xdnd_send_status( xdnd_send_status(
&state.xcb_connection, &state.xcb_connection,
@ -848,18 +866,21 @@ impl X11Client {
Event::SelectionNotify(event) => { Event::SelectionNotify(event) => {
let window = self.get_window(event.requestor)?; let window = self.get_window(event.requestor)?;
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
let property = state.xcb_connection.get_property( let reply = get_reply(
|| "Failed to get XDND_DATA",
state.xcb_connection.get_property(
false, false,
event.requestor, event.requestor,
state.atoms.XDND_DATA, state.atoms.XDND_DATA,
AtomEnum::ANY, AtomEnum::ANY,
0, 0,
1024, 1024,
); ),
if property.as_ref().log_err().is_none() { )
.log_err();
let Some(reply) = reply else {
return Some(()); return Some(());
} };
if let Ok(reply) = property.unwrap().reply() {
match str::from_utf8(&reply.value) { match str::from_utf8(&reply.value) {
Ok(file_list) => { Ok(file_list) => {
let paths: SmallVec<[_; 2]> = file_list let paths: SmallVec<[_; 2]> = file_list
@ -878,7 +899,6 @@ impl X11Client {
Err(_) => {} Err(_) => {}
} }
} }
}
Event::ConfigureNotify(event) => { Event::ConfigureNotify(event) => {
let bounds = Bounds { let bounds = Bounds {
origin: Point { origin: Point {
@ -891,11 +911,17 @@ impl X11Client {
}, },
}; };
let window = self.get_window(event.window)?; let window = self.get_window(event.window)?;
window.configure(bounds).unwrap(); window
.set_bounds(bounds)
.context("X11: Failed to set window bounds")
.log_err();
} }
Event::PropertyNotify(event) => { Event::PropertyNotify(event) => {
let window = self.get_window(event.window)?; let window = self.get_window(event.window)?;
window.property_notify(event).unwrap(); window
.property_notify(event)
.context("X11: Failed to handle property notify")
.log_err();
} }
Event::FocusIn(event) => { Event::FocusIn(event) => {
let window = self.get_window(event.event)?; let window = self.get_window(event.event)?;
@ -1262,10 +1288,12 @@ impl X11Client {
state.pointer_device_states.remove(&info.deviceid); state.pointer_device_states.remove(&info.deviceid);
} }
} }
state.pointer_device_states = get_new_pointer_device_states( if let Some(pointer_device_states) = current_pointer_device_states(
&state.xcb_connection, &state.xcb_connection,
&state.pointer_device_states, &state.pointer_device_states,
); ) {
state.pointer_device_states = pointer_device_states;
}
} }
Event::XinputDeviceChanged(event) => { Event::XinputDeviceChanged(event) => {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
@ -1302,8 +1330,7 @@ impl X11Client {
state.modifiers, state.modifiers,
event.detail.into(), event.detail.into(),
)); ));
let mut ximc = state.ximc.take().unwrap(); let (mut ximc, mut xim_handler) = state.take_xim()?;
let mut xim_handler = state.xim_handler.take().unwrap();
drop(state); drop(state);
xim_handler.window = event.event; xim_handler.window = event.event;
ximc.forward_event( ximc.forward_event(
@ -1312,10 +1339,10 @@ impl X11Client {
xim::ForwardEventFlag::empty(), xim::ForwardEventFlag::empty(),
&event, &event,
) )
.unwrap(); .context("X11: Failed to forward XIM event")
.log_err();
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
state.ximc = Some(ximc); state.restore_xim(ximc, xim_handler);
state.xim_handler = Some(xim_handler);
drop(state); drop(state);
} }
event => { event => {
@ -1326,7 +1353,10 @@ impl X11Client {
} }
fn xim_handle_commit(&self, window: xproto::Window, text: String) -> Option<()> { fn xim_handle_commit(&self, window: xproto::Window, text: String) -> Option<()> {
let window = self.get_window(window).unwrap(); let Some(window) = self.get_window(window) else {
log::error!("bug: Failed to get window for XIM commit");
return None;
};
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
let keystroke = state.pre_key_char_down.take(); let keystroke = state.pre_key_char_down.take();
state.composing = false; state.composing = false;
@ -1343,11 +1373,13 @@ impl X11Client {
} }
fn xim_handle_preedit(&self, window: xproto::Window, text: String) -> Option<()> { fn xim_handle_preedit(&self, window: xproto::Window, text: String) -> Option<()> {
let window = self.get_window(window).unwrap(); let Some(window) = self.get_window(window) else {
log::error!("bug: Failed to get window for XIM preedit");
return None;
};
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
let mut ximc = state.ximc.take().unwrap(); let (mut ximc, mut xim_handler) = state.take_xim()?;
let mut xim_handler = state.xim_handler.take().unwrap();
state.composing = !text.is_empty(); state.composing = !text.is_empty();
drop(state); drop(state);
window.handle_ime_preedit(text); window.handle_ime_preedit(text);
@ -1375,8 +1407,7 @@ impl X11Client {
.ok(); .ok();
} }
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
state.ximc = Some(ximc); state.restore_xim(ximc, xim_handler);
state.xim_handler = Some(xim_handler);
drop(state); drop(state);
Some(()) Some(())
} }
@ -1429,15 +1460,13 @@ impl LinuxClient for X11Client {
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> { fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
let state = self.0.borrow(); let state = self.0.borrow();
Some(Rc::new(
X11Display::new( X11Display::new(
&state.xcb_connection, &state.xcb_connection,
state.scale_factor, state.scale_factor,
state.x_root_index, state.x_root_index,
) )
.expect("There should always be a root index"), .log_err()
)) .map(|display| Rc::new(display) as Rc<dyn PlatformDisplay>)
} }
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> { fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
@ -1464,7 +1493,10 @@ impl LinuxClient for X11Client {
params: WindowParams, params: WindowParams,
) -> anyhow::Result<Box<dyn PlatformWindow>> { ) -> anyhow::Result<Box<dyn PlatformWindow>> {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
let x_window = state.xcb_connection.generate_id().unwrap(); let x_window = state
.xcb_connection
.generate_id()
.context("X11: Failed to generate window ID")?;
let window = X11Window::new( let window = X11Window::new(
handle, handle,
@ -1480,16 +1512,17 @@ impl LinuxClient for X11Client {
state.scale_factor, state.scale_factor,
state.common.appearance, state.common.appearance,
)?; )?;
state check_reply(
.xcb_connection || "Failed to set XdndAware property",
.change_property32( state.xcb_connection.change_property32(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
x_window, x_window,
state.atoms.XdndAware, state.atoms.XdndAware,
state.atoms.XA_ATOM, state.atoms.XA_ATOM,
&[5], &[5],
),
) )
.unwrap(); .log_err();
let window_ref = WindowRef { let window_ref = WindowRef {
window: window.0.clone(), window: window.0.clone(),
@ -1532,7 +1565,7 @@ impl LinuxClient for X11Client {
) )
.anyhow() .anyhow()
.and_then(|cookie| cookie.check().anyhow()) .and_then(|cookie| cookie.check().anyhow())
.context("setting cursor style") .context("X11: Failed to set cursor style")
.log_err(); .log_err();
} }
@ -1555,7 +1588,7 @@ impl LinuxClient for X11Client {
clipboard::ClipboardKind::Primary, clipboard::ClipboardKind::Primary,
clipboard::WaitConfig::None, clipboard::WaitConfig::None,
) )
.context("Failed to write to clipboard (primary)") .context("X11 Failed to write to clipboard (primary)")
.log_with_level(log::Level::Debug); .log_with_level(log::Level::Debug);
} }
@ -1568,7 +1601,7 @@ impl LinuxClient for X11Client {
clipboard::ClipboardKind::Clipboard, clipboard::ClipboardKind::Clipboard,
clipboard::WaitConfig::None, clipboard::WaitConfig::None,
) )
.context("Failed to write to clipboard (clipboard)") .context("X11: Failed to write to clipboard (clipboard)")
.log_with_level(log::Level::Debug); .log_with_level(log::Level::Debug);
state.clipboard_item.replace(item); state.clipboard_item.replace(item);
} }
@ -1578,7 +1611,7 @@ impl LinuxClient for X11Client {
return state return state
.clipboard .clipboard
.get_any(clipboard::ClipboardKind::Primary) .get_any(clipboard::ClipboardKind::Primary)
.context("Failed to read from clipboard (primary)") .context("X11: Failed to read from clipboard (primary)")
.log_with_level(log::Level::Debug); .log_with_level(log::Level::Debug);
} }
@ -1595,17 +1628,21 @@ impl LinuxClient for X11Client {
return state return state
.clipboard .clipboard
.get_any(clipboard::ClipboardKind::Clipboard) .get_any(clipboard::ClipboardKind::Clipboard)
.context("Failed to read from clipboard (clipboard)") .context("X11: Failed to read from clipboard (clipboard)")
.log_with_level(log::Level::Debug); .log_with_level(log::Level::Debug);
} }
fn run(&self) { fn run(&self) {
let mut event_loop = self let Some(mut event_loop) = self
.0 .0
.borrow_mut() .borrow_mut()
.event_loop .event_loop
.take() .take()
.expect("App is already running"); .context("X11Client::run called but it's already running")
.log_err()
else {
return;
};
event_loop.run(None, &mut self.clone(), |_| {}).log_err(); event_loop.run(None, &mut self.clone(), |_| {}).log_err();
} }
@ -1641,7 +1678,7 @@ impl LinuxClient for X11Client {
let window_ids = reply let window_ids = reply
.value .value
.chunks_exact(4) .chunks_exact(4)
.map(|chunk| u32::from_ne_bytes(chunk.try_into().unwrap())) .filter_map(|chunk| chunk.try_into().ok().map(u32::from_ne_bytes))
.collect::<Vec<xproto::Window>>(); .collect::<Vec<xproto::Window>>();
let mut handles = Vec::new(); let mut handles = Vec::new();
@ -1664,6 +1701,30 @@ impl LinuxClient for X11Client {
} }
impl X11ClientState { impl X11ClientState {
fn has_xim(&self) -> bool {
self.ximc.is_some() && self.xim_handler.is_some()
}
fn take_xim(&mut self) -> Option<(X11rbClient<Rc<XCBConnection>>, XimHandler)> {
let ximc = self
.ximc
.take()
.ok_or(anyhow!("bug: XIM connection not set"))
.log_err()?;
if let Some(xim_handler) = self.xim_handler.take() {
Some((ximc, xim_handler))
} else {
self.ximc = Some(ximc);
log::error!("bug: XIM handler not set");
None
}
}
fn restore_xim(&mut self, ximc: X11rbClient<Rc<XCBConnection>>, xim_handler: XimHandler) {
self.ximc = Some(ximc);
self.xim_handler = Some(xim_handler);
}
fn update_refresh_loop(&mut self, x_window: xproto::Window) { fn update_refresh_loop(&mut self, x_window: xproto::Window) {
let Some(window_ref) = self.windows.get_mut(&x_window) else { let Some(window_ref) = self.windows.get_mut(&x_window) else {
return; return;
@ -1697,21 +1758,19 @@ impl X11ClientState {
}); });
} }
(true, None) => { (true, None) => {
let screen_resources = self let Some(screen_resources) = get_reply(
.xcb_connection || "Failed to get screen resources",
.randr_get_screen_resources_current(x_window) self.xcb_connection
.unwrap() .randr_get_screen_resources_current(x_window),
.reply() )
.expect("Could not find available screens"); .log_err() else {
return;
};
// Ideally this would be re-queried when the window changes screens, but there // Ideally this would be re-queried when the window changes screens, but there
// doesn't seem to be an efficient / straightforward way to do this. Should also be // doesn't seem to be an efficient / straightforward way to do this. Should also be
// updated when screen configurations change. // updated when screen configurations change.
let refresh_rate = mode_refresh_rate( let mode_info = screen_resources.crtcs.iter().find_map(|crtc| {
screen_resources
.crtcs
.iter()
.find_map(|crtc| {
let crtc_info = self let crtc_info = self
.xcb_connection .xcb_connection
.randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME) .randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
@ -1723,9 +1782,17 @@ impl X11ClientState {
.modes .modes
.iter() .iter()
.find(|m| m.id == crtc_info.mode) .find(|m| m.id == crtc_info.mode)
}) });
.expect("Unable to find screen refresh rate"), let refresh_rate = match mode_info {
Some(mode_info) => mode_refresh_rate(mode_info),
None => {
log::error!(
"Failed to get screen mode info from xrandr, \
defaulting to 60hz refresh rate."
); );
Duration::from_micros(1_000_000 / 60)
}
};
let event_loop_token = self.start_refresh_loop(x_window, refresh_rate); let event_loop_token = self.start_refresh_loop(x_window, refresh_rate);
let Some(window_ref) = self.windows.get_mut(&x_window) else { let Some(window_ref) = self.windows.get_mut(&x_window) else {
@ -1772,7 +1839,7 @@ impl X11ClientState {
calloop::timer::TimeoutAction::ToInstant(instant) calloop::timer::TimeoutAction::ToInstant(instant)
} }
}) })
.expect("Failed to initialize refresh timer") .expect("Failed to initialize window refresh timer")
} }
fn get_cursor_icon(&mut self, style: CursorStyle) -> Option<xproto::Cursor> { fn get_cursor_icon(&mut self, style: CursorStyle) -> Option<xproto::Cursor> {
@ -1784,7 +1851,7 @@ impl X11ClientState {
match style { match style {
CursorStyle::None => match create_invisible_cursor(&self.xcb_connection) { CursorStyle::None => match create_invisible_cursor(&self.xcb_connection) {
Ok(loaded_cursor) => result = Ok(loaded_cursor), Ok(loaded_cursor) => result = Ok(loaded_cursor),
Err(err) => result = Err(err.context("error while creating invisible cursor")), Err(err) => result = Err(err.context("X11: error while creating invisible cursor")),
}, },
_ => 'outer: { _ => 'outer: {
let mut errors = String::new(); let mut errors = String::new();
@ -1827,14 +1894,14 @@ impl X11ClientState {
{ {
Ok(default) => { Ok(default) => {
log_cursor_icon_warning(err.context(format!( log_cursor_icon_warning(err.context(format!(
"x11: error loading cursor icon, falling back on default icon '{}'", "X11: error loading cursor icon, falling back on default icon '{}'",
DEFAULT_CURSOR_ICON_NAME DEFAULT_CURSOR_ICON_NAME
))); )));
Some(default) Some(default)
} }
Err(default_err) => { Err(default_err) => {
log_cursor_icon_warning(err.context(default_err).context(format!( log_cursor_icon_warning(err.context(default_err).context(format!(
"x11: error loading default cursor fallback '{}'", "X11: error loading default cursor fallback '{}'",
DEFAULT_CURSOR_ICON_NAME DEFAULT_CURSOR_ICON_NAME
))); )));
None None
@ -1857,7 +1924,7 @@ pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64); let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
let micros = 1_000_000_000 / millihertz; let micros = 1_000_000_000 / millihertz;
log::info!("Refreshing at {} micros", micros); log::info!("Refreshing every {}ms", micros / 1_000);
Duration::from_micros(micros) Duration::from_micros(micros)
} }
@ -1868,66 +1935,63 @@ fn fp3232_to_f32(value: xinput::Fp3232) -> f32 {
fn check_compositor_present(xcb_connection: &XCBConnection, root: u32) -> bool { fn check_compositor_present(xcb_connection: &XCBConnection, root: u32) -> bool {
// Method 1: Check for _NET_WM_CM_S{root} // Method 1: Check for _NET_WM_CM_S{root}
let atom_name = format!("_NET_WM_CM_S{}", root); let atom_name = format!("_NET_WM_CM_S{}", root);
let atom = xcb_connection let atom1 = get_reply(
.intern_atom(false, atom_name.as_bytes()) || format!("Failed to intern {atom_name}"),
.unwrap() xcb_connection.intern_atom(false, atom_name.as_bytes()),
.reply() );
.map(|reply| reply.atom) let method1 = match atom1.log_with_level(Level::Debug) {
.unwrap_or(0); Some(reply) if reply.atom != x11rb::NONE => {
let atom = reply.atom;
let method1 = if atom != 0 { get_reply(
xcb_connection || format!("Failed to get {atom_name} owner"),
.get_selection_owner(atom) xcb_connection.get_selection_owner(atom),
.unwrap() )
.reply()
.map(|reply| reply.owner != 0) .map(|reply| reply.owner != 0)
.log_with_level(Level::Debug)
.unwrap_or(false) .unwrap_or(false)
} else { }
false _ => false,
}; };
// Method 2: Check for _NET_WM_CM_OWNER // Method 2: Check for _NET_WM_CM_OWNER
let atom_name = "_NET_WM_CM_OWNER"; let atom_name = "_NET_WM_CM_OWNER";
let atom = xcb_connection let atom2 = get_reply(
.intern_atom(false, atom_name.as_bytes()) || format!("Failed to intern {atom_name}"),
.unwrap() xcb_connection.intern_atom(false, atom_name.as_bytes()),
.reply() );
.map(|reply| reply.atom) let method2 = match atom2.log_with_level(Level::Debug) {
.unwrap_or(0); Some(reply) if reply.atom != x11rb::NONE => {
let atom = reply.atom;
let method2 = if atom != 0 { get_reply(
xcb_connection || format!("Failed to get {atom_name}"),
.get_property(false, root, atom, xproto::AtomEnum::WINDOW, 0, 1) xcb_connection.get_property(false, root, atom, xproto::AtomEnum::WINDOW, 0, 1),
.unwrap() )
.reply()
.map(|reply| reply.value_len > 0) .map(|reply| reply.value_len > 0)
.unwrap_or(false) .unwrap_or(false)
} else { }
false _ => return false,
}; };
// Method 3: Check for _NET_SUPPORTING_WM_CHECK // Method 3: Check for _NET_SUPPORTING_WM_CHECK
let atom_name = "_NET_SUPPORTING_WM_CHECK"; let atom_name = "_NET_SUPPORTING_WM_CHECK";
let atom = xcb_connection let atom3 = get_reply(
.intern_atom(false, atom_name.as_bytes()) || format!("Failed to intern {atom_name}"),
.unwrap() xcb_connection.intern_atom(false, atom_name.as_bytes()),
.reply() );
.map(|reply| reply.atom) let method3 = match atom3.log_with_level(Level::Debug) {
.unwrap_or(0); Some(reply) if reply.atom != x11rb::NONE => {
let atom = reply.atom;
let method3 = if atom != 0 { get_reply(
xcb_connection || format!("Failed to get {atom_name}"),
.get_property(false, root, atom, xproto::AtomEnum::WINDOW, 0, 1) xcb_connection.get_property(false, root, atom, xproto::AtomEnum::WINDOW, 0, 1),
.unwrap() )
.reply()
.map(|reply| reply.value_len > 0) .map(|reply| reply.value_len > 0)
.unwrap_or(false) .unwrap_or(false)
} else { }
false _ => return false,
}; };
// TODO: Remove this log::debug!(
log::info!(
"Compositor detection: _NET_WM_CM_S?={}, _NET_WM_CM_OWNER={}, _NET_SUPPORTING_WM_CHECK={}", "Compositor detection: _NET_WM_CM_S?={}, _NET_WM_CM_OWNER={}, _NET_SUPPORTING_WM_CHECK={}",
method1, method1,
method2, method2,
@ -1942,28 +2006,28 @@ fn check_gtk_frame_extents_supported(
atoms: &XcbAtoms, atoms: &XcbAtoms,
root: xproto::Window, root: xproto::Window,
) -> bool { ) -> bool {
let supported_atoms = xcb_connection let Some(supported_atoms) = get_reply(
.get_property( || "Failed to get _NET_SUPPORTED",
xcb_connection.get_property(
false, false,
root, root,
atoms._NET_SUPPORTED, atoms._NET_SUPPORTED,
xproto::AtomEnum::ATOM, xproto::AtomEnum::ATOM,
0, 0,
1024, 1024,
),
) )
.unwrap() .log_with_level(Level::Debug) else {
.reply() return false;
.map(|reply| { };
// Convert Vec<u8> to Vec<u32>
reply let supported_atom_ids: Vec<u32> = supported_atoms
.value .value
.chunks_exact(4) .chunks_exact(4)
.map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) .filter_map(|chunk| chunk.try_into().ok().map(u32::from_ne_bytes))
.collect::<Vec<u32>>() .collect();
})
.unwrap_or_default();
supported_atoms.contains(&atoms._GTK_FRAME_EXTENTS) supported_atom_ids.contains(&atoms._GTK_FRAME_EXTENTS)
} }
fn xdnd_is_atom_supported(atom: u32, atoms: &XcbAtoms) -> bool { fn xdnd_is_atom_supported(atom: u32, atoms: &XcbAtoms) -> bool {
@ -1980,17 +2044,19 @@ fn xdnd_get_supported_atom(
supported_atoms: &XcbAtoms, supported_atoms: &XcbAtoms,
target: xproto::Window, target: xproto::Window,
) -> u32 { ) -> u32 {
let property = xcb_connection if let Some(reply) = get_reply(
.get_property( || "Failed to get XDnD supported atoms",
xcb_connection.get_property(
false, false,
target, target,
supported_atoms.XdndTypeList, supported_atoms.XdndTypeList,
AtomEnum::ANY, AtomEnum::ANY,
0, 0,
1024, 1024,
),
) )
.unwrap(); .log_with_level(Level::Warn)
if let Ok(reply) = property.reply() { {
if let Some(atoms) = reply.value32() { if let Some(atoms) = reply.value32() {
for atom in atoms { for atom in atoms {
if xdnd_is_atom_supported(atom, &supported_atoms) { if xdnd_is_atom_supported(atom, &supported_atoms) {
@ -2016,9 +2082,11 @@ fn xdnd_send_finished(
sequence: 0, sequence: 0,
response_type: xproto::CLIENT_MESSAGE_EVENT, response_type: xproto::CLIENT_MESSAGE_EVENT,
}; };
xcb_connection check_reply(
.send_event(false, target, EventMask::default(), message) || "Failed to send XDnD finished event",
.unwrap(); xcb_connection.send_event(false, target, EventMask::default(), message),
)
.log_err();
} }
fn xdnd_send_status( fn xdnd_send_status(
@ -2036,22 +2104,24 @@ fn xdnd_send_status(
sequence: 0, sequence: 0,
response_type: xproto::CLIENT_MESSAGE_EVENT, response_type: xproto::CLIENT_MESSAGE_EVENT,
}; };
xcb_connection check_reply(
.send_event(false, target, EventMask::default(), message) || "Failed to send XDnD status event",
.unwrap(); xcb_connection.send_event(false, target, EventMask::default(), message),
)
.log_err();
} }
/// Recomputes `pointer_device_states` by querying all pointer devices. /// Recomputes `pointer_device_states` by querying all pointer devices.
/// When a device is present in `scroll_values_to_preserve`, its value for `ScrollAxisState.scroll_value` is used. /// When a device is present in `scroll_values_to_preserve`, its value for `ScrollAxisState.scroll_value` is used.
fn get_new_pointer_device_states( fn current_pointer_device_states(
xcb_connection: &XCBConnection, xcb_connection: &XCBConnection,
scroll_values_to_preserve: &BTreeMap<xinput::DeviceId, PointerDeviceState>, scroll_values_to_preserve: &BTreeMap<xinput::DeviceId, PointerDeviceState>,
) -> BTreeMap<xinput::DeviceId, PointerDeviceState> { ) -> Option<BTreeMap<xinput::DeviceId, PointerDeviceState>> {
let devices_query_result = xcb_connection let devices_query_result = get_reply(
.xinput_xi_query_device(XINPUT_ALL_DEVICES) || "Failed to query XInput devices",
.unwrap() xcb_connection.xinput_xi_query_device(XINPUT_ALL_DEVICES),
.reply() )
.unwrap(); .log_err()?;
let mut pointer_device_states = BTreeMap::new(); let mut pointer_device_states = BTreeMap::new();
pointer_device_states.extend( pointer_device_states.extend(
@ -2094,7 +2164,7 @@ fn get_new_pointer_device_states(
if pointer_device_states.is_empty() { if pointer_device_states.is_empty() {
log::error!("Found no xinput mouse pointers."); log::error!("Found no xinput mouse pointers.");
} }
return pointer_device_states; return Some(pointer_device_states);
} }
/// Returns true if the device is a pointer device. Does not include pointer device groups. /// Returns true if the device is a pointer device. Does not include pointer device groups.

View file

@ -1,4 +1,5 @@
use anyhow::{Context as _, anyhow}; use anyhow::{Context as _, anyhow};
use x11rb::connection::RequestConnection;
use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig}; use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
use crate::{ use crate::{
@ -32,6 +33,7 @@ use std::{
}; };
use super::{X11Display, XINPUT_ALL_DEVICE_GROUPS, XINPUT_ALL_DEVICES}; use super::{X11Display, XINPUT_ALL_DEVICE_GROUPS, XINPUT_ALL_DEVICES};
x11rb::atom_manager! { x11rb::atom_manager! {
pub XcbAtoms: AtomsCookie { pub XcbAtoms: AtomsCookie {
XA_ATOM, XA_ATOM,
@ -318,43 +320,57 @@ impl rwh::HasDisplayHandle for X11Window {
} }
} }
fn check_reply<C, F>( pub(crate) fn check_reply<E, F, C>(
failure_context: F, failure_context: F,
result: Result<VoidCookie<'_, Rc<XCBConnection>>, ConnectionError>, result: Result<VoidCookie<'_, C>, ConnectionError>,
) -> anyhow::Result<()> ) -> anyhow::Result<()>
where where
C: Display + Send + Sync + 'static, E: Display + Send + Sync + 'static,
F: FnOnce() -> C, F: FnOnce() -> E,
C: RequestConnection,
{ {
result result
.map_err(|connection_error| anyhow!(connection_error)) .map_err(handle_connection_error)
.and_then(|response| { .and_then(|response| response.check().map_err(|reply_error| anyhow!(reply_error)))
response
.check()
.map_err(|error_response| anyhow!(error_response))
})
.with_context(failure_context) .with_context(failure_context)
} }
fn get_reply<C, F, O>( pub(crate) fn get_reply<E, F, C, O>(
failure_context: F, failure_context: F,
result: Result<Cookie<'_, Rc<XCBConnection>, O>, ConnectionError>, result: Result<Cookie<'_, C, O>, ConnectionError>,
) -> anyhow::Result<O> ) -> anyhow::Result<O>
where where
C: Display + Send + Sync + 'static, E: Display + Send + Sync + 'static,
F: FnOnce() -> C, F: FnOnce() -> E,
C: RequestConnection,
O: x11rb::x11_utils::TryParse, O: x11rb::x11_utils::TryParse,
{ {
result result
.map_err(|connection_error| anyhow!(connection_error)) .map_err(handle_connection_error)
.and_then(|response| { .and_then(|response| response.reply().map_err(|reply_error| anyhow!(reply_error)))
response
.reply()
.map_err(|error_response| anyhow!(error_response))
})
.with_context(failure_context) .with_context(failure_context)
} }
/// Convert X11 connection errors to `anyhow::Error` and panic for unrecoverable errors.
pub(crate) fn handle_connection_error(err: ConnectionError) -> anyhow::Error {
match err {
ConnectionError::UnknownError => anyhow!("X11 connection: Unknown error"),
ConnectionError::UnsupportedExtension => anyhow!("X11 connection: Unsupported extension"),
ConnectionError::MaximumRequestLengthExceeded => {
anyhow!("X11 connection: Maximum request length exceeded")
}
ConnectionError::FdPassingFailed => {
panic!("X11 connection: File descriptor passing failed")
}
ConnectionError::ParseError(parse_error) => {
anyhow!(parse_error).context("Parse error in X11 response")
}
ConnectionError::InsufficientMemory => panic!("X11 connection: Insufficient memory"),
ConnectionError::IoError(err) => anyhow!(err).context("X11 connection: IOError"),
_ => anyhow!(err),
}
}
impl X11WindowState { impl X11WindowState {
pub fn new( pub fn new(
handle: AnyWindowHandle, handle: AnyWindowHandle,
@ -581,7 +597,7 @@ impl X11WindowState {
), ),
)?; )?;
xcb.flush().with_context(|| "X11 Flush failed.")?; xcb.flush()?;
let renderer = { let renderer = {
let raw_window = RawWindow { let raw_window = RawWindow {
@ -641,8 +657,7 @@ impl X11WindowState {
|| "X11 DestroyWindow failed while cleaning it up after setup failure.", || "X11 DestroyWindow failed while cleaning it up after setup failure.",
xcb.destroy_window(x_window), xcb.destroy_window(x_window),
)?; )?;
xcb.flush() xcb.flush()?;
.with_context(|| "X11 Flush failed while cleaning it up after setup failure.")?;
} }
setup_result setup_result
@ -670,9 +685,7 @@ impl Drop for X11WindowHandle {
|| "X11 DestroyWindow failed while dropping X11WindowHandle.", || "X11 DestroyWindow failed while dropping X11WindowHandle.",
self.xcb.destroy_window(self.id), self.xcb.destroy_window(self.id),
)?; )?;
self.xcb self.xcb.flush()?;
.flush()
.with_context(|| "X11 Flush failed while dropping X11WindowHandle.")?;
anyhow::Ok(()) anyhow::Ok(())
}) })
.log_err(); .log_err();
@ -691,10 +704,7 @@ impl Drop for X11Window {
|| "X11 DestroyWindow failure.", || "X11 DestroyWindow failure.",
self.0.xcb.destroy_window(self.0.x_window), self.0.xcb.destroy_window(self.0.x_window),
)?; )?;
self.0 self.0.xcb.flush()?;
.xcb
.flush()
.with_context(|| "X11 Flush failed after calling DestroyWindow.")?;
anyhow::Ok(()) anyhow::Ok(())
}) })
@ -812,7 +822,7 @@ impl X11Window {
let state = self.0.state.borrow(); let state = self.0.state.borrow();
check_reply( check_reply(
|| "X11 UngrabPointer before move/resize of window ailed.", || "X11 UngrabPointer before move/resize of window failed.",
self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME), self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
)?; )?;
@ -846,7 +856,11 @@ impl X11Window {
} }
fn flush(&self) -> anyhow::Result<()> { fn flush(&self) -> anyhow::Result<()> {
self.0.xcb.flush().with_context(|| "X11 Flush failed.") self.0
.xcb
.flush()
.map_err(handle_connection_error)
.context("X11 flush failed")
} }
} }
@ -889,9 +903,13 @@ impl X11WindowStatePtr {
)?; )?;
if reply.value_len != 0 { if reply.value_len != 0 {
let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap()); if let Ok(bytes) = reply.value[0..4].try_into() {
let atom = u32::from_ne_bytes(bytes);
let edge_constraints = EdgeConstraints::from_atom(atom); let edge_constraints = EdgeConstraints::from_atom(atom);
state.edge_constraints.replace(edge_constraints); state.edge_constraints.replace(edge_constraints);
} else {
log::error!("Failed to parse GTK_EDGE_CONSTRAINTS");
}
} }
Ok(()) Ok(())
@ -1030,7 +1048,7 @@ impl X11WindowStatePtr {
bounds bounds
} }
pub fn configure(&self, bounds: Bounds<i32>) -> anyhow::Result<()> { pub fn set_bounds(&self, bounds: Bounds<i32>) -> anyhow::Result<()> {
let mut resize_args = None; let mut resize_args = None;
let is_resize; let is_resize;
{ {
@ -1196,12 +1214,14 @@ impl PlatformWindow for X11Window {
} }
fn mouse_position(&self) -> Point<Pixels> { fn mouse_position(&self) -> Point<Pixels> {
let reply = get_reply( get_reply(
|| "X11 QueryPointer failed.", || "X11 QueryPointer failed.",
self.0.xcb.query_pointer(self.0.x_window), self.0.xcb.query_pointer(self.0.x_window),
) )
.unwrap(); .log_err()
.map_or(Point::new(Pixels(0.0), Pixels(0.0)), |reply| {
Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into()) Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
})
} }
fn modifiers(&self) -> Modifiers { fn modifiers(&self) -> Modifiers {
@ -1269,7 +1289,7 @@ impl PlatformWindow for X11Window {
xproto::Time::CURRENT_TIME, xproto::Time::CURRENT_TIME,
) )
.log_err(); .log_err();
self.flush().unwrap(); self.flush().log_err();
} }
fn is_active(&self) -> bool { fn is_active(&self) -> bool {
@ -1323,7 +1343,7 @@ impl PlatformWindow for X11Window {
&data, &data,
), ),
) )
.unwrap(); .log_err();
} }
fn map_window(&mut self) -> anyhow::Result<()> { fn map_window(&mut self) -> anyhow::Result<()> {
@ -1359,7 +1379,7 @@ impl PlatformWindow for X11Window {
message, message,
), ),
) )
.unwrap(); .log_err();
} }
fn zoom(&self) { fn zoom(&self) {
@ -1370,7 +1390,7 @@ impl PlatformWindow for X11Window {
state.atoms._NET_WM_STATE_MAXIMIZED_VERT, state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
state.atoms._NET_WM_STATE_MAXIMIZED_HORZ, state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
) )
.unwrap(); .log_err();
} }
fn toggle_fullscreen(&self) { fn toggle_fullscreen(&self) {
@ -1381,7 +1401,7 @@ impl PlatformWindow for X11Window {
state.atoms._NET_WM_STATE_FULLSCREEN, state.atoms._NET_WM_STATE_FULLSCREEN,
xproto::AtomEnum::NONE.into(), xproto::AtomEnum::NONE.into(),
) )
.unwrap(); .log_err();
} }
fn is_fullscreen(&self) -> bool { fn is_fullscreen(&self) -> bool {
@ -1444,9 +1464,11 @@ impl PlatformWindow for X11Window {
|| "X11 UngrabPointer failed.", || "X11 UngrabPointer failed.",
self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME), self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
) )
.unwrap(); .log_err();
let coords = self.get_root_position(position).unwrap(); let Some(coords) = self.get_root_position(position).log_err() else {
return;
};
let message = ClientMessageEvent::new( let message = ClientMessageEvent::new(
32, 32,
self.0.x_window, self.0.x_window,
@ -1468,16 +1490,16 @@ impl PlatformWindow for X11Window {
message, message,
), ),
) )
.unwrap(); .log_err();
} }
fn start_window_move(&self) { fn start_window_move(&self) {
const MOVERESIZE_MOVE: u32 = 8; const MOVERESIZE_MOVE: u32 = 8;
self.send_moveresize(MOVERESIZE_MOVE).unwrap(); self.send_moveresize(MOVERESIZE_MOVE).log_err();
} }
fn start_window_resize(&self, edge: ResizeEdge) { fn start_window_resize(&self, edge: ResizeEdge) {
self.send_moveresize(edge.to_moveresize()).unwrap(); self.send_moveresize(edge.to_moveresize()).log_err();
} }
fn window_decorations(&self) -> crate::Decorations { fn window_decorations(&self) -> crate::Decorations {
@ -1552,7 +1574,7 @@ impl PlatformWindow for X11Window {
bytemuck::cast_slice::<u32, u8>(&insets), bytemuck::cast_slice::<u32, u8>(&insets),
), ),
) )
.unwrap(); .log_err();
} }
} }
@ -1574,7 +1596,7 @@ impl PlatformWindow for X11Window {
WindowDecorations::Client => [1 << 1, 0, 0, 0, 0], WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
}; };
check_reply( let success = check_reply(
|| "X11 ChangeProperty for _MOTIF_WM_HINTS failed.", || "X11 ChangeProperty for _MOTIF_WM_HINTS failed.",
self.0.xcb.change_property( self.0.xcb.change_property(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
@ -1586,7 +1608,11 @@ impl PlatformWindow for X11Window {
bytemuck::cast_slice::<u32, u8>(&hints_data), bytemuck::cast_slice::<u32, u8>(&hints_data),
), ),
) )
.unwrap(); .log_err();
let Some(()) = success else {
return;
};
match decorations { match decorations {
WindowDecorations::Server => { WindowDecorations::Server => {