415 lines
14 KiB
Rust
415 lines
14 KiB
Rust
|
#![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 loop’s 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)
|
|||
|
}
|
|||
|
|