Skip to content

Commit

Permalink
Implement Resolving Hotkeys on macOS
Browse files Browse the repository at this point in the history
This adds the implementation for resolving `KeyCode` on macOS based on
the user's current keyboard layout.
  • Loading branch information
CryZe committed Oct 24, 2021
1 parent aba2ac9 commit 70a0d3c
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 5 deletions.
56 changes: 56 additions & 0 deletions crates/livesplit-hotkey/src/macos/carbon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#![allow(dead_code)]

use super::cf::StringRef;
use std::os::raw::{c_ulong, c_void};

mod opaque {
pub enum TISInputSource {}
pub enum UCKeyboardLayout {}
}

pub type TISInputSourceRef = *mut opaque::TISInputSource;

pub type OptionBits = u32;
pub type UniCharCount = c_ulong;
pub type UniChar = u16;
pub type OSStatus = i32;

bitflags::bitflags! {
#[repr(transparent)]
pub struct UCKeyTranslateBits: OptionBits {
const NO_DEAD_KEYS_BIT = 0;
}
}

#[repr(u16)]
#[non_exhaustive]
pub enum UCKeyAction {
Down = 0,
Up = 1,
AutoKey = 2,
Display = 3,
}

#[link(name = "Carbon", kind = "framework")]
extern "C" {
pub static kTISPropertyUnicodeKeyLayoutData: StringRef;
pub fn TISCopyCurrentKeyboardInputSource() -> TISInputSourceRef;
pub fn TISCopyCurrentKeyboardLayoutInputSource() -> TISInputSourceRef;
pub fn TISGetInputSourceProperty(
input_source: TISInputSourceRef,
property_key: StringRef,
) -> *mut c_void;
pub fn UCKeyTranslate(
key_layout_ptr: *const opaque::UCKeyboardLayout,
virtual_key_code: u16,
key_action: u16,
modifier_key_state: u32,
keyboard_type: u32,
key_translate_options: OptionBits,
dead_key_state: *mut u32,
max_string_length: UniCharCount,
actual_string_length: *mut UniCharCount,
unicode_string: *mut UniChar,
) -> OSStatus;
pub fn LMGetKbdType() -> u8;
}
3 changes: 3 additions & 0 deletions crates/livesplit-hotkey/src/macos/cf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod opaque {
pub enum RunLoop {}
pub enum RunLoopSource {}
pub enum String {}
pub enum Data {}
}

pub type AllocatorRef = *mut opaque::Allocator;
Expand All @@ -15,6 +16,7 @@ pub type RunLoopSourceRef = *mut opaque::RunLoopSource;

pub type StringRef = *const opaque::String;
pub type TypeRef = *const c_void;
pub type DataRef = *const opaque::Data;

pub type RunLoopMode = StringRef;

Expand Down Expand Up @@ -62,4 +64,5 @@ extern "C" {

pub fn CFRelease(cf: TypeRef);

pub fn CFDataGetBytePtr(the_data: DataRef) -> *const u8;
}
118 changes: 113 additions & 5 deletions crates/livesplit-hotkey/src/macos/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
mod carbon;
mod cf;
mod cg;

use self::{
carbon::{
kTISPropertyUnicodeKeyLayoutData, LMGetKbdType, TISCopyCurrentKeyboardInputSource,
TISCopyCurrentKeyboardLayoutInputSource, TISGetInputSourceProperty, UCKeyAction,
UCKeyTranslate, UCKeyTranslateBits,
},
cf::{
kCFAllocatorDefault, kCFRunLoopDefaultMode, CFMachPortCreateRunLoopSource, CFRelease,
CFRunLoopAddSource, CFRunLoopContainsSource, CFRunLoopGetCurrent, CFRunLoopRemoveSource,
CFRunLoopRun,
kCFAllocatorDefault, kCFRunLoopDefaultMode, CFDataGetBytePtr,
CFMachPortCreateRunLoopSource, CFRelease, CFRunLoopAddSource, CFRunLoopContainsSource,
CFRunLoopGetCurrent, CFRunLoopRemoveSource, CFRunLoopRun,
},
cg::{
CGEventTapCreate, EventMask, EventRef, EventTapLocation, EventTapOptions,
Expand Down Expand Up @@ -293,6 +299,108 @@ unsafe extern "C" fn callback(
event
}

pub(crate) fn try_resolve(_key_code: KeyCode) -> Option<String> {
None
pub(crate) fn try_resolve(key_code: KeyCode) -> Option<String> {
unsafe {
let current_keyboard_raw = TISCopyCurrentKeyboardInputSource();
if current_keyboard_raw.is_null() {
return None;
}
let mut current_keyboard = Owned(current_keyboard_raw);

let mut layout_data =
TISGetInputSourceProperty(current_keyboard.0, kTISPropertyUnicodeKeyLayoutData);

if layout_data.is_null() {
let current_keyboard_raw = TISCopyCurrentKeyboardLayoutInputSource();
if current_keyboard_raw.is_null() {
return None;
}
current_keyboard = Owned(current_keyboard_raw);

layout_data =
TISGetInputSourceProperty(current_keyboard.0, kTISPropertyUnicodeKeyLayoutData);
if layout_data.is_null() {
return None;
}
}

let keyboard_layout = CFDataGetBytePtr(layout_data.cast());

let key_code = match key_code {
KeyCode::Backquote => 0x32,
KeyCode::Backslash => 0x2A,
KeyCode::Backspace => 0x33,
KeyCode::BracketLeft => 0x21,
KeyCode::BracketRight => 0x1E,
KeyCode::Comma => 0x2B,
KeyCode::Digit0 => 0x1D,
KeyCode::Digit1 => 0x12,
KeyCode::Digit2 => 0x13,
KeyCode::Digit3 => 0x14,
KeyCode::Digit4 => 0x15,
KeyCode::Digit5 => 0x17,
KeyCode::Digit6 => 0x16,
KeyCode::Digit7 => 0x1A,
KeyCode::Digit8 => 0x1C,
KeyCode::Digit9 => 0x19,
KeyCode::Equal => 0x18,
KeyCode::IntlBackslash => 0x0A,
KeyCode::IntlRo => 0x5E,
KeyCode::IntlYen => 0x5D,
KeyCode::KeyA => 0x00,
KeyCode::KeyB => 0x0B,
KeyCode::KeyC => 0x08,
KeyCode::KeyD => 0x02,
KeyCode::KeyE => 0x0E,
KeyCode::KeyF => 0x03,
KeyCode::KeyG => 0x05,
KeyCode::KeyH => 0x04,
KeyCode::KeyI => 0x22,
KeyCode::KeyJ => 0x26,
KeyCode::KeyK => 0x28,
KeyCode::KeyL => 0x25,
KeyCode::KeyM => 0x2E,
KeyCode::KeyN => 0x2D,
KeyCode::KeyO => 0x1F,
KeyCode::KeyP => 0x23,
KeyCode::KeyQ => 0x0C,
KeyCode::KeyR => 0x0F,
KeyCode::KeyS => 0x01,
KeyCode::KeyT => 0x11,
KeyCode::KeyU => 0x20,
KeyCode::KeyV => 0x09,
KeyCode::KeyW => 0x0D,
KeyCode::KeyX => 0x07,
KeyCode::KeyY => 0x10,
KeyCode::KeyZ => 0x06,
KeyCode::Minus => 0x1B,
KeyCode::Period => 0x2F,
KeyCode::Quote => 0x27,
KeyCode::Semicolon => 0x29,
KeyCode::Slash => 0x2C,
_ => return None,
};

let mut chars = [0; 4];
let mut len = 0;

UCKeyTranslate(
keyboard_layout.cast(),
key_code,
UCKeyAction::Display as _,
0,
LMGetKbdType() as _,
UCKeyTranslateBits::NO_DEAD_KEYS_BIT.bits(),
&mut 0,
4,
&mut len,
chars.as_mut_ptr(),
);

if len == 0 {
return None;
}

String::from_utf16(&chars[..len as usize]).ok()
}
}

0 comments on commit 70a0d3c

Please sign in to comment.