rusty-keys/src/macos/mod.rs

415 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(improper_ctypes)]
use crate::*;
use std::env;
use std::process::exit;
use getopts::Options;
use std::fs::File;
pub mod codes;
use codes::*;
use core_graphics::event::CGKeyCode;
use core_graphics::event::*;
use core_graphics::event_source::*;
use core_graphics::event::{CGEventTapLocation, CGEventType};
type MacOSKeyMaps = KeyMaps<CGEventSource, CGKeyCode, CGEvent, Option<CGEvent>>;
type CallbackPointer = (MacOSKeyMaps, CGEventSource);
/*
// possible types for event_source
Private = -1,
CombinedSessionState = 0,
HIDSystemState = 1,
// possible types for tapLocation
HID,
Session,
AnnotatedSession,
*/
// macOS seems to require this, or it ignores shift, WHY?
const delay: std::time::Duration = std::time::Duration::from_millis(20);
const tapLocation: CGEventTapLocation = CGEventTapLocation::Session;
// this is only used if tapLocation is HID, to prevent us from mapping our own key inputs
const uniqueHIDUserData: i64 = 45;
impl KeyEvent<CGKeyCode> for CGEvent {
fn code(&self) -> CGKeyCode {
self.get_integer_value_field(EventField::KEYBOARD_EVENT_KEYCODE) as CGKeyCode
}
fn value(&self) -> KeyState {
let event_type = self.get_type();
match event_type {
CGEventType::FlagsChanged => {
let flags = self.get_flags().bits(); // todo: fix cast?
let mask = match self.code() {
KEY_LEFTCTRL => NX_DEVICELCTLKEYMASK,
KEY_RIGHTCTRL => NX_DEVICERCTLKEYMASK,
KEY_LEFTSHIFT => NX_DEVICELSHIFTKEYMASK,
KEY_RIGHTSHIFT => NX_DEVICERSHIFTKEYMASK,
KEY_LEFTMETA => NX_DEVICELCMDKEYMASK,
KEY_RIGHTMETA => NX_DEVICERCMDKEYMASK,
KEY_LEFTALT => NX_DEVICELALTKEYMASK,
KEY_RIGHTALT => NX_DEVICERALTKEYMASK,
KEY_CAPSLOCK => NX_DEVICECAPSLOCKMASK,
_ => panic!("unhandled key: {}", self.code()),
};
if (flags & mask) != 0 { KeyState::DOWN } else { KeyState::UP }
},
CGEventType::KeyDown => KeyState::DOWN,
CGEventType::KeyUp => KeyState::UP,
CGEventType::TapDisabledByTimeout => {
println!("Quartz event tap disabled because of timeout; attempting to reregister.");
todo!("implement register listener");
//register_listener(channel);
//KeyState::OTHER
},
_ => {
println!("Received unknown EventType: {:?}", event_type);
KeyState::OTHER
},
}
}
}
impl Keyboard<CGKeyCode, CGEvent, Option<CGEvent>> for CGEventSource {
fn send(&self, event: &mut CGEvent) -> Result<Option<CGEvent>> {
//println!("send orig: {}", event.code());
//Ok(Some(event.event))
self.send_mod_code_value(event.code(), event.value() == KeyState::UP, event)
}
fn send_mod_code(&self, code: CGKeyCode, event: &mut CGEvent) -> Result<Option<CGEvent>> {
// event.value should only ever be UP/DOWN when this method is called
//println!("send_mod_code orig: {} code: {}", event.code(), code);
//unsafe { CGEventSetIntegerValueField(event.event, kCGKeyboardEventKeycode, code as i64) };
//Ok(Some(event.event))
self.send_mod_code_value(code, event.value() == KeyState::UP, event)
}
fn send_mod_code_value(&self, code: CGKeyCode, up_not_down: bool, _event: &mut CGEvent) -> Result<Option<CGEvent>> {
//println!("send_mod_code_value orig: {} code: {}, up_not_down: {}", event.code(), code, up_not_down);
//return Ok(None);
let event =
CGEvent::new_keyboard_event(self.clone(), code, !up_not_down)
.expect("Failed creating event");
match tapLocation {
CGEventTapLocation::HID => event.set_integer_value_field(EventField::EVENT_SOURCE_USER_DATA, uniqueHIDUserData),
_ => {}
};
event.post(tapLocation);
Ok(None)
}
fn synchronize(&self) -> Result<Option<CGEvent>> {
std::thread::sleep(delay);
Ok(None)
}
fn left_shift_code(&self) -> CGKeyCode {
KEY_LEFTSHIFT
}
fn right_shift_code(&self) -> CGKeyCode {
KEY_RIGHTSHIFT
}
fn caps_lock_code(&self) -> CGKeyCode {
KEY_CAPSLOCK
}
fn block_key(&self) -> Result<Option<CGEvent>> {
Ok(None)
}
}
pub fn main_res() -> Result<()> {
let config = parse_args();
//println!("Config: {:?}", config);
let key_map = key_map();
//println!("key_map: {:?}", key_map);
let key_maps = MacOSKeyMaps::from_cfg(&key_map, &config.config_file);
//println!("key_maps: {}", key_maps);
let callback_pointer: CallbackPointer = (key_maps, CGEventSource::new(CGEventSourceStateID::Private).expect("Failed creating event source"));
let mask = CGEventMaskBit(CGEventType::KeyDown)
| CGEventMaskBit(CGEventType::KeyUp)
| CGEventMaskBit(CGEventType::FlagsChanged)
;
unsafe {
let options = 0;
// Create the event tap
let event_tap = CGEventTapCreate(
kCGSessionEventTap,
kCGHeadInsertEventTap,
options,
mask,
callback,
&callback_pointer,
);
if event_tap.is_null() {
panic!("Unable to create event tap. Please make sure you have the correct permissions");
}
println!("Created event tap...");
let allocator = kCFAllocatorDefault;
let current_event_loop = CFRunLoopGetCurrent();
let mode = kCFRunLoopCommonModes;
// Create Run Loop Source
let run_loop_source = CFMachPortCreateRunLoopSource(allocator, event_tap, 0);
// Add Run Loop Source to the current event loop
CFRunLoopAddSource(current_event_loop, run_loop_source, mode);
// Enable the tap
CGEventTapEnable(event_tap, true);
CFRunLoopRun();
}
Ok(())
}
#[derive(Debug)]
struct Config {
config_file: String
}
impl Config {
fn new(config_file: String) -> Self {
Config { config_file: config_file }
}
}
fn get_env_push(key: &str, to_push: &str, vec: &mut Vec<String>) {
if let Some(var) = env::var_os(key) {
if let Ok(str) = var.into_string() {
let mut str = str.to_owned();
str.push_str(to_push);
vec.push(str);
}
}
}
fn parse_args() -> Config {
fn print_usage(program: &str, opts: Options) {
let brief = format!("Usage: {} [options] [keymap.toml]", program);
println!("{}", opts.usage(&brief));
}
let args: Vec<_> = env::args().collect();
let mut default_configs = Vec::new();
get_env_push("USERPROFILE", "\\keymap.toml", &mut default_configs);
get_env_push("APPDATA", "\\keymap.toml", &mut default_configs);
default_configs.push("keymap.toml".to_string());
let c_msg = format!("specify the keymap config file to use (default in order: {:?})", default_configs);
let mut opts = Options::new();
opts.optflag("h", "help", "prints this help message");
opts.optflag("v", "version", "prints the version");
opts.optopt("c", "config", &c_msg, "FILE");
let matches = opts.parse(&args[1..]);
if matches.is_err() {
print_usage(&args[0], opts);
exit(1);
}
let matches = matches.unwrap();
if matches.opt_present("h") {
print_usage(&args[0], opts);
exit(0);
}
if matches.opt_present("v") {
println!("rusty-keys {}", VERSION);
exit(0);
}
let config_file = matches.opt_str("c").unwrap_or_else(|| {
let remaining_args = matches.free;
if remaining_args.len() > 0 {
remaining_args[0].clone()
} else {
for keymap in default_configs.drain(..) {
if File::open(&keymap).is_ok() {
return keymap;
}
}
println!("Error: no keymap.toml found...");
print_usage(&args[0], opts);
exit(1);
}
});
Config::new(config_file)
}
use libc;
// Opaque Pointer Types
type Pointer = *mut libc::c_void;
type CFMachPortRef = Pointer;
// Integer Types
type CGEventMask = u64;
type CGEventTapOptions = u32;
type CGEventTapPlacement = u32;
// Callback Type
type CGEventTapCallBack = extern "C" fn(Pointer, CGEventType, CGEvent, &mut CallbackPointer) -> CGEvent;
// Constants
const kCGSessionEventTap: CGEventTapLocation = CGEventTapLocation::HID;
const kCGHeadInsertEventTap: CGEventTapPlacement = 0;
// Link to ApplicationServices/ApplicationServices.h and Carbon/Carbon.h
#[link(name = "ApplicationServices", kind = "framework")]
#[link(name = "Carbon", kind = "framework")]
extern {
/// Pass through to the default loop modes
pub static kCFRunLoopCommonModes: Pointer;
/// Pass through to the default allocator
pub static kCFAllocatorDefault: Pointer;
/// Run the current threads loop in default mode
pub fn CFRunLoopRun();
/// Obtain the current threads loop
pub fn CFRunLoopGetCurrent() -> Pointer;
/// Create an event tap
///
/// # Arguments
///
/// * `place` - The location of the new event tap. Pass one of
/// the constants listed in Event Tap Locations. Only
/// processes running as the root user may locate an
/// event tap at the point where HID events enter the
/// window server; for other users, this function
/// returns NULL.
///
/// * `options` - The placement of the new event tap in the
/// list of active event taps. Pass one of the
/// constants listed in Event Tap Placement.
///
/// * `eventsOfInterest` - A constant that specifies whether
/// the new event tap is a passive listener or an
/// active filter.
///
/// * `callback` - A bit mask that specifies the set of events
/// to be observed. For a list of possible events,
/// see Event Types. For information on how to
/// specify the mask, see CGEventMask. If the event
/// tap is not permitted to monitor one or more of
/// the events specified in the eventsOfInterest
/// parameter, then the appropriate bits in the mask
/// are cleared. If that action results in an empty
/// mask, this function returns NULL. callback
///
/// * `refcon` - An event tap callback function that you
/// provide. Your callback function is invoked from
/// the run loop to which the event tap is added as a
/// source. The thread safety of the callback is
/// defined by the run loops environment. To learn
/// more about event tap callbacks, see
/// CGEventTapCallBack. refcon
///
/// * `channel` - A pointer to user-defined data. This pointer
/// is passed into the callback function specified in
/// the callback parameter. Here we use it as a mpsc
/// channel.
pub fn CGEventTapCreate(
tap: CGEventTapLocation,
place: CGEventTapPlacement,
options: CGEventTapOptions,
eventsOfInterest: CGEventMask,
callback: CGEventTapCallBack,
channel: &CallbackPointer,
) -> CFMachPortRef;
/// Creates a CFRunLoopSource object for a CFMachPort
/// object.
///
/// The run loop source is not automatically added to
/// a run loop. To add the source to a run loop, use
/// CFRunLoopAddSource
pub fn CFMachPortCreateRunLoopSource(
allocator: Pointer,
port: CFMachPortRef,
order: libc::c_int,
) -> Pointer;
/// Adds a CFRunLoopSource object to a run loop mode.
pub fn CFRunLoopAddSource(
run_loop: Pointer,
run_loop_source: Pointer,
mode: Pointer,
);
pub fn CGEventTapEnable(port: CFMachPortRef, enable: bool);
}
const NX_DEVICELCTLKEYMASK: u64 = 0x00000001;
const NX_DEVICERCTLKEYMASK: u64 = 0x00002000;
const NX_DEVICELSHIFTKEYMASK: u64 = 0x00000002;
const NX_DEVICERSHIFTKEYMASK: u64 = 0x00000004;
const NX_DEVICELCMDKEYMASK: u64 = 0x00000008;
const NX_DEVICERCMDKEYMASK: u64 = 0x00000010;
const NX_DEVICELALTKEYMASK: u64 = 0x00000020;
const NX_DEVICERALTKEYMASK: u64 = 0x00000040;
const NX_DEVICECAPSLOCKMASK: u64 = 1 << 16;
/// This callback will be registered to be invoked from the run loop
/// to which the event tap is added as a source.
#[no_mangle]
#[allow(unused_variables, improper_ctypes_definitions)]
pub extern fn callback(proxy: Pointer, event_type: CGEventType, mut event: CGEvent, callback_pointer: &mut CallbackPointer) -> CGEvent {
let (key_maps, event_source) = callback_pointer;
match tapLocation {
CGEventTapLocation::HID => {
let user_data = event.get_integer_value_field(EventField::EVENT_SOURCE_USER_DATA);
if user_data == uniqueHIDUserData {
return event;
}
}
_ => {}
};
key_maps.send_event(&mut event, &event_source).expect("macos shouldn't error...")
.unwrap_or_else(|| {
event.set_type(CGEventType::Null);
event
}) // None means return NULL
}
/// Redefine macro for bitshifting from header as function here
pub fn CGEventMaskBit(eventType: CGEventType) -> CGEventMask {
1 << (eventType as u32)
}