mod ffi;
mod error;
mod keyboard;
mod mouse;
mod screen;
use error::PlatformError;
type Error = crate::GenericError<PlatformError>;
const SHIFT_BIT: u32 = 0;
const OPTION_BIT: u32 = 1;
#[derive(Copy, Clone)]
struct KeyInfo {
key_code: u8,
modifiers: u8,
}
pub struct Context {
hid_connect: ffi::io_connect_t,
event_source: core_graphics::event_source::CGEventSource,
modifiers: ffi::IOOptionBits,
button_state: u8,
key_map: std::collections::HashMap<char, KeyInfo>,
}
fn connect_to_service(name: *const u8, connect_type: u32) -> Result<ffi::io_connect_t, Error> {
unsafe {
let matching = ffi::IOServiceMatching(name);
let mut iterator = ffi::IO_OBJECT_NULL;
let error_code = ffi::IOServiceGetMatchingServices(ffi::kIOMasterPortDefault, matching, &mut iterator);
if error_code != ffi::kIOReturnSuccess {
return Err(Error::Platform(PlatformError::new(error_code)));
}
let mut found = false;
let mut service;
let mut connect = ffi::IO_OBJECT_NULL;
loop {
service = ffi::IOIteratorNext(iterator);
if service == ffi::IO_OBJECT_NULL {
break;
}
if ffi::IOServiceOpen(service, ffi::mach_task_self_, connect_type, &mut connect) == ffi::kIOReturnSuccess {
found = true;
ffi::IOObjectRelease(service);
break;
}
ffi::IOObjectRelease(service);
}
ffi::IOObjectRelease(iterator);
if found {
Ok(connect)
} else {
Err(Error::Unknown)
}
}
}
fn create_key_map() -> Result<std::collections::HashMap<char, KeyInfo>, Error> {
use std::ffi::c_void;
use std::collections::hash_map::{HashMap, Entry};
const MAX_STRING_LENGTH: usize = 255;
let input_source;
let layout;
unsafe {
input_source = ffi::TISCopyCurrentKeyboardLayoutInputSource();
if input_source.is_null() {
return Err(Error::Unknown);
}
let layout_data = ffi::TISGetInputSourceProperty(
input_source, ffi::kTISPropertyUnicodeKeyLayoutData
);
if layout_data.is_null() {
ffi::CFRelease(input_source as *mut c_void);
return Err(Error::Unknown);
}
layout = ffi::CFDataGetBytePtr(layout_data) as *const ffi::UCKeyboardLayout;
}
let keyboard_type = unsafe { ffi::LMGetKbdType() };
let mut key_map = HashMap::new();
let mut dead_keys = 0;
let mut length = 0;
let mut string: [ffi::UniChar; MAX_STRING_LENGTH] = [0; MAX_STRING_LENGTH];
for mod_idx in 0..4 {
let shift_bit = (mod_idx & (1 << SHIFT_BIT)) << (ffi::shiftKeyBit - SHIFT_BIT - 8);
let option_bit = (mod_idx & (1 << OPTION_BIT)) << (ffi::optionKeyBit - OPTION_BIT - 8);
let modifiers = shift_bit | option_bit;
for key_code in 0..128 {
let status = unsafe {
ffi::UCKeyTranslate(
layout,
key_code,
ffi::kUCKeyActionDisplay,
modifiers,
keyboard_type as u32,
ffi::kUCKeyTranslateNoDeadKeysMask,
&mut dead_keys,
MAX_STRING_LENGTH as ffi::UniCharCount,
&mut length,
string.as_mut_ptr(),
)
};
if status != 0 {
unsafe {
ffi::CFRelease(input_source as *mut c_void);
}
return Err(Error::Unknown);
}
let string_utf16 = &string[..length as usize];
let string_utf32 = std::char::decode_utf16(string_utf16.iter().cloned())
.collect::<Vec<_>>();
if string_utf32.len() == 1 {
if let Ok(ch) = string_utf32[0] {
if let Entry::Vacant(entry) = key_map.entry(ch) {
entry.insert(KeyInfo {
key_code: key_code as u8,
modifiers: mod_idx as u8,
});
}
}
}
}
}
unsafe {
ffi::CFRelease(input_source as *mut c_void);
}
key_map.insert('\n', KeyInfo {
key_code: ffi::kVK_Return,
modifiers: 0,
});
Ok(key_map)
}
impl Context {
pub fn new() -> Result<Self, Error> {
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
let key_map = create_key_map()?;
let event_source = match CGEventSource::new(CGEventSourceStateID::Private) {
Ok(s) => s,
Err(()) => return Err(Error::Unknown),
};
let hid_connect = connect_to_service(ffi::kIOHIDSystemClass.as_ptr(), ffi::kIOHIDParamConnectType)?;
Ok(Self {
hid_connect,
event_source,
modifiers: 0,
button_state: 0,
key_map,
})
}
fn post_event(
&self,
event_type: u32,
event: *const ffi::NXEventData,
flags: ffi::IOOptionBits,
options: ffi::IOOptionBits
) -> Result<(), Error> {
let error_code = unsafe {
ffi::IOHIDPostEvent(
self.hid_connect,
event_type,
ffi::IOGPoint { x: 0, y: 0 },
event,
ffi::kNXEventDataVersion,
flags,
options
)
};
if error_code == ffi::kIOReturnSuccess {
Ok(())
} else {
Err(Error::Platform(PlatformError::new(error_code)))
}
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe {
ffi::IOServiceClose(self.hid_connect);
}
}
}