From b76e0d997e6f930b846da9ab52507dd51ca0964f Mon Sep 17 00:00:00 2001 From: Roman <40907255+witelokk@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:59:11 -0800 Subject: [PATCH] Linux: Rewrite the event loop using calloop (#8314) 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 Co-authored-by: Tadeo Kondrak Co-authored-by: Mikayla Maki Co-authored-by: julia --- Cargo.lock | 28 ++ crates/gpui/Cargo.toml | 2 + crates/gpui/src/platform/linux.rs | 3 +- crates/gpui/src/platform/linux/client.rs | 1 - .../src/platform/linux/client_dispatcher.rs | 3 - crates/gpui/src/platform/linux/dispatcher.rs | 111 +++-- crates/gpui/src/platform/linux/platform.rs | 165 +++---- crates/gpui/src/platform/linux/wayland.rs | 2 - .../gpui/src/platform/linux/wayland/client.rs | 212 ++++---- .../linux/wayland/client_dispatcher.rs | 30 -- .../gpui/src/platform/linux/wayland/window.rs | 80 ++- crates/gpui/src/platform/linux/x11.rs | 2 - crates/gpui/src/platform/linux/x11/client.rs | 466 +++++++++++------- .../platform/linux/x11/client_dispatcher.rs | 64 --- crates/gpui/src/platform/linux/x11/window.rs | 78 +-- 15 files changed, 615 insertions(+), 632 deletions(-) delete mode 100644 crates/gpui/src/platform/linux/client_dispatcher.rs delete mode 100644 crates/gpui/src/platform/linux/wayland/client_dispatcher.rs delete mode 100644 crates/gpui/src/platform/linux/x11/client_dispatcher.rs diff --git a/Cargo.lock b/Cargo.lock index fb85018168..d4486db94c 100644 --- a/Cargo.lock +++ b/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", diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index f27fd0ca7f..8431dd5599 100644 --- a/crates/gpui/Cargo.toml +++ b/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 diff --git a/crates/gpui/src/platform/linux.rs b/crates/gpui/src/platform/linux.rs index be908eb0b9..f334b23399 100644 --- a/crates/gpui/src/platform/linux.rs +++ b/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::*; diff --git a/crates/gpui/src/platform/linux/client.rs b/crates/gpui/src/platform/linux/client.rs index d314202972..0d71e4ef00 100644 --- a/crates/gpui/src/platform/linux/client.rs +++ b/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); fn displays(&self) -> Vec>; fn display(&self, id: DisplayId) -> Option>; fn open_window( diff --git a/crates/gpui/src/platform/linux/client_dispatcher.rs b/crates/gpui/src/platform/linux/client_dispatcher.rs deleted file mode 100644 index 823e2df0b7..0000000000 --- a/crates/gpui/src/platform/linux/client_dispatcher.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub trait ClientDispatcher: Send + Sync { - fn dispatch_on_main_thread(&self); -} diff --git a/crates/gpui/src/platform/linux/dispatcher.rs b/crates/gpui/src/platform/linux/dispatcher.rs index 26301ad95a..4f2b27dce3 100644 --- a/crates/gpui/src/platform/linux/dispatcher.rs +++ b/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, parker: Mutex, - timed_tasks: Mutex>, - main_sender: flume::Sender, + main_sender: Sender, + timer_sender: Sender, background_sender: flume::Sender, - _background_thread: thread::JoinHandle<()>, + _background_threads: Vec>, main_thread_id: thread::ThreadId, } impl LinuxDispatcher { - pub fn new( - main_sender: flume::Sender, - client_dispatcher: &Arc, - ) -> Self { + pub fn new(main_sender: Sender) -> Self { let (background_sender, background_receiver) = flume::unbounded::(); - 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::>(); + + let (timer_sender, timer_channel) = calloop::channel::channel::(); + 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) { diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 9657f015c8..1195859ef3 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/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)>>, become_active: Option>, resign_active: Option>, - pub(crate) quit: Option>, + quit: Option>, reopen: Option>, event: Option bool>>, app_menu_action: Option>, @@ -41,12 +43,13 @@ pub(crate) struct Callbacks { } pub(crate) struct LinuxPlatformInner { + pub(crate) event_loop: RefCell>, + pub(crate) loop_handle: Rc>, + pub(crate) loop_signal: LoopSignal, pub(crate) background_executor: BackgroundExecutor, pub(crate) foreground_executor: ForegroundExecutor, - pub(crate) main_receiver: flume::Receiver, pub(crate) text_system: Arc, - pub(crate) callbacks: Mutex, - pub(crate) state: Mutex, + pub(crate) callbacks: RefCell, } pub(crate) struct LinuxPlatform { @@ -54,10 +57,6 @@ pub(crate) struct LinuxPlatform { inner: Rc, } -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::(); + let (main_sender, main_receiver) = calloop::channel::channel::(); 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()); + + 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(); + } + }); + + let dispatcher = Arc::new(LinuxDispatcher::new(main_sender)); + + 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()), + text_system, + callbacks, }); if use_wayland { - Self::new_wayland(main_sender, main_receiver, text_system, callbacks, state) + Self { + client: Rc::new(WaylandClient::new(Rc::clone(&inner))), + inner, + } } else { - Self::new_x11(main_sender, main_receiver, text_system, callbacks, state) - } - } - - fn new_wayland( - main_sender: Sender, - main_receiver: Receiver, - text_system: Arc, - callbacks: Mutex, - state: Mutex, - ) -> Self { - let conn = Arc::new(Connection::connect_to_env().unwrap()); - let client_dispatcher: Arc = - 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), - } - } - - fn new_x11( - main_sender: Sender, - main_receiver: Receiver, - text_system: Arc, - callbacks: Mutex, - state: Mutex, - ) -> 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 = - Arc::new(X11ClientDispatcher::new(&xcb_connection, x_root_index)); - 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(X11Client::new( - Rc::clone(&inner), - xcb_connection, - x_root_index, - atoms, - )); - Self { - client, - inner: Rc::clone(&inner), + Self { + client: X11Client::new(Rc::clone(&inner)), + inner, + } } } } @@ -171,11 +121,24 @@ impl Platform for LinuxPlatform { } fn run(&self, on_finish_launching: Box) { - 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)>) { - 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) { - self.inner.callbacks.lock().become_active = Some(callback); + self.inner.callbacks.borrow_mut().become_active = Some(callback); } fn on_resign_active(&self, callback: Box) { - self.inner.callbacks.lock().resign_active = Some(callback); + self.inner.callbacks.borrow_mut().resign_active = Some(callback); } fn on_quit(&self, callback: Box) { - self.inner.callbacks.lock().quit = Some(callback); + self.inner.callbacks.borrow_mut().quit = Some(callback); } fn on_reopen(&self, callback: Box) { - self.inner.callbacks.lock().reopen = Some(callback); + self.inner.callbacks.borrow_mut().reopen = Some(callback); } fn on_event(&self, callback: Box bool>) { - self.inner.callbacks.lock().event = Some(callback); + self.inner.callbacks.borrow_mut().event = Some(callback); } fn on_app_menu_action(&self, callback: Box) { - 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) { - 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 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 { diff --git a/crates/gpui/src/platform/linux/wayland.rs b/crates/gpui/src/platform/linux/wayland.rs index 0078289ba2..63e106cc6d 100644 --- a/crates/gpui/src/platform/linux/wayland.rs +++ b/crates/gpui/src/platform/linux/wayland.rs @@ -2,9 +2,7 @@ #![allow(unused_variables)] pub(crate) use client::*; -pub(crate) use client_dispatcher::*; mod client; -mod client_dispatcher; mod display; mod window; diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index aa6df7ffe4..55e5670110 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/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, - buffer: Option, - wm_base: Option, + compositor: wl_compositor::WlCompositor, + wm_base: xdg_wm_base::XdgWmBase, viewporter: Option, fractional_scale_manager: Option, decoration_manager: Option, windows: Vec<(xdg_surface::XdgSurface, Rc)>, platform_inner: Rc, - wl_seat: Option, keymap_state: Option, repeat: KeyRepeat, modifiers: Modifiers, @@ -59,6 +60,7 @@ pub(crate) struct WaylandClientStateInner { button_pressed: Option, mouse_focused_window: Option>, keyboard_focused_window: Option>, + loop_handle: Rc>, } #[derive(Clone)] @@ -73,24 +75,40 @@ pub(crate) struct KeyRepeat { pub(crate) struct WaylandClient { platform_inner: Rc, - conn: Arc, state: WaylandClientState, - event_queue: Mutex>, qh: Arc>, } +const WL_SEAT_VERSION: u32 = 4; + impl WaylandClient { - pub(crate) fn new(linux_platform_inner: Rc, conn: Arc) -> 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) -> Self { + let conn = Connection::connect_to_env().unwrap(); + + let (globals, mut event_queue) = registry_queue_init::(&conn).unwrap(); + let qh = event_queue.handle(); + + globals.contents().with_list(|list| { + for global in list { + if global.interface == "wl_seat" { + globals.registry().bind::( + 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 = 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) { - 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> { Vec::new() } @@ -160,10 +165,8 @@ impl Client for WaylandClient { ) -> Box { 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 = Rc::new(WaylandWindowState::new( wl_surface.clone(), viewport, Arc::new(toplevel), @@ -217,62 +219,27 @@ impl Client for WaylandClient { } } -impl Dispatch for WaylandClientState { +impl Dispatch for WaylandClientState { fn event( state: &mut Self, registry: &wl_registry::WlRegistry, event: wl_registry::Event, - _: &(), + _: &GlobalListContents, _: &Connection, qh: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); - if let wl_registry::Event::Global { - name, interface, .. - } = event - { - match &interface[..] { - "wl_compositor" => { - let compositor = - registry.bind::(name, 1, qh, ()); - state.compositor = Some(compositor); + match event { + wl_registry::Event::Global { + name, + interface, + version: _, + } => { + if interface.as_str() == "wl_seat" { + registry.bind::(name, 4, qh, ()); } - "xdg_wm_base" => { - let wm_base = registry.bind::(name, 1, qh, ()); - state.wm_base = Some(wm_base); - } - "wl_seat" => { - let seat = registry.bind::(name, 4, qh, ()); - state.wl_seat = Some(seat); - } - "wp_fractional_scale_manager_v1" => { - let manager = registry - .bind::( - name, - 1, - qh, - (), - ); - state.fractional_scale_manager = Some(manager); - } - "wp_viewporter" => { - let view_porter = - registry.bind::(name, 1, qh, ()); - state.viewporter = Some(view_porter); - } - "zxdg_decoration_manager_v1" => { - // Unstable and optional - let decoration_manager = registry - .bind::( - name, - 1, - qh, - (), - ); - state.decoration_manager = Some(decoration_manager); - } - _ => {} - }; + } + wl_registry::Event::GlobalRemove { name: _ } => {} + _ => {} } } } @@ -367,7 +334,7 @@ impl Dispatch 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 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_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(); - 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; + 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 => { diff --git a/crates/gpui/src/platform/linux/wayland/client_dispatcher.rs b/crates/gpui/src/platform/linux/wayland/client_dispatcher.rs deleted file mode 100644 index c9154b2f6a..0000000000 --- a/crates/gpui/src/platform/linux/wayland/client_dispatcher.rs +++ /dev/null @@ -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, - event_queue: Arc>, -} - -impl WaylandClientDispatcher { - pub(crate) fn new(conn: &Arc) -> 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) {} -} diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 3a0c82cd16..d086222b61 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/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, - wl_surf: &Arc, - bounds: Bounds, - ) -> Self { + fn new(wl_surf: &Arc, bounds: Bounds) -> Self { let raw = RawWindow { window: wl_surf.id().as_ptr().cast::(), - display: conn.backend().display_ptr().cast::(), + display: wl_surf + .backend() + .upgrade() + .unwrap() + .display_ptr() + .cast::(), }; let gpu = Arc::new( unsafe { @@ -105,9 +106,8 @@ impl WaylandWindowInner { } pub(crate) struct WaylandWindowState { - conn: Arc, - inner: Mutex, - pub(crate) callbacks: Mutex, + inner: RefCell, + pub(crate) callbacks: RefCell, pub(crate) surface: Arc, pub(crate) toplevel: Arc, viewport: Option, @@ -115,7 +115,6 @@ pub(crate) struct WaylandWindowState { impl WaylandWindowState { pub(crate) fn new( - conn: &Arc, wl_surf: Arc, viewport: Option, toplevel: Arc, @@ -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 { - 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 { - 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) { - self.0.callbacks.lock().request_frame = Some(callback); + self.0.callbacks.borrow_mut().request_frame = Some(callback); } fn on_input(&self, callback: Box bool>) { - self.0.callbacks.lock().input = Some(callback); + self.0.callbacks.borrow_mut().input = Some(callback); } fn on_active_status_change(&self, callback: Box) { - 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, f32)>) { - self.0.callbacks.lock().resize = Some(callback); + self.0.callbacks.borrow_mut().resize = Some(callback); } fn on_fullscreen(&self, callback: Box) { @@ -367,15 +365,15 @@ impl PlatformWindow for WaylandWindow { } fn on_moved(&self, callback: Box) { - self.0.callbacks.lock().moved = Some(callback); + self.0.callbacks.borrow_mut().moved = Some(callback); } fn on_should_close(&self, callback: Box bool>) { - self.0.callbacks.lock().should_close = Some(callback); + self.0.callbacks.borrow_mut().should_close = Some(callback); } fn on_close(&self, callback: Box) { - self.0.callbacks.lock().close = Some(callback); + self.0.callbacks.borrow_mut().close = Some(callback); } fn on_appearance_changed(&self, callback: Box) { @@ -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 { - let inner = self.0.inner.lock(); + let inner = self.0.inner.borrow_mut(); inner.renderer.sprite_atlas().clone() } diff --git a/crates/gpui/src/platform/linux/x11.rs b/crates/gpui/src/platform/linux/x11.rs index bcb2215b01..958da047d6 100644 --- a/crates/gpui/src/platform/linux/x11.rs +++ b/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::*; diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 0a185462d9..239d6c33af 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/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>, + pub(crate) windows_to_refresh: HashSet, xkb: xkbcommon::xkb::State, } pub(crate) struct X11Client { platform_inner: Rc, - xcb_connection: Arc, + xcb_connection: Rc, x_root_index: i32, atoms: XcbAtoms, - state: Mutex, + refresh_millis: Cell, + state: RefCell, } impl X11Client { - pub(crate) fn new( - inner: Rc, - xcb_connection: Arc, - x_root_index: i32, - atoms: XcbAtoms, - ) -> Self { + pub(crate) fn new(inner: Rc) -> Rc { + 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 = 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::( + 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) -> Option> { + let state = self.state.borrow(); + state.windows.get(&win).cloned() + } + + fn present(&self) { + let state = self.state.borrow_mut(); + for window_state in state.windows.values() { + window_state.refresh(); } } - fn get_window(&self, win: x::Window) -> Rc { - let state = self.state.lock(); - Rc::clone(&state.windows[&win]) + 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::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, + })); + } 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::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, + })); + } + _ => {} + }; + + Some(()) } } impl Client for X11Client { - fn run(&self, on_finish_launching: Box) { - on_finish_launching(); - let mut windows_to_refresh = HashSet::::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(); - } - } - } - 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::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, - position, - modifiers, - })); - } - 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, - position, - modifiers, - })); - } - _ => {} - } - } - - if let Some(ref mut fun) = self.platform_inner.callbacks.lock().quit { - fun(); - } - } - fn displays(&self) -> Vec> { 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 +} diff --git a/crates/gpui/src/platform/linux/x11/client_dispatcher.rs b/crates/gpui/src/platform/linux/x11/client_dispatcher.rs deleted file mode 100644 index 07f67af99f..0000000000 --- a/crates/gpui/src/platform/linux/x11/client_dispatcher.rs +++ /dev/null @@ -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, - x_listener_window: x::Window, -} - -impl X11ClientDispatcher { - pub fn new(xcb_connection: &Arc, 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(); - } -} diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index e4293d7b9d..7df4a8416d 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/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>, @@ -85,12 +88,12 @@ struct RawWindow { } pub(crate) struct X11WindowState { - xcb_connection: Arc, + xcb_connection: Rc, display: Rc, raw: RawWindow, x_window: x::Window, - callbacks: Mutex, - inner: Mutex, + callbacks: RefCell, + inner: RefCell, } #[derive(Clone)] @@ -136,7 +139,7 @@ impl rwh::HasDisplayHandle for X11Window { impl X11WindowState { pub fn new( options: WindowOptions, - xcb_connection: &Arc, + xcb_connection: &Rc, 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 { - 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 { - 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) { - self.0.callbacks.lock().request_frame = Some(callback); + self.0.callbacks.borrow_mut().request_frame = Some(callback); } fn on_input(&self, callback: Box bool>) { - self.0.callbacks.lock().input = Some(callback); + self.0.callbacks.borrow_mut().input = Some(callback); } fn on_active_status_change(&self, callback: Box) { - 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, f32)>) { - self.0.callbacks.lock().resize = Some(callback); + self.0.callbacks.borrow_mut().resize = Some(callback); } fn on_fullscreen(&self, callback: Box) { - self.0.callbacks.lock().fullscreen = Some(callback); + self.0.callbacks.borrow_mut().fullscreen = Some(callback); } fn on_moved(&self, callback: Box) { - self.0.callbacks.lock().moved = Some(callback); + self.0.callbacks.borrow_mut().moved = Some(callback); } fn on_should_close(&self, callback: Box bool>) { - self.0.callbacks.lock().should_close = Some(callback); + self.0.callbacks.borrow_mut().should_close = Some(callback); } fn on_close(&self, callback: Box) { - self.0.callbacks.lock().close = Some(callback); + self.0.callbacks.borrow_mut().close = Some(callback); } fn on_appearance_changed(&self, callback: Box) { - 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 { - let inner = self.0.inner.lock(); + let inner = self.0.inner.borrow_mut(); inner.renderer.sprite_atlas().clone() }