From 45465c5f46abed6c6ce370fffde5edc8e4cd5aa3 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 5 Apr 2024 21:18:49 +0200 Subject: [PATCH] X11: Split off the event loop into a separate module (#183) This PR splits off the X11 event loop logic into a separate module. It also changes the X11 implementation of the `Window` type to take only a shared reference to the inner type (`&WindowInner` instead of `&mut WindowInner`), bringing it in line with the other backends. This does not change any of the logic however, it only separates some of the window state from the event loop state, to make sure they don't step on each other's toes in the future (particularly around the WindowHandler). This is part of the effort to split up #174 into smaller pieces. --- src/x11/event_loop.rs | 303 ++++++++++++++++++++++++++++++++++++ src/x11/mod.rs | 1 + src/x11/visual_info.rs | 2 +- src/x11/window.rs | 320 +++----------------------------------- src/x11/xcb_connection.rs | 13 +- 5 files changed, 332 insertions(+), 307 deletions(-) create mode 100644 src/x11/event_loop.rs diff --git a/src/x11/event_loop.rs b/src/x11/event_loop.rs new file mode 100644 index 0000000..6b6ecd3 --- /dev/null +++ b/src/x11/event_loop.rs @@ -0,0 +1,303 @@ +use crate::x11::keyboard::{convert_key_press_event, convert_key_release_event, key_mods}; +use crate::x11::{ParentHandle, Window, WindowInner}; +use crate::{ + Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, WindowEvent, WindowHandler, + WindowInfo, +}; +use std::error::Error; +use std::os::fd::AsRawFd; +use std::time::{Duration, Instant}; +use x11rb::connection::Connection; +use x11rb::protocol::Event as XEvent; + +pub(super) struct EventLoop { + handler: Box, + window: WindowInner, + parent_handle: Option, + + new_physical_size: Option, + frame_interval: Duration, + event_loop_running: bool, +} + +impl EventLoop { + pub fn new( + window: WindowInner, handler: impl WindowHandler + 'static, + parent_handle: Option, + ) -> Self { + Self { + window, + handler: Box::new(handler), + parent_handle, + frame_interval: Duration::from_millis(15), + event_loop_running: false, + new_physical_size: None, + } + } + + #[inline] + fn drain_xcb_events(&mut self) -> Result<(), Box> { + // the X server has a tendency to send spurious/extraneous configure notify events when a + // window is resized, and we need to batch those together and just send one resize event + // when they've all been coalesced. + self.new_physical_size = None; + + while let Some(event) = self.window.xcb_connection.conn.poll_for_event()? { + self.handle_xcb_event(event); + } + + if let Some(size) = self.new_physical_size.take() { + self.window.window_info = + WindowInfo::from_physical_size(size, self.window.window_info.scale()); + + let window_info = self.window.window_info; + + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Window(WindowEvent::Resized(window_info)), + ); + } + + Ok(()) + } + + // Event loop + // FIXME: poll() acts fine on linux, sometimes funky on *BSD. XCB upstream uses a define to + // switch between poll() and select() (the latter of which is fine on *BSD), and we should do + // the same. + pub fn run(&mut self) -> Result<(), Box> { + use nix::poll::*; + + let xcb_fd = self.window.xcb_connection.conn.as_raw_fd(); + + let mut last_frame = Instant::now(); + self.event_loop_running = true; + + while self.event_loop_running { + // We'll try to keep a consistent frame pace. If the last frame couldn't be processed in + // the expected frame time, this will throttle down to prevent multiple frames from + // being queued up. The conditional here is needed because event handling and frame + // drawing is interleaved. The `poll()` function below will wait until the next frame + // can be drawn, or until the window receives an event. We thus need to manually check + // if it's already time to draw a new frame. + let next_frame = last_frame + self.frame_interval; + if Instant::now() >= next_frame { + self.handler.on_frame(&mut crate::Window::new(Window { inner: &self.window })); + last_frame = Instant::max(next_frame, Instant::now() - self.frame_interval); + } + + let mut fds = [PollFd::new(xcb_fd, PollFlags::POLLIN)]; + + // Check for any events in the internal buffers + // before going to sleep: + self.drain_xcb_events()?; + + // FIXME: handle errors + poll(&mut fds, next_frame.duration_since(Instant::now()).subsec_millis() as i32) + .unwrap(); + + if let Some(revents) = fds[0].revents() { + if revents.contains(PollFlags::POLLERR) { + panic!("xcb connection poll error"); + } + + if revents.contains(PollFlags::POLLIN) { + self.drain_xcb_events()?; + } + } + + // Check if the parents's handle was dropped (such as when the host + // requested the window to close) + // + // FIXME: This will need to be changed from just setting an atomic to somehow + // synchronizing with the window being closed (using a synchronous channel, or + // by joining on the event loop thread). + if let Some(parent_handle) = &self.parent_handle { + if parent_handle.parent_did_drop() { + self.handle_must_close(); + self.window.close_requested.set(false); + } + } + + // Check if the user has requested the window to close + if self.window.close_requested.get() { + self.handle_must_close(); + self.window.close_requested.set(false); + } + } + + Ok(()) + } + + fn handle_xcb_event(&mut self, event: XEvent) { + // For all the keyboard and mouse events, you can fetch + // `x`, `y`, `detail`, and `state`. + // - `x` and `y` are the position inside the window where the cursor currently is + // when the event happened. + // - `detail` will tell you which keycode was pressed/released (for keyboard events) + // or which mouse button was pressed/released (for mouse events). + // For mouse events, here's what the value means (at least on my current mouse): + // 1 = left mouse button + // 2 = middle mouse button (scroll wheel) + // 3 = right mouse button + // 4 = scroll wheel up + // 5 = scroll wheel down + // 8 = lower side button ("back" button) + // 9 = upper side button ("forward" button) + // Note that you *will* get a "button released" event for even the scroll wheel + // events, which you can probably ignore. + // - `state` will tell you the state of the main three mouse buttons and some of + // the keyboard modifier keys at the time of the event. + // http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 + + match event { + //// + // window + //// + XEvent::ClientMessage(event) => { + if event.format == 32 + && event.data.as_data32()[0] + == self.window.xcb_connection.atoms.WM_DELETE_WINDOW + { + self.handle_close_requested(); + } + } + + XEvent::ConfigureNotify(event) => { + let new_physical_size = PhySize::new(event.width as u32, event.height as u32); + + if self.new_physical_size.is_some() + || new_physical_size != self.window.window_info.physical_size() + { + self.new_physical_size = Some(new_physical_size); + } + } + + //// + // mouse + //// + XEvent::MotionNotify(event) => { + let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); + let logical_pos = physical_pos.to_logical(&self.window.window_info); + + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::CursorMoved { + position: logical_pos, + modifiers: key_mods(event.state), + }), + ); + } + + XEvent::EnterNotify(event) => { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::CursorEntered), + ); + // since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated, + // we generate a CursorMoved as well, so the mouse position from here isn't lost + let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); + let logical_pos = physical_pos.to_logical(&self.window.window_info); + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::CursorMoved { + position: logical_pos, + modifiers: key_mods(event.state), + }), + ); + } + + XEvent::LeaveNotify(_) => { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::CursorLeft), + ); + } + + XEvent::ButtonPress(event) => match event.detail { + 4..=7 => { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::WheelScrolled { + delta: match event.detail { + 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, + 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, + 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, + 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, + _ => unreachable!(), + }, + modifiers: key_mods(event.state), + }), + ); + } + detail => { + let button_id = mouse_id(detail); + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::ButtonPressed { + button: button_id, + modifiers: key_mods(event.state), + }), + ); + } + }, + + XEvent::ButtonRelease(event) => { + if !(4..=7).contains(&event.detail) { + let button_id = mouse_id(event.detail); + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Mouse(MouseEvent::ButtonReleased { + button: button_id, + modifiers: key_mods(event.state), + }), + ); + } + } + + //// + // keys + //// + XEvent::KeyPress(event) => { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Keyboard(convert_key_press_event(&event)), + ); + } + + XEvent::KeyRelease(event) => { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Keyboard(convert_key_release_event(&event)), + ); + } + + _ => {} + } + } + + fn handle_close_requested(&mut self) { + // FIXME: handler should decide whether window stays open or not + self.handle_must_close(); + } + + fn handle_must_close(&mut self) { + self.handler.on_event( + &mut crate::Window::new(Window { inner: &self.window }), + Event::Window(WindowEvent::WillClose), + ); + + self.event_loop_running = false; + } +} + +fn mouse_id(id: u8) -> MouseButton { + match id { + 1 => MouseButton::Left, + 2 => MouseButton::Middle, + 3 => MouseButton::Right, + 8 => MouseButton::Back, + 9 => MouseButton::Forward, + id => MouseButton::Other(id), + } +} diff --git a/src/x11/mod.rs b/src/x11/mod.rs index 71faf04..149df0b 100644 --- a/src/x11/mod.rs +++ b/src/x11/mod.rs @@ -5,5 +5,6 @@ mod window; pub use window::*; mod cursor; +mod event_loop; mod keyboard; mod visual_info; diff --git a/src/x11/visual_info.rs b/src/x11/visual_info.rs index 602b2f0..3f1be38 100644 --- a/src/x11/visual_info.rs +++ b/src/x11/visual_info.rs @@ -63,7 +63,7 @@ impl WindowVisualConfig { // For this 32-bit depth to work, you also need to define a color map and set a border // pixel: https://cgit.freedesktop.org/xorg/xserver/tree/dix/window.c#n818 -pub fn create_color_map( +fn create_color_map( connection: &XcbConnection, visual_id: Visualid, ) -> Result> { let colormap = connection.conn.generate_id()?; diff --git a/src/x11/window.rs b/src/x11/window.rs index 86af03c..5b801ec 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -1,11 +1,10 @@ +use std::cell::Cell; use std::error::Error; use std::ffi::c_void; -use std::os::fd::AsRawFd; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc; use std::sync::Arc; use std::thread; -use std::time::*; use raw_window_handle::{ HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, XlibDisplayHandle, @@ -17,19 +16,17 @@ use x11rb::protocol::xproto::{ AtomEnum, ChangeWindowAttributesAux, ConfigureWindowAux, ConnectionExt as _, CreateGCAux, CreateWindowAux, EventMask, PropMode, Visualid, Window as XWindow, WindowClass, }; -use x11rb::protocol::Event as XEvent; use x11rb::wrapper::ConnectionExt as _; use super::XcbConnection; use crate::{ - Event, MouseButton, MouseCursor, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent, - WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, + Event, MouseCursor, Size, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, + WindowScalePolicy, }; -use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mods}; - #[cfg(feature = "opengl")] use crate::gl::{platform, GlContext}; +use crate::x11::event_loop::EventLoop; use crate::x11::visual_info::WindowVisualConfig; pub struct WindowHandle { @@ -66,7 +63,7 @@ unsafe impl HasRawWindowHandle for WindowHandle { } } -struct ParentHandle { +pub(crate) struct ParentHandle { close_requested: Arc, is_open: Arc, } @@ -96,26 +93,21 @@ impl Drop for ParentHandle { } } -struct WindowInner { - xcb_connection: XcbConnection, +pub(crate) struct WindowInner { + pub(crate) xcb_connection: XcbConnection, window_id: XWindow, - window_info: WindowInfo, + pub(crate) window_info: WindowInfo, visual_id: Visualid, - mouse_cursor: MouseCursor, - - frame_interval: Duration, - event_loop_running: bool, - close_requested: bool, + mouse_cursor: Cell, - new_physical_size: Option, - parent_handle: Option, + pub(crate) close_requested: Cell, #[cfg(feature = "opengl")] gl_context: Option, } pub struct Window<'a> { - inner: &'a mut WindowInner, + pub(crate) inner: &'a WindowInner, } // Hack to allow sending a RawWindowHandle between threads. Do not make public @@ -284,14 +276,9 @@ impl<'a> Window<'a> { window_id, window_info, visual_id: visual_info.visual_id, - mouse_cursor: MouseCursor::default(), - - frame_interval: Duration::from_millis(15), - event_loop_running: false, - close_requested: false, + mouse_cursor: Cell::new(MouseCursor::default()), - new_physical_size: None, - parent_handle, + close_requested: Cell::new(false), #[cfg(feature = "opengl")] gl_context, @@ -307,13 +294,13 @@ impl<'a> Window<'a> { let _ = tx.send(Ok(SendableRwh(window.raw_window_handle()))); - inner.run_event_loop(&mut handler)?; + EventLoop::new(inner, handler, parent_handle).run()?; Ok(()) } - pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) { - if self.inner.mouse_cursor == mouse_cursor { + pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) { + if self.inner.mouse_cursor.get() == mouse_cursor { return; } @@ -327,11 +314,11 @@ impl<'a> Window<'a> { let _ = self.inner.xcb_connection.conn.flush(); } - self.inner.mouse_cursor = mouse_cursor; + self.inner.mouse_cursor.set(mouse_cursor); } pub fn close(&mut self) { - self.inner.close_requested = true; + self.inner.close_requested.set(true); } pub fn has_focus(&mut self) -> bool { @@ -364,266 +351,6 @@ impl<'a> Window<'a> { } } -impl WindowInner { - #[inline] - fn drain_xcb_events(&mut self, handler: &mut dyn WindowHandler) -> Result<(), Box> { - // the X server has a tendency to send spurious/extraneous configure notify events when a - // window is resized, and we need to batch those together and just send one resize event - // when they've all been coalesced. - self.new_physical_size = None; - - while let Some(event) = self.xcb_connection.conn.poll_for_event()? { - self.handle_xcb_event(handler, event); - } - - if let Some(size) = self.new_physical_size.take() { - self.window_info = WindowInfo::from_physical_size(size, self.window_info.scale()); - - let window_info = self.window_info; - - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Window(WindowEvent::Resized(window_info)), - ); - } - - Ok(()) - } - - // Event loop - // FIXME: poll() acts fine on linux, sometimes funky on *BSD. XCB upstream uses a define to - // switch between poll() and select() (the latter of which is fine on *BSD), and we should do - // the same. - fn run_event_loop(&mut self, handler: &mut dyn WindowHandler) -> Result<(), Box> { - use nix::poll::*; - - let xcb_fd = self.xcb_connection.conn.as_raw_fd(); - - let mut last_frame = Instant::now(); - self.event_loop_running = true; - - while self.event_loop_running { - // We'll try to keep a consistent frame pace. If the last frame couldn't be processed in - // the expected frame time, this will throttle down to prevent multiple frames from - // being queued up. The conditional here is needed because event handling and frame - // drawing is interleaved. The `poll()` function below will wait until the next frame - // can be drawn, or until the window receives an event. We thus need to manually check - // if it's already time to draw a new frame. - let next_frame = last_frame + self.frame_interval; - if Instant::now() >= next_frame { - handler.on_frame(&mut crate::Window::new(Window { inner: self })); - last_frame = Instant::max(next_frame, Instant::now() - self.frame_interval); - } - - let mut fds = [PollFd::new(xcb_fd, PollFlags::POLLIN)]; - - // Check for any events in the internal buffers - // before going to sleep: - self.drain_xcb_events(handler)?; - - // FIXME: handle errors - poll(&mut fds, next_frame.duration_since(Instant::now()).subsec_millis() as i32) - .unwrap(); - - if let Some(revents) = fds[0].revents() { - if revents.contains(PollFlags::POLLERR) { - panic!("xcb connection poll error"); - } - - if revents.contains(PollFlags::POLLIN) { - self.drain_xcb_events(handler)?; - } - } - - // Check if the parents's handle was dropped (such as when the host - // requested the window to close) - // - // FIXME: This will need to be changed from just setting an atomic to somehow - // synchronizing with the window being closed (using a synchronous channel, or - // by joining on the event loop thread). - if let Some(parent_handle) = &self.parent_handle { - if parent_handle.parent_did_drop() { - self.handle_must_close(handler); - self.close_requested = false; - } - } - - // Check if the user has requested the window to close - if self.close_requested { - self.handle_must_close(handler); - self.close_requested = false; - } - } - - Ok(()) - } - - fn handle_close_requested(&mut self, handler: &mut dyn WindowHandler) { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Window(WindowEvent::WillClose), - ); - - // FIXME: handler should decide whether window stays open or not - self.event_loop_running = false; - } - - fn handle_must_close(&mut self, handler: &mut dyn WindowHandler) { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Window(WindowEvent::WillClose), - ); - - self.event_loop_running = false; - } - - fn handle_xcb_event(&mut self, handler: &mut dyn WindowHandler, event: XEvent) { - // For all of the keyboard and mouse events, you can fetch - // `x`, `y`, `detail`, and `state`. - // - `x` and `y` are the position inside the window where the cursor currently is - // when the event happened. - // - `detail` will tell you which keycode was pressed/released (for keyboard events) - // or which mouse button was pressed/released (for mouse events). - // For mouse events, here's what the value means (at least on my current mouse): - // 1 = left mouse button - // 2 = middle mouse button (scroll wheel) - // 3 = right mouse button - // 4 = scroll wheel up - // 5 = scroll wheel down - // 8 = lower side button ("back" button) - // 9 = upper side button ("forward" button) - // Note that you *will* get a "button released" event for even the scroll wheel - // events, which you can probably ignore. - // - `state` will tell you the state of the main three mouse buttons and some of - // the keyboard modifier keys at the time of the event. - // http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 - - match event { - //// - // window - //// - XEvent::ClientMessage(event) => { - if event.format == 32 - && event.data.as_data32()[0] == self.xcb_connection.atoms.WM_DELETE_WINDOW - { - self.handle_close_requested(handler); - } - } - - XEvent::ConfigureNotify(event) => { - let new_physical_size = PhySize::new(event.width as u32, event.height as u32); - - if self.new_physical_size.is_some() - || new_physical_size != self.window_info.physical_size() - { - self.new_physical_size = Some(new_physical_size); - } - } - - //// - // mouse - //// - XEvent::MotionNotify(event) => { - let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); - let logical_pos = physical_pos.to_logical(&self.window_info); - - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::CursorMoved { - position: logical_pos, - modifiers: key_mods(event.state), - }), - ); - } - - XEvent::EnterNotify(event) => { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::CursorEntered), - ); - // since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated, - // we generate a CursorMoved as well, so the mouse position from here isn't lost - let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32); - let logical_pos = physical_pos.to_logical(&self.window_info); - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::CursorMoved { - position: logical_pos, - modifiers: key_mods(event.state), - }), - ); - } - - XEvent::LeaveNotify(_) => { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::CursorLeft), - ); - } - - XEvent::ButtonPress(event) => match event.detail { - 4..=7 => { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::WheelScrolled { - delta: match event.detail { - 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, - 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, - 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, - 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, - _ => unreachable!(), - }, - modifiers: key_mods(event.state), - }), - ); - } - detail => { - let button_id = mouse_id(detail); - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::ButtonPressed { - button: button_id, - modifiers: key_mods(event.state), - }), - ); - } - }, - - XEvent::ButtonRelease(event) => { - if !(4..=7).contains(&event.detail) { - let button_id = mouse_id(event.detail); - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Mouse(MouseEvent::ButtonReleased { - button: button_id, - modifiers: key_mods(event.state), - }), - ); - } - } - - //// - // keys - //// - XEvent::KeyPress(event) => { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Keyboard(convert_key_press_event(&event)), - ); - } - - XEvent::KeyRelease(event) => { - handler.on_event( - &mut crate::Window::new(Window { inner: self }), - Event::Keyboard(convert_key_release_event(&event)), - ); - } - - _ => {} - } - } -} - unsafe impl<'a> HasRawWindowHandle for Window<'a> { fn raw_window_handle(&self) -> RawWindowHandle { let mut handle = XlibWindowHandle::empty(); @@ -647,17 +374,6 @@ unsafe impl<'a> HasRawDisplayHandle for Window<'a> { } } -fn mouse_id(id: u8) -> MouseButton { - match id { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - 8 => MouseButton::Back, - 9 => MouseButton::Forward, - id => MouseButton::Other(id), - } -} - pub fn copy_to_clipboard(_data: &str) { todo!() } diff --git a/src/x11/xcb_connection.rs b/src/x11/xcb_connection.rs index cd47670..a5ea06d 100644 --- a/src/x11/xcb_connection.rs +++ b/src/x11/xcb_connection.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::collections::hash_map::{Entry, HashMap}; use std::error::Error; @@ -30,7 +31,7 @@ pub struct XcbConnection { pub(crate) atoms: Atoms, pub(crate) resources: resource_manager::Database, pub(crate) cursor_handle: CursorHandle, - pub(super) cursor_cache: HashMap, + pub(super) cursor_cache: RefCell>, } impl XcbConnection { @@ -56,7 +57,7 @@ impl XcbConnection { atoms, resources, cursor_handle, - cursor_cache: HashMap::new(), + cursor_cache: RefCell::new(HashMap::new()), }) } @@ -101,8 +102,12 @@ impl XcbConnection { } #[inline] - pub fn get_cursor(&mut self, cursor: MouseCursor) -> Result> { - match self.cursor_cache.entry(cursor) { + pub fn get_cursor(&self, cursor: MouseCursor) -> Result> { + // PANIC: this function is the only point where we access the cache, and we never call + // external functions that may make a reentrant call to this function + let mut cursor_cache = self.cursor_cache.borrow_mut(); + + match cursor_cache.entry(cursor) { Entry::Occupied(entry) => Ok(*entry.get()), Entry::Vacant(entry) => { let cursor =