mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-24 19:10:24 +00:00
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.
This commit is contained in:
parent
282cc71df9
commit
521b2b12e4
4 changed files with 80 additions and 39 deletions
|
@ -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<xcb::Connection>,
|
||||
x_listener_window: x::Window,
|
||||
parker: Mutex<Parker>,
|
||||
timed_tasks: Mutex<Vec<(Instant, Runnable)>>,
|
||||
main_sender: flume::Sender<Runnable>,
|
||||
main_receiver: flume::Receiver<Runnable>,
|
||||
background_sender: flume::Sender<Runnable>,
|
||||
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::<Runnable>();
|
||||
pub fn new(
|
||||
main_sender: flume::Sender<Runnable>,
|
||||
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: &[],
|
||||
});
|
||||
|
||||
let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
|
||||
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) {
|
||||
|
|
|
@ -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<LinuxDispatcher>,
|
||||
main_receiver: flume::Receiver<Runnable>,
|
||||
text_system: Arc<LinuxTextSystem>,
|
||||
state: Mutex<LinuxPlatformState>,
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxPlatformState {
|
||||
quit_requested: bool,
|
||||
windows: HashMap<x::Window, Arc<LinuxWindowState>>,
|
||||
}
|
||||
|
||||
|
@ -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::<Runnable>();
|
||||
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
|
||||
|
|
|
@ -76,7 +76,7 @@ impl PlatformTextSystem for LinuxTextSystem {
|
|||
unimplemented!()
|
||||
}
|
||||
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
|
||||
unimplemented!()
|
||||
None
|
||||
}
|
||||
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||
unimplemented!()
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue