//! Narzędzie DEV: podejrzyj listę okien z kompozytora. //! //! PO CO: weryfikuje protokół `wlr-foreign-toplevel-management` po stronie //! kompozytora (tytuł, `app_id`, stan). To ten sam protokół, na którym będzie //! opierał się pasek zadań panelu PaganDE — dobrze go najpierw zobaczyć „gołym //! okiem”, zanim wbudujemy go w UI. //! //! Użycie: //! WAYLAND_DISPLAY=wayland-1 cargo run --example toplevels //! //! Zostaw uruchomione i otwieraj/zamykaj okna — wypisze zmiany na żywo. use std::collections::HashMap; use std::process::ExitCode; use wayland_client::{ globals::{registry_queue_init, GlobalListContents}, protocol::wl_registry, Connection, Dispatch, QueueHandle, }; use wayland_protocols_wlr::foreign_toplevel::v1::client::{ zwlr_foreign_toplevel_handle_v1 as handle, zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1, zwlr_foreign_toplevel_manager_v1 as manager, zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, }; /// Stan klienta. Trzymamy uchwyty, żeby wiedzieć, ile okien widzimy. #[derive(Default)] struct State { handles: HashMap, titles: HashMap, } /// Klucz uchwytu (stabilny w obrębie sesji klienta). fn key(handle: &ZwlrForeignToplevelHandleV1) -> usize { use wayland_client::Proxy; handle.id().protocol_id() as usize } /// Dekoduje tablicę stanów protokołu (`u32` → nazwa). fn decode_states(bytes: &[u8]) -> Vec<&'static str> { bytes .chunks_exact(4) .map(|chunk| { // Tablica przychodzi w kolejności bajtów hosta (native endian). let value = u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]); match value { 0 => "maximized", 1 => "minimized", 2 => "activated", 3 => "fullscreen", _ => "?", } }) .collect() } impl Dispatch for State { fn event( _state: &mut Self, _registry: &wl_registry::WlRegistry, _event: wl_registry::Event, _data: &GlobalListContents, _conn: &Connection, _qh: &QueueHandle, ) { } } impl Dispatch for State { // Zdarzenie `toplevel` tworzy nowy obiekt u klienta — `wayland-client` wymaga // wskazania, jakim typem go utworzyć (inaczej panikuje). wayland_client::event_created_child!(State, ZwlrForeignToplevelManagerV1, [ manager::EVT_TOPLEVEL_OPCODE => (ZwlrForeignToplevelHandleV1, ()) ]); fn event( _state: &mut Self, _manager: &ZwlrForeignToplevelManagerV1, event: manager::Event, _data: &(), _conn: &Connection, _qh: &QueueHandle, ) { match event { manager::Event::Toplevel { toplevel } => { println!("[+] nowe okno (obiekt {})", key(&toplevel)); // Uchwyt poznajemy po zdarzeniach; trzymamy go, by nie zniknął. _state.handles.insert(key(&toplevel), toplevel); } manager::Event::Finished => println!("[*] kompozytor zakończył listę okien"), _ => {} } } } impl Dispatch for State { fn event( state: &mut Self, handle: &ZwlrForeignToplevelHandleV1, event: handle::Event, _data: &(), _conn: &Connection, _qh: &QueueHandle, ) { match event { handle::Event::Title { title } => { println!(" tytuł: {title}"); state.titles.insert(key(handle), title); } handle::Event::AppId { app_id } => println!(" app_id: {app_id}"), handle::Event::State { state: bytes } => { println!(" stan: [{}]", decode_states(&bytes).join(", ")); } handle::Event::Done => println!(" ---"), handle::Event::Closed => { let id = key(handle); let title = state.titles.remove(&id).unwrap_or_default(); state.handles.remove(&id); println!("[-] zamknięte: {title}"); } _ => {} } } } fn main() -> ExitCode { let connection = match Connection::connect_to_env() { Ok(connection) => connection, Err(err) => { eprintln!("nie mogę połączyć się z Waylandem: {err}"); return ExitCode::FAILURE; } }; let (globals, mut queue) = match registry_queue_init::(&connection) { Ok(pair) => pair, Err(err) => { eprintln!("błąd rejestru Waylanda: {err}"); return ExitCode::FAILURE; } }; let qh = queue.handle(); let mut state = State::default(); // Bindujemy menedżera listy okien (wersja maks. 3). if let Err(err) = globals.bind::(&qh, 1..=3, ()) { eprintln!("kompozytor nie oferuje wlr-foreign-toplevel-management: {err}"); return ExitCode::FAILURE; } println!("nasłuchuję na listę okien (Ctrl+C kończy)..."); loop { if let Err(err) = queue.blocking_dispatch(&mut state) { eprintln!("błąd pętli zdarzeń: {err}"); return ExitCode::FAILURE; } } }