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

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

310 lines Raw ← Back
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
//! `zwlr_layer_shell_v1` / `zwlr_layer_surface_v1` — powierzchnie warstwowe (panele, docki).
//!
//! To fundament przyszłego pulpitu Luna: pasek górny, dock i tło to powierzchnie
//! kotwiczone do krawędzi monitora, niezależne od zwykłych okien `xdg_toplevel`.

use std::sync::Mutex;

use wayland_protocols_wlr::layer_shell::v1::server::{
    zwlr_layer_shell_v1 as layer_shell, zwlr_layer_surface_v1 as layer_surface,
};
use wayland_server::{
    Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
};

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

/// Kotwice krawędzi (`zwlr_layer_surface_v1.anchor`).
pub const ANCHOR_TOP: u32 = 1;
pub const ANCHOR_BOTTOM: u32 = 2;
pub const ANCHOR_LEFT: u32 = 4;
pub const ANCHOR_RIGHT: u32 = 8;

/// Zebrane właściwości powierzchni warstwowej.
#[derive(Debug, Clone, Copy, Default)]
pub struct LayerState {
    /// Warstwa (`background`/`bottom`/`top`/`overlay`).
    pub layer: u32,
    /// Bitmaska kotwic.
    pub anchor: u32,
    /// Żądana szerokość (0 = wybiera kompozytor/klient).
    pub width: i32,
    /// Żądana wysokość (0 = wybiera kompozytor/klient).
    pub height: i32,
    /// Strefa wyłączna (zarezerwowane miejsce na krawędzi).
    pub exclusive_zone: i32,
    pub margin_top: i32,
    pub margin_right: i32,
    pub margin_bottom: i32,
    pub margin_left: i32,
    /// `none`/`exclusive`/`on_demand`.
    pub keyboard_interactivity: u32,
}

impl LayerState {
    fn anchors(&self) -> (bool, bool, bool, bool) {
        (
            self.anchor & ANCHOR_LEFT != 0,
            self.anchor & ANCHOR_RIGHT != 0,
            self.anchor & ANCHOR_TOP != 0,
            self.anchor & ANCHOR_BOTTOM != 0,
        )
    }

    /// Rozmiar, jaki kompozytor proponuje klientowi w `configure`.
    pub fn configure_size(&self, out_w: f64, out_h: f64) -> (i32, i32) {
        let (left, right, top, bottom) = self.anchors();
        let w = if left && right && self.width == 0 {
            (out_w - self.margin_left as f64 - self.margin_right as f64).max(0.0) as i32
        } else {
            self.width
        };
        let h = if top && bottom && self.height == 0 {
            (out_h - self.margin_top as f64 - self.margin_bottom as f64).max(0.0) as i32
        } else {
            self.height
        };
        (w, h)
    }

    /// Pozycja lewego-górnego narożnika na monitorze (z uwzględnieniem kotwic i marginesów).
    pub fn position(&self, out_w: f64, out_h: f64, w: f64, h: f64) -> (f64, f64) {
        let (left, right, top, bottom) = self.anchors();
        let x = if left {
            self.margin_left as f64
        } else if right {
            out_w - self.margin_right as f64 - w
        } else {
            ((out_w - w) / 2.0).max(0.0)
        };
        let y = if top {
            self.margin_top as f64
        } else if bottom {
            out_h - self.margin_bottom as f64 - h
        } else {
            ((out_h - h) / 2.0).max(0.0)
        };
        (x, y)
    }
}

/// Dane powierzchni warstwowej.
pub struct LayerData {
    /// Monitor, na którym kotwiczymy (indeks w [`crate::monitors::Outputs`]); `None` = wszystkie.
    pub output: Option<usize>,
    /// Właściwości (aktualizowane przez `set_*`).
    pub state: Mutex<LayerState>,
    /// Czy wysłano już `configure`.
    pub configured: Mutex<bool>,
    /// Slot okna na każdym monitorze (`output=None` → jedno okno na wyjście).
    pub slots: Mutex<Vec<Option<usize>>>,
}

impl LayerData {
    pub fn new(layer: u32, output: Option<usize>) -> Self {
        Self {
            output,
            state: Mutex::new(LayerState {
                layer,
                ..LayerState::default()
            }),
            configured: Mutex::new(false),
            slots: Mutex::new(Vec::new()),
        }
    }
}

/// Kolejność rysowania wynikająca z warstwy (panele nad oknami, tło pod nimi).
pub fn layer_z(layer: u32) -> i32 {
    match layer {
        0 => -200,      // background
        1 => -100,      // bottom
        2 => 1_000_000, // top
        _ => 2_000_000, // overlay
    }
}

fn anchor_bits(a: WEnum<layer_surface::Anchor>) -> u32 {
    match a {
        WEnum::Value(v) => v.bits(),
        WEnum::Unknown(v) => v,
    }
}

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

impl Dispatch<layer_shell::ZwlrLayerShellV1, ()> for State {
    fn request(
        _state: &mut Self,
        _client: &Client,
        _resource: &layer_shell::ZwlrLayerShellV1,
        request: layer_shell::Request,
        _data: &(),
        _handle: &DisplayHandle,
        data_init: &mut DataInit<'_, Self>,
    ) {
        if let layer_shell::Request::GetLayerSurface {
            id,
            surface,
            output,
            layer,
            ..
        } = request
        {
            let layer = match layer {
                WEnum::Value(v) => v as u32,
                WEnum::Unknown(v) => v,
            };
            // Indeks monitora niesiony przez zasób `wl_output`.
            let output = output.and_then(|o| o.data::<usize>().copied());
            if std::env::var("SELENE_STATS").is_ok() {
                eprintln!("selene/layer: get_layer_surface (layer={layer}, output={output:?})");
            }
            let ls = data_init.init(id, LayerData::new(layer, output));
            // Zapamiętaj powiązanie po stronie `wl_surface` (potrzebne przy `commit`).
            if let Some(sd) = surface.data::<SurfaceData>() {
                *sd.layer.lock_or_recover() = Some(ls);
            }
        }
    }
}

impl Dispatch<layer_surface::ZwlrLayerSurfaceV1, LayerData> for State {
    fn request(
        state: &mut Self,
        _client: &Client,
        resource: &layer_surface::ZwlrLayerSurfaceV1,
        request: layer_surface::Request,
        data: &LayerData,
        _handle: &DisplayHandle,
        _data_init: &mut DataInit<'_, Self>,
    ) {
        let mut st = data.state.lock_or_recover();
        match request {
            layer_surface::Request::SetSize { width, height } => {
                st.width = width as i32;
                st.height = height as i32;
            }
            layer_surface::Request::SetAnchor { anchor } => {
                st.anchor = anchor_bits(anchor);
            }
            layer_surface::Request::SetExclusiveZone { zone } => {
                st.exclusive_zone = zone;
            }
            layer_surface::Request::SetMargin {
                top,
                right,
                bottom,
                left,
            } => {
                st.margin_top = top;
                st.margin_right = right;
                st.margin_bottom = bottom;
                st.margin_left = left;
            }
            layer_surface::Request::SetKeyboardInteractivity {
                keyboard_interactivity,
            } => {
                st.keyboard_interactivity = match keyboard_interactivity {
                    WEnum::Value(v) => v as u32,
                    WEnum::Unknown(v) => v,
                };
            }
            layer_surface::Request::SetLayer { layer } => {
                st.layer = match layer {
                    WEnum::Value(v) => v as u32,
                    WEnum::Unknown(v) => v,
                };
            }
            layer_surface::Request::Destroy => {
                // Panel znika — jego rezerwacja miejsca też (per monitor).
                if st.layer == 2
                    && st.anchor & crate::layer::ANCHOR_TOP != 0
                    && st.exclusive_zone > 0
                {
                    state.top_insets.remove(&data.output.unwrap_or(0));
                }
                // Zdejmij powierzchnię z widoku (na wszystkich monitorach).
                drop(st);
                let slots = data.slots.lock_or_recover().clone();
                let mut windows = state.windows.lock_or_recover();
                for slot in slots.into_iter().flatten() {
                    if let Some(w) = windows.get_mut(slot) {
                        w.mapped = false;
                    }
                }
                let _ = resource;
            }
            // `get_popup` i pozostałe: poza zakresem MVP.
            _ => {}
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn st(anchor: u32, width: i32, height: i32) -> LayerState {
        LayerState {
            anchor,
            width,
            height,
            ..LayerState::default()
        }
    }

    #[test]
    fn top_panel_stretches_full_width() {
        // Górny pasek: TOP|LEFT|RIGHT, wysokość 32, szerokość 0 → pełna szerokość.
        let s = st(ANCHOR_TOP | ANCHOR_LEFT | ANCHOR_RIGHT, 0, 32);
        assert_eq!(s.configure_size(1280.0, 800.0), (1280, 32));
        assert_eq!(s.position(1280.0, 800.0, 1280.0, 32.0), (0.0, 0.0));
    }

    #[test]
    fn bottom_right_dock_is_placed_at_corner() {
        // Dock przy prawym-dolnym narożniku, marginesy 8.
        let mut s = st(ANCHOR_BOTTOM | ANCHOR_RIGHT, 200, 400);
        s.margin_right = 8;
        s.margin_bottom = 8;
        assert_eq!(s.configure_size(1280.0, 800.0), (200, 400));
        assert_eq!(s.position(1280.0, 800.0, 200.0, 400.0), (1072.0, 392.0));
    }

    #[test]
    fn unanchored_surface_is_centered() {
        let s = st(0, 400, 200);
        assert_eq!(s.position(1280.0, 800.0, 400.0, 200.0), (440.0, 300.0));
    }

    #[test]
    fn exclusive_zone_does_not_resize_below_zero() {
        let mut s = st(ANCHOR_TOP | ANCHOR_LEFT | ANCHOR_RIGHT, 0, 0);
        // Marginesy większe niż ekran nie mogą dać ujemnego rozmiaru.
        s.margin_left = 5000;
        assert_eq!(s.configure_size(1280.0, 800.0).0, 0);
    }

    #[test]
    fn layer_order_puts_top_above_windows_and_background_below() {
        assert!(layer_z(2) > 0);
        assert!(layer_z(3) > layer_z(2));
        assert!(layer_z(0) < 0);
        // `bottom` leży nad `background`.
        assert!(layer_z(1) > layer_z(0));
    }
}