🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
//! `zwlr_foreign_toplevel_management` (unstable v1) — lista okien dla powłoki.
//!
//! Kompozytor wystawia globalny menedżer; każdy klient (np. pasek `luna-shell`) dostaje
//! **uchwyty** do wszystkich okien najwyższego poziomu i powiadomienia o zmianach
//! (tytuł, `app_id`, stan: aktywny/zminimalizowany/zmaksymalizowany/fullscreen, zamknięcie).
//! Klient może poprosić o aktywację albo zamknięcie okna — to czyni pasek użytecznym.
use wayland_protocols_wlr::foreign_toplevel::v1::server::{
zwlr_foreign_toplevel_handle_v1 as handle, zwlr_foreign_toplevel_manager_v1 as manager,
};
use wayland_server::{
protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch,
New, Resource,
};
use crate::compositor::SurfaceData;
use crate::seat;
use crate::state::State;
use crate::sync::LockExt;
/// Stany okna (`zwlr_foreign_toplevel_handle_v1.state`).
pub const STATE_MAXIMIZED: u32 = 0;
pub const STATE_MINIMIZED: u32 = 1;
pub const STATE_ACTIVATED: u32 = 2;
pub const STATE_FULLSCREEN: u32 = 3;
/// Uchwyt okna po stronie klienta (`zwlr_foreign_toplevel_handle_v1`).
pub struct ToplevelHandleData {
/// Powierzchnia, którą ten uchwyt reprezentuje.
pub surface: WlSurface,
}
/// Informacje o toplevelu trzymane przy powierzchni (do rozsyłania do menedżerów).
#[derive(Default)]
pub struct ToplevelInfo {
pub title: String,
pub app_id: String,
/// Uchwyty utworzone dla podłączonych menedżerów (po jednym na klienta).
pub handles: Vec<handle::ZwlrForeignToplevelHandleV1>,
}
impl GlobalDispatch<manager::ZwlrForeignToplevelManagerV1, ()> for State {
fn bind(
state: &mut Self,
handle: &DisplayHandle,
_client: &Client,
resource: New<manager::ZwlrForeignToplevelManagerV1>,
_data: &(),
data_init: &mut DataInit<'_, Self>,
) {
let mgr = data_init.init(resource, ());
// Nowo podłączony menedżer: zgłoś istniejące okna.
for surface in state.slots.clone() {
if is_toplevel(&surface) {
announce(state, handle, &mgr, &surface);
}
}
state.toplevel_managers.push(mgr);
}
}
impl Dispatch<manager::ZwlrForeignToplevelManagerV1, ()> for State {
fn request(
state: &mut Self,
_client: &Client,
resource: &manager::ZwlrForeignToplevelManagerV1,
request: manager::Request,
_data: &(),
_handle: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
if let manager::Request::Stop = request {
// Klient kończy: zdejmij menedżera i wszystkie jego uchwyty.
let mgr_id = resource.id();
let client_id = resource.client().map(|c| c.id());
state.toplevel_managers.retain(|m| m.id() != mgr_id);
if let Some(client_id) = client_id {
for surface in state.slots.clone() {
if let Some(sd) = surface.data::<SurfaceData>() {
if let Some(info) = sd.toplevel.lock_or_recover().as_mut() {
info.handles.retain(|h| {
h.client().map(|c| c.id() != client_id).unwrap_or(false)
});
}
}
}
}
}
}
}
impl Dispatch<handle::ZwlrForeignToplevelHandleV1, ToplevelHandleData> for State {
fn request(
state: &mut Self,
_client: &Client,
resource: &handle::ZwlrForeignToplevelHandleV1,
request: handle::Request,
data: &ToplevelHandleData,
_handle: &DisplayHandle,
_data_init: &mut DataInit<'_, Self>,
) {
match request {
handle::Request::Activate { .. } => {
if let Some(slot) = slot_of(&data.surface) {
seat::activate_slot(state, slot);
broadcast_states(state);
}
}
handle::Request::Close => {
seat::close_surface(state, &data.surface);
}
handle::Request::SetMinimized => {
if let Some(slot) = slot_of(&data.surface) {
seat::set_minimized(state, slot, true);
}
}
handle::Request::UnsetMinimized => {
if let Some(slot) = slot_of(&data.surface) {
seat::set_minimized(state, slot, false);
}
}
handle::Request::SetMaximized => {
if let Some(slot) = slot_of(&data.surface) {
seat::set_maximized(state, slot, true);
}
}
handle::Request::UnsetMaximized => {
if let Some(slot) = slot_of(&data.surface) {
seat::set_maximized(state, slot, false);
}
}
handle::Request::SetFullscreen { .. } => {
if let Some(slot) = slot_of(&data.surface) {
seat::set_fullscreen(state, slot, true);
}
}
handle::Request::UnsetFullscreen => {
if let Some(slot) = slot_of(&data.surface) {
seat::set_fullscreen(state, slot, false);
}
}
handle::Request::Destroy => {
forget_handle(&data.surface, resource.id());
}
_ => {}
}
}
}
/// Czy powierzchnia jest oknem najwyższego poziomu (`xdg_toplevel`, nie popup/layer)?
fn is_toplevel(surface: &WlSurface) -> bool {
surface
.data::<SurfaceData>()
.map(|sd| sd.toplevel.lock_or_recover().is_some())
.unwrap_or(false)
}
/// Slot okna w modelu dla powierzchni.
fn slot_of(surface: &WlSurface) -> Option<usize> {
surface
.data::<SurfaceData>()
.and_then(|sd| *sd.slot.lock_or_recover())
}
/// Rejestruje okno jako toplevel (wołane przy `get_toplevel`).
pub fn register_toplevel(surface: &WlSurface) {
if let Some(sd) = surface.data::<SurfaceData>() {
let mut t = sd.toplevel.lock_or_recover();
if t.is_none() {
*t = Some(ToplevelInfo::default());
}
}
}
/// Zgłasza nowo podłączonemu menedżerowi istniejące okno (uchwyt + pierwszy stan).
pub fn announce(
state: &State,
display: &DisplayHandle,
mgr: &manager::ZwlrForeignToplevelManagerV1,
surface: &WlSurface,
) {
let Some(client) = mgr.client() else {
return;
};
let Ok(handle) = client
.create_resource::<handle::ZwlrForeignToplevelHandleV1, ToplevelHandleData, State>(
display,
3,
ToplevelHandleData {
surface: surface.clone(),
},
)
else {
return;
};
let _ = mgr.send_event(manager::Event::Toplevel {
toplevel: handle.clone(),
});
if let Some(sd) = surface.data::<SurfaceData>() {
if let Some(info) = sd.toplevel.lock_or_recover().as_mut() {
info.handles.push(handle.clone());
}
}
send_full(state, surface, &handle);
}
/// Rozsyła aktualizację tytułu do wszystkich menedżerów.
pub fn broadcast_title(state: &State, surface: &WlSurface, title: &str) {
for_each_handle(surface, |h| {
let _ = h.send_event(handle::Event::Title {
title: title.to_owned(),
});
let _ = h.send_event(handle::Event::Done);
});
let _ = state;
}
/// Rozsyła aktualizację `app_id`.
pub fn broadcast_app_id(state: &State, surface: &WlSurface, app_id: &str) {
for_each_handle(surface, |h| {
let _ = h.send_event(handle::Event::AppId {
app_id: app_id.to_owned(),
});
let _ = h.send_event(handle::Event::Done);
});
let _ = state;
}
/// Rozsyła stan (aktywny/minimalizowany/…) wszystkich okien — po zmianie fokusu.
pub fn broadcast_states(state: &State) {
for surface in state.slots.clone() {
if !is_toplevel(&surface) {
continue;
}
let Some(slot) = slot_of(&surface) else {
continue;
};
let bytes = state_bytes(state, slot);
for_each_handle(&surface, |h| {
let _ = h.send_event(handle::Event::State {
state: bytes.clone(),
});
let _ = h.send_event(handle::Event::Done);
});
}
}
/// Powiadamia o zamknięciu okna (i zwalnia uchwyty).
pub fn announce_closed(surface: &WlSurface) {
if let Some(sd) = surface.data::<SurfaceData>() {
if let Some(mut info) = sd.toplevel.lock_or_recover().take() {
for h in info.handles.drain(..) {
let _ = h.send_event(handle::Event::Closed);
}
}
}
}
/// Jak [`announce_closed`], ale działa wprost na danych powierzchni — używane przy
/// zniknięciu powierzchni (`Dispatch::destroyed`), gdy zasób jest już niszczony i nie
/// wolno się do niego odwoływać.
pub fn close_for_data(data: &SurfaceData) {
if let Some(mut info) = data.toplevel.lock_or_recover().take() {
for h in info.handles.drain(..) {
let _ = h.send_event(handle::Event::Closed);
}
}
}
/// Pełny stan okna wysyłany nowemu uchwytowi (title + app_id + stan + `done`).
fn send_full(state: &State, surface: &WlSurface, handle: &handle::ZwlrForeignToplevelHandleV1) {
let (title, app_id) = toplevel_text(surface);
let _ = handle.send_event(handle::Event::Title { title });
let _ = handle.send_event(handle::Event::AppId { app_id });
if let Some(slot) = slot_of(surface) {
let _ = handle.send_event(handle::Event::State {
state: state_bytes(state, slot),
});
}
let _ = handle.send_event(handle::Event::Done);
}
/// `(title, app_id)` okna z jego danych.
fn toplevel_text(surface: &WlSurface) -> (String, String) {
surface
.data::<SurfaceData>()
.and_then(|sd| {
sd.toplevel
.lock_or_recover()
.as_ref()
.map(|t| (t.title.clone(), t.app_id.clone()))
})
.unwrap_or_default()
}
/// Bity stanu okna (`slot`) zakodowane jako tablica `u32` (native endian).
fn state_bytes(state: &State, slot: usize) -> Vec<u8> {
let focused = state.keyboard_focus.as_ref().map(|(s, _)| *s);
let mut flags: Vec<u32> = Vec::new();
{
let windows = state.windows.lock_or_recover();
if let Some(w) = windows.get(slot) {
if w.maximized {
flags.push(STATE_MAXIMIZED);
}
if w.minimized {
flags.push(STATE_MINIMIZED);
}
if w.fullscreen {
flags.push(STATE_FULLSCREEN);
}
}
}
if focused == Some(slot) {
flags.push(STATE_ACTIVATED);
}
let mut out = Vec::with_capacity(flags.len() * 4);
for f in flags {
out.extend_from_slice(&f.to_ne_bytes());
}
out
}
fn for_each_handle(surface: &WlSurface, mut f: impl FnMut(&handle::ZwlrForeignToplevelHandleV1)) {
let handles = surface
.data::<SurfaceData>()
.and_then(|sd| {
sd.toplevel
.lock_or_recover()
.as_ref()
.map(|t| t.handles.clone())
})
.unwrap_or_default();
for h in &handles {
f(h);
}
}
/// Dezaktywuje pojedynczy uchwyt (po jego `destroy`).
fn forget_handle(surface: &WlSurface, id: wayland_server::backend::ObjectId) {
if let Some(sd) = surface.data::<SurfaceData>() {
if let Some(info) = sd.toplevel.lock_or_recover().as_mut() {
info.handles.retain(|h| h.id() != id);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn state_encoding_is_one_u32_per_flag() {
let s = State::default();
s.windows.lock_or_recover().push(crate::state::Window {
x: 0.0,
y: 0.0,
width: 10,
height: 10,
image_offset: (0.0, 0.0),
z: 1,
title: "t".into(),
popup: false,
layer: false,
mapped: true,
workspace: 0,
input: None,
input_capture: false,
minimized: true,
maximized: true,
fullscreen: false,
ssd: false,
saved: None,
data: std::sync::Arc::new(libluna_render::ImageData::new(1, 1, vec![0, 0, 0, 0])),
});
let bytes = state_bytes(&s, 0);
assert_eq!(bytes.len(), 4 * 2); // maximized + minimized (brak fokusu)
assert_eq!(
u32::from_ne_bytes(bytes[0..4].try_into().unwrap()),
STATE_MAXIMIZED
);
assert_eq!(
u32::from_ne_bytes(bytes[4..8].try_into().unwrap()),
STATE_MINIMIZED
);
}
}