Add support for macOS and X11 key events with code from druid
This commit is contained in:
parent
e569015c31
commit
9748e16ebe
|
@ -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]
|
||||||
|
|
10
src/event.rs
10
src/event.rs
|
@ -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,
|
||||||
|
|
274
src/keyboard.rs
274
src/keyboard.rs
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
367
src/macos/keyboard.rs
Normal 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
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod keyboard;
|
||||||
mod window;
|
mod window;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
431
src/x11/keyboard.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,4 +4,5 @@ use xcb_connection::XcbConnection;
|
||||||
mod window;
|
mod window;
|
||||||
pub use window::*;
|
pub use window::*;
|
||||||
|
|
||||||
mod cursor;
|
mod cursor;
|
||||||
|
mod keyboard;
|
|
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue