amharc 0.6.0

A cinematic GTK4 and GStreamer desktop video player with a floating widget mode
Documentation
//! Always-on-top support for the widget viewer.
//!
//! GTK 4 dropped `gtk_window_set_keep_above`, so on X11 the EWMH
//! `_NET_WM_STATE` client message is sent directly. The target window is
//! located by matching `_NET_WM_PID` against this process, which avoids
//! guessing geometry or titles. Returns `false` when the display is not
//! X11 or the compositor rejects the hint, so callers can explain why.

use std::{env, process};

pub fn set_keep_above(enable: bool) -> bool {
    set_keep_above_impl(enable).is_ok()
}

fn set_keep_above_impl(enable: bool) -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(not(target_os = "linux"))]
    {
        let _ = enable;
        Err("unsupported platform".into())
    }

    #[cfg(target_os = "linux")]
    {
        use x11rb::connection::Connection;
        use x11rb::protocol::xproto::{AtomEnum, ClientMessageEvent, ConnectionExt, EventMask};

        let (conn, screen) = x11rb::connect(None).map_err(|e| e.to_string())?;
        let root = conn.setup().roots[screen].root;
        let pid = process::id();

        let net_wm_state = conn.intern_atom(false, b"_NET_WM_STATE")?.reply()?.atom;
        let state_above = conn
            .intern_atom(false, b"_NET_WM_STATE_ABOVE")?
            .reply()?
            .atom;
        let net_wm_pid = conn.intern_atom(false, b"_NET_WM_PID")?.reply()?.atom;
        let net_client_list = conn.intern_atom(false, b"_NET_CLIENT_LIST")?.reply()?.atom;
        let wm_class = conn.intern_atom(false, b"WM_CLASS")?.reply()?.atom;
        // GTK 4 no longer advertises _NET_WM_PID on some builds, so WM_CLASS
        // (instance name, derived from the program name) is the primary match.
        let prgname = env::args()
            .next()
            .as_deref()
            .and_then(|argv0| std::path::Path::new(argv0).file_name())
            .map(|name| name.to_string_lossy().into_owned());

        // The window manager may nest client windows inside frame windows,
        // so enumerate managed clients via EWMH instead of walking the tree.
        let clients = conn
            .get_property(false, root, net_client_list, AtomEnum::WINDOW, 0, 1024)?
            .reply()?
            .value32()
            .ok_or("unreadable _NET_CLIENT_LIST")?
            .collect::<Vec<_>>();

        let mut target = None;
        for child in clients {
            let pid_matches = conn
                .get_property(false, child, net_wm_pid, AtomEnum::CARDINAL, 0, 1)?
                .reply()
                .ok()
                .and_then(|reply| reply.value32().and_then(|mut v| v.next()))
                .is_some_and(|owner| owner == pid);
            if pid_matches {
                target = Some(child);
                break;
            }
            let class_matches = prgname
                .as_ref()
                .and_then(|name| {
                    conn.get_property(false, child, wm_class, AtomEnum::STRING, 0, 128)
                        .ok()?
                        .reply()
                        .ok()
                        .map(|reply| {
                            reply
                                .value
                                .split(|&b| b == 0)
                                .any(|part| part == name.as_bytes())
                        })
                })
                .unwrap_or(false);
            if class_matches {
                target = Some(child);
                break;
            }
        }
        let Some(window) = target else {
            return Err("own top-level window not found".into());
        };

        let mode: u32 = if enable { 1 } else { 0 };
        let event = ClientMessageEvent::new(32, window, net_wm_state, [mode, state_above, 0, 1, 0]);
        conn.send_event(
            false,
            root,
            EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
            event,
        )
        .map_err(|e| e.to_string())?;
        conn.flush().map_err(|e| e.to_string())?;
        Ok(())
    }
}