diff --git a/README.md b/README.md index 1735724..e02a95d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Ferese Ferese is an experimental Wayland tiling compositor written in Rust with -Smithay. Development currently targets the Phase 0 nested compositor described -in [spec.md](spec.md). +Smithay. The nested compositor is complete and development currently targets +the M1 native protocol and interaction milestone in [spec.md](spec.md). ## Development @@ -20,4 +20,3 @@ cargo run -p ferese -- foot ``` Set `RUST_LOG=ferese=debug` for detailed compositor logging. - diff --git a/compositor/src/grabs/mod.rs b/compositor/src/grabs/mod.rs new file mode 100644 index 0000000..e382919 --- /dev/null +++ b/compositor/src/grabs/mod.rs @@ -0,0 +1,5 @@ +mod move_grab; +mod resize_grab; + +pub use move_grab::MoveSurfaceGrab; +pub use resize_grab::{ResizeEdge, ResizeSurfaceGrab, handle_resize_commit}; diff --git a/compositor/src/grabs/move_grab.rs b/compositor/src/grabs/move_grab.rs new file mode 100644 index 0000000..d3abf6f --- /dev/null +++ b/compositor/src/grabs/move_grab.rs @@ -0,0 +1,147 @@ +use smithay::{ + desktop::Window, + input::pointer::{ + AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, + GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, + GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData, MotionEvent, PointerGrab, + PointerInnerHandle, RelativeMotionEvent, + }, + reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::{Logical, Point}, +}; + +use crate::Ferese; + +pub struct MoveSurfaceGrab { + pub start_data: GrabStartData, + pub window: Window, + pub initial_location: Point, +} + +impl PointerGrab for MoveSurfaceGrab { + fn motion( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + _focus: Option<(WlSurface, Point)>, + event: &MotionEvent, + ) { + handle.motion(data, None, event); + let location = self.initial_location.to_f64() + (event.location - self.start_data.location); + data.space + .map_element(self.window.clone(), location.to_i32_round(), true); + } + + fn relative_motion( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + focus: Option<(WlSurface, Point)>, + event: &RelativeMotionEvent, + ) { + handle.relative_motion(data, focus, event); + } + + fn button( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &ButtonEvent, + ) { + handle.button(data, event); + if handle.current_pressed().is_empty() { + handle.unset_grab(self, data, event.serial, event.time, true); + } + } + + fn axis( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + frame: AxisFrame, + ) { + handle.axis(data, frame); + } + + fn frame(&mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>) { + handle.frame(data); + } + + fn gesture_swipe_begin( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GestureSwipeBeginEvent, + ) { + handle.gesture_swipe_begin(data, event); + } + + fn gesture_swipe_update( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GestureSwipeUpdateEvent, + ) { + handle.gesture_swipe_update(data, event); + } + + fn gesture_swipe_end( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GestureSwipeEndEvent, + ) { + handle.gesture_swipe_end(data, event); + } + + fn gesture_pinch_begin( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GesturePinchBeginEvent, + ) { + handle.gesture_pinch_begin(data, event); + } + + fn gesture_pinch_update( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GesturePinchUpdateEvent, + ) { + handle.gesture_pinch_update(data, event); + } + + fn gesture_pinch_end( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GesturePinchEndEvent, + ) { + handle.gesture_pinch_end(data, event); + } + + fn gesture_hold_begin( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GestureHoldBeginEvent, + ) { + handle.gesture_hold_begin(data, event); + } + + fn gesture_hold_end( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GestureHoldEndEvent, + ) { + handle.gesture_hold_end(data, event); + } + + fn start_data(&self) -> &GrabStartData { + &self.start_data + } + + fn unset(&mut self, _data: &mut Ferese) {} +} diff --git a/compositor/src/grabs/resize_grab.rs b/compositor/src/grabs/resize_grab.rs new file mode 100644 index 0000000..2617764 --- /dev/null +++ b/compositor/src/grabs/resize_grab.rs @@ -0,0 +1,401 @@ +use std::cell::RefCell; + +use smithay::{ + desktop::{Space, Window}, + input::pointer::{ + AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, + GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, + GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData, 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::with_states, shell::xdg::SurfaceCachedState}, +}; + +use crate::Ferese; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ResizeEdge(xdg_toplevel::ResizeEdge); + +impl From for ResizeEdge { + fn from(edge: xdg_toplevel::ResizeEdge) -> Self { + Self(edge) + } +} + +impl ResizeEdge { + fn left(self) -> bool { + matches!( + self.0, + xdg_toplevel::ResizeEdge::Left + | xdg_toplevel::ResizeEdge::TopLeft + | xdg_toplevel::ResizeEdge::BottomLeft + ) + } + + fn right(self) -> bool { + matches!( + self.0, + xdg_toplevel::ResizeEdge::Right + | xdg_toplevel::ResizeEdge::TopRight + | xdg_toplevel::ResizeEdge::BottomRight + ) + } + + fn top(self) -> bool { + matches!( + self.0, + xdg_toplevel::ResizeEdge::Top + | xdg_toplevel::ResizeEdge::TopLeft + | xdg_toplevel::ResizeEdge::TopRight + ) + } + + fn bottom(self) -> bool { + matches!( + self.0, + xdg_toplevel::ResizeEdge::Bottom + | xdg_toplevel::ResizeEdge::BottomLeft + | xdg_toplevel::ResizeEdge::BottomRight + ) + } +} + +pub struct ResizeSurfaceGrab { + start_data: GrabStartData, + window: Window, + edges: ResizeEdge, + initial_rect: Rectangle, + last_size: Size, +} + +impl ResizeSurfaceGrab { + pub fn new( + start_data: GrabStartData, + window: Window, + edges: ResizeEdge, + initial_rect: Rectangle, + ) -> Self { + ResizeState::with( + window + .toplevel() + .expect("managed window has a toplevel") + .wl_surface(), + |state| { + *state = ResizeState::Resizing { + edges, + initial_rect, + } + }, + ); + Self { + start_data, + window, + edges, + initial_rect, + last_size: initial_rect.size, + } + } +} + +impl PointerGrab for ResizeSurfaceGrab { + fn motion( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + _focus: Option<(WlSurface, Point)>, + event: &MotionEvent, + ) { + handle.motion(data, None, event); + let delta = event.location - self.start_data.location; + let surface = self + .window + .toplevel() + .expect("managed window has a toplevel"); + let (minimum, maximum) = with_states(surface.wl_surface(), |states| { + let mut cached = states.cached_state.get::(); + let state = cached.current(); + (state.min_size, state.max_size) + }); + self.last_size = + constrained_size(self.initial_rect.size, delta, self.edges, minimum, maximum); + + surface.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Resizing); + state.size = Some(self.last_size); + }); + surface.send_pending_configure(); + } + + fn relative_motion( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + focus: Option<(WlSurface, Point)>, + event: &RelativeMotionEvent, + ) { + handle.relative_motion(data, focus, event); + } + + fn button( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &ButtonEvent, + ) { + handle.button(data, event); + if handle.current_pressed().is_empty() { + handle.unset_grab(self, data, event.serial, event.time, true); + let surface = self + .window + .toplevel() + .expect("managed window has a toplevel"); + surface.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Resizing); + state.size = Some(self.last_size); + }); + surface.send_pending_configure(); + ResizeState::with(surface.wl_surface(), |state| { + *state = ResizeState::WaitingForFinalCommit { + edges: self.edges, + initial_rect: self.initial_rect, + }; + }); + } + } + + fn axis( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + frame: AxisFrame, + ) { + handle.axis(data, frame); + } + + fn frame(&mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>) { + handle.frame(data); + } + + fn gesture_swipe_begin( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GestureSwipeBeginEvent, + ) { + handle.gesture_swipe_begin(data, event); + } + fn gesture_swipe_update( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GestureSwipeUpdateEvent, + ) { + handle.gesture_swipe_update(data, event); + } + fn gesture_swipe_end( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GestureSwipeEndEvent, + ) { + handle.gesture_swipe_end(data, event); + } + fn gesture_pinch_begin( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GesturePinchBeginEvent, + ) { + handle.gesture_pinch_begin(data, event); + } + fn gesture_pinch_update( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GesturePinchUpdateEvent, + ) { + handle.gesture_pinch_update(data, event); + } + fn gesture_pinch_end( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GesturePinchEndEvent, + ) { + handle.gesture_pinch_end(data, event); + } + fn gesture_hold_begin( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GestureHoldBeginEvent, + ) { + handle.gesture_hold_begin(data, event); + } + fn gesture_hold_end( + &mut self, + data: &mut Ferese, + handle: &mut PointerInnerHandle<'_, Ferese>, + event: &GestureHoldEndEvent, + ) { + handle.gesture_hold_end(data, event); + } + + fn start_data(&self) -> &GrabStartData { + &self.start_data + } + + fn unset(&mut self, _data: &mut Ferese) {} +} + +#[derive(Clone, Copy, Debug, Default)] +enum ResizeState { + #[default] + Idle, + Resizing { + edges: ResizeEdge, + initial_rect: Rectangle, + }, + WaitingForFinalCommit { + edges: ResizeEdge, + initial_rect: Rectangle, + }, +} + +impl ResizeState { + fn with(surface: &WlSurface, callback: impl FnOnce(&mut Self) -> T) -> T { + with_states(surface, |states| { + states.data_map.insert_if_missing(RefCell::::default); + callback( + &mut states + .data_map + .get::>() + .expect("resize state was inserted") + .borrow_mut(), + ) + }) + } + + fn commit(&mut self) -> Option<(ResizeEdge, Rectangle)> { + match *self { + Self::Idle => None, + Self::Resizing { + edges, + initial_rect, + } => Some((edges, initial_rect)), + Self::WaitingForFinalCommit { + edges, + initial_rect, + } => { + *self = Self::Idle; + Some((edges, initial_rect)) + } + } + } +} + +pub fn handle_resize_commit(space: &mut Space, surface: &WlSurface) { + let Some(window) = space + .elements() + .find(|window| { + window + .toplevel() + .is_some_and(|toplevel| toplevel.wl_surface() == surface) + }) + .cloned() + else { + return; + }; + let Some(mut location) = space.element_location(&window) else { + return; + }; + let geometry = window.geometry(); + let Some((edges, initial_rect)) = ResizeState::with(surface, ResizeState::commit) else { + return; + }; + + if edges.left() { + location.x = initial_rect.loc.x + initial_rect.size.w - geometry.size.w; + } + if edges.top() { + location.y = initial_rect.loc.y + initial_rect.size.h - geometry.size.h; + } + if edges.left() || edges.top() { + space.map_element(window, location, false); + } +} + +fn constrained_size( + initial: Size, + delta: Point, + edges: ResizeEdge, + minimum: Size, + maximum: Size, +) -> Size { + let mut width = initial.w; + let mut height = initial.h; + if edges.left() { + width -= delta.x as i32; + } else if edges.right() { + width += delta.x as i32; + } + if edges.top() { + height -= delta.y as i32; + } else if edges.bottom() { + height += delta.y as i32; + } + + let maximum_width = if maximum.w == 0 { i32::MAX } else { maximum.w }; + let maximum_height = if maximum.h == 0 { i32::MAX } else { maximum.h }; + ( + width.clamp(minimum.w.max(1), maximum_width), + height.clamp(minimum.h.max(1), maximum_height), + ) + .into() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn left_and_top_edges_invert_pointer_delta() { + let size = constrained_size( + (800, 600).into(), + (100.0, 50.0).into(), + ResizeEdge(xdg_toplevel::ResizeEdge::TopLeft), + (1, 1).into(), + (0, 0).into(), + ); + assert_eq!(size, (700, 550).into()); + } + + #[test] + fn right_and_bottom_edges_follow_pointer_delta() { + let size = constrained_size( + (800, 600).into(), + (100.0, 50.0).into(), + ResizeEdge(xdg_toplevel::ResizeEdge::BottomRight), + (1, 1).into(), + (0, 0).into(), + ); + assert_eq!(size, (900, 650).into()); + } + + #[test] + fn client_constraints_clamp_interactive_size() { + let size = constrained_size( + (800, 600).into(), + (1_000.0, 1_000.0).into(), + ResizeEdge(xdg_toplevel::ResizeEdge::BottomRight), + (640, 480).into(), + (1_024, 768).into(), + ); + assert_eq!(size, (1_024, 768).into()); + } +} diff --git a/compositor/src/handlers/compositor.rs b/compositor/src/handlers/compositor.rs index e852804..bc86224 100644 --- a/compositor/src/handlers/compositor.rs +++ b/compositor/src/handlers/compositor.rs @@ -44,7 +44,7 @@ impl CompositorHandler for Ferese { window.on_commit(); } } - xdg_shell::handle_commit(&mut self.popups, &self.space, surface); + xdg_shell::handle_commit(&mut self.popups, &mut self.space, surface); } } diff --git a/compositor/src/handlers/xdg_shell.rs b/compositor/src/handlers/xdg_shell.rs index fb06d5a..4142d41 100644 --- a/compositor/src/handlers/xdg_shell.rs +++ b/compositor/src/handlers/xdg_shell.rs @@ -2,9 +2,16 @@ use smithay::{ desktop::{ PopupKind, PopupManager, Space, Window, find_popup_root_surface, get_popup_toplevel_coords, }, + input::{ + Seat, + pointer::{Focus, GrabStartData}, + }, reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode, - reexports::wayland_server::protocol::{wl_seat, wl_surface::WlSurface}, - utils::Serial, + reexports::wayland_server::{ + Resource, + protocol::{wl_seat, wl_surface::WlSurface}, + }, + utils::{Rectangle, Serial}, wayland::{ compositor::with_states, shell::xdg::decoration::XdgDecorationHandler, @@ -15,7 +22,10 @@ use smithay::{ }, }; -use crate::Ferese; +use crate::{ + Ferese, + grabs::{MoveSurfaceGrab, ResizeEdge, ResizeSurfaceGrab, handle_resize_commit}, +}; impl XdgShellHandler for Ferese { fn xdg_shell_state(&mut self) -> &mut XdgShellState { @@ -48,20 +58,103 @@ impl XdgShellHandler for Ferese { surface.send_repositioned(token); } - fn move_request(&mut self, _: ToplevelSurface, _: wl_seat::WlSeat, _: Serial) {} + fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) { + let Some(seat) = Seat::from_resource(&seat) else { + return; + }; + let Some(start_data) = check_grab(&seat, surface.wl_surface(), serial) else { + return; + }; + let Some(pointer) = seat.get_pointer() else { + return; + }; + let Some(window) = self + .space + .elements() + .find(|window| { + window + .toplevel() + .is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface()) + }) + .cloned() + else { + return; + }; + let Some(initial_location) = self.space.element_location(&window) else { + return; + }; + pointer.set_grab( + self, + MoveSurfaceGrab { + start_data, + window, + initial_location, + }, + serial, + Focus::Clear, + ); + } fn resize_request( &mut self, - _: ToplevelSurface, - _: wl_seat::WlSeat, - _: Serial, - _: smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, + surface: ToplevelSurface, + seat: wl_seat::WlSeat, + serial: Serial, + edges: smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, ) { + let Some(seat) = Seat::from_resource(&seat) else { + return; + }; + let Some(start_data) = check_grab(&seat, surface.wl_surface(), serial) else { + return; + }; + let Some(pointer) = seat.get_pointer() else { + return; + }; + let Some(window) = self + .space + .elements() + .find(|window| { + window + .toplevel() + .is_some_and(|toplevel| toplevel.wl_surface() == surface.wl_surface()) + }) + .cloned() + else { + return; + }; + let Some(location) = self.space.element_location(&window) else { + return; + }; + let rect = Rectangle::new(location, window.geometry().size); + pointer.set_grab( + self, + ResizeSurfaceGrab::new(start_data, window, ResizeEdge::from(edges), rect), + serial, + Focus::Clear, + ); } fn grab(&mut self, _: PopupSurface, _: wl_seat::WlSeat, _: Serial) {} } +fn check_grab( + seat: &Seat, + surface: &WlSurface, + serial: Serial, +) -> Option> { + let pointer = seat.get_pointer()?; + if !pointer.has_grab(serial) { + return None; + } + let start_data = pointer.grab_start_data()?; + let (focus, _) = start_data.focus.as_ref()?; + focus + .id() + .same_client_as(&surface.id()) + .then_some(start_data) +} + impl XdgDecorationHandler for Ferese { fn new_decoration(&mut self, toplevel: ToplevelSurface) { set_decoration_mode(&toplevel, Mode::ClientSide); @@ -87,7 +180,8 @@ fn set_decoration_mode(toplevel: &ToplevelSurface, mode: Mode) { toplevel.send_pending_configure(); } -pub fn handle_commit(popups: &mut PopupManager, space: &Space, surface: &WlSurface) { +pub fn handle_commit(popups: &mut PopupManager, space: &mut Space, surface: &WlSurface) { + handle_resize_commit(space, surface); if let Some(window) = space .elements() .find(|window| { diff --git a/compositor/src/main.rs b/compositor/src/main.rs index 437509c..d217623 100644 --- a/compositor/src/main.rs +++ b/compositor/src/main.rs @@ -1,5 +1,6 @@ #![allow(irrefutable_let_patterns)] +mod grabs; mod handlers; mod input; mod state;