🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
//! 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<usize, ZwlrForeignToplevelHandleV1>,
titles: HashMap<usize, String>,
}
/// 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<wl_registry::WlRegistry, GlobalListContents> for State {
fn event(
_state: &mut Self,
_registry: &wl_registry::WlRegistry,
_event: wl_registry::Event,
_data: &GlobalListContents,
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
}
}
impl Dispatch<ZwlrForeignToplevelManagerV1, ()> 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<Self>,
) {
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<ZwlrForeignToplevelHandleV1, ()> for State {
fn event(
state: &mut Self,
handle: &ZwlrForeignToplevelHandleV1,
event: handle::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
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::<State>(&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::<ZwlrForeignToplevelManagerV1, State, ()>(&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;
}
}
}