use std::cell::RefCell; use smithay::desktop::Window; use smithay::input::pointer::{ AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent, }; use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; use smithay::utils::{Logical, Point, Rectangle, Size}; use smithay::wayland::compositor; use smithay::wayland::shell::xdg::SurfaceCachedState; use crate::Niri; 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; ResizeSurfaceState::with(window.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 Niri, handle: &mut PointerInnerHandle<'_, Niri>, _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().wl_surface(), |states| { let data = states.cached_state.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 = (max_size.w == 0).then(i32::max_value).unwrap_or(max_size.w); let max_height = (max_size.h == 0).then(i32::max_value).unwrap_or(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(); 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 Niri, handle: &mut PointerInnerHandle<'_, Niri>, focus: Option<(WlSurface, Point)>, event: &RelativeMotionEvent, ) { handle.relative_motion(data, focus, event); } fn button( &mut self, data: &mut Niri, handle: &mut PointerInnerHandle<'_, Niri>, 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(data, event.serial, event.time); let 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 Niri, handle: &mut PointerInnerHandle<'_, Niri>, details: AxisFrame, ) { handle.axis(data, details) } fn start_data(&self) -> &PointerGrabStartData { &self.start_data } } /// 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, } } } pub fn handle_commit(window: &Window) -> Option<()> { // FIXME let surface = window.toplevel().wl_surface(); ResizeSurfaceState::with(surface, |state| { state.commit(); }); // 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(()) }