use std::thread;
use std::time::Duration;
use core_graphics::event::{CGEvent, CGEventFlags, CGEventTapLocation};
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
use super::error::AicError;
use super::keymap::{is_modifier, resolve_key, resolve_modifier};
fn event_source() -> Result<CGEventSource, AicError> {
CGEventSource::new(CGEventSourceStateID::HIDSystemState)
.map_err(|_| AicError::EventCreationFailed("failed to create event source".into()))
}
fn post_key(keycode: u16, down: bool, flags: CGEventFlags) -> Result<(), AicError> {
let source = event_source()?;
let event = CGEvent::new_keyboard_event(source, keycode, down)
.map_err(|_| AicError::EventCreationFailed("failed to create keyboard event".into()))?;
if flags != CGEventFlags::CGEventFlagNull {
event.set_flags(flags);
}
event.post(CGEventTapLocation::HID);
Ok(())
}
pub fn press_key(key: &str) -> Result<(), AicError> {
let keycode = resolve_key(key)?;
post_key(keycode, true, CGEventFlags::CGEventFlagNull)?;
thread::sleep(Duration::from_millis(10));
post_key(keycode, false, CGEventFlags::CGEventFlagNull)?;
Ok(())
}
pub fn key_combo(keys: &[String]) -> Result<(), AicError> {
if keys.is_empty() {
return Err(AicError::EventCreationFailed(
"combo requires at least one key".into(),
));
}
let mut flags = CGEventFlags::CGEventFlagNull;
let mut main_key = None;
for (i, k) in keys.iter().enumerate() {
if i == keys.len() - 1 && !is_modifier(k) {
main_key = Some(k.as_str());
} else if is_modifier(k) {
flags |= resolve_modifier(k)?;
} else {
main_key = Some(
keys.last()
.expect("keys is non-empty: we are iterating over it")
.as_str(),
);
break;
}
}
let main_key = main_key.ok_or_else(|| {
AicError::EventCreationFailed("combo requires a non-modifier key as last argument".into())
})?;
let keycode = resolve_key(main_key)?;
post_key(keycode, true, flags)?;
thread::sleep(Duration::from_millis(10));
post_key(keycode, false, flags)?;
Ok(())
}
pub fn type_text(text: &str, delay_ms: u64) -> Result<(), AicError> {
let source = event_source()?;
for ch in text.chars() {
let event = CGEvent::new_keyboard_event(source.clone(), 0, true)
.map_err(|_| AicError::EventCreationFailed("failed to create typing event".into()))?;
let buf = [ch as u16];
event.set_string_from_utf16_unchecked(&buf);
event.post(CGEventTapLocation::HID);
let event_up = CGEvent::new_keyboard_event(source.clone(), 0, false)
.map_err(|_| AicError::EventCreationFailed("failed to create typing event".into()))?;
event_up.post(CGEventTapLocation::HID);
thread::sleep(Duration::from_millis(delay_ms));
}
Ok(())
}