Improve error handling and resource cleanup in linux/x11/window.rs (#21079)

* Fixes registration of event handler for xinput-2 device changes,
revealed by this improvement.

* Pushes `.unwrap()` panic-ing outwards to callers.

* Includes a description of what the X11 call was doing when a failure
was encountered.

* Fixes a variety of places where the X11 reply wasn't being inspected
for failures.

* Destroys windows on failure during setup. New structure makes it
possible for the caller of `open_window` to carry on despite failures,
and so partially initialized window should be removed (though all calls
I looked at also panic currently).

Considered pushing this through `linux/x11/client.rs` too but figured
it'd be nice to minimize merge conflicts with #20853.

Release Notes:

- N/A
This commit is contained in:
Michael Sloan 2024-11-22 16:03:46 -07:00 committed by GitHub
parent 8240a52a39
commit c9f2c2792c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 454 additions and 349 deletions

View file

@ -776,11 +776,11 @@ impl X11Client {
}, },
}; };
let window = self.get_window(event.window)?; let window = self.get_window(event.window)?;
window.configure(bounds); window.configure(bounds).unwrap();
} }
Event::PropertyNotify(event) => { Event::PropertyNotify(event) => {
let window = self.get_window(event.window)?; let window = self.get_window(event.window)?;
window.property_notify(event); window.property_notify(event).unwrap();
} }
Event::FocusIn(event) => { Event::FocusIn(event) => {
let window = self.get_window(event.event)?; let window = self.get_window(event.event)?;
@ -1258,11 +1258,9 @@ impl LinuxClient for X11Client {
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(root_id, _)| { .filter_map(|(root_id, _)| {
Some(Rc::new(X11Display::new( Some(Rc::new(
&state.xcb_connection, X11Display::new(&state.xcb_connection, state.scale_factor, root_id).ok()?,
state.scale_factor, ) as Rc<dyn PlatformDisplay>)
root_id,
)?) as Rc<dyn PlatformDisplay>)
}) })
.collect() .collect()
} }
@ -1283,11 +1281,9 @@ impl LinuxClient for X11Client {
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> { fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
let state = self.0.borrow(); let state = self.0.borrow();
Some(Rc::new(X11Display::new( Some(Rc::new(
&state.xcb_connection, X11Display::new(&state.xcb_connection, state.scale_factor, id.0 as usize).ok()?,
state.scale_factor, ))
id.0 as usize,
)?))
} }
fn open_window( fn open_window(

View file

@ -13,12 +13,17 @@ pub(crate) struct X11Display {
impl X11Display { impl X11Display {
pub(crate) fn new( pub(crate) fn new(
xc: &XCBConnection, xcb: &XCBConnection,
scale_factor: f32, scale_factor: f32,
x_screen_index: usize, x_screen_index: usize,
) -> Option<Self> { ) -> anyhow::Result<Self> {
let screen = xc.setup().roots.get(x_screen_index).unwrap(); let Some(screen) = xcb.setup().roots.get(x_screen_index) else {
Some(Self { return Err(anyhow::anyhow!(
"No screen found with index {}",
x_screen_index
));
};
Ok(Self {
x_screen_index, x_screen_index,
bounds: Bounds { bounds: Bounds {
origin: Default::default(), origin: Default::default(),

View file

@ -1,4 +1,4 @@
use anyhow::Context; use anyhow::{anyhow, Context};
use crate::{ use crate::{
platform::blade::{BladeRenderer, BladeSurfaceConfig}, platform::blade::{BladeRenderer, BladeSurfaceConfig},
@ -14,6 +14,8 @@ use raw_window_handle as rwh;
use util::{maybe, ResultExt}; use util::{maybe, ResultExt};
use x11rb::{ use x11rb::{
connection::Connection, connection::Connection,
cookie::{Cookie, VoidCookie},
errors::ConnectionError,
properties::WmSizeHints, properties::WmSizeHints,
protocol::{ protocol::{
sync, sync,
@ -25,7 +27,7 @@ use x11rb::{
}; };
use std::{ use std::{
cell::RefCell, ffi::c_void, mem::size_of, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc, cell::RefCell, ffi::c_void, fmt::Display, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc,
sync::Arc, sync::Arc,
}; };
@ -77,17 +79,16 @@ x11rb::atom_manager! {
} }
} }
fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent { fn query_render_extent(
let reply = xcb_connection xcb: &Rc<XCBConnection>,
.get_geometry(x_window) x_window: xproto::Window,
.unwrap() ) -> anyhow::Result<gpu::Extent> {
.reply() let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
.unwrap(); Ok(gpu::Extent {
gpu::Extent {
width: reply.width as u32, width: reply.width as u32,
height: reply.height as u32, height: reply.height as u32,
depth: 1, depth: 1,
} })
} }
impl ResizeEdge { impl ResizeEdge {
@ -148,7 +149,7 @@ impl EdgeConstraints {
} }
} }
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
struct Visual { struct Visual {
id: xproto::Visualid, id: xproto::Visualid,
colormap: u32, colormap: u32,
@ -163,8 +164,8 @@ struct VisualSet {
black_pixel: u32, black_pixel: u32,
} }
fn find_visuals(xcb_connection: &XCBConnection, screen_index: usize) -> VisualSet { fn find_visuals(xcb: &XCBConnection, screen_index: usize) -> VisualSet {
let screen = &xcb_connection.setup().roots[screen_index]; let screen = &xcb.setup().roots[screen_index];
let mut set = VisualSet { let mut set = VisualSet {
inherit: Visual { inherit: Visual {
id: screen.root_visual, id: screen.root_visual,
@ -277,13 +278,16 @@ impl X11WindowState {
pub(crate) struct X11WindowStatePtr { pub(crate) struct X11WindowStatePtr {
pub state: Rc<RefCell<X11WindowState>>, pub state: Rc<RefCell<X11WindowState>>,
pub(crate) callbacks: Rc<RefCell<Callbacks>>, pub(crate) callbacks: Rc<RefCell<Callbacks>>,
xcb_connection: Rc<XCBConnection>, xcb: Rc<XCBConnection>,
x_window: xproto::Window, x_window: xproto::Window,
} }
impl rwh::HasWindowHandle for RawWindow { impl rwh::HasWindowHandle for RawWindow {
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> { fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
let non_zero = NonZeroU32::new(self.window_id).unwrap(); let Some(non_zero) = NonZeroU32::new(self.window_id) else {
log::error!("RawWindow.window_id zero when getting window handle.");
return Err(rwh::HandleError::Unavailable);
};
let mut handle = rwh::XcbWindowHandle::new(non_zero); let mut handle = rwh::XcbWindowHandle::new(non_zero);
handle.visual_id = NonZeroU32::new(self.visual_id); handle.visual_id = NonZeroU32::new(self.visual_id);
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) }) Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
@ -291,7 +295,10 @@ impl rwh::HasWindowHandle for RawWindow {
} }
impl rwh::HasDisplayHandle for RawWindow { impl rwh::HasDisplayHandle for RawWindow {
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> { fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
let non_zero = NonNull::new(self.connection).unwrap(); let Some(non_zero) = NonNull::new(self.connection) else {
log::error!("Null RawWindow.connection when getting display handle.");
return Err(rwh::HandleError::Unavailable);
};
let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32); let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) }) Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
} }
@ -308,6 +315,43 @@ impl rwh::HasDisplayHandle for X11Window {
} }
} }
fn check_reply<C, F>(
failure_context: F,
result: Result<VoidCookie<'_, Rc<XCBConnection>>, ConnectionError>,
) -> anyhow::Result<()>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
{
result
.map_err(|connection_error| anyhow!(connection_error))
.and_then(|response| {
response
.check()
.map_err(|error_response| anyhow!(error_response))
})
.with_context(failure_context)
}
fn get_reply<C, F, O>(
failure_context: F,
result: Result<Cookie<'_, Rc<XCBConnection>, O>, ConnectionError>,
) -> anyhow::Result<O>
where
C: Display + Send + Sync + 'static,
F: FnOnce() -> C,
O: x11rb::x11_utils::TryParse,
{
result
.map_err(|connection_error| anyhow!(connection_error))
.and_then(|response| {
response
.reply()
.map_err(|error_response| anyhow!(error_response))
})
.with_context(failure_context)
}
impl X11WindowState { impl X11WindowState {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
@ -315,7 +359,7 @@ impl X11WindowState {
client: X11ClientStatePtr, client: X11ClientStatePtr,
executor: ForegroundExecutor, executor: ForegroundExecutor,
params: WindowParams, params: WindowParams,
xcb_connection: &Rc<XCBConnection>, xcb: &Rc<XCBConnection>,
client_side_decorations_supported: bool, client_side_decorations_supported: bool,
x_main_screen_index: usize, x_main_screen_index: usize,
x_window: xproto::Window, x_window: xproto::Window,
@ -327,7 +371,7 @@ impl X11WindowState {
.display_id .display_id
.map_or(x_main_screen_index, |did| did.0 as usize); .map_or(x_main_screen_index, |did| did.0 as usize);
let visual_set = find_visuals(&xcb_connection, x_screen_index); let visual_set = find_visuals(&xcb, x_screen_index);
let visual = match visual_set.transparent { let visual = match visual_set.transparent {
Some(visual) => visual, Some(visual) => visual,
@ -341,12 +385,12 @@ impl X11WindowState {
let colormap = if visual.colormap != 0 { let colormap = if visual.colormap != 0 {
visual.colormap visual.colormap
} else { } else {
let id = xcb_connection.generate_id().unwrap(); let id = xcb.generate_id()?;
log::info!("Creating colormap {}", id); log::info!("Creating colormap {}", id);
xcb_connection check_reply(
.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id) || format!("X11 CreateColormap failed. id: {}", id),
.unwrap() xcb.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id),
.check()?; )?;
id id
}; };
@ -370,8 +414,12 @@ impl X11WindowState {
bounds.size.height = 600.into(); bounds.size.height = 600.into();
} }
xcb_connection check_reply(
.create_window( || {
format!("X11 CreateWindow failed. depth: {}, x_window: {}, visual_set.root: {}, bounds.origin.x.0: {}, bounds.origin.y.0: {}, bounds.size.width.0: {}, bounds.size.height.0: {}",
visual.depth, x_window, visual_set.root, bounds.origin.x.0 + 2, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0)
},
xcb.create_window(
visual.depth, visual.depth,
x_window, x_window,
visual_set.root, visual_set.root,
@ -383,96 +431,101 @@ impl X11WindowState {
xproto::WindowClass::INPUT_OUTPUT, xproto::WindowClass::INPUT_OUTPUT,
visual.id, visual.id,
&win_aux, &win_aux,
) ),
.unwrap() )?;
.check().with_context(|| {
format!("CreateWindow request to X server failed. depth: {}, x_window: {}, visual_set.root: {}, bounds.origin.x.0: {}, bounds.origin.y.0: {}, bounds.size.width.0: {}, bounds.size.height.0: {}",
visual.depth, x_window, visual_set.root, bounds.origin.x.0 + 2, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0)
})?;
// Collect errors during setup, so that window can be destroyed on failure.
let setup_result = maybe!({
if let Some(size) = params.window_min_size { if let Some(size) = params.window_min_size {
let mut size_hints = WmSizeHints::new(); let mut size_hints = WmSizeHints::new();
size_hints.min_size = Some((size.width.0 as i32, size.height.0 as i32)); let min_size = (size.width.0 as i32, size.height.0 as i32);
size_hints size_hints.min_size = Some(min_size);
.set_normal_hints(xcb_connection, x_window) check_reply(
.unwrap(); || {
format!(
"X11 change of WM_SIZE_HINTS failed. min_size: {:?}",
min_size
)
},
size_hints.set_normal_hints(xcb, x_window),
)?;
} }
let reply = xcb_connection let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
.get_geometry(x_window)
.unwrap()
.reply()
.unwrap();
if reply.x == 0 && reply.y == 0 { if reply.x == 0 && reply.y == 0 {
bounds.origin.x.0 += 2; bounds.origin.x.0 += 2;
// Work around a bug where our rendered content appears // Work around a bug where our rendered content appears
// outside the window bounds when opened at the default position // outside the window bounds when opened at the default position
// (14px, 49px on X + Gnome + Ubuntu 22). // (14px, 49px on X + Gnome + Ubuntu 22).
xcb_connection let x = bounds.origin.x.0;
.configure_window( let y = bounds.origin.y.0;
x_window, check_reply(
&xproto::ConfigureWindowAux::new() || format!("X11 ConfigureWindow failed. x: {}, y: {}", x, y),
.x(bounds.origin.x.0) xcb.configure_window(x_window, &xproto::ConfigureWindowAux::new().x(x).y(y)),
.y(bounds.origin.y.0), )?;
)
.unwrap();
} }
if let Some(titlebar) = params.titlebar { if let Some(titlebar) = params.titlebar {
if let Some(title) = titlebar.title { if let Some(title) = titlebar.title {
xcb_connection check_reply(
.change_property8( || "X11 ChangeProperty8 on window title failed.",
xcb.change_property8(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
x_window, x_window,
xproto::AtomEnum::WM_NAME, xproto::AtomEnum::WM_NAME,
xproto::AtomEnum::STRING, xproto::AtomEnum::STRING,
title.as_bytes(), title.as_bytes(),
) ),
.unwrap(); )?;
} }
} }
if params.kind == WindowKind::PopUp { if params.kind == WindowKind::PopUp {
xcb_connection check_reply(
.change_property32( || "X11 ChangeProperty32 setting window type for pop-up failed.",
xcb.change_property32(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
x_window, x_window,
atoms._NET_WM_WINDOW_TYPE, atoms._NET_WM_WINDOW_TYPE,
xproto::AtomEnum::ATOM, xproto::AtomEnum::ATOM,
&[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION], &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
) ),
.unwrap(); )?;
} }
xcb_connection check_reply(
.change_property32( || "X11 ChangeProperty32 setting protocols failed.",
xcb.change_property32(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
x_window, x_window,
atoms.WM_PROTOCOLS, atoms.WM_PROTOCOLS,
xproto::AtomEnum::ATOM, xproto::AtomEnum::ATOM,
&[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST], &[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST],
) ),
.unwrap(); )?;
sync::initialize(xcb_connection, 3, 1).unwrap(); get_reply(
let sync_request_counter = xcb_connection.generate_id().unwrap(); || "X11 sync protocol initialize failed.",
sync::create_counter( sync::initialize(xcb, 3, 1),
xcb_connection, )?;
sync_request_counter, let sync_request_counter = xcb.generate_id()?;
sync::Int64 { lo: 0, hi: 0 }, check_reply(
) || "X11 sync CreateCounter failed.",
.unwrap(); sync::create_counter(xcb, sync_request_counter, sync::Int64 { lo: 0, hi: 0 }),
)?;
xcb_connection check_reply(
.change_property32( || "X11 ChangeProperty32 setting sync request counter failed.",
xcb.change_property32(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
x_window, x_window,
atoms._NET_WM_SYNC_REQUEST_COUNTER, atoms._NET_WM_SYNC_REQUEST_COUNTER,
xproto::AtomEnum::CARDINAL, xproto::AtomEnum::CARDINAL,
&[sync_request_counter], &[sync_request_counter],
) ),
.unwrap(); )?;
xcb_connection check_reply(
.xinput_xi_select_events( || "X11 XiSelectEvents failed.",
xcb.xinput_xi_select_events(
x_window, x_window,
&[xinput::EventMask { &[xinput::EventMask {
deviceid: XINPUT_ALL_DEVICE_GROUPS, deviceid: XINPUT_ALL_DEVICE_GROUPS,
@ -484,28 +537,27 @@ impl X11WindowState {
| xinput::XIEventMask::LEAVE, | xinput::XIEventMask::LEAVE,
], ],
}], }],
) ),
.unwrap(); )?;
xcb_connection check_reply(
.xinput_xi_select_events( || "X11 XiSelectEvents for device changes failed.",
xcb.xinput_xi_select_events(
x_window, x_window,
&[xinput::EventMask { &[xinput::EventMask {
deviceid: XINPUT_ALL_DEVICES, deviceid: XINPUT_ALL_DEVICES,
mask: vec![ mask: vec![
xinput::XIEventMask::HIERARCHY, xinput::XIEventMask::HIERARCHY | xinput::XIEventMask::DEVICE_CHANGED,
xinput::XIEventMask::DEVICE_CHANGED,
], ],
}], }],
) ),
.unwrap(); )?;
xcb_connection.flush().unwrap(); xcb.flush().with_context(|| "X11 Flush failed.")?;
let raw = RawWindow { let raw = RawWindow {
connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection( connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(xcb)
xcb_connection, as *mut _,
) as *mut _,
screen_id: x_screen_index, screen_id: x_screen_index,
window_id: x_window, window_id: x_window,
visual_id: visual.id, visual_id: visual.id,
@ -521,27 +573,27 @@ impl X11WindowState {
}, },
) )
} }
.map_err(|e| anyhow::anyhow!("{:?}", e))?, .map_err(|e| anyhow!("{:?}", e))?,
); );
let config = BladeSurfaceConfig { let config = BladeSurfaceConfig {
// Note: this has to be done after the GPU init, or otherwise // Note: this has to be done after the GPU init, or otherwise
// the sizes are immediately invalidated. // the sizes are immediately invalidated.
size: query_render_extent(xcb_connection, x_window), size: query_render_extent(xcb, x_window)?,
// We set it to transparent by default, even if we have client-side // We set it to transparent by default, even if we have client-side
// decorations, since those seem to work on X11 even without `true` here. // decorations, since those seem to work on X11 even without `true` here.
// If the window appearance changes, then the renderer will get updated // If the window appearance changes, then the renderer will get updated
// too // too
transparent: false, transparent: false,
}; };
xcb_connection.map_window(x_window).unwrap(); check_reply(|| "X11 MapWindow failed.", xcb.map_window(x_window))?;
let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
Ok(Self { Ok(Self {
client, client,
executor, executor,
display: Rc::new( display,
X11Display::new(xcb_connection, scale_factor, x_screen_index).unwrap(),
),
_raw: raw, _raw: raw,
x_root_window: visual_set.root, x_root_window: visual_set.root,
bounds: bounds.to_pixels(scale_factor), bounds: bounds.to_pixels(scale_factor),
@ -566,6 +618,18 @@ impl X11WindowState {
counter_id: sync_request_counter, counter_id: sync_request_counter,
last_sync_counter: None, last_sync_counter: None,
}) })
});
if setup_result.is_err() {
check_reply(
|| "X11 DestroyWindow failed while cleaning it up after setup failure.",
xcb.destroy_window(x_window),
)?;
xcb.flush()
.with_context(|| "X11 Flush failed while cleaning it up after setup failure.")?;
}
setup_result
} }
fn content_size(&self) -> Size<Pixels> { fn content_size(&self) -> Size<Pixels> {
@ -577,6 +641,28 @@ impl X11WindowState {
} }
} }
/// A handle to an X11 window which destroys it on Drop.
pub struct X11WindowHandle {
id: xproto::Window,
xcb: Rc<XCBConnection>,
}
impl Drop for X11WindowHandle {
fn drop(&mut self) {
maybe!({
check_reply(
|| "X11 DestroyWindow failed while dropping X11WindowHandle.",
self.xcb.destroy_window(self.id),
)?;
self.xcb
.flush()
.with_context(|| "X11 Flush failed while dropping X11WindowHandle.")?;
anyhow::Ok(())
})
.log_err();
}
}
pub(crate) struct X11Window(pub X11WindowStatePtr); pub(crate) struct X11Window(pub X11WindowStatePtr);
impl Drop for X11Window { impl Drop for X11Window {
@ -585,13 +671,17 @@ impl Drop for X11Window {
state.renderer.destroy(); state.renderer.destroy();
let destroy_x_window = maybe!({ let destroy_x_window = maybe!({
self.0.xcb_connection.unmap_window(self.0.x_window)?; check_reply(
self.0.xcb_connection.destroy_window(self.0.x_window)?; || "X11 DestroyWindow failure.",
self.0.xcb_connection.flush()?; self.0.xcb.destroy_window(self.0.x_window),
)?;
self.0
.xcb
.flush()
.with_context(|| "X11 Flush failed after calling DestroyWindow.")?;
anyhow::Ok(()) anyhow::Ok(())
}) })
.context("unmapping and destroying X11 window")
.log_err(); .log_err();
if destroy_x_window.is_some() { if destroy_x_window.is_some() {
@ -627,7 +717,7 @@ impl X11Window {
client: X11ClientStatePtr, client: X11ClientStatePtr,
executor: ForegroundExecutor, executor: ForegroundExecutor,
params: WindowParams, params: WindowParams,
xcb_connection: &Rc<XCBConnection>, xcb: &Rc<XCBConnection>,
client_side_decorations_supported: bool, client_side_decorations_supported: bool,
x_main_screen_index: usize, x_main_screen_index: usize,
x_window: xproto::Window, x_window: xproto::Window,
@ -641,7 +731,7 @@ impl X11Window {
client, client,
executor, executor,
params, params,
xcb_connection, xcb,
client_side_decorations_supported, client_side_decorations_supported,
x_main_screen_index, x_main_screen_index,
x_window, x_window,
@ -650,17 +740,23 @@ impl X11Window {
appearance, appearance,
)?)), )?)),
callbacks: Rc::new(RefCell::new(Callbacks::default())), callbacks: Rc::new(RefCell::new(Callbacks::default())),
xcb_connection: xcb_connection.clone(), xcb: xcb.clone(),
x_window, x_window,
}; };
let state = ptr.state.borrow_mut(); let state = ptr.state.borrow_mut();
ptr.set_wm_properties(state); ptr.set_wm_properties(state)?;
Ok(Self(ptr)) Ok(Self(ptr))
} }
fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) { fn set_wm_hints<C: Display + Send + Sync + 'static, F: FnOnce() -> C>(
&self,
failure_context: F,
wm_hint_property_state: WmHintPropertyState,
prop1: u32,
prop2: u32,
) -> anyhow::Result<()> {
let state = self.0.state.borrow(); let state = self.0.state.borrow();
let message = ClientMessageEvent::new( let message = ClientMessageEvent::new(
32, 32,
@ -668,51 +764,45 @@ impl X11Window {
state.atoms._NET_WM_STATE, state.atoms._NET_WM_STATE,
[wm_hint_property_state as u32, prop1, prop2, 1, 0], [wm_hint_property_state as u32, prop1, prop2, 1, 0],
); );
self.0 check_reply(
.xcb_connection failure_context,
.send_event( self.0.xcb.send_event(
false, false,
state.x_root_window, state.x_root_window,
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
message, message,
),
) )
.unwrap()
.check()
.unwrap();
} }
fn get_root_position(&self, position: Point<Pixels>) -> TranslateCoordinatesReply { fn get_root_position(
&self,
position: Point<Pixels>,
) -> anyhow::Result<TranslateCoordinatesReply> {
let state = self.0.state.borrow(); let state = self.0.state.borrow();
self.0 get_reply(
.xcb_connection || "X11 TranslateCoordinates failed.",
.translate_coordinates( self.0.xcb.translate_coordinates(
self.0.x_window, self.0.x_window,
state.x_root_window, state.x_root_window,
(position.x.0 * state.scale_factor) as i16, (position.x.0 * state.scale_factor) as i16,
(position.y.0 * state.scale_factor) as i16, (position.y.0 * state.scale_factor) as i16,
),
) )
.unwrap()
.reply()
.unwrap()
} }
fn send_moveresize(&self, flag: u32) { fn send_moveresize(&self, flag: u32) -> anyhow::Result<()> {
let state = self.0.state.borrow(); let state = self.0.state.borrow();
self.0 check_reply(
.xcb_connection || "X11 UngrabPointer before move/resize of window ailed.",
.ungrab_pointer(x11rb::CURRENT_TIME) self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
.unwrap() )?;
.check()
.unwrap();
let pointer = self let pointer = get_reply(
.0 || "X11 QueryPointer before move/resize of window failed.",
.xcb_connection self.0.xcb.query_pointer(self.0.x_window),
.query_pointer(self.0.x_window) )?;
.unwrap()
.reply()
.unwrap();
let message = ClientMessageEvent::new( let message = ClientMessageEvent::new(
32, 32,
self.0.x_window, self.0.x_window,
@ -725,17 +815,21 @@ impl X11Window {
0, 0,
], ],
); );
self.0 check_reply(
.xcb_connection || "X11 SendEvent to move/resize window failed.",
.send_event( self.0.xcb.send_event(
false, false,
state.x_root_window, state.x_root_window,
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
message, message,
) ),
.unwrap(); )?;
self.0.xcb_connection.flush().unwrap(); self.flush()
}
fn flush(&self) -> anyhow::Result<()> {
self.0.xcb.flush().with_context(|| "X11 Flush failed.")
} }
} }
@ -751,51 +845,56 @@ impl X11WindowStatePtr {
} }
} }
pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) { pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) -> anyhow::Result<()> {
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
if event.atom == state.atoms._NET_WM_STATE { if event.atom == state.atoms._NET_WM_STATE {
self.set_wm_properties(state); self.set_wm_properties(state)?;
} else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS { } else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
self.set_edge_constraints(state); self.set_edge_constraints(state)?;
} }
Ok(())
} }
fn set_edge_constraints(&self, mut state: std::cell::RefMut<X11WindowState>) { fn set_edge_constraints(
let reply = self &self,
.xcb_connection mut state: std::cell::RefMut<X11WindowState>,
.get_property( ) -> anyhow::Result<()> {
let reply = get_reply(
|| "X11 GetProperty for _GTK_EDGE_CONSTRAINTS failed.",
self.xcb.get_property(
false, false,
self.x_window, self.x_window,
state.atoms._GTK_EDGE_CONSTRAINTS, state.atoms._GTK_EDGE_CONSTRAINTS,
xproto::AtomEnum::CARDINAL, xproto::AtomEnum::CARDINAL,
0, 0,
4, 4,
) ),
.unwrap() )?;
.reply()
.unwrap();
if reply.value_len != 0 { if reply.value_len != 0 {
let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap()); let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap());
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);
} }
Ok(())
} }
fn set_wm_properties(&self, mut state: std::cell::RefMut<X11WindowState>) { fn set_wm_properties(
let reply = self &self,
.xcb_connection mut state: std::cell::RefMut<X11WindowState>,
.get_property( ) -> anyhow::Result<()> {
let reply = get_reply(
|| "X11 GetProperty for _NET_WM_STATE failed.",
self.xcb.get_property(
false, false,
self.x_window, self.x_window,
state.atoms._NET_WM_STATE, state.atoms._NET_WM_STATE,
xproto::AtomEnum::ATOM, xproto::AtomEnum::ATOM,
0, 0,
u32::MAX, u32::MAX,
) ),
.unwrap() )?;
.reply()
.unwrap();
let atoms = reply let atoms = reply
.value .value
@ -821,6 +920,8 @@ impl X11WindowStatePtr {
state.hidden = true; state.hidden = true;
} }
} }
Ok(())
} }
pub fn close(&self) { pub fn close(&self) {
@ -912,7 +1013,7 @@ impl X11WindowStatePtr {
bounds bounds
} }
pub fn configure(&self, bounds: Bounds<i32>) { pub fn configure(&self, bounds: Bounds<i32>) -> anyhow::Result<()> {
let mut resize_args = None; let mut resize_args = None;
let is_resize; let is_resize;
{ {
@ -930,7 +1031,7 @@ impl X11WindowStatePtr {
state.bounds = bounds; state.bounds = bounds;
} }
let gpu_size = query_render_extent(&self.xcb_connection, self.x_window); let gpu_size = query_render_extent(&self.xcb, self.x_window)?;
if true { if true {
state.renderer.update_drawable_size(size( state.renderer.update_drawable_size(size(
DevicePixels(gpu_size.width as i32), DevicePixels(gpu_size.width as i32),
@ -939,7 +1040,10 @@ impl X11WindowStatePtr {
resize_args = Some((state.content_size(), state.scale_factor)); resize_args = Some((state.content_size(), state.scale_factor));
} }
if let Some(value) = state.last_sync_counter.take() { if let Some(value) = state.last_sync_counter.take() {
sync::set_counter(&self.xcb_connection, state.counter_id, value).unwrap(); check_reply(
|| "X11 sync SetCounter failed.",
sync::set_counter(&self.xcb, state.counter_id, value),
)?;
} }
} }
@ -951,9 +1055,11 @@ impl X11WindowStatePtr {
} }
if !is_resize { if !is_resize {
if let Some(ref mut fun) = callbacks.moved { if let Some(ref mut fun) = callbacks.moved {
fun() fun();
} }
} }
Ok(())
} }
pub fn set_active(&self, focus: bool) { pub fn set_active(&self, focus: bool) {
@ -1025,12 +1131,10 @@ impl PlatformWindow for X11Window {
} }
fn mouse_position(&self) -> Point<Pixels> { fn mouse_position(&self) -> Point<Pixels> {
let reply = self let reply = get_reply(
.0 || "X11 QueryPointer failed.",
.xcb_connection self.0.xcb.query_pointer(self.0.x_window),
.query_pointer(self.0.x_window) )
.unwrap()
.reply()
.unwrap(); .unwrap();
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())
} }
@ -1073,7 +1177,7 @@ impl PlatformWindow for X11Window {
data, data,
); );
self.0 self.0
.xcb_connection .xcb
.send_event( .send_event(
false, false,
self.0.state.borrow().x_root_window, self.0.state.borrow().x_root_window,
@ -1082,14 +1186,14 @@ impl PlatformWindow for X11Window {
) )
.log_err(); .log_err();
self.0 self.0
.xcb_connection .xcb
.set_input_focus( .set_input_focus(
xproto::InputFocus::POINTER_ROOT, xproto::InputFocus::POINTER_ROOT,
self.0.x_window, self.0.x_window,
xproto::Time::CURRENT_TIME, xproto::Time::CURRENT_TIME,
) )
.log_err(); .log_err();
self.0.xcb_connection.flush().unwrap(); self.flush().unwrap();
} }
fn is_active(&self) -> bool { fn is_active(&self) -> bool {
@ -1101,28 +1205,30 @@ impl PlatformWindow for X11Window {
} }
fn set_title(&mut self, title: &str) { fn set_title(&mut self, title: &str) {
self.0 check_reply(
.xcb_connection || "X11 ChangeProperty8 on WM_NAME failed.",
.change_property8( self.0.xcb.change_property8(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
self.0.x_window, self.0.x_window,
xproto::AtomEnum::WM_NAME, xproto::AtomEnum::WM_NAME,
xproto::AtomEnum::STRING, xproto::AtomEnum::STRING,
title.as_bytes(), title.as_bytes(),
),
) )
.unwrap(); .unwrap();
self.0 check_reply(
.xcb_connection || "X11 ChangeProperty8 on _NET_WM_NAME failed.",
.change_property8( self.0.xcb.change_property8(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
self.0.x_window, self.0.x_window,
self.0.state.borrow().atoms._NET_WM_NAME, self.0.state.borrow().atoms._NET_WM_NAME,
self.0.state.borrow().atoms.UTF8_STRING, self.0.state.borrow().atoms.UTF8_STRING,
title.as_bytes(), title.as_bytes(),
),
) )
.unwrap(); .unwrap();
self.0.xcb_connection.flush().unwrap(); self.flush().unwrap();
} }
fn set_app_id(&mut self, app_id: &str) { fn set_app_id(&mut self, app_id: &str) {
@ -1131,17 +1237,16 @@ impl PlatformWindow for X11Window {
data.push(b'\0'); data.push(b'\0');
data.extend(app_id.bytes()); // class data.extend(app_id.bytes()); // class
self.0 check_reply(
.xcb_connection || "X11 ChangeProperty8 for WM_CLASS failed.",
.change_property8( self.0.xcb.change_property8(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
self.0.x_window, self.0.x_window,
xproto::AtomEnum::WM_CLASS, xproto::AtomEnum::WM_CLASS,
xproto::AtomEnum::STRING, xproto::AtomEnum::STRING,
&data, &data,
),
) )
.unwrap()
.check()
.unwrap(); .unwrap();
} }
@ -1169,35 +1274,38 @@ impl PlatformWindow for X11Window {
state.atoms.WM_CHANGE_STATE, state.atoms.WM_CHANGE_STATE,
[WINDOW_ICONIC_STATE, 0, 0, 0, 0], [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
); );
self.0 check_reply(
.xcb_connection || "X11 SendEvent to minimize window failed.",
.send_event( self.0.xcb.send_event(
false, false,
state.x_root_window, state.x_root_window,
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
message, message,
),
) )
.unwrap()
.check()
.unwrap(); .unwrap();
} }
fn zoom(&self) { fn zoom(&self) {
let state = self.0.state.borrow(); let state = self.0.state.borrow();
self.set_wm_hints( self.set_wm_hints(
|| "X11 SendEvent to maximize a window failed.",
WmHintPropertyState::Toggle, WmHintPropertyState::Toggle,
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();
} }
fn toggle_fullscreen(&self) { fn toggle_fullscreen(&self) {
let state = self.0.state.borrow(); let state = self.0.state.borrow();
self.set_wm_hints( self.set_wm_hints(
|| "X11 SendEvent to fullscreen a window failed.",
WmHintPropertyState::Toggle, WmHintPropertyState::Toggle,
state.atoms._NET_WM_STATE_FULLSCREEN, state.atoms._NET_WM_STATE_FULLSCREEN,
xproto::AtomEnum::NONE.into(), xproto::AtomEnum::NONE.into(),
); )
.unwrap();
} }
fn is_fullscreen(&self) -> bool { fn is_fullscreen(&self) -> bool {
@ -1253,14 +1361,13 @@ impl PlatformWindow for X11Window {
fn show_window_menu(&self, position: Point<Pixels>) { fn show_window_menu(&self, position: Point<Pixels>) {
let state = self.0.state.borrow(); let state = self.0.state.borrow();
self.0 check_reply(
.xcb_connection || "X11 UngrabPointer failed.",
.ungrab_pointer(x11rb::CURRENT_TIME) self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
.unwrap() )
.check()
.unwrap(); .unwrap();
let coords = self.get_root_position(position); let coords = self.get_root_position(position).unwrap();
let message = ClientMessageEvent::new( let message = ClientMessageEvent::new(
32, 32,
self.0.x_window, self.0.x_window,
@ -1273,26 +1380,25 @@ impl PlatformWindow for X11Window {
0, 0,
], ],
); );
self.0 check_reply(
.xcb_connection || "X11 SendEvent to show window menu failed.",
.send_event( self.0.xcb.send_event(
false, false,
state.x_root_window, state.x_root_window,
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
message, message,
),
) )
.unwrap()
.check()
.unwrap(); .unwrap();
} }
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); self.send_moveresize(MOVERESIZE_MOVE).unwrap();
} }
fn start_window_resize(&self, edge: ResizeEdge) { fn start_window_resize(&self, edge: ResizeEdge) {
self.send_moveresize(edge.to_moveresize()); self.send_moveresize(edge.to_moveresize()).unwrap();
} }
fn window_decorations(&self) -> crate::Decorations { fn window_decorations(&self) -> crate::Decorations {
@ -1355,9 +1461,9 @@ impl PlatformWindow for X11Window {
if state.last_insets != insets { if state.last_insets != insets {
state.last_insets = insets; state.last_insets = insets;
self.0 check_reply(
.xcb_connection || "X11 ChangeProperty for _GTK_FRAME_EXTENTS failed.",
.change_property( self.0.xcb.change_property(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
self.0.x_window, self.0.x_window,
state.atoms._GTK_FRAME_EXTENTS, state.atoms._GTK_FRAME_EXTENTS,
@ -1365,9 +1471,8 @@ impl PlatformWindow for X11Window {
size_of::<u32>() as u8 * 8, size_of::<u32>() as u8 * 8,
4, 4,
bytemuck::cast_slice::<u32, u8>(&insets), bytemuck::cast_slice::<u32, u8>(&insets),
),
) )
.unwrap()
.check()
.unwrap(); .unwrap();
} }
} }
@ -1390,19 +1495,18 @@ impl PlatformWindow for X11Window {
WindowDecorations::Client => [1 << 1, 0, 0, 0, 0], WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
}; };
self.0 check_reply(
.xcb_connection || "X11 ChangeProperty for _MOTIF_WM_HINTS failed.",
.change_property( self.0.xcb.change_property(
xproto::PropMode::REPLACE, xproto::PropMode::REPLACE,
self.0.x_window, self.0.x_window,
state.atoms._MOTIF_WM_HINTS, state.atoms._MOTIF_WM_HINTS,
state.atoms._MOTIF_WM_HINTS, state.atoms._MOTIF_WM_HINTS,
std::mem::size_of::<u32>() as u8 * 8, size_of::<u32>() as u8 * 8,
5, 5,
bytemuck::cast_slice::<u32, u8>(&hints_data), bytemuck::cast_slice::<u32, u8>(&hints_data),
),
) )
.unwrap()
.check()
.unwrap(); .unwrap();
match decorations { match decorations {