Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions src/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::windowing::registry::{
self, COSMIC_WAYLAND_BACKEND, GNOME_SHELL_EXTENSION_BACKEND, GNOME_SHELL_INTROSPECT_BACKEND,
HYPRLAND_BACKEND, KWIN_BACKEND,
HYPRLAND_BACKEND, I3_BACKEND, KWIN_BACKEND, X11_BACKEND,
};
use schemars::JsonSchema;
use serde::Serialize;
Expand Down Expand Up @@ -240,6 +240,23 @@ fn capability_map(
if windowing.cosmic_helper.ok {
window_backends.push("cosmic".to_string());
}
// i3 and the generic X11/EWMH backend have no dedicated WindowingReport
// field; read them from the probe map (tried last) so the capability list
// matches the backends the registry will actually use.
if windowing
.backends
.get(I3_BACKEND)
.is_some_and(|check| check.ok)
{
window_backends.push(I3_BACKEND.to_string());
}
if windowing
.backends
.get(X11_BACKEND)
.is_some_and(|check| check.ok)
{
window_backends.push(X11_BACKEND.to_string());
}

let mut accessibility_backends = Vec::new();
if accessibility.at_spi_enabled.ok || accessibility.toolkit_accessibility.ok {
Expand Down Expand Up @@ -578,14 +595,16 @@ fn windowing_report(platform: &PlatformReport) -> WindowingReport {
let can_focus_apps = probes.iter().any(|probe| probe.can_focus_apps);
let can_focus_windows = probes.iter().any(|probe| probe.can_focus_windows);
let note = if can_list_windows {
if cosmic_helper.ok && is_cosmic_wayland_platform(platform) {
if !can_focus_windows {
"A window listing backend is available for list_windows, but focused-window and targeted-input verification are unavailable (for example wmctrl is present but xprop is missing on X11)."
} else if cosmic_helper.ok && is_cosmic_wayland_platform(platform) {
"A COSMIC Wayland window backend is available for list_windows, focused_window, and targeted input verification."
} else if kwin.ok {
"A KWin/Plasma window backend is available for list_windows, focused_window, and targeted input verification."
} else if hyprland.ok {
"A Hyprland window backend is available for list_windows, focused_window, and targeted input verification."
} else {
"A GNOME window listing backend is available for list_windows, focused_window, and targeted input verification."
"A window listing backend is available for list_windows, focused_window, and targeted input verification."
Comment thread
avifenesh marked this conversation as resolved.
}
} else {
"Window listing is unavailable or denied. Computer Use can still use screenshots, AT-SPI, and global ydotool input, but targeted window input cannot be verified. On GNOME, run setup_window_targeting to install the optional GNOME Shell extension backend. On COSMIC, ensure the bundled COSMIC helper is present and can connect to the session. On KDE/Plasma, ensure KWin exposes org.kde.KWin scripting on the session bus. On Hyprland, ensure hyprctl is available in the session."
Expand Down
36 changes: 16 additions & 20 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1243,7 +1243,7 @@ impl ComputerUseLinux {

#[tool(
name = "move_window",
description = "Move a window to a new desktop position (frame top-left in desktop coordinates). Useful to recover windows that are partially off-screen. Requires the computer-use-linux GNOME Shell extension.",
description = "Move a window to a new desktop position (frame top-left in desktop coordinates). Useful to recover windows that are partially off-screen. Works through the computer-use-linux GNOME Shell extension or a generic X11/EWMH window manager (wmctrl).",
annotations(
read_only_hint = false,
destructive_hint = false,
Expand All @@ -1257,16 +1257,15 @@ impl ComputerUseLinux {
) -> Json<WindowGeometryOutput> {
let received = Some(serde_json::json!(params.clone()));
let target = params.target.clone().into_target();
self.window_geometry_op(received, &target, |window_id| async move {
crate::windowing::backends::gnome::move_extension_window(window_id, params.x, params.y)
.await
self.window_geometry_op(received, &target, |window| async move {
registry::move_window(&window, params.x, params.y).await
})
.await
}

#[tool(
name = "resize_window",
description = "Resize a window to a new frame width/height in desktop pixels, unmaximizing it first if needed. Useful to fit a window fully on-screen. Requires the computer-use-linux GNOME Shell extension.",
description = "Resize a window to a new frame width/height in desktop pixels, unmaximizing it first if needed. Useful to fit a window fully on-screen. Works through the computer-use-linux GNOME Shell extension or a generic X11/EWMH window manager (wmctrl).",
annotations(
read_only_hint = false,
destructive_hint = false,
Expand All @@ -1280,13 +1279,8 @@ impl ComputerUseLinux {
) -> Json<WindowGeometryOutput> {
let received = Some(serde_json::json!(params.clone()));
let target = params.target.clone().into_target();
self.window_geometry_op(received, &target, |window_id| async move {
crate::windowing::backends::gnome::resize_extension_window(
window_id,
params.width,
params.height,
)
.await
self.window_geometry_op(received, &target, |window| async move {
registry::resize_window(&window, params.width, params.height).await
})
.await
}
Expand Down Expand Up @@ -2219,7 +2213,7 @@ impl ComputerUseLinux {
op: F,
) -> Json<WindowGeometryOutput>
where
F: FnOnce(u64) -> Fut,
F: FnOnce(crate::windowing::WindowInfo) -> Fut,
Fut: Future<Output = Result<String>>,
{
let windows = match list_windows().await {
Expand All @@ -2229,29 +2223,31 @@ impl ComputerUseLinux {
return Json(WindowGeometryOutput {
ok: false,
implemented: true,
backend: crate::windowing::GNOME_SHELL_EXTENSION_BACKEND.to_string(),
backend: "unknown".to_string(),
window: None,
message: format!("Window listing failed: {error}"),
permissions_hint: window_permission_hint(&error),
received,
});
}
};
let window_id = match resolve_window_target(&windows, target) {
Ok(window) => window.window_id,
let window = match resolve_window_target(&windows, target) {
Ok(window) => window.clone(),
Err(error) => {
return Json(WindowGeometryOutput {
ok: false,
implemented: true,
backend: crate::windowing::GNOME_SHELL_EXTENSION_BACKEND.to_string(),
backend: "unknown".to_string(),
window: None,
message: format!("{error:#}"),
permissions_hint: None,
received,
});
}
};
match op(window_id).await {
let backend = window.backend.clone();
let window_id = window.window_id;
match op(window).await {
Ok(message) => {
// Re-query so the caller sees the compositor-final geometry
// (tiling constraints, minimum sizes, etc. may adjust it).
Expand All @@ -2269,7 +2265,7 @@ impl ComputerUseLinux {
Json(WindowGeometryOutput {
ok: true,
implemented: true,
backend: crate::windowing::GNOME_SHELL_EXTENSION_BACKEND.to_string(),
backend,
window,
message,
permissions_hint: None,
Expand All @@ -2281,7 +2277,7 @@ impl ComputerUseLinux {
Json(WindowGeometryOutput {
ok: false,
implemented: true,
backend: crate::windowing::GNOME_SHELL_EXTENSION_BACKEND.to_string(),
backend,
window: None,
permissions_hint: window_permission_hint(&error),
message: error,
Expand Down
1 change: 1 addition & 0 deletions src/windowing/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pub mod gnome;
pub mod hyprland;
pub mod i3;
pub mod kwin;
pub mod x11;
Loading