diff --git a/crates/macos/src/notifications/nc_session.rs b/crates/macos/src/notifications/nc_session.rs index 99ec8e2a..ecf36e4b 100644 --- a/crates/macos/src/notifications/nc_session.rs +++ b/crates/macos/src/notifications/nc_session.rs @@ -35,44 +35,17 @@ impl Drop for NcSession { #[cfg(target_os = "macos")] pub(super) fn nc_pid() -> Option { - 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::() - .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::() - .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::().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 { - 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::() - .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::() - .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")) -}