app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Drive your own KiCad from an AI, in the background: open every editor, read the whole board as data (the EDA Bridge Standard board model), stack-up, part heights, netlists, place, route, pour, DRC and exports.
← Commit history
Delete rust/crates/kicad-platform/src/linux/x11.rs
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(())- })-}