← Commit history

Delete rust/crates/kicad-platform/src/linux/x11.rs

Colby Knox ·7a6bdbe386 ·8d ago ·parent 8c9312a
1 file changed −637
rust/crates/kicad-platform/src/linux/x11.rsdeleted−637
@@ -1,637 +0,0 @@-//! KiCad's windows on Linux, through the X server.-//!-//! KiCad 10 is a wxGTK application that runs on X11 even inside a Wayland session: KiCad-//! sets `GDK_BACKEND=x11` itself, so on GNOME, KDE and other Wayland desktops its frames,-//! editors and dialogs are XWayland windows. One X11 layer therefore covers both kinds of-//! session. The window manager publishes the EWMH properties read here (`_NET_CLIENT_LIST`,-//! `_NET_WM_PID`, `_NET_WM_STATE`, `_NET_ACTIVE_WINDOW`, ...), and every request that changes-//! a window goes to the window manager as a client message, the way a pager or taskbar asks.-//!-//! What X cannot see: windows of native Wayland applications (a browser, a terminal) are not-//! in the X window list, so on a Wayland desktop `foreground()` is 0 while one of those has-//! focus, and `z_top()` only orders X windows. The bridge's KiCad windows are always visible.-//!-//! Pure Rust (x11rb's own protocol implementation): nothing links libX11 or libxcb.--use std::sync::Mutex;-use std::time::{Duration, Instant};--use x11rb::connection::{Connection, RequestConnection};-use x11rb::errors::{ConnectionError, ReplyError, ReplyOrIdError};-use x11rb::protocol::xproto::{-    AtomEnum, ClientMessageEvent, ConfigureWindowAux, ConnectionExt as _, EventMask, GetPropertyReply, ImageFormat, ImageOrder, MapState, StackMode, Window,-};-use x11rb::rust_connection::RustConnection;--use crate::{Capture, Placement, WindowInfo};--x11rb::atom_manager! {-    pub Atoms: AtomsCookie {-        UTF8_STRING,-        WM_STATE,-        WM_CHANGE_STATE,-        _NET_CLIENT_LIST,-        _NET_CLIENT_LIST_STACKING,-        _NET_ACTIVE_WINDOW,-        _NET_WM_NAME,-        _NET_WM_PID,-        _NET_WM_STATE,-        _NET_WM_STATE_HIDDEN,-        _NET_WM_STATE_MAXIMIZED_VERT,-        _NET_WM_STATE_MAXIMIZED_HORZ,-        _NET_WM_STATE_MODAL,-        _NET_WM_WINDOW_TYPE,-        _NET_WM_WINDOW_TYPE_DIALOG,-        _NET_WM_WINDOW_TYPE_DESKTOP,-        _NET_WM_WINDOW_TYPE_DOCK,-        _NET_CLOSE_WINDOW,-        _NET_RESTACK_WINDOW,-        _NET_FRAME_EXTENTS,-        _NET_SUPPORTING_WM_CHECK,-    }-}--struct X {-    conn: RustConnection,-    root: Window,-    atoms: Atoms,-    screen_w: u16,-    screen_h: u16,-}--static X: Mutex<Option<X>> = Mutex::new(None);--/// The kind of graphical session the bridge runs in: "wayland", "x11" or "none".-pub fn session_type() -> &'static str {-    let t = std::env::var("XDG_SESSION_TYPE").unwrap_or_default().to_lowercase();-    if t == "wayland" || std::env::var_os("WAYLAND_DISPLAY").is_some_and(|v| !v.is_empty()) {-        return "wayland";-    }-    if t == "x11" || std::env::var_os("DISPLAY").is_some_and(|v| !v.is_empty()) {-        return "x11";-    }-    "none"-}--/// The X cookie file to try when XAUTHORITY is unset or stale. GNOME's Mutter writes-/// `$XDG_RUNTIME_DIR/.mutter-Xwaylandauth.*` for XWayland; most X11 sessions use ~/.Xauthority.-fn fallback_xauthority() -> Option<String> {-    let runtime = std::env::var("XDG_RUNTIME_DIR").ok().filter(|s| !s.is_empty()).or_else(|| {-        let uid = std::fs::read_to_string("/proc/self/status").ok()?.lines().find(|l| l.starts_with("Uid:"))?.split_whitespace().nth(1)?.to_string();-        Some(format!("/run/user/{uid}"))-    });-    if let Some(dir) = runtime {-        if let Ok(rd) = std::fs::read_dir(&dir) {-            let mut c: Vec<_> = rd.flatten().filter(|e| e.file_name().to_string_lossy().starts_with(".mutter-Xwaylandauth.")).collect();-            c.sort_by_key(|e| std::cmp::Reverse(e.metadata().and_then(|m| m.modified()).ok()));-            if let Some(e) = c.first() {-                return Some(e.path().to_string_lossy().to_string());-            }-        }-    }-    let home = std::env::var("HOME").ok()?;-    let p = format!("{home}/.Xauthority");-    std::path::Path::new(&p).is_file().then_some(p)-}--fn connect() -> Result<X, String> {-    if std::env::var_os("DISPLAY").is_none_or(|v| v.is_empty()) {-        // The standard "not implemented on <os>" form, so a verb answers not_supported_on_platform-        // (readiness already says windowControl: false without a display).-        return Err(format!("window control: not implemented on linux without a desktop session (DISPLAY is not set in the bridge's environment; session: {})", session_type()));-    }-    let (conn, screen) = match RustConnection::connect(None) {-        Ok(c) => c,-        Err(first) => {-            // XAUTHORITY missing or pointing at an old session's cookie: try the session's own.-            let current = std::env::var("XAUTHORITY").unwrap_or_default();-            match fallback_xauthority().filter(|p| *p != current) {-                Some(p) => {-                    std::env::set_var("XAUTHORITY", &p);-                    RustConnection::connect(None).map_err(|e| format!("X display {}: {first}; with XAUTHORITY={p}: {e}", std::env::var("DISPLAY").unwrap_or_default()))?-                }-                None => return Err(format!("X display {}: {first}", std::env::var("DISPLAY").unwrap_or_default())),-            }-        }-    };-    let s = &conn.setup().roots[screen];-    let (root, screen_w, screen_h) = (s.root, s.width_in_pixels, s.height_in_pixels);-    let atoms = Atoms::new(&conn).map_err(|e| e.to_string())?.reply().map_err(|e| e.to_string())?;-    Ok(X { conn, root, atoms, screen_w, screen_h })-}--/// Run `f` on the shared connection, connecting on first use and once more after the-/// server went away (XWayland restarts with the session).-fn with<T>(f: impl Fn(&X) -> Result<T, XErr>) -> Result<T, String> {-    let mut guard = X.lock().unwrap_or_else(|p| p.into_inner());-    for attempt in 0..2 {-        if guard.is_none() {-            *guard = Some(connect()?);-        }-        match f(guard.as_ref().unwrap()) {-            Ok(v) => return Ok(v),-            Err(XErr::Conn(e)) => {-                *guard = None;-                if attempt == 1 {-                    return Err(format!("X connection lost: {e}"));-                }-            }-            Err(XErr::Other(e)) => return Err(e),-        }-    }-    Err("X connection lost".into())-}--pub enum XErr {-    Conn(String),-    Other(String),-}-impl From<ConnectionError> for XErr {-    fn from(e: ConnectionError) -> Self {-        XErr::Conn(e.to_string())-    }-}-impl From<ReplyError> for XErr {-    fn from(e: ReplyError) -> Self {-        match e {-            ReplyError::ConnectionError(c) => XErr::Conn(c.to_string()),-            ReplyError::X11Error(x) => XErr::Other(format!("X error {:?}", x.error_kind)),-        }-    }-}-impl From<ReplyOrIdError> for XErr {-    fn from(e: ReplyOrIdError) -> Self {-        match e {-            ReplyOrIdError::ConnectionError(c) => XErr::Conn(c.to_string()),-            other => XErr::Other(other.to_string()),-        }-    }-}--fn win(h: u64) -> Result<Window, XErr> {-    u32::try_from(h).ok().filter(|w| *w != 0).ok_or_else(|| XErr::Other(format!("{h} is not an X window id")))-}--impl X {-    fn prop(&self, w: Window, name: u32, ty: impl Into<u32>, len: u32) -> Result<Option<GetPropertyReply>, XErr> {-        match self.conn.get_property(false, w, name, ty, 0, len)?.reply() {-            Ok(r) if r.format != 0 => Ok(Some(r)),-            Ok(_) => Ok(None),-            Err(ReplyError::X11Error(_)) => Ok(None), // window gone-            Err(e) => Err(e.into()),-        }-    }-    fn windows_prop(&self, w: Window, name: u32) -> Result<Vec<Window>, XErr> {-        Ok(self.prop(w, name, AtomEnum::WINDOW, 4096)?.and_then(|r| r.value32().map(|v| v.collect())).unwrap_or_default())-    }-    fn cardinal(&self, w: Window, name: u32, ty: impl Into<u32>) -> Result<Option<u32>, XErr> {-        Ok(self.prop(w, name, ty, 1)?.and_then(|r| r.value32().and_then(|mut v| v.next())))-    }-    fn atoms_prop(&self, w: Window, name: u32) -> Result<Vec<u32>, XErr> {-        Ok(self.prop(w, name, AtomEnum::ATOM, 64)?.and_then(|r| r.value32().map(|v| v.collect())).unwrap_or_default())-    }-    fn title(&self, w: Window) -> Result<String, XErr> {-        if let Some(r) = self.prop(w, self.atoms._NET_WM_NAME, self.atoms.UTF8_STRING, 1024)? {-            return Ok(String::from_utf8_lossy(&r.value).to_string());-        }-        // WM_NAME is Latin-1 (STRING) or COMPOUND_TEXT; KiCad sets _NET_WM_NAME, others may not.-        Ok(self.prop(w, AtomEnum::WM_NAME.into(), AtomEnum::ANY, 1024)?.map(|r| r.value.iter().map(|b| *b as char).collect()).unwrap_or_default())-    }-    /// WM_CLASS is "instance\0class\0"; the class ("pcbnew", "kicad", "eeschema") is what we report.-    fn class(&self, w: Window) -> Result<String, XErr> {-        let Some(r) = self.prop(w, AtomEnum::WM_CLASS.into(), AtomEnum::STRING, 256)? else { return Ok(String::new()) };-        let parts: Vec<&[u8]> = r.value.split(|b| *b == 0).filter(|p| !p.is_empty()).collect();-        Ok(parts.get(1).or(parts.first()).map(|p| String::from_utf8_lossy(p).to_string()).unwrap_or_default())-    }-    /// The client area in root coordinates (x, y, width, height).-    fn rect(&self, w: Window) -> Result<Option<(i32, i32, i32, i32)>, XErr> {-        let g = match self.conn.get_geometry(w)?.reply() {-            Ok(g) => g,-            Err(ReplyError::X11Error(_)) => return Ok(None),-            Err(e) => return Err(e.into()),-        };-        let t = match self.conn.translate_coordinates(w, self.root, 0, 0)?.reply() {-            Ok(t) => t,-            Err(ReplyError::X11Error(_)) => return Ok(None),-            Err(e) => return Err(e.into()),-        };-        Ok(Some((t.dst_x as i32, t.dst_y as i32, g.width as i32, g.height as i32)))-    }-    fn viewable(&self, w: Window) -> Result<Option<bool>, XErr> {-        match self.conn.get_window_attributes(w)?.reply() {-            Ok(a) => Ok(Some(a.map_state == MapState::VIEWABLE)),-            Err(ReplyError::X11Error(_)) => Ok(None),-            Err(e) => Err(e.into()),-        }-    }-    fn info(&self, w: Window) -> Result<Option<WindowInfo>, XErr> {-        let Some(viewable) = self.viewable(w)? else { return Ok(None) };-        let state = self.atoms_prop(w, self.atoms._NET_WM_STATE)?;-        let a = &self.atoms;-        let minimized = state.contains(&a._NET_WM_STATE_HIDDEN) || self.cardinal(w, a.WM_STATE, a.WM_STATE)? == Some(3);-        let maximized = state.contains(&a._NET_WM_STATE_MAXIMIZED_VERT) && state.contains(&a._NET_WM_STATE_MAXIMIZED_HORZ);-        let pid = self.cardinal(w, a._NET_WM_PID, AtomEnum::CARDINAL)?.unwrap_or(0);-        let mut owner = self.cardinal(w, AtomEnum::WM_TRANSIENT_FOR.into(), AtomEnum::WINDOW)?.unwrap_or(0) as u64;-        // A dialog the toolkit left without a transient-for is still a dialog: own it to the root,-        // so the dialog test (owner != 0) holds without naming a parent that does not exist.-        if owner == 0 && (state.contains(&a._NET_WM_STATE_MODAL) || self.atoms_prop(w, a._NET_WM_WINDOW_TYPE)?.contains(&a._NET_WM_WINDOW_TYPE_DIALOG)) {-            owner = self.root as u64;-        }-        let Some(rect) = self.rect(w)? else { return Ok(None) };-        Ok(Some(WindowInfo {-            hwnd: w as u64,-            title: self.title(w)?,-            class_name: self.class(w)?,-            pid,-            exe: exe_of(pid),-            visible: viewable && !minimized,-            owner,-            rect,-            minimized,-            maximized,-            hung: pid != 0 && stopped(pid),-        }))-    }-    /// The managed top-level windows, topmost first.-    fn stacking(&self) -> Result<Vec<Window>, XErr> {-        let mut v = self.windows_prop(self.root, self.atoms._NET_CLIENT_LIST_STACKING)?;-        if v.is_empty() {-            v = self.windows_prop(self.root, self.atoms._NET_CLIENT_LIST)?;-        }-        v.reverse();-        Ok(v)-    }-    fn send_wm(&self, w: Window, ty: u32, data: [u32; 5]) -> Result<(), XErr> {-        let ev = ClientMessageEvent::new(32, w, ty, data);-        self.conn.send_event(false, self.root, EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, ev)?;-        self.conn.flush()?;-        Ok(())-    }-    fn active(&self) -> Result<u64, XErr> {-        Ok(self.cardinal(self.root, self.atoms._NET_ACTIVE_WINDOW, AtomEnum::WINDOW)?.unwrap_or(0) as u64)-    }-}--fn exe_of(pid: u32) -> String {-    if pid == 0 {-        return String::new();-    }-    std::fs::read_link(format!("/proc/{pid}/exe"))-        .ok()-        .and_then(|p| p.file_name().map(|s| s.to_string_lossy().trim_end_matches(" (deleted)").to_string()))-        .or_else(|| std::fs::read_to_string(format!("/proc/{pid}/comm")).ok().map(|s| s.trim().to_string()))-        .unwrap_or_default()-}--/// A stopped (SIGSTOP / traced) process cannot answer its windows: the closest X has to "hung".-fn stopped(pid: u32) -> bool {-    std::fs::read_to_string(format!("/proc/{pid}/stat")).ok().and_then(|s| s.get(s.rfind(')')? + 2..)?.chars().next()).is_some_and(|c| c == 'T' || c == 't')-}--// ---- the Platform methods --------------------------------------------------------------pub fn list_windows() -> Result<Vec<WindowInfo>, String> {-    with(|x| {-        let mut out = Vec::new();-        for w in x.stacking()? {-            if let Some(i) = x.info(w)? {-                if i.visible {-                    out.push(i);-                }-            }-        }-        Ok(out)-    })-}--pub fn window_info(h: u64) -> Result<Option<WindowInfo>, String> {-    if h == 0 || u32::try_from(h).is_err() {-        return Ok(None);-    }-    with(|x| x.info(h as u32))-}--/// Every X sub-window under a frame. GTK draws its widgets itself, so these are the native-/// sub-windows only (the OpenGL canvas is one); they carry no text.-pub fn child_windows(h: u64) -> Result<Vec<WindowInfo>, String> {-    with(|x| {-        let top = win(h)?;-        let mut out = Vec::new();-        let mut queue = vec![top];-        while let Some(p) = queue.pop() {-            let tree = match x.conn.query_tree(p)?.reply() {-                Ok(t) => t,-                Err(ReplyError::X11Error(_)) => continue,-                Err(e) => return Err(e.into()),-            };-            for c in tree.children {-                queue.push(c);-                let Some(rect) = x.rect(c)? else { continue };-                let visible = x.viewable(c)?.unwrap_or(false);-                out.push(WindowInfo { hwnd: c as u64, title: x.title(c)?, class_name: x.class(c)?, owner: p as u64, rect, visible, ..Default::default() });-            }-        }-        Ok(out)-    })-}--pub fn post_close(h: u64) -> Result<(), String> {-    with(|x| {-        let w = win(h)?;-        // source 2 = a pager: the window manager sends the client WM_DELETE_WINDOW, as a click on its close button does.-        x.send_wm(w, x.atoms._NET_CLOSE_WINDOW, [0, 2, 0, 0, 0])-    })-}--pub fn foreground() -> Result<u64, String> {-    with(|x| x.active())-}--/// The top of the X stacking order: first visible, non-minimized, titled window of a useful size.-pub fn z_top() -> Result<u64, String> {-    with(|x| {-        for w in x.stacking()? {-            let Some(i) = x.info(w)? else { continue };-            if i.visible && !i.title.is_empty() && i.rect.2 >= 50 && i.rect.3 >= 50 {-                return Ok(w as u64);-            }-        }-        Ok(0)-    })-}--pub fn push_to_background(h: u64) -> Result<(), String> {-    with(|x| {-        let w = win(h)?;-        // _NET_RESTACK_WINDOW: source 2, no sibling, Below = lowest in its layer. No activation change.-        x.send_wm(w, x.atoms._NET_RESTACK_WINDOW, [2, 0, u32::from(StackMode::BELOW), 0, 0])?;-        // A window manager without _NET_RESTACK_WINDOW still honours a ConfigureRequest.-        x.conn.configure_window(w, &ConfigureWindowAux::new().stack_mode(StackMode::BELOW))?;-        x.conn.flush()?;-        Ok(())-    })-}--/// Map a withdrawn window without asking for focus. A minimized window stays minimized:-/// un-minimizing activates it on every X window manager.-pub fn show_no_activate(h: u64) -> Result<(), String> {-    with(|x| {-        let w = win(h)?;-        if x.viewable(w)? == Some(false) && !x.atoms_prop(w, x.atoms._NET_WM_STATE)?.contains(&x.atoms._NET_WM_STATE_HIDDEN) {-            x.conn.map_window(w)?;-            x.conn.flush()?;-        }-        Ok(())-    })-}--/// Ask the window manager to activate `h` (as a taskbar does), then wait briefly to see it hold.-pub fn activate(h: u64, wait_ms: u64) -> Result<bool, String> {-    let current = with(|x| {-        let w = win(h)?;-        let current = x.active()?;-        // un-minimize first: _NET_ACTIVE_WINDOW restores a hidden window on most WMs, WM_CHANGE_STATE on the rest-        if x.atoms_prop(w, x.atoms._NET_WM_STATE)?.contains(&x.atoms._NET_WM_STATE_HIDDEN) {-            x.send_wm(w, x.atoms.WM_CHANGE_STATE, [1, 0, 0, 0, 0])?;-        }-        x.send_wm(w, x.atoms._NET_ACTIVE_WINDOW, [2, 0, current as u32, 0, 0])?;-        Ok(current)-    })?;-    if current == h {-        return Ok(true);-    }-    let until = Instant::now() + Duration::from_millis(wait_ms);-    while Instant::now() < until {-        if foreground()? == h {-            return Ok(true);-        }-        std::thread::sleep(Duration::from_millis(50));-    }-    Ok(foreground()? == h)-}--pub fn placement(h: u64) -> Result<Placement, String> {-    with(|x| {-        let w = win(h)?;-        let (px, py, width, height) = x.rect(w)?.ok_or_else(|| XErr::Other(format!("window {h} is gone")))?;-        let st = x.atoms_prop(w, x.atoms._NET_WM_STATE)?;-        let maximized = st.contains(&x.atoms._NET_WM_STATE_MAXIMIZED_VERT) && st.contains(&x.atoms._NET_WM_STATE_MAXIMIZED_HORZ);-        Ok(Placement { x: px, y: py, width, height, maximized })-    })-}--pub fn set_placement(h: u64, p: &Placement) -> Result<(), String> {-    with(|x| {-        let w = win(h)?;-        let a = &x.atoms;-        // _NET_WM_STATE: action 1 = add, 0 = remove, both maximize atoms, source 2.-        let action = u32::from(p.maximized);-        x.send_wm(w, a._NET_WM_STATE, [action, a._NET_WM_STATE_MAXIMIZED_VERT, a._NET_WM_STATE_MAXIMIZED_HORZ, 2, 0])?;-        if !p.maximized && p.width > 0 && p.height > 0 {-            // The client area was reported, so position the client: subtract the frame the WM adds.-            let ext = x.prop(w, a._NET_FRAME_EXTENTS, AtomEnum::CARDINAL, 4)?.and_then(|r| r.value32().map(|v| v.collect::<Vec<u32>>())).unwrap_or_default();-            let (left, top) = (ext.first().copied().unwrap_or(0) as i32, ext.get(2).copied().unwrap_or(0) as i32);-            let aux = ConfigureWindowAux::new().x(p.x - left).y(p.y - top).width(p.width as u32).height(p.height as u32);-            x.conn.configure_window(w, &aux)?;-        }-        x.conn.flush()?;-        Ok(())-    })-}--pub fn on_a_monitor(rect: (i32, i32, i32, i32)) -> Result<bool, String> {-    with(|x| {-        let (sx, sy) = (x.screen_w as i32, x.screen_h as i32);-        let (l, t, w, h) = rect;-        Ok(w > 0 && h > 0 && l < sx && t < sy && l + w > 0 && t + h > 0)-    })-}--/// The window's pixels, top-down RGB8. With a compositing window manager (GNOME, KDE,-/// Cinnamon, and XWayland always) a covered window still reads back its own contents.-pub fn capture_window(h: u64) -> Result<Capture, String> {-    with(|x| {-        let w = win(h)?;-        if x.atoms_prop(w, x.atoms._NET_WM_STATE)?.contains(&x.atoms._NET_WM_STATE_HIDDEN) {-            return Err(XErr::Other(format!("window {h} is minimized; it has no pixels to read (show it first)")));-        }-        let g = x.conn.get_geometry(w)?.reply()?;-        let (width, height) = (g.width, g.height);-        if width == 0 || height == 0 {-            return Err(XErr::Other(format!("window {h} has an empty rect")));-        }-        // GetImage replies are bounded by the maximum request length; read in bands of rows.-        let setup = x.conn.setup();-        let max_bytes = (x.conn.maximum_request_bytes().max(262_144) - 64) as u32;-        let rows = (max_bytes / (width as u32 * 4)).clamp(1, height as u32) as u16;-        let bpp = setup.pixmap_formats.iter().find(|f| f.depth == g.depth).map(|f| f.bits_per_pixel).unwrap_or(32);-        if bpp != 32 && bpp != 24 {-            return Err(XErr::Other(format!("window depth {} ({bpp} bits per pixel) is not a true-colour visual", g.depth)));-        }-        let lsb = setup.image_byte_order == ImageOrder::LSB_FIRST;-        let mut rgb = Vec::with_capacity(width as usize * height as usize * 3);-        let mut y = 0u16;-        while y < height {-            let band = rows.min(height - y);-            let img = x.conn.get_image(ImageFormat::Z_PIXMAP, w, 0, y as i16, width, band, !0)?.reply()?;-            let step = (bpp / 8) as usize;-            let stride = img.data.len() / band as usize;-            for r in 0..band as usize {-                let row = &img.data[r * stride..];-                for c in 0..width as usize {-                    let p = &row[c * step..c * step + step];-                    // LSB first: B G R (X); MSB first: (X) R G B.-                    let (rr, gg, b) = if lsb { (p[2], p[1], p[0]) } else { (p[step - 3], p[step - 2], p[step - 1]) };-                    rgb.extend_from_slice(&[rr, gg, b]);-                }-            }-            y += band;-        }-        Ok(Capture { width: width as u32, height: height as u32, rgb })-    })-}--/// The OpenGL canvas: wxGLCanvas is a native X sub-window; the largest viewable one is the canvas.-pub fn gl_canvas_rect(h: u64) -> Result<Option<(i32, i32, i32, i32)>, String> {-    let frame = window_info(h)?.ok_or_else(|| format!("window {h} is gone"))?;-    let best = child_windows(h)?.into_iter().filter(|c| c.visible && c.rect.2 >= 100 && c.rect.3 >= 100).max_by_key(|c| c.rect.2 as i64 * c.rect.3 as i64);-    Ok(best.map(|c| (c.rect.0 - frame.rect.0, c.rect.1 - frame.rect.1, c.rect.2, c.rect.3)))-}--/// Seconds since the user last touched keyboard or mouse. Mutter (GNOME, Wayland or X11)-/// answers for the whole session through its idle monitor; other desktops through the-/// X screensaver extension (X input only).-pub fn seconds_since_input() -> Result<f64, String> {-    let mut cmd = std::process::Command::new("gdbus");-    cmd.args(["call", "--session", "--dest", "org.gnome.Mutter.IdleMonitor", "--object-path", "/org/gnome/Mutter/IdleMonitor/Core", "--method", "org.gnome.Mutter.IdleMonitor.GetIdletime", "--timeout", "2"]);-    cmd.stdin(std::process::Stdio::null()).stderr(std::process::Stdio::null());-    if let Ok(o) = cmd.output() {-        // "(uint64 1234,)"-        let s = String::from_utf8_lossy(&o.stdout);-        if let Some(ms) = s.split_whitespace().nth(1).and_then(|t| t.trim_end_matches([',', ')']).parse::<u64>().ok()) {-            return Ok(ms as f64 / 1000.0);-        }-    }-    with(|x| {-        use x11rb::protocol::screensaver::ConnectionExt as _;-        if x.conn.extension_information(x11rb::protocol::screensaver::X11_EXTENSION_NAME)?.is_none() {-            return Err(XErr::Other("no idle clock: neither Mutter's idle monitor nor the X screensaver extension answered".into()));-        }-        let r = x.conn.screensaver_query_info(x.root)?.reply()?;-        Ok(r.ms_since_user_input as f64 / 1000.0)-    })-}--/// Is a window manager running that speaks EWMH (the bridge's window verbs need one)?-#[allow(dead_code)]-pub fn wm_name() -> Result<Option<String>, String> {-    with(|x| {-        let Some(check) = x.cardinal(x.root, x.atoms._NET_SUPPORTING_WM_CHECK, AtomEnum::WINDOW)? else { return Ok(None) };-        Ok(Some(x.title(check)?).filter(|s| !s.is_empty()))-    })-}--/// The desktop's own windows (desktop icons, panels, docks): never a foreground target.-pub fn is_shell_window(h: u64) -> bool {-    with(|x| {-        let w = win(h)?;-        let t = x.atoms_prop(w, x.atoms._NET_WM_WINDOW_TYPE)?;-        Ok(t.contains(&x.atoms._NET_WM_WINDOW_TYPE_DESKTOP) || t.contains(&x.atoms._NET_WM_WINDOW_TYPE_DOCK))-    })-    .unwrap_or(false)-}--/// The X keysym for a Windows virtual-key code (the bridge's key names resolve to these).-fn keysym_for(vk: u16, ch: Option<char>) -> Option<u32> {-    if let Some(c) = ch {-        // Latin-1 characters are their own keysym; others are 0x01000000 + code point.-        let u = c as u32;-        return Some(if (0x20..=0x7e).contains(&u) || (0xa0..=0xff).contains(&u) { u } else { 0x0100_0000 + u });-    }-    Some(match vk {-        0x08 => 0xff08,                       // Back-        0x09 => 0xff09,                       // Tab-        0x0D => 0xff0d,                       // Return-        0x1B => 0xff1b,                       // Escape-        0x20 => 0x20,                         // space-        0x21 => 0xff55,                       // Prior-        0x22 => 0xff56,                       // Next-        0x23 => 0xff57,                       // End-        0x24 => 0xff50,                       // Home-        0x25 => 0xff51,                       // Left-        0x26 => 0xff52,                       // Up-        0x27 => 0xff53,                       // Right-        0x28 => 0xff54,                       // Down-        0x2D => 0xff63,                       // Insert-        0x2E => 0xffff,                       // Delete-        0x30..=0x39 => vk as u32,             // 0-9-        0x41..=0x5A => vk as u32 + 0x20,      // a-z-        0x70..=0x7B => 0xffbe + (vk as u32 - 0x70), // F1-F12-        0x6B => 0xffab,                       // KP_Add-        0x6D => 0xffad,                       // KP_Subtract-        0xBB => 0x3d,                         // = (VK_OEM_PLUS)-        0xBD => 0x2d,                         // - (VK_OEM_MINUS)-        _ => return None,-    })-}--/// Does X keyboard focus sit inside `w` (on it or on one of its sub-windows)?-fn focus_inside(x: &X, w: Window) -> Result<bool, XErr> {-    let mut f = x.conn.get_input_focus()?.reply()?.focus;-    for _ in 0..32 {-        if f == w {-            return Ok(true);-        }-        if f == x11rb::NONE || f == 1 || f == x.root {-            return Ok(false); // None / PointerRoot / the root-        }-        match x.conn.query_tree(f)?.reply() {-            Ok(t) => f = t.parent,-            Err(_) => return Ok(false),-        }-    }-    Ok(false)-}--/// A key press and release for one window. GTK ignores synthetic (XSendEvent) keys, so the key-/// goes through XTest, and ONLY while X keyboard focus already sits in that window: the key then-/// reaches nothing else, and no focus or stacking changes. On a Wayland desktop XTest input stays-/// inside XWayland, so the user's Wayland windows never see it. Otherwise this refuses.-pub fn post_key(h: u64, vk: u16, ch: Option<char>) -> Result<(), String> {-    use x11rb::protocol::xproto::{KEY_PRESS_EVENT, KEY_RELEASE_EVENT};-    use x11rb::protocol::xtest::ConnectionExt as _;-    let sym = keysym_for(vk, ch).ok_or_else(|| format!("no X keysym for virtual key {vk:#x}"))?;-    with(|x| {-        let w = win(h)?;-        if x.conn.extension_information(x11rb::protocol::xtest::X11_EXTENSION_NAME)?.is_none() {-            return Err(XErr::Other("the X server has no XTEST extension".into()));-        }-        if !focus_inside(x, w)? {-            return Err(XErr::Other(format!(-                "not_focused: window {h} does not have the X keyboard focus. KiCad (GTK) ignores synthetic key events, so a key reaches only the focused KiCad window; the bridge never moves focus to type"-            )));-        }-        let setup = x.conn.setup();-        let (min, max) = (setup.min_keycode, setup.max_keycode);-        let map = x.conn.get_keyboard_mapping(min, max - min + 1)?.reply()?;-        let per = (map.keysyms_per_keycode as usize).max(1);-        let found = map.keysyms.chunks(per).enumerate().find_map(|(i, syms)| syms.iter().position(|s| *s == sym).filter(|l| *l <= 1).map(|l| (min + i as u8, l == 1)));-        let (code, shift) = found.ok_or_else(|| XErr::Other(format!("keysym {sym:#x} is not on this keyboard map")))?;-        let shift_code = map.keysyms.chunks(per).position(|syms| syms.first() == Some(&0xffe1)).map(|i| min + i as u8);-        let press = |ty: u8, c: u8| x.conn.xtest_fake_input(ty, c, x11rb::CURRENT_TIME, x.root, 0, 0, 0);-        if shift {-            if let Some(sc) = shift_code {-                press(KEY_PRESS_EVENT, sc)?;-            }-        }-        press(KEY_PRESS_EVENT, code)?;-        press(KEY_RELEASE_EVENT, code)?;-        if shift {-            if let Some(sc) = shift_code {-                press(KEY_RELEASE_EVENT, sc)?;-            }-        }-        x.conn.flush()?;-        Ok(())-    })-}