auv-driver-linux 0.0.22

Linux driver for AUV
//! Window accessibility tree snapshots via AT-SPI.
//!
//! This mirrors the Windows UIA tree surface and the macOS AX tree direction:
//! AUV captures a flattened tree for one top-level window, then re-resolves
//! path-targeted focus/action requests against the current AT-SPI tree.

use auv_driver_common::error::DriverResult;
use auv_driver_common::geometry::Rect;
use auv_driver_common::input::{DisturbanceLevel, InputActionResult, InputAttempt, InputDeliveryPath};
use auv_driver_common::window::Window;

use crate::atspi;

#[derive(Clone, Debug, PartialEq)]
pub struct AxNode {
  pub depth: usize,
  pub path: String,
  pub control_type: String,
  pub name: String,
  pub value: Option<String>,
  pub automation_id: String,
  pub class_name: String,
  pub focused: bool,
  pub bounds: Rect,
}

#[derive(Clone, Debug, PartialEq)]
pub struct AxTreeSnapshot {
  pub window_ref: String,
  pub nodes: Vec<AxNode>,
}

pub fn snapshot_window(window: &Window) -> DriverResult<AxTreeSnapshot> {
  let snapshot = atspi::snapshot_window(window)?;
  Ok(AxTreeSnapshot {
    window_ref: snapshot.window_ref,
    nodes: snapshot
      .nodes
      .into_iter()
      .map(|node| AxNode {
        depth: node.depth,
        path: node.path,
        control_type: node.role,
        name: node.name,
        value: node.value,
        automation_id: node.accessible_id,
        class_name: String::new(),
        focused: node.focused,
        bounds: node.bounds,
      })
      .collect(),
  })
}

pub fn focus_node(window: &Window, node_path: &str) -> DriverResult<InputActionResult> {
  let result = atspi::focus_node(window, node_path)?;
  let mut attempts = Vec::new();
  if let Some(reason) = result.fallback_reason {
    attempts.push(InputAttempt::failure(InputDeliveryPath::AxFocus, reason));
  }
  attempts.push(InputAttempt::success(InputDeliveryPath::AxFocus));
  Ok(InputActionResult {
    selected_path: InputDeliveryPath::AxFocus,
    attempts,
    verified: false,
    mouse_disturbance: DisturbanceLevel::None,
    focus_disturbance: DisturbanceLevel::Foreground,
    clipboard_disturbance: DisturbanceLevel::None,
  })
}

pub fn select_node(window: &Window, node_path: &str) -> DriverResult<InputActionResult> {
  let result = atspi::select_node(window, node_path)?;
  Ok(InputActionResult {
    selected_path: InputDeliveryPath::AxPress,
    attempts: vec![InputAttempt {
      path: InputDeliveryPath::AxPress,
      succeeded: true,
      message: Some(result.action_name),
    }],
    verified: false,
    mouse_disturbance: DisturbanceLevel::None,
    focus_disturbance: DisturbanceLevel::Foreground,
    clipboard_disturbance: DisturbanceLevel::None,
  })
}

#[cfg(test)]
#[path = "accessibility_test.rs"]
mod tests;