use core::ptr::NonNull;
use log::{debug, error, info};
use objc2::rc::Retained;
use std::ffi::c_void;
use stdext::function_name;
use super::workspace::WorkspaceObserver;
use crate::errors::{Error, Result};
use crate::events::{Event, EventSender};
use crate::platform::OSStatus;
use crate::util::Cleanuper;
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
#[repr(C)]
pub struct ProcessSerialNumber {
pub high: u32,
pub low: u32,
}
type ProcessCallbackFn = extern "C-unwind" fn(
this: *mut c_void,
event: *const ProcessEvent,
context: *const c_void,
) -> OSStatus;
unsafe extern "C" {
fn GetApplicationEventTarget() -> *const ProcessEventTarget;
fn InstallEventHandler(
target: *const ProcessEventTarget,
handler: ProcessCallbackFn,
event_len: u32,
events: *const EventTypeSpec,
user_data: *const c_void,
handler_ref: *mut *const ProcessEventHandler,
) -> OSStatus;
fn RemoveEventHandler(handler_ref: *const ProcessEventHandler) -> OSStatus;
fn GetEventParameter(
event: *const ProcessEvent,
param_name: u32,
param_type: u32,
actual_type: *mut u32,
size: u32,
actual_size: *mut u32,
data: *mut c_void,
) -> OSStatus;
fn GetEventKind(event: *const ProcessEvent) -> u32;
fn GetNextProcess(psn: *mut ProcessSerialNumber) -> OSStatus;
}
#[repr(C)]
struct EventTypeSpec {
event_class: u32,
event_kind: u32,
}
#[repr(C)]
struct ProcessEventHandler {
_opaque: [u8; 0],
}
#[repr(C)]
struct ProcessEventTarget {
_opaque: [u8; 0],
}
#[repr(C)]
struct ProcessEvent {
_opaque: [u8; 0],
}
#[repr(C)]
pub(super) struct ProcessHandler {
events: EventSender,
cleanup: Option<Cleanuper>,
observer: Retained<WorkspaceObserver>,
}
#[repr(C)]
#[allow(dead_code)]
enum ProcessEventApp {
Activated = 1,
Deactivated = 2,
Quit = 3,
LaunchNotification = 4,
Launched = 5,
Terminated = 6,
FrontSwitched = 7,
FocusMenuBar = 8,
FocusNextDocumentWindow = 9,
FocusNextFloatingWindow = 10,
FocusToolbar = 11,
FocusDrawer = 12,
GetDockTileMenu = 20,
UpdateDockTile = 21,
IsEventInInstantMouser = 104,
Hidden = 107,
Shown = 108,
SystemUIModeChanged = 109,
AvailableWindowBoundsChanged = 110,
ActiveWindowChanged = 111,
}
impl ProcessHandler {
pub(super) fn new(events: EventSender, observer: Retained<WorkspaceObserver>) -> Self {
ProcessHandler {
events,
cleanup: None,
observer,
}
}
pub(super) fn start(&mut self) -> Result<()> {
const APPL_CLASS: &str = "appl";
const PROCESS_EVENT_LAUNCHED: u32 = 5;
const PROCESS_EVENT_TERMINATED: u32 = 6;
const PROCESS_EVENT_FRONTSWITCHED: u32 = 7;
info!("{}: Registering process_handler", function_name!());
let mut psn = ProcessSerialNumber::default();
while 0 == unsafe { GetNextProcess(&raw mut psn) } {
self.process_handler(psn, ProcessEventApp::Launched);
}
let target = unsafe { GetApplicationEventTarget() };
let event_class = u32::from_be_bytes(APPL_CLASS.as_bytes().try_into()?);
let events = [
EventTypeSpec {
event_class,
event_kind: PROCESS_EVENT_LAUNCHED,
},
EventTypeSpec {
event_class,
event_kind: PROCESS_EVENT_TERMINATED,
},
EventTypeSpec {
event_class,
event_kind: PROCESS_EVENT_FRONTSWITCHED,
},
];
let mut handler: *const ProcessEventHandler = std::ptr::null();
let result = unsafe {
InstallEventHandler(
target,
Self::callback,
events.len().try_into()?,
events.as_ptr(),
NonNull::new_unchecked(self).as_ptr().cast(),
&raw mut handler,
)
};
if result != 0 || handler.is_null() {
return Err(Error::PermissionDenied(format!(
"{}: Error registering process event handler.",
function_name!()
)));
}
debug!(
"{}: Registered process_handler (result = {result}): {handler:x?}",
function_name!()
);
self.cleanup = Some(Cleanuper::new(Box::new(move || unsafe {
info!(
"{}: Unregistering process_handler: {handler:?}",
function_name!()
);
RemoveEventHandler(handler);
})));
Ok(())
}
extern "C-unwind" fn callback(
_: *mut c_void,
event: *const ProcessEvent,
this: *const c_void,
) -> OSStatus {
match NonNull::new(this.cast_mut())
.map(|this| unsafe { this.cast::<ProcessHandler>().as_mut() })
{
Some(this) => {
const PARAM: &str = "psn "; let param_name = u32::from_be_bytes(PARAM.as_bytes().try_into().unwrap());
let param_type = param_name;
let mut psn = ProcessSerialNumber::default();
let res = unsafe {
GetEventParameter(
event,
param_name,
param_type,
std::ptr::null_mut(),
std::mem::size_of::<ProcessSerialNumber>()
.try_into()
.unwrap(),
std::ptr::null_mut(),
NonNull::from(&mut psn).as_ptr().cast(),
)
};
if res == 0 {
let decoded: ProcessEventApp =
unsafe { std::mem::transmute(GetEventKind(event)) };
this.process_handler(psn, decoded);
}
}
_ => error!("Zero passed to Process Handler."),
}
0
}
fn process_handler(&mut self, psn: ProcessSerialNumber, event: ProcessEventApp) {
let _ = match event {
ProcessEventApp::Launched => self.events.send(Event::ApplicationLaunched {
psn,
observer: self.observer.clone(),
}),
ProcessEventApp::Terminated => self.events.send(Event::ApplicationTerminated { psn }),
ProcessEventApp::FrontSwitched => {
self.events.send(Event::ApplicationFrontSwitched { psn })
}
_ => {
error!(
"{}: Unknown process event: {}",
function_name!(),
event as u32
);
Ok(())
}
}
.inspect_err(|err| error!("{}: error sending event: {err}", function_name!()));
}
}