diff --git a/Cargo.lock b/Cargo.lock index f1a75df..0ec964f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,6 +31,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "aliasable" version = "0.1.3" @@ -801,6 +810,12 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.186" @@ -892,6 +907,15 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.8.2" @@ -911,8 +935,10 @@ dependencies = [ name = "milkyway" version = "0.1.0" dependencies = [ + "bitflags 2.13.0", "parking_lot", "smithay", + "tracing-subscriber", "wayland-protocols", "wayland-server", "winit 0.30.13", @@ -948,6 +974,15 @@ dependencies = [ "jni-sys 0.3.1", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1529,6 +1564,23 @@ dependencies = [ "bitflags 2.13.0", ] +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + [[package]] name = "reis" version = "0.7.0" @@ -1663,6 +1715,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "2.0.1" @@ -1890,6 +1951,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -1975,6 +2045,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -2013,6 +2113,12 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 9441c32..e85e71b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,5 @@ smithay = { path = "./smithay", features = ["desktop", "wayland-backend", "xwayl wayland-protocols = { version = "0.32.13", features = ["wayland-server", "server"] } wayland-server = { version = "0.31.13", features = ["dlopen"] } winit = "0.30.13" +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } +bitflags = "2.2.1" diff --git a/src/backend/mod.rs b/src/backend/mod.rs deleted file mode 100644 index 01328db..0000000 --- a/src/backend/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -use smithay::{ - backend::{ - renderer::{gles::GlesRenderer, RendererSuper}, - winit::WinitGraphicsBackend, - }, - utils::{Physical, Size}, -}; - -pub mod winit; - -pub trait Backend { - fn get_fb( - &mut self, - ) -> Result< - ( - &mut GlesRenderer, - ::Framebuffer<'_>, - ), - Box, - >; - fn bounds(&self) -> Size; -} - -impl Backend for WinitGraphicsBackend { - fn get_fb( - &mut self, - ) -> Result< - ( - &mut GlesRenderer, - ::Framebuffer<'_>, - ), - Box, - > { - return Ok(self.bind()?); - } - fn bounds(&self) -> Size { - return self.window_size(); - } -} diff --git a/src/backend/winit.rs b/src/backend/winit.rs deleted file mode 100644 index 9a9aed0..0000000 --- a/src/backend/winit.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::time::Duration; - -use smithay::backend::input::ButtonState; -use smithay::input::pointer::MotionEvent; -use smithay::reexports::calloop::EventLoop; -use smithay::reexports::winit::event_loop::pump_events::PumpStatus; -use smithay::utils::SERIAL_COUNTER; -use smithay::{ - backend::{ - input::{AbsolutePositionEvent, Event, InputEvent, KeyboardKeyEvent, PointerButtonEvent}, - renderer::gles::GlesRenderer, - winit::{self, WinitEvent}, - }, - input::keyboard::FilterResult, - reexports::wayland_server::Display, - utils::{Rectangle, Size}, -}; - -use crate::state::State; - -pub fn run_winit() -> Result<(), Box> { - let mut display: Display = Display::new()?; - let mut event_loop = EventLoop::try_new()?; - let (mut backend, mut winit) = winit::init::()?; - - let mut state = State::new(&display, &event_loop, backend.window_size())?; - state.setup_xwayland(&display)?; - - let keyboard = state - .seat - .add_keyboard(Default::default(), 600, 25) - .unwrap(); - - unsafe { std::env::set_var("WAYLAND_DISPLAY", "wayland-5") }; - std::process::Command::new("weston-terminal").spawn().ok(); - let damage = Rectangle::from_size(backend.window_size()); - - loop { - let status = winit.dispatch_new_events(|event| match event { - WinitEvent::Resized { .. } => {} - WinitEvent::Input(event) => match event { - InputEvent::Keyboard { event } => { - keyboard.input::<(), _>( - &mut state, - event.key_code(), - event.state(), - SERIAL_COUNTER.next_serial(), - event.time_msec(), - |_, _, _| { - // - FilterResult::Forward - }, - ); - } - InputEvent::PointerMotionAbsolute { event } => { - let size = backend.window_size(); - let location = event.position_transformed(Size::new(size.w, size.h)); - let locationrel = event.position_transformed(Size::new(1, 1)); - let ev = smithay::input::pointer::MotionEvent { - location, - serial: SERIAL_COUNTER.next_serial(), - time: event.time_msec(), - }; - state.motion_event(ev, locationrel); - } - InputEvent::PointerButton { event } => { - let ev = smithay::input::pointer::ButtonEvent { - serial: SERIAL_COUNTER.next_serial(), - time: event.time_msec(), - button: event.button_code(), - state: event.state(), - }; - - if event.state() == ButtonState::Pressed { - if let Some(surface) = state.focused_surface() { - { - if let Some(ref surface) = surface.surface { - keyboard.set_focus( - &mut state, - Some(surface.wl_surface().clone()), - SERIAL_COUNTER.next_serial(), - ); - } - if let Some(ref surface) = surface.xsurface { - keyboard.set_focus( - &mut state, - surface.wl_surface(), - SERIAL_COUNTER.next_serial(), - ); - } - } - { - let mut space = state.space.lock(); - space.raise_element(&surface, true); - } - } else { - keyboard.set_focus(&mut state, None, SERIAL_COUNTER.next_serial()); - } - } - - state.button_event(ev); - } - - // InputEvent::PointerAxis { event } => todo!(), - // InputEvent::DeviceAdded { device } => todo!(), - // InputEvent::DeviceRemoved { device } => todo!(), - // InputEvent::GestureSwipeBegin { event } => todo!(), - // InputEvent::GestureSwipeUpdate { event } => todo!(), - // InputEvent::GestureSwipeEnd { event } => todo!(), - // InputEvent::GesturePinchBegin { event } => todo!(), - // InputEvent::GesturePinchUpdate { event } => todo!(), - // InputEvent::GesturePinchEnd { event } => todo!(), - // InputEvent::GestureHoldBegin { event } => todo!(), - // InputEvent::GestureHoldEnd { event } => todo!(), - // InputEvent::TouchDown { event } => todo!(), - // InputEvent::TouchMotion { event } => todo!(), - // InputEvent::TouchUp { event } => todo!(), - // InputEvent::TouchCancel { event } => todo!(), - // InputEvent::TouchFrame { event } => todo!(), - // InputEvent::TabletToolAxis { event } => todo!(), - // InputEvent::TabletToolProximity { event } => todo!(), - // InputEvent::TabletToolTip { event } => todo!(), - // InputEvent::TabletToolButton { event } => todo!(), - // InputEvent::SwitchToggle { event } => todo!(), - // InputEvent::Special(_) => todo!(), - _ => {} - }, - _ => (), - }); - - match status { - PumpStatus::Continue => (), - PumpStatus::Exit(_) => return Ok(()), - }; - - state.render(&mut backend, &display)?; - - display.dispatch_clients(&mut state)?; - display.flush_clients()?; - - // It is important that all events on the display have been dispatched and flushed to clients before - // swapping buffers because this operation may block. - backend.submit(Some(&[damage]))?; - - { - let mut space = state.space.lock(); - space.refresh(); - } - - event_loop.dispatch(Duration::from_millis(1), &mut state)?; - } -} diff --git a/src/grabs/mod.rs b/src/grabs/mod.rs new file mode 100644 index 0000000..5756e1b --- /dev/null +++ b/src/grabs/mod.rs @@ -0,0 +1,5 @@ +pub mod move_grab; +pub use move_grab::MoveSurfaceGrab; + +pub mod resize_grab; +pub use resize_grab::ResizeSurfaceGrab; diff --git a/src/grabs/move_grab.rs b/src/grabs/move_grab.rs new file mode 100644 index 0000000..462eb80 --- /dev/null +++ b/src/grabs/move_grab.rs @@ -0,0 +1,160 @@ +//! Move grab is the state of a composer during which the client window is being dragged around. +//! +//! eg. Usually whenever a user clicks on the app's titlebar and starts dragging, the compositors +//! enters a MoveSurfaceGrab state. + +use crate::State; +use smithay::{ + desktop::Window, + input::pointer::{ + AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, + GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, + GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, + MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent, + }, + reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::{Logical, Point}, +}; + +pub struct MoveSurfaceGrab { + pub start_data: PointerGrabStartData, + pub window: Window, + pub initial_window_location: Point, +} + +impl PointerGrab for MoveSurfaceGrab { + fn motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(WlSurface, Point)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus + handle.motion(data, None, event); + + let delta = event.location - self.start_data.location; + let new_location = self.initial_window_location.to_f64() + delta; + data.space + .map_element(self.window.clone(), new_location.to_i32_round(), true); + } + + fn relative_motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + focus: Option<(WlSurface, Point)>, + event: &RelativeMotionEvent, + ) { + handle.relative_motion(data, focus, event); + } + + fn button( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + handle.button(data, event); + + // The button is a button code as defined in the + // Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT. + const BTN_LEFT: u32 = 0x110; + + if !handle.current_pressed().contains(&BTN_LEFT) { + // No more buttons are pressed, release the grab. + handle.unset_grab(self, data, event.serial, event.time, true); + } + } + + fn axis( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(data, details) + } + + fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) { + handle.frame(data); + } + + fn gesture_swipe_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeBeginEvent, + ) { + handle.gesture_swipe_begin(data, event) + } + + fn gesture_swipe_update( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeUpdateEvent, + ) { + handle.gesture_swipe_update(data, event) + } + + fn gesture_swipe_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeEndEvent, + ) { + handle.gesture_swipe_end(data, event) + } + + fn gesture_pinch_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchBeginEvent, + ) { + handle.gesture_pinch_begin(data, event) + } + + fn gesture_pinch_update( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchUpdateEvent, + ) { + handle.gesture_pinch_update(data, event) + } + + fn gesture_pinch_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchEndEvent, + ) { + handle.gesture_pinch_end(data, event) + } + + fn gesture_hold_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureHoldBeginEvent, + ) { + handle.gesture_hold_begin(data, event) + } + + fn gesture_hold_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureHoldEndEvent, + ) { + handle.gesture_hold_end(data, event) + } + + fn start_data(&self) -> &PointerGrabStartData { + &self.start_data + } + + fn unset(&mut self, _data: &mut State) {} +} diff --git a/src/grabs/resize_grab.rs b/src/grabs/resize_grab.rs new file mode 100644 index 0000000..209cfbb --- /dev/null +++ b/src/grabs/resize_grab.rs @@ -0,0 +1,386 @@ +//! Resize grab is the state of a composer during which the client window is being resized. +//! +//! eg. Usually whenever a user clicks on the app's border and starts dragging, the compositors +//! enters a ResizeSurfaceGrab state. + +use crate::State; +use smithay::{ + desktop::{Space, Window}, + input::pointer::{ + AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, + GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, + GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, + MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent, + }, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel, + wayland_server::protocol::wl_surface::WlSurface, + }, + utils::{Logical, Point, Rectangle, Size}, + wayland::{compositor, shell::xdg::SurfaceCachedState}, +}; +use std::cell::RefCell; + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct ResizeEdge: u32 { + const TOP = 0b0001; + const BOTTOM = 0b0010; + const LEFT = 0b0100; + const RIGHT = 0b1000; + + const TOP_LEFT = Self::TOP.bits() | Self::LEFT.bits(); + const BOTTOM_LEFT = Self::BOTTOM.bits() | Self::LEFT.bits(); + + const TOP_RIGHT = Self::TOP.bits() | Self::RIGHT.bits(); + const BOTTOM_RIGHT = Self::BOTTOM.bits() | Self::RIGHT.bits(); + } +} + +impl From for ResizeEdge { + #[inline] + fn from(x: xdg_toplevel::ResizeEdge) -> Self { + Self::from_bits(x as u32).unwrap() + } +} + +pub struct ResizeSurfaceGrab { + start_data: PointerGrabStartData, + window: Window, + + edges: ResizeEdge, + + initial_rect: Rectangle, + last_window_size: Size, +} + +impl ResizeSurfaceGrab { + pub fn start( + start_data: PointerGrabStartData, + window: Window, + edges: ResizeEdge, + initial_window_rect: Rectangle, + ) -> Self { + let initial_rect = initial_window_rect; + + if let Some(toplevel) = window.toplevel() { + ResizeSurfaceState::with(toplevel.wl_surface(), |state| { + *state = ResizeSurfaceState::Resizing { + edges, + initial_rect, + }; + }); + } + + Self { + start_data, + window, + edges, + initial_rect, + last_window_size: initial_rect.size, + } + } +} + +impl PointerGrab for ResizeSurfaceGrab { + fn motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(WlSurface, Point)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus + handle.motion(data, None, event); + + let mut delta = event.location - self.start_data.location; + + let mut new_window_width = self.initial_rect.size.w; + let mut new_window_height = self.initial_rect.size.h; + + if self.edges.intersects(ResizeEdge::LEFT | ResizeEdge::RIGHT) { + if self.edges.intersects(ResizeEdge::LEFT) { + delta.x = -delta.x; + } + + new_window_width = (self.initial_rect.size.w as f64 + delta.x) as i32; + } + + if self.edges.intersects(ResizeEdge::TOP | ResizeEdge::BOTTOM) { + if self.edges.intersects(ResizeEdge::TOP) { + delta.y = -delta.y; + } + + new_window_height = (self.initial_rect.size.h as f64 + delta.y) as i32; + } + + let (min_size, max_size) = + compositor::with_states(self.window.toplevel().unwrap().wl_surface(), |states| { + let mut guard = states.cached_state.get::(); + let data = guard.current(); + (data.min_size, data.max_size) + }); + + let min_width = min_size.w.max(1); + let min_height = min_size.h.max(1); + + let max_width = if max_size.w == 0 { + i32::MAX + } else { + max_size.w + }; + let max_height = if max_size.h == 0 { + i32::MAX + } else { + max_size.h + }; + + self.last_window_size = Size::from(( + new_window_width.max(min_width).min(max_width), + new_window_height.max(min_height).min(max_height), + )); + + let xdg = self.window.toplevel().unwrap(); + xdg.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Resizing); + state.size = Some(self.last_window_size); + }); + + xdg.send_pending_configure(); + } + + fn relative_motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + focus: Option<(WlSurface, Point)>, + event: &RelativeMotionEvent, + ) { + handle.relative_motion(data, focus, event); + } + + fn button( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + handle.button(data, event); + + // The button is a button code as defined in the + // Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT. + const BTN_LEFT: u32 = 0x110; + + if !handle.current_pressed().contains(&BTN_LEFT) { + // No more buttons are pressed, release the grab. + handle.unset_grab(self, data, event.serial, event.time, true); + + if let Some(xdg) = self.window.toplevel() { + xdg.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Resizing); + state.size = Some(self.last_window_size); + }); + + xdg.send_pending_configure(); + ResizeSurfaceState::with(xdg.wl_surface(), |state| { + *state = ResizeSurfaceState::WaitingForLastCommit { + edges: self.edges, + initial_rect: self.initial_rect, + }; + }); + } + } + } + + fn axis( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(data, details) + } + + fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) { + handle.frame(data); + } + + fn gesture_swipe_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeBeginEvent, + ) { + handle.gesture_swipe_begin(data, event) + } + + fn gesture_swipe_update( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeUpdateEvent, + ) { + handle.gesture_swipe_update(data, event) + } + + fn gesture_swipe_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeEndEvent, + ) { + handle.gesture_swipe_end(data, event) + } + + fn gesture_pinch_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchBeginEvent, + ) { + handle.gesture_pinch_begin(data, event) + } + + fn gesture_pinch_update( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchUpdateEvent, + ) { + handle.gesture_pinch_update(data, event) + } + + fn gesture_pinch_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchEndEvent, + ) { + handle.gesture_pinch_end(data, event) + } + + fn gesture_hold_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureHoldBeginEvent, + ) { + handle.gesture_hold_begin(data, event) + } + + fn gesture_hold_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureHoldEndEvent, + ) { + handle.gesture_hold_end(data, event) + } + + fn start_data(&self) -> &PointerGrabStartData { + &self.start_data + } + + fn unset(&mut self, _data: &mut State) {} +} + +/// State of the resize operation. +/// +/// It is stored inside of WlSurface, +/// and can be accessed using [`ResizeSurfaceState::with`] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] +enum ResizeSurfaceState { + #[default] + Idle, + Resizing { + edges: ResizeEdge, + /// The initial window size and location. + initial_rect: Rectangle, + }, + /// Resize is done, we are now waiting for last commit, to do the final move + WaitingForLastCommit { + edges: ResizeEdge, + /// The initial window size and location. + initial_rect: Rectangle, + }, +} + +impl ResizeSurfaceState { + fn with(surface: &WlSurface, cb: F) -> T + where + F: FnOnce(&mut Self) -> T, + { + compositor::with_states(surface, |states| { + states.data_map.insert_if_missing(RefCell::::default); + let state = states.data_map.get::>().unwrap(); + + cb(&mut state.borrow_mut()) + }) + } + + fn commit(&mut self) -> Option<(ResizeEdge, Rectangle)> { + match *self { + Self::Resizing { + edges, + initial_rect, + } => Some((edges, initial_rect)), + Self::WaitingForLastCommit { + edges, + initial_rect, + } => { + // The resize is done, let's go back to idle + *self = Self::Idle; + + Some((edges, initial_rect)) + } + Self::Idle => None, + } + } +} + +/// Should be called on `WlSurface::commit` +pub fn handle_commit(space: &mut Space, surface: &WlSurface) -> Option<()> { + let window = space + .elements() + .filter(|w| w.toplevel().is_some()) + .find(|w| w.toplevel().unwrap().wl_surface() == surface) + .cloned()?; + + let mut window_loc = space.element_location(&window)?; + let geometry = window.geometry(); + + let new_loc: Point, Logical> = ResizeSurfaceState::with(surface, |state| { + state + .commit() + .and_then(|(edges, initial_rect)| { + // If the window is being resized by top or left, its location must be adjusted + // accordingly. + edges.intersects(ResizeEdge::TOP_LEFT).then(|| { + let new_x = edges + .intersects(ResizeEdge::LEFT) + .then_some(initial_rect.loc.x + (initial_rect.size.w - geometry.size.w)); + + let new_y = edges + .intersects(ResizeEdge::TOP) + .then_some(initial_rect.loc.y + (initial_rect.size.h - geometry.size.h)); + + (new_x, new_y).into() + }) + }) + .unwrap_or_default() + }); + + if let Some(new_x) = new_loc.x { + window_loc.x = new_x; + } + if let Some(new_y) = new_loc.y { + window_loc.y = new_y; + } + + if new_loc.x.is_some() || new_loc.y.is_some() { + // If TOP or LEFT side of the window got resized, we have to move it + space.map_element(window, window_loc, false); + } + + Some(()) +} diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs new file mode 100644 index 0000000..383132f --- /dev/null +++ b/src/handlers/compositor.rs @@ -0,0 +1,64 @@ +use crate::{grabs::resize_grab, state::ClientState, state::State}; +use smithay::{ + backend::renderer::utils::on_commit_buffer_handler, + reexports::wayland_server::{ + protocol::{wl_buffer, wl_surface::WlSurface}, + Client, + }, + wayland::{ + buffer::BufferHandler, + compositor::{ + get_parent, is_sync_subsurface, CompositorClientState, CompositorHandler, + CompositorState, + }, + shm::{ShmHandler, ShmState}, + }, + xwayland::XWaylandClientData, +}; + +use super::xdg_shell; + +impl CompositorHandler for State { + fn compositor_state(&mut self) -> &mut CompositorState { + &mut self.compositor_state + } + + fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { + if let Some(state) = client.get_data::() { + &state.compositor_state + } else { + &client.get_data::().unwrap().compositor_state + } + } + + fn commit(&mut self, surface: &WlSurface) { + on_commit_buffer_handler::(surface); + if !is_sync_subsurface(surface) { + let mut root = surface.clone(); + while let Some(parent) = get_parent(&root) { + root = parent; + } + if let Some(window) = self + .space + .elements() + .filter(|w| w.toplevel().is_some()) + .find(|w| w.toplevel().unwrap().wl_surface() == &root) + { + window.on_commit(); + } + }; + + xdg_shell::handle_commit(&mut self.popups, &self.space, surface); + resize_grab::handle_commit(&mut self.space, surface); + } +} + +impl BufferHandler for State { + fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {} +} + +impl ShmHandler for State { + fn shm_state(&self) -> &ShmState { + &self.shm_state + } +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..df07d0a --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,92 @@ +mod compositor; +mod xdg_shell; + +use crate::State; + +// +// Wl Seat +// + +use smithay::input::dnd::{DnDGrab, DndGrabHandler, GrabType, Source}; +use smithay::input::pointer::Focus; +use smithay::input::{Seat, SeatHandler, SeatState}; +use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; +use smithay::reexports::wayland_server::Resource; +use smithay::utils::Serial; +use smithay::wayland::output::OutputHandler; +use smithay::wayland::selection::data_device::{ + set_data_device_focus, DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, +}; +use smithay::wayland::selection::SelectionHandler; + +impl SeatHandler for State { + type KeyboardFocus = WlSurface; + type PointerFocus = WlSurface; + type TouchFocus = WlSurface; + + fn seat_state(&mut self) -> &mut SeatState { + &mut self.seat_state + } + + fn cursor_image( + &mut self, + _seat: &Seat, + _image: smithay::input::pointer::CursorImageStatus, + ) { + } + + fn focus_changed(&mut self, seat: &Seat, focused: Option<&WlSurface>) { + let dh = &self.display_handle; + let client = focused.and_then(|s| dh.get_client(s.id()).ok()); + set_data_device_focus(dh, seat, client); + } +} + +// +// Wl Data Device +// + +impl SelectionHandler for State { + type SelectionUserData = (); +} + +impl DataDeviceHandler for State { + fn data_device_state(&mut self) -> &mut DataDeviceState { + &mut self.data_device_state + } +} + +impl DndGrabHandler for State {} +impl WaylandDndGrabHandler for State { + fn dnd_requested( + &mut self, + source: S, + _icon: Option, + seat: Seat, + serial: Serial, + type_: GrabType, + ) { + match type_ { + GrabType::Pointer => { + let ptr = seat.get_pointer().unwrap(); + let start_data = ptr.grab_start_data().unwrap(); + + // create a dnd grab to start the operation + let grab = DnDGrab::new_pointer(&self.display_handle, start_data, source, seat); + ptr.set_grab(self, grab, serial, Focus::Keep); + } + GrabType::Touch => { + // smallvil lacks touch handling + source.cancel(); + } + } + } +} + +// +// Wl Output & Xdg Output +// + +impl OutputHandler for State {} + +smithay::delegate_dispatch2!(State); diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs new file mode 100644 index 0000000..764eae4 --- /dev/null +++ b/src/handlers/xdg_shell.rs @@ -0,0 +1,225 @@ +use smithay::{ + desktop::{ + find_popup_root_surface, get_popup_toplevel_coords, PopupKind, PopupManager, Space, Window, + }, + input::{ + pointer::{Focus, GrabStartData as PointerGrabStartData}, + Seat, + }, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel, + wayland_server::{ + protocol::{wl_seat, wl_surface::WlSurface}, + Resource, + }, + }, + utils::{Rectangle, Serial}, + wayland::{ + compositor::with_states, + shell::xdg::{ + PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, + XdgToplevelSurfaceData, + }, + }, +}; + +use crate::{ + grabs::{MoveSurfaceGrab, ResizeSurfaceGrab}, + state::State, +}; + +impl XdgShellHandler for State { + fn xdg_shell_state(&mut self) -> &mut XdgShellState { + &mut self.xdg_shell_state + } + + fn new_toplevel(&mut self, surface: ToplevelSurface) { + let window = Window::new_wayland_window(surface); + self.space.map_element(window, (0, 0), false); + } + + fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) { + self.unconstrain_popup(&surface); + let _ = self.popups.track_popup(PopupKind::Xdg(surface)); + } + + fn reposition_request( + &mut self, + surface: PopupSurface, + positioner: PositionerState, + token: u32, + ) { + surface.with_pending_state(|state| { + let geometry = positioner.get_geometry(); + state.geometry = geometry; + state.positioner = positioner; + }); + self.unconstrain_popup(&surface); + surface.send_repositioned(token); + } + + fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) { + let seat = Seat::from_resource(&seat).unwrap(); + + let wl_surface = surface.wl_surface(); + + if let Some(start_data) = check_grab(&seat, wl_surface, serial) { + let pointer = seat.get_pointer().unwrap(); + + let window = self + .space + .elements() + .filter(|w| w.toplevel().is_some()) + .find(|w| w.toplevel().unwrap().wl_surface() == wl_surface) + .unwrap() + .clone(); + let initial_window_location = self.space.element_location(&window).unwrap(); + + let grab = MoveSurfaceGrab { + start_data, + window, + initial_window_location, + }; + + pointer.set_grab(self, grab, serial, Focus::Clear); + } + } + + fn resize_request( + &mut self, + surface: ToplevelSurface, + seat: wl_seat::WlSeat, + serial: Serial, + edges: xdg_toplevel::ResizeEdge, + ) { + let seat = Seat::from_resource(&seat).unwrap(); + + let wl_surface = surface.wl_surface(); + + if let Some(start_data) = check_grab(&seat, wl_surface, serial) { + let pointer = seat.get_pointer().unwrap(); + + let window = self + .space + .elements() + .filter(|w| w.toplevel().is_some()) + .find(|w| w.toplevel().unwrap().wl_surface() == wl_surface) + .unwrap() + .clone(); + let initial_window_location = self.space.element_location(&window).unwrap(); + let initial_window_size = window.geometry().size; + + surface.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Resizing); + }); + + surface.send_pending_configure(); + + let grab = ResizeSurfaceGrab::start( + start_data, + window, + edges.into(), + Rectangle::new(initial_window_location, initial_window_size), + ); + + pointer.set_grab(self, grab, serial, Focus::Clear); + } + } + + fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) { + // TODO popup grabs + } +} + +fn check_grab( + seat: &Seat, + surface: &WlSurface, + serial: Serial, +) -> Option> { + let pointer = seat.get_pointer()?; + + // Check that this surface has a click grab. + if !pointer.has_grab(serial) { + return None; + } + + let start_data = pointer.grab_start_data()?; + + let (focus, _) = start_data.focus.as_ref()?; + // If the focus was for a different surface, ignore the request. + if !focus.id().same_client_as(&surface.id()) { + return None; + } + + Some(start_data) +} + +/// Should be called on `WlSurface::commit` +pub fn handle_commit(popups: &mut PopupManager, space: &Space, surface: &WlSurface) { + // Handle toplevel commits. + if let Some(window) = space + .elements() + .filter(|w| w.toplevel().is_some()) + .find(|w| w.toplevel().unwrap().wl_surface() == surface) + .cloned() + { + let initial_configure_sent = with_states(surface, |states| { + states + .data_map + .get::() + .unwrap() + .lock() + .unwrap() + .initial_configure_sent + }); + + if !initial_configure_sent { + window.toplevel().unwrap().send_configure(); + } + } + + // Handle popup commits. + popups.commit(surface); + if let Some(popup) = popups.find_popup(surface) { + match popup { + PopupKind::Xdg(ref xdg) => { + if !xdg.is_initial_configure_sent() { + // NOTE: This should never fail as the initial configure is always + // allowed. + xdg.send_configure().expect("initial configure failed"); + } + } + PopupKind::InputMethod(ref _input_method) => {} + } + } +} + +impl State { + fn unconstrain_popup(&self, popup: &PopupSurface) { + let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else { + return; + }; + let Some(window) = self + .space + .elements() + .filter(|w| w.toplevel().is_some()) + .find(|w| w.toplevel().unwrap().wl_surface() == &root) + else { + return; + }; + + let output = self.space.outputs().next().unwrap(); + let output_geo = self.space.output_geometry(output).unwrap(); + let window_geo = self.space.element_geometry(window).unwrap(); + + // The target geometry for the positioner should be relative to its parent's geometry, so + // we will compute that here. + let mut target = output_geo; + target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone())); + target.loc -= window_geo.loc; + + popup.with_pending_state(|state| { + state.geometry = state.positioner.get_unconstrained_geometry(target); + }); + } +} diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..e0ee859 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,146 @@ +use smithay::{ + backend::input::{ + AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent, + KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, + }, + input::{ + keyboard::FilterResult, + pointer::{AxisFrame, ButtonEvent, MotionEvent}, + }, + reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::SERIAL_COUNTER, +}; + +use crate::state::State; + +impl State { + pub fn process_input_event(&mut self, event: InputEvent) { + match event { + InputEvent::Keyboard { event, .. } => { + let serial = SERIAL_COUNTER.next_serial(); + let time = Event::time_msec(&event); + + self.seat.get_keyboard().unwrap().input::<(), _>( + self, + event.key_code(), + event.state(), + serial, + time, + |_, _, _| FilterResult::Forward, + ); + } + InputEvent::PointerMotion { .. } => {} + InputEvent::PointerMotionAbsolute { event, .. } => { + let output = self.space.outputs().next().unwrap(); + + let output_geo = self.space.output_geometry(output).unwrap(); + + let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64(); + + let serial = SERIAL_COUNTER.next_serial(); + + let pointer = self.seat.get_pointer().unwrap(); + + let under = self.surface_under(pos); + + pointer.motion( + self, + under, + &MotionEvent { + location: pos, + serial, + time: event.time_msec(), + }, + ); + pointer.frame(self); + } + InputEvent::PointerButton { event, .. } => { + let pointer = self.seat.get_pointer().unwrap(); + let keyboard = self.seat.get_keyboard().unwrap(); + + let serial = SERIAL_COUNTER.next_serial(); + + let button = event.button_code(); + + let button_state = event.state(); + + if ButtonState::Pressed == button_state && !pointer.is_grabbed() { + if let Some((window, _loc)) = self + .space + .element_under(pointer.current_location()) + .map(|(w, l)| (w.clone(), l)) + { + self.space.raise_element(&window, true); + if let Some(toplevel) = window.toplevel() { + keyboard.set_focus(self, Some(toplevel.wl_surface().clone()), serial); + } + self.space.elements().for_each(|window| { + if let Some(toplevel) = window.toplevel() { + toplevel.send_pending_configure(); + } + }); + } else { + self.space.elements().for_each(|window| { + window.set_activated(false); + if let Some(toplevel) = window.toplevel() { + toplevel.send_pending_configure(); + } + }); + keyboard.set_focus(self, Option::::None, serial); + } + }; + + pointer.button( + self, + &ButtonEvent { + button, + state: button_state, + serial, + time: event.time_msec(), + }, + ); + pointer.frame(self); + } + InputEvent::PointerAxis { event, .. } => { + let source = event.source(); + + let horizontal_amount = event.amount(Axis::Horizontal).unwrap_or_else(|| { + event.amount_v120(Axis::Horizontal).unwrap_or(0.0) * 15.0 / 120. + }); + let vertical_amount = event.amount(Axis::Vertical).unwrap_or_else(|| { + event.amount_v120(Axis::Vertical).unwrap_or(0.0) * 15.0 / 120. + }); + let horizontal_amount_discrete = event.amount_v120(Axis::Horizontal); + let vertical_amount_discrete = event.amount_v120(Axis::Vertical); + + let mut frame = AxisFrame::new(event.time_msec()).source(source); + if horizontal_amount != 0.0 { + frame = frame.value(Axis::Horizontal, horizontal_amount); + if let Some(discrete) = horizontal_amount_discrete { + frame = frame.v120(Axis::Horizontal, discrete as i32); + } + } + if vertical_amount != 0.0 { + frame = frame.value(Axis::Vertical, vertical_amount); + if let Some(discrete) = vertical_amount_discrete { + frame = frame.v120(Axis::Vertical, discrete as i32); + } + } + + if source == AxisSource::Finger { + if event.amount(Axis::Horizontal) == Some(0.0) { + frame = frame.stop(Axis::Horizontal); + } + if event.amount(Axis::Vertical) == Some(0.0) { + frame = frame.stop(Axis::Vertical); + } + } + + let pointer = self.seat.get_pointer().unwrap(); + pointer.axis(self, frame); + pointer.frame(self); + } + _ => {} + } + } +} diff --git a/src/main.rs b/src/main.rs index 3543de1..ecedd53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,59 @@ -use backend::winit::run_winit; +#![allow(irrefutable_let_patterns)] -pub mod backend; -pub mod state; +mod handlers; + +mod grabs; +mod input; +mod state; +mod winit; +mod xwayland; + +use smithay::reexports::{calloop::EventLoop, wayland_server::Display, winit::event_loop}; +pub use state::State; fn main() -> Result<(), Box> { - run_winit() + init_logging(); + + let mut event_loop = EventLoop::try_new()?; + let display: Display = Display::new()?; + + let mut state = State::new(display, event_loop.handle()); + + // Open a Wayland/X11 window for our nested compositor + crate::winit::init_winit(&mut state)?; + + // Set WAYLAND_DISPLAY to our socket name, so child processes connect to Smallvil rather + // than the host compositor + unsafe { std::env::set_var("WAYLAND_DISPLAY", &state.socket_name) }; + + spawn_client(); + + event_loop.run(None, &mut state, move |_| { + // Smallvil is running + })?; + + Ok(()) +} + +fn init_logging() { + if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() { + tracing_subscriber::fmt().with_env_filter(env_filter).init(); + } else { + tracing_subscriber::fmt().init(); + } +} + +fn spawn_client() { + let mut args = std::env::args().skip(1); + let flag = args.next(); + let arg = args.next(); + + match (flag.as_deref(), arg) { + // (Some("-c") | Some("--command"), Some(command)) => { + // std::process::Command::new(command).spawn().ok(); + // } + _ => { + std::process::Command::new("weston-terminal").spawn().ok(); + } + } } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..9589c6c --- /dev/null +++ b/src/state.rs @@ -0,0 +1,188 @@ +use std::{ffi::OsString, sync::Arc, time::Duration}; + +use parking_lot::Mutex; +use smithay::{ + desktop::{PopupManager, Space, Window, WindowSurfaceType}, + input::{Seat, SeatState}, + reexports::{ + calloop::{ + generic::Generic, EventLoop, Interest, LoopHandle, LoopSignal, Mode, PostAction, + }, + wayland_server::{ + backend::{ClientData, ClientId, DisconnectReason}, + protocol::wl_surface::WlSurface, + Display, DisplayHandle, + }, + }, + utils::{Logical, Point}, + wayland::{ + compositor::{CompositorClientState, CompositorState}, + output::OutputManagerState, + selection::data_device::DataDeviceState, + shell::xdg::XdgShellState, + shm::ShmState, + socket::ListeningSocketSource, + xwayland_shell::XWaylandShellState, + }, +}; + +use crate::xwayland::XWaylandState; + +pub struct State { + pub start_time: std::time::Instant, + pub socket_name: OsString, + pub display_handle: DisplayHandle, + + pub space: Space, + pub handle: LoopHandle<'static, Self>, + // pub loop_signal: LoopSignal, + + // Smithay State + pub compositor_state: CompositorState, + pub xdg_shell_state: XdgShellState, + pub shm_state: ShmState, + pub output_manager_state: OutputManagerState, + pub seat_state: SeatState, + pub data_device_state: DataDeviceState, + pub popups: PopupManager, + + pub seat: Seat, + + pub xwayland_state: XWaylandState, +} + +impl State { + pub fn new(display: Display, handle: LoopHandle<'static, Self>) -> Self { + let start_time = std::time::Instant::now(); + + let dh = display.handle(); + + // Here we initialize implementations of some wayland protocols + // Some of them require us to implement traits on the Smallvil state, + // you can find those implementations in the `crate::handlers` module + + // Initialize protocols needed for displaying windows + let compositor_state = CompositorState::new::(&dh); + let xdg_shell_state = XdgShellState::new::(&dh); + let shm_state = ShmState::new::(&dh, vec![]); + let popups = PopupManager::default(); + + let output_manager_state = OutputManagerState::new_with_xdg_output::(&dh); + + // Data device is responsible for clipboard and drag-and-drop + let data_device_state = DataDeviceState::new::(&dh); + + // A seat is a group of keyboards, pointer and touch devices. + // A seat typically has a pointer and maintains a keyboard focus and a pointer focus. + let mut seat_state = SeatState::new(); + let mut seat: Seat = seat_state.new_wl_seat(&dh, "winit"); + + // Notify clients that we have a keyboard, for the sake of the example we assume that keyboard is always present. + // You may want to track keyboard hot-plug in real compositor. + seat.add_keyboard(Default::default(), 200, 25).unwrap(); + + // Notify clients that we have a pointer (mouse) + // Here we assume that there is always pointer plugged in + seat.add_pointer(); + + // A space represents a two-dimensional plane. Windows and Outputs can be mapped onto it. + // + // Windows get a position and stacking order through mapping. + // Outputs become views of a part of the Space and can be rendered via Space::render_output. + let space = Space::default(); + Self::setup_xwayland(display.handle(), handle.clone()); + + let socket_name = Self::init_wayland_listener(display, handle.clone()); + + let xwayland_state = XWaylandState { + xwm: None, + xwayland_shell_state: XWaylandShellState::new::(&dh), + }; + + Self { + start_time, + display_handle: dh, + + space, + socket_name, + + compositor_state, + xdg_shell_state, + shm_state, + output_manager_state, + seat_state, + data_device_state, + popups, + seat, + xwayland_state, + handle, + } + } + + fn init_wayland_listener( + display: Display, + loop_handle: LoopHandle<'static, Self>, + ) -> OsString { + // Creates a new listening socket, automatically choosing the next available `wayland` socket name. + let listening_socket = ListeningSocketSource::new_auto().unwrap(); + + // Get the name of the listening socket. + // Clients will connect to this socket. + let socket_name = listening_socket.socket_name().to_os_string(); + + loop_handle + .insert_source(listening_socket, move |client_stream, _, state| { + // Inside the callback, you should insert the client into the display. + // + // You may also associate some data with the client when inserting the client. + state + .display_handle + .insert_client(client_stream, Arc::new(ClientState::default())) + .unwrap(); + }) + .expect("Failed to init the wayland event source."); + + // You also need to add the display itself to the event loop, so that client events will be processed by wayland-server. + loop_handle + .insert_source( + Generic::new(display, Interest::READ, Mode::Level), + |_, display, state| { + // Safety: we don't drop the display + unsafe { + display.get_mut().dispatch_clients(state).unwrap(); + } + Ok(PostAction::Continue) + }, + ) + .unwrap(); + + socket_name + } + + pub fn surface_under( + &self, + pos: Point, + ) -> Option<(WlSurface, Point)> { + self.space + .element_under(pos) + .and_then(|(window, location)| { + window + .surface_under(pos - location.to_f64(), WindowSurfaceType::ALL) + .map(|(s, p)| (s, (p + location).to_f64())) + }) + } + + pub fn run(&mut self) {} +} + +/// Data associated with a wayland client that connects to Smallvil. +/// One instance of this type per client. +#[derive(Default)] +pub struct ClientState { + pub compositor_state: CompositorClientState, +} + +impl ClientData for ClientState { + fn initialized(&self, _client_id: ClientId) {} + fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {} +} diff --git a/src/state/mod.rs b/src/state/mod.rs deleted file mode 100644 index 4640003..0000000 --- a/src/state/mod.rs +++ /dev/null @@ -1,468 +0,0 @@ -pub mod xdg; -pub mod xwayland; - -use parking_lot::Mutex; -use std::{sync::Arc, time::Instant}; - -use smithay::{ - backend::{ - input::ButtonState, - renderer::{ - element::{ - surface::{render_elements_from_surface_tree, WaylandSurfaceRenderElement}, - AsRenderElements, Kind, - }, - gles::GlesRenderer, - utils::{draw_render_elements, on_commit_buffer_handler}, - Color32F, Frame, Renderer, - }, - }, - delegate_dispatch2, - desktop::{ - layer_map_for_output, space::SpaceElement, LayerSurface, Space, Window, WindowSurfaceType, - }, - input::{ - dnd::DndGrabHandler, - pointer::{ButtonEvent, Focus, GrabStartData, MotionEvent, PointerHandle}, - Seat, SeatHandler, SeatState, - }, - output::{Mode, Output, PhysicalProperties, Subpixel}, - reexports::{ - calloop::{EventLoop, LoopHandle}, - rustix::fs::Stat, - wayland_server::Display, - }, - utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Size, Transform}, - wayland::{ - buffer::BufferHandler, - compositor::{ - with_surface_tree_downward, CompositorClientState, CompositorHandler, CompositorState, - SurfaceAttributes, TraversalAction, - }, - output::OutputHandler, - selection::{ - data_device::{DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler}, - SelectionHandler, - }, - shell::xdg::{ToplevelSurface, XdgShellHandler, XdgShellState}, - shm::{ShmHandler, ShmState}, - xwayland_shell::XWaylandShellState, - }, - xwayland::{X11Surface, XWaylandClientData}, -}; -use wayland_server::{ - backend::{ClientData, ClientId, DisconnectReason}, - protocol::{ - wl_buffer, - wl_surface::{self, WlSurface}, - }, - Client, ListeningSocket, -}; - -use crate::state::{xdg::MoveGrab, xwayland::XWaylandState}; - -pub struct State { - output: Output, - pub seat: Seat, - - pub space: Mutex>, - clients: Mutex>, - - pub pointer_location: Point, - - start_time: Instant, - listener: ListeningSocket, - - pub event_loop_handle: LoopHandle<'static, Self>, - pub xwayland_state: XWaylandState, - - pointer: Arc>, - - start_data: GrabStartData, - - compositor_state: CompositorState, - xdg_shell_state: XdgShellState, - shm_state: ShmState, - seat_state: SeatState, - data_device_state: DataDeviceState, -} - -impl State { - pub fn new( - display: &Display, - event_loop: &EventLoop<'static, Self>, - size: Size, - ) -> Result> { - let dh = display.handle(); - - let compositor_state = CompositorState::new::(&dh); - let shm_state = ShmState::new::(&dh, vec![]); - let mut seat_state = SeatState::new(); - let mut seat = seat_state.new_wl_seat(&dh, "milkyway_seat"); - let mut space = Space::default(); - let listener = ListeningSocket::bind("wayland-5").unwrap(); - - let event_loop_handle = event_loop.handle(); - - let pointer = Arc::new(seat.add_pointer()); - - let mode = Mode { - size, - refresh: 60_000, - }; - let output = Output::new( - "winit".to_string(), - PhysicalProperties { - size: (0, 0).into(), - subpixel: Subpixel::Unknown, - make: "Smithay".into(), - model: "Winit".into(), - serial_number: "Unknown".into(), - }, - ); - let _global = output.create_global::(&dh); - output.change_current_state( - Some(mode), - Some(Transform::Flipped180), - None, - Some((0, 0).into()), - ); - output.set_preferred(mode); - - space.map_output(&output, (0, 0)); - - Ok(State { - output, - compositor_state, - xdg_shell_state: XdgShellState::new::(&dh), - shm_state, - seat_state, - data_device_state: DataDeviceState::new::(&dh), - seat, - start_time: Instant::now(), - listener, - clients: Mutex::new(vec![]), - space: Mutex::new(space), - pointer, - pointer_location: Point::new(0.0, 0.0), - event_loop_handle, - xwayland_state: XWaylandState { - xwm: None, - xwayland_shell_state: XWaylandShellState::new::(&dh), - }, - start_data: GrabStartData { - focus: None, - button: 0, - location: Point::new(0.0, 0.0), - }, - }) - } - - pub fn render( - &self, - backend: &mut A, - display: &Display, - ) -> Result<(), Box> - where - A: crate::backend::Backend, - { - let space = self.space.lock(); - let size = backend.bounds(); - let damage = Rectangle::from_size(size); - let (renderer, mut framebuffer) = backend.get_fb().unwrap(); - - let mut elements = space - .elements() - .flat_map(|element| { - let loc = space.element_location(element).unwrap_or(Point::new(0, 0)); - let position = - loc.to_physical_precise_round(self.output.current_scale().fractional_scale()); - element - .window - .render_elements(renderer, position, Scale { x: 1.0, y: 1.0 }, 1.0) - }) - .collect::>>(); - - elements.reverse(); - - let mut frame = renderer - .render(&mut framebuffer, size, Transform::Flipped180) - .unwrap(); - frame - .clear(Color32F::new(0.1, 0.0, 0.0, 1.0), &[damage]) - .unwrap(); - - draw_render_elements(&mut frame, 1.0, &elements, &[damage]).unwrap(); - // We rely on the nested compositor to do the sync for us - let _ = frame.finish().unwrap(); - - for surface in self.xdg_shell_state.toplevel_surfaces() { - self.send_frames_surface_tree(surface.wl_surface()); - } - - if let Some(stream) = self.listener.accept()? { - let client = display - .handle() - .insert_client(stream, Arc::new(ClientState::default())) - .unwrap(); - let mut clients = self.clients.lock(); - clients.push(client); - } - - Ok(()) - } - - // pub fn focused_surface(&mut self) -> Option { - // let space = self.space.lock(); - // // let mut surfaces = vec![]; - // let mut elements = space.elements().collect::>(); - // elements.reverse(); - // for window in elements { - // if window.is_in_input_region(&self.pointer_location) { - // return Some(window.clone()); - // } - // } - - // None - // } - - pub fn button_event(&mut self, ev: ButtonEvent) { - let pointer = self.pointer.clone(); - pointer.button(self, &ev); - pointer.frame(self); - } - - pub fn surface_under( - &self, - pos: Point, - ) -> Option<(WlSurface, Point)> { - self.space - .lock() - .element_under(pos) - .and_then(|(window, location)| { - window - .window - .surface_under(pos - location.to_f64(), WindowSurfaceType::ALL) - .map(|(s, p)| (s, (p + location).to_f64())) - }) - } - - pub fn focused_surface(&mut self) -> Option { - let space = self.space.lock(); - // let mut surfaces = vec![]; - let mut elements = space.elements().collect::>(); - elements.reverse(); - for window in elements { - if window.is_in_input_region(&self.pointer_location) { - return Some(window.clone()); - } - } - - None - // self.space - // .lock() - // .element_under(self.pointer_location) - // .and_then(|(window, _location)| Some(window.clone())) - } - - pub fn motion_event(&mut self, ev: MotionEvent, locationrel: Point) { - let pointer = self.pointer.clone(); - self.pointer_location = ev.location; - if let Some(surface) = self.focused_surface() { - if let Some(surface) = surface.surface { - pointer.motion(self, Some((surface.wl_surface().clone(), locationrel)), &ev); - } - if let Some(surface) = surface.xsurface { - if let Some(surface) = surface.wl_surface() { - pointer.motion(self, Some((surface, locationrel)), &ev); - } - } - pointer.frame(self); - } else { - pointer.motion(self, None, &ev); - pointer.frame(self); - } - } - - fn send_frames_surface_tree(&self, surface: &wl_surface::WlSurface) { - let time = self.start_time.elapsed().as_millis() as u32; - - with_surface_tree_downward( - surface, - (), - |_, _, &()| TraversalAction::DoChildren(()), - |_surf, states, &()| { - // the surface may not have any user_data if it is a subsurface and has not - // yet been committed - for callback in states - .cached_state - .get::() - .current() - .frame_callbacks - .drain(..) - { - callback.done(time); - } - }, - |_, _, &()| true, - ); - } -} - -impl BufferHandler for State { - fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {} -} - -impl SelectionHandler for State { - type SelectionUserData = (); -} -impl WaylandDndGrabHandler for State {} - -impl DataDeviceHandler for State { - fn data_device_state(&mut self) -> &mut DataDeviceState { - &mut self.data_device_state - } -} - -impl CompositorHandler for State { - fn compositor_state(&mut self) -> &mut CompositorState { - &mut self.compositor_state - } - - fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { - if let Some(state) = client.get_data::() { - return &state.compositor_state; - } - if let Some(state) = client.get_data::() { - return &state.compositor_state; - } - panic!("unknown client state type?"); - } - - fn commit(&mut self, surface: &WlSurface) { - on_commit_buffer_handler::(surface); - } -} - -impl ShmHandler for State { - fn shm_state(&self) -> &ShmState { - &self.shm_state - } -} - -impl SeatHandler for State { - type KeyboardFocus = WlSurface; - type PointerFocus = WlSurface; - type TouchFocus = WlSurface; - - fn seat_state(&mut self) -> &mut SeatState { - &mut self.seat_state - } - - fn focus_changed(&mut self, _seat: &Seat, _focused: Option<&WlSurface>) {} - fn cursor_image( - &mut self, - _seat: &Seat, - _image: smithay::input::pointer::CursorImageStatus, - ) { - } -} - -impl DndGrabHandler for State {} - -#[derive(PartialEq, Clone)] -pub struct AppWrapper { - pub surface: Option, - pub xsurface: Option, - pub window: Window, -} - -impl AppWrapper { - pub fn new_wayland(surface: ToplevelSurface) -> Self { - let window = Window::new_wayland_window(surface.clone()); - Self { - surface: Some(surface), - xsurface: None, - window, - } - } - pub fn new_x11(xsurface: X11Surface) -> Self { - let window = Window::new_x11_window(xsurface.clone()); - Self { - surface: None, - xsurface: Some(xsurface), - window, - } - } - - pub fn from_window(window: Window) -> Self { - if let Some(xsurface) = window.x11_surface() { - Self { - surface: None, - xsurface: Some(xsurface.clone()), - window, - } - } else if let Some(surface) = window.toplevel() { - Self { - surface: Some(surface.clone()), - xsurface: None, - window, - } - } else { - panic!(); - } - } -} - -impl SpaceElement for AppWrapper { - fn bbox(&self) -> Rectangle { - return SpaceElement::bbox(&self.window); - } - - fn is_in_input_region( - &self, - point: &smithay::utils::Point, - ) -> bool { - return self.window.is_in_input_region(point); - } - - fn set_activate(&self, activated: bool) { - return self.window.set_activate(activated); - } - - fn output_enter( - &self, - output: &smithay::output::Output, - overlap: Rectangle, - ) { - return self.window.output_enter(output, overlap); - } - - fn output_leave(&self, output: &smithay::output::Output) { - return self.window.output_leave(output); - } -} - -impl IsAlive for AppWrapper { - fn alive(&self) -> bool { - self.window.alive() - } -} -#[derive(Default)] -pub struct ClientState { - compositor_state: CompositorClientState, -} -impl ClientData for ClientState { - fn initialized(&self, _client_id: ClientId) { - println!("initialized"); - } - - fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) { - println!("disconnected"); - } -} - -impl OutputHandler for State {} - -// Macros used to delegate protocol handling to types in the app state. -delegate_dispatch2!(State); diff --git a/src/state/xdg.rs b/src/state/xdg.rs deleted file mode 100644 index 17e7b7d..0000000 --- a/src/state/xdg.rs +++ /dev/null @@ -1,283 +0,0 @@ -use smithay::desktop::Window; -use smithay::input::pointer::{ - AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent, - GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, - GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData, MotionEvent, PointerGrab, - PointerInnerHandle, RelativeMotionEvent, -}; -use smithay::input::SeatHandler; -use smithay::utils::{Logical, Point}; -use smithay::wayland::shell::xdg::PopupSurface; -use smithay::{ - utils::Serial, - wayland::shell::xdg::{PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState}, -}; -use wayland_protocols::xdg::shell::server::xdg_toplevel; -use wayland_server::protocol::{wl_output, wl_seat}; - -use super::{AppWrapper, State}; - -impl XdgShellHandler for State { - fn xdg_shell_state(&mut self) -> &mut XdgShellState { - &mut self.xdg_shell_state - } - - fn new_toplevel(&mut self, surface: ToplevelSurface) { - surface.with_pending_state(|state| { - state.states.set(xdg_toplevel::State::Activated); - }); - surface.send_configure(); - - let mut space = self.space.lock(); - - space.map_element(AppWrapper::new_wayland(surface), (0, 0), true); - } - - fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) { - // Handle popup creation here - } - - fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) { - // Handle popup grab here - } - - fn reposition_request( - &mut self, - _surface: PopupSurface, - _positioner: PositionerState, - _token: u32, - ) { - // Handle popup reposition here - } - - fn move_request(&mut self, wl_surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) { - let window = { - let space = self.space.lock(); - space - .elements() - .find(|w| { - if let Some(surface) = w.surface.clone() { - surface == wl_surface - } else { - false - } - }) - .unwrap() - .clone() - }; - let pointer = self.pointer.clone(); - let loc = { - let space = self.space.lock(); - space.element_location(&window).unwrap() - }; - - let start_data = self.pointer.grab_start_data().unwrap(); - - pointer.set_grab( - self, - MoveGrab { - start_data, - window: window.window.clone(), - initial_window_location: loc, - app: window, - }, - serial, - Focus::Clear, - ); - } - - fn resize_request( - &mut self, - surface: ToplevelSurface, - seat: wl_seat::WlSeat, - serial: Serial, - edges: xdg_toplevel::ResizeEdge, - ) { - // println!( - // "resize {:?}", - // self.pointer_location_og - self.pointer_location - // ); - } - - // fn maximize_request(&mut self, surface: ToplevelSurface) { - // surface.send_configure(); - // } - - // fn unmaximize_request(&mut self, surface: ToplevelSurface) {} - - // fn fullscreen_request( - // &mut self, - // surface: ToplevelSurface, - // output: Option, - // ) { - // surface.send_configure(); - // } - - // fn unfullscreen_request(&mut self, surface: ToplevelSurface) {} - - // fn minimize_request(&mut self, surface: ToplevelSurface) {} - - // fn show_window_menu( - // &mut self, - // surface: ToplevelSurface, - // seat: wl_seat::WlSeat, - // serial: Serial, - // location: smithay::utils::Point, - // ) { - // } - - // fn client_destroyed(&mut self, client: smithay::wayland::shell::xdg::ShellClient) {} - - // fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {} - - // fn popup_destroyed(&mut self, surface: PopupSurface) {} - - // fn app_id_changed(&mut self, surface: ToplevelSurface) {} - - // fn title_changed(&mut self, surface: ToplevelSurface) {} - - // fn parent_changed(&mut self, surface: ToplevelSurface) {} -} - -pub struct MoveGrab { - pub start_data: GrabStartData, - pub window: Window, - pub initial_window_location: Point, - pub app: AppWrapper, -} - -impl PointerGrab for MoveGrab { - fn motion( - &mut self, - state: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - focus: Option<(::PointerFocus, Point)>, - event: &MotionEvent, - ) { - handle.motion(state, None, event); - let delta = event.location - self.start_data.location; - let new_location = self.initial_window_location.to_f64() + delta; - - let mut space = state.space.lock(); - space.map_element(self.app.clone(), new_location.to_i32_round(), true); - } - - fn relative_motion( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - focus: Option<(::PointerFocus, Point)>, - event: &RelativeMotionEvent, - ) { - handle.relative_motion(data, focus, event); - } - - fn button( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &ButtonEvent, - ) { - handle.button(data, event); - - // The button is a button code as defined in the - // Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT. - const BTN_LEFT: u32 = 0x110; - - if !handle.current_pressed().contains(&BTN_LEFT) { - // No more buttons are pressed, release the grab. - handle.unset_grab(self, data, event.serial, event.time, true); - } - } - - fn axis( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - details: AxisFrame, - ) { - handle.axis(data, details) - } - - fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) { - handle.frame(data); - } - - fn gesture_swipe_begin( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &GestureSwipeBeginEvent, - ) { - handle.gesture_swipe_begin(data, event); - } - - fn gesture_swipe_update( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &GestureSwipeUpdateEvent, - ) { - handle.gesture_swipe_update(data, event); - } - - fn gesture_swipe_end( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &GestureSwipeEndEvent, - ) { - handle.gesture_swipe_end(data, event); - } - - fn gesture_pinch_begin( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &GesturePinchBeginEvent, - ) { - handle.gesture_pinch_begin(data, event); - } - - fn gesture_pinch_update( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &GesturePinchUpdateEvent, - ) { - handle.gesture_pinch_update(data, event); - } - - fn gesture_pinch_end( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &GesturePinchEndEvent, - ) { - handle.gesture_pinch_end(data, event); - } - - fn gesture_hold_begin( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &GestureHoldBeginEvent, - ) { - handle.gesture_hold_begin(data, event); - } - - fn gesture_hold_end( - &mut self, - data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &GestureHoldEndEvent, - ) { - handle.gesture_hold_end(data, event); - } - - fn start_data(&self) -> &GrabStartData { - &self.start_data - } - - fn unset(&mut self, data: &mut State) {} -} diff --git a/src/winit.rs b/src/winit.rs new file mode 100644 index 0000000..650a83c --- /dev/null +++ b/src/winit.rs @@ -0,0 +1,114 @@ +use std::time::Duration; + +use smithay::{ + backend::{ + renderer::{ + damage::OutputDamageTracker, element::surface::WaylandSurfaceRenderElement, + gles::GlesRenderer, + }, + winit::{self, WinitEvent}, + }, + output::{Mode, Output, PhysicalProperties, Subpixel}, + reexports::calloop::EventLoop, + utils::{Rectangle, Transform}, +}; + +use crate::State; + +pub fn init_winit(state: &mut State) -> Result<(), Box> { + let (mut backend, winit) = winit::init()?; + + let mode = Mode { + size: backend.window_size(), + refresh: 60_000, + }; + + let output = Output::new( + "winit".to_string(), + PhysicalProperties { + size: (0, 0).into(), + subpixel: Subpixel::Unknown, + make: "Milkyway".into(), + model: "Winit".into(), + serial_number: "Unknown".into(), + }, + ); + let _global = output.create_global::(&state.display_handle); + output.change_current_state( + Some(mode), + Some(Transform::Flipped180), + None, + Some((0, 0).into()), + ); + output.set_preferred(mode); + + state.space.map_output(&output, (0, 0)); + + let mut damage_tracker = OutputDamageTracker::from_output(&output); + + state.handle.insert_source(winit, move |event, _, state| { + match event { + WinitEvent::Resized { size, .. } => { + output.change_current_state( + Some(Mode { + size, + refresh: 60_000, + }), + None, + None, + None, + ); + } + WinitEvent::Input(event) => state.process_input_event(event), + WinitEvent::Redraw => { + let size = backend.window_size(); + let damage = Rectangle::from_size(size); + + { + let (renderer, mut framebuffer) = backend.bind().unwrap(); + smithay::desktop::space::render_output::< + _, + WaylandSurfaceRenderElement, + _, + _, + >( + &output, + renderer, + &mut framebuffer, + 1.0, + 0, + [&state.space], + &[], + &mut damage_tracker, + [0.07, 0.16, 0.286, 1.0], + ) + .unwrap(); + } + backend.submit(Some(&[damage])).unwrap(); + + state.space.elements().for_each(|window| { + window.send_frame( + &output, + state.start_time.elapsed(), + Some(Duration::ZERO), + |_, _| Some(output.clone()), + ) + }); + + state.space.refresh(); + state.popups.cleanup(); + let _ = state.display_handle.flush_clients(); + + // Ask for redraw to schedule new frame. + backend.window().request_redraw(); + } + WinitEvent::CloseRequested => { + std::process::exit(0); + // state.loop_signal.stop(); + } + _ => (), + }; + })?; + + Ok(()) +} diff --git a/src/state/xwayland.rs b/src/xwayland.rs similarity index 84% rename from src/state/xwayland.rs rename to src/xwayland.rs index f7804cf..f7429c1 100644 --- a/src/state/xwayland.rs +++ b/src/xwayland.rs @@ -1,7 +1,8 @@ use std::process::Stdio; use smithay::{ - delegate_dispatch2, + desktop::Window, + reexports::calloop::{EventLoop, LoopHandle}, utils::{Logical, Rectangle}, wayland::xwayland_shell::{XWaylandShellHandler, XWaylandShellState}, xwayland::{ @@ -9,9 +10,9 @@ use smithay::{ X11Surface, X11Wm, XWayland, XWaylandEvent, XwmHandler, }, }; -use wayland_server::Display; +use wayland_server::{Display, DisplayHandle}; -use crate::state::{AppWrapper, State}; +use crate::state::State; pub struct XWaylandState { pub xwm: Option, @@ -30,9 +31,8 @@ impl XwmHandler for State { // grant it — the window will be mapped once you set this window.set_mapped(true).unwrap(); - let mut space = self.space.lock(); - - space.map_element(AppWrapper::new_x11(window), (0, 0), true); + self.space + .map_element(Window::new_x11_window(window.clone()), (0, 0), true); // insert it into your Space/window management here } @@ -89,10 +89,9 @@ impl XWaylandShellHandler for State { impl State { pub fn setup_xwayland( - &mut self, - display: &Display, + dh: DisplayHandle, + loop_handle: LoopHandle<'static, Self>, ) -> Result<(), Box> { - let dh = display.handle(); let (xwayland, client) = XWayland::spawn( &dh, None, @@ -104,15 +103,13 @@ impl State { |_| {}, )?; - let event_loop_handle = self.event_loop_handle.clone(); - event_loop_handle.insert_source(xwayland, move |event, _, state| { + loop_handle.insert_source(xwayland, move |event, _, state| { match event { XWaylandEvent::Ready { x11_socket, display_number, } => { - let event_loop = state.event_loop_handle.clone(); - let wm = X11Wm::start_wm(event_loop, &dh, x11_socket, client.clone()) + let wm = X11Wm::start_wm(state.handle.clone(), &dh, x11_socket, client.clone()) .expect("failed to start X11 wm"); state.xwayland_state.xwm = Some(wm);