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

PaganDE/pagan-toolkit/examples/toplevels.rs main

160 linii Raw ← Powrót
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;
        }
    }
}