mirror of
https://github.com/harivansh-afk/deskctl.git
synced 2026-04-18 05:01:56 +00:00
stabilize (#3)
* specs * Stabilize deskctl runtime foundation Co-authored-by: Codex <noreply@openai.com> * opsx archive --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
d487a60209
commit
6dce22eaef
22 changed files with 1289 additions and 295 deletions
|
|
@ -1,25 +1,41 @@
|
|||
pub mod annotate;
|
||||
pub mod x11;
|
||||
|
||||
use crate::core::types::Snapshot;
|
||||
use anyhow::Result;
|
||||
use image::RgbaImage;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BackendWindow {
|
||||
pub native_id: u32,
|
||||
pub title: String,
|
||||
pub app_name: String,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub focused: bool,
|
||||
pub minimized: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub trait DesktopBackend: Send {
|
||||
/// Capture a screenshot and return a z-ordered window tree with @wN refs.
|
||||
fn snapshot(&mut self, annotate: bool) -> Result<Snapshot>;
|
||||
/// Collect z-ordered windows for read-only queries and targeting.
|
||||
fn list_windows(&mut self) -> Result<Vec<BackendWindow>>;
|
||||
|
||||
/// Focus a window by its X11 window ID.
|
||||
fn focus_window(&mut self, xcb_id: u32) -> Result<()>;
|
||||
/// Capture the current desktop image without writing it to disk.
|
||||
fn capture_screenshot(&mut self) -> Result<RgbaImage>;
|
||||
|
||||
/// Focus a window by its backend-native window handle.
|
||||
fn focus_window(&mut self, native_id: u32) -> Result<()>;
|
||||
|
||||
/// Move a window to absolute coordinates.
|
||||
fn move_window(&mut self, xcb_id: u32, x: i32, y: i32) -> Result<()>;
|
||||
fn move_window(&mut self, native_id: u32, x: i32, y: i32) -> Result<()>;
|
||||
|
||||
/// Resize a window.
|
||||
fn resize_window(&mut self, xcb_id: u32, w: u32, h: u32) -> Result<()>;
|
||||
fn resize_window(&mut self, native_id: u32, w: u32, h: u32) -> Result<()>;
|
||||
|
||||
/// Close a window gracefully.
|
||||
fn close_window(&mut self, xcb_id: u32) -> Result<()>;
|
||||
fn close_window(&mut self, native_id: u32) -> Result<()>;
|
||||
|
||||
/// Click at absolute coordinates.
|
||||
fn click(&mut self, x: i32, y: i32) -> Result<()>;
|
||||
|
|
@ -51,9 +67,6 @@ pub trait DesktopBackend: Send {
|
|||
/// Get the current mouse position.
|
||||
fn mouse_position(&self) -> Result<(i32, i32)>;
|
||||
|
||||
/// Take a screenshot and save to a path (no window tree).
|
||||
fn screenshot(&mut self, path: &str, annotate: bool) -> Result<String>;
|
||||
|
||||
/// Launch an application.
|
||||
fn launch(&self, command: &str, args: &[String]) -> Result<u32>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ use x11rb::protocol::xproto::{
|
|||
};
|
||||
use x11rb::rust_connection::RustConnection;
|
||||
|
||||
use super::annotate::annotate_screenshot;
|
||||
use crate::core::types::{Snapshot, WindowInfo};
|
||||
use crate::backend::BackendWindow;
|
||||
|
||||
struct Atoms {
|
||||
client_list_stacking: Atom,
|
||||
|
|
@ -71,10 +70,9 @@ impl X11Backend {
|
|||
Ok(windows)
|
||||
}
|
||||
|
||||
fn collect_window_infos(&self) -> Result<Vec<WindowInfo>> {
|
||||
fn collect_window_infos(&self) -> Result<Vec<BackendWindow>> {
|
||||
let active_window = self.active_window()?;
|
||||
let mut window_infos = Vec::new();
|
||||
let mut ref_counter = 1usize;
|
||||
|
||||
for window in self.stacked_windows()? {
|
||||
let title = self.window_title(window).unwrap_or_default();
|
||||
|
|
@ -89,9 +87,8 @@ impl X11Backend {
|
|||
};
|
||||
|
||||
let minimized = self.window_is_minimized(window).unwrap_or(false);
|
||||
window_infos.push(WindowInfo {
|
||||
ref_id: format!("w{ref_counter}"),
|
||||
xcb_id: window,
|
||||
window_infos.push(BackendWindow {
|
||||
native_id: window,
|
||||
title,
|
||||
app_name,
|
||||
x,
|
||||
|
|
@ -101,7 +98,6 @@ impl X11Backend {
|
|||
focused: active_window == Some(window),
|
||||
minimized,
|
||||
});
|
||||
ref_counter += 1;
|
||||
}
|
||||
|
||||
Ok(window_infos)
|
||||
|
|
@ -231,32 +227,15 @@ impl X11Backend {
|
|||
}
|
||||
|
||||
impl super::DesktopBackend for X11Backend {
|
||||
fn snapshot(&mut self, annotate: bool) -> Result<Snapshot> {
|
||||
let window_infos = self.collect_window_infos()?;
|
||||
let mut image = self.capture_root_image()?;
|
||||
|
||||
// Annotate if requested - draw bounding boxes and @wN labels
|
||||
if annotate {
|
||||
annotate_screenshot(&mut image, &window_infos);
|
||||
}
|
||||
|
||||
// Save screenshot
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis();
|
||||
let screenshot_path = format!("/tmp/deskctl-{timestamp}.png");
|
||||
image
|
||||
.save(&screenshot_path)
|
||||
.context("Failed to save screenshot")?;
|
||||
|
||||
Ok(Snapshot {
|
||||
screenshot: screenshot_path,
|
||||
windows: window_infos,
|
||||
})
|
||||
fn list_windows(&mut self) -> Result<Vec<BackendWindow>> {
|
||||
self.collect_window_infos()
|
||||
}
|
||||
|
||||
fn focus_window(&mut self, xcb_id: u32) -> Result<()> {
|
||||
fn capture_screenshot(&mut self) -> Result<RgbaImage> {
|
||||
self.capture_root_image()
|
||||
}
|
||||
|
||||
fn focus_window(&mut self, native_id: u32) -> Result<()> {
|
||||
// Use _NET_ACTIVE_WINDOW client message (avoids focus-stealing prevention)
|
||||
let net_active = self
|
||||
.conn
|
||||
|
|
@ -269,7 +248,7 @@ impl super::DesktopBackend for X11Backend {
|
|||
response_type: x11rb::protocol::xproto::CLIENT_MESSAGE_EVENT,
|
||||
format: 32,
|
||||
sequence: 0,
|
||||
window: xcb_id,
|
||||
window: native_id,
|
||||
type_: net_active,
|
||||
data: ClientMessageData::from([
|
||||
2u32, 0, 0, 0, 0, // source=2 (pager), timestamp=0, currently_active=0
|
||||
|
|
@ -288,25 +267,25 @@ impl super::DesktopBackend for X11Backend {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn move_window(&mut self, xcb_id: u32, x: i32, y: i32) -> Result<()> {
|
||||
fn move_window(&mut self, native_id: u32, x: i32, y: i32) -> Result<()> {
|
||||
self.conn
|
||||
.configure_window(xcb_id, &ConfigureWindowAux::new().x(x).y(y))?;
|
||||
.configure_window(native_id, &ConfigureWindowAux::new().x(x).y(y))?;
|
||||
self.conn
|
||||
.flush()
|
||||
.context("Failed to flush X11 connection")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resize_window(&mut self, xcb_id: u32, w: u32, h: u32) -> Result<()> {
|
||||
fn resize_window(&mut self, native_id: u32, w: u32, h: u32) -> Result<()> {
|
||||
self.conn
|
||||
.configure_window(xcb_id, &ConfigureWindowAux::new().width(w).height(h))?;
|
||||
.configure_window(native_id, &ConfigureWindowAux::new().width(w).height(h))?;
|
||||
self.conn
|
||||
.flush()
|
||||
.context("Failed to flush X11 connection")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close_window(&mut self, xcb_id: u32) -> Result<()> {
|
||||
fn close_window(&mut self, native_id: u32) -> Result<()> {
|
||||
// Use _NET_CLOSE_WINDOW for graceful close (respects WM protocols)
|
||||
let net_close = self
|
||||
.conn
|
||||
|
|
@ -319,7 +298,7 @@ impl super::DesktopBackend for X11Backend {
|
|||
response_type: x11rb::protocol::xproto::CLIENT_MESSAGE_EVENT,
|
||||
format: 32,
|
||||
sequence: 0,
|
||||
window: xcb_id,
|
||||
window: native_id,
|
||||
type_: net_close,
|
||||
data: ClientMessageData::from([
|
||||
0u32, 2, 0, 0, 0, // timestamp=0, source=2 (pager)
|
||||
|
|
@ -463,18 +442,6 @@ impl super::DesktopBackend for X11Backend {
|
|||
Ok((reply.root_x as i32, reply.root_y as i32))
|
||||
}
|
||||
|
||||
fn screenshot(&mut self, path: &str, annotate: bool) -> Result<String> {
|
||||
let mut image = self.capture_root_image()?;
|
||||
|
||||
if annotate {
|
||||
let window_infos = self.collect_window_infos()?;
|
||||
annotate_screenshot(&mut image, &window_infos);
|
||||
}
|
||||
|
||||
image.save(path).context("Failed to save screenshot")?;
|
||||
Ok(path.to_string())
|
||||
}
|
||||
|
||||
fn launch(&self, command: &str, args: &[String]) -> Result<u32> {
|
||||
let child = std::process::Command::new(command)
|
||||
.args(args)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue