Overhaul the Keyboard API

Overhaul the keyboard API in winit to mimic the W3C specification
to achieve better crossplatform parity. The `KeyboardInput` event
is now uses `KeyEvent` which consists of:

  - `physical_key` - a cross platform way to refer to scancodes;
  - `logical_key`  - keysym value, which shows your key respecting the
                     layout;
  - `text`         - the text produced by this keypress;
  - `location`     - the location of the key on the keyboard;
  - `repeat`       - whether the key was produced by the repeat.

And also a `platform_specific` field which encapsulates extra
information on desktop platforms, like key without modifiers
and text with all modifiers.

The `Modifiers` were also slightly reworked as in, the information
whether the left or right modifier is pressed is now also exposed
on platforms where it could be queried reliably. The support was
also added for the web and orbital platforms finishing the API
change.

This change made the `OptionAsAlt` API on macOS redundant thus it
was removed all together.

Co-authored-by: Artúr Kovács <kovacs.artur.barnabas@gmail.com>
Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
Co-authored-by: daxpedda <daxpedda@gmail.com>
Fixes: #2631.
Fixes: #2055.
Fixes: #2032.
Fixes: #1904.
Fixes: #1810.
Fixes: #1700.
Fixes: #1443.
Fixes: #1343.
Fixes: #1208.
Fixes: #1151.
Fixes: #812.
Fixes: #600.
Fixes: #361.
Fixes: #343.
This commit is contained in:
Markus Røyset 2023-05-28 20:02:59 +02:00 committed by GitHub
parent f3f46cb3f6
commit 918430979f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 9577 additions and 3419 deletions

View file

@ -1,6 +1,12 @@
#![allow(clippy::unnecessary_cast)]
use std::{boxed::Box, os::raw::*, ptr, str, sync::Mutex};
use std::{
boxed::Box,
collections::{HashMap, VecDeque},
os::raw::*,
ptr, str,
sync::Mutex,
};
use objc2::declare::{Ivar, IvarDrop};
use objc2::foundation::{
@ -11,23 +17,21 @@ use objc2::rc::{Id, Owned, Shared, WeakId};
use objc2::runtime::{Object, Sel};
use objc2::{class, declare_class, msg_send, msg_send_id, sel, ClassType};
use super::appkit::{
NSApp, NSCursor, NSEvent, NSEventModifierFlags, NSEventPhase, NSResponder, NSTrackingRectTag,
NSView,
use super::{
appkit::{NSApp, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTrackingRectTag, NSView},
event::{code_to_key, code_to_location},
};
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
use crate::{
dpi::{LogicalPosition, LogicalSize},
event::{
DeviceEvent, ElementState, Event, Ime, KeyboardInput, ModifiersState, MouseButton,
MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent,
DeviceEvent, ElementState, Event, Ime, Modifiers, MouseButton, MouseScrollDelta,
TouchPhase, WindowEvent,
},
keyboard::{Key, KeyCode, KeyLocation, ModifiersState},
platform::scancode::KeyCodeExtScancode,
platform_impl::platform::{
app_state::AppState,
event::{
char_to_keycode, check_function_keys, event_mods, modifier_event, scancode_to_keycode,
EventWrapper,
},
event::{create_key_event, event_mods, EventWrapper},
util,
window::WinitWindow,
DEVICE_ID,
@ -66,12 +70,60 @@ enum ImeState {
Commited,
}
bitflags! {
struct ModLocationMask: u8 {
const LEFT = 1;
const RIGHT = 2;
}
}
impl ModLocationMask {
fn from_location(loc: KeyLocation) -> ModLocationMask {
match loc {
KeyLocation::Left => ModLocationMask::LEFT,
KeyLocation::Right => ModLocationMask::RIGHT,
_ => unreachable!(),
}
}
}
pub fn key_to_modifier(key: &Key) -> ModifiersState {
match key {
Key::Alt => ModifiersState::ALT,
Key::Control => ModifiersState::CONTROL,
Key::Super => ModifiersState::SUPER,
Key::Shift => ModifiersState::SHIFT,
_ => unreachable!(),
}
}
fn get_right_modifier_code(key: &Key) -> KeyCode {
match key {
Key::Alt => KeyCode::AltRight,
Key::Control => KeyCode::ControlRight,
Key::Shift => KeyCode::ShiftRight,
Key::Super => KeyCode::SuperRight,
_ => unreachable!(),
}
}
fn get_left_modifier_code(key: &Key) -> KeyCode {
match key {
Key::Alt => KeyCode::AltLeft,
Key::Control => KeyCode::ControlLeft,
Key::Shift => KeyCode::ShiftLeft,
Key::Super => KeyCode::SuperLeft,
_ => unreachable!(),
}
}
#[derive(Debug)]
pub(super) struct ViewState {
pub cursor_state: Mutex<CursorState>,
ime_position: LogicalPosition<f64>,
pub(super) modifiers: ModifiersState,
pub(super) modifiers: Modifiers,
phys_modifiers: HashMap<Key, ModLocationMask>,
tracking_rect: Option<NSTrackingRectTag>,
// phys_modifiers: HashSet<KeyCode>,
ime_state: ImeState,
input_source: String,
@ -85,54 +137,6 @@ pub(super) struct ViewState {
forward_key_to_app: bool,
}
fn get_characters(event: &NSEvent, ignore_modifiers: bool) -> String {
if ignore_modifiers {
event.charactersIgnoringModifiers()
} else {
event.characters()
}
.expect("expected characters to be non-null")
.to_string()
}
// As defined in: https://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT
fn is_corporate_character(c: char) -> bool {
matches!(c,
'\u{F700}'..='\u{F747}'
| '\u{F802}'..='\u{F84F}'
| '\u{F850}'
| '\u{F85C}'
| '\u{F85D}'
| '\u{F85F}'
| '\u{F860}'..='\u{F86B}'
| '\u{F870}'..='\u{F8FF}'
)
}
// Retrieves a layout-independent keycode given an event.
fn retrieve_keycode(event: &NSEvent) -> Option<VirtualKeyCode> {
#[inline]
fn get_code(ev: &NSEvent, raw: bool) -> Option<VirtualKeyCode> {
let characters = get_characters(ev, raw);
characters.chars().next().and_then(char_to_keycode)
}
// 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 = event.scancode();
scancode_to_keycode(scancode).or_else(|| check_function_keys(&get_characters(event, true)))
})
}
declare_class!(
#[derive(Debug)]
#[allow(non_snake_case)]
@ -162,6 +166,7 @@ declare_class!(
cursor_state: Default::default(),
ime_position: LogicalPosition::new(0.0, 0.0),
modifiers: Default::default(),
phys_modifiers: Default::default(),
tracking_rect: None,
ime_state: ImeState::Disabled,
input_source: String::new(),
@ -471,16 +476,6 @@ declare_class!(
}
// Get the characters from the event.
let ev_mods = event_mods(event);
let ignore_alt_characters = match self.window().option_as_alt() {
OptionAsAlt::OnlyLeft if event.lalt_pressed() => true,
OptionAsAlt::OnlyRight if event.ralt_pressed() => true,
OptionAsAlt::Both if ev_mods.alt() => true,
_ => false,
} && !ev_mods.ctrl()
&& !ev_mods.logo();
let characters = get_characters(event, ignore_alt_characters);
let old_ime_state = self.state.ime_state;
self.state.forward_key_to_app = false;
@ -491,13 +486,7 @@ declare_class!(
// `doCommandBySelector`. (doCommandBySelector means that the keyboard input
// is not handled by IME and should be handled by the application)
if self.state.ime_allowed {
let new_event = if ignore_alt_characters {
replace_event_chars(event, &characters)
} else {
event.copy()
};
let events_for_nsview = NSArray::from_slice(&[new_event]);
let events_for_nsview = NSArray::from_slice(&[event.copy()]);
unsafe { self.interpretKeyEvents(&events_for_nsview) };
// If the text was commited we must treat the next keyboard event as IME related.
@ -507,10 +496,7 @@ declare_class!(
}
}
let scancode = event.scancode() as u32;
let virtual_keycode = retrieve_keycode(event);
self.update_potentially_stale_modifiers(event);
self.update_modifiers(event, false);
let had_ime_input = match self.state.ime_state {
ImeState::Commited => {
@ -524,89 +510,36 @@ declare_class!(
};
if !had_ime_input || self.state.forward_key_to_app {
#[allow(deprecated)]
let key_event = create_key_event(event, true, event.is_a_repeat(), None);
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: ElementState::Pressed,
scancode,
virtual_keycode,
modifiers: ev_mods,
},
event: key_event,
is_synthetic: false,
});
for character in characters.chars().filter(|c| !is_corporate_character(*c)) {
self.queue_event(WindowEvent::ReceivedCharacter(character));
}
}
}
#[sel(keyUp:)]
fn key_up(&mut self, event: &NSEvent) {
trace_scope!("keyUp:");
let scancode = event.scancode() as u32;
let virtual_keycode = retrieve_keycode(event);
self.update_potentially_stale_modifiers(event);
self.update_modifiers(event, false);
// We want to send keyboard input when we are currently in the ground state.
if matches!(self.state.ime_state, ImeState::Ground | ImeState::Disabled) {
#[allow(deprecated)]
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: ElementState::Released,
scancode,
virtual_keycode,
modifiers: event_mods(event),
},
event: create_key_event(event, false, false, None),
is_synthetic: false,
});
}
}
#[sel(flagsChanged:)]
fn flags_changed(&mut self, event: &NSEvent) {
fn flags_changed(&mut self, ns_event: &NSEvent) {
trace_scope!("flagsChanged:");
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSShiftKeyMask,
self.state.modifiers.shift(),
) {
self.state.modifiers.toggle(ModifiersState::SHIFT);
self.queue_event(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSControlKeyMask,
self.state.modifiers.ctrl(),
) {
self.state.modifiers.toggle(ModifiersState::CTRL);
self.queue_event(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSCommandKeyMask,
self.state.modifiers.logo(),
) {
self.state.modifiers.toggle(ModifiersState::LOGO);
self.queue_event(window_event);
}
if let Some(window_event) = modifier_event(
event,
NSEventModifierFlags::NSAlternateKeyMask,
self.state.modifiers.alt(),
) {
self.state.modifiers.toggle(ModifiersState::ALT);
self.queue_event(window_event);
}
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers));
self.update_modifiers(ns_event, true);
}
#[sel(insertTab:)]
@ -636,25 +569,17 @@ declare_class!(
#[sel(cancelOperation:)]
fn cancel_operation(&mut self, _sender: *const Object) {
trace_scope!("cancelOperation:");
let scancode = 0x2f;
let virtual_keycode = scancode_to_keycode(scancode);
debug_assert_eq!(virtual_keycode, Some(VirtualKeyCode::Period));
let event = NSApp()
.currentEvent()
.expect("could not find current event");
self.update_potentially_stale_modifiers(&event);
self.update_modifiers(&event, false);
let event = create_key_event(&event, true, event.is_a_repeat(), None);
#[allow(deprecated)]
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: ElementState::Pressed,
scancode: scancode as _,
virtual_keycode,
modifiers: event_mods(&event),
},
event,
is_synthetic: false,
});
}
@ -778,14 +703,13 @@ declare_class!(
},
};
self.update_potentially_stale_modifiers(event);
self.update_modifiers(event, false);
self.queue_device_event(DeviceEvent::MouseWheel { delta });
self.queue_event(WindowEvent::MouseWheel {
device_id: DEVICE_ID,
delta,
phase,
modifiers: event_mods(event),
});
}
@ -947,25 +871,120 @@ impl WinitView {
}
// Update `state.modifiers` if `event` has something different
fn update_potentially_stale_modifiers(&mut self, event: &NSEvent) {
let event_modifiers = event_mods(event);
if self.state.modifiers != event_modifiers {
self.state.modifiers = event_modifiers;
fn update_modifiers(&mut self, ns_event: &NSEvent, is_flags_changed_event: bool) {
use ElementState::{Pressed, Released};
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers));
let current_modifiers = event_mods(ns_event);
let prev_modifiers = self.state.modifiers;
self.state.modifiers = current_modifiers;
// This function was called form the flagsChanged event, which is triggered
// when the user presses/releases a modifier even if the same kind of modifier
// has already been pressed
if is_flags_changed_event {
let scancode = ns_event.key_code();
let keycode = KeyCode::from_scancode(scancode as u32);
// We'll correct the `is_press` later.
let mut event = create_key_event(ns_event, false, false, Some(keycode));
let key = code_to_key(keycode, scancode);
let event_modifier = key_to_modifier(&key);
event.physical_key = keycode;
event.logical_key = key.clone();
event.location = code_to_location(keycode);
let location_mask = ModLocationMask::from_location(event.location);
let phys_mod = self
.state
.phys_modifiers
.entry(key)
.or_insert(ModLocationMask::empty());
let is_active = current_modifiers.state().contains(event_modifier);
let mut events = VecDeque::with_capacity(2);
// There is no API for getting whether the button was pressed or released
// during this event. For this reason we have to do a bit of magic below
// to come up with a good guess whether this key was pressed or released.
// (This is not trivial because there are multiple buttons that may affect
// the same modifier)
if !is_active {
event.state = Released;
if phys_mod.contains(ModLocationMask::LEFT) {
let mut event = event.clone();
event.location = KeyLocation::Left;
event.physical_key = get_left_modifier_code(&event.logical_key);
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
if phys_mod.contains(ModLocationMask::RIGHT) {
event.location = KeyLocation::Right;
event.physical_key = get_right_modifier_code(&event.logical_key);
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
*phys_mod = ModLocationMask::empty();
} else {
// is_active
if *phys_mod == location_mask {
// Here we hit a contradiction:
// The modifier state was "changed" to active,
// yet the only pressed modifier key was the one that we
// just got a change event for.
// This seemingly means that the only pressed modifier is now released,
// but at the same time the modifier became active.
//
// But this scenario is possible if we released modifiers
// while the application was not in focus. (Because we don't
// get informed of modifier key events while the application
// is not focused)
// In this case we prioritize the information
// about the current modifier state which means
// that the button was pressed.
event.state = Pressed;
} else {
phys_mod.toggle(location_mask);
let is_pressed = phys_mod.contains(location_mask);
event.state = if is_pressed { Pressed } else { Released };
}
events.push_back(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event,
is_synthetic: false,
});
}
for event in events {
self.queue_event(event);
}
}
if prev_modifiers == current_modifiers {
return;
}
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers));
}
fn mouse_click(&mut self, event: &NSEvent, button_state: ElementState) {
let button = mouse_button(event);
self.update_potentially_stale_modifiers(event);
self.update_modifiers(event, false);
self.queue_event(WindowEvent::MouseInput {
device_id: DEVICE_ID,
state: button_state,
button,
modifiers: event_mods(event),
});
}
@ -990,12 +1009,11 @@ impl WinitView {
let y = view_rect.size.height as f64 - view_point.y as f64;
let logical_position = LogicalPosition::new(x, y);
self.update_potentially_stale_modifiers(event);
self.update_modifiers(event, false);
self.queue_event(WindowEvent::CursorMoved {
device_id: DEVICE_ID,
position: logical_position.to_physical(self.scale_factor()),
modifiers: event_mods(event),
});
}
}
@ -1012,21 +1030,3 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
n => MouseButton::Other(n as u16),
}
}
fn replace_event_chars(event: &NSEvent, characters: &str) -> Id<NSEvent, Shared> {
let ns_chars = NSString::from_str(characters);
let chars_ignoring_mods = event.charactersIgnoringModifiers().unwrap();
NSEvent::keyEventWithType(
event.type_(),
event.locationInWindow(),
event.modifierFlags(),
event.timestamp(),
event.window_number(),
None,
&ns_chars,
&chars_ignoring_mods,
event.is_a_repeat(),
event.scancode(),
)
}