Linux: Rewrite the event loop using calloop (#8314)

Roman , Dzmitry Malyshau , Tadeo Kondrak , Mikayla Maki , and julia created

This PR unifies the event loop code for Wayland and X11. On Wayland,
blocking dispatch is now used. On X11, the invisible window is no longer
needed.

Release Notes:

- N/A

---------

Co-authored-by: Dzmitry Malyshau <kvark@fastmail.com>
Co-authored-by: Tadeo Kondrak <me@tadeo.ca>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
Co-authored-by: julia <julia@zed.dev>

Change summary

Cargo.lock                                                  |  28 
crates/gpui/Cargo.toml                                      |   2 
crates/gpui/src/platform/linux.rs                           |   3 
crates/gpui/src/platform/linux/client.rs                    |   1 
crates/gpui/src/platform/linux/client_dispatcher.rs         |   3 
crates/gpui/src/platform/linux/dispatcher.rs                | 111 +
crates/gpui/src/platform/linux/platform.rs                  | 155 -
crates/gpui/src/platform/linux/wayland.rs                   |   2 
crates/gpui/src/platform/linux/wayland/client.rs            | 214 +--
crates/gpui/src/platform/linux/wayland/client_dispatcher.rs |  30 
crates/gpui/src/platform/linux/wayland/window.rs            |  80 
crates/gpui/src/platform/linux/x11.rs                       |   2 
crates/gpui/src/platform/linux/x11/client.rs                | 440 ++++--
crates/gpui/src/platform/linux/x11/client_dispatcher.rs     |  64 -
crates/gpui/src/platform/linux/x11/window.rs                |  78 
15 files changed, 598 insertions(+), 615 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1631,6 +1631,32 @@ dependencies = [
  "util",
 ]
 
+[[package]]
+name = "calloop"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298"
+dependencies = [
+ "bitflags 2.4.1",
+ "log",
+ "polling 3.3.2",
+ "rustix 0.38.30",
+ "slab",
+ "thiserror",
+]
+
+[[package]]
+name = "calloop-wayland-source"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
+dependencies = [
+ "calloop",
+ "rustix 0.38.30",
+ "wayland-backend",
+ "wayland-client",
+]
+
 [[package]]
 name = "castaway"
 version = "0.1.2"
@@ -4002,6 +4028,8 @@ dependencies = [
  "blade-macros",
  "block",
  "bytemuck",
+ "calloop",
+ "calloop-wayland-source",
  "cbindgen",
  "cocoa",
  "collections",

crates/gpui/Cargo.toml 🔗

@@ -103,6 +103,8 @@ wayland-protocols = { version = "0.31.2", features = ["client", "staging", "unst
 wayland-backend = { version = "0.3.3", features = ["client_system"] }
 xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
 as-raw-xcb-connection = "1"
+calloop = "0.12.4"
+calloop-wayland-source = "0.2.0"
 #TODO: use these on all platforms
 blade-graphics.workspace = true
 blade-macros.workspace = true

crates/gpui/src/platform/linux.rs 🔗

@@ -1,5 +1,4 @@
 mod client;
-mod client_dispatcher;
 mod dispatcher;
 mod platform;
 mod text_system;
@@ -10,4 +9,4 @@ mod x11;
 pub(crate) use dispatcher::*;
 pub(crate) use platform::*;
 pub(crate) use text_system::*;
-pub(crate) use x11::*;
+// pub(crate) use x11::*;

crates/gpui/src/platform/linux/client.rs 🔗

@@ -4,7 +4,6 @@ use crate::platform::PlatformWindow;
 use crate::{AnyWindowHandle, DisplayId, PlatformDisplay, WindowOptions};
 
 pub trait Client {
-    fn run(&self, on_finish_launching: Box<dyn FnOnce()>);
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
     fn open_window(

crates/gpui/src/platform/linux/dispatcher.rs 🔗

@@ -4,47 +4,88 @@
 //todo!(linux): remove
 #![allow(unused_variables)]
 
-use crate::platform::linux::client_dispatcher::ClientDispatcher;
 use crate::{PlatformDispatcher, TaskLabel};
 use async_task::Runnable;
+use calloop::{
+    channel::{self, Sender},
+    timer::TimeoutAction,
+    EventLoop,
+};
 use parking::{Parker, Unparker};
 use parking_lot::Mutex;
-use std::{
-    panic,
-    sync::Arc,
-    thread,
-    time::{Duration, Instant},
-};
+use std::{thread, time::Duration};
+use util::ResultExt;
+
+struct TimerAfter {
+    duration: Duration,
+    runnable: Runnable,
+}
 
 pub(crate) struct LinuxDispatcher {
-    client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync>,
     parker: Mutex<Parker>,
-    timed_tasks: Mutex<Vec<(Instant, Runnable)>>,
-    main_sender: flume::Sender<Runnable>,
+    main_sender: Sender<Runnable>,
+    timer_sender: Sender<TimerAfter>,
     background_sender: flume::Sender<Runnable>,
-    _background_thread: thread::JoinHandle<()>,
+    _background_threads: Vec<thread::JoinHandle<()>>,
     main_thread_id: thread::ThreadId,
 }
 
 impl LinuxDispatcher {
-    pub fn new(
-        main_sender: flume::Sender<Runnable>,
-        client_dispatcher: &Arc<dyn ClientDispatcher + Send + Sync>,
-    ) -> Self {
+    pub fn new(main_sender: Sender<Runnable>) -> Self {
         let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
-        let background_thread = thread::spawn(move || {
-            profiling::register_thread!("background");
-            for runnable in background_receiver {
-                let _ignore_panic = panic::catch_unwind(|| runnable.run());
-            }
+        let thread_count = std::thread::available_parallelism()
+            .map(|i| i.get())
+            .unwrap_or(1);
+
+        let mut background_threads = (0..thread_count)
+            .map(|_| {
+                let receiver = background_receiver.clone();
+                std::thread::spawn(move || {
+                    for runnable in receiver {
+                        runnable.run();
+                    }
+                })
+            })
+            .collect::<Vec<_>>();
+
+        let (timer_sender, timer_channel) = calloop::channel::channel::<TimerAfter>();
+        let timer_thread = std::thread::spawn(|| {
+            let mut event_loop: EventLoop<()> =
+                EventLoop::try_new().expect("Failed to initialize timer loop!");
+
+            let handle = event_loop.handle();
+            let timer_handle = event_loop.handle();
+            handle
+                .insert_source(timer_channel, move |e, _, _| {
+                    if let channel::Event::Msg(timer) = e {
+                        // This has to be in an option to satisfy the borrow checker. The callback below should only be scheduled once.
+                        let mut runnable = Some(timer.runnable);
+                        timer_handle
+                            .insert_source(
+                                calloop::timer::Timer::from_duration(timer.duration),
+                                move |e, _, _| {
+                                    if let Some(runnable) = runnable.take() {
+                                        runnable.run();
+                                    }
+                                    TimeoutAction::Drop
+                                },
+                            )
+                            .expect("Failed to start timer");
+                    }
+                })
+                .expect("Failed to start timer thread");
+
+            event_loop.run(None, &mut (), |_| {}).log_err();
         });
+
+        background_threads.push(timer_thread);
+
         Self {
-            client_dispatcher: Arc::clone(client_dispatcher),
             parker: Mutex::new(Parker::new()),
-            timed_tasks: Mutex::new(Vec::new()),
             main_sender,
+            timer_sender,
             background_sender,
-            _background_thread: background_thread,
+            _background_threads: background_threads,
             main_thread_id: thread::current().id(),
         }
     }
@@ -60,29 +101,19 @@ impl PlatformDispatcher for LinuxDispatcher {
     }
 
     fn dispatch_on_main_thread(&self, runnable: Runnable) {
-        self.main_sender.send(runnable).unwrap();
-        self.client_dispatcher.dispatch_on_main_thread();
+        self.main_sender
+            .send(runnable)
+            .expect("Main thread is gone");
     }
 
     fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
-        let moment = Instant::now() + duration;
-        let mut timed_tasks = self.timed_tasks.lock();
-        timed_tasks.push((moment, runnable));
-        timed_tasks.sort_unstable_by(|(a, _), (b, _)| b.cmp(a));
+        self.timer_sender
+            .send(TimerAfter { duration, runnable })
+            .expect("Timer thread has died");
     }
 
     fn tick(&self, background_only: bool) -> bool {
-        let mut timed_tasks = self.timed_tasks.lock();
-        let old_count = timed_tasks.len();
-        while let Some(&(moment, _)) = timed_tasks.last() {
-            if moment <= Instant::now() {
-                let (_, runnable) = timed_tasks.pop().unwrap();
-                runnable.run();
-            } else {
-                break;
-            }
-        }
-        timed_tasks.len() != old_count
+        false
     }
 
     fn park(&self) {

crates/gpui/src/platform/linux/platform.rs 🔗

@@ -1,5 +1,6 @@
 #![allow(unused)]
 
+use std::cell::RefCell;
 use std::env;
 use std::{
     path::{Path, PathBuf},
@@ -10,6 +11,7 @@ use std::{
 
 use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest};
 use async_task::Runnable;
+use calloop::{EventLoop, LoopHandle, LoopSignal};
 use flume::{Receiver, Sender};
 use futures::channel::oneshot;
 use parking_lot::Mutex;
@@ -17,9 +19,7 @@ use time::UtcOffset;
 use wayland_client::Connection;
 
 use crate::platform::linux::client::Client;
-use crate::platform::linux::client_dispatcher::ClientDispatcher;
-use crate::platform::linux::wayland::{WaylandClient, WaylandClientDispatcher};
-use crate::platform::{X11Client, X11ClientDispatcher, XcbAtoms};
+use crate::platform::linux::wayland::WaylandClient;
 use crate::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
     ForegroundExecutor, Keymap, LinuxDispatcher, LinuxTextSystem, Menu, PathPromptOptions,
@@ -27,12 +27,14 @@ use crate::{
     SemanticVersion, Task, WindowOptions,
 };
 
+use super::x11::X11Client;
+
 #[derive(Default)]
 pub(crate) struct Callbacks {
     open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
     become_active: Option<Box<dyn FnMut()>>,
     resign_active: Option<Box<dyn FnMut()>>,
-    pub(crate) quit: Option<Box<dyn FnMut()>>,
+    quit: Option<Box<dyn FnMut()>>,
     reopen: Option<Box<dyn FnMut()>>,
     event: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
     app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
@@ -41,12 +43,13 @@ pub(crate) struct Callbacks {
 }
 
 pub(crate) struct LinuxPlatformInner {
+    pub(crate) event_loop: RefCell<EventLoop<'static, ()>>,
+    pub(crate) loop_handle: Rc<LoopHandle<'static, ()>>,
+    pub(crate) loop_signal: LoopSignal,
     pub(crate) background_executor: BackgroundExecutor,
     pub(crate) foreground_executor: ForegroundExecutor,
-    pub(crate) main_receiver: flume::Receiver<Runnable>,
     pub(crate) text_system: Arc<LinuxTextSystem>,
-    pub(crate) callbacks: Mutex<Callbacks>,
-    pub(crate) state: Mutex<LinuxPlatformState>,
+    pub(crate) callbacks: RefCell<Callbacks>,
 }
 
 pub(crate) struct LinuxPlatform {
@@ -54,10 +57,6 @@ pub(crate) struct LinuxPlatform {
     inner: Rc<LinuxPlatformInner>,
 }
 
-pub(crate) struct LinuxPlatformState {
-    pub(crate) quit_requested: bool,
-}
-
 impl Default for LinuxPlatform {
     fn default() -> Self {
         Self::new()
@@ -69,90 +68,41 @@ impl LinuxPlatform {
         let wayland_display = env::var_os("WAYLAND_DISPLAY");
         let use_wayland = wayland_display.is_some() && !wayland_display.unwrap().is_empty();
 
-        let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
+        let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
         let text_system = Arc::new(LinuxTextSystem::new());
-        let callbacks = Mutex::new(Callbacks::default());
-        let state = Mutex::new(LinuxPlatformState {
-            quit_requested: false,
-        });
+        let callbacks = RefCell::new(Callbacks::default());
 
-        if use_wayland {
-            Self::new_wayland(main_sender, main_receiver, text_system, callbacks, state)
-        } else {
-            Self::new_x11(main_sender, main_receiver, text_system, callbacks, state)
-        }
-    }
+        let event_loop = EventLoop::try_new().unwrap();
+        event_loop
+            .handle()
+            .insert_source(main_receiver, |event, _, _| {
+                if let calloop::channel::Event::Msg(runnable) = event {
+                    runnable.run();
+                }
+            });
 
-    fn new_wayland(
-        main_sender: Sender<Runnable>,
-        main_receiver: Receiver<Runnable>,
-        text_system: Arc<LinuxTextSystem>,
-        callbacks: Mutex<Callbacks>,
-        state: Mutex<LinuxPlatformState>,
-    ) -> Self {
-        let conn = Arc::new(Connection::connect_to_env().unwrap());
-        let client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync> =
-            Arc::new(WaylandClientDispatcher::new(&conn));
-        let dispatcher = Arc::new(LinuxDispatcher::new(main_sender, &client_dispatcher));
-        let inner = Rc::new(LinuxPlatformInner {
-            background_executor: BackgroundExecutor::new(dispatcher.clone()),
-            foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
-            main_receiver,
-            text_system,
-            callbacks,
-            state,
-        });
-        let client = Rc::new(WaylandClient::new(Rc::clone(&inner), Arc::clone(&conn)));
-        Self {
-            client,
-            inner: Rc::clone(&inner),
-        }
-    }
+        let dispatcher = Arc::new(LinuxDispatcher::new(main_sender));
 
-    fn new_x11(
-        main_sender: Sender<Runnable>,
-        main_receiver: Receiver<Runnable>,
-        text_system: Arc<LinuxTextSystem>,
-        callbacks: Mutex<Callbacks>,
-        state: Mutex<LinuxPlatformState>,
-    ) -> Self {
-        let (xcb_connection, x_root_index) = xcb::Connection::connect_with_extensions(
-            None,
-            &[xcb::Extension::Present, xcb::Extension::Xkb],
-            &[],
-        )
-        .unwrap();
-
-        let xkb_ver = xcb_connection
-            .wait_for_reply(xcb_connection.send_request(&xcb::xkb::UseExtension {
-                wanted_major: xcb::xkb::MAJOR_VERSION as u16,
-                wanted_minor: xcb::xkb::MINOR_VERSION as u16,
-            }))
-            .unwrap();
-        assert!(xkb_ver.supported());
-
-        let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
-        let xcb_connection = Arc::new(xcb_connection);
-        let client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync> =
-            Arc::new(X11ClientDispatcher::new(&xcb_connection, x_root_index));
-        let dispatcher = Arc::new(LinuxDispatcher::new(main_sender, &client_dispatcher));
         let inner = Rc::new(LinuxPlatformInner {
+            loop_handle: Rc::new(event_loop.handle()),
+            loop_signal: event_loop.get_signal(),
+            event_loop: RefCell::new(event_loop),
             background_executor: BackgroundExecutor::new(dispatcher.clone()),
             foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
-            main_receiver,
             text_system,
             callbacks,
-            state,
         });
-        let client = Rc::new(X11Client::new(
-            Rc::clone(&inner),
-            xcb_connection,
-            x_root_index,
-            atoms,
-        ));
-        Self {
-            client,
-            inner: Rc::clone(&inner),
+
+        if use_wayland {
+            Self {
+                client: Rc::new(WaylandClient::new(Rc::clone(&inner))),
+                inner,
+            }
+        } else {
+            Self {
+                client: X11Client::new(Rc::clone(&inner)),
+                inner,
+            }
         }
     }
 }
@@ -171,11 +121,24 @@ impl Platform for LinuxPlatform {
     }
 
     fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
-        self.client.run(on_finish_launching)
+        on_finish_launching();
+        self.inner
+            .event_loop
+            .borrow_mut()
+            .run(None, &mut (), |data| {})
+            .expect("Run loop failed");
+
+        let mut lock = self.inner.callbacks.borrow_mut();
+        if let Some(mut fun) = lock.quit.take() {
+            drop(lock);
+            fun();
+            let mut lock = self.inner.callbacks.borrow_mut();
+            lock.quit = Some(fun);
+        }
     }
 
     fn quit(&self) {
-        self.inner.state.lock().quit_requested = true;
+        self.inner.loop_signal.stop();
     }
 
     //todo!(linux)
@@ -219,7 +182,7 @@ impl Platform for LinuxPlatform {
     }
 
     fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
-        self.inner.callbacks.lock().open_urls = Some(callback);
+        self.inner.callbacks.borrow_mut().open_urls = Some(callback);
     }
 
     fn prompt_for_paths(
@@ -306,35 +269,35 @@ impl Platform for LinuxPlatform {
     }
 
     fn on_become_active(&self, callback: Box<dyn FnMut()>) {
-        self.inner.callbacks.lock().become_active = Some(callback);
+        self.inner.callbacks.borrow_mut().become_active = Some(callback);
     }
 
     fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
-        self.inner.callbacks.lock().resign_active = Some(callback);
+        self.inner.callbacks.borrow_mut().resign_active = Some(callback);
     }
 
     fn on_quit(&self, callback: Box<dyn FnMut()>) {
-        self.inner.callbacks.lock().quit = Some(callback);
+        self.inner.callbacks.borrow_mut().quit = Some(callback);
     }
 
     fn on_reopen(&self, callback: Box<dyn FnMut()>) {
-        self.inner.callbacks.lock().reopen = Some(callback);
+        self.inner.callbacks.borrow_mut().reopen = Some(callback);
     }
 
     fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
-        self.inner.callbacks.lock().event = Some(callback);
+        self.inner.callbacks.borrow_mut().event = Some(callback);
     }
 
     fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
-        self.inner.callbacks.lock().app_menu_action = Some(callback);
+        self.inner.callbacks.borrow_mut().app_menu_action = Some(callback);
     }
 
     fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
-        self.inner.callbacks.lock().will_open_app_menu = Some(callback);
+        self.inner.callbacks.borrow_mut().will_open_app_menu = Some(callback);
     }
 
     fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
-        self.inner.callbacks.lock().validate_app_menu_command = Some(callback);
+        self.inner.callbacks.borrow_mut().validate_app_menu_command = Some(callback);
     }
 
     fn os_name(&self) -> &'static str {

crates/gpui/src/platform/linux/wayland/client.rs 🔗

@@ -3,10 +3,12 @@ use std::rc::Rc;
 use std::sync::Arc;
 use std::time::Duration;
 
-use parking_lot::Mutex;
-use smol::Timer;
+use calloop::timer::{TimeoutAction, Timer};
+use calloop::LoopHandle;
+use calloop_wayland_source::WaylandSource;
 use wayland_backend::client::ObjectId;
 use wayland_backend::protocol::WEnum;
+use wayland_client::globals::{registry_queue_init, GlobalListContents};
 use wayland_client::protocol::wl_callback::WlCallback;
 use wayland_client::protocol::wl_pointer::AxisRelativeDirection;
 use wayland_client::{
@@ -16,7 +18,7 @@ use wayland_client::{
         wl_shm, wl_shm_pool,
         wl_surface::{self, WlSurface},
     },
-    Connection, Dispatch, EventQueue, Proxy, QueueHandle,
+    Connection, Dispatch, Proxy, QueueHandle,
 };
 use wayland_protocols::wp::fractional_scale::v1::client::{
     wp_fractional_scale_manager_v1, wp_fractional_scale_v1,
@@ -39,18 +41,17 @@ use crate::{
     ScrollWheelEvent, TouchPhase, WindowOptions,
 };
 
-const MIN_KEYCODE: u32 = 8; // used to convert evdev scancode to xkb scancode
+/// Used to convert evdev scancode to xkb scancode
+const MIN_KEYCODE: u32 = 8;
 
 pub(crate) struct WaylandClientStateInner {
-    compositor: Option<wl_compositor::WlCompositor>,
-    buffer: Option<wl_buffer::WlBuffer>,
-    wm_base: Option<xdg_wm_base::XdgWmBase>,
+    compositor: wl_compositor::WlCompositor,
+    wm_base: xdg_wm_base::XdgWmBase,
     viewporter: Option<wp_viewporter::WpViewporter>,
     fractional_scale_manager: Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
     decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
     windows: Vec<(xdg_surface::XdgSurface, Rc<WaylandWindowState>)>,
     platform_inner: Rc<LinuxPlatformInner>,
-    wl_seat: Option<wl_seat::WlSeat>,
     keymap_state: Option<xkb::State>,
     repeat: KeyRepeat,
     modifiers: Modifiers,
@@ -59,6 +60,7 @@ pub(crate) struct WaylandClientStateInner {
     button_pressed: Option<MouseButton>,
     mouse_focused_window: Option<Rc<WaylandWindowState>>,
     keyboard_focused_window: Option<Rc<WaylandWindowState>>,
+    loop_handle: Rc<LoopHandle<'static, ()>>,
 }
 
 #[derive(Clone)]
@@ -73,24 +75,40 @@ pub(crate) struct KeyRepeat {
 
 pub(crate) struct WaylandClient {
     platform_inner: Rc<LinuxPlatformInner>,
-    conn: Arc<Connection>,
     state: WaylandClientState,
-    event_queue: Mutex<EventQueue<WaylandClientState>>,
     qh: Arc<QueueHandle<WaylandClientState>>,
 }
 
+const WL_SEAT_VERSION: u32 = 4;
+
 impl WaylandClient {
-    pub(crate) fn new(linux_platform_inner: Rc<LinuxPlatformInner>, conn: Arc<Connection>) -> Self {
-        let state = WaylandClientState(Rc::new(RefCell::new(WaylandClientStateInner {
-            compositor: None,
-            buffer: None,
-            wm_base: None,
-            viewporter: None,
-            fractional_scale_manager: None,
-            decoration_manager: None,
+    pub(crate) fn new(linux_platform_inner: Rc<LinuxPlatformInner>) -> Self {
+        let conn = Connection::connect_to_env().unwrap();
+
+        let (globals, mut event_queue) = registry_queue_init::<WaylandClientState>(&conn).unwrap();
+        let qh = event_queue.handle();
+
+        globals.contents().with_list(|list| {
+            for global in list {
+                if global.interface == "wl_seat" {
+                    globals.registry().bind::<wl_seat::WlSeat, _, _>(
+                        global.name,
+                        WL_SEAT_VERSION,
+                        &qh,
+                        (),
+                    );
+                }
+            }
+        });
+
+        let mut state_inner = Rc::new(RefCell::new(WaylandClientStateInner {
+            compositor: globals.bind(&qh, 1..=1, ()).unwrap(),
+            wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
+            viewporter: globals.bind(&qh, 1..=1, ()).ok(),
+            fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
+            decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
             windows: Vec::new(),
             platform_inner: Rc::clone(&linux_platform_inner),
-            wl_seat: None,
             keymap_state: None,
             repeat: KeyRepeat {
                 characters_per_second: 16,
@@ -110,41 +128,28 @@ impl WaylandClient {
             button_pressed: None,
             mouse_focused_window: None,
             keyboard_focused_window: None,
-        })));
-        let event_queue: EventQueue<WaylandClientState> = conn.new_event_queue();
-        let qh = event_queue.handle();
+            loop_handle: Rc::clone(&linux_platform_inner.loop_handle),
+        }));
+
+        let source = WaylandSource::new(conn, event_queue);
+
+        let mut state = WaylandClientState(Rc::clone(&state_inner));
+        linux_platform_inner
+            .loop_handle
+            .insert_source(source, move |_, queue, _| {
+                queue.dispatch_pending(&mut state)
+            })
+            .unwrap();
+
         Self {
             platform_inner: linux_platform_inner,
-            conn,
-            state,
-            event_queue: Mutex::new(event_queue),
+            state: WaylandClientState(state_inner),
             qh: Arc::new(qh),
         }
     }
 }
 
 impl Client for WaylandClient {
-    fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
-        let display = self.conn.display();
-        let mut eq = self.event_queue.lock();
-        let _registry = display.get_registry(&self.qh, ());
-
-        eq.roundtrip(&mut self.state.clone()).unwrap();
-
-        on_finish_launching();
-        while !self.platform_inner.state.lock().quit_requested {
-            eq.flush().unwrap();
-            eq.dispatch_pending(&mut self.state.clone()).unwrap();
-            if let Some(guard) = self.conn.prepare_read() {
-                guard.read().unwrap();
-                eq.dispatch_pending(&mut self.state.clone()).unwrap();
-            }
-            if let Ok(runnable) = self.platform_inner.main_receiver.try_recv() {
-                runnable.run();
-            }
-        }
-    }
-
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
         Vec::new()
     }
@@ -160,10 +165,8 @@ impl Client for WaylandClient {
     ) -> Box<dyn PlatformWindow> {
         let mut state = self.state.0.borrow_mut();
 
-        let wm_base = state.wm_base.as_ref().unwrap();
-        let compositor = state.compositor.as_ref().unwrap();
-        let wl_surface = compositor.create_surface(&self.qh, ());
-        let xdg_surface = wm_base.get_xdg_surface(&wl_surface, &self.qh, ());
+        let wl_surface = state.compositor.create_surface(&self.qh, ());
+        let xdg_surface = state.wm_base.get_xdg_surface(&wl_surface, &self.qh, ());
         let toplevel = xdg_surface.get_toplevel(&self.qh, ());
         let wl_surface = Arc::new(wl_surface);
 
@@ -200,8 +203,7 @@ impl Client for WaylandClient {
         wl_surface.frame(&self.qh, wl_surface.clone());
         wl_surface.commit();
 
-        let window_state = Rc::new(WaylandWindowState::new(
-            &self.conn,
+        let window_state: Rc<WaylandWindowState> = Rc::new(WaylandWindowState::new(
             wl_surface.clone(),
             viewport,
             Arc::new(toplevel),
@@ -217,62 +219,27 @@ impl Client for WaylandClient {
     }
 }
 
-impl Dispatch<wl_registry::WlRegistry, ()> for WaylandClientState {
+impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientState {
     fn event(
         state: &mut Self,
         registry: &wl_registry::WlRegistry,
         event: wl_registry::Event,
-        _: &(),
+        _: &GlobalListContents,
         _: &Connection,
         qh: &QueueHandle<Self>,
     ) {
-        let mut state = state.0.borrow_mut();
-        if let wl_registry::Event::Global {
-            name, interface, ..
-        } = event
-        {
-            match &interface[..] {
-                "wl_compositor" => {
-                    let compositor =
-                        registry.bind::<wl_compositor::WlCompositor, _, _>(name, 1, qh, ());
-                    state.compositor = Some(compositor);
-                }
-                "xdg_wm_base" => {
-                    let wm_base = registry.bind::<xdg_wm_base::XdgWmBase, _, _>(name, 1, qh, ());
-                    state.wm_base = Some(wm_base);
-                }
-                "wl_seat" => {
-                    let seat = registry.bind::<wl_seat::WlSeat, _, _>(name, 4, qh, ());
-                    state.wl_seat = Some(seat);
-                }
-                "wp_fractional_scale_manager_v1" => {
-                    let manager = registry
-                        .bind::<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, _, _>(
-                        name,
-                        1,
-                        qh,
-                        (),
-                    );
-                    state.fractional_scale_manager = Some(manager);
-                }
-                "wp_viewporter" => {
-                    let view_porter =
-                        registry.bind::<wp_viewporter::WpViewporter, _, _>(name, 1, qh, ());
-                    state.viewporter = Some(view_porter);
-                }
-                "zxdg_decoration_manager_v1" => {
-                    // Unstable and optional
-                    let decoration_manager = registry
-                        .bind::<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, _, _>(
-                        name,
-                        1,
-                        qh,
-                        (),
-                    );
-                    state.decoration_manager = Some(decoration_manager);
+        match event {
+            wl_registry::Event::Global {
+                name,
+                interface,
+                version: _,
+            } => {
+                if interface.as_str() == "wl_seat" {
+                    registry.bind::<wl_seat::WlSeat, _, _>(name, 4, qh, ());
                 }
-                _ => {}
-            };
+            }
+            wl_registry::Event::GlobalRemove { name: _ } => {}
+            _ => {}
         }
     }
 }
@@ -367,7 +334,7 @@ impl Dispatch<xdg_toplevel::XdgToplevel, ()> for WaylandClientState {
                     true
                 }
             });
-            state.platform_inner.state.lock().quit_requested |= state.windows.is_empty();
+            state.platform_inner.loop_signal.stop();
         }
     }
 }
@@ -529,34 +496,29 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
                             let id = state.repeat.current_id;
                             let this = this.clone();
 
+                            let timer = Timer::from_duration(delay);
+                            let state_ = Rc::clone(&this.0);
                             state
-                                .platform_inner
-                                .foreground_executor
-                                .spawn(async move {
-                                    let mut wait_time = delay;
-
-                                    loop {
-                                        Timer::after(wait_time).await;
-
-                                        let state = this.0.borrow_mut();
-                                        let is_repeating = id == state.repeat.current_id
-                                            && state.repeat.current_keysym.is_some()
-                                            && state.keyboard_focused_window.is_some();
-
-                                        if !is_repeating {
-                                            return;
-                                        }
-
-                                        state
-                                            .keyboard_focused_window
-                                            .as_ref()
-                                            .unwrap()
-                                            .handle_input(input.clone());
-
-                                        wait_time = Duration::from_secs(1) / rate;
+                                .loop_handle
+                                .insert_source(timer, move |event, _metadata, shared_data| {
+                                    let state_ = state_.borrow_mut();
+                                    let is_repeating = id == state_.repeat.current_id
+                                        && state_.repeat.current_keysym.is_some()
+                                        && state_.keyboard_focused_window.is_some();
+
+                                    if !is_repeating {
+                                        return TimeoutAction::Drop;
                                     }
+
+                                    state_
+                                        .keyboard_focused_window
+                                        .as_ref()
+                                        .unwrap()
+                                        .handle_input(input.clone());
+
+                                    TimeoutAction::ToDuration(Duration::from_secs(1) / rate)
                                 })
-                                .detach();
+                                .unwrap();
                         }
                     }
                     wl_keyboard::KeyState::Released => {

crates/gpui/src/platform/linux/wayland/client_dispatcher.rs 🔗

@@ -1,30 +0,0 @@
-use std::sync::Arc;
-
-use wayland_client::{Connection, EventQueue};
-
-use crate::platform::linux::client_dispatcher::ClientDispatcher;
-
-pub(crate) struct WaylandClientDispatcher {
-    conn: Arc<Connection>,
-    event_queue: Arc<EventQueue<Connection>>,
-}
-
-impl WaylandClientDispatcher {
-    pub(crate) fn new(conn: &Arc<Connection>) -> Self {
-        let event_queue = conn.new_event_queue();
-        Self {
-            conn: Arc::clone(conn),
-            event_queue: Arc::new(event_queue),
-        }
-    }
-}
-
-impl Drop for WaylandClientDispatcher {
-    fn drop(&mut self) {
-        //todo!(linux)
-    }
-}
-
-impl ClientDispatcher for WaylandClientDispatcher {
-    fn dispatch_on_main_thread(&self) {}
-}

crates/gpui/src/platform/linux/wayland/window.rs 🔗

@@ -1,4 +1,5 @@
 use std::any::Any;
+use std::cell::RefCell;
 use std::ffi::c_void;
 use std::rc::Rc;
 use std::sync::Arc;
@@ -6,7 +7,6 @@ use std::sync::Arc;
 use blade_graphics as gpu;
 use blade_rwh::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle};
 use futures::channel::oneshot::Receiver;
-use parking_lot::Mutex;
 use raw_window_handle::{
     DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
 };
@@ -66,14 +66,15 @@ unsafe impl HasRawDisplayHandle for RawWindow {
 }
 
 impl WaylandWindowInner {
-    fn new(
-        conn: &Arc<wayland_client::Connection>,
-        wl_surf: &Arc<wl_surface::WlSurface>,
-        bounds: Bounds<i32>,
-    ) -> Self {
+    fn new(wl_surf: &Arc<wl_surface::WlSurface>, bounds: Bounds<i32>) -> Self {
         let raw = RawWindow {
             window: wl_surf.id().as_ptr().cast::<c_void>(),
-            display: conn.backend().display_ptr().cast::<c_void>(),
+            display: wl_surf
+                .backend()
+                .upgrade()
+                .unwrap()
+                .display_ptr()
+                .cast::<c_void>(),
         };
         let gpu = Arc::new(
             unsafe {
@@ -105,9 +106,8 @@ impl WaylandWindowInner {
 }
 
 pub(crate) struct WaylandWindowState {
-    conn: Arc<wayland_client::Connection>,
-    inner: Mutex<WaylandWindowInner>,
-    pub(crate) callbacks: Mutex<Callbacks>,
+    inner: RefCell<WaylandWindowInner>,
+    pub(crate) callbacks: RefCell<Callbacks>,
     pub(crate) surface: Arc<wl_surface::WlSurface>,
     pub(crate) toplevel: Arc<xdg_toplevel::XdgToplevel>,
     viewport: Option<wp_viewport::WpViewport>,
@@ -115,7 +115,6 @@ pub(crate) struct WaylandWindowState {
 
 impl WaylandWindowState {
     pub(crate) fn new(
-        conn: &Arc<wayland_client::Connection>,
         wl_surf: Arc<wl_surface::WlSurface>,
         viewport: Option<wp_viewport::WpViewport>,
         toplevel: Arc<xdg_toplevel::XdgToplevel>,
@@ -139,34 +138,33 @@ impl WaylandWindowState {
         };
 
         Self {
-            conn: Arc::clone(conn),
             surface: Arc::clone(&wl_surf),
-            inner: Mutex::new(WaylandWindowInner::new(&Arc::clone(conn), &wl_surf, bounds)),
-            callbacks: Mutex::new(Callbacks::default()),
+            inner: RefCell::new(WaylandWindowInner::new(&wl_surf, bounds)),
+            callbacks: RefCell::new(Callbacks::default()),
             toplevel,
             viewport,
         }
     }
 
     pub fn update(&self) {
-        let mut cb = self.callbacks.lock();
+        let mut cb = self.callbacks.borrow_mut();
         if let Some(mut fun) = cb.request_frame.take() {
             drop(cb);
             fun();
-            self.callbacks.lock().request_frame = Some(fun);
+            self.callbacks.borrow_mut().request_frame = Some(fun);
         }
     }
 
     pub fn set_size_and_scale(&self, width: i32, height: i32, scale: f32) {
-        self.inner.lock().scale = scale;
-        self.inner.lock().bounds.size.width = width;
-        self.inner.lock().bounds.size.height = height;
-        self.inner.lock().renderer.update_drawable_size(size(
+        self.inner.borrow_mut().scale = scale;
+        self.inner.borrow_mut().bounds.size.width = width;
+        self.inner.borrow_mut().bounds.size.height = height;
+        self.inner.borrow_mut().renderer.update_drawable_size(size(
             width as f64 * scale as f64,
             height as f64 * scale as f64,
         ));
 
-        if let Some(ref mut fun) = self.callbacks.lock().resize {
+        if let Some(ref mut fun) = self.callbacks.borrow_mut().resize {
             fun(
                 Size {
                     width: px(width as f32),
@@ -182,12 +180,12 @@ impl WaylandWindowState {
     }
 
     pub fn resize(&self, width: i32, height: i32) {
-        let scale = self.inner.lock().scale;
+        let scale = self.inner.borrow_mut().scale;
         self.set_size_and_scale(width, height, scale);
     }
 
     pub fn rescale(&self, scale: f32) {
-        let bounds = self.inner.lock().bounds;
+        let bounds = self.inner.borrow_mut().bounds;
         self.set_size_and_scale(bounds.size.width, bounds.size.height, scale)
     }
 
@@ -200,13 +198,13 @@ impl WaylandWindowState {
     /// of the decorations. This is because the state of the decorations
     /// is managed by the compositor and not the client.
     pub fn set_decoration_state(&self, state: WaylandDecorationState) {
-        self.inner.lock().decoration_state = state;
+        self.inner.borrow_mut().decoration_state = state;
         log::trace!("Window decorations are now handled by {:?}", state);
         // todo!(linux) - Handle this properly
     }
 
     pub fn close(&self) {
-        let mut callbacks = self.callbacks.lock();
+        let mut callbacks = self.callbacks.borrow_mut();
         if let Some(fun) = callbacks.close.take() {
             fun()
         }
@@ -214,13 +212,13 @@ impl WaylandWindowState {
     }
 
     pub fn handle_input(&self, input: PlatformInput) {
-        if let Some(ref mut fun) = self.callbacks.lock().input {
+        if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
             if fun(input.clone()) {
                 return;
             }
         }
         if let PlatformInput::KeyDown(event) = input {
-            let mut inner = self.inner.lock();
+            let mut inner = self.inner.borrow_mut();
             if let Some(ref mut input_handler) = inner.input_handler {
                 if let Some(ime_key) = &event.keystroke.ime_key {
                     input_handler.replace_text_in_range(None, ime_key);
@@ -230,7 +228,7 @@ impl WaylandWindowState {
     }
 
     pub fn set_focused(&self, focus: bool) {
-        if let Some(ref mut fun) = self.callbacks.lock().active_status_change {
+        if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
             fun(focus);
         }
     }
@@ -258,7 +256,7 @@ impl PlatformWindow for WaylandWindow {
     }
 
     fn content_size(&self) -> Size<Pixels> {
-        let inner = self.0.inner.lock();
+        let inner = self.0.inner.borrow_mut();
         Size {
             width: Pixels(inner.bounds.size.width as f32),
             height: Pixels(inner.bounds.size.height as f32),
@@ -266,7 +264,7 @@ impl PlatformWindow for WaylandWindow {
     }
 
     fn scale_factor(&self) -> f32 {
-        self.0.inner.lock().scale
+        self.0.inner.borrow_mut().scale
     }
 
     //todo!(linux)
@@ -300,11 +298,11 @@ impl PlatformWindow for WaylandWindow {
     }
 
     fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
-        self.0.inner.lock().input_handler = Some(input_handler);
+        self.0.inner.borrow_mut().input_handler = Some(input_handler);
     }
 
     fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
-        self.0.inner.lock().input_handler.take()
+        self.0.inner.borrow_mut().input_handler.take()
     }
 
     //todo!(linux)
@@ -347,19 +345,19 @@ impl PlatformWindow for WaylandWindow {
     }
 
     fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
-        self.0.callbacks.lock().request_frame = Some(callback);
+        self.0.callbacks.borrow_mut().request_frame = Some(callback);
     }
 
     fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
-        self.0.callbacks.lock().input = Some(callback);
+        self.0.callbacks.borrow_mut().input = Some(callback);
     }
 
     fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
-        self.0.callbacks.lock().active_status_change = Some(callback);
+        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
     }
 
     fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
-        self.0.callbacks.lock().resize = Some(callback);
+        self.0.callbacks.borrow_mut().resize = Some(callback);
     }
 
     fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
@@ -367,15 +365,15 @@ impl PlatformWindow for WaylandWindow {
     }
 
     fn on_moved(&self, callback: Box<dyn FnMut()>) {
-        self.0.callbacks.lock().moved = Some(callback);
+        self.0.callbacks.borrow_mut().moved = Some(callback);
     }
 
     fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
-        self.0.callbacks.lock().should_close = Some(callback);
+        self.0.callbacks.borrow_mut().should_close = Some(callback);
     }
 
     fn on_close(&self, callback: Box<dyn FnOnce()>) {
-        self.0.callbacks.lock().close = Some(callback);
+        self.0.callbacks.borrow_mut().close = Some(callback);
     }
 
     fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
@@ -388,12 +386,12 @@ impl PlatformWindow for WaylandWindow {
     }
 
     fn draw(&self, scene: &Scene) {
-        let mut inner = self.0.inner.lock();
+        let mut inner = self.0.inner.borrow_mut();
         inner.renderer.draw(scene);
     }
 
     fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
-        let inner = self.0.inner.lock();
+        let inner = self.0.inner.borrow_mut();
         inner.renderer.sprite_atlas().clone()
     }
 

crates/gpui/src/platform/linux/x11.rs 🔗

@@ -1,11 +1,9 @@
 mod client;
-mod client_dispatcher;
 mod display;
 mod event;
 mod window;
 
 pub(crate) use client::*;
-pub(crate) use client_dispatcher::*;
 pub(crate) use display::*;
 pub(crate) use event::*;
 pub(crate) use window::*;

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -1,40 +1,60 @@
-use std::{rc::Rc, sync::Arc};
+use std::cell::{Cell, RefCell};
+use std::rc::Rc;
+use std::time::Duration;
 
-use parking_lot::Mutex;
 use xcb::{x, Xid as _};
 use xkbcommon::xkb;
 
 use collections::{HashMap, HashSet};
 
 use crate::platform::linux::client::Client;
-use crate::platform::{
-    LinuxPlatformInner, PlatformWindow, X11Display, X11Window, X11WindowState, XcbAtoms,
-};
+use crate::platform::{LinuxPlatformInner, PlatformWindow};
 use crate::{
     AnyWindowHandle, Bounds, DisplayId, PlatformDisplay, PlatformInput, Point, ScrollDelta, Size,
     TouchPhase, WindowOptions,
 };
 
+use super::{X11Display, X11Window, X11WindowState, XcbAtoms};
+use calloop::generic::{FdWrapper, Generic};
+
 pub(crate) struct X11ClientState {
     pub(crate) windows: HashMap<x::Window, Rc<X11WindowState>>,
+    pub(crate) windows_to_refresh: HashSet<x::Window>,
     xkb: xkbcommon::xkb::State,
 }
 
 pub(crate) struct X11Client {
     platform_inner: Rc<LinuxPlatformInner>,
-    xcb_connection: Arc<xcb::Connection>,
+    xcb_connection: Rc<xcb::Connection>,
     x_root_index: i32,
     atoms: XcbAtoms,
-    state: Mutex<X11ClientState>,
+    refresh_millis: Cell<u64>,
+    state: RefCell<X11ClientState>,
 }
 
 impl X11Client {
-    pub(crate) fn new(
-        inner: Rc<LinuxPlatformInner>,
-        xcb_connection: Arc<xcb::Connection>,
-        x_root_index: i32,
-        atoms: XcbAtoms,
-    ) -> Self {
+    pub(crate) fn new(inner: Rc<LinuxPlatformInner>) -> Rc<Self> {
+        let (xcb_connection, x_root_index) = xcb::Connection::connect_with_extensions(
+            None,
+            &[
+                xcb::Extension::Present,
+                xcb::Extension::Xkb,
+                xcb::Extension::RandR,
+            ],
+            &[],
+        )
+        .unwrap();
+
+        let xkb_ver = xcb_connection
+            .wait_for_reply(xcb_connection.send_request(&xcb::xkb::UseExtension {
+                wanted_major: xcb::xkb::MAJOR_VERSION as u16,
+                wanted_minor: xcb::xkb::MINOR_VERSION as u16,
+            }))
+            .unwrap();
+        assert!(xkb_ver.supported());
+
+        let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
+        let xcb_connection = Rc::new(xcb_connection);
         let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
         let xkb_device_id = xkb::x11::get_core_keyboard_device_id(&xcb_connection);
         let xkb_keymap = xkb::x11::keymap_new_from_device(
@@ -43,195 +63,235 @@ impl X11Client {
             xkb_device_id,
             xkb::KEYMAP_COMPILE_NO_FLAGS,
         );
+
         let xkb_state =
             xkb::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id);
 
-        Self {
-            platform_inner: inner,
-            xcb_connection,
+        let client: Rc<X11Client> = Rc::new(Self {
+            platform_inner: inner.clone(),
+            xcb_connection: xcb_connection.clone(),
             x_root_index,
             atoms,
-            state: Mutex::new(X11ClientState {
+            refresh_millis: Cell::new(16),
+            state: RefCell::new(X11ClientState {
                 windows: HashMap::default(),
+                windows_to_refresh: HashSet::default(),
                 xkb: xkb_state,
             }),
-        }
+        });
+
+        // Safety: Safe if xcb::Connection always returns a valid fd
+        let fd = unsafe { FdWrapper::new(xcb_connection.clone()) };
+
+        inner
+            .loop_handle
+            .insert_source(
+                Generic::new_with_error::<xcb::Error>(
+                    fd,
+                    calloop::Interest::READ,
+                    calloop::Mode::Level,
+                ),
+                {
+                    let client = client.clone();
+                    move |readiness, _, _| {
+                        if readiness.readable || readiness.error {
+                            while let Some(event) = xcb_connection.poll_for_event()? {
+                                client.handle_event(event);
+                            }
+                        }
+                        Ok(calloop::PostAction::Continue)
+                    }
+                },
+            )
+            .expect("Failed to initialize x11 event source");
+
+        inner
+            .loop_handle
+            .insert_source(
+                calloop::timer::Timer::from_duration(Duration::from_millis(
+                    client.refresh_millis.get(),
+                )),
+                {
+                    let client = client.clone();
+                    move |_, _, _| {
+                        client.present();
+                        calloop::timer::TimeoutAction::ToDuration(Duration::from_millis(
+                            client.refresh_millis.get(),
+                        ))
+                    }
+                },
+            )
+            .expect("Failed to initialize refresh timer");
+
+        client
     }
 
-    fn get_window(&self, win: x::Window) -> Rc<X11WindowState> {
-        let state = self.state.lock();
-        Rc::clone(&state.windows[&win])
+    fn get_window(&self, win: x::Window) -> Option<Rc<X11WindowState>> {
+        let state = self.state.borrow();
+        state.windows.get(&win).cloned()
     }
-}
 
-impl Client for X11Client {
-    fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
-        on_finish_launching();
-        let mut windows_to_refresh = HashSet::<x::Window>::default();
-        while !self.platform_inner.state.lock().quit_requested {
-            // We prioritize work in the following order:
-            //   1. input events from X11
-            //   2. runnables for the main thread
-            //   3. drawing/presentation
-            let event = if let Some(event) = self.xcb_connection.poll_for_event().unwrap() {
-                event
-            } else if let Ok(runnable) = self.platform_inner.main_receiver.try_recv() {
-                runnable.run();
-                continue;
-            } else if let Some(x_window) = windows_to_refresh.iter().next().cloned() {
-                windows_to_refresh.remove(&x_window);
-                let window = self.get_window(x_window);
-                window.refresh();
-                window.request_refresh();
-                continue;
-            } else {
-                profiling::scope!("Wait for event");
-                self.xcb_connection.wait_for_event().unwrap()
-            };
-
-            match event {
-                xcb::Event::X(x::Event::ClientMessage(ev)) => {
-                    if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
-                        if atom == self.atoms.wm_del_window.resource_id() {
-                            windows_to_refresh.remove(&ev.window());
-                            // window "x" button clicked by user, we gracefully exit
-                            let window = self.state.lock().windows.remove(&ev.window()).unwrap();
-                            window.destroy();
-                            let state = self.state.lock();
-                            self.platform_inner.state.lock().quit_requested |=
-                                state.windows.is_empty();
+    fn present(&self) {
+        let state = self.state.borrow_mut();
+        for window_state in state.windows.values() {
+            window_state.refresh();
+        }
+    }
+
+    fn handle_event(&self, event: xcb::Event) -> Option<()> {
+        match event {
+            xcb::Event::X(x::Event::ClientMessage(event)) => {
+                if let x::ClientMessageData::Data32([atom, ..]) = event.data() {
+                    if atom == self.atoms.wm_del_window.resource_id() {
+                        self.state
+                            .borrow_mut()
+                            .windows_to_refresh
+                            .remove(&event.window());
+                        // window "x" button clicked by user, we gracefully exit
+                        let window = self
+                            .state
+                            .borrow_mut()
+                            .windows
+                            .remove(&event.window())
+                            .unwrap();
+                        window.destroy();
+                        let state = self.state.borrow();
+                        if state.windows.is_empty() {
+                            self.platform_inner.loop_signal.stop();
                         }
                     }
                 }
-                xcb::Event::X(x::Event::Expose(ev)) => {
-                    windows_to_refresh.insert(ev.window());
-                }
-                xcb::Event::X(x::Event::ConfigureNotify(ev)) => {
-                    let bounds = Bounds {
-                        origin: Point {
-                            x: ev.x().into(),
-                            y: ev.y().into(),
-                        },
-                        size: Size {
-                            width: ev.width().into(),
-                            height: ev.height().into(),
-                        },
-                    };
-                    self.get_window(ev.window()).configure(bounds)
-                }
-                xcb::Event::Present(xcb::present::Event::CompleteNotify(ev)) => {
-                    windows_to_refresh.insert(ev.window());
-                }
-                xcb::Event::Present(xcb::present::Event::IdleNotify(_ev)) => {}
-                xcb::Event::X(x::Event::FocusIn(ev)) => {
-                    let window = self.get_window(ev.event());
-                    window.set_focused(true);
-                }
-                xcb::Event::X(x::Event::FocusOut(ev)) => {
-                    let window = self.get_window(ev.event());
-                    window.set_focused(false);
-                }
-                xcb::Event::X(x::Event::KeyPress(ev)) => {
-                    let window = self.get_window(ev.event());
-                    let modifiers = super::modifiers_from_state(ev.state());
-                    let keystroke = {
-                        let code = ev.detail().into();
-                        let mut state = self.state.lock();
-                        let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
-                        state.xkb.update_key(code, xkb::KeyDirection::Down);
-                        keystroke
-                    };
-
-                    window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
-                        keystroke,
-                        is_held: false,
+            }
+            xcb::Event::X(x::Event::ConfigureNotify(event)) => {
+                let bounds = Bounds {
+                    origin: Point {
+                        x: event.x().into(),
+                        y: event.y().into(),
+                    },
+                    size: Size {
+                        width: event.width().into(),
+                        height: event.height().into(),
+                    },
+                };
+                let window = self.get_window(event.window())?;
+                window.configure(bounds);
+            }
+            xcb::Event::X(x::Event::FocusIn(event)) => {
+                let window = self.get_window(event.event())?;
+                window.set_focused(true);
+            }
+            xcb::Event::X(x::Event::FocusOut(event)) => {
+                let window = self.get_window(event.event())?;
+                window.set_focused(false);
+            }
+            xcb::Event::X(x::Event::KeyPress(event)) => {
+                let window = self.get_window(event.event())?;
+                let modifiers = super::modifiers_from_state(event.state());
+                let keystroke = {
+                    let code = event.detail().into();
+                    let mut state = self.state.borrow_mut();
+                    let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
+                    state.xkb.update_key(code, xkb::KeyDirection::Down);
+                    keystroke
+                };
+
+                window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
+                    keystroke,
+                    is_held: false,
+                }));
+            }
+            xcb::Event::X(x::Event::KeyRelease(event)) => {
+                let window = self.get_window(event.event())?;
+                let modifiers = super::modifiers_from_state(event.state());
+                let keystroke = {
+                    let code = event.detail().into();
+                    let mut state = self.state.borrow_mut();
+                    let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
+                    state.xkb.update_key(code, xkb::KeyDirection::Up);
+                    keystroke
+                };
+
+                window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
+            }
+            xcb::Event::X(x::Event::ButtonPress(event)) => {
+                let window = self.get_window(event.event())?;
+                let modifiers = super::modifiers_from_state(event.state());
+                let position = Point::new(
+                    (event.event_x() as f32).into(),
+                    (event.event_y() as f32).into(),
+                );
+                if let Some(button) = super::button_of_key(event.detail()) {
+                    window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
+                        button,
+                        position,
+                        modifiers,
+                        click_count: 1,
                     }));
-                }
-                xcb::Event::X(x::Event::KeyRelease(ev)) => {
-                    let window = self.get_window(ev.event());
-                    let modifiers = super::modifiers_from_state(ev.state());
-                    let keystroke = {
-                        let code = ev.detail().into();
-                        let mut state = self.state.lock();
-                        let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
-                        state.xkb.update_key(code, xkb::KeyDirection::Up);
-                        keystroke
-                    };
-
-                    window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
-                }
-                xcb::Event::X(x::Event::ButtonPress(ev)) => {
-                    let window = self.get_window(ev.event());
-                    let modifiers = super::modifiers_from_state(ev.state());
-                    let position =
-                        Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
-                    if let Some(button) = super::button_of_key(ev.detail()) {
-                        window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
-                            button,
-                            position,
-                            modifiers,
-                            click_count: 1,
-                        }));
-                    } else if ev.detail() >= 4 && ev.detail() <= 5 {
-                        // https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
-                        let delta_x = if ev.detail() == 4 { 1.0 } else { -1.0 };
-                        window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
-                            position,
-                            delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
-                            modifiers,
-                            touch_phase: TouchPhase::default(),
-                        }));
-                    } else {
-                        log::warn!("Unknown button press: {ev:?}");
-                    }
-                }
-                xcb::Event::X(x::Event::ButtonRelease(ev)) => {
-                    let window = self.get_window(ev.event());
-                    let modifiers = super::modifiers_from_state(ev.state());
-                    let position =
-                        Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
-                    if let Some(button) = super::button_of_key(ev.detail()) {
-                        window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
-                            button,
-                            position,
-                            modifiers,
-                            click_count: 1,
-                        }));
-                    }
-                }
-                xcb::Event::X(x::Event::MotionNotify(ev)) => {
-                    let window = self.get_window(ev.event());
-                    let pressed_button = super::button_from_state(ev.state());
-                    let position =
-                        Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
-                    let modifiers = super::modifiers_from_state(ev.state());
-                    window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
-                        pressed_button,
+                } else if event.detail() >= 4 && event.detail() <= 5 {
+                    // https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
+                    let delta_x = if event.detail() == 4 { 1.0 } else { -1.0 };
+                    window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
                         position,
+                        delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
                         modifiers,
+                        touch_phase: TouchPhase::default(),
                     }));
+                } else {
+                    log::warn!("Unknown button press: {event:?}");
                 }
-                xcb::Event::X(x::Event::LeaveNotify(ev)) => {
-                    let window = self.get_window(ev.event());
-                    let pressed_button = super::button_from_state(ev.state());
-                    let position =
-                        Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
-                    let modifiers = super::modifiers_from_state(ev.state());
-                    window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
-                        pressed_button,
+            }
+            xcb::Event::X(x::Event::ButtonRelease(event)) => {
+                let window = self.get_window(event.event())?;
+                let modifiers = super::modifiers_from_state(event.state());
+                let position = Point::new(
+                    (event.event_x() as f32).into(),
+                    (event.event_y() as f32).into(),
+                );
+                if let Some(button) = super::button_of_key(event.detail()) {
+                    window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
+                        button,
                         position,
                         modifiers,
+                        click_count: 1,
                     }));
                 }
-                _ => {}
             }
-        }
+            xcb::Event::X(x::Event::MotionNotify(event)) => {
+                let window = self.get_window(event.event())?;
+                let pressed_button = super::button_from_state(event.state());
+                let position = Point::new(
+                    (event.event_x() as f32).into(),
+                    (event.event_y() as f32).into(),
+                );
+                let modifiers = super::modifiers_from_state(event.state());
+                window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
+                    pressed_button,
+                    position,
+                    modifiers,
+                }));
+            }
+            xcb::Event::X(x::Event::LeaveNotify(event)) => {
+                let window = self.get_window(event.event())?;
+                let pressed_button = super::button_from_state(event.state());
+                let position = Point::new(
+                    (event.event_x() as f32).into(),
+                    (event.event_y() as f32).into(),
+                );
+                let modifiers = super::modifiers_from_state(event.state());
+                window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
+                    pressed_button,
+                    position,
+                    modifiers,
+                }));
+            }
+            _ => {}
+        };
 
-        if let Some(ref mut fun) = self.platform_inner.callbacks.lock().quit {
-            fun();
-        }
+        Some(())
     }
+}
 
+impl Client for X11Client {
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
         let setup = self.xcb_connection.get_setup();
         setup
@@ -264,10 +324,40 @@ impl Client for X11Client {
         ));
         window_ptr.request_refresh();
 
+        let cookie = self
+            .xcb_connection
+            .send_request(&xcb::randr::GetScreenResourcesCurrent { window: x_window });
+        let screen_resources = self.xcb_connection.wait_for_reply(cookie).expect("TODO");
+        let crtc = screen_resources.crtcs().first().expect("TODO");
+
+        let cookie = self.xcb_connection.send_request(&xcb::randr::GetCrtcInfo {
+            crtc: crtc.to_owned(),
+            config_timestamp: xcb::x::Time::CurrentTime as u32,
+        });
+        let crtc_info = self.xcb_connection.wait_for_reply(cookie).expect("TODO");
+
+        let mode_id = crtc_info.mode().resource_id();
+        let mode = screen_resources
+            .modes()
+            .iter()
+            .find(|m| m.id == mode_id)
+            .expect("Missing screen mode for crtc specified mode id");
+
+        let refresh_millies = mode_refresh_rate_millis(mode);
+
+        self.refresh_millis.set(refresh_millies);
+
         self.state
-            .lock()
+            .borrow_mut()
             .windows
             .insert(x_window, Rc::clone(&window_ptr));
         Box::new(X11Window(window_ptr))
     }
 }
+
+// Adatpted from:
+// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
+pub fn mode_refresh_rate_millis(mode: &xcb::randr::ModeInfo) -> u64 {
+    let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
+    (millihertz as f64 / 1_000_000.) as u64
+}

crates/gpui/src/platform/linux/x11/client_dispatcher.rs 🔗

@@ -1,64 +0,0 @@
-use std::sync::Arc;
-
-use xcb::x;
-
-use crate::platform::linux::client_dispatcher::ClientDispatcher;
-
-pub(crate) struct X11ClientDispatcher {
-    xcb_connection: Arc<xcb::Connection>,
-    x_listener_window: x::Window,
-}
-
-impl X11ClientDispatcher {
-    pub fn new(xcb_connection: &Arc<xcb::Connection>, x_root_index: i32) -> Self {
-        let x_listener_window = xcb_connection.generate_id();
-        let screen = xcb_connection
-            .get_setup()
-            .roots()
-            .nth(x_root_index as usize)
-            .unwrap();
-        xcb_connection.send_request(&x::CreateWindow {
-            depth: 0,
-            wid: x_listener_window,
-            parent: screen.root(),
-            x: 0,
-            y: 0,
-            width: 1,
-            height: 1,
-            border_width: 0,
-            class: x::WindowClass::InputOnly,
-            visual: screen.root_visual(),
-            value_list: &[],
-        });
-
-        Self {
-            xcb_connection: Arc::clone(xcb_connection),
-            x_listener_window,
-        }
-    }
-}
-
-impl Drop for X11ClientDispatcher {
-    fn drop(&mut self) {
-        self.xcb_connection.send_request(&x::DestroyWindow {
-            window: self.x_listener_window,
-        });
-    }
-}
-
-impl ClientDispatcher for X11ClientDispatcher {
-    fn dispatch_on_main_thread(&self) {
-        // Send a message to the invisible window, forcing
-        // the main loop to wake up and dispatch the runnable.
-        self.xcb_connection.send_request(&x::SendEvent {
-            propagate: false,
-            destination: x::SendEventDest::Window(self.x_listener_window),
-            event_mask: x::EventMask::NO_EVENT,
-            event: &x::VisibilityNotifyEvent::new(
-                self.x_listener_window,
-                x::Visibility::Unobscured,
-            ),
-        });
-        self.xcb_connection.flush().unwrap();
-    }
-}

crates/gpui/src/platform/linux/x11/window.rs 🔗

@@ -4,7 +4,7 @@
 use crate::{
     platform::blade::BladeRenderer, size, Bounds, GlobalPixels, Modifiers, Pixels, PlatformAtlas,
     PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
-    Scene, Size, WindowAppearance, WindowBounds, WindowOptions, X11Display,
+    Scene, Size, WindowAppearance, WindowBounds, WindowOptions,
 };
 use blade_graphics as gpu;
 use parking_lot::Mutex;
@@ -16,6 +16,7 @@ use xcb::{
 };
 
 use std::{
+    cell::RefCell,
     ffi::c_void,
     mem,
     num::NonZeroU32,
@@ -24,6 +25,8 @@ use std::{
     sync::{self, Arc},
 };
 
+use super::X11Display;
+
 #[derive(Default)]
 struct Callbacks {
     request_frame: Option<Box<dyn FnMut()>>,
@@ -85,12 +88,12 @@ struct RawWindow {
 }
 
 pub(crate) struct X11WindowState {
-    xcb_connection: Arc<xcb::Connection>,
+    xcb_connection: Rc<xcb::Connection>,
     display: Rc<dyn PlatformDisplay>,
     raw: RawWindow,
     x_window: x::Window,
-    callbacks: Mutex<Callbacks>,
-    inner: Mutex<LinuxWindowInner>,
+    callbacks: RefCell<Callbacks>,
+    inner: RefCell<LinuxWindowInner>,
 }
 
 #[derive(Clone)]
@@ -136,7 +139,7 @@ impl rwh::HasDisplayHandle for X11Window {
 impl X11WindowState {
     pub fn new(
         options: WindowOptions,
-        xcb_connection: &Arc<xcb::Connection>,
+        xcb_connection: &Rc<xcb::Connection>,
         x_main_screen_index: i32,
         x_window: x::Window,
         atoms: &XcbAtoms,
@@ -253,12 +256,12 @@ impl X11WindowState {
         let gpu_extent = query_render_extent(xcb_connection, x_window);
 
         Self {
-            xcb_connection: Arc::clone(xcb_connection),
+            xcb_connection: xcb_connection.clone(),
             display: Rc::new(X11Display::new(xcb_connection, x_screen_index)),
             raw,
             x_window,
-            callbacks: Mutex::new(Callbacks::default()),
-            inner: Mutex::new(LinuxWindowInner {
+            callbacks: RefCell::new(Callbacks::default()),
+            inner: RefCell::new(LinuxWindowInner {
                 bounds,
                 scale_factor: 1.0,
                 renderer: BladeRenderer::new(gpu, gpu_extent),
@@ -268,23 +271,26 @@ impl X11WindowState {
     }
 
     pub fn destroy(&self) {
-        self.inner.lock().renderer.destroy();
+        self.inner.borrow_mut().renderer.destroy();
         self.xcb_connection.send_request(&x::UnmapWindow {
             window: self.x_window,
         });
         self.xcb_connection.send_request(&x::DestroyWindow {
             window: self.x_window,
         });
-        if let Some(fun) = self.callbacks.lock().close.take() {
+        if let Some(fun) = self.callbacks.borrow_mut().close.take() {
             fun();
         }
         self.xcb_connection.flush().unwrap();
     }
 
     pub fn refresh(&self) {
-        let mut cb = self.callbacks.lock();
-        if let Some(ref mut fun) = cb.request_frame {
+        let mut cb = self.callbacks.borrow_mut();
+        if let Some(mut fun) = cb.request_frame.take() {
+            drop(cb);
             fun();
+            let mut cb = self.callbacks.borrow_mut();
+            cb.request_frame = Some(fun);
         }
     }
 
@@ -292,7 +298,7 @@ impl X11WindowState {
         let mut resize_args = None;
         let do_move;
         {
-            let mut inner = self.inner.lock();
+            let mut inner = self.inner.borrow_mut();
             let old_bounds = mem::replace(&mut inner.bounds, bounds);
             do_move = old_bounds.origin != bounds.origin;
             //todo!(linux): use normal GPUI types here, refactor out the double
@@ -306,7 +312,7 @@ impl X11WindowState {
             }
         }
 
-        let mut callbacks = self.callbacks.lock();
+        let mut callbacks = self.callbacks.borrow_mut();
         if let Some((content_size, scale_factor)) = resize_args {
             if let Some(ref mut fun) = callbacks.resize {
                 fun(content_size, scale_factor)
@@ -330,13 +336,13 @@ impl X11WindowState {
     }
 
     pub fn handle_input(&self, input: PlatformInput) {
-        if let Some(ref mut fun) = self.callbacks.lock().input {
+        if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
             if fun(input.clone()) {
                 return;
             }
         }
         if let PlatformInput::KeyDown(event) = input {
-            let mut inner = self.inner.lock();
+            let mut inner = self.inner.borrow_mut();
             if let Some(ref mut input_handler) = inner.input_handler {
                 if let Some(ime_key) = &event.keystroke.ime_key {
                     input_handler.replace_text_in_range(None, ime_key);
@@ -346,7 +352,7 @@ impl X11WindowState {
     }
 
     pub fn set_focused(&self, focus: bool) {
-        if let Some(ref mut fun) = self.callbacks.lock().active_status_change {
+        if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
             fun(focus);
         }
     }
@@ -354,15 +360,21 @@ impl X11WindowState {
 
 impl PlatformWindow for X11Window {
     fn bounds(&self) -> WindowBounds {
-        WindowBounds::Fixed(self.0.inner.lock().bounds.map(|v| GlobalPixels(v as f32)))
+        WindowBounds::Fixed(
+            self.0
+                .inner
+                .borrow_mut()
+                .bounds
+                .map(|v| GlobalPixels(v as f32)),
+        )
     }
 
     fn content_size(&self) -> Size<Pixels> {
-        self.0.inner.lock().content_size()
+        self.0.inner.borrow_mut().content_size()
     }
 
     fn scale_factor(&self) -> f32 {
-        self.0.inner.lock().scale_factor
+        self.0.inner.borrow_mut().scale_factor
     }
 
     //todo!(linux)
@@ -400,11 +412,11 @@ impl PlatformWindow for X11Window {
     }
 
     fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
-        self.0.inner.lock().input_handler = Some(input_handler);
+        self.0.inner.borrow_mut().input_handler = Some(input_handler);
     }
 
     fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
-        self.0.inner.lock().input_handler.take()
+        self.0.inner.borrow_mut().input_handler.take()
     }
 
     //todo!(linux)
@@ -464,39 +476,39 @@ impl PlatformWindow for X11Window {
     }
 
     fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
-        self.0.callbacks.lock().request_frame = Some(callback);
+        self.0.callbacks.borrow_mut().request_frame = Some(callback);
     }
 
     fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
-        self.0.callbacks.lock().input = Some(callback);
+        self.0.callbacks.borrow_mut().input = Some(callback);
     }
 
     fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
-        self.0.callbacks.lock().active_status_change = Some(callback);
+        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
     }
 
     fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
-        self.0.callbacks.lock().resize = Some(callback);
+        self.0.callbacks.borrow_mut().resize = Some(callback);
     }
 
     fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
-        self.0.callbacks.lock().fullscreen = Some(callback);
+        self.0.callbacks.borrow_mut().fullscreen = Some(callback);
     }
 
     fn on_moved(&self, callback: Box<dyn FnMut()>) {
-        self.0.callbacks.lock().moved = Some(callback);
+        self.0.callbacks.borrow_mut().moved = Some(callback);
     }
 
     fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
-        self.0.callbacks.lock().should_close = Some(callback);
+        self.0.callbacks.borrow_mut().should_close = Some(callback);
     }
 
     fn on_close(&self, callback: Box<dyn FnOnce()>) {
-        self.0.callbacks.lock().close = Some(callback);
+        self.0.callbacks.borrow_mut().close = Some(callback);
     }
 
     fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
-        self.0.callbacks.lock().appearance_changed = Some(callback);
+        self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
     }
 
     //todo!(linux)
@@ -505,12 +517,12 @@ impl PlatformWindow for X11Window {
     }
 
     fn draw(&self, scene: &Scene) {
-        let mut inner = self.0.inner.lock();
+        let mut inner = self.0.inner.borrow_mut();
         inner.renderer.draw(scene);
     }
 
     fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
-        let inner = self.0.inner.lock();
+        let inner = self.0.inner.borrow_mut();
         inner.renderer.sprite_atlas().clone()
     }