//! Graby wskaźnika: przeciąganie okna (move) i zmiana rozmiaru (resize). //! //! PO CO: w modelu hybrydowego CSD to KLIENT mówi kompozytorowi "użytkownik //! chwycił mój pasek tytułu" / "użytkownik chwycił moją krawędź", wysyłając //! standardowe `xdg_toplevel.move` / `xdg_toplevel.resize`. Kompozytor //! odpowiada wtedy ustanowieniem *graba* — na czas przeciągania wskaźnik nie //! wysyła zdarzeń do klientów, a my przesuwamy/rozciągamy okno. //! //! To jest właściwe, standardowe rozwiązanie: nie potrzebujemy żadnego //! niestandardowego protokołu Wayland. use std::cell::RefCell; use smithay::{ desktop::WindowSurface, 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, utils::{IsAlive, Logical, Point, Rectangle, Serial, Size}, wayland::{compositor::with_states, shell::xdg::SurfaceCachedState}, }; use crate::{ focus::PointerFocusTarget, snap::{snap_geometry, snap_zone, SnapZone}, state::MyCompositor, window::PaganWindow, }; /// Krawędzie zmiany rozmiaru. Wartości bitowe są zgodne z `xdg_toplevel::ResizeEdge` /// (dzięki temu konwersja jest trywialna i nie potrzebujemy crate `bitflags`). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ResizeEdge(u32); impl ResizeEdge { pub const NONE: Self = Self(0); pub const TOP: Self = Self(1); pub const BOTTOM: Self = Self(2); pub const LEFT: Self = Self(4); pub const TOP_LEFT: Self = Self(5); pub const BOTTOM_LEFT: Self = Self(6); pub const RIGHT: Self = Self(8); pub const TOP_RIGHT: Self = Self(9); pub const BOTTOM_RIGHT: Self = Self(10); /// Czy nachodzą na siebie jakiekolwiek bity (np. TOP_LEFT zawiera TOP). pub fn intersects(self, other: Self) -> bool { self.0 & other.0 != 0 } } impl From for ResizeEdge { fn from(x: xdg_toplevel::ResizeEdge) -> Self { Self(x as u32) } } impl From for xdg_toplevel::ResizeEdge { fn from(x: ResizeEdge) -> Self { // Try_from jest poprawne, bo nasze stałe pokrywają się z wariantami protokołu. Self::try_from(x.0).unwrap_or(xdg_toplevel::ResizeEdge::None) } } /// Konwersja z krawędzi X11 (przychodzi z `XwmHandler::resize_request`). /// /// PO CO: okna X11 proszą o resize tą samą krawędzią, ale Smithay opisuje ją /// osobnym enumem bez wariantu `None` — mapujemy wprost na nasze stałe, więc /// jeden grab obsługuje oba światy. impl From for ResizeEdge { fn from(x: smithay::xwayland::xwm::ResizeEdge) -> Self { use smithay::xwayland::xwm::ResizeEdge as X; match x { X::Top => ResizeEdge::TOP, X::Bottom => ResizeEdge::BOTTOM, X::Left => ResizeEdge::LEFT, X::Right => ResizeEdge::RIGHT, X::TopLeft => ResizeEdge::TOP_LEFT, X::BottomLeft => ResizeEdge::BOTTOM_LEFT, X::TopRight => ResizeEdge::TOP_RIGHT, X::BottomRight => ResizeEdge::BOTTOM_RIGHT, } } } /// Informacje potrzebne do prowadzenia operacji zmiany rozmiaru. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct ResizeData { pub edges: ResizeEdge, pub initial_window_location: Point, pub initial_window_size: Size, } /// Stan operacji zmiany rozmiaru. Maszyna stanów jest potrzebna, by poprawnie /// zakończyć resize: klient musi potwierdzić ostatni configure. #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] pub enum ResizeState { #[default] NotResizing, /// Trwa przeciąganie krawędzi. Resizing(ResizeData), /// Resize zakończony — czekamy na ack ostatniego configure. WaitingForFinalAck(ResizeData, Serial), /// Czekamy, aż klient zacommituje finalny rozmiar. WaitingForCommit(ResizeData), } /// Dane doklejane do powierzchni okna w `data_map` Smithay. #[derive(Default)] pub struct SurfaceData { pub resize_state: ResizeState, } /// Grab przeciągania okna za pasek tytułu. pub struct PointerMoveSurfaceGrab { pub start_data: PointerGrabStartData, pub window: PaganWindow, pub initial_window_location: Point, /// Rozmiar okna w chwili rozpoczęcia przeciągania. PO CO: po wyjściu kursora /// ze strefy doklejania musimy przywrócić okno do rozmiaru sprzed snapa. pub initial_window_size: Size, /// Aktualnie aktywne doklejenie (jeśli kursor jest w strefie ekranu). pub current_snap: Option, } impl PointerMoveSurfaceGrab { /// Obszar wyjścia pod kursorem (we współrzędnych logicznych przestrzeni). /// /// PO CO przez `Space::output_under`, a nie „pierwsze wyjście”: przy wielu /// monitorach doklejać trzeba do TEGO ekranu, na którym jest kursor. fn output_under( data: &MyCompositor, point: Point, ) -> Option> { let output = data.space.output_under(point).next().cloned()?; data.space.output_geometry(&output) } /// Wysyła klientowi nową geometrię + stan „tiled”/„maximized” przez `configure`. /// /// PO CO nie wystarczy `map_element`: pozycję zmieniamy sami, ale ROZMIAR musi /// zatwierdzić klient — to on rysuje bufor w swoim procesie. Stany `Tiled*` /// mówią klientowi, że okno jest częścią kafla; `None` = przywrócenie (czyścimy /// wszystkie stany, bo okno wraca do swobodnego trybu). /// /// X11 nie ma stanów xdg — okno X11 dostaje pozycję i rozmiar jednym /// `X11Surface::configure(Rectangle)`, dlatego bierzemy pełny prostokąt. fn send_geometry(&self, geometry: Rectangle, zone: Option) { // (lewo, prawo, góra, dół) — które krawędzie kafla zaznaczyć. // Dla `Top` używamy `Maximized` (pełny ekran to nie kafel, a maksymalizacja). let (l, r, t, b, maximized) = match zone { Some(SnapZone::Left) => (true, false, true, true, false), Some(SnapZone::Right) => (false, true, true, true, false), Some(SnapZone::Top) => (false, false, false, false, true), Some(SnapZone::Bottom) => (false, false, false, true, false), Some(SnapZone::TopLeft) => (true, false, true, false, false), Some(SnapZone::TopRight) => (false, true, true, false, false), Some(SnapZone::BottomLeft) => (true, false, false, true, false), Some(SnapZone::BottomRight) => (false, true, false, true, false), None => (false, false, false, false, false), }; match self.window.underlying_surface() { WindowSurface::Wayland(xdg) => { xdg.with_pending_state(|state| { state.size = Some(geometry.size); let s = &mut state.states; // Najpierw czyścimy wszystko, potem ustawiamy to, co trzeba — // dzięki temu przejście np. „lewa połowa → ćwiartka” nie zostawia // starych flag. s.unset(xdg_toplevel::State::TiledLeft); s.unset(xdg_toplevel::State::TiledRight); s.unset(xdg_toplevel::State::TiledTop); s.unset(xdg_toplevel::State::TiledBottom); s.unset(xdg_toplevel::State::Maximized); if l { s.set(xdg_toplevel::State::TiledLeft); } if r { s.set(xdg_toplevel::State::TiledRight); } if t { s.set(xdg_toplevel::State::TiledTop); } if b { s.set(xdg_toplevel::State::TiledBottom); } if maximized { s.set(xdg_toplevel::State::Maximized); } }); xdg.send_pending_configure(); } WindowSurface::X11(x11) => { let _ = x11.configure(geometry); } } } } impl PointerGrab for PointerMoveSurfaceGrab { fn motion( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, _focus: Option<(PointerFocusTarget, Point)>, event: &MotionEvent, ) { // Podczas graba żaden klient nie ma fokusa wskaźnika. handle.motion(data, None, event); // Martwe okno (klient je zamknął) — kończymy grab, żeby nie ruszać // powierzchni, której już nie ma. if !self.window.alive() { data.snap_preview = None; handle.unset_grab(self, data, event.serial, event.time, true); return; } let delta = event.location - self.start_data.location; let new_location = self.initial_window_location.to_f64() + delta; // Czy kursor jest w strefie doklejania? Jeśli tak, pokazujemy DUCHA // (podgląd geometrii) — ale okna jeszcze NIE zmieniamy rozmiarem ani // pozycją docelową. Snap zatwierdzamy dopiero przy puszczeniu przycisku. let target = Self::output_under(data, event.location).and_then(|geometry| { snap_zone(event.location, geometry, data.snap_config) .map(|zone| (zone, snap_geometry(zone, geometry))) }); match target { Some((zone, geometry)) => { self.current_snap = Some(zone); data.snap_preview = Some(geometry); } None => { self.current_snap = None; data.snap_preview = None; } } // Okno przez cały czas podąża za kursorem, w rozmiarze z początku ruchu. data.space .map_element(self.window.clone(), new_location.to_i32_round(), false); } fn relative_motion( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, focus: Option<(PointerFocusTarget, Point)>, event: &RelativeMotionEvent, ) { handle.relative_motion(data, focus, event); } fn button( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &ButtonEvent, ) { handle.button(data, event); if handle.current_pressed().is_empty() { // Puszczono wszystkie przyciski. DOPIERO TERAZ zatwierdzamy snap: // jeśli kursor stał w strefie, wysyłamy klientowi docelową geometrię // i ustawiamy okno; w przeciwnym razie okno zostaje tam, gdzie je // przeciągnęliśmy. if let Some(zone) = self.current_snap.take() { if let Some(geometry) = Self::output_under(data, data.pointer_location) .map(|output| snap_geometry(zone, output)) { self.send_geometry(geometry, Some(zone)); data.space .map_element(self.window.clone(), geometry.loc, false); } } data.snap_preview = None; // Puszczono wszystkie przyciski — kończymy przeciąganie. handle.unset_grab(self, data, event.serial, event.time, true); } } fn axis( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, details: AxisFrame, ) { handle.axis(data, details) } fn frame( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, ) { handle.frame(data); } fn gesture_swipe_begin( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GestureSwipeBeginEvent, ) { handle.gesture_swipe_begin(data, event); } fn gesture_swipe_update( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GestureSwipeUpdateEvent, ) { handle.gesture_swipe_update(data, event); } fn gesture_swipe_end( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GestureSwipeEndEvent, ) { handle.gesture_swipe_end(data, event); } fn gesture_pinch_begin( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GesturePinchBeginEvent, ) { handle.gesture_pinch_begin(data, event); } fn gesture_pinch_update( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GesturePinchUpdateEvent, ) { handle.gesture_pinch_update(data, event); } fn gesture_pinch_end( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GesturePinchEndEvent, ) { handle.gesture_pinch_end(data, event); } fn gesture_hold_begin( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GestureHoldBeginEvent, ) { handle.gesture_hold_begin(data, event); } fn gesture_hold_end( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GestureHoldEndEvent, ) { handle.gesture_hold_end(data, event); } fn start_data(&self) -> &PointerGrabStartData { &self.start_data } fn unset(&mut self, data: &mut MyCompositor) { // Grab bywa zdejmowany z różnych powodów — zawsze chowamy ducha snapu. data.snap_preview = None; } } /// Grab zmiany rozmiaru okna za krawędź. pub struct PointerResizeSurfaceGrab { pub start_data: PointerGrabStartData, pub window: PaganWindow, pub edges: ResizeEdge, pub initial_window_location: Point, pub initial_window_size: Size, pub last_window_size: Size, } impl PointerResizeSurfaceGrab { /// Wspólna logika zakończenia resize dla powierzchni Waylanda: zdejmij stan /// "Resizing", wyślij finalny configure i przesuń okno, gdy zmienialiśmy /// lewą/górną krawędź (bo rosnąc w lewo, okno musi "uciekać" w lewo). fn finish_resize(&self, data: &mut MyCompositor, serial: Serial) { // Zakończenie różni się zależnie od typu okna: // * Wayland (xdg) — zdejmujemy stan „Resizing” i wysyłamy finalny configure; // klient potwierdzi go asynchronicznie (maszyna stanów niżej). // * X11 — jedno `configure` ustawia pozycję i rozmiar; nie ma ack-a. match self.window.underlying_surface() { WindowSurface::Wayland(xdg) => { xdg.with_pending_state(|state| { state.states.unset(xdg_toplevel::State::Resizing); state.size = Some(self.last_window_size); }); xdg.send_pending_configure(); } WindowSurface::X11(x11) => { let location = data .space .element_location(&self.window) .unwrap_or_else(|| Point::from((0, 0))); let _ = x11.configure(Rectangle::new(location, self.last_window_size)); } } if self.edges.intersects(ResizeEdge::TOP_LEFT) { let geometry = self.window.geometry(); let mut location = data .space .element_location(&self.window) .unwrap_or_else(|| Point::from((0, 0))); if self.edges.intersects(ResizeEdge::LEFT) { location.x = self.initial_window_location.x + (self.initial_window_size.w - geometry.size.w); } if self.edges.intersects(ResizeEdge::TOP) { location.y = self.initial_window_location.y + (self.initial_window_size.h - geometry.size.h); } data.space.map_element(self.window.clone(), location, false); if let Some(x11) = self.window.x11_surface() { let _ = x11.configure(Rectangle::new(location, self.last_window_size)); } } // Tylko Wayland prowadzi maszynę stanów resize w `SurfaceData` okna. if let WindowSurface::Wayland(_) = self.window.underlying_surface() { if let Some(surface) = self.window.wl_surface() { with_states(&surface, |states| { let mut surface_data = states .data_map .get::>() .expect("SurfaceData must be present; see ensure_initial_configure") .borrow_mut(); if let ResizeState::Resizing(resize_data) = surface_data.resize_state { surface_data.resize_state = ResizeState::WaitingForFinalAck(resize_data, serial); } }); } } } } impl PointerGrab for PointerResizeSurfaceGrab { fn motion( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, _focus: Option<(PointerFocusTarget, Point)>, event: &MotionEvent, ) { handle.motion(data, None, event); // Martwe okno nie ma sensu — kończymy grab. if !self.window.alive() { handle.unset_grab(self, data, event.serial, event.time, true); return; } let (mut dx, mut dy) = (event.location - self.start_data.location).into(); let mut new_window_width = self.initial_window_size.w; let mut new_window_height = self.initial_window_size.h; if self.edges.intersects(ResizeEdge::LEFT) || self.edges.intersects(ResizeEdge::RIGHT) { if self.edges.intersects(ResizeEdge::LEFT) { // Ciągnięcie lewej krawędzi w lewo (dx<0) zwiększa szerokość. dx = -dx; } new_window_width = (self.initial_window_size.w as f64 + dx) as i32; } if self.edges.intersects(ResizeEdge::TOP) || self.edges.intersects(ResizeEdge::BOTTOM) { if self.edges.intersects(ResizeEdge::TOP) { dy = -dy; } new_window_height = (self.initial_window_size.h as f64 + dy) as i32; } // Poszanowanie minimalnego/maksymalnego rozmiaru zadeklarowanego przez klienta. let (min_size, max_size) = if let Some(surface) = self.window.wl_surface() { with_states(&surface, |states| { let mut guard = states.cached_state.get::(); let cfg = guard.current(); (cfg.min_size, cfg.max_size) }) } else { ((0, 0).into(), (0, 0).into()) }; 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 }; new_window_width = new_window_width.max(min_width).min(max_width); new_window_height = new_window_height.max(min_height).min(max_height); self.last_window_size = (new_window_width, new_window_height).into(); match self.window.underlying_surface() { WindowSurface::Wayland(xdg) => { xdg.with_pending_state(|state| { state.states.set(xdg_toplevel::State::Resizing); state.size = Some(self.last_window_size); }); xdg.send_pending_configure(); } WindowSurface::X11(x11) => { let location = data .space .element_location(&self.window) .unwrap_or_else(|| Point::from((0, 0))); let _ = x11.configure(Rectangle::new(location, self.last_window_size)); } } } fn relative_motion( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, focus: Option<(PointerFocusTarget, Point)>, event: &RelativeMotionEvent, ) { handle.relative_motion(data, focus, event); } fn button( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &ButtonEvent, ) { handle.button(data, event); if handle.current_pressed().is_empty() { handle.unset_grab(self, data, event.serial, event.time, true); if !self.window.alive() { return; } self.finish_resize(data, event.serial); } } fn axis( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, details: AxisFrame, ) { handle.axis(data, details) } fn frame( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, ) { handle.frame(data); } fn gesture_swipe_begin( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GestureSwipeBeginEvent, ) { handle.gesture_swipe_begin(data, event); } fn gesture_swipe_update( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GestureSwipeUpdateEvent, ) { handle.gesture_swipe_update(data, event); } fn gesture_swipe_end( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GestureSwipeEndEvent, ) { handle.gesture_swipe_end(data, event); } fn gesture_pinch_begin( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GesturePinchBeginEvent, ) { handle.gesture_pinch_begin(data, event); } fn gesture_pinch_update( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GesturePinchUpdateEvent, ) { handle.gesture_pinch_update(data, event); } fn gesture_pinch_end( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GesturePinchEndEvent, ) { handle.gesture_pinch_end(data, event); } fn gesture_hold_begin( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GestureHoldBeginEvent, ) { handle.gesture_hold_begin(data, event); } fn gesture_hold_end( &mut self, data: &mut MyCompositor, handle: &mut PointerInnerHandle<'_, MyCompositor>, event: &GestureHoldEndEvent, ) { handle.gesture_hold_end(data, event); } fn start_data(&self) -> &PointerGrabStartData { &self.start_data } fn unset(&mut self, _data: &mut MyCompositor) {} }