car-desktop 0.9.0

OS-level screen capture, accessibility inspection, and input synthesis for Common Agent Runtime
Documentation
//! OS-level screen capture, accessibility inspection, and input
//! synthesis for the Common Agent Runtime.
//!
//! `car-desktop` is the sibling of `car-browser`. Both emit the
//! same `UiMap` perception payload, register tool verbs with the
//! same async shape, and plug into the executor's OPA loop
//! interchangeably. The difference is reach: `car-browser` sees
//! whatever a Chromium instance renders, `car-desktop` sees every
//! window the host OS renders — native apps, Electron / Tauri
//! chrome outside the webview, a simulator window, Tokhn's own
//! Bevy GUI.
//!
//! The canonical entry point is the `DesktopBackend` trait. v1
//! ships one concrete backend (`macos::MacBackend`) behind the
//! `macos` feature flag; `windows::WindowsBackend` and
//! `linux::LinuxBackend` compile on every target but return
//! `CarDesktopError::PlatformUnsupported` from every method. See
//! `docs/CAR_DESKTOP.md` for the Q2/Q3 roadmap.
//!
//! # Safety
//!
//! Input synthesis is the highest-consequence primitive in this
//! crate. Six hard-coded rules apply to every `click`/`type`/
//! `keypress` path:
//!
//! 1. Target window required (every request carries a `WindowHandle`).
//! 2. Clicks clamped to the target window's frame (`OutOfTargetWindow`).
//! 3. Destructive-label fuzzy match (`DestructiveActionGated` unless
//!    `unsafe_ok: true`).
//! 4. Rate limit of 8 events/sec/window (`RateLimited`).
//! 5. Dry-run flag per request (logs intent without posting).
//! 6. Global Esc-Esc kill switch while any mission is in flight.
//!
//! See `safety.rs` for the cross-platform pieces and
//! `macos::input` for the platform hook-up.

pub mod backend;
pub mod errors;
pub mod models;
pub mod perception;
pub mod safety;
pub mod tools;

#[cfg(feature = "macos")]
pub mod macos;

// `linux` and `windows` are pure-Rust stub backends that return
// PlatformUnsupported from every method. They compile on every
// target so `default_backend()` has a fallback when the macOS
// feature isn't enabled, and so cross-platform callers can reach
// either constructor directly.
pub mod linux;
pub mod windows;

pub use backend::DesktopBackend;
pub use errors::{CarDesktopError, Permission, Result};
pub use models::{
    A11yNode, Bounds, ClickRequest, DisplayId, Frame, Key, KeyPressRequest, Modifier,
    MouseButton, PermissionRequest, PermissionSnapshot, TypeRequest, UiMap, WindowFilter,
    WindowFrame, WindowHandle, WindowInfo,
};
pub use perception::{empty_a11y_uimap, populated_uimap, AX_DEPTH_CAP, AX_NODE_CAP};
pub use safety::{destructive_word_in, PerWindowRateLimiter, DESTRUCTIVE_WORDS, POST_EVENT_SETTLE};

#[cfg(feature = "macos")]
pub use macos::MacBackend;

pub use linux::LinuxBackend;
pub use windows::WindowsBackend;

/// Construct the best-available `DesktopBackend` for the current
/// target. Returns a boxed trait object so callers can treat every
/// platform uniformly. On macOS (with the `macos` feature enabled,
/// which it is by `default`) this returns a `MacBackend`; on other
/// targets it returns a `WindowsBackend`/`LinuxBackend` that fail
/// every call with `PlatformUnsupported`.
pub fn default_backend() -> Box<dyn DesktopBackend> {
    #[cfg(all(target_os = "macos", feature = "macos"))]
    {
        Box::new(macos::MacBackend::new())
    }
    #[cfg(all(target_os = "macos", not(feature = "macos")))]
    {
        // macOS target without the macos feature — fall back to the
        // PlatformUnsupported Linux backend since the app still
        // wants a Send + Sync DesktopBackend it can call with a
        // predictable error.
        Box::new(linux::LinuxBackend::new())
    }
    #[cfg(all(not(target_os = "macos"), target_os = "windows"))]
    {
        Box::new(windows::WindowsBackend::new())
    }
    #[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
    {
        Box::new(linux::LinuxBackend::new())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn default_backend_compiles_and_returns_a_boxed_trait_object() {
        // The point of this test is that construction never panics
        // and the returned object answers list_windows with a
        // well-defined outcome. On macOS with the macos feature,
        // CD-02 onward means list_windows returns Ok(non-empty
        // Vec). On every other config it returns an
        // Err(PlatformUnsupported | NotYetImplemented).
        let backend = default_backend();
        let result = backend.list_windows(WindowFilter::default()).await;
        if cfg!(all(target_os = "macos", feature = "macos")) {
            let windows = result.expect("list_windows must succeed on macOS+feature");
            assert!(
                !windows.is_empty(),
                "macOS should surface at least one on-screen window"
            );
        } else {
            assert!(
                matches!(
                    result,
                    Err(CarDesktopError::PlatformUnsupported { .. })
                        | Err(CarDesktopError::NotYetImplemented { .. })
                ),
                "unexpected error variant: {result:?}"
            );
        }
    }

    #[test]
    fn scaffold_exports_core_types() {
        // Compile-time surface check: if any of these go missing
        // from the public API this test stops compiling.
        let _e: CarDesktopError = CarDesktopError::PlatformUnsupported { platform: "x" };
        let _s: PermissionSnapshot = PermissionSnapshot {
            screen_recording: false,
            accessibility: false,
            needs_restart: false,
        };
        let _h: WindowHandle = WindowHandle::new(1, 1);
        let _f: WindowFilter = WindowFilter::default();
        let _p: Permission = Permission::ScreenRecording;
    }
}