1
0
Fork 0

Add support for macOS and X11 key events with code from druid

This commit is contained in:
Joakim Frostegård 2020-11-13 20:41:17 +01:00
parent e569015c31
commit 9748e16ebe
11 changed files with 1124 additions and 37 deletions

View file

@ -15,6 +15,7 @@ license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
log = "0.4.11" log = "0.4.11"
keyboard-types = "0.5.0"
raw-window-handle = "0.3.3" raw-window-handle = "0.3.3"
[target.'cfg(target_os="linux")'.dependencies] [target.'cfg(target_os="linux")'.dependencies]

View file

@ -1,12 +1,12 @@
use crate::{WindowInfo, Point}; use crate::{WindowInfo, Point, KeyEvent};
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum KeyboardEvent { pub enum KeyboardEvent {
KeyPressed(u32), KeyPressed(KeyEvent),
KeyReleased(u32), KeyReleased(KeyEvent),
CharacterInput(char),
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum MouseButton { pub enum MouseButton {
Left, Left,

View file

@ -1,26 +1,260 @@
// TODO: Add a method to the Window that returns the // Copyright 2020 The Druid Authors.
// current modifier state. //
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// The current state of the keyboard modifiers. // Baseview modifications to druid code:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] // - move code_to_location function to this file
pub struct ModifiersState {
pub shift: bool, //! Keyboard types.
pub control: bool,
pub alt: bool, // This is a reasonable lint, but we keep signatures in sync with the
pub logo: bool, // bitflags implementation of the inner Modifiers type.
#![allow(clippy::trivially_copy_pass_by_ref)]
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
pub use keyboard_types::{Code, KeyState, Location};
/// The meaning (mapped value) of a keypress.
pub type KbKey = keyboard_types::Key;
/// Information about a keyboard event.
///
/// Note that this type is similar to [`KeyboardEvent`] in keyboard-types,
/// but has a few small differences for convenience. It is missing the `state`
/// field because that is already implicit in the event.
///
/// [`KeyboardEvent`]: keyboard_types::KeyboardEvent
#[non_exhaustive]
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub struct KeyEvent {
/// Whether the key is pressed or released.
pub state: KeyState,
/// Logical key value.
pub key: KbKey,
/// Physical key position.
pub code: Code,
/// Location for keys with multiple instances on common keyboards.
pub location: Location,
/// Flags for pressed modifier keys.
pub mods: Modifiers,
/// True if the key is currently auto-repeated.
pub repeat: bool,
/// Events with this flag should be ignored in a text editor
/// and instead composition events should be used.
pub is_composing: bool,
} }
impl ModifiersState { /// The modifiers.
/// Returns true if the current [`ModifiersState`] has at least the same ///
/// modifiers enabled as the given value, and false otherwise. /// This type is a thin wrappers around [`keyboard_types::Modifiers`],
/// /// mostly for the convenience methods. If those get upstreamed, it
/// [`ModifiersState`]: struct.ModifiersState.html /// will simply become that type.
pub fn matches_atleast(&self, modifiers: ModifiersState) -> bool { ///
let shift = !modifiers.shift || self.shift; /// [`keyboard_types::Modifiers`]: keyboard_types::Modifiers
let control = !modifiers.control || self.control; #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
let alt = !modifiers.alt || self.alt; pub struct Modifiers(keyboard_types::Modifiers);
let logo = !modifiers.logo || self.logo;
shift && control && alt && logo /// A convenience trait for creating Key objects.
///
/// This trait is implemented by [`KbKey`] itself and also strings, which are
/// converted into the `Character` variant. It is defined this way and not
/// using the standard `Into` mechanism because `KbKey` is a type in an external
/// crate.
///
/// [`KbKey`]: KbKey
pub trait IntoKey {
fn into_key(self) -> KbKey;
}
impl KeyEvent {
#[doc(hidden)]
/// Create a key event for testing purposes.
pub fn for_test(mods: impl Into<Modifiers>, key: impl IntoKey) -> KeyEvent {
let mods = mods.into();
let key = key.into_key();
KeyEvent {
key,
code: Code::Unidentified,
location: Location::Standard,
state: KeyState::Down,
mods,
is_composing: false,
repeat: false,
}
} }
} }
impl Modifiers {
pub const ALT: Modifiers = Modifiers(keyboard_types::Modifiers::ALT);
pub const ALT_GRAPH: Modifiers = Modifiers(keyboard_types::Modifiers::ALT_GRAPH);
pub const CAPS_LOCK: Modifiers = Modifiers(keyboard_types::Modifiers::CAPS_LOCK);
pub const CONTROL: Modifiers = Modifiers(keyboard_types::Modifiers::CONTROL);
pub const FN: Modifiers = Modifiers(keyboard_types::Modifiers::FN);
pub const FN_LOCK: Modifiers = Modifiers(keyboard_types::Modifiers::FN_LOCK);
pub const META: Modifiers = Modifiers(keyboard_types::Modifiers::META);
pub const NUM_LOCK: Modifiers = Modifiers(keyboard_types::Modifiers::NUM_LOCK);
pub const SCROLL_LOCK: Modifiers = Modifiers(keyboard_types::Modifiers::SCROLL_LOCK);
pub const SHIFT: Modifiers = Modifiers(keyboard_types::Modifiers::SHIFT);
pub const SYMBOL: Modifiers = Modifiers(keyboard_types::Modifiers::SYMBOL);
pub const SYMBOL_LOCK: Modifiers = Modifiers(keyboard_types::Modifiers::SYMBOL_LOCK);
pub const HYPER: Modifiers = Modifiers(keyboard_types::Modifiers::HYPER);
pub const SUPER: Modifiers = Modifiers(keyboard_types::Modifiers::SUPER);
/// Get the inner value.
///
/// Note that this function might go away if our changes are upstreamed.
pub fn raw(&self) -> keyboard_types::Modifiers {
self.0
}
/// Determine whether Shift is set.
pub fn shift(&self) -> bool {
self.contains(Modifiers::SHIFT)
}
/// Determine whether Ctrl is set.
pub fn ctrl(&self) -> bool {
self.contains(Modifiers::CONTROL)
}
/// Determine whether Alt is set.
pub fn alt(&self) -> bool {
self.contains(Modifiers::ALT)
}
/// Determine whether Meta is set.
pub fn meta(&self) -> bool {
self.contains(Modifiers::META)
}
/// Returns an empty set of modifiers.
pub fn empty() -> Modifiers {
Default::default()
}
/// Returns `true` if no modifiers are set.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Returns `true` if all the modifiers in `other` are set.
pub fn contains(&self, other: Modifiers) -> bool {
self.0.contains(other.0)
}
/// Inserts or removes the specified modifiers depending on the passed value.
pub fn set(&mut self, other: Modifiers, value: bool) {
self.0.set(other.0, value)
}
}
impl BitAnd for Modifiers {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Modifiers(self.0 & rhs.0)
}
}
impl BitAndAssign for Modifiers {
// rhs is the "right-hand side" of the expression `a &= b`
fn bitand_assign(&mut self, rhs: Self) {
*self = Modifiers(self.0 & rhs.0)
}
}
impl BitOr for Modifiers {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Modifiers(self.0 | rhs.0)
}
}
impl BitOrAssign for Modifiers {
// rhs is the "right-hand side" of the expression `a &= b`
fn bitor_assign(&mut self, rhs: Self) {
*self = Modifiers(self.0 | rhs.0)
}
}
impl BitXor for Modifiers {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self {
Modifiers(self.0 ^ rhs.0)
}
}
impl BitXorAssign for Modifiers {
// rhs is the "right-hand side" of the expression `a &= b`
fn bitxor_assign(&mut self, rhs: Self) {
*self = Modifiers(self.0 ^ rhs.0)
}
}
impl Not for Modifiers {
type Output = Self;
fn not(self) -> Self {
Modifiers(!self.0)
}
}
impl IntoKey for KbKey {
fn into_key(self) -> KbKey {
self
}
}
impl IntoKey for &str {
fn into_key(self) -> KbKey {
KbKey::Character(self.into())
}
}
#[cfg(any(all(feature = "x11", target_os = "linux"), target_os = "macos"))]
/// Map key code to location.
///
/// The logic for this is adapted from InitKeyEvent in TextInputHandler (in the Mozilla
/// mac port).
///
/// Note: in the original, this is based on kVK constants, but since we don't have those
/// readily available, we use the mapping to code (which should be effectively lossless).
pub fn code_to_location(code: Code) -> Location {
match code {
Code::MetaLeft | Code::ShiftLeft | Code::AltLeft | Code::ControlLeft => Location::Left,
Code::MetaRight | Code::ShiftRight | Code::AltRight | Code::ControlRight => Location::Right,
Code::Numpad0
| Code::Numpad1
| Code::Numpad2
| Code::Numpad3
| Code::Numpad4
| Code::Numpad5
| Code::Numpad6
| Code::Numpad7
| Code::Numpad8
| Code::Numpad9
| Code::NumpadAdd
| Code::NumpadComma
| Code::NumpadDecimal
| Code::NumpadDivide
| Code::NumpadEnter
| Code::NumpadEqual
| Code::NumpadMultiply
| Code::NumpadSubtract => Location::Numpad,
_ => Location::Standard,
}
}

View file

@ -21,7 +21,7 @@ mod mouse_cursor;
mod window_info; mod window_info;
mod window_open_options; mod window_open_options;
pub use event::*; pub use event::*;
pub use keyboard::*; pub use keyboard::KeyEvent;
pub use mouse_cursor::MouseCursor; pub use mouse_cursor::MouseCursor;
pub use window_info::*; pub use window_info::*;
pub use window_open_options::*; pub use window_open_options::*;

367
src/macos/keyboard.rs Normal file
View file

@ -0,0 +1,367 @@
// Copyright 2020 The Druid Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Baseview modifications to druid code:
// - move from_nsstring function to this file
// - update imports, paths etc
//! Conversion of platform keyboard event into cross-platform event.
use cocoa::appkit::{NSEvent, NSEventModifierFlags, NSEventType};
use cocoa::foundation::NSString;
use cocoa::base::id;
use keyboard_types::{Code, KeyState};
use objc::{msg_send, sel, sel_impl};
use crate::keyboard::{code_to_location, KbKey, KeyEvent, Modifiers};
pub(crate) fn from_nsstring(s: id) -> String {
unsafe {
let slice = std::slice::from_raw_parts(s.UTF8String() as *const _, s.len());
let result = std::str::from_utf8_unchecked(slice);
result.into()
}
}
/// State for processing of keyboard events.
///
/// This needs to be stateful for proper processing of dead keys. The current
/// implementation is somewhat primitive and is not based on IME; in the future
/// when IME is implemented, it will need to be redone somewhat, letting the IME
/// be the authoritative source of truth for Unicode string values of keys.
///
/// Most of the logic in this module is adapted from Mozilla, and in particular
/// TextInputHandler.mm.
pub(crate) struct KeyboardState {
last_mods: NSEventModifierFlags,
}
/// Convert a macOS platform key code (keyCode field of NSEvent).
///
/// The primary source for this mapping is:
/// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values
///
/// It should also match up with CODE_MAP_MAC bindings in
/// NativeKeyToDOMCodeName.h.
fn key_code_to_code(key_code: u16) -> Code {
match key_code {
0x00 => Code::KeyA,
0x01 => Code::KeyS,
0x02 => Code::KeyD,
0x03 => Code::KeyF,
0x04 => Code::KeyH,
0x05 => Code::KeyG,
0x06 => Code::KeyZ,
0x07 => Code::KeyX,
0x08 => Code::KeyC,
0x09 => Code::KeyV,
0x0a => Code::IntlBackslash,
0x0b => Code::KeyB,
0x0c => Code::KeyQ,
0x0d => Code::KeyW,
0x0e => Code::KeyE,
0x0f => Code::KeyR,
0x10 => Code::KeyY,
0x11 => Code::KeyT,
0x12 => Code::Digit1,
0x13 => Code::Digit2,
0x14 => Code::Digit3,
0x15 => Code::Digit4,
0x16 => Code::Digit6,
0x17 => Code::Digit5,
0x18 => Code::Equal,
0x19 => Code::Digit9,
0x1a => Code::Digit7,
0x1b => Code::Minus,
0x1c => Code::Digit8,
0x1d => Code::Digit0,
0x1e => Code::BracketRight,
0x1f => Code::KeyO,
0x20 => Code::KeyU,
0x21 => Code::BracketLeft,
0x22 => Code::KeyI,
0x23 => Code::KeyP,
0x24 => Code::Enter,
0x25 => Code::KeyL,
0x26 => Code::KeyJ,
0x27 => Code::Quote,
0x28 => Code::KeyK,
0x29 => Code::Semicolon,
0x2a => Code::Backslash,
0x2b => Code::Comma,
0x2c => Code::Slash,
0x2d => Code::KeyN,
0x2e => Code::KeyM,
0x2f => Code::Period,
0x30 => Code::Tab,
0x31 => Code::Space,
0x32 => Code::Backquote,
0x33 => Code::Backspace,
0x34 => Code::NumpadEnter,
0x35 => Code::Escape,
0x36 => Code::MetaRight,
0x37 => Code::MetaLeft,
0x38 => Code::ShiftLeft,
0x39 => Code::CapsLock,
// Note: in the linked source doc, this is "OSLeft"
0x3a => Code::AltLeft,
0x3b => Code::ControlLeft,
0x3c => Code::ShiftRight,
// Note: in the linked source doc, this is "OSRight"
0x3d => Code::AltRight,
0x3e => Code::ControlRight,
0x3f => Code::Fn, // No events fired
//0x40 => Code::F17,
0x41 => Code::NumpadDecimal,
0x43 => Code::NumpadMultiply,
0x45 => Code::NumpadAdd,
0x47 => Code::NumLock,
0x48 => Code::AudioVolumeUp,
0x49 => Code::AudioVolumeDown,
0x4a => Code::AudioVolumeMute,
0x4b => Code::NumpadDivide,
0x4c => Code::NumpadEnter,
0x4e => Code::NumpadSubtract,
//0x4f => Code::F18,
//0x50 => Code::F19,
0x51 => Code::NumpadEqual,
0x52 => Code::Numpad0,
0x53 => Code::Numpad1,
0x54 => Code::Numpad2,
0x55 => Code::Numpad3,
0x56 => Code::Numpad4,
0x57 => Code::Numpad5,
0x58 => Code::Numpad6,
0x59 => Code::Numpad7,
//0x5a => Code::F20,
0x5b => Code::Numpad8,
0x5c => Code::Numpad9,
0x5d => Code::IntlYen,
0x5e => Code::IntlRo,
0x5f => Code::NumpadComma,
0x60 => Code::F5,
0x61 => Code::F6,
0x62 => Code::F7,
0x63 => Code::F3,
0x64 => Code::F8,
0x65 => Code::F9,
0x66 => Code::Lang2,
0x67 => Code::F11,
0x68 => Code::Lang1,
// Note: this is listed as F13, but in testing with a standard
// USB kb, this the code produced by PrtSc.
0x69 => Code::PrintScreen,
//0x6a => Code::F16,
//0x6b => Code::F14,
0x6d => Code::F10,
0x6e => Code::ContextMenu,
0x6f => Code::F12,
//0x71 => Code::F15,
0x72 => Code::Help,
0x73 => Code::Home,
0x74 => Code::PageUp,
0x75 => Code::Delete,
0x76 => Code::F4,
0x77 => Code::End,
0x78 => Code::F2,
0x79 => Code::PageDown,
0x7a => Code::F1,
0x7b => Code::ArrowLeft,
0x7c => Code::ArrowRight,
0x7d => Code::ArrowDown,
0x7e => Code::ArrowUp,
_ => Code::Unidentified,
}
}
/// Convert code to key.
///
/// On macOS, for non-printable keys, the keyCode we get from the event serves is
/// really more of a key than a physical scan code.
///
/// When this function returns None, the code can be considered printable.
///
/// The logic for this function is derived from KEY_MAP_COCOA bindings in
/// NativeKeyToDOMKeyName.h.
fn code_to_key(code: Code) -> Option<KbKey> {
Some(match code {
Code::Escape => KbKey::Escape,
Code::ShiftLeft | Code::ShiftRight => KbKey::Shift,
Code::AltLeft | Code::AltRight => KbKey::Alt,
Code::MetaLeft | Code::MetaRight => KbKey::Meta,
Code::ControlLeft | Code::ControlRight => KbKey::Control,
Code::CapsLock => KbKey::CapsLock,
// kVK_ANSI_KeypadClear
Code::NumLock => KbKey::Clear,
Code::Fn => KbKey::Fn,
Code::F1 => KbKey::F1,
Code::F2 => KbKey::F2,
Code::F3 => KbKey::F3,
Code::F4 => KbKey::F4,
Code::F5 => KbKey::F5,
Code::F6 => KbKey::F6,
Code::F7 => KbKey::F7,
Code::F8 => KbKey::F8,
Code::F9 => KbKey::F9,
Code::F10 => KbKey::F10,
Code::F11 => KbKey::F11,
Code::F12 => KbKey::F12,
Code::Pause => KbKey::Pause,
Code::ScrollLock => KbKey::ScrollLock,
Code::PrintScreen => KbKey::PrintScreen,
Code::Insert => KbKey::Insert,
Code::Delete => KbKey::Delete,
Code::Tab => KbKey::Tab,
Code::Backspace => KbKey::Backspace,
Code::ContextMenu => KbKey::ContextMenu,
// kVK_JIS_Kana
Code::Lang1 => KbKey::KanjiMode,
// kVK_JIS_Eisu
Code::Lang2 => KbKey::Eisu,
Code::Home => KbKey::Home,
Code::End => KbKey::End,
Code::PageUp => KbKey::PageUp,
Code::PageDown => KbKey::PageDown,
Code::ArrowLeft => KbKey::ArrowLeft,
Code::ArrowRight => KbKey::ArrowRight,
Code::ArrowUp => KbKey::ArrowUp,
Code::ArrowDown => KbKey::ArrowDown,
Code::Enter => KbKey::Enter,
Code::NumpadEnter => KbKey::Enter,
Code::Help => KbKey::Help,
_ => return None,
})
}
fn is_valid_key(s: &str) -> bool {
match s.chars().next() {
None => false,
Some(c) => c >= ' ' && c != '\x7f' && !('\u{e000}'..'\u{f900}').contains(&c),
}
}
fn is_modifier_code(code: Code) -> bool {
matches!(
code,
Code::ShiftLeft
| Code::ShiftRight
| Code::AltLeft
| Code::AltRight
| Code::ControlLeft
| Code::ControlRight
| Code::MetaLeft
| Code::MetaRight
| Code::CapsLock
| Code::Help
)
}
impl KeyboardState {
pub(crate) fn new() -> KeyboardState {
let last_mods = NSEventModifierFlags::empty();
KeyboardState { last_mods }
}
pub(crate) fn process_native_event(&mut self, event: id) -> Option<KeyEvent> {
unsafe {
let event_type = event.eventType();
let key_code = event.keyCode();
let code = key_code_to_code(key_code);
let location = code_to_location(code);
let raw_mods = event.modifierFlags();
let mods = make_modifiers(raw_mods);
let state = match event_type {
NSEventType::NSKeyDown => KeyState::Down,
NSEventType::NSKeyUp => KeyState::Up,
NSEventType::NSFlagsChanged => {
// We use `bits` here because we want to distinguish the
// device dependent bits (when both left and right keys
// may be pressed, for example).
let any_down = raw_mods.bits() & !self.last_mods.bits();
self.last_mods = raw_mods;
if is_modifier_code(code) {
if any_down == 0 {
KeyState::Up
} else {
KeyState::Down
}
} else {
// HandleFlagsChanged has some logic for this; it might
// happen when an app is deactivated by Command-Tab. In
// that case, the best thing to do is synthesize the event
// from the modifiers. But a challenge there is that we
// might get multiple events.
return None;
}
}
_ => unreachable!(),
};
let is_composing = false;
let repeat: bool = event_type == NSEventType::NSKeyDown && msg_send![event, isARepeat];
let key = if let Some(key) = code_to_key(code) {
key
} else {
let characters = from_nsstring(event.characters());
if is_valid_key(&characters) {
KbKey::Character(characters)
} else {
let chars_ignoring = from_nsstring(event.charactersIgnoringModifiers());
if is_valid_key(&chars_ignoring) {
KbKey::Character(chars_ignoring)
} else {
// There may be more heroic things we can do here.
KbKey::Unidentified
}
}
};
let event = KeyEvent {
code,
key,
location,
mods,
state,
is_composing,
repeat,
};
Some(event)
}
}
}
const MODIFIER_MAP: &[(NSEventModifierFlags, Modifiers)] = &[
(NSEventModifierFlags::NSShiftKeyMask, Modifiers::SHIFT),
(NSEventModifierFlags::NSAlternateKeyMask, Modifiers::ALT),
(NSEventModifierFlags::NSControlKeyMask, Modifiers::CONTROL),
(NSEventModifierFlags::NSCommandKeyMask, Modifiers::META),
(
NSEventModifierFlags::NSAlphaShiftKeyMask,
Modifiers::CAPS_LOCK,
),
];
pub(crate) fn make_modifiers(raw: NSEventModifierFlags) -> Modifiers {
let mut modifiers = Modifiers::empty();
for &(flags, mods) in MODIFIER_MAP {
if raw.contains(flags) {
modifiers |= mods;
}
}
modifiers
}

View file

@ -1,3 +1,4 @@
mod keyboard;
mod window; mod window;
mod view; mod view;

View file

@ -15,7 +15,7 @@ use objc::{
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
Event, MouseButton, MouseEvent, Point, WindowHandler, Event, KeyboardEvent, MouseButton, MouseEvent, Point, WindowHandler,
WindowOpenOptions WindowOpenOptions
}; };
use crate::MouseEvent::{ButtonPressed, ButtonReleased}; use crate::MouseEvent::{ButtonPressed, ButtonReleased};
@ -134,6 +134,15 @@ unsafe fn create_view_class<H: WindowHandler>() -> &'static Class {
middle_mouse_up::<H> as extern "C" fn(&Object, Sel, id), middle_mouse_up::<H> as extern "C" fn(&Object, Sel, id),
); );
class.add_method(
sel!(keyDown:),
key_down::<H> as extern "C" fn(&Object, Sel, id),
);
class.add_method(
sel!(keyUp:),
key_up::<H> as extern "C" fn(&Object, Sel, id),
);
class.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR_NAME); class.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR_NAME);
class.register() class.register()
@ -333,4 +342,30 @@ mouse_simple_extern_fn!(middle_mouse_down, ButtonPressed(MouseButton::Middle));
mouse_simple_extern_fn!(middle_mouse_up, ButtonReleased(MouseButton::Middle)); mouse_simple_extern_fn!(middle_mouse_up, ButtonReleased(MouseButton::Middle));
mouse_simple_extern_fn!(mouse_entered, MouseEvent::CursorEntered); mouse_simple_extern_fn!(mouse_entered, MouseEvent::CursorEntered);
mouse_simple_extern_fn!(mouse_exited, MouseEvent::CursorLeft); mouse_simple_extern_fn!(mouse_exited, MouseEvent::CursorLeft);
extern "C" fn key_down<H: WindowHandler>(this: &Object, _: Sel, event: id){
let state: &mut WindowState<H> = unsafe {
WindowState::from_field(this)
};
if let Some(key_event) = state.process_native_key_event(event){
let event = Event::Keyboard(KeyboardEvent::KeyPressed(key_event));
state.trigger_event(event);
}
}
extern "C" fn key_up<H: WindowHandler>(this: &Object, _: Sel, event: id){
let state: &mut WindowState<H> = unsafe {
WindowState::from_field(this)
};
if let Some(key_event) = state.process_native_key_event(event){
let event = Event::Keyboard(KeyboardEvent::KeyReleased(key_event));
state.trigger_event(event);
}
}

View file

@ -17,11 +17,12 @@ use objc::{msg_send, runtime::Object, sel, sel_impl};
use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle}; use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle};
use crate::{ use crate::{
Event, Parent, WindowHandler, WindowOpenOptions, WindowScalePolicy, Event, keyboard::KeyEvent, Parent, WindowHandler, WindowOpenOptions,
WindowInfo WindowScalePolicy, WindowInfo
}; };
use super::view::create_view; use super::view::create_view;
use super::keyboard::KeyboardState;
/// Name of the field used to store the `WindowState` pointer in the custom /// Name of the field used to store the `WindowState` pointer in the custom
@ -156,6 +157,7 @@ impl Window {
let window_state_arc = Arc::new(WindowState { let window_state_arc = Arc::new(WindowState {
window, window,
window_handler, window_handler,
keyboard_state: KeyboardState::new(),
}); });
let window_state_pointer = Arc::into_raw( let window_state_pointer = Arc::into_raw(
@ -177,6 +179,7 @@ impl Window {
pub(super) struct WindowState<H: WindowHandler> { pub(super) struct WindowState<H: WindowHandler> {
window: Window, window: Window,
window_handler: H, window_handler: H,
keyboard_state: KeyboardState,
} }
@ -195,6 +198,13 @@ impl <H: WindowHandler>WindowState<H> {
pub(super) fn trigger_event(&mut self, event: Event){ pub(super) fn trigger_event(&mut self, event: Event){
self.window_handler.on_event(&mut self.window, event); self.window_handler.on_event(&mut self.window, event);
} }
pub(super) fn process_native_key_event(
&mut self,
event: *mut Object
) -> Option<KeyEvent> {
self.keyboard_state.process_native_event(event)
}
} }

431
src/x11/keyboard.rs Normal file
View file

@ -0,0 +1,431 @@
// Copyright 2020 The Druid Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Baseview modifications to druid code:
// - collect functions from various files
// - update imports, paths etc
//! X11 keyboard handling
use super::super::shared;
use crate::keyboard::code_to_location;
use crate::keyboard::{Code, KbKey, Modifiers};
use x11rb::protocol::xproto::{Keycode, KeyPressEvent};
use crate::keyboard::KeyEvent;
/// Convert a hardware scan code to a key.
///
/// Note: this is a hardcoded layout. We need to detect the user's
/// layout from the system and apply it.
fn code_to_key(code: Code, m: Modifiers) -> KbKey {
fn a(s: &str) -> KbKey {
KbKey::Character(s.into())
}
fn s(mods: Modifiers, base: &str, shifted: &str) -> KbKey {
if mods.shift() {
KbKey::Character(shifted.into())
} else {
KbKey::Character(base.into())
}
}
fn n(mods: Modifiers, base: KbKey, num: &str) -> KbKey {
if mods.contains(Modifiers::NUM_LOCK) != mods.shift() {
KbKey::Character(num.into())
} else {
base
}
}
match code {
Code::KeyA => s(m, "a", "A"),
Code::KeyB => s(m, "b", "B"),
Code::KeyC => s(m, "c", "C"),
Code::KeyD => s(m, "d", "D"),
Code::KeyE => s(m, "e", "E"),
Code::KeyF => s(m, "f", "F"),
Code::KeyG => s(m, "g", "G"),
Code::KeyH => s(m, "h", "H"),
Code::KeyI => s(m, "i", "I"),
Code::KeyJ => s(m, "j", "J"),
Code::KeyK => s(m, "k", "K"),
Code::KeyL => s(m, "l", "L"),
Code::KeyM => s(m, "m", "M"),
Code::KeyN => s(m, "n", "N"),
Code::KeyO => s(m, "o", "O"),
Code::KeyP => s(m, "p", "P"),
Code::KeyQ => s(m, "q", "Q"),
Code::KeyR => s(m, "r", "R"),
Code::KeyS => s(m, "s", "S"),
Code::KeyT => s(m, "t", "T"),
Code::KeyU => s(m, "u", "U"),
Code::KeyV => s(m, "v", "V"),
Code::KeyW => s(m, "w", "W"),
Code::KeyX => s(m, "x", "X"),
Code::KeyY => s(m, "y", "Y"),
Code::KeyZ => s(m, "z", "Z"),
Code::Digit0 => s(m, "0", ")"),
Code::Digit1 => s(m, "1", "!"),
Code::Digit2 => s(m, "2", "@"),
Code::Digit3 => s(m, "3", "#"),
Code::Digit4 => s(m, "4", "$"),
Code::Digit5 => s(m, "5", "%"),
Code::Digit6 => s(m, "6", "^"),
Code::Digit7 => s(m, "7", "&"),
Code::Digit8 => s(m, "8", "*"),
Code::Digit9 => s(m, "9", "("),
Code::Backquote => s(m, "`", "~"),
Code::Minus => s(m, "-", "_"),
Code::Equal => s(m, "=", "+"),
Code::BracketLeft => s(m, "[", "{"),
Code::BracketRight => s(m, "]", "}"),
Code::Backslash => s(m, "\\", "|"),
Code::Semicolon => s(m, ";", ":"),
Code::Quote => s(m, "'", "\""),
Code::Comma => s(m, ",", "<"),
Code::Period => s(m, ".", ">"),
Code::Slash => s(m, "/", "?"),
Code::Space => a(" "),
Code::Escape => KbKey::Escape,
Code::Backspace => KbKey::Backspace,
Code::Tab => KbKey::Tab,
Code::Enter => KbKey::Enter,
Code::ControlLeft => KbKey::Control,
Code::ShiftLeft => KbKey::Shift,
Code::ShiftRight => KbKey::Shift,
Code::NumpadMultiply => a("*"),
Code::AltLeft => KbKey::Alt,
Code::CapsLock => KbKey::CapsLock,
Code::F1 => KbKey::F1,
Code::F2 => KbKey::F2,
Code::F3 => KbKey::F3,
Code::F4 => KbKey::F4,
Code::F5 => KbKey::F5,
Code::F6 => KbKey::F6,
Code::F7 => KbKey::F7,
Code::F8 => KbKey::F8,
Code::F9 => KbKey::F9,
Code::F10 => KbKey::F10,
Code::NumLock => KbKey::NumLock,
Code::ScrollLock => KbKey::ScrollLock,
Code::Numpad0 => n(m, KbKey::Insert, "0"),
Code::Numpad1 => n(m, KbKey::End, "1"),
Code::Numpad2 => n(m, KbKey::ArrowDown, "2"),
Code::Numpad3 => n(m, KbKey::PageDown, "3"),
Code::Numpad4 => n(m, KbKey::ArrowLeft, "4"),
Code::Numpad5 => n(m, KbKey::Clear, "5"),
Code::Numpad6 => n(m, KbKey::ArrowRight, "6"),
Code::Numpad7 => n(m, KbKey::Home, "7"),
Code::Numpad8 => n(m, KbKey::ArrowUp, "8"),
Code::Numpad9 => n(m, KbKey::PageUp, "9"),
Code::NumpadSubtract => a("-"),
Code::NumpadAdd => a("+"),
Code::NumpadDecimal => n(m, KbKey::Delete, "."),
Code::IntlBackslash => s(m, "\\", "|"),
Code::F11 => KbKey::F11,
Code::F12 => KbKey::F12,
// This mapping is based on the picture in the w3c spec.
Code::IntlRo => a("\\"),
Code::Convert => KbKey::Convert,
Code::KanaMode => KbKey::KanaMode,
Code::NonConvert => KbKey::NonConvert,
Code::NumpadEnter => KbKey::Enter,
Code::ControlRight => KbKey::Control,
Code::NumpadDivide => a("/"),
Code::PrintScreen => KbKey::PrintScreen,
Code::AltRight => KbKey::Alt,
Code::Home => KbKey::Home,
Code::ArrowUp => KbKey::ArrowUp,
Code::PageUp => KbKey::PageUp,
Code::ArrowLeft => KbKey::ArrowLeft,
Code::ArrowRight => KbKey::ArrowRight,
Code::End => KbKey::End,
Code::ArrowDown => KbKey::ArrowDown,
Code::PageDown => KbKey::PageDown,
Code::Insert => KbKey::Insert,
Code::Delete => KbKey::Delete,
Code::AudioVolumeMute => KbKey::AudioVolumeMute,
Code::AudioVolumeDown => KbKey::AudioVolumeDown,
Code::AudioVolumeUp => KbKey::AudioVolumeUp,
Code::NumpadEqual => a("="),
Code::Pause => KbKey::Pause,
Code::NumpadComma => a(","),
Code::Lang1 => KbKey::HangulMode,
Code::Lang2 => KbKey::HanjaMode,
Code::IntlYen => a("¥"),
Code::MetaLeft => KbKey::Meta,
Code::MetaRight => KbKey::Meta,
Code::ContextMenu => KbKey::ContextMenu,
Code::BrowserStop => KbKey::BrowserStop,
Code::Again => KbKey::Again,
Code::Props => KbKey::Props,
Code::Undo => KbKey::Undo,
Code::Select => KbKey::Select,
Code::Copy => KbKey::Copy,
Code::Open => KbKey::Open,
Code::Paste => KbKey::Paste,
Code::Find => KbKey::Find,
Code::Cut => KbKey::Cut,
Code::Help => KbKey::Help,
Code::LaunchApp2 => KbKey::LaunchApplication2,
Code::WakeUp => KbKey::WakeUp,
Code::LaunchApp1 => KbKey::LaunchApplication1,
Code::LaunchMail => KbKey::LaunchMail,
Code::BrowserFavorites => KbKey::BrowserFavorites,
Code::BrowserBack => KbKey::BrowserBack,
Code::BrowserForward => KbKey::BrowserForward,
Code::Eject => KbKey::Eject,
Code::MediaTrackNext => KbKey::MediaTrackNext,
Code::MediaPlayPause => KbKey::MediaPlayPause,
Code::MediaTrackPrevious => KbKey::MediaTrackPrevious,
Code::MediaStop => KbKey::MediaStop,
Code::MediaSelect => KbKey::LaunchMediaPlayer,
Code::BrowserHome => KbKey::BrowserHome,
Code::BrowserRefresh => KbKey::BrowserRefresh,
Code::BrowserSearch => KbKey::BrowserSearch,
_ => KbKey::Unidentified,
}
}
#[cfg(target_os = "linux")]
/// Map hardware keycode to code.
///
/// In theory, the hardware keycode is device dependent, but in
/// practice it's probably pretty reliable.
///
/// The logic is based on NativeKeyToDOMCodeName.h in Mozilla.
fn hardware_keycode_to_code(hw_keycode: u16) -> Code {
match hw_keycode {
0x0009 => Code::Escape,
0x000A => Code::Digit1,
0x000B => Code::Digit2,
0x000C => Code::Digit3,
0x000D => Code::Digit4,
0x000E => Code::Digit5,
0x000F => Code::Digit6,
0x0010 => Code::Digit7,
0x0011 => Code::Digit8,
0x0012 => Code::Digit9,
0x0013 => Code::Digit0,
0x0014 => Code::Minus,
0x0015 => Code::Equal,
0x0016 => Code::Backspace,
0x0017 => Code::Tab,
0x0018 => Code::KeyQ,
0x0019 => Code::KeyW,
0x001A => Code::KeyE,
0x001B => Code::KeyR,
0x001C => Code::KeyT,
0x001D => Code::KeyY,
0x001E => Code::KeyU,
0x001F => Code::KeyI,
0x0020 => Code::KeyO,
0x0021 => Code::KeyP,
0x0022 => Code::BracketLeft,
0x0023 => Code::BracketRight,
0x0024 => Code::Enter,
0x0025 => Code::ControlLeft,
0x0026 => Code::KeyA,
0x0027 => Code::KeyS,
0x0028 => Code::KeyD,
0x0029 => Code::KeyF,
0x002A => Code::KeyG,
0x002B => Code::KeyH,
0x002C => Code::KeyJ,
0x002D => Code::KeyK,
0x002E => Code::KeyL,
0x002F => Code::Semicolon,
0x0030 => Code::Quote,
0x0031 => Code::Backquote,
0x0032 => Code::ShiftLeft,
0x0033 => Code::Backslash,
0x0034 => Code::KeyZ,
0x0035 => Code::KeyX,
0x0036 => Code::KeyC,
0x0037 => Code::KeyV,
0x0038 => Code::KeyB,
0x0039 => Code::KeyN,
0x003A => Code::KeyM,
0x003B => Code::Comma,
0x003C => Code::Period,
0x003D => Code::Slash,
0x003E => Code::ShiftRight,
0x003F => Code::NumpadMultiply,
0x0040 => Code::AltLeft,
0x0041 => Code::Space,
0x0042 => Code::CapsLock,
0x0043 => Code::F1,
0x0044 => Code::F2,
0x0045 => Code::F3,
0x0046 => Code::F4,
0x0047 => Code::F5,
0x0048 => Code::F6,
0x0049 => Code::F7,
0x004A => Code::F8,
0x004B => Code::F9,
0x004C => Code::F10,
0x004D => Code::NumLock,
0x004E => Code::ScrollLock,
0x004F => Code::Numpad7,
0x0050 => Code::Numpad8,
0x0051 => Code::Numpad9,
0x0052 => Code::NumpadSubtract,
0x0053 => Code::Numpad4,
0x0054 => Code::Numpad5,
0x0055 => Code::Numpad6,
0x0056 => Code::NumpadAdd,
0x0057 => Code::Numpad1,
0x0058 => Code::Numpad2,
0x0059 => Code::Numpad3,
0x005A => Code::Numpad0,
0x005B => Code::NumpadDecimal,
0x005E => Code::IntlBackslash,
0x005F => Code::F11,
0x0060 => Code::F12,
0x0061 => Code::IntlRo,
0x0064 => Code::Convert,
0x0065 => Code::KanaMode,
0x0066 => Code::NonConvert,
0x0068 => Code::NumpadEnter,
0x0069 => Code::ControlRight,
0x006A => Code::NumpadDivide,
0x006B => Code::PrintScreen,
0x006C => Code::AltRight,
0x006E => Code::Home,
0x006F => Code::ArrowUp,
0x0070 => Code::PageUp,
0x0071 => Code::ArrowLeft,
0x0072 => Code::ArrowRight,
0x0073 => Code::End,
0x0074 => Code::ArrowDown,
0x0075 => Code::PageDown,
0x0076 => Code::Insert,
0x0077 => Code::Delete,
0x0079 => Code::AudioVolumeMute,
0x007A => Code::AudioVolumeDown,
0x007B => Code::AudioVolumeUp,
0x007D => Code::NumpadEqual,
0x007F => Code::Pause,
0x0081 => Code::NumpadComma,
0x0082 => Code::Lang1,
0x0083 => Code::Lang2,
0x0084 => Code::IntlYen,
0x0085 => Code::MetaLeft,
0x0086 => Code::MetaRight,
0x0087 => Code::ContextMenu,
0x0088 => Code::BrowserStop,
0x0089 => Code::Again,
0x008A => Code::Props,
0x008B => Code::Undo,
0x008C => Code::Select,
0x008D => Code::Copy,
0x008E => Code::Open,
0x008F => Code::Paste,
0x0090 => Code::Find,
0x0091 => Code::Cut,
0x0092 => Code::Help,
0x0094 => Code::LaunchApp2,
0x0097 => Code::WakeUp,
0x0098 => Code::LaunchApp1,
// key to right of volume controls on T430s produces 0x9C
// but no documentation of what it should map to :/
0x00A3 => Code::LaunchMail,
0x00A4 => Code::BrowserFavorites,
0x00A6 => Code::BrowserBack,
0x00A7 => Code::BrowserForward,
0x00A9 => Code::Eject,
0x00AB => Code::MediaTrackNext,
0x00AC => Code::MediaPlayPause,
0x00AD => Code::MediaTrackPrevious,
0x00AE => Code::MediaStop,
0x00B3 => Code::MediaSelect,
0x00B4 => Code::BrowserHome,
0x00B5 => Code::BrowserRefresh,
0x00E1 => Code::BrowserSearch,
_ => Code::Unidentified,
}
}
// Extracts the keyboard modifiers from, e.g., the `state` field of
// `xcb::xproto::ButtonPressEvent`
fn key_mods(mods: u16) -> Modifiers {
let mut ret = Modifiers::default();
let mut key_masks = [
(xproto::ModMask::Shift, Modifiers::SHIFT),
(xproto::ModMask::Control, Modifiers::CONTROL),
// X11's mod keys are configurable, but this seems
// like a reasonable default for US keyboards, at least,
// where the "windows" key seems to be MOD_MASK_4.
(xproto::ModMask::M1, Modifiers::ALT),
(xproto::ModMask::M2, Modifiers::NUM_LOCK),
(xproto::ModMask::M4, Modifiers::META),
(xproto::ModMask::Lock, Modifiers::CAPS_LOCK),
];
for (mask, modifiers) in &mut key_masks {
if mods & (*mask as u16) != 0 {
ret |= *modifiers;
}
}
ret
}
pub(super) fn convert_key_press_event(
key_press: &xcb::KeyPressEvent,
) -> KeyEvent {
let hw_keycode = key_press.detail();
let code = hardware_keycode_to_code(hw_keycode);
let mods = key_mods(key_press.state());
let key = code_to_key(code, mods);
let location = code_to_location(code);
let state = KeyState::Down;
KeyEvent {
code,
key,
mods,
location,
state,
repeat: false,
is_composing: false,
}
}
pub(super) fn convert_key_release_event(
key_release: &xcb::KeyReleaseEvent
) -> KeyEvent {
let hw_keycode = key_release.detail();
let code = hardware_keycode_to_code(hw_keycode);
let mods = key_mods(key_release.state());
let key = code_to_key(code, mods);
let location = code_to_location(code);
let state = KeyState::Up;
KeyEvent {
code,
key,
mods,
location,
state,
repeat: false,
is_composing: false,
}
}

View file

@ -4,4 +4,5 @@ use xcb_connection::XcbConnection;
mod window; mod window;
pub use window::*; pub use window::*;
mod cursor; mod cursor;
mod keyboard;

View file

@ -11,8 +11,9 @@ use raw_window_handle::{
use super::XcbConnection; use super::XcbConnection;
use crate::{ use crate::{
Event, KeyboardEvent, MouseButton, MouseCursor, MouseEvent, Parent, ScrollDelta, WindowEvent, Event, KeyboardEvent, MouseButton, MouseCursor, MouseEvent, Parent,
WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, PhyPoint, PhySize, ScrollDelta, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions,
WindowScalePolicy, PhyPoint, PhySize,
}; };
pub struct Window { pub struct Window {
@ -392,21 +393,27 @@ impl Window {
//// ////
xcb::KEY_PRESS => { xcb::KEY_PRESS => {
let event = unsafe { xcb::cast_event::<xcb::KeyPressEvent>(&event) }; let event = unsafe { xcb::cast_event::<xcb::KeyPressEvent>(&event) };
let detail = event.detail();
let event = Event::Keyboard(KeyboardEvent::KeyPressed(
convert_key_press_event(&event)
));
handler.on_event( handler.on_event(
self, self,
Event::Keyboard(KeyboardEvent::KeyPressed(detail as u32)), event
); );
} }
xcb::KEY_RELEASE => { xcb::KEY_RELEASE => {
let event = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&event) }; let event = unsafe { xcb::cast_event::<xcb::KeyReleaseEvent>(&event) };
let detail = event.detail();
let event = Event::Keyboard(KeyboardEvent::KeyReleased(
convert_key_release_event(&event)
));
handler.on_event( handler.on_event(
self, self,
Event::Keyboard(KeyboardEvent::KeyReleased(detail as u32)), event
); );
} }