use accessibility_sys::{AXError, AXObserverRef, AXUIElementRef};
use log::{error, info};
use objc2::rc::{Retained, autoreleasepool};
use objc2_app_kit::NSApplication;
use objc2_core_foundation::{CFRunLoop, CFString, kCFRunLoopDefaultMode};
use std::ffi::c_void;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use stdext::function_name;
use crate::config::{CONFIGURATION_FILE, Config};
use crate::errors::{Error, Result};
use crate::events::{Event, EventSender};
use crate::manager::{check_ax_privilege, check_separate_spaces};
use display::DisplayHandler;
use input::InputHandler;
use mission_control::MissionControlHandler;
use process::ProcessHandler;
pub use process::ProcessSerialNumber;
pub use workspace::WorkspaceObserver;
mod display;
mod input;
mod mission_control;
mod process;
pub mod service;
mod workspace;
pub type OSStatus = i32;
pub type WinID = i32;
pub type ConnID = i64;
pub type Pid = i32;
pub type CFStringRef = *const CFString;
type AXObserverCallback = unsafe extern "C" fn(
observer: AXObserverRef,
element: AXUIElementRef,
notification: CFStringRef,
refcon: *mut c_void,
);
unsafe extern "C" {
pub fn AXObserverCreate(
application: Pid,
callback: AXObserverCallback,
out_observer: &mut AXObserverRef,
) -> AXError;
pub fn AXObserverAddNotification(
observer: AXObserverRef,
element: AXUIElementRef,
notification: &CFString,
refcon: *mut c_void,
) -> AXError;
pub fn AXObserverRemoveNotification(
observer: AXObserverRef,
element: AXUIElementRef,
notification: &CFString,
) -> AXError;
}
pub struct PlatformCallbacks {
events: EventSender,
process_handler: ProcessHandler,
event_handler: InputHandler,
workspace_observer: Retained<WorkspaceObserver>,
mission_control_observer: MissionControlHandler,
display_handler: DisplayHandler,
}
impl PlatformCallbacks {
pub fn new(events: EventSender) -> Result<std::pin::Pin<Box<Self>>> {
let config = Config::new(CONFIGURATION_FILE.as_path())?;
events.send(Event::InitialConfig(config.clone()))?;
let workspace_observer = WorkspaceObserver::new(events.clone());
Ok(Box::pin(PlatformCallbacks {
process_handler: ProcessHandler::new(events.clone(), workspace_observer.clone()),
event_handler: InputHandler::new(events.clone(), config.clone()),
workspace_observer,
mission_control_observer: MissionControlHandler::new(events.clone()),
display_handler: DisplayHandler::new(events.clone()),
events,
}))
}
pub fn setup_handlers(&mut self) -> Result<()> {
if !NSApplication::load() {
return Err(Error::PermissionDenied(format!(
"{}: Can not startup Cocoa runloop from Carbon code.",
function_name!()
)));
}
if !check_ax_privilege() {
return Err(Error::PermissionDenied(format!(
"{}: Accessibility permissions are required. Please enable them in System Preferences -> Security & Privacy -> Privacy -> Accessibility.",
function_name!()
)));
}
if !check_separate_spaces() {
error!(
"{}: Option 'display has separate spaces' disabled.",
function_name!()
);
return Err(Error::InvalidConfig(
"Option 'display has separate spaces' disabled.".to_string(),
));
}
self.event_handler.start()?;
self.display_handler.start()?;
self.mission_control_observer.observe()?;
self.workspace_observer.start();
self.process_handler.start()?;
self.events.send(Event::ProcessesLoaded)
}
pub fn run(&mut self, quit: &Arc<AtomicBool>) {
info!("{}: Starting run loop...", function_name!());
loop {
if quit.load(std::sync::atomic::Ordering::Relaxed) {
break;
}
autoreleasepool(|_| unsafe {
CFRunLoop::run_in_mode(kCFRunLoopDefaultMode, 3.0, false);
});
}
_ = self.events.send(Event::Exit);
info!("{}: Run loop finished.", function_name!());
}
}