diff --git a/src/app_executor.rs b/src/app_executor.rs index 98b9b9a..6d7e486 100644 --- a/src/app_executor.rs +++ b/src/app_executor.rs @@ -10,7 +10,7 @@ use crate::{ }, config::{GlyphlowConfig, RoleOfInterest, VisibilityCheckingLevel, WorkFlow, WorkFlowAction}, drawer::GlyphlowDrawingLayer, - os_util::get_focused_pid, + os_util::{get_focused, get_system_alarm_window}, util::{Frame, HintBox, estimate_frame_for_text, hint_boxes_from_frames, select_range_helper}, }; use accessibility::{AXUIElement, AXUIElementActions, AXUIElementAttributes}; @@ -460,9 +460,7 @@ impl AppExecutor { let screen_frame = Frame::from_origion(self.screen_size); // NOTE: prioritize system alarms - if let Ok(app) = AXUIElement::application_with_bundle("com.apple.coreservices.uiagent") - && let Ok(window) = app.focused_window() - { + if let Some(window) = get_system_alarm_window() { let frame = window.get_frame(screen_frame); self.last_window_frame = frame; self.is_electron = false; @@ -477,9 +475,8 @@ impl AppExecutor { return Some(frame); } - let (pid, is_electron) = get_focused_pid()?; + let (pid, focused_app, is_electron) = get_focused()?; self.is_electron = is_electron; - let focused_app = AXUIElement::application(pid); // HACK: need this to bootstrap UI tree generation for some electron apps, // e.g. Discord @@ -1273,7 +1270,7 @@ impl AppExecutor { .. }) = self.selected.as_ref() else { - panic!("An element is supposed to be selected before entering Mode::Scrolling!") + return; }; if *role == RoleOfInterest::ScrollBar { diff --git a/src/ax_element.rs b/src/ax_element.rs index 4f6bc01..5d2ca90 100644 --- a/src/ax_element.rs +++ b/src/ax_element.rs @@ -423,6 +423,13 @@ impl GetAttribute for AXUIElement { msg.push_str(&format!("Role: {}\n", fp.role)); + if let Some(f) = fp.frame { + let CGPoint { x, y } = f.top_left; + msg.push_str(&format!("pos: x: {x}, y: {y}\n")); + let (w, h) = f.size(); + msg.push_str(&format!("size: width: {w}, height: {h}\n")); + } + if let Ok(t) = self.title() { msg.push_str(&format!("title: {}\n", t)); } @@ -439,13 +446,6 @@ impl GetAttribute for AXUIElement { msg.push_str(&format!("value: {:?}\n", v)); } - if let Some(f) = fp.frame { - let CGPoint { x, y } = f.top_left; - msg.push_str(&format!("pos: x: {x}, y: {y}\n")); - let (w, h) = f.size(); - msg.push_str(&format!("size: width: {w}, height: {h}\n")); - } - msg // for attr in &self.attribute_names().unwrap() { // println!( @@ -770,7 +770,13 @@ pub fn traverse_elements( } // NOTE: don't do it for AXSectionList, e.g. Apple Music kAXListRole if element.subrole().is_ok_and(|r| r == kAXContentListSubrole) => { - if let Some(area_frame) = ele_fp.frame.and_then(|f| f.intersect(&window_frame)) { + if let Some(area_frame) = ele_fp.frame.and_then(|f| f.intersect(&window_frame)) + && { + let (w, h) = area_frame.size(); + // HACK: Some content lists may have fake sizes, e.g. Slack + w > 10.0 && h > 10.0 + } + { window_frame = area_frame; }; } diff --git a/src/os_util.rs b/src/os_util.rs index 5d3ef83..1ffd6f2 100644 --- a/src/os_util.rs +++ b/src/os_util.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use accessibility::{AXUIElement, AXUIElementAttributes}; use accessibility_sys::{AXIsProcessTrustedWithOptions, kAXTrustedCheckOptionPrompt}; use core_foundation::{ base::TCFType, boolean::CFBoolean, dictionary::CFDictionary, string::CFString, @@ -27,12 +28,32 @@ fn check_is_electron_app(app: &Retained) -> Option { Some(false) } -pub fn get_focused_pid() -> Option<(i32, bool)> { +const APPLE_ALARM_BUNDLE_IDS: [&str; 3] = [ + "com.apple.coreservices.uiagent", + "com.apple.accessibility.universalAccessAuthWarn", + "com.apple.CoreLocationAgent", +]; + +pub fn get_system_alarm_window() -> Option { + for bundle_id in APPLE_ALARM_BUNDLE_IDS { + if let Ok(app) = AXUIElement::application_with_bundle(bundle_id) + && let Ok(window) = app.focused_window() + { + return Some(window); + } + } + None +} + +pub fn get_focused() -> Option<(i32, AXUIElement, bool)> { let workspace = NSWorkspace::sharedWorkspace(); let app = workspace.frontmostApplication()?; + // eprintln!("Focused app: {:?}", app.bundleIdentifier()); + let pid = app.processIdentifier(); Some(( - app.processIdentifier(), + pid, + AXUIElement::application(pid), check_is_electron_app(&app).unwrap_or_default(), )) }