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 <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>
This commit is contained in:
Roman 2024-02-28 14:59:11 -08:00 committed by GitHub
parent 198dfe0097
commit b76e0d997e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 615 additions and 632 deletions

View file

@ -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);
match event {
wl_registry::Event::Global {
name,
interface,
version: _,
} => {
if interface.as_str() == "wl_seat" {
registry.bind::<wl_seat::WlSeat, _, _>(name, 4, qh, ());
}
"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);
}
_ => {}
};
}
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_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 => {