From 20e16ca508758fe8f71c59c04357f7c99cfd6e7e Mon Sep 17 00:00:00 2001 From: Val Packett Date: Wed, 4 Mar 2026 22:07:53 -0300 Subject: [PATCH] Add resize/move via pointer --- src/deco.rs | 122 +++++++++++++++++++++++++++++++++++++++++-- src/globals.rs | 45 +++++++++++++++- src/main.rs | 1 + src/seat.rs | 137 +++++++++++++++++++++++++++++++++++++++++++++++++ src/xdg.rs | 27 ++++++---- 5 files changed, 315 insertions(+), 17 deletions(-) create mode 100644 src/seat.rs diff --git a/src/deco.rs b/src/deco.rs index f5028bf..0b8f914 100644 --- a/src/deco.rs +++ b/src/deco.rs @@ -1,8 +1,21 @@ use std::{cell::RefCell, rc::Rc}; use wl_proxy::{ + fixed::Fixed, object::ObjectCoreApi, - protocols::wayland::{wl_buffer::WlBuffer, wl_subsurface::WlSubsurface, wl_surface::WlSurface}, + protocols::{ + cursor_shape_v1::wp_cursor_shape_device_v1::{ + WpCursorShapeDeviceV1, WpCursorShapeDeviceV1Shape, + }, + wayland::{ + wl_buffer::WlBuffer, + wl_pointer::WlPointerButtonState, + wl_seat::WlSeat, + wl_subsurface::WlSubsurface, + wl_surface::{WlSurface, WlSurfaceHandler}, + }, + xdg_shell::xdg_toplevel::{XdgToplevel, XdgToplevelResizeEdge}, + }, }; use crate::{buffer::SimpleBufferPool, globals::Globals, util::Bounds}; @@ -14,12 +27,104 @@ pub struct Deco { wl_surface: Rc, subsurface: Rc, + toplevel: Rc, pool: RefCell, globals: Rc, } impl Deco { - pub fn new(host_surface: &Rc, globals: &Rc) -> Deco { + fn resize_anchor(&self, x: i32, y: i32) -> XdgToplevelResizeEdge { + let n = y < self.border_size; + let e = x >= self.bounds.borrow().width + self.border_size; + let w = x < self.border_size; + let s = y >= self.bounds.borrow().height + self.border_size; + if n && e { + XdgToplevelResizeEdge::TOP_RIGHT + } else if n && w { + XdgToplevelResizeEdge::TOP_LEFT + } else if s && e { + XdgToplevelResizeEdge::BOTTOM_RIGHT + } else if s && w { + XdgToplevelResizeEdge::BOTTOM_LEFT + } else if n { + XdgToplevelResizeEdge::TOP + } else if e { + XdgToplevelResizeEdge::RIGHT + } else if w { + XdgToplevelResizeEdge::LEFT + } else if s { + XdgToplevelResizeEdge::BOTTOM + } else { + XdgToplevelResizeEdge::NONE + } + } +} + +fn resize_cursor(anchor: XdgToplevelResizeEdge) -> WpCursorShapeDeviceV1Shape { + match anchor { + XdgToplevelResizeEdge::TOP_LEFT => WpCursorShapeDeviceV1Shape::NW_RESIZE, + XdgToplevelResizeEdge::TOP_RIGHT => WpCursorShapeDeviceV1Shape::NE_RESIZE, + XdgToplevelResizeEdge::BOTTOM_LEFT => WpCursorShapeDeviceV1Shape::SW_RESIZE, + XdgToplevelResizeEdge::BOTTOM_RIGHT => WpCursorShapeDeviceV1Shape::SE_RESIZE, + XdgToplevelResizeEdge::TOP => WpCursorShapeDeviceV1Shape::N_RESIZE, + XdgToplevelResizeEdge::LEFT => WpCursorShapeDeviceV1Shape::W_RESIZE, + XdgToplevelResizeEdge::RIGHT => WpCursorShapeDeviceV1Shape::E_RESIZE, + XdgToplevelResizeEdge::BOTTOM => WpCursorShapeDeviceV1Shape::S_RESIZE, + _ => WpCursorShapeDeviceV1Shape::DEFAULT, + } +} + +pub struct DecoSurface(Rc); + +impl WlSurfaceHandler for DecoSurface {} + +impl DecoSurface { + pub fn pointer_motion( + &self, + serial: u32, + surface_x: Fixed, + surface_y: Fixed, + cursor_shape: Option<&Rc>, + ) { + let x = surface_x.to_i32_round_towards_nearest(); + let y = surface_y.to_i32_round_towards_nearest(); + cursor_shape + .unwrap() + .send_set_shape(serial, resize_cursor(self.0.resize_anchor(x, y))); + } + + pub fn pointer_button( + &self, + seat: &Rc, + serial: u32, + surface_x: Fixed, + surface_y: Fixed, + button: u32, + state: WlPointerButtonState, + ) { + let x = surface_x.to_i32_round_towards_nearest(); + let y = surface_y.to_i32_round_towards_nearest(); + let anchor = self.0.resize_anchor(x, y); + const BTN_LEFT: u32 = 0x110; + if button != BTN_LEFT { + return; + } + if state == WlPointerButtonState::PRESSED { + if anchor == XdgToplevelResizeEdge::NONE { + self.0.toplevel.send_move(seat, serial); + } else { + self.0.toplevel.send_resize(seat, serial, anchor); + } + } + } +} + +impl Deco { + pub fn new( + host_surface: &Rc, + toplevel: &Rc, + globals: &Rc, + ) -> Rc { let wl_surface = globals.wl_compositor.new_send_create_surface(); wl_surface.set_forward_to_client(false); let subsurface = globals @@ -28,7 +133,7 @@ impl Deco { subsurface.set_forward_to_client(false); subsurface.send_place_below(&host_surface); let pool = SimpleBufferPool::new(globals, 1, 1).unwrap(); - Deco { + let deco = Rc::new(Deco { bounds: RefCell::new(Bounds { x: 0, y: 0, @@ -37,11 +142,14 @@ impl Deco { }), top_size: 24, border_size: 8, - wl_surface, + wl_surface: wl_surface.clone(), subsurface, + toplevel: toplevel.clone(), pool: RefCell::new(pool), globals: globals.clone(), - } + }); + wl_surface.set_handler(DecoSurface(deco.clone())); + deco } pub fn handle_window_geometry( @@ -88,6 +196,10 @@ impl Deco { self.wl_surface.send_damage_buffer(0, 0, width, height); let input_region = self.globals.wl_compositor.new_send_create_region(); input_region.set_forward_to_client(false); + input_region.send_add(0, 0, width, self.top_size); + input_region.send_add(0, 0, self.border_size, height); + input_region.send_add(width - self.border_size, 0, self.border_size, height); + input_region.send_add(0, height - self.border_size, width, self.border_size); self.wl_surface.send_set_input_region(Some(&input_region)); self.subsurface .send_set_position(bounds.x - self.border_size, bounds.y - self.top_size); diff --git a/src/globals.rs b/src/globals.rs index ceaf818..2e29958 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -1,4 +1,4 @@ -use std::{mem, rc::Rc}; +use std::{collections::HashMap, mem, rc::Rc}; use wl_proxy::{ object::{ConcreteObject, Object, ObjectCoreApi, ObjectUtils}, @@ -15,12 +15,16 @@ pub use wl_proxy::protocols::{ wl_display::WlDisplay, wl_display::WlDisplayHandler, wl_registry::{WlRegistry, WlRegistryHandler}, + wl_seat::WlSeat, wl_shm::WlShm, wl_subcompositor::WlSubcompositor, }, xdg_shell::xdg_wm_base::XdgWmBase, }; +use crate::seat::ProxyWlSeat; + +#[allow(dead_code)] pub struct Globals { pub wl_display: Rc, pub wl_compositor: Rc, @@ -69,9 +73,24 @@ impl WlRegistryHandler for ProxyWlRegistry { ) } XdgWmBase::INTERFACE => bind!(xdg_wm_base, XdgWmBase, 7), + WlSeat::INTERFACE => display.handle_new_seat(slf, name, version), _ => {} } } + + fn handle_global_remove(&mut self, _slf: &Rc, name: u32) { + let display = &mut *self.wl_display.get_handler_mut::(); + if display.globals.is_none() { + display.initial_seats.remove(&name); + return; + } + if let Some(seat) = display.seats.remove(&name) { + // let proxy_wl_seat = &mut *seat.get_handler_mut::(); + // proxy_wl_seat.handle_capabilities(WlSeatCapability::empty()); + seat.unset_handler(); + seat.send_release(); + } + } } /// Handler used for the initial, proxy-created wl_callback. @@ -115,6 +134,9 @@ impl WlCallbackHandler for ProxyFirstSyncHandler { wl_registry.set_handler(crate::ClientWlRegistry::new(&globals)); } display.globals = Some(globals); + for (name, version) in mem::take(&mut display.initial_seats) { + display.handle_new_seat(&self.wl_registry, name, version); + } } } @@ -128,6 +150,11 @@ pub struct ClientWlDisplay { /// The globals object created after our initial roundtrip. globals: Option>, + /// The seats advertised by the server. + seats: HashMap>, + /// The seats advertised by the server that we haven't yet created a wl_seat object + /// for because the initial roundtrip has not yet completed. + initial_seats: HashMap, wl_compositor: Option>, wl_shm: Option>, @@ -139,6 +166,22 @@ pub struct ClientWlDisplay { } impl ClientWlDisplay { + fn handle_new_seat(&mut self, registry: &Rc, name: u32, version: u32) { + let Some(globals) = &self.globals else { + self.initial_seats.insert(name, version); + return; + }; + let proxy = registry.state().create_object::(version.min(10)); + proxy.set_forward_to_client(false); + registry.send_bind(name, proxy.clone()); + proxy.set_handler(ProxyWlSeat { + wl_seat: proxy.clone(), + globals: globals.clone(), + wl_pointer: None, + }); + self.seats.insert(name, proxy); + } + pub fn new() -> Self { Default::default() } diff --git a/src/main.rs b/src/main.rs index 70756f8..fa61f83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ use crate::globals::Globals; mod buffer; mod deco; mod globals; +mod seat; mod util; mod xdg; diff --git a/src/seat.rs b/src/seat.rs new file mode 100644 index 0000000..5e0edfd --- /dev/null +++ b/src/seat.rs @@ -0,0 +1,137 @@ +use std::rc::Rc; + +use wl_proxy::{ + fixed::Fixed, + object::{Object, ObjectCoreApi, ObjectUtils}, + protocols::{ + cursor_shape_v1::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1, + wayland::{ + wl_pointer::{WlPointer, WlPointerButtonState, WlPointerHandler}, + wl_seat::{WlSeat, WlSeatCapability, WlSeatHandler}, + wl_surface::WlSurface, + }, + }, +}; + +use crate::{deco::DecoSurface, globals::Globals}; + +#[derive(Clone)] +pub struct ProxyWlSeat { + pub globals: Rc, + pub wl_seat: Rc, + pub wl_pointer: Option>, +} + +impl ProxyWlSeat { + fn handle_capabilities(&mut self, capabilities: WlSeatCapability) { + if capabilities.contains(WlSeatCapability::POINTER) { + if self.wl_pointer.is_none() { + let wl_pointer = self.wl_seat.new_send_get_pointer(); + wl_pointer.set_forward_to_client(false); + let wp_cursor_shape_device_v1 = self + .globals + .wp_cursor_shape_manager_v1 + .as_ref() + .map(|m| m.new_send_get_pointer(&wl_pointer)); + wl_pointer.set_handler(ProxyWlPointer { + serial: None, + seat: self.wl_seat.clone(), + wp_cursor_shape_device_v1, + surface: Default::default(), + last_xy: None, + }); + self.wl_pointer = Some(wl_pointer); + } + } else { + if let Some(wl_pointer) = self.wl_pointer.take() { + let h = &mut *wl_pointer.get_handler_mut::(); + if let Some(dev) = &h.wp_cursor_shape_device_v1 { + dev.send_destroy(); + } + wl_pointer.unset_handler(); + wl_pointer.send_release(); + } + } + } +} + +impl WlSeatHandler for ProxyWlSeat { + fn handle_capabilities(&mut self, _slf: &Rc, capabilities: WlSeatCapability) { + self.handle_capabilities(capabilities); + } +} + +struct ProxyWlPointer { + serial: Option, + seat: Rc, + wp_cursor_shape_device_v1: Option>, + surface: Option>, + last_xy: Option<(Fixed, Fixed)>, +} + +impl WlPointerHandler for ProxyWlPointer { + fn handle_enter( + &mut self, + _slf: &Rc, + serial: u32, + surface: &Rc, + surface_x: Fixed, + surface_y: Fixed, + ) { + self.surface = Some(surface.clone()); + self.serial = Some(serial); + self.last_xy = Some((surface_x, surface_y)); + if let Ok(deco) = surface.try_get_handler_mut::() { + deco.pointer_motion( + serial, + surface_x, + surface_y, + self.wp_cursor_shape_device_v1.as_ref(), + ); + } + } + + fn handle_leave(&mut self, _slf: &Rc, _serial: u32, _surface: &Rc) { + self.surface = None; + } + + fn handle_motion( + &mut self, + _slf: &Rc, + _time: u32, + surface_x: Fixed, + surface_y: Fixed, + ) { + let Some(ref surface) = self.surface else { + return; + }; + self.last_xy = Some((surface_x, surface_y)); + if let Ok(deco) = surface.try_get_handler_mut::() { + deco.pointer_motion( + self.serial.unwrap(), + surface_x, + surface_y, + self.wp_cursor_shape_device_v1.as_ref(), + ); + } + } + + fn handle_button( + &mut self, + _slf: &Rc, + serial: u32, + _time: u32, + button: u32, + state: WlPointerButtonState, + ) { + let Some(ref surface) = self.surface else { + return; + }; + let Some((surface_x, surface_y)) = self.last_xy else { + return; + }; + if let Ok(deco) = surface.try_get_handler_mut::() { + deco.pointer_button(&self.seat, serial, surface_x, surface_y, button, state); + } + } +} diff --git a/src/xdg.rs b/src/xdg.rs index aa0708f..87d0d42 100644 --- a/src/xdg.rs +++ b/src/xdg.rs @@ -25,19 +25,25 @@ impl XdgWmBaseHandler for ClientXdgWmBase { surface: &Rc, ) { id.set_handler(ClientXdgSurface { - deco: Rc::new(Deco::new(surface, &self.globals)), + deco: None, + globals: self.globals.clone(), + surface: surface.clone(), }); slf.send_get_xdg_surface(id, surface); } } struct ClientXdgSurface { - deco: Rc, + globals: Rc, + surface: Rc, + deco: Option>, } impl XdgSurfaceHandler for ClientXdgSurface { fn handle_get_toplevel(&mut self, slf: &Rc, id: &Rc) { - id.set_handler(ClientXdgToplevel::new(&self.deco)); + let deco = Deco::new(&self.surface, id, &self.globals); + id.set_handler(ClientXdgToplevel::new(&deco)); + self.deco = Some(deco); slf.send_get_toplevel(id); } @@ -58,15 +64,14 @@ impl XdgSurfaceHandler for ClientXdgSurface { fn handle_set_window_geometry( &mut self, slf: &Rc, - x: i32, - y: i32, - width: i32, - height: i32, + mut x: i32, + mut y: i32, + mut width: i32, + mut height: i32, ) { - let (x, y, width, height) = self - .deco - .handle_window_geometry(x, y, width, height) - .unwrap(); + if let Some(deco) = self.deco.as_ref() { + (x, y, width, height) = deco.handle_window_geometry(x, y, width, height).unwrap(); + } slf.send_set_window_geometry(x, y, width, height); }