Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Signed-off-by: Hal Gentz <zegentzy@protonmail.com>
  • Loading branch information
goddessfreya committed Apr 25, 2019
1 parent dd9de5a commit 02d1aae
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 50 deletions.
101 changes: 86 additions & 15 deletions src/platform_impl/macos/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,67 @@ use event::{
};
use platform_impl::platform::DEVICE_ID;

pub fn to_virtual_keycode(scancode: c_ushort) -> Option<VirtualKeyCode> {
pub fn char_to_keycode(c: char) -> Option<VirtualKeyCode> {
// We only translate keys that are affected by keyboard layout.
//
// Note that since keys are translated in a somewhat "dumb" way (reading character)
// there is a concern that some combination, i.e. Cmd+char, causes the wrong
// letter to be received, and so we receive the wrong key.
//
// Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626
Some(match c {
'a' | 'A' => VirtualKeyCode::A,
'b' | 'B' => VirtualKeyCode::B,
'c' | 'C' => VirtualKeyCode::C,
'd' | 'D' => VirtualKeyCode::D,
'e' | 'E' => VirtualKeyCode::E,
'f' | 'F' => VirtualKeyCode::F,
'g' | 'G' => VirtualKeyCode::G,
'h' | 'H' => VirtualKeyCode::H,
'i' | 'I' => VirtualKeyCode::I,
'j' | 'J' => VirtualKeyCode::J,
'k' | 'K' => VirtualKeyCode::K,
'l' | 'L' => VirtualKeyCode::L,
'm' | 'M' => VirtualKeyCode::M,
'n' | 'N' => VirtualKeyCode::N,
'o' | 'O' => VirtualKeyCode::O,
'p' | 'P' => VirtualKeyCode::P,
'q' | 'Q' => VirtualKeyCode::Q,
'r' | 'R' => VirtualKeyCode::R,
's' | 'S' => VirtualKeyCode::S,
't' | 'T' => VirtualKeyCode::T,
'u' | 'U' => VirtualKeyCode::U,
'v' | 'V' => VirtualKeyCode::V,
'w' | 'W' => VirtualKeyCode::W,
'x' | 'X' => VirtualKeyCode::X,
'y' | 'Y' => VirtualKeyCode::Y,
'z' | 'Z' => VirtualKeyCode::Z,
'1' | '!' => VirtualKeyCode::Key1,
'2' | '@' => VirtualKeyCode::Key2,
'3' | '#' => VirtualKeyCode::Key3,
'4' | '$' => VirtualKeyCode::Key4,
'5' | '%' => VirtualKeyCode::Key5,
'6' | '^' => VirtualKeyCode::Key6,
'7' | '&' => VirtualKeyCode::Key7,
'8' | '*' => VirtualKeyCode::Key8,
'9' | '(' => VirtualKeyCode::Key9,
'0' | ')' => VirtualKeyCode::Key0,
'=' | '+' => VirtualKeyCode::Equals,
'-' | '_' => VirtualKeyCode::Minus,
']' | '}' => VirtualKeyCode::RBracket,
'[' | '{' => VirtualKeyCode::LBracket,
'\''| '"' => VirtualKeyCode::Apostrophe,
';' | ':' => VirtualKeyCode::Semicolon,
'\\'| '|' => VirtualKeyCode::Backslash,
',' | '<' => VirtualKeyCode::Comma,
'/' | '?' => VirtualKeyCode::Slash,
'.' | '>' => VirtualKeyCode::Period,
'`' | '~' => VirtualKeyCode::Grave,
_ => return None,
})
}

pub fn scancode_to_keycode(scancode: c_ushort) -> Option<VirtualKeyCode> {
Some(match scancode {
0x00 => VirtualKeyCode::A,
0x01 => VirtualKeyCode::S,
Expand Down Expand Up @@ -147,17 +207,18 @@ pub fn to_virtual_keycode(scancode: c_ushort) -> Option<VirtualKeyCode> {
// While F1-F20 have scancodes we can match on, we have to check against UTF-16
// constants for the rest.
// https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ
pub fn check_function_keys(string: &Option<String>) -> Option<VirtualKeyCode> {
string
.as_ref()
.and_then(|string| string.encode_utf16().next())
.and_then(|character| match character {
0xf718 => Some(VirtualKeyCode::F21),
0xf719 => Some(VirtualKeyCode::F22),
0xf71a => Some(VirtualKeyCode::F23),
0xf71b => Some(VirtualKeyCode::F24),
_ => None,
pub fn check_function_keys(string: &String) -> Option<VirtualKeyCode> {
if let Some(ch) = string.encode_utf16().next() {
return Some(match ch {
0xf718 => VirtualKeyCode::F21,
0xf719 => VirtualKeyCode::F22,
0xf71a => VirtualKeyCode::F23,
0xf71b => VirtualKeyCode::F24,
_ => return None,
})
}

None
}

pub fn event_mods(event: id) -> ModifiersState {
Expand All @@ -172,6 +233,16 @@ pub fn event_mods(event: id) -> ModifiersState {
}
}

pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character,
// and there is no easy way to navtively retrieve the layout-dependent character.
// In winit, we use keycode to refer to the key's character, and so this function aligns
// AppKit's terminology with ours.
unsafe {
msg_send![event, keyCode]
}
}

pub unsafe fn modifier_event(
ns_event: id,
keymask: NSEventModifierFlags,
Expand All @@ -184,14 +255,14 @@ pub unsafe fn modifier_event(
} else {
ElementState::Pressed
};
let keycode = NSEvent::keyCode(ns_event);
let scancode = keycode as u32;
let virtual_keycode = to_virtual_keycode(keycode);

let scancode = get_scancode(ns_event);
let virtual_keycode = scancode_to_keycode(scancode);
Some(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state,
scancode,
scancode: scancode as _,
virtual_keycode,
modifiers: event_mods(ns_event),
},
Expand Down
85 changes: 50 additions & 35 deletions src/platform_impl/macos/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use {
};
use platform_impl::platform::{
app_state::AppState, DEVICE_ID,
event::{check_function_keys, event_mods, modifier_event, to_virtual_keycode},
event::{check_function_keys, event_mods, modifier_event, char_to_keycode, get_scancode, scancode_to_keycode},
util::{self, IdRef}, ffi::*, window::get_window_id,
};

Expand Down Expand Up @@ -504,16 +504,53 @@ extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) {
trace!("Completed `doCommandBySelector`");
}

fn get_characters(event: id) -> Option<String> {
fn get_characters(event: id, ignore_modifiers: bool) -> String {
unsafe {
let characters: id = msg_send![event, characters];
let characters: id = if ignore_modifiers {
msg_send![event, charactersIgnoringModifiers]
} else {
msg_send![event, characters]
};

assert_ne!(characters, nil);
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
);

let string = str::from_utf8_unchecked(slice);
Some(string.to_owned())
string.to_owned()
}
}

// Retrieves a layout-independent keycode given an event.
fn retrieve_keycode(event: id) -> Option<VirtualKeyCode> {
#[inline]
fn get_code(ev: id, raw: bool) -> Option<VirtualKeyCode> {
let characters = get_characters(ev, raw);
characters.chars().next().map_or(None, |c| char_to_keycode(c))
}

// Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first.
// If we don't get a match, then we fall back to unmodified characters.
let code = get_code(event, false)
.or_else(|| {
get_code(event, true)
});

// We've checked all layout related keys, so fall through to scancode.
// Reaching this code means that the key is layout-independent (e.g. Backspace, Return).
//
// We're additionally checking here for F21-F24 keys, since their keycode
// can vary, but we know that they are encoded
// in characters property.
code.or_else(|| {
let scancode = get_scancode(event);
scancode_to_keycode(scancode)
.or_else(|| {
check_function_keys(&get_characters(event, true))
})
})
}

extern fn key_down(this: &Object, _sel: Sel, event: id) {
Expand All @@ -522,17 +559,13 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) {
let state_ptr: *mut c_void = *this.get_ivar("winitState");
let state = &mut *(state_ptr as *mut ViewState);
let window_id = WindowId(get_window_id(state.nswindow));
let characters = get_characters(event, false);

state.raw_characters = get_characters(event);
state.raw_characters = Some(characters.clone());

let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);

let keycode: c_ushort = msg_send![event, keyCode];
// We are checking here for F21-F24 keys, since their keycode
// can vary, but we know that they are encoded
// in characters property.
let virtual_keycode = to_virtual_keycode(keycode).or_else(|| {
check_function_keys(&state.raw_characters)
});
let scancode = keycode as u32;
let is_repeat = msg_send![event, isARepeat];

let window_event = Event::WindowEvent {
Expand All @@ -548,22 +581,11 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) {
},
};

let characters: id = msg_send![event, characters];
let slice = slice::from_raw_parts(
characters.UTF8String() as *const c_uchar,
characters.len(),
);
let string = str::from_utf8_unchecked(slice);

state.raw_characters = {
Some(string.to_owned())
};

let pass_along = {
AppState::queue_event(window_event);
// Emit `ReceivedCharacter` for key repeats
if is_repeat && state.is_key_down {
for character in string.chars() {
for character in characters.chars() {
AppState::queue_event(Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(character),
Expand Down Expand Up @@ -594,16 +616,9 @@ extern fn key_up(this: &Object, _sel: Sel, event: id) {

state.is_key_down = false;

// We need characters here to check for additional keys such as
// F21-F24.
let characters = get_characters(event);
let scancode = get_scancode(event) as u32;
let virtual_keycode = retrieve_keycode(event);

let keycode: c_ushort = msg_send![event, keyCode];
let virtual_keycode = to_virtual_keycode(keycode)
.or_else(|| {
check_function_keys(&characters)
});
let scancode = keycode as u32;
let window_event = Event::WindowEvent {
window_id: WindowId(get_window_id(state.nswindow)),
event: WindowEvent::KeyboardInput {
Expand Down Expand Up @@ -707,7 +722,7 @@ extern fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
let state = &mut *(state_ptr as *mut ViewState);

let scancode = 0x2f;
let virtual_keycode = to_virtual_keycode(scancode);
let virtual_keycode = scancode_to_keycode(scancode);
debug_assert_eq!(virtual_keycode, Some(VirtualKeyCode::Period));

let event: id = msg_send![NSApp(), currentEvent];
Expand Down

0 comments on commit 02d1aae

Please sign in to comment.