From 521b2b12e463e0d7aa0cd713198b7adf771407ed Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Sun, 4 Feb 2024 23:52:22 -0800 Subject: [PATCH] linux: create a hidden window inside the platform It allows us to receive messages from the dispatcher, which breaks us out of waiting and lets us execute main thread runnables as a part of the main loop. --- crates/gpui/src/platform/linux/dispatcher.rs | 82 +++++++++++++------ crates/gpui/src/platform/linux/platform.rs | 34 +++++--- crates/gpui/src/platform/linux/text_system.rs | 2 +- crates/gpui/src/platform/linux/window.rs | 1 + 4 files changed, 80 insertions(+), 39 deletions(-) diff --git a/crates/gpui/src/platform/linux/dispatcher.rs b/crates/gpui/src/platform/linux/dispatcher.rs index 49f10449f8..28526d360a 100644 --- a/crates/gpui/src/platform/linux/dispatcher.rs +++ b/crates/gpui/src/platform/linux/dispatcher.rs @@ -7,29 +7,50 @@ use async_task::Runnable; use parking::{Parker, Unparker}; use parking_lot::Mutex; use std::{ - panic, thread, + panic, + sync::Arc, + thread, time::{Duration, Instant}, }; +use xcb::x; pub(crate) struct LinuxDispatcher { + xcb_connection: Arc, + x_listener_window: x::Window, parker: Mutex, timed_tasks: Mutex>, main_sender: flume::Sender, - main_receiver: flume::Receiver, background_sender: flume::Sender, - background_thread: thread::JoinHandle<()>, + _background_thread: thread::JoinHandle<()>, main_thread_id: thread::ThreadId, } -impl Default for LinuxDispatcher { - fn default() -> Self { - Self::new() - } -} - impl LinuxDispatcher { - pub fn new() -> Self { - let (main_sender, main_receiver) = flume::unbounded::(); + pub fn new( + main_sender: flume::Sender, + 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: &[], + }); + let (background_sender, background_receiver) = flume::unbounded::(); let background_thread = thread::spawn(move || { for runnable in background_receiver { @@ -37,21 +58,23 @@ impl LinuxDispatcher { } }); LinuxDispatcher { + xcb_connection: Arc::clone(xcb_connection), + x_listener_window, parker: Mutex::new(Parker::new()), timed_tasks: Mutex::new(Vec::new()), main_sender, - main_receiver, background_sender, - background_thread, + _background_thread: background_thread, main_thread_id: thread::current().id(), } } +} - pub fn tick_main(&self) { - assert!(self.is_main_thread()); - if let Ok(runnable) = self.main_receiver.try_recv() { - runnable.run(); - } +impl Drop for LinuxDispatcher { + fn drop(&mut self) { + self.xcb_connection.send_request(&x::DestroyWindow { + window: self.x_listener_window, + }); } } @@ -66,6 +89,18 @@ impl PlatformDispatcher for LinuxDispatcher { fn dispatch_on_main_thread(&self, runnable: Runnable) { self.main_sender.send(runnable).unwrap(); + // Send a message to the invisible window, forcing + // tha 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(); } fn dispatch_after(&self, duration: Duration, runnable: Runnable) { @@ -76,24 +111,17 @@ impl PlatformDispatcher for LinuxDispatcher { } fn tick(&self, background_only: bool) -> bool { - let mut ran = false; - if self.is_main_thread() && !background_only { - for runnable in self.main_receiver.try_iter() { - runnable.run(); - ran = true; - } - } 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(); - ran = true; } else { break; } } - ran + timed_tasks.len() != old_count } fn park(&self) { diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 3ce7666246..60cf135412 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -7,6 +7,7 @@ use crate::{ PlatformTextSystem, PlatformWindow, Point, Result, SemanticVersion, Size, Task, WindowOptions, }; +use async_task::Runnable; use collections::{HashMap, HashSet}; use futures::channel::oneshot; use parking_lot::Mutex; @@ -37,12 +38,13 @@ pub(crate) struct LinuxPlatform { atoms: XcbAtoms, background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, - dispatcher: Arc, + main_receiver: flume::Receiver, text_system: Arc, state: Mutex, } pub(crate) struct LinuxPlatformState { + quit_requested: bool, windows: HashMap>, } @@ -57,17 +59,24 @@ impl LinuxPlatform { let (xcb_connection, x_root_index) = xcb::Connection::connect(None).unwrap(); let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap(); - let dispatcher = Arc::new(LinuxDispatcher::new()); + let xcb_connection = Arc::new(xcb_connection); + let (main_sender, main_receiver) = flume::unbounded::(); + let dispatcher = Arc::new(LinuxDispatcher::new( + main_sender, + &xcb_connection, + x_root_index, + )); Self { - xcb_connection: Arc::new(xcb_connection), + xcb_connection, x_root_index, atoms, background_executor: BackgroundExecutor::new(dispatcher.clone()), foreground_executor: ForegroundExecutor::new(dispatcher.clone()), - dispatcher, + main_receiver, text_system: Arc::new(LinuxTextSystem::new()), state: Mutex::new(LinuxPlatformState { + quit_requested: false, windows: HashMap::default(), }), } @@ -92,8 +101,7 @@ impl Platform for LinuxPlatform { //Note: here and below, don't keep the lock() open when calling // into window functions as they may invoke callbacks that need // to immediately access the platform (self). - - while !self.state.lock().windows.is_empty() { + while !self.state.lock().quit_requested { let event = self.xcb_connection.wait_for_event().unwrap(); match event { xcb::Event::X(x::Event::ClientMessage(ev)) => { @@ -129,15 +137,18 @@ impl Platform for LinuxPlatform { }; window.configure(bounds) } - ref other => { - println!("Other event {:?}", other); - } + _ => {} + } + + if let Ok(runnable) = self.main_receiver.try_recv() { + runnable.run(); } - self.dispatcher.tick_main(); } } - fn quit(&self) {} + fn quit(&self) { + self.state.lock().quit_requested = true; + } fn restart(&self) {} @@ -186,6 +197,7 @@ impl Platform for LinuxPlatform { x_window, &self.atoms, )); + self.state .lock() .windows diff --git a/crates/gpui/src/platform/linux/text_system.rs b/crates/gpui/src/platform/linux/text_system.rs index 62935fc232..187ffb526a 100644 --- a/crates/gpui/src/platform/linux/text_system.rs +++ b/crates/gpui/src/platform/linux/text_system.rs @@ -76,7 +76,7 @@ impl PlatformTextSystem for LinuxTextSystem { unimplemented!() } fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option { - unimplemented!() + None } fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result> { unimplemented!() diff --git a/crates/gpui/src/platform/linux/window.rs b/crates/gpui/src/platform/linux/window.rs index 3818ec2c65..031dcb0cd6 100644 --- a/crates/gpui/src/platform/linux/window.rs +++ b/crates/gpui/src/platform/linux/window.rs @@ -237,6 +237,7 @@ impl LinuxWindowState { if let Some(fun) = self.callbacks.lock().close.take() { fun(); } + self.xcb_connection.flush().unwrap(); } pub fn expose(&self) {