Add resize/move via pointer

This commit is contained in:
Val Packett 2026-03-04 22:07:53 -03:00
parent 673af15cfd
commit 20e16ca508
5 changed files with 315 additions and 17 deletions

View file

@ -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<WlSurface>,
subsurface: Rc<WlSubsurface>,
toplevel: Rc<XdgToplevel>,
pool: RefCell<SimpleBufferPool>,
globals: Rc<Globals>,
}
impl Deco {
pub fn new(host_surface: &Rc<WlSurface>, globals: &Rc<Globals>) -> 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<Deco>);
impl WlSurfaceHandler for DecoSurface {}
impl DecoSurface {
pub fn pointer_motion(
&self,
serial: u32,
surface_x: Fixed,
surface_y: Fixed,
cursor_shape: Option<&Rc<WpCursorShapeDeviceV1>>,
) {
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<WlSeat>,
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<WlSurface>,
toplevel: &Rc<XdgToplevel>,
globals: &Rc<Globals>,
) -> Rc<Deco> {
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);

View file

@ -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<WlDisplay>,
pub wl_compositor: Rc<WlCompositor>,
@ -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<WlRegistry>, name: u32) {
let display = &mut *self.wl_display.get_handler_mut::<ClientWlDisplay>();
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::<ProxyWlSeat>();
// 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<Rc<Globals>>,
/// The seats advertised by the server.
seats: HashMap<u32, Rc<WlSeat>>,
/// 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<u32, u32>,
wl_compositor: Option<Rc<WlCompositor>>,
wl_shm: Option<Rc<WlShm>>,
@ -139,6 +166,22 @@ pub struct ClientWlDisplay {
}
impl ClientWlDisplay {
fn handle_new_seat(&mut self, registry: &Rc<WlRegistry>, name: u32, version: u32) {
let Some(globals) = &self.globals else {
self.initial_seats.insert(name, version);
return;
};
let proxy = registry.state().create_object::<WlSeat>(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()
}

View file

@ -15,6 +15,7 @@ use crate::globals::Globals;
mod buffer;
mod deco;
mod globals;
mod seat;
mod util;
mod xdg;

137
src/seat.rs Normal file
View file

@ -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<Globals>,
pub wl_seat: Rc<WlSeat>,
pub wl_pointer: Option<Rc<WlPointer>>,
}
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::<ProxyWlPointer>();
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<WlSeat>, capabilities: WlSeatCapability) {
self.handle_capabilities(capabilities);
}
}
struct ProxyWlPointer {
serial: Option<u32>,
seat: Rc<WlSeat>,
wp_cursor_shape_device_v1: Option<Rc<WpCursorShapeDeviceV1>>,
surface: Option<Rc<WlSurface>>,
last_xy: Option<(Fixed, Fixed)>,
}
impl WlPointerHandler for ProxyWlPointer {
fn handle_enter(
&mut self,
_slf: &Rc<WlPointer>,
serial: u32,
surface: &Rc<WlSurface>,
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::<DecoSurface>() {
deco.pointer_motion(
serial,
surface_x,
surface_y,
self.wp_cursor_shape_device_v1.as_ref(),
);
}
}
fn handle_leave(&mut self, _slf: &Rc<WlPointer>, _serial: u32, _surface: &Rc<WlSurface>) {
self.surface = None;
}
fn handle_motion(
&mut self,
_slf: &Rc<WlPointer>,
_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::<DecoSurface>() {
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<WlPointer>,
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::<DecoSurface>() {
deco.pointer_button(&self.seat, serial, surface_x, surface_y, button, state);
}
}
}

View file

@ -25,19 +25,25 @@ impl XdgWmBaseHandler for ClientXdgWmBase {
surface: &Rc<WlSurface>,
) {
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<Deco>,
globals: Rc<Globals>,
surface: Rc<WlSurface>,
deco: Option<Rc<Deco>>,
}
impl XdgSurfaceHandler for ClientXdgSurface {
fn handle_get_toplevel(&mut self, slf: &Rc<XdgSurface>, id: &Rc<XdgToplevel>) {
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<XdgSurface>,
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);
}