🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
//! 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<xdg_toplevel::ResizeEdge> for ResizeEdge {
fn from(x: xdg_toplevel::ResizeEdge) -> Self {
Self(x as u32)
}
}
impl From<ResizeEdge> 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<smithay::xwayland::xwm::ResizeEdge> 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<i32, Logical>,
pub initial_window_size: Size<i32, Logical>,
}
/// 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<MyCompositor>,
pub window: PaganWindow,
pub initial_window_location: Point<i32, Logical>,
/// 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<i32, Logical>,
/// Aktualnie aktywne doklejenie (jeśli kursor jest w strefie ekranu).
pub current_snap: Option<SnapZone>,
}
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<f64, Logical>,
) -> Option<Rectangle<i32, Logical>> {
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<i32, Logical>, zone: Option<SnapZone>) {
// (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<MyCompositor> for PointerMoveSurfaceGrab {
fn motion(
&mut self,
data: &mut MyCompositor,
handle: &mut PointerInnerHandle<'_, MyCompositor>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
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<f64, Logical>)>,
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<MyCompositor> {
&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<MyCompositor>,
pub window: PaganWindow,
pub edges: ResizeEdge,
pub initial_window_location: Point<i32, Logical>,
pub initial_window_size: Size<i32, Logical>,
pub last_window_size: Size<i32, Logical>,
}
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::<RefCell<SurfaceData>>()
.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<MyCompositor> for PointerResizeSurfaceGrab {
fn motion(
&mut self,
data: &mut MyCompositor,
handle: &mut PointerInnerHandle<'_, MyCompositor>,
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
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::<SurfaceCachedState>();
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<f64, Logical>)>,
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<MyCompositor> {
&self.start_data
}
fn unset(&mut self, _data: &mut MyCompositor) {}
}