pub mod click;
pub mod driver;
pub mod list_windows;
pub mod mouse_move;
pub mod press_key;
pub mod screenshot;
pub mod scroll;
pub mod type_text;
use std::path::Path;
use std::process::Command;
use serde_json::Value;
use crate::domain::{ToolMetadata, ToolOutcome, ToolRunMetadata};
pub use click::ClickTool;
pub use driver::ComputerUseDriver;
pub use list_windows::ListWindowsTool;
pub use mouse_move::MouseMoveTool;
pub use press_key::PressKeyTool;
pub use screenshot::ScreenshotTool;
pub use scroll::ScrollTool;
pub use type_text::TypeTextTool;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Backend {
X11,
Wayland,
MacOS,
Windows,
Unsupported,
}
impl Backend {
pub fn is_usable(self) -> bool {
!matches!(self, Backend::Unsupported)
}
}
pub fn probe() -> Backend {
if cfg!(target_os = "macos") {
if has_command("screencapture") {
return Backend::MacOS;
}
return Backend::Unsupported;
}
if cfg!(target_os = "windows") {
return Backend::Unsupported;
}
if std::env::var("WAYLAND_DISPLAY").is_ok()
&& has_command("grim")
&& (has_command("ydotool") || has_command("wtype"))
{
return Backend::Wayland;
}
if std::env::var("DISPLAY").is_ok()
&& has_command("scrot")
&& has_command("xdotool")
&& xdpyinfo_alive()
{
return Backend::X11;
}
Backend::Unsupported
}
pub fn display_is_reachable(backend: Backend) -> bool {
match backend {
Backend::X11 => xdpyinfo_alive(),
Backend::Wayland => std::env::var("WAYLAND_DISPLAY").is_ok(),
Backend::MacOS | Backend::Windows => true,
Backend::Unsupported => false,
}
}
pub(super) fn has_command(name: &str) -> bool {
Command::new("which")
.arg(name)
.output()
.map(|o| o.status.success() && !o.stdout.is_empty())
.unwrap_or(false)
}
fn xdpyinfo_alive() -> bool {
if !has_command("xdpyinfo") {
return Command::new("xdotool")
.arg("getactivewindow")
.output()
.map(|o| o.status.success())
.unwrap_or(false);
}
match Command::new("timeout").arg("0.2").arg("xdpyinfo").output() {
Ok(o) => o.status.success(),
Err(_) => {
Command::new("xdpyinfo")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
},
}
}
#[allow(dead_code)]
pub(crate) fn path_stem(p: &Path) -> String {
p.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
.unwrap_or_else(|| "unknown".to_string())
}
pub(super) fn computer_use_success(
action: &'static str,
params: Value,
output: String,
duration_secs: f64,
) -> ToolOutcome {
ToolOutcome::success(output, format!("{} completed", action), duration_secs).with_metadata(
ToolRunMetadata {
detail: ToolMetadata::ComputerUse {
action: action.to_string(),
params,
},
..ToolRunMetadata::default()
},
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn backend_unsupported_is_not_usable() {
assert!(!Backend::Unsupported.is_usable());
assert!(Backend::X11.is_usable());
assert!(Backend::Wayland.is_usable());
assert!(Backend::MacOS.is_usable());
}
#[test]
fn probe_does_not_panic_on_headless() {
let _ = probe();
}
}