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:
Dzmitry Malyshau 2024-02-04 23:52:22 -08:00
parent 282cc71df9
commit 521b2b12e4
4 changed files with 80 additions and 39 deletions

View file

@ -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) {

View file

@ -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

View file

@ -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!()

View file

@ -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) {