mirror of
https://github.com/lahfir/agent-desktop.git
synced 2026-08-09 00:17:27 +00:00
fix(macos): use pgrep and async osascript for NC lifecycle
CGWindowList requires Screen Recording permission on macOS Sequoia to read other processes' window names, and SystemUIServer blocks AX access to its menu bar (crashes with Obj-C foreign exception). Switch to pgrep for PID lookup and osascript clicking ControlCenter's Clock menu bar item, spawned async to avoid blocking.
This commit is contained in:
parent
2374330bb1
commit
979758538f
1 changed files with 22 additions and 128 deletions
|
|
@ -35,44 +35,17 @@ impl Drop for NcSession {
|
|||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(super) fn nc_pid() -> Option<i32> {
|
||||
use core_foundation::base::{CFType, TCFType};
|
||||
use core_foundation::number::CFNumber;
|
||||
use core_foundation::string::CFString;
|
||||
use core_foundation_sys::dictionary::CFDictionaryGetValue;
|
||||
use core_graphics::display::CGDisplay;
|
||||
use core_graphics::window::{
|
||||
kCGWindowListOptionOnScreenOnly, kCGWindowOwnerName, kCGWindowOwnerPID,
|
||||
};
|
||||
let output = std::process::Command::new("pgrep")
|
||||
.arg("-x")
|
||||
.arg("NotificationCenter")
|
||||
.output()
|
||||
.ok()?;
|
||||
|
||||
let arr = CGDisplay::window_list_info(kCGWindowListOptionOnScreenOnly, None)?;
|
||||
for raw in arr.get_all_values() {
|
||||
if raw.is_null() {
|
||||
continue;
|
||||
}
|
||||
let name = unsafe {
|
||||
let v = CFDictionaryGetValue(raw as _, kCGWindowOwnerName as _);
|
||||
if v.is_null() {
|
||||
continue;
|
||||
}
|
||||
CFType::wrap_under_get_rule(v as _)
|
||||
.downcast::<CFString>()
|
||||
.map(|s| s.to_string())
|
||||
};
|
||||
if name.as_deref() == Some("NotificationCenter") {
|
||||
let pid = unsafe {
|
||||
let v = CFDictionaryGetValue(raw as _, kCGWindowOwnerPID as _);
|
||||
if v.is_null() {
|
||||
return None;
|
||||
}
|
||||
CFType::wrap_under_get_rule(v as _)
|
||||
.downcast::<CFNumber>()
|
||||
.and_then(|n| n.to_i64())
|
||||
.map(|p| p as i32)
|
||||
};
|
||||
return pid;
|
||||
}
|
||||
}
|
||||
None
|
||||
String::from_utf8_lossy(&output.stdout)
|
||||
.trim()
|
||||
.lines()
|
||||
.next()
|
||||
.and_then(|line| line.trim().parse::<i32>().ok())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
|
@ -95,54 +68,20 @@ fn is_nc_open() -> bool {
|
|||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn open_nc() -> Result<(), AdapterError> {
|
||||
use crate::tree::{copy_ax_array, copy_string_attr, element_for_pid};
|
||||
use accessibility_sys::kAXRoleAttribute;
|
||||
let script = r#"tell application "System Events" to tell its application process "ControlCenter"
|
||||
click (first menu bar item of menu bar 1 whose description is "Clock")
|
||||
end tell"#;
|
||||
|
||||
let sui_pid = find_system_ui_server_pid()?;
|
||||
let sui_app = element_for_pid(sui_pid);
|
||||
let menubar = copy_ax_array(&sui_app, "AXMenuBar")
|
||||
.or_else(|| copy_ax_array(&sui_app, "AXExtrasMenuBar"))
|
||||
.ok_or_else(|| AdapterError::internal("SystemUIServer: no menu bar found"))?;
|
||||
|
||||
let mut clock_item = None;
|
||||
for bar_items in [
|
||||
copy_ax_array(&sui_app, "AXExtrasMenuBar").unwrap_or_default(),
|
||||
menubar,
|
||||
] {
|
||||
for item in &bar_items {
|
||||
let role = copy_string_attr(item, kAXRoleAttribute);
|
||||
if role.as_deref() != Some("AXMenuBarItem") && role.as_deref() != Some("AXMenuExtra") {
|
||||
continue;
|
||||
}
|
||||
let desc = copy_string_attr(item, "AXDescription");
|
||||
let title = copy_string_attr(item, "AXTitle");
|
||||
let is_clock = desc
|
||||
.as_deref()
|
||||
.map(|d| d.contains("Clock") || d.contains("clock") || d.contains("Date"))
|
||||
.unwrap_or(false)
|
||||
|| title
|
||||
.as_deref()
|
||||
.map(|t| t.contains("Clock") || t.contains("clock") || t.contains("Date"))
|
||||
.unwrap_or(false);
|
||||
if is_clock {
|
||||
clock_item = Some(item.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if clock_item.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let clock = clock_item
|
||||
.ok_or_else(|| AdapterError::internal("SystemUIServer: clock menu bar item not found"))?;
|
||||
|
||||
if !crate::actions::ax_helpers::try_ax_action(&clock, "AXPress") {
|
||||
return Err(AdapterError::internal(
|
||||
"Failed to press clock menu bar item to open Notification Center",
|
||||
));
|
||||
}
|
||||
let mut child = std::process::Command::new("osascript")
|
||||
.arg("-e")
|
||||
.arg(script)
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.spawn()
|
||||
.map_err(|e| AdapterError::internal(format!("Failed to spawn osascript: {e}")))?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
let _ = child.try_wait();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -192,48 +131,3 @@ fn wait_for_nc_ready() -> Result<(), AdapterError> {
|
|||
fn wait_for_nc_ready() -> Result<(), AdapterError> {
|
||||
Err(AdapterError::not_supported("wait_for_nc_ready"))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn find_system_ui_server_pid() -> Result<i32, AdapterError> {
|
||||
use core_foundation::base::{CFType, TCFType};
|
||||
use core_foundation::number::CFNumber;
|
||||
use core_foundation::string::CFString;
|
||||
use core_foundation_sys::dictionary::CFDictionaryGetValue;
|
||||
use core_graphics::display::CGDisplay;
|
||||
use core_graphics::window::{
|
||||
kCGWindowListOptionOnScreenOnly, kCGWindowOwnerName, kCGWindowOwnerPID,
|
||||
};
|
||||
|
||||
let arr = CGDisplay::window_list_info(kCGWindowListOptionOnScreenOnly, None)
|
||||
.ok_or_else(|| AdapterError::internal("Failed to get window list"))?;
|
||||
|
||||
for raw in arr.get_all_values() {
|
||||
if raw.is_null() {
|
||||
continue;
|
||||
}
|
||||
let name = unsafe {
|
||||
let v = CFDictionaryGetValue(raw as _, kCGWindowOwnerName as _);
|
||||
if v.is_null() {
|
||||
continue;
|
||||
}
|
||||
CFType::wrap_under_get_rule(v as _)
|
||||
.downcast::<CFString>()
|
||||
.map(|s| s.to_string())
|
||||
};
|
||||
if name.as_deref() == Some("SystemUIServer") {
|
||||
let pid = unsafe {
|
||||
let v = CFDictionaryGetValue(raw as _, kCGWindowOwnerPID as _);
|
||||
if v.is_null() {
|
||||
return Err(AdapterError::internal("SystemUIServer: no PID"));
|
||||
}
|
||||
CFType::wrap_under_get_rule(v as _)
|
||||
.downcast::<CFNumber>()
|
||||
.and_then(|n| n.to_i64())
|
||||
.map(|p| p as i32)
|
||||
.ok_or_else(|| AdapterError::internal("SystemUIServer: bad PID"))?
|
||||
};
|
||||
return Ok(pid);
|
||||
}
|
||||
}
|
||||
Err(AdapterError::internal("SystemUIServer process not found"))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue