use std::{ffi::c_void, ptr::null};
use crate::{backend::macos::core_foundation::CFType, error::UIErrorKind, ApplicationId, MouseButton, Position, Size, UIResult};
use super::core_foundation::{CFArray, CFArrayIterator, CFArrayRef, CFDictionary, CFNumber, CFNumberType, CFString};
type CGFloat = f64;
pub struct CGEvent {
inner: CFType,
}
#[allow(unused)]
impl CGEvent {
pub fn from_state(state_source: CGEventSource) -> Option<Self> {
let ptr = unsafe { CGEventCreate(state_source.inner.ptr()) };
let inner = CFType::from_create(ptr)?;
Some(Self { inner })
}
pub fn mouse_move(point: CGPoint) -> Option<Self> {
let source = null();
let ty = CGEventType::MouseMoved;
let mouse = CGMouseType::Left;
let ptr = unsafe { CGEventCreateMouseEvent(source, ty, point, mouse) };
let inner = CFType::from_create(ptr)?;
Some(Self { inner })
}
pub fn mouse_click(button: CGMouseType, down: bool, point: CGPoint) -> Option<Self> {
let source = null();
let ty = match (button, down) {
(CGMouseType::Left, false) => CGEventType::LeftMouseUp,
(CGMouseType::Left, true) => CGEventType::LeftMouseDown,
(CGMouseType::Center, false) => CGEventType::OtherMouseUp,
(CGMouseType::Center, true) => CGEventType::OtherMouseDown,
(CGMouseType::Right, false) => CGEventType::RightMouseUp,
(CGMouseType::Right, true) => CGEventType::RightMouseDown,
};
let mouse = CGMouseType::Left;
let ptr = unsafe { CGEventCreateMouseEvent(source, ty, point, mouse) };
let inner = CFType::from_create(ptr)?;
Some(Self { inner })
}
pub fn set_type(&mut self, ty: CGEventType) {
unsafe {
CGEventSetType(self.inner.ptr(), ty);
}
}
#[must_use]
pub fn with_type(mut self, ty: CGEventType) -> Self {
self.set_type(ty);
self
}
pub fn mouse_location(&self) -> CGPoint {
unsafe { CGEventGetLocation(self.inner.ptr()) }
}
pub fn post(self) {
unsafe {
CGEventPost(CGEventTapLocation::HIDEventTap, self.inner.ptr());
}
}
}
pub struct CGEventSource {
inner: CFType,
}
impl CGEventSource {
pub fn new(source: CGEventSourceStateID) -> Option<Self> {
let ptr = unsafe { CGEventSourceCreate(source) };
let inner = CFType::from_create(ptr)?;
Some(Self { inner })
}
pub fn combined_state() -> Option<Self> {
Self::new(CGEventSourceStateID::CombinedSessionState)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct CGPoint {
pub x: CGFloat,
pub y: CGFloat,
}
impl From<Position> for CGPoint {
fn from(value: Position) -> Self {
Self {
x: value.x(),
y: value.y(),
}
}
}
impl From<CGPoint> for Position {
fn from(value: CGPoint) -> Self {
Self::new(value.x, value.y)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct CGSize {
pub width: CGFloat,
pub height: CGFloat,
}
impl From<Size> for CGSize {
fn from(value: Size) -> Self {
Self {
width: value.width(),
height: value.height(),
}
}
}
impl From<CGSize> for Size {
fn from(value: CGSize) -> Self {
Self::new(value.width, value.height)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
#[allow(unused)]
pub enum CGEventType {
Null = 0,
LeftMouseDown = 1,
LeftMouseUp = 2,
RightMouseDown = 3,
RightMouseUp = 4,
MouseMoved = 5,
LeftMouseDragged = 6,
RightMouseDragged = 7,
OtherMouseDown = 25,
OtherMouseUp = 26,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
#[allow(unused)]
pub enum CGMouseType {
Left = 0,
Right = 1,
Center = 2,
}
impl From<MouseButton> for CGMouseType {
fn from(value: MouseButton) -> Self {
match value {
MouseButton::Left => Self::Left,
MouseButton::Middle => Self::Center,
MouseButton::Right => Self::Right,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
#[allow(unused)]
pub enum CGEventTapLocation {
HIDEventTap = 0,
SessionEventTap = 1,
AnnotatedSessionEventTap = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
#[allow(unused)]
pub enum CGEventSourceStateID {
Private = -1,
CombinedSessionState = 0,
HIDSystemState = 1,
}
pub type CGEventSourceRef = *const c_void;
pub type CGEventRef = *const c_void;
pub type CGWindowListOption = u32;
pub type CGWindowId = u32;
pub(super) struct CGWindowList {
array: CFArrayIterator<CFDictionary<CFString>>,
}
impl CGWindowList {
pub fn create() -> UIResult<Self> {
Self::new(None)
}
pub fn new(relative_to: Option<ApplicationId>) -> UIResult<Self> {
let option = CGWindowListOptionType::ALL;
let relative_to = relative_to.unwrap_or(ApplicationId(0)).0 as u32;
let array = unsafe { CGWindowListCopyWindowInfo(option, relative_to) };
let Some(inner) = CFType::from_create(array) else {
return Err(UIErrorKind::AllocationFailed { resource: "CGWindowListCopyWindowInfo" }.into());
};
let array = CFArray::from_ptr(inner);
Ok(Self {
array: array.iter(),
})
}
}
impl Iterator for CGWindowList {
type Item = CGWindowInformation;
fn next(&mut self) -> Option<Self::Item> {
Some(CGWindowInformation {
dict: self.array.next()?,
})
}
}
pub struct CGWindowInformation {
pub(crate) dict: CFDictionary<CFString>,
}
#[allow(unused)]
impl CGWindowInformation {
pub fn create_description(id: CGWindowId) -> UIResult<Self> {
let Some(ids) = CFArray::from_slice(&[id]) else {
return Err(UIErrorKind::AllocationFailed { resource: "CGArray" }.into());
};
debug_assert_eq!(ids.len(), 1);
let Some(ptr) = CFType::from_create(unsafe { CGWindowListCreateDescriptionFromArray(ids.inner.ptr()) }) else {
return Err(UIErrorKind::AllocationFailed { resource: "CGWindowListCreateDescriptionFromArray" }.into());
};
let dict_array = CFArray::from_ptr(ptr);
let Some(dict_ref) = dict_array.get_ptr(0) else {
return Err(UIErrorKind::AllocationFailed { resource: "CGWindowListDescription" }.into());
};
let dict = CFDictionary::from_get(dict_ref)?;
Ok(Self { dict })
}
pub fn owner_pid(&self) -> Option<u32> {
let key = CFString::from("kCGWindowOwnerPID");
let value: CFNumber = self.dict.get(&key)?;
let value = value.get(CFNumberType::Int)?;
Some(value as _)
}
pub fn number(&self) -> Option<u32> {
let key = CFString::new("kCGWindowNumber")?;
self.dict.get::<u32>(&key)
}
pub fn owner_name(&self) -> UIResult<String> {
let Some(key) = CFString::new("kCGWindowOwnerName") else {
return Err(UIErrorKind::AllocationFailed { resource: "CFString of kCGWindowOwnerName" }.into());
};
let Some(value) = self.dict.get::<CFString>(&key) else {
return Ok(String::new());
};
value.to_string()
}
}
struct CGWindowListOptionType;
#[allow(unused)]
impl CGWindowListOptionType {
const ALL: u32 = 0;
const ON_SCREEN_ONLY: u32 = 1;
const ON_SCREEN_ABOVE_WINDOW: u32 = 2;
const ON_SCREEN_BELOW_WINDOW: u32 = 4;
const INCLUDING_WINDOW: u32 = 8;
const EXCLUDE_DESKTOP_ELEMENTS: u32 = 16;
}
#[allow(unused)]
extern "C-unwind" {
fn CGEventCreateMouseEvent(source: CGEventSourceRef, ty: CGEventType, point: CGPoint, mouse: CGMouseType) -> CGEventRef;
fn CGEventSetType(event: CGEventRef, ty: CGEventType);
fn CGEventPost(tap: CGEventTapLocation, event: CGEventRef);
fn CGEventCreate(source: CGEventSourceRef) -> CGEventRef;
fn CGEventGetLocation(event: CGEventRef) -> CGPoint;
fn CGEventSourceCreate(state_id: CGEventSourceStateID) -> CGEventSourceRef;
fn CGWindowListCreate(option: CGWindowListOption, relative_to_window: CGWindowId) -> CFArrayRef;
fn CGWindowListCreateDescriptionFromArray(window_array: CFArrayRef) -> CFArrayRef;
fn CGWindowListCopyWindowInfo(option: CGWindowListOption, relative_to: CGWindowId) -> CFArrayRef;
}
#[cfg(test)]
mod tests {
use super::CGWindowList;
#[test]
fn test_cg_window_list() {
let _: Vec<_> = CGWindowList::create().unwrap().collect();
}
}