Language independent hotkeys (#34053)
Addresses #10972 Closes #24950 Closes #24499 Adds _key_en_ to _Keystroke_ that is derived from key's scan code. This is more lightweight approach than #32529 Currently has been tested on x11 and windows. Mac code hasn't been implemented yet. Release Notes: - linux: When typing non-ASCII keys on Linux we will now also match keybindings against the QWERTY-equivalent layout. This should allow most of Zed's builtin shortcuts to work out of the box on most keyboard layouts. **Breaking change**: If you had been using `keysym` names in your keyboard shortcut file (`ctrl-cyrillic_yeru`, etc.) you should now use the QWERTY-equivalent characters instead. --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
51df8a17ef
commit
f50041779d
5 changed files with 108 additions and 13 deletions
|
@ -13,6 +13,9 @@ pub struct Keystroke {
|
|||
|
||||
/// key is the character printed on the key that was pressed
|
||||
/// e.g. for option-s, key is "s"
|
||||
/// On layouts that do not have ascii keys (e.g. Thai)
|
||||
/// this will be the ASCII-equivalent character (q instead of ๆ),
|
||||
/// and the typed character will be present in key_char.
|
||||
pub key: String,
|
||||
|
||||
/// key_char is the character that could have been typed when
|
||||
|
|
|
@ -706,12 +706,81 @@ pub(super) fn log_cursor_icon_warning(message: impl std::fmt::Display) {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
pub(crate) enum KeycodeSource {
|
||||
X11,
|
||||
Wayland,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
impl KeycodeSource {
|
||||
fn guess_ascii(&self, keycode: Keycode, shift: bool) -> Option<char> {
|
||||
let raw = match self {
|
||||
// For historical reasons X11 adds 8 to keycodes
|
||||
Self::X11 => keycode.raw() - 8,
|
||||
// For no particular reason, wayland doesn't.
|
||||
Self::Wayland => keycode.raw(),
|
||||
};
|
||||
let c = match (raw, shift) {
|
||||
(16, _) => 'q',
|
||||
(17, _) => 'w',
|
||||
(18, _) => 'e',
|
||||
(19, _) => 'r',
|
||||
(20, _) => 't',
|
||||
(21, _) => 'y',
|
||||
(22, _) => 'u',
|
||||
(23, _) => 'i',
|
||||
(24, _) => 'o',
|
||||
(25, _) => 'p',
|
||||
(26, false) => '[',
|
||||
(26, true) => '{',
|
||||
(27, false) => ']',
|
||||
(27, true) => '}',
|
||||
(30, _) => 'a',
|
||||
(31, _) => 's',
|
||||
(32, _) => 'd',
|
||||
(33, _) => 'f',
|
||||
(34, _) => 'g',
|
||||
(35, _) => 'h',
|
||||
(36, _) => 'j',
|
||||
(37, _) => 'k',
|
||||
(38, _) => 'l',
|
||||
(39, false) => ';',
|
||||
(39, true) => ':',
|
||||
(40, false) => '\'',
|
||||
(40, true) => '"',
|
||||
(41, false) => '`',
|
||||
(41, true) => '~',
|
||||
(43, false) => '\\',
|
||||
(43, true) => '|',
|
||||
(44, _) => 'z',
|
||||
(45, _) => 'x',
|
||||
(46, _) => 'c',
|
||||
(47, _) => 'v',
|
||||
(48, _) => 'b',
|
||||
(49, _) => 'n',
|
||||
(50, _) => 'm',
|
||||
(51, false) => ',',
|
||||
(51, true) => '>',
|
||||
(52, false) => '.',
|
||||
(52, true) => '<',
|
||||
(53, false) => '/',
|
||||
(53, true) => '?',
|
||||
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(c)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
impl crate::Keystroke {
|
||||
pub(super) fn from_xkb(
|
||||
state: &State,
|
||||
mut modifiers: crate::Modifiers,
|
||||
keycode: Keycode,
|
||||
source: KeycodeSource,
|
||||
) -> Self {
|
||||
let key_utf32 = state.key_get_utf32(keycode);
|
||||
let key_utf8 = state.key_get_utf8(keycode);
|
||||
|
@ -773,6 +842,8 @@ impl crate::Keystroke {
|
|||
let name = xkb::keysym_get_name(key_sym).to_lowercase();
|
||||
if key_sym.is_keypad_key() {
|
||||
name.replace("kp_", "")
|
||||
} else if let Some(key_en) = source.guess_ascii(keycode, modifiers.shift) {
|
||||
String::from(key_en)
|
||||
} else {
|
||||
name
|
||||
}
|
||||
|
|
|
@ -69,7 +69,6 @@ use super::{
|
|||
window::{ImeInput, WaylandWindowStatePtr},
|
||||
};
|
||||
|
||||
use crate::platform::{PlatformWindow, blade::BladeContext};
|
||||
use crate::{
|
||||
AnyWindowHandle, Bounds, Capslock, CursorStyle, DOUBLE_CLICK_INTERVAL, DevicePixels, DisplayId,
|
||||
FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon,
|
||||
|
@ -78,6 +77,10 @@ use crate::{
|
|||
PlatformInput, PlatformKeyboardLayout, Point, SCROLL_LINES, ScaledPixels, ScrollDelta,
|
||||
ScrollWheelEvent, Size, TouchPhase, WindowParams, point, px, size,
|
||||
};
|
||||
use crate::{
|
||||
KeycodeSource,
|
||||
platform::{PlatformWindow, blade::BladeContext},
|
||||
};
|
||||
use crate::{
|
||||
SharedString,
|
||||
platform::linux::{
|
||||
|
@ -1293,8 +1296,12 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
|||
|
||||
match key_state {
|
||||
wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => {
|
||||
let mut keystroke =
|
||||
Keystroke::from_xkb(&keymap_state, state.modifiers, keycode);
|
||||
let mut keystroke = Keystroke::from_xkb(
|
||||
&keymap_state,
|
||||
state.modifiers,
|
||||
keycode,
|
||||
KeycodeSource::Wayland,
|
||||
);
|
||||
if let Some(mut compose) = state.compose_state.take() {
|
||||
compose.feed(keysym);
|
||||
match compose.status() {
|
||||
|
@ -1379,7 +1386,12 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
|||
}
|
||||
wl_keyboard::KeyState::Released if !keysym.is_modifier_key() => {
|
||||
let input = PlatformInput::KeyUp(KeyUpEvent {
|
||||
keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
|
||||
keystroke: Keystroke::from_xkb(
|
||||
keymap_state,
|
||||
state.modifiers,
|
||||
keycode,
|
||||
KeycodeSource::Wayland,
|
||||
),
|
||||
});
|
||||
|
||||
if state.repeat.current_keycode == Some(keycode) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{Capslock, xcb_flush};
|
||||
use crate::{Capslock, KeycodeSource, xcb_flush};
|
||||
use core::str;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
|
@ -1034,7 +1034,8 @@ impl X11Client {
|
|||
xkb_state.latched_layout,
|
||||
xkb_state.locked_layout,
|
||||
);
|
||||
let mut keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
let mut keystroke =
|
||||
crate::Keystroke::from_xkb(&state.xkb, modifiers, code, KeycodeSource::X11);
|
||||
let keysym = state.xkb.key_get_one_sym(code);
|
||||
if keysym.is_modifier_key() {
|
||||
return Some(());
|
||||
|
@ -1102,7 +1103,8 @@ impl X11Client {
|
|||
xkb_state.latched_layout,
|
||||
xkb_state.locked_layout,
|
||||
);
|
||||
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
let keystroke =
|
||||
crate::Keystroke::from_xkb(&state.xkb, modifiers, code, KeycodeSource::X11);
|
||||
let keysym = state.xkb.key_get_one_sym(code);
|
||||
if keysym.is_modifier_key() {
|
||||
return Some(());
|
||||
|
@ -1326,6 +1328,7 @@ impl X11Client {
|
|||
&state.xkb,
|
||||
state.modifiers,
|
||||
event.detail.into(),
|
||||
KeycodeSource::X11,
|
||||
));
|
||||
let (mut ximc, mut xim_handler) = state.take_xim()?;
|
||||
drop(state);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue