use core::ptr::NonNull;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use scopeguard::ScopeGuard;
use std::ffi::c_void;
use std::marker::PhantomPinned;
use std::pin::Pin;
use stdext::function_name;
use tracing::{Level, debug, error, instrument};
use crate::errors::Result;
use crate::events::{Event, EventSender};
use crate::platform::{ConnID, OSStatus, WinID, WorkspaceId};
use crate::util::MacResult;
unsafe extern "C" {
pub fn SLSMainConnectionID() -> ConnID;
pub fn SLSSpaceGetType(cid: ConnID, sid: WorkspaceId) -> libc::c_int;
pub fn SLSRegisterConnectionNotifyProc(
cid: ConnID,
callback: extern "C-unwind" fn(u32, *mut c_void, usize, *mut c_void, ConnID),
event: u32,
data: *mut c_void,
) -> OSStatus;
}
pub(super) struct NotifyHandler {
events: EventSender,
conn: ConnID,
_pin: PhantomPinned,
}
pub(super) type PinnedNotifyHandler =
ScopeGuard<Pin<Box<NotifyHandler>>, Box<dyn FnOnce(Pin<Box<NotifyHandler>>)>>;
impl NotifyHandler {
pub(super) fn new(events: EventSender) -> Self {
Self {
events,
conn: unsafe { SLSMainConnectionID() },
_pin: PhantomPinned,
}
}
pub(super) fn start(self) -> Result<PinnedNotifyHandler> {
debug!("Registering notify handler");
let cid = self.conn;
let mut pinned = Box::pin(self);
let this = unsafe { NonNull::new_unchecked(pinned.as_mut().get_unchecked_mut()) }.as_ptr();
let events = [
KnownCGSEvent::SpaceCreated,
KnownCGSEvent::SpaceCurrentChanged,
KnownCGSEvent::SpaceDestroyed,
KnownCGSEvent::SpaceWindowDestroyed,
];
for event in events {
unsafe {
SLSRegisterConnectionNotifyProc(cid, Self::callback, event.into(), this.cast())
}
.to_result(function_name!())?;
}
Ok(scopeguard::guard(
pinned,
Box::new(|_pin: Pin<Box<Self>>| {
debug!("Unregistering notify handler");
}),
))
}
extern "C-unwind" fn callback(
event_id: u32,
data: *mut c_void,
len: usize,
context: *mut c_void,
cid: ConnID,
) {
if let Some(this) =
NonNull::new(context).map(|this| unsafe { this.cast::<NotifyHandler>().as_mut() })
{
this.notify_handler(event_id, data, len, cid);
} else {
error!("Zero passed to Notify Handler.");
}
}
#[instrument(level = Level::DEBUG, skip_all, fields(event_id, len))]
fn notify_handler(&self, event_id: u32, data: *mut c_void, len: usize, _cid: ConnID) {
let CGSEventType::Known(event) = event_id.into() else {
debug!("Unknown event received: {event_id}");
return;
};
match event {
KnownCGSEvent::SpaceCreated
| KnownCGSEvent::SpaceDestroyed
| KnownCGSEvent::SpaceCurrentChanged => {
if let Some(space_id) = from_bytes::<WorkspaceId>(data, len) {
if matches!(event, KnownCGSEvent::SpaceDestroyed) {
_ = self.events.send(Event::SpaceDestroyed { space_id });
} else {
let space_type = unsafe { SLSSpaceGetType(self.conn, space_id) };
if space_type == 0 && matches!(event, KnownCGSEvent::SpaceCreated) {
_ = self.events.send(Event::SpaceCreated { space_id });
} else {
debug!("{event} space = {space_id}, space_type = {space_type}");
}
}
}
}
KnownCGSEvent::SpaceWindowDestroyed | KnownCGSEvent::SpaceWindowCreated => {
let offset = std::mem::size_of::<u64>();
if let Some(space) = from_bytes::<WorkspaceId>(data, len)
&& let Some(window_id) = from_bytes::<WinID>(
unsafe { data.byte_add(offset) },
len.saturating_sub(offset),
)
{
debug!("{event} space = {space}, window_id = {window_id}");
_ = self.events.send(Event::WindowDestroyed { window_id });
}
}
KnownCGSEvent::WindowClosed
| KnownCGSEvent::WindowMoved
| KnownCGSEvent::WindowResized
| KnownCGSEvent::WindowReordered
| KnownCGSEvent::WindowLevelChanged
| KnownCGSEvent::WindowUnhidden
| KnownCGSEvent::WindowHidden
| KnownCGSEvent::WindowManagerActivatingClickOrdering
| KnownCGSEvent::WindowOrderingGroupChanged
| KnownCGSEvent::WindowParentChanged => {
let window_id = from_bytes::<WinID>(data, len);
debug!("{event} window_id = {window_id:?}");
}
_ => {
let bytes = (!data.is_null() && len > 0)
.then_some(unsafe { std::slice::from_raw_parts(data as *const u8, len) });
debug!("Unhandled event {event}: {bytes:?}");
}
}
}
}
fn from_bytes<T>(data: *const c_void, len: usize) -> Option<T> {
let size = std::mem::size_of::<T>();
(!data.is_null() && len >= size)
.then_some(unsafe { std::ptr::read_unaligned(data.cast::<T>()) })
}
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
enum KnownCGSEvent {
DisplayWillSleep = 102,
DisplayDidWake = 103,
WindowUpdated = 723,
WindowClosed = 804,
WindowMoved = 806,
WindowResized = 807,
WindowReordered = 808,
WindowLevelChanged = 811,
WindowUnhidden = 815,
WindowHidden = 816,
MissionControlEntered = 1204,
PackagesStatusBarSpaceChanged = 1308,
WindowTitleChanged = 1322,
SpaceWindowCreated = 1325,
SpaceWindowDestroyed = 1326,
SpaceCreated = 1327,
SpaceDestroyed = 1328,
SpaceCurrentChanged = 1329,
WindowManagerActivatingClickOrdering = 1333,
WindowManagerSpaceFrontConnectionChanged = 1334,
WindowManagerGlobalFrontConnectionChanged = 1335,
WindowOrderingGroupChanged = 1336,
SpaceWindowTransactionCommitted = 1338,
SpaceWindowBatchReassociated = 1339,
SpaceWindowManagementCapabilitiesChanged = 1340,
WindowParentChanged = 1341,
ManagedSpaceMembershipUpdated = 1342,
WorkspaceWillChange = 1400,
WorkspaceDidChange = 1401,
WorkspaceWindowIsViewable = 1402,
WorkspaceWindowIsNotViewable = 1403,
WorkspaceWindowDidMove = 1404,
WorkspacePrefsDidChange = 1405,
WorkspacesWindowDragDidStart = 1411,
WorkspacesWindowDragDidEnd = 1412,
WorkspacesWindowDragWillEnd = 1413,
WorkspacesShowSpaceForProcess = 1414,
WorkspacesWindowDidOrderInOnNonCurrentManagedSpacesOnly = 1415,
WorkspacesWindowDidOrderOutOnNonCurrentManagedSpaces = 1416,
FrontmostApplicationChanged = 1508,
TransitionDidFinish = 1700,
All = 0xFFFF_FFFF,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum CGSEventType {
Known(KnownCGSEvent),
Unknown(u32),
}
impl From<u32> for CGSEventType {
fn from(v: u32) -> Self {
match KnownCGSEvent::try_from(v) {
Ok(k) => Self::Known(k),
Err(_) => Self::Unknown(v),
}
}
}
impl From<CGSEventType> for u32 {
fn from(k: CGSEventType) -> u32 {
match k {
CGSEventType::Known(k) => k as u32,
CGSEventType::Unknown(v) => v,
}
}
}
impl std::fmt::Display for KnownCGSEvent {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
impl std::fmt::Display for CGSEventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CGSEventType::Known(k) => write!(f, "{k}"),
CGSEventType::Unknown(v) => write!(f, "Unknown({v})"),
}
}
}