mod keysym;mod recording;mod wayland;mod x11;use std::sync::OnceLock;use async_trait::async_trait;pub use recording::{Recorder, post_process_recording};use warp_errors::report_error;use crate::{ActionResult, Options, TargetedAction};/// Returns true if a Wayland environment is available.fn is_wayland_available() -> bool { std::env::var("WAYLAND_DISPLAY") .map(|v| !v.is_empty()) .unwrap_or(false)}/// Returns true if an X11 environment is available.fn is_x11_available() -> bool { std::env::var("DISPLAY") .map(|v| !v.is_empty()) .unwrap_or(false)}pub fn is_supported_on_current_platform() -> bool { is_wayland_available() || is_x11_available()}/// Reports whether background, per-window control is available. On X11 it is implemented with a/// dedicated XInput2 (MPX) master device pair, so it requires an XI2-capable server; the Wayland/// path drives inputs through XDG portals, which have no per-window targeting.pub fn background_supported() -> bool { if is_wayland_available() || !is_x11_available() { return false; } // The probe opens an X connection; cache it since this is consulted on every agent request. static SUPPORTED: OnceLock<bool> = OnceLock::new(); *SUPPORTED.get_or_init(x11::probe_background_support)}/// Ends the background computer-use session owned by `owner`. On X11 this removes the/// session's shared agent seat (and its on-screen cursor), implicitly releasing any input state/// it still holds. Wayland has no background per-window control, so there is nothing to tear/// down.pub fn end_background_session(owner: &str) { if is_wayland_available() || !is_x11_available() { return; } x11::end_background_session(owner);}/// Enumerates the on-screen windows so a caller can pick one to target. Only supported on X11;/// returns an empty list on Wayland or when no display is reachable.pub fn enumerate_windows() -> Vec<crate::WindowInfo> { if is_wayland_available() || !is_x11_available() { return Vec::new(); } x11::enumerate_windows()}/// Lists on-screen windows as a formatted diagnostic string. Only supported on X11.pub fn list_windows() -> Result<String, String> { if is_wayland_available() || !is_x11_available() { return Err("Window listing is only supported on X11.".to_string()); } x11::list_windows()}pub struct Actor { inner: ActorInner,}enum ActorInner { /// Wayland environment (uses XDG portals for input and screenshots). Wayland(Box<wayland::Actor>), /// X11 environment (uses XTEST for input). X11(Box<x11::Actor>), /// No supported display server available. Unsupported,}impl Actor { pub fn new() -> Self { let inner = if is_wayland_available() { // On Wayland, use native XDG portals for input and screenshots. match wayland::Actor::new() { Ok(actor) => ActorInner::Wayland(Box::new(actor)), Err(e) => { report_error!(anyhow::anyhow!(e).context("Failed to create Wayland actor")); ActorInner::Unsupported } } } else if is_x11_available() { // Pure X11 environment. match x11::Actor::new() { Ok(actor) => ActorInner::X11(Box::new(actor)), Err(e) => { report_error!(anyhow::anyhow!(e).context("Failed to create X11 actor")); ActorInner::Unsupported } } } else { ActorInner::Unsupported }; Self { inner } }}#[async_trait]impl super::Actor for Actor { fn platform(&self) -> Option<super::Platform> { match &self.inner { ActorInner::Wayland(actor) => actor.platform(), ActorInner::X11(actor) => actor.platform(), ActorInner::Unsupported => None, } } fn set_background_session_owner(&mut self, owner: Option<String>) { match &mut self.inner { // The X11 actor keys its shared, session-scoped agent seat by the owner. ActorInner::X11(actor) => actor.set_background_session_owner(owner), // Wayland has no background per-window control; nothing to tag. ActorInner::Wayland(_) | ActorInner::Unsupported => {} } } async fn perform_actions( &mut self, actions: &[TargetedAction], options: Options, ) -> Result<ActionResult, String> { match &mut self.inner { ActorInner::Wayland(actor) => actor.perform_actions(actions, options).await, ActorInner::X11(actor) => actor.perform_actions(actions, options).await, ActorInner::Unsupported => Err( "Computer use is not available: No supported display server detected. \ X11 or Wayland is required." .to_string(), ), } }}
mod keysym;
mod recording;
mod wayland;
mod x11;
use std::sync::OnceLock;
use async_trait::async_trait;
pub use recording::{Recorder, post_process_recording};
use warp_errors::report_error;
use crate::{ActionResult, Options, TargetedAction};
/// Returns true if a Wayland environment is available.
fn is_wayland_available() -> bool {
std::env::var("WAYLAND_DISPLAY")
.map(|v| !v.is_empty())
.unwrap_or(false)
}
/// Returns true if an X11 environment is available.
fn is_x11_available() -> bool {
std::env::var("DISPLAY")
.map(|v| !v.is_empty())
.unwrap_or(false)
}
pub fn is_supported_on_current_platform() -> bool {
is_wayland_available() || is_x11_available()
}
/// Reports whether background, per-window control is available. On X11 it is implemented with a
/// dedicated XInput2 (MPX) master device pair, so it requires an XI2-capable server; the Wayland
/// path drives inputs through XDG portals, which have no per-window targeting.
pub fn background_supported() -> bool {
if is_wayland_available() || !is_x11_available() {
return false;
}
// The probe opens an X connection; cache it since this is consulted on every agent request.
static SUPPORTED: OnceLock<bool> = OnceLock::new();
*SUPPORTED.get_or_init(x11::probe_background_support)
}
/// Ends the background computer-use session owned by `owner`. On X11 this removes the
/// session's shared agent seat (and its on-screen cursor), implicitly releasing any input state
/// it still holds. Wayland has no background per-window control, so there is nothing to tear
/// down.
pub fn end_background_session(owner: &str) {
if is_wayland_available() || !is_x11_available() {
return;
}
x11::end_background_session(owner);
}
/// Enumerates the on-screen windows so a caller can pick one to target. Only supported on X11;
/// returns an empty list on Wayland or when no display is reachable.
pub fn enumerate_windows() -> Vec<crate::WindowInfo> {
if is_wayland_available() || !is_x11_available() {
return Vec::new();
}
x11::enumerate_windows()
}
/// Lists on-screen windows as a formatted diagnostic string. Only supported on X11.
pub fn list_windows() -> Result<String, String> {
if is_wayland_available() || !is_x11_available() {
return Err("Window listing is only supported on X11.".to_string());
}
x11::list_windows()
}
pub struct Actor {
inner: ActorInner,
}
enum ActorInner {
/// Wayland environment (uses XDG portals for input and screenshots).
Wayland(Box<wayland::Actor>),
/// X11 environment (uses XTEST for input).
X11(Box<x11::Actor>),
/// No supported display server available.
Unsupported,
}
impl Actor {
pub fn new() -> Self {
let inner = if is_wayland_available() {
// On Wayland, use native XDG portals for input and screenshots.
match wayland::Actor::new() {
Ok(actor) => ActorInner::Wayland(Box::new(actor)),
Err(e) => {
report_error!(anyhow::anyhow!(e).context("Failed to create Wayland actor"));
ActorInner::Unsupported
}
}
} else if is_x11_available() {
// Pure X11 environment.
match x11::Actor::new() {
Ok(actor) => ActorInner::X11(Box::new(actor)),
Err(e) => {
report_error!(anyhow::anyhow!(e).context("Failed to create X11 actor"));
ActorInner::Unsupported
}
}
} else {
ActorInner::Unsupported
};
Self { inner }
}
}
#[async_trait]
impl super::Actor for Actor {
fn platform(&self) -> Option<super::Platform> {
match &self.inner {
ActorInner::Wayland(actor) => actor.platform(),
ActorInner::X11(actor) => actor.platform(),
ActorInner::Unsupported => None,
}
}
fn set_background_session_owner(&mut self, owner: Option<String>) {
match &mut self.inner {
// The X11 actor keys its shared, session-scoped agent seat by the owner.
ActorInner::X11(actor) => actor.set_background_session_owner(owner),
// Wayland has no background per-window control; nothing to tag.
ActorInner::Wayland(_) | ActorInner::Unsupported => {}
}
}
async fn perform_actions(
&mut self,
actions: &[TargetedAction],
options: Options,
) -> Result<ActionResult, String> {
match &mut self.inner {
ActorInner::Wayland(actor) => actor.perform_actions(actions, options).await,
ActorInner::X11(actor) => actor.perform_actions(actions, options).await,
ActorInner::Unsupported => Err(
"Computer use is not available: No supported display server detected. \
X11 or Wayland is required."
.to_string(),
),
}
}
}