🔒 Repository is read-only – file editing is disabled.

luna/de/selene/src/shell.rs main

398 lines Raw ← Back
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
//! `xdg_wm_base` / `xdg_surface` / `xdg_toplevel` — okna aplikacji (protokół xdg-shell).

use std::sync::Mutex;

use libluna_core::Rect;

use wayland_protocols::xdg::shell::server::{
    xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base,
};
use wayland_server::{
    protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch,
    New, Resource, WEnum,
};

use crate::compositor::SurfaceData;
use crate::seat::toplevel_slot;
use crate::state::State;
use crate::sync::LockExt;

/// Geometria popupu zebrana z `xdg_positioner`.
#[derive(Debug, Clone, Copy, Default)]
pub struct PopupGeom {
    pub width: i32,
    pub height: i32,
    pub anchor_x: f64,
    pub anchor_y: f64,
    pub anchor_w: f64,
    pub anchor_h: f64,
    pub offset_x: f64,
    pub offset_y: f64,
    /// Krawędź/narożnik kotwicy (enum `xdg_positioner.anchor`: 0=środek, 1=góra,
    /// 2=dół, 3=lewo, 4=prawo, 5–8=narożniki).
    pub anchor: u8,
    /// Kierunek rozwoju popupu (`xdg_positioner.gravity`, te same wartości).
    pub gravity: u8,
    /// `constraint_adjustment` (bity: 1=slide_x, 2=slide_y, 4=flip_x, 8=flip_y).
    pub constraint: u32,
}

/// Dane `xdg_positioner`.
#[derive(Debug, Default)]
pub struct XdgPositionerData {
    pub geom: Mutex<PopupGeom>,
}

/// Dane `xdg_surface`.
#[allow(dead_code)]
pub struct XdgSurfaceData {
    /// Powiązana powierzchnia `wl_surface`.
    pub wl_surface: WlSurface,
    /// Utworzona powierzchnia najwyższego poziomu (jeśli `get_toplevel`).
    pub toplevel: Mutex<Option<xdg_toplevel::XdgToplevel>>,
    /// Utworzony popup (jeśli `get_popup`).
    pub popup: Mutex<Option<xdg_popup::XdgPopup>>,
    /// Powierzchnia rodzica (dla popupu).
    pub parent: Mutex<Option<WlSurface>>,
    /// Geometria popupu z positionera.
    pub geom: Mutex<PopupGeom>,
    /// Widoczna geometria okna (`xdg_surface.set_window_geometry`) — bez cieni CSD.
    pub window_geometry: Mutex<Option<Rect>>,
    /// Czy to popup.
    pub is_popup: Mutex<bool>,
    /// Czy wysłano już pierwszą konfigurację.
    pub configured: Mutex<bool>,
}

impl XdgSurfaceData {
    pub fn new(wl_surface: WlSurface) -> Self {
        Self {
            wl_surface,
            toplevel: Mutex::new(None),
            popup: Mutex::new(None),
            parent: Mutex::new(None),
            geom: Mutex::new(PopupGeom::default()),
            window_geometry: Mutex::new(None),
            is_popup: Mutex::new(false),
            configured: Mutex::new(false),
        }
    }
}

/// Dane `xdg_toplevel`.
pub struct XdgToplevelData {
    pub title: Mutex<String>,
    pub app_id: Mutex<String>,
    /// Powiązana powierzchnia (do aktualizacji modelu okien).
    pub surface: WlSurface,
    /// Powiązany obiekt `zxdg_toplevel_decoration_v1` (jeśli klient go utworzył).
    pub decoration: Mutex<Option<crate::decoration::ToplevelDecoration>>,
    /// Negocjowany tryb dekoracji. `None` = klient nie ma obiektu dekoracji;
    /// `Some(Server)` = dekorujemy my (rysujemy pasek); `Some(Client)` = klient sam.
    pub deco: Mutex<Option<crate::decoration::DecoMode>>,
}

impl XdgToplevelData {
    pub fn new(surface: WlSurface) -> Self {
        Self {
            title: Mutex::new(String::new()),
            app_id: Mutex::new(String::new()),
            surface,
            decoration: Mutex::new(None),
            deco: Mutex::new(None),
        }
    }
}

impl GlobalDispatch<xdg_wm_base::XdgWmBase, ()> for State {
    fn bind(
        _state: &mut Self,
        _handle: &DisplayHandle,
        _client: &Client,
        resource: New<xdg_wm_base::XdgWmBase>,
        _data: &(),
        data_init: &mut DataInit<'_, Self>,
    ) {
        data_init.init(resource, ());
    }
}

impl Dispatch<xdg_wm_base::XdgWmBase, ()> for State {
    fn request(
        _state: &mut Self,
        _client: &Client,
        _resource: &xdg_wm_base::XdgWmBase,
        request: xdg_wm_base::Request,
        _data: &(),
        _handle: &DisplayHandle,
        data_init: &mut DataInit<'_, Self>,
    ) {
        match request {
            xdg_wm_base::Request::CreatePositioner { id } => {
                data_init.init(id, XdgPositionerData::default());
            }
            xdg_wm_base::Request::GetXdgSurface { id, surface } => {
                let xdg = data_init.init(id, XdgSurfaceData::new(surface.clone()));
                // Zapamiętaj powiązanie po stronie `wl_surface` (do konfiguracji przy commit).
                if let Some(sd) = surface.data::<SurfaceData>() {
                    *sd.xdg_surface.lock_or_recover() = Some(xdg);
                }
            }
            _ => {}
        }
    }
}

impl Dispatch<xdg_surface::XdgSurface, XdgSurfaceData> for State {
    fn request(
        state: &mut Self,
        _client: &Client,
        _resource: &xdg_surface::XdgSurface,
        request: xdg_surface::Request,
        data: &XdgSurfaceData,
        handle: &DisplayHandle,
        data_init: &mut DataInit<'_, Self>,
    ) {
        match request {
            xdg_surface::Request::GetToplevel { id } => {
                let toplevel = data_init.init(id, XdgToplevelData::new(data.wl_surface.clone()));
                *data.toplevel.lock_or_recover() = Some(toplevel);
                // Zarejestruj okno dla paska (foreign-toplevel) i zgłoś menedżerom.
                crate::foreign_toplevel::register_toplevel(&data.wl_surface);
                let managers = state.toplevel_managers.clone();
                for mgr in &managers {
                    crate::foreign_toplevel::announce(state, handle, mgr, &data.wl_surface);
                }
            }
            xdg_surface::Request::GetPopup {
                id,
                parent,
                positioner,
            } => {
                let popup = data_init.init(id, ());
                *data.popup.lock_or_recover() = Some(popup);
                *data.is_popup.lock_or_recover() = true;
                *data.parent.lock_or_recover() = parent
                    .as_ref()
                    .and_then(|p| p.data::<XdgSurfaceData>())
                    .map(|p| p.wl_surface.clone());
                *data.geom.lock_or_recover() = positioner
                    .data::<XdgPositionerData>()
                    .map(|p| *p.geom.lock_or_recover())
                    .unwrap_or_default();
            }
            xdg_surface::Request::AckConfigure { .. } => {
                // Konfiguracja potwierdzona — nic więcej nie trzeba po stronie MVP.
            }
            xdg_surface::Request::SetWindowGeometry {
                x,
                y,
                width,
                height,
            } => {
                *data.window_geometry.lock_or_recover() = Some(Rect::from_xywh(
                    x as f64,
                    y as f64,
                    width as f64,
                    height as f64,
                ));
            }
            _ => {}
        }
    }
}

impl Dispatch<xdg_toplevel::XdgToplevel, XdgToplevelData> for State {
    fn request(
        state: &mut Self,
        _client: &Client,
        _resource: &xdg_toplevel::XdgToplevel,
        request: xdg_toplevel::Request,
        data: &XdgToplevelData,
        _handle: &DisplayHandle,
        _data_init: &mut DataInit<'_, Self>,
    ) {
        match request {
            xdg_toplevel::Request::SetTitle { title } => {
                *data.title.lock_or_recover() = title.clone();
                state.toplevels += 1;
                // Odśwież tytuł w modelu okien i rozgłoś do pasków (foreign-toplevel).
                if let Some(sd) = data.surface.data::<SurfaceData>() {
                    if let Some(info) = sd.toplevel.lock_or_recover().as_mut() {
                        info.title = title.clone();
                    }
                    if let Some(slot) = *sd.slot.lock_or_recover() {
                        if let Some(window) = state.windows.lock_or_recover().get_mut(slot) {
                            window.title = title.clone();
                        }
                    }
                }
                crate::foreign_toplevel::broadcast_title(state, &data.surface, &title);
            }
            xdg_toplevel::Request::SetAppId { app_id } => {
                *data.app_id.lock_or_recover() = app_id.clone();
                if let Some(sd) = data.surface.data::<SurfaceData>() {
                    if let Some(info) = sd.toplevel.lock_or_recover().as_mut() {
                        info.app_id = app_id.clone();
                    }
                }
                crate::foreign_toplevel::broadcast_app_id(state, &data.surface, &app_id);
            }
            xdg_toplevel::Request::Destroy => {
                crate::foreign_toplevel::announce_closed(&data.surface);
            }
            // Okna z własnymi dekoracjami (CSD, GTK/libadwaita) proszą kompozytor, żeby
            // to on przesuwał/skalował okno w trakcie przeciągania ich paska. Bez obsługi
            // tych żądań takich okien nie da się przesunąć w ogóle.
            xdg_toplevel::Request::Move { .. } => {
                crate::seat::interactive_move_from_toplevel(state, data);
            }
            xdg_toplevel::Request::Resize { edges, .. } => {
                crate::seat::interactive_resize_from_toplevel(state, data, edges);
            }
            // Stany okna. Klient (np. libLuna, GTK, Qt) może poprosić o minimalizację,
            // maksymalizację albo pełny ekran — wcześniej te żądania były ignorowane,
            // przez co przyciski w pasku tytułu aplikacji nic nie robiły.
            xdg_toplevel::Request::SetMinimized => {
                if std::env::var("SELENE_STATS").is_ok() {
                    eprintln!("selene/win: set_minimized slot={:?}", toplevel_slot(data));
                }
                if let Some(slot) = toplevel_slot(data) {
                    crate::seat::set_minimized(state, slot, true);
                }
            }
            // Uwaga: xdg-shell **nie ma** żądania `close` — przycisk ✕ w CSD
            // obsługuje sam klient (niszczy toplevel). Kompozytor wysyła tylko
            // zdarzenie `close` w drugą stronę (`send_close`).
            xdg_toplevel::Request::SetMaximized => {
                if std::env::var("SELENE_STATS").is_ok() {
                    eprintln!("selene/win: set_maximized slot={:?}", toplevel_slot(data));
                }
                if let Some(slot) = toplevel_slot(data) {
                    crate::seat::set_maximized(state, slot, true);
                }
            }
            xdg_toplevel::Request::UnsetMaximized => {
                if let Some(slot) = toplevel_slot(data) {
                    crate::seat::set_maximized(state, slot, false);
                }
            }
            xdg_toplevel::Request::SetFullscreen { .. } => {
                if let Some(slot) = toplevel_slot(data) {
                    crate::seat::set_fullscreen(state, slot, true);
                }
            }
            xdg_toplevel::Request::UnsetFullscreen => {
                if let Some(slot) = toplevel_slot(data) {
                    crate::seat::set_fullscreen(state, slot, false);
                }
            }
            // `Activate` nie istnieje w tej wersji xdg-shell (obsługujemy aktywację
            // przez `zwlr_foreign_toplevel_handle_v1.activate`).
            _ => {}
        }
    }
}

impl Dispatch<xdg_positioner::XdgPositioner, XdgPositionerData> for State {
    fn request(
        _state: &mut Self,
        _client: &Client,
        _resource: &xdg_positioner::XdgPositioner,
        request: xdg_positioner::Request,
        data: &XdgPositionerData,
        _handle: &DisplayHandle,
        _data_init: &mut DataInit<'_, Self>,
    ) {
        let mut geom = data.geom.lock_or_recover();
        match request {
            xdg_positioner::Request::SetSize { width, height } => {
                geom.width = width;
                geom.height = height;
            }
            xdg_positioner::Request::SetAnchorRect {
                x,
                y,
                width,
                height,
            } => {
                geom.anchor_x = x as f64;
                geom.anchor_y = y as f64;
                geom.anchor_w = width as f64;
                geom.anchor_h = height as f64;
            }
            xdg_positioner::Request::SetOffset { x, y } => {
                geom.offset_x = x as f64;
                geom.offset_y = y as f64;
            }
            xdg_positioner::Request::SetAnchor { anchor } => {
                geom.anchor = match anchor {
                    WEnum::Value(v) => anchor_value(v),
                    WEnum::Unknown(v) => v.min(8) as u8,
                };
            }
            xdg_positioner::Request::SetGravity { gravity } => {
                geom.gravity = match gravity {
                    WEnum::Value(v) => gravity_value(v),
                    WEnum::Unknown(v) => v.min(8) as u8,
                };
            }
            xdg_positioner::Request::SetConstraintAdjustment {
                constraint_adjustment,
            } => {
                geom.constraint = match constraint_adjustment {
                    WEnum::Value(v) => v.bits(),
                    WEnum::Unknown(v) => v,
                };
            }
            _ => {}
        }
    }
}

/// Wartość `xdg_positioner.anchor` (enum 0–8, nie bitmaski).
fn anchor_value(v: xdg_positioner::Anchor) -> u8 {
    use xdg_positioner::Anchor;
    match v {
        Anchor::None => 0,
        Anchor::Top => 1,
        Anchor::Bottom => 2,
        Anchor::Left => 3,
        Anchor::Right => 4,
        Anchor::TopLeft => 5,
        Anchor::BottomLeft => 6,
        Anchor::TopRight => 7,
        Anchor::BottomRight => 8,
        _ => 0,
    }
}

/// Wartość `xdg_positioner.gravity` (enum 0–8, te same wartości co `anchor`).
fn gravity_value(v: xdg_positioner::Gravity) -> u8 {
    use xdg_positioner::Gravity;
    match v {
        Gravity::None => 0,
        Gravity::Top => 1,
        Gravity::Bottom => 2,
        Gravity::Left => 3,
        Gravity::Right => 4,
        Gravity::TopLeft => 5,
        Gravity::BottomLeft => 6,
        Gravity::TopRight => 7,
        Gravity::BottomRight => 8,
        _ => 0,
    }
}

impl Dispatch<xdg_popup::XdgPopup, ()> for State {
    fn request(
        _state: &mut Self,
        _client: &Client,
        _resource: &xdg_popup::XdgPopup,
        _request: xdg_popup::Request,
        _data: &(),
        _handle: &DisplayHandle,
        _data_init: &mut DataInit<'_, Self>,
    ) {
    }
}