add context window monitor
Writes the current context window to logs/current_context_window (uses a symlink to a session ID). This PR was unfortunately generated by a different LLM and did a ton of superficial reformating, it's actually a fairly small and benign change, but I don't want to roll back everything. Hope that's ok.
This commit is contained in:
@@ -3,7 +3,9 @@ use anyhow::{Context, Result};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use accessibility::{AXUIElement, AXUIElementAttributes, ElementFinder, TreeVisitor, TreeWalker, TreeWalkerFlow};
|
||||
use accessibility::{
|
||||
AXUIElement, AXUIElementAttributes, ElementFinder, TreeVisitor, TreeWalker, TreeWalkerFlow,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use core_foundation::base::TCFType;
|
||||
@@ -23,46 +25,46 @@ impl MacAxController {
|
||||
{
|
||||
// Check if we have accessibility permissions by trying to get system-wide element
|
||||
let _system = AXUIElement::system_wide();
|
||||
|
||||
|
||||
Ok(Self {
|
||||
app_cache: std::sync::Mutex::new(HashMap::new()),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
anyhow::bail!("macOS Accessibility API is only available on macOS")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// List all running applications
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn list_applications(&self) -> Result<Vec<AXApplication>> {
|
||||
let apps = Self::get_running_applications()?;
|
||||
Ok(apps)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn list_applications(&self) -> Result<Vec<AXApplication>> {
|
||||
anyhow::bail!("Not supported on this platform")
|
||||
}
|
||||
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn get_running_applications() -> Result<Vec<AXApplication>> {
|
||||
use cocoa::appkit::NSApplicationActivationPolicy;
|
||||
use cocoa::base::{id, nil};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
|
||||
unsafe {
|
||||
let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
|
||||
let running_apps: id = msg_send![workspace, runningApplications];
|
||||
let count: usize = msg_send![running_apps, count];
|
||||
|
||||
|
||||
let mut apps = Vec::new();
|
||||
|
||||
|
||||
for i in 0..count {
|
||||
let app: id = msg_send![running_apps, objectAtIndex: i];
|
||||
|
||||
|
||||
// Get app name
|
||||
let localized_name: id = msg_send![app, localizedName];
|
||||
if localized_name == nil {
|
||||
@@ -76,7 +78,7 @@ impl MacAxController {
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
// Get bundle ID
|
||||
let bundle_id_obj: id = msg_send![app, bundleIdentifier];
|
||||
let bundle_id = if bundle_id_obj != nil {
|
||||
@@ -93,13 +95,15 @@ impl MacAxController {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
|
||||
// Get PID
|
||||
let pid: i32 = msg_send![app, processIdentifier];
|
||||
|
||||
|
||||
// Skip background-only apps
|
||||
let activation_policy: i64 = msg_send![app, activationPolicy];
|
||||
if activation_policy == NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular as i64 {
|
||||
if activation_policy
|
||||
== NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular as i64
|
||||
{
|
||||
apps.push(AXApplication {
|
||||
name,
|
||||
bundle_id,
|
||||
@@ -107,32 +111,32 @@ impl MacAxController {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(apps)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Get the frontmost (active) application
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn get_frontmost_app(&self) -> Result<AXApplication> {
|
||||
use cocoa::base::{id, nil};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
|
||||
unsafe {
|
||||
let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
|
||||
let frontmost_app: id = msg_send![workspace, frontmostApplication];
|
||||
|
||||
|
||||
if frontmost_app == nil {
|
||||
anyhow::bail!("No frontmost application");
|
||||
}
|
||||
|
||||
|
||||
// Get app name
|
||||
let localized_name: id = msg_send![frontmost_app, localizedName];
|
||||
let name_ptr: *const i8 = msg_send![localized_name, UTF8String];
|
||||
let name = std::ffi::CStr::from_ptr(name_ptr)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
|
||||
// Get bundle ID
|
||||
let bundle_id_obj: id = msg_send![frontmost_app, bundleIdentifier];
|
||||
let bundle_id = if bundle_id_obj != nil {
|
||||
@@ -149,10 +153,10 @@ impl MacAxController {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
|
||||
// Get PID
|
||||
let pid: i32 = msg_send![frontmost_app, processIdentifier];
|
||||
|
||||
|
||||
Ok(AXApplication {
|
||||
name,
|
||||
bundle_id,
|
||||
@@ -160,12 +164,12 @@ impl MacAxController {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn get_frontmost_app(&self) -> Result<AXApplication> {
|
||||
anyhow::bail!("Not supported on this platform")
|
||||
}
|
||||
|
||||
|
||||
/// Get AXUIElement for an application by name or PID
|
||||
#[cfg(target_os = "macos")]
|
||||
fn get_app_element(&self, app_name: &str) -> Result<AXUIElement> {
|
||||
@@ -176,79 +180,79 @@ impl MacAxController {
|
||||
return Ok(element.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Find the app by name
|
||||
let apps = Self::get_running_applications()?;
|
||||
let app = apps
|
||||
.iter()
|
||||
.find(|a| a.name == app_name)
|
||||
.ok_or_else(|| anyhow::anyhow!("Application '{}' not found", app_name))?;
|
||||
|
||||
|
||||
// Create AXUIElement for the app
|
||||
let element = AXUIElement::application(app.pid);
|
||||
|
||||
|
||||
// Cache it
|
||||
{
|
||||
let mut cache = self.app_cache.lock().unwrap();
|
||||
cache.insert(app_name.to_string(), element.clone());
|
||||
}
|
||||
|
||||
|
||||
Ok(element)
|
||||
}
|
||||
|
||||
|
||||
/// Activate (bring to front) an application
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn activate_app(&self, app_name: &str) -> Result<()> {
|
||||
use cocoa::base::id;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
|
||||
// Find the app
|
||||
let apps = Self::get_running_applications()?;
|
||||
let app = apps
|
||||
.iter()
|
||||
.find(|a| a.name == app_name)
|
||||
.ok_or_else(|| anyhow::anyhow!("Application '{}' not found", app_name))?;
|
||||
|
||||
|
||||
unsafe {
|
||||
let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
|
||||
let running_apps: id = msg_send![workspace, runningApplications];
|
||||
let count: usize = msg_send![running_apps, count];
|
||||
|
||||
|
||||
for i in 0..count {
|
||||
let running_app: id = msg_send![running_apps, objectAtIndex: i];
|
||||
let pid: i32 = msg_send![running_app, processIdentifier];
|
||||
|
||||
|
||||
if pid == app.pid {
|
||||
let _: bool = msg_send![running_app, activateWithOptions: 0];
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
anyhow::bail!("Failed to activate application")
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn activate_app(&self, _app_name: &str) -> Result<()> {
|
||||
anyhow::bail!("Not supported on this platform")
|
||||
}
|
||||
|
||||
|
||||
/// Get the UI hierarchy of an application
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn get_ui_tree(&self, app_name: &str, max_depth: usize) -> Result<String> {
|
||||
let app_element = self.get_app_element(app_name)?;
|
||||
let mut output = format!("Application: {}\n", app_name);
|
||||
|
||||
|
||||
Self::build_ui_tree(&app_element, &mut output, 0, max_depth)?;
|
||||
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn get_ui_tree(&self, _app_name: &str, _max_depth: usize) -> Result<String> {
|
||||
anyhow::bail!("Not supported on this platform")
|
||||
}
|
||||
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn build_ui_tree(
|
||||
element: &AXUIElement,
|
||||
@@ -259,21 +263,22 @@ impl MacAxController {
|
||||
if depth >= max_depth {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
let indent = " ".repeat(depth);
|
||||
|
||||
|
||||
// Get role
|
||||
let role = element.role().ok().map(|s| s.to_string())
|
||||
let role = element
|
||||
.role()
|
||||
.ok()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| "Unknown".to_string());
|
||||
|
||||
|
||||
// Get title
|
||||
let title = element.title().ok()
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let title = element.title().ok().map(|s| s.to_string());
|
||||
|
||||
// Get identifier
|
||||
let identifier = element.identifier().ok()
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let identifier = element.identifier().ok().map(|s| s.to_string());
|
||||
|
||||
// Format output
|
||||
output.push_str(&format!("{}Role: {}", indent, role));
|
||||
if let Some(t) = title {
|
||||
@@ -283,7 +288,7 @@ impl MacAxController {
|
||||
output.push_str(&format!(", ID: {}", id));
|
||||
}
|
||||
output.push('\n');
|
||||
|
||||
|
||||
// Get children
|
||||
if let Ok(children) = element.children() {
|
||||
for i in 0..children.len() {
|
||||
@@ -292,10 +297,10 @@ impl MacAxController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Find UI elements in an application
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn find_elements(
|
||||
@@ -307,7 +312,7 @@ impl MacAxController {
|
||||
) -> Result<Vec<AXElement>> {
|
||||
let app_element = self.get_app_element(app_name)?;
|
||||
let mut found_elements = Vec::new();
|
||||
|
||||
|
||||
let visitor = ElementCollector {
|
||||
role_filter: role.map(|s| s.to_string()),
|
||||
title_filter: title.map(|s| s.to_string()),
|
||||
@@ -315,13 +320,13 @@ impl MacAxController {
|
||||
results: std::cell::RefCell::new(&mut found_elements),
|
||||
depth: std::cell::Cell::new(0),
|
||||
};
|
||||
|
||||
|
||||
let walker = TreeWalker::new();
|
||||
walker.walk(&app_element, &visitor);
|
||||
|
||||
|
||||
Ok(found_elements)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn find_elements(
|
||||
&self,
|
||||
@@ -332,7 +337,7 @@ impl MacAxController {
|
||||
) -> Result<Vec<AXElement>> {
|
||||
anyhow::bail!("Not supported on this platform")
|
||||
}
|
||||
|
||||
|
||||
/// Find a single element (helper for click, set_value, etc.)
|
||||
#[cfg(target_os = "macos")]
|
||||
fn find_element(
|
||||
@@ -343,19 +348,17 @@ impl MacAxController {
|
||||
identifier: Option<&str>,
|
||||
) -> Result<AXUIElement> {
|
||||
let app_element = self.get_app_element(app_name)?;
|
||||
|
||||
|
||||
let role_str = role.to_string();
|
||||
let title_str = title.map(|s| s.to_string());
|
||||
let identifier_str = identifier.map(|s| s.to_string());
|
||||
|
||||
|
||||
let finder = ElementFinder::new(
|
||||
&app_element,
|
||||
move |element| {
|
||||
// Check role
|
||||
let elem_role = element.role()
|
||||
.ok()
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let elem_role = element.role().ok().map(|s| s.to_string());
|
||||
|
||||
if let Some(r) = elem_role {
|
||||
if !r.contains(&role_str) {
|
||||
return false;
|
||||
@@ -363,13 +366,11 @@ impl MacAxController {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check title if specified
|
||||
if let Some(ref title_filter) = title_str {
|
||||
let elem_title = element.title()
|
||||
.ok()
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let elem_title = element.title().ok().map(|s| s.to_string());
|
||||
|
||||
if let Some(t) = elem_title {
|
||||
if !t.contains(title_filter) {
|
||||
return false;
|
||||
@@ -378,13 +379,11 @@ impl MacAxController {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check identifier if specified
|
||||
if let Some(ref id_filter) = identifier_str {
|
||||
let elem_id = element.identifier()
|
||||
.ok()
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let elem_id = element.identifier().ok().map(|s| s.to_string());
|
||||
|
||||
if let Some(id) = elem_id {
|
||||
if !id.contains(id_filter) {
|
||||
return false;
|
||||
@@ -393,15 +392,15 @@ impl MacAxController {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
true
|
||||
},
|
||||
Some(std::time::Duration::from_secs(2)),
|
||||
);
|
||||
|
||||
|
||||
finder.find().context("Element not found")
|
||||
}
|
||||
|
||||
|
||||
/// Click on a UI element
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn click_element(
|
||||
@@ -412,16 +411,16 @@ impl MacAxController {
|
||||
identifier: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let element = self.find_element(app_name, role, title, identifier)?;
|
||||
|
||||
|
||||
// Perform the press action
|
||||
let action_name = CFString::new("AXPress");
|
||||
element
|
||||
.perform_action(&action_name)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to perform press action: {:?}", e))?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn click_element(
|
||||
&self,
|
||||
@@ -432,7 +431,7 @@ impl MacAxController {
|
||||
) -> Result<()> {
|
||||
anyhow::bail!("Not supported on this platform")
|
||||
}
|
||||
|
||||
|
||||
/// Set the value of a UI element
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn set_value(
|
||||
@@ -444,16 +443,17 @@ impl MacAxController {
|
||||
identifier: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let element = self.find_element(app_name, role, title, identifier)?;
|
||||
|
||||
|
||||
// Set the value - convert CFString to CFType
|
||||
let cf_value = CFString::new(value);
|
||||
|
||||
element.set_value(cf_value.as_CFType())
|
||||
|
||||
element
|
||||
.set_value(cf_value.as_CFType())
|
||||
.map_err(|e| anyhow::anyhow!("Failed to set value: {:?}", e))?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn set_value(
|
||||
&self,
|
||||
@@ -465,7 +465,7 @@ impl MacAxController {
|
||||
) -> Result<()> {
|
||||
anyhow::bail!("Not supported on this platform")
|
||||
}
|
||||
|
||||
|
||||
/// Get the value of a UI element
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn get_value(
|
||||
@@ -476,11 +476,12 @@ impl MacAxController {
|
||||
identifier: Option<&str>,
|
||||
) -> Result<String> {
|
||||
let element = self.find_element(app_name, role, title, identifier)?;
|
||||
|
||||
|
||||
// Get the value
|
||||
let value_type = element.value()
|
||||
let value_type = element
|
||||
.value()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get value: {:?}", e))?;
|
||||
|
||||
|
||||
// Try to downcast to CFString
|
||||
if let Some(cf_string) = value_type.downcast::<CFString>() {
|
||||
Ok(cf_string.to_string())
|
||||
@@ -489,7 +490,7 @@ impl MacAxController {
|
||||
Ok(format!("<non-string value>"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn get_value(
|
||||
&self,
|
||||
@@ -500,52 +501,52 @@ impl MacAxController {
|
||||
) -> Result<String> {
|
||||
anyhow::bail!("Not supported on this platform")
|
||||
}
|
||||
|
||||
|
||||
/// Type text into the currently focused element (uses system text input)
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn type_text(&self, app_name: &str, text: &str) -> Result<()> {
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::foundation::NSString;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
|
||||
// First, make sure the app is active
|
||||
self.activate_app(app_name)?;
|
||||
|
||||
|
||||
// Wait for app to fully activate
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
|
||||
|
||||
// Send a Tab key to try to focus on a text field
|
||||
// This helps ensure something is focused before we paste
|
||||
let _ = self.press_key(app_name, "tab", vec![]);
|
||||
std::thread::sleep(std::time::Duration::from_millis(800));
|
||||
|
||||
|
||||
// Save old clipboard, set new content, paste, then restore
|
||||
let old_content: id;
|
||||
unsafe {
|
||||
// Get the general pasteboard
|
||||
let pasteboard: id = msg_send![class!(NSPasteboard), generalPasteboard];
|
||||
|
||||
|
||||
// Save current clipboard content
|
||||
let ns_string_type = NSString::alloc(nil).init_str("public.utf8-plain-text");
|
||||
old_content = msg_send![pasteboard, stringForType: ns_string_type];
|
||||
|
||||
|
||||
// Clear and set new content
|
||||
let _: () = msg_send![pasteboard, clearContents];
|
||||
|
||||
|
||||
let ns_string = NSString::alloc(nil).init_str(text);
|
||||
let ns_type = NSString::alloc(nil).init_str("public.utf8-plain-text");
|
||||
let _: bool = msg_send![pasteboard, setString:ns_string forType:ns_type];
|
||||
}
|
||||
|
||||
|
||||
// Wait a moment for clipboard to update
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
|
||||
|
||||
// Paste using Cmd+V (outside unsafe block)
|
||||
self.press_key(app_name, "v", vec!["command"])?;
|
||||
|
||||
|
||||
// Wait for paste to complete
|
||||
std::thread::sleep(std::time::Duration::from_millis(300));
|
||||
|
||||
|
||||
// Restore old clipboard content if it existed
|
||||
unsafe {
|
||||
if old_content != nil {
|
||||
@@ -555,15 +556,15 @@ impl MacAxController {
|
||||
let _: bool = msg_send![pasteboard, setString:old_content forType:ns_type];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn type_text(&self, _app_name: &str, _text: &str) -> Result<()> {
|
||||
anyhow::bail!("Not supported on this platform")
|
||||
}
|
||||
|
||||
|
||||
/// Focus on a text field or text area element
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn focus_element(
|
||||
@@ -574,40 +575,34 @@ impl MacAxController {
|
||||
identifier: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let element = self.find_element(app_name, role, title, identifier)?;
|
||||
|
||||
|
||||
// Set focused attribute to true
|
||||
use core_foundation::boolean::CFBoolean;
|
||||
let cf_true = CFBoolean::true_value();
|
||||
|
||||
element.set_attribute(&accessibility::AXAttribute::focused(), cf_true)
|
||||
|
||||
element
|
||||
.set_attribute(&accessibility::AXAttribute::focused(), cf_true)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to focus element: {:?}", e))?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Press a keyboard shortcut
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn press_key(
|
||||
&self,
|
||||
app_name: &str,
|
||||
key: &str,
|
||||
modifiers: Vec<&str>,
|
||||
) -> Result<()> {
|
||||
use core_graphics::event::{
|
||||
CGEvent, CGEventFlags, CGEventTapLocation,
|
||||
};
|
||||
pub fn press_key(&self, app_name: &str, key: &str, modifiers: Vec<&str>) -> Result<()> {
|
||||
use core_graphics::event::{CGEvent, CGEventFlags, CGEventTapLocation};
|
||||
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
|
||||
|
||||
|
||||
// First, make sure the app is active
|
||||
self.activate_app(app_name)?;
|
||||
|
||||
|
||||
// Wait a bit for activation
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
|
||||
// Map key string to key code
|
||||
let key_code = Self::key_to_keycode(key)
|
||||
.ok_or_else(|| anyhow::anyhow!("Unknown key: {}", key))?;
|
||||
|
||||
let key_code =
|
||||
Self::key_to_keycode(key).ok_or_else(|| anyhow::anyhow!("Unknown key: {}", key))?;
|
||||
|
||||
// Map modifiers to flags
|
||||
let mut flags = CGEventFlags::CGEventFlagNull;
|
||||
for modifier in modifiers {
|
||||
@@ -619,39 +614,37 @@ impl MacAxController {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create event source
|
||||
let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState)
|
||||
.ok().context("Failed to create event source")?;
|
||||
|
||||
.ok()
|
||||
.context("Failed to create event source")?;
|
||||
|
||||
// Create key down event
|
||||
let key_down = CGEvent::new_keyboard_event(source.clone(), key_code, true)
|
||||
.ok().context("Failed to create key down event")?;
|
||||
.ok()
|
||||
.context("Failed to create key down event")?;
|
||||
key_down.set_flags(flags);
|
||||
|
||||
|
||||
// Create key up event
|
||||
let key_up = CGEvent::new_keyboard_event(source, key_code, false)
|
||||
.ok().context("Failed to create key up event")?;
|
||||
.ok()
|
||||
.context("Failed to create key up event")?;
|
||||
key_up.set_flags(flags);
|
||||
|
||||
|
||||
// Post events
|
||||
key_down.post(CGEventTapLocation::HID);
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
key_up.post(CGEventTapLocation::HID);
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn press_key(
|
||||
&self,
|
||||
_app_name: &str,
|
||||
_key: &str,
|
||||
_modifiers: Vec<&str>,
|
||||
) -> Result<()> {
|
||||
pub fn press_key(&self, _app_name: &str, _key: &str, _modifiers: Vec<&str>) -> Result<()> {
|
||||
anyhow::bail!("Not supported on this platform")
|
||||
}
|
||||
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn key_to_keycode(key: &str) -> Option<u16> {
|
||||
// Map common keys to keycodes
|
||||
@@ -743,62 +736,55 @@ struct ElementCollector<'a> {
|
||||
impl<'a> TreeVisitor for ElementCollector<'a> {
|
||||
fn enter_element(&self, element: &AXUIElement) -> TreeWalkerFlow {
|
||||
self.depth.set(self.depth.get() + 1);
|
||||
|
||||
|
||||
if self.depth.get() > 20 {
|
||||
return TreeWalkerFlow::SkipSubtree;
|
||||
}
|
||||
|
||||
|
||||
// Get element properties
|
||||
let role = element.role()
|
||||
let role = element
|
||||
.role()
|
||||
.ok()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| "Unknown".to_string());
|
||||
|
||||
let title = element.title()
|
||||
.ok()
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let identifier = element.identifier()
|
||||
.ok()
|
||||
.map(|s| s.to_string());
|
||||
|
||||
|
||||
let title = element.title().ok().map(|s| s.to_string());
|
||||
|
||||
let identifier = element.identifier().ok().map(|s| s.to_string());
|
||||
|
||||
// Check if this element matches the filters
|
||||
let role_matches = self.role_filter.as_ref().map_or(true, |r| role.contains(r));
|
||||
let title_matches = self.title_filter.as_ref().map_or(true, |t| {
|
||||
title.as_ref().map_or(false, |title_str| title_str.contains(t))
|
||||
title
|
||||
.as_ref()
|
||||
.map_or(false, |title_str| title_str.contains(t))
|
||||
});
|
||||
let identifier_matches = self.identifier_filter.as_ref().map_or(true, |id| {
|
||||
identifier.as_ref().map_or(false, |id_str| id_str.contains(id))
|
||||
identifier
|
||||
.as_ref()
|
||||
.map_or(false, |id_str| id_str.contains(id))
|
||||
});
|
||||
|
||||
|
||||
if role_matches && title_matches && identifier_matches {
|
||||
// Get additional properties
|
||||
let value = element.value()
|
||||
let value = element
|
||||
.value()
|
||||
.ok()
|
||||
.and_then(|v| {
|
||||
v.downcast::<CFString>().map(|s| s.to_string())
|
||||
});
|
||||
|
||||
let label = element.description()
|
||||
.ok()
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let enabled = element.enabled()
|
||||
.ok()
|
||||
.map(|b| b.into())
|
||||
.unwrap_or(false);
|
||||
|
||||
let focused = element.focused()
|
||||
.ok()
|
||||
.map(|b| b.into())
|
||||
.unwrap_or(false);
|
||||
|
||||
.and_then(|v| v.downcast::<CFString>().map(|s| s.to_string()));
|
||||
|
||||
let label = element.description().ok().map(|s| s.to_string());
|
||||
|
||||
let enabled = element.enabled().ok().map(|b| b.into()).unwrap_or(false);
|
||||
|
||||
let focused = element.focused().ok().map(|b| b.into()).unwrap_or(false);
|
||||
|
||||
// Count children
|
||||
let children_count = element.children()
|
||||
let children_count = element
|
||||
.children()
|
||||
.ok()
|
||||
.map(|arr| arr.len() as usize)
|
||||
.unwrap_or(0);
|
||||
|
||||
|
||||
self.results.borrow_mut().push(AXElement {
|
||||
role,
|
||||
title,
|
||||
@@ -812,10 +798,10 @@ impl<'a> TreeVisitor for ElementCollector<'a> {
|
||||
children_count,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
TreeWalkerFlow::Continue
|
||||
}
|
||||
|
||||
|
||||
fn exit_element(&self, _element: &AXUIElement) {
|
||||
self.depth.set(self.depth.get() - 1);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ impl AXElement {
|
||||
/// Convert to a human-readable string representation
|
||||
pub fn to_string(&self) -> String {
|
||||
let mut parts = vec![format!("Role: {}", self.role)];
|
||||
|
||||
|
||||
if let Some(ref title) = self.title {
|
||||
parts.push(format!("Title: {}", title));
|
||||
}
|
||||
@@ -47,19 +47,19 @@ impl AXElement {
|
||||
if let Some(ref id) = self.identifier {
|
||||
parts.push(format!("ID: {}", id));
|
||||
}
|
||||
|
||||
|
||||
parts.push(format!("Enabled: {}", self.enabled));
|
||||
parts.push(format!("Focused: {}", self.focused));
|
||||
|
||||
|
||||
if let Some((x, y)) = self.position {
|
||||
parts.push(format!("Position: ({:.0}, {:.0})", x, y));
|
||||
}
|
||||
if let Some((w, h)) = self.size {
|
||||
parts.push(format!("Size: ({:.0}, {:.0})", w, h));
|
||||
}
|
||||
|
||||
|
||||
parts.push(format!("Children: {}", self.children_count));
|
||||
|
||||
|
||||
parts.join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user