Compare commits
22 Commits
message-ed
...
scan-code
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eac6a9222 | ||
|
|
7c3cffdc52 | ||
|
|
5a3186b659 | ||
|
|
caf54844de | ||
|
|
745ebe2313 | ||
|
|
5c95e942e6 | ||
|
|
f979f24bfa | ||
|
|
411b9abb9e | ||
|
|
81d4d48ef2 | ||
|
|
cd9284761a | ||
|
|
34f9eef879 | ||
|
|
23cf6bf268 | ||
|
|
c97e477eb1 | ||
|
|
16804a81cc | ||
|
|
8bf39bf768 | ||
|
|
75922e8fcd | ||
|
|
2eb83364ae | ||
|
|
5d22585ef5 | ||
|
|
71303fa18b | ||
|
|
5753b978a0 | ||
|
|
9cf2490ed7 | ||
|
|
28ea3ea529 |
13
.github/actions/run_tests/action.yml
vendored
13
.github/actions/run_tests/action.yml
vendored
@@ -1,6 +1,12 @@
|
||||
name: "Run tests"
|
||||
description: "Runs the tests"
|
||||
|
||||
inputs:
|
||||
use-xvfb:
|
||||
description: "Whether to run tests with xvfb"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
@@ -20,4 +26,9 @@ runs:
|
||||
|
||||
- name: Run tests
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo nextest run --workspace --no-fail-fast
|
||||
run: |
|
||||
if [ "${{ inputs.use-xvfb }}" == "true" ]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1024x768x24 -nolisten tcp" cargo nextest run --workspace --no-fail-fast
|
||||
else
|
||||
cargo nextest run --workspace --no-fail-fast
|
||||
fi
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -319,6 +319,8 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
with:
|
||||
use-xvfb: true
|
||||
|
||||
- name: Build other binaries and features
|
||||
run: |
|
||||
|
||||
@@ -240,7 +240,8 @@ impl EditorTestContext {
|
||||
// unlike cx.simulate_keystrokes(), this does not run_until_parked
|
||||
// so you can use it to test detailed timing
|
||||
pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
|
||||
let keystroke = Keystroke::parse(keystroke_text).unwrap();
|
||||
let keyboard_mapper = self.keyboard_mapper();
|
||||
let keystroke = Keystroke::parse(keystroke_text, keyboard_mapper.as_ref()).unwrap();
|
||||
self.cx.dispatch_keystroke(self.window, keystroke);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,10 @@ use crate::{
|
||||
AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId,
|
||||
EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext,
|
||||
Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
|
||||
PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle,
|
||||
PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource,
|
||||
SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window,
|
||||
WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
|
||||
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, PromptBuilder,
|
||||
PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle,
|
||||
Reservation, ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
|
||||
TextSystem, Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
|
||||
colors::{Colors, GlobalColors},
|
||||
current_platform, hash, init_app_menus,
|
||||
};
|
||||
@@ -262,6 +262,7 @@ pub struct App {
|
||||
pub(crate) window_handles: FxHashMap<WindowId, AnyWindowHandle>,
|
||||
pub(crate) focus_handles: Arc<FocusMap>,
|
||||
pub(crate) keymap: Rc<RefCell<Keymap>>,
|
||||
pub(crate) keyboard_mapper: Box<dyn PlatformKeyboardMapper>,
|
||||
pub(crate) keyboard_layout: Box<dyn PlatformKeyboardLayout>,
|
||||
pub(crate) global_action_listeners:
|
||||
FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
|
||||
@@ -308,6 +309,7 @@ impl App {
|
||||
|
||||
let text_system = Arc::new(TextSystem::new(platform.text_system()));
|
||||
let entities = EntityMap::new();
|
||||
let keyboard_mapper = platform.keyboard_mapper();
|
||||
let keyboard_layout = platform.keyboard_layout();
|
||||
|
||||
let app = Rc::new_cyclic(|this| AppCell {
|
||||
@@ -333,6 +335,7 @@ impl App {
|
||||
window_handles: FxHashMap::default(),
|
||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
keymap: Rc::new(RefCell::new(Keymap::default())),
|
||||
keyboard_mapper,
|
||||
keyboard_layout,
|
||||
global_action_listeners: FxHashMap::default(),
|
||||
pending_effects: VecDeque::new(),
|
||||
@@ -369,6 +372,7 @@ impl App {
|
||||
move || {
|
||||
if let Some(app) = app.upgrade() {
|
||||
let cx = &mut app.borrow_mut();
|
||||
cx.keyboard_mapper = cx.platform.keyboard_mapper();
|
||||
cx.keyboard_layout = cx.platform.keyboard_layout();
|
||||
cx.keyboard_layout_observers
|
||||
.clone()
|
||||
@@ -413,6 +417,11 @@ impl App {
|
||||
self.quitting = false;
|
||||
}
|
||||
|
||||
/// Get the keyboard mapper of current keyboard layout
|
||||
pub fn keyboard_mapper(&self) -> &dyn PlatformKeyboardMapper {
|
||||
self.keyboard_mapper.as_ref()
|
||||
}
|
||||
|
||||
/// Get the id of the current keyboard layout
|
||||
pub fn keyboard_layout(&self) -> &dyn PlatformKeyboardLayout {
|
||||
self.keyboard_layout.as_ref()
|
||||
|
||||
@@ -3,9 +3,9 @@ use crate::{
|
||||
BackgroundExecutor, BorrowAppContext, Bounds, ClipboardItem, DrawPhase, Drawable, Element,
|
||||
Empty, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Modifiers,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||
Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform,
|
||||
TestScreenCaptureSource, TestWindow, TextSystem, VisualContext, Window, WindowBounds,
|
||||
WindowHandle, WindowOptions,
|
||||
Platform, PlatformKeyboardMapper, Point, Render, Result, Size, Task, TestDispatcher,
|
||||
TestPlatform, TestScreenCaptureSource, TestWindow, TextSystem, VisualContext, Window,
|
||||
WindowBounds, WindowHandle, WindowOptions,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{Stream, StreamExt, channel::oneshot};
|
||||
@@ -397,14 +397,20 @@ impl TestAppContext {
|
||||
self.background_executor.run_until_parked()
|
||||
}
|
||||
|
||||
/// Returns the current keyboard mapper for this platform.
|
||||
pub fn keyboard_mapper(&self) -> Box<dyn PlatformKeyboardMapper> {
|
||||
self.test_platform.keyboard_mapper()
|
||||
}
|
||||
|
||||
/// simulate_keystrokes takes a space-separated list of keys to type.
|
||||
/// cx.simulate_keystrokes("cmd-shift-p b k s p enter")
|
||||
/// in Zed, this will run backspace on the current editor through the command palette.
|
||||
/// This will also run the background executor until it's parked.
|
||||
pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) {
|
||||
let keyboard_mapper = self.keyboard_mapper();
|
||||
for keystroke in keystrokes
|
||||
.split(' ')
|
||||
.map(Keystroke::parse)
|
||||
.map(|source| Keystroke::parse(source, keyboard_mapper.as_ref()))
|
||||
.map(Result::unwrap)
|
||||
{
|
||||
self.dispatch_keystroke(window, keystroke);
|
||||
@@ -418,7 +424,12 @@ impl TestAppContext {
|
||||
/// will type abc into your current editor
|
||||
/// This will also run the background executor until it's parked.
|
||||
pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
|
||||
for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
|
||||
let keyboard_mapper = self.keyboard_mapper();
|
||||
for keystroke in input
|
||||
.split("")
|
||||
.map(|source| Keystroke::parse(source, keyboard_mapper.as_ref()))
|
||||
.map(Result::unwrap)
|
||||
{
|
||||
self.dispatch_keystroke(window, keystroke);
|
||||
}
|
||||
|
||||
|
||||
@@ -538,8 +538,22 @@ mod test {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap());
|
||||
cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap());
|
||||
cx.dispatch_keystroke(
|
||||
*window,
|
||||
Keystroke {
|
||||
modifiers: crate::Modifiers::none(),
|
||||
key: "a".to_owned(),
|
||||
key_char: None,
|
||||
},
|
||||
);
|
||||
cx.dispatch_keystroke(
|
||||
*window,
|
||||
Keystroke {
|
||||
modifiers: crate::Modifiers::control(),
|
||||
key: "g".to_owned(),
|
||||
key_char: None,
|
||||
},
|
||||
);
|
||||
|
||||
window
|
||||
.update(cx, |test_view, _, _| {
|
||||
|
||||
@@ -310,7 +310,11 @@ mod tests {
|
||||
assert!(
|
||||
keymap
|
||||
.bindings_for_input(
|
||||
&[Keystroke::parse("ctrl-a").unwrap()],
|
||||
&[Keystroke {
|
||||
modifiers: crate::Modifiers::control(),
|
||||
key: "a".to_owned(),
|
||||
key_char: None
|
||||
}],
|
||||
&[KeyContext::parse("barf").unwrap()],
|
||||
)
|
||||
.0
|
||||
@@ -319,7 +323,11 @@ mod tests {
|
||||
assert!(
|
||||
!keymap
|
||||
.bindings_for_input(
|
||||
&[Keystroke::parse("ctrl-a").unwrap()],
|
||||
&[Keystroke {
|
||||
modifiers: crate::Modifiers::control(),
|
||||
key: "a".to_owned(),
|
||||
key_char: None
|
||||
}],
|
||||
&[KeyContext::parse("editor").unwrap()],
|
||||
)
|
||||
.0
|
||||
@@ -330,7 +338,11 @@ mod tests {
|
||||
assert!(
|
||||
keymap
|
||||
.bindings_for_input(
|
||||
&[Keystroke::parse("ctrl-a").unwrap()],
|
||||
&[Keystroke {
|
||||
modifiers: crate::Modifiers::control(),
|
||||
key: "a".to_owned(),
|
||||
key_char: None
|
||||
}],
|
||||
&[KeyContext::parse("editor mode=full").unwrap()],
|
||||
)
|
||||
.0
|
||||
@@ -341,7 +353,11 @@ mod tests {
|
||||
assert!(
|
||||
keymap
|
||||
.bindings_for_input(
|
||||
&[Keystroke::parse("ctrl-b").unwrap()],
|
||||
&[Keystroke {
|
||||
modifiers: crate::Modifiers::control(),
|
||||
key: "b".to_owned(),
|
||||
key_char: None
|
||||
}],
|
||||
&[KeyContext::parse("barf").unwrap()],
|
||||
)
|
||||
.0
|
||||
@@ -360,8 +376,16 @@ mod tests {
|
||||
let mut keymap = Keymap::default();
|
||||
keymap.add_bindings(bindings.clone());
|
||||
|
||||
let space = || Keystroke::parse("space").unwrap();
|
||||
let w = || Keystroke::parse("w").unwrap();
|
||||
let space = || Keystroke {
|
||||
modifiers: crate::Modifiers::none(),
|
||||
key: "space".to_owned(),
|
||||
key_char: None,
|
||||
};
|
||||
let w = || Keystroke {
|
||||
modifiers: crate::Modifiers::none(),
|
||||
key: "w".to_owned(),
|
||||
key_char: None,
|
||||
};
|
||||
|
||||
let space_w = [space(), w()];
|
||||
let space_w_w = [space(), w(), w()];
|
||||
|
||||
@@ -2,7 +2,10 @@ use std::rc::Rc;
|
||||
|
||||
use collections::HashMap;
|
||||
|
||||
use crate::{Action, InvalidKeystrokeError, KeyBindingContextPredicate, Keystroke};
|
||||
use crate::{
|
||||
Action, InvalidKeystrokeError, KeyBindingContextPredicate, Keystroke, PlatformKeyboardMapper,
|
||||
TestKeyboardMapper,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// A keybinding and its associated metadata, from the keymap.
|
||||
@@ -25,12 +28,20 @@ impl Clone for KeyBinding {
|
||||
impl KeyBinding {
|
||||
/// Construct a new keybinding from the given data. Panics on parse error.
|
||||
pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
|
||||
let keyboard_mapper = TestKeyboardMapper::new();
|
||||
let context_predicate = if let Some(context) = context {
|
||||
Some(KeyBindingContextPredicate::parse(context).unwrap().into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self::load(keystrokes, Box::new(action), context_predicate, None).unwrap()
|
||||
Self::load(
|
||||
keystrokes,
|
||||
Box::new(action),
|
||||
context_predicate,
|
||||
None,
|
||||
&keyboard_mapper,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Load a keybinding from the given raw data.
|
||||
@@ -39,10 +50,11 @@ impl KeyBinding {
|
||||
action: Box<dyn Action>,
|
||||
context_predicate: Option<Rc<KeyBindingContextPredicate>>,
|
||||
key_equivalents: Option<&HashMap<char, char>>,
|
||||
keyboard_mapper: &dyn PlatformKeyboardMapper,
|
||||
) -> std::result::Result<Self, InvalidKeystrokeError> {
|
||||
let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes
|
||||
.split_whitespace()
|
||||
.map(Keystroke::parse)
|
||||
.map(|source| Keystroke::parse(source, keyboard_mapper))
|
||||
.collect::<std::result::Result<_, _>>()?;
|
||||
|
||||
if let Some(equivalents) = key_equivalents {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod app_menu;
|
||||
mod keyboard;
|
||||
mod keycode;
|
||||
mod keystroke;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
@@ -66,6 +67,7 @@ use uuid::Uuid;
|
||||
|
||||
pub use app_menu::*;
|
||||
pub use keyboard::*;
|
||||
pub use keycode::*;
|
||||
pub use keystroke::*;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
@@ -194,7 +196,6 @@ pub(crate) trait Platform: 'static {
|
||||
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>);
|
||||
fn on_reopen(&self, callback: Box<dyn FnMut()>);
|
||||
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
|
||||
|
||||
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
|
||||
fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
|
||||
@@ -214,7 +215,6 @@ pub(crate) trait Platform: 'static {
|
||||
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
|
||||
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
|
||||
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
|
||||
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
|
||||
|
||||
fn compositor_name(&self) -> &'static str {
|
||||
""
|
||||
@@ -235,6 +235,10 @@ pub(crate) trait Platform: 'static {
|
||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
|
||||
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
|
||||
fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
|
||||
|
||||
fn keyboard_mapper(&self) -> Box<dyn PlatformKeyboardMapper>;
|
||||
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
|
||||
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
|
||||
}
|
||||
|
||||
/// A handle to a platform's display, e.g. a monitor or laptop screen.
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{Modifiers, ScanCode};
|
||||
|
||||
/// A trait for platform-specific keyboard layouts
|
||||
pub trait PlatformKeyboardLayout {
|
||||
/// Get the keyboard layout ID, which should be unique to the layout
|
||||
@@ -5,3 +9,109 @@ pub trait PlatformKeyboardLayout {
|
||||
/// Get the keyboard layout display name
|
||||
fn name(&self) -> &str;
|
||||
}
|
||||
|
||||
/// TODO:
|
||||
pub trait PlatformKeyboardMapper {
|
||||
/// TODO:
|
||||
fn scan_code_to_key(&self, scan_code: ScanCode, modifiers: &mut Modifiers) -> Result<String>;
|
||||
}
|
||||
|
||||
/// TODO:
|
||||
pub struct TestKeyboardMapper {
|
||||
#[cfg(target_os = "windows")]
|
||||
mapper: super::WindowsKeyboardMapper,
|
||||
#[cfg(target_os = "macos")]
|
||||
mapper: super::MacKeyboardMapper,
|
||||
#[cfg(target_os = "linux")]
|
||||
mapper: super::LinuxKeyboardMapper,
|
||||
}
|
||||
|
||||
impl PlatformKeyboardMapper for TestKeyboardMapper {
|
||||
fn scan_code_to_key(&self, scan_code: ScanCode, modifiers: &mut Modifiers) -> Result<String> {
|
||||
self.mapper.scan_code_to_key(scan_code, modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestKeyboardMapper {
|
||||
/// TODO:
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
#[cfg(target_os = "windows")]
|
||||
mapper: super::WindowsKeyboardMapper::new(),
|
||||
#[cfg(target_os = "macos")]
|
||||
mapper: super::MacKeyboardMapper::new(),
|
||||
#[cfg(target_os = "linux")]
|
||||
mapper: super::LinuxKeyboardMapper::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A dummy keyboard mapper that does not support any key mappings
|
||||
pub struct EmptyKeyboardMapper;
|
||||
|
||||
impl PlatformKeyboardMapper for EmptyKeyboardMapper {
|
||||
fn scan_code_to_key(&self, _scan_code: ScanCode, _modifiers: &mut Modifiers) -> Result<String> {
|
||||
anyhow::bail!("EmptyKeyboardMapper does not support scan codes")
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_letter_key(key: &str) -> bool {
|
||||
matches!(
|
||||
key,
|
||||
"a" | "b"
|
||||
| "c"
|
||||
| "d"
|
||||
| "e"
|
||||
| "f"
|
||||
| "g"
|
||||
| "h"
|
||||
| "i"
|
||||
| "j"
|
||||
| "k"
|
||||
| "l"
|
||||
| "m"
|
||||
| "n"
|
||||
| "o"
|
||||
| "p"
|
||||
| "q"
|
||||
| "r"
|
||||
| "s"
|
||||
| "t"
|
||||
| "u"
|
||||
| "v"
|
||||
| "w"
|
||||
| "x"
|
||||
| "y"
|
||||
| "z"
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::{Modifiers, ScanCode};
|
||||
|
||||
use super::{PlatformKeyboardMapper, TestKeyboardMapper};
|
||||
|
||||
#[test]
|
||||
fn test_scan_code_to_key() {
|
||||
let mapper = TestKeyboardMapper::new();
|
||||
for scan_code in ScanCode::iter() {
|
||||
let mut modifiers = Modifiers::default();
|
||||
let key = mapper.scan_code_to_key(scan_code, &mut modifiers).unwrap();
|
||||
assert_eq!(key, scan_code.to_key(false));
|
||||
assert_eq!(modifiers, Modifiers::default());
|
||||
|
||||
let mut modifiers = Modifiers::shift();
|
||||
let shifted_key = mapper.scan_code_to_key(scan_code, &mut modifiers).unwrap();
|
||||
assert_eq!(shifted_key, scan_code.to_key(true));
|
||||
if shifted_key != key {
|
||||
assert_eq!(modifiers, Modifiers::default());
|
||||
} else {
|
||||
assert_eq!(modifiers, Modifiers::shift());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
590
crates/gpui/src/platform/keycode.rs
Normal file
590
crates/gpui/src/platform/keycode.rs
Normal file
@@ -0,0 +1,590 @@
|
||||
use strum::EnumIter;
|
||||
|
||||
/// Scan codes for the keyboard, which are used to identify keys in a keyboard layout-independent way.
|
||||
/// Currently, we only support a limited set of scan codes here:
|
||||
/// https://code.visualstudio.com/docs/configure/keybindings#_keyboard-layoutindependent-bindings
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
|
||||
pub enum ScanCode {
|
||||
/// F1 key
|
||||
F1,
|
||||
/// F1 key
|
||||
F2,
|
||||
/// F1 key
|
||||
F3,
|
||||
/// F1 key
|
||||
F4,
|
||||
/// F1 key
|
||||
F5,
|
||||
/// F1 key
|
||||
F6,
|
||||
/// F1 key
|
||||
F7,
|
||||
/// F1 key
|
||||
F8,
|
||||
/// F1 key
|
||||
F9,
|
||||
/// F1 key
|
||||
F10,
|
||||
/// F1 key
|
||||
F11,
|
||||
/// F1 key
|
||||
F12,
|
||||
/// F1 key
|
||||
F13,
|
||||
/// F1 key
|
||||
F14,
|
||||
/// F1 key
|
||||
F15,
|
||||
/// F1 key
|
||||
F16,
|
||||
/// F1 key
|
||||
F17,
|
||||
/// F1 key
|
||||
F18,
|
||||
/// F1 key
|
||||
F19,
|
||||
/// F20 key
|
||||
F20,
|
||||
/// F20 key
|
||||
F21,
|
||||
/// F20 key
|
||||
F22,
|
||||
/// F20 key
|
||||
F23,
|
||||
/// F20 key
|
||||
F24,
|
||||
/// A key on the main keyboard.
|
||||
A,
|
||||
/// B key on the main keyboard.
|
||||
B,
|
||||
/// C key on the main keyboard.
|
||||
C,
|
||||
/// D key on the main keyboard.
|
||||
D,
|
||||
/// E key on the main keyboard.
|
||||
E,
|
||||
/// F key on the main keyboard.
|
||||
F,
|
||||
/// G key on the main keyboard.
|
||||
G,
|
||||
/// H key on the main keyboard.
|
||||
H,
|
||||
/// I key on the main keyboard.
|
||||
I,
|
||||
/// J key on the main keyboard.
|
||||
J,
|
||||
/// K key on the main keyboard.
|
||||
K,
|
||||
/// L key on the main keyboard.
|
||||
L,
|
||||
/// M key on the main keyboard.
|
||||
M,
|
||||
/// N key on the main keyboard.
|
||||
N,
|
||||
/// O key on the main keyboard.
|
||||
O,
|
||||
/// P key on the main keyboard.
|
||||
P,
|
||||
/// Q key on the main keyboard.
|
||||
Q,
|
||||
/// R key on the main keyboard.
|
||||
R,
|
||||
/// S key on the main keyboard.
|
||||
S,
|
||||
/// T key on the main keyboard.
|
||||
T,
|
||||
/// U key on the main keyboard.
|
||||
U,
|
||||
/// V key on the main keyboard.
|
||||
V,
|
||||
/// W key on the main keyboard.
|
||||
W,
|
||||
/// X key on the main keyboard.
|
||||
X,
|
||||
/// Y key on the main keyboard.
|
||||
Y,
|
||||
/// Z key on the main keyboard.
|
||||
Z,
|
||||
/// 0 key on the main keyboard.
|
||||
Digit0,
|
||||
/// 1 key on the main keyboard.
|
||||
Digit1,
|
||||
/// 2 key on the main keyboard.
|
||||
Digit2,
|
||||
/// 3 key on the main keyboard.
|
||||
Digit3,
|
||||
/// 4 key on the main keyboard.
|
||||
Digit4,
|
||||
/// 5 key on the main keyboard.
|
||||
Digit5,
|
||||
/// 6 key on the main keyboard.
|
||||
Digit6,
|
||||
/// 7 key on the main keyboard.
|
||||
Digit7,
|
||||
/// 8 key on the main keyboard.
|
||||
Digit8,
|
||||
/// 9 key on the main keyboard.
|
||||
Digit9,
|
||||
|
||||
/// Backquote key on the main keyboard: `
|
||||
Backquote,
|
||||
/// Minus key on the main keyboard: -
|
||||
Minus,
|
||||
/// Equal key on the main keyboard: =
|
||||
Equal,
|
||||
/// BracketLeft key on the main keyboard: [
|
||||
BracketLeft,
|
||||
/// BracketRight key on the main keyboard: ]
|
||||
BracketRight,
|
||||
/// Backslash key on the main keyboard: \
|
||||
Backslash,
|
||||
/// Semicolon key on the main keyboard: ;
|
||||
Semicolon,
|
||||
/// Quote key on the main keyboard: '
|
||||
Quote,
|
||||
/// Comma key on the main keyboard: ,
|
||||
Comma,
|
||||
/// Period key on the main keyboard: .
|
||||
Period,
|
||||
/// Slash key on the main keyboard: /
|
||||
Slash,
|
||||
|
||||
/// Left arrow key
|
||||
Left,
|
||||
/// Up arrow key
|
||||
Up,
|
||||
/// Right arrow key
|
||||
Right,
|
||||
/// Down arrow key
|
||||
Down,
|
||||
/// PAGE UP key
|
||||
PageUp,
|
||||
/// PAGE DOWN key
|
||||
PageDown,
|
||||
/// END key
|
||||
End,
|
||||
/// HOME key
|
||||
Home,
|
||||
|
||||
/// TAB key
|
||||
Tab,
|
||||
/// ENTER key, also known as RETURN key
|
||||
/// This does not distinguish between the main Enter key and the numeric keypad Enter key.
|
||||
Enter,
|
||||
/// ESCAPE key
|
||||
Escape,
|
||||
/// SPACE key
|
||||
Space,
|
||||
/// BACKSPACE key
|
||||
Backspace,
|
||||
/// DELETE key
|
||||
Delete,
|
||||
|
||||
// Pause, not supported yet
|
||||
// CapsLock, not supported yet
|
||||
/// INSERT key
|
||||
Insert,
|
||||
// The following keys are not supported yet:
|
||||
// Numpad0,
|
||||
// Numpad1,
|
||||
// Numpad2,
|
||||
// Numpad3,
|
||||
// Numpad4,
|
||||
// Numpad5,
|
||||
// Numpad6,
|
||||
// Numpad7,
|
||||
// Numpad8,
|
||||
// Numpad9,
|
||||
// NumpadMultiply,
|
||||
// NumpadAdd,
|
||||
// NumpadComma,
|
||||
// NumpadSubtract,
|
||||
// NumpadDecimal,
|
||||
// NumpadDivide,
|
||||
}
|
||||
|
||||
impl ScanCode {
|
||||
/// Parse a scan code from a string.
|
||||
pub fn parse(source: &str) -> Option<Self> {
|
||||
match source {
|
||||
"[f1]" => Some(Self::F1),
|
||||
"[f2]" => Some(Self::F2),
|
||||
"[f3]" => Some(Self::F3),
|
||||
"[f4]" => Some(Self::F4),
|
||||
"[f5]" => Some(Self::F5),
|
||||
"[f6]" => Some(Self::F6),
|
||||
"[f7]" => Some(Self::F7),
|
||||
"[f8]" => Some(Self::F8),
|
||||
"[f9]" => Some(Self::F9),
|
||||
"[f10]" => Some(Self::F10),
|
||||
"[f11]" => Some(Self::F11),
|
||||
"[f12]" => Some(Self::F12),
|
||||
"[f13]" => Some(Self::F13),
|
||||
"[f14]" => Some(Self::F14),
|
||||
"[f15]" => Some(Self::F15),
|
||||
"[f16]" => Some(Self::F16),
|
||||
"[f17]" => Some(Self::F17),
|
||||
"[f18]" => Some(Self::F18),
|
||||
"[f19]" => Some(Self::F19),
|
||||
"[f20]" => Some(Self::F20),
|
||||
"[f21]" => Some(Self::F21),
|
||||
"[f22]" => Some(Self::F22),
|
||||
"[f23]" => Some(Self::F23),
|
||||
"[f24]" => Some(Self::F24),
|
||||
"[a]" | "[keya]" => Some(Self::A),
|
||||
"[b]" | "[keyb]" => Some(Self::B),
|
||||
"[c]" | "[keyc]" => Some(Self::C),
|
||||
"[d]" | "[keyd]" => Some(Self::D),
|
||||
"[e]" | "[keye]" => Some(Self::E),
|
||||
"[f]" | "[keyf]" => Some(Self::F),
|
||||
"[g]" | "[keyg]" => Some(Self::G),
|
||||
"[h]" | "[keyh]" => Some(Self::H),
|
||||
"[i]" | "[keyi]" => Some(Self::I),
|
||||
"[j]" | "[keyj]" => Some(Self::J),
|
||||
"[k]" | "[keyk]" => Some(Self::K),
|
||||
"[l]" | "[keyl]" => Some(Self::L),
|
||||
"[m]" | "[keym]" => Some(Self::M),
|
||||
"[n]" | "[keyn]" => Some(Self::N),
|
||||
"[o]" | "[keyo]" => Some(Self::O),
|
||||
"[p]" | "[keyp]" => Some(Self::P),
|
||||
"[q]" | "[keyq]" => Some(Self::Q),
|
||||
"[r]" | "[keyr]" => Some(Self::R),
|
||||
"[s]" | "[keys]" => Some(Self::S),
|
||||
"[t]" | "[keyt]" => Some(Self::T),
|
||||
"[u]" | "[keyu]" => Some(Self::U),
|
||||
"[v]" | "[keyv]" => Some(Self::V),
|
||||
"[w]" | "[keyw]" => Some(Self::W),
|
||||
"[x]" | "[keyx]" => Some(Self::X),
|
||||
"[y]" | "[keyy]" => Some(Self::Y),
|
||||
"[z]" | "[keyz]" => Some(Self::Z),
|
||||
"[0]" | "[digit0]" => Some(Self::Digit0),
|
||||
"[1]" | "[digit1]" => Some(Self::Digit1),
|
||||
"[2]" | "[digit2]" => Some(Self::Digit2),
|
||||
"[3]" | "[digit3]" => Some(Self::Digit3),
|
||||
"[4]" | "[digit4]" => Some(Self::Digit4),
|
||||
"[5]" | "[digit5]" => Some(Self::Digit5),
|
||||
"[6]" | "[digit6]" => Some(Self::Digit6),
|
||||
"[7]" | "[digit7]" => Some(Self::Digit7),
|
||||
"[8]" | "[digit8]" => Some(Self::Digit8),
|
||||
"[9]" | "[digit9]" => Some(Self::Digit9),
|
||||
|
||||
"[backquote]" => Some(Self::Backquote),
|
||||
"[minus]" => Some(Self::Minus),
|
||||
"[equal]" => Some(Self::Equal),
|
||||
"[bracketleft]" => Some(Self::BracketLeft),
|
||||
"[bracketright]" => Some(Self::BracketRight),
|
||||
"[backslash]" => Some(Self::Backslash),
|
||||
"[semicolon]" => Some(Self::Semicolon),
|
||||
"[quote]" => Some(Self::Quote),
|
||||
"[comma]" => Some(Self::Comma),
|
||||
"[period]" => Some(Self::Period),
|
||||
"[slash]" => Some(Self::Slash),
|
||||
|
||||
"[left]" | "[arrowleft]" => Some(Self::Left),
|
||||
"[up]" | "[arrowup]" => Some(Self::Up),
|
||||
"[right]" | "[arrowright]" => Some(Self::Right),
|
||||
"[down]" | "[arrowdown]" => Some(Self::Down),
|
||||
"[pageup]" => Some(Self::PageUp),
|
||||
"[pagedown]" => Some(Self::PageDown),
|
||||
"[end]" => Some(Self::End),
|
||||
"[home]" => Some(Self::Home),
|
||||
|
||||
"[tab]" => Some(Self::Tab),
|
||||
"[enter]" => Some(Self::Enter),
|
||||
"[escape]" => Some(Self::Escape),
|
||||
"[space]" => Some(Self::Space),
|
||||
"[backspace]" => Some(Self::Backspace),
|
||||
"[delete]" => Some(Self::Delete),
|
||||
|
||||
// "[pause]" => Some(Self::Pause),
|
||||
// "[capslock]" => Some(Self::CapsLock),
|
||||
"[insert]" => Some(Self::Insert),
|
||||
|
||||
// "[numpad0]" => Some(Self::Numpad0),
|
||||
// "[numpad1]" => Some(Self::Numpad1),
|
||||
// "[numpad2]" => Some(Self::Numpad2),
|
||||
// "[numpad3]" => Some(Self::Numpad3),
|
||||
// "[numpad4]" => Some(Self::Numpad4),
|
||||
// "[numpad5]" => Some(Self::Numpad5),
|
||||
// "[numpad6]" => Some(Self::Numpad6),
|
||||
// "[numpad7]" => Some(Self::Numpad7),
|
||||
// "[numpad8]" => Some(Self::Numpad8),
|
||||
// "[numpad9]" => Some(Self::Numpad9),
|
||||
// "[numpadmultiply]" => Some(Self::NumpadMultiply),
|
||||
// "[numpadadd]" => Some(Self::NumpadAdd),
|
||||
// "[numpadcomma]" => Some(Self::NumpadComma),
|
||||
// "[numpadsubtract]" => Some(Self::NumpadSubtract),
|
||||
// "[numpaddecimal]" => Some(Self::NumpadDecimal),
|
||||
// "[numpaddivide]" => Some(Self::NumpadDivide),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the scan code to its key face for immutable keys.
|
||||
pub fn try_to_key(&self) -> Option<String> {
|
||||
Some(
|
||||
match self {
|
||||
ScanCode::F1 => "f1",
|
||||
ScanCode::F2 => "f2",
|
||||
ScanCode::F3 => "f3",
|
||||
ScanCode::F4 => "f4",
|
||||
ScanCode::F5 => "f5",
|
||||
ScanCode::F6 => "f6",
|
||||
ScanCode::F7 => "f7",
|
||||
ScanCode::F8 => "f8",
|
||||
ScanCode::F9 => "f9",
|
||||
ScanCode::F10 => "f10",
|
||||
ScanCode::F11 => "f11",
|
||||
ScanCode::F12 => "f12",
|
||||
ScanCode::F13 => "f13",
|
||||
ScanCode::F14 => "f14",
|
||||
ScanCode::F15 => "f15",
|
||||
ScanCode::F16 => "f16",
|
||||
ScanCode::F17 => "f17",
|
||||
ScanCode::F18 => "f18",
|
||||
ScanCode::F19 => "f19",
|
||||
ScanCode::F20 => "f20",
|
||||
ScanCode::F21 => "f21",
|
||||
ScanCode::F22 => "f22",
|
||||
ScanCode::F23 => "f23",
|
||||
ScanCode::F24 => "f24",
|
||||
ScanCode::Left => "left",
|
||||
ScanCode::Up => "up",
|
||||
ScanCode::Right => "right",
|
||||
ScanCode::Down => "down",
|
||||
ScanCode::PageUp => "pageup",
|
||||
ScanCode::PageDown => "pagedown",
|
||||
ScanCode::End => "end",
|
||||
ScanCode::Home => "home",
|
||||
ScanCode::Tab => "tab",
|
||||
ScanCode::Enter => "enter",
|
||||
ScanCode::Escape => "escape",
|
||||
ScanCode::Space => "space",
|
||||
ScanCode::Backspace => "backspace",
|
||||
ScanCode::Delete => "delete",
|
||||
ScanCode::Insert => "insert",
|
||||
_ => return None,
|
||||
}
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
/// This function is used to convert the scan code to its key face on US keyboard layout.
|
||||
/// Only used for tests.
|
||||
pub fn to_key(&self, shift: bool) -> &str {
|
||||
match self {
|
||||
ScanCode::F1 => "f1",
|
||||
ScanCode::F2 => "f2",
|
||||
ScanCode::F3 => "f3",
|
||||
ScanCode::F4 => "f4",
|
||||
ScanCode::F5 => "f5",
|
||||
ScanCode::F6 => "f6",
|
||||
ScanCode::F7 => "f7",
|
||||
ScanCode::F8 => "f8",
|
||||
ScanCode::F9 => "f9",
|
||||
ScanCode::F10 => "f10",
|
||||
ScanCode::F11 => "f11",
|
||||
ScanCode::F12 => "f12",
|
||||
ScanCode::F13 => "f13",
|
||||
ScanCode::F14 => "f14",
|
||||
ScanCode::F15 => "f15",
|
||||
ScanCode::F16 => "f16",
|
||||
ScanCode::F17 => "f17",
|
||||
ScanCode::F18 => "f18",
|
||||
ScanCode::F19 => "f19",
|
||||
ScanCode::F20 => "f20",
|
||||
ScanCode::F21 => "f21",
|
||||
ScanCode::F22 => "f22",
|
||||
ScanCode::F23 => "f23",
|
||||
ScanCode::F24 => "f24",
|
||||
ScanCode::A => "a",
|
||||
ScanCode::B => "b",
|
||||
ScanCode::C => "c",
|
||||
ScanCode::D => "d",
|
||||
ScanCode::E => "e",
|
||||
ScanCode::F => "f",
|
||||
ScanCode::G => "g",
|
||||
ScanCode::H => "h",
|
||||
ScanCode::I => "i",
|
||||
ScanCode::J => "j",
|
||||
ScanCode::K => "k",
|
||||
ScanCode::L => "l",
|
||||
ScanCode::M => "m",
|
||||
ScanCode::N => "n",
|
||||
ScanCode::O => "o",
|
||||
ScanCode::P => "p",
|
||||
ScanCode::Q => "q",
|
||||
ScanCode::R => "r",
|
||||
ScanCode::S => "s",
|
||||
ScanCode::T => "t",
|
||||
ScanCode::U => "u",
|
||||
ScanCode::V => "v",
|
||||
ScanCode::W => "w",
|
||||
ScanCode::X => "x",
|
||||
ScanCode::Y => "y",
|
||||
ScanCode::Z => "z",
|
||||
ScanCode::Digit0 => {
|
||||
if shift {
|
||||
")"
|
||||
} else {
|
||||
"0"
|
||||
}
|
||||
}
|
||||
ScanCode::Digit1 => {
|
||||
if shift {
|
||||
"!"
|
||||
} else {
|
||||
"1"
|
||||
}
|
||||
}
|
||||
ScanCode::Digit2 => {
|
||||
if shift {
|
||||
"@"
|
||||
} else {
|
||||
"2"
|
||||
}
|
||||
}
|
||||
ScanCode::Digit3 => {
|
||||
if shift {
|
||||
"#"
|
||||
} else {
|
||||
"3"
|
||||
}
|
||||
}
|
||||
ScanCode::Digit4 => {
|
||||
if shift {
|
||||
"$"
|
||||
} else {
|
||||
"4"
|
||||
}
|
||||
}
|
||||
ScanCode::Digit5 => {
|
||||
if shift {
|
||||
"%"
|
||||
} else {
|
||||
"5"
|
||||
}
|
||||
}
|
||||
ScanCode::Digit6 => {
|
||||
if shift {
|
||||
"^"
|
||||
} else {
|
||||
"6"
|
||||
}
|
||||
}
|
||||
ScanCode::Digit7 => {
|
||||
if shift {
|
||||
"&"
|
||||
} else {
|
||||
"7"
|
||||
}
|
||||
}
|
||||
ScanCode::Digit8 => {
|
||||
if shift {
|
||||
"*"
|
||||
} else {
|
||||
"8"
|
||||
}
|
||||
}
|
||||
ScanCode::Digit9 => {
|
||||
if shift {
|
||||
"("
|
||||
} else {
|
||||
"9"
|
||||
}
|
||||
}
|
||||
ScanCode::Backquote => {
|
||||
if shift {
|
||||
"~"
|
||||
} else {
|
||||
"`"
|
||||
}
|
||||
}
|
||||
ScanCode::Minus => {
|
||||
if shift {
|
||||
"_"
|
||||
} else {
|
||||
"-"
|
||||
}
|
||||
}
|
||||
ScanCode::Equal => {
|
||||
if shift {
|
||||
"+"
|
||||
} else {
|
||||
"="
|
||||
}
|
||||
}
|
||||
ScanCode::BracketLeft => {
|
||||
if shift {
|
||||
"{"
|
||||
} else {
|
||||
"["
|
||||
}
|
||||
}
|
||||
ScanCode::BracketRight => {
|
||||
if shift {
|
||||
"}"
|
||||
} else {
|
||||
"]"
|
||||
}
|
||||
}
|
||||
ScanCode::Backslash => {
|
||||
if shift {
|
||||
"|"
|
||||
} else {
|
||||
"\\"
|
||||
}
|
||||
}
|
||||
ScanCode::Semicolon => {
|
||||
if shift {
|
||||
":"
|
||||
} else {
|
||||
";"
|
||||
}
|
||||
}
|
||||
ScanCode::Quote => {
|
||||
if shift {
|
||||
"\""
|
||||
} else {
|
||||
"'"
|
||||
}
|
||||
}
|
||||
ScanCode::Comma => {
|
||||
if shift {
|
||||
"<"
|
||||
} else {
|
||||
","
|
||||
}
|
||||
}
|
||||
ScanCode::Period => {
|
||||
if shift {
|
||||
">"
|
||||
} else {
|
||||
"."
|
||||
}
|
||||
}
|
||||
ScanCode::Slash => {
|
||||
if shift {
|
||||
"?"
|
||||
} else {
|
||||
"/"
|
||||
}
|
||||
}
|
||||
ScanCode::Left => "left",
|
||||
ScanCode::Up => "up",
|
||||
ScanCode::Right => "right",
|
||||
ScanCode::Down => "down",
|
||||
ScanCode::PageUp => "pageup",
|
||||
ScanCode::PageDown => "pagedown",
|
||||
ScanCode::End => "end",
|
||||
ScanCode::Home => "home",
|
||||
ScanCode::Tab => "tab",
|
||||
ScanCode::Enter => "enter",
|
||||
ScanCode::Escape => "escape",
|
||||
ScanCode::Space => "space",
|
||||
ScanCode::Backspace => "backspace",
|
||||
ScanCode::Delete => "delete",
|
||||
ScanCode::Insert => "insert",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
use anyhow::Context;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{Display, Write},
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{PlatformKeyboardMapper, ScanCode};
|
||||
|
||||
/// A keystroke and associated metadata generated by the platform
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
||||
@@ -93,7 +97,10 @@ impl Keystroke {
|
||||
/// key_char syntax is only used for generating test events,
|
||||
/// secondary means "cmd" on macOS and "ctrl" on other platforms
|
||||
/// when matching a key with an key_char set will be matched without it.
|
||||
pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
|
||||
pub fn parse(
|
||||
source: &str,
|
||||
keyboard_mapper: &dyn PlatformKeyboardMapper,
|
||||
) -> std::result::Result<Self, InvalidKeystrokeError> {
|
||||
let mut modifiers = Modifiers::none();
|
||||
let mut key = None;
|
||||
let mut key_char = None;
|
||||
@@ -184,9 +191,24 @@ impl Keystroke {
|
||||
}
|
||||
});
|
||||
|
||||
let key = key.ok_or_else(|| InvalidKeystrokeError {
|
||||
// Create error once for reuse
|
||||
let error = || InvalidKeystrokeError {
|
||||
keystroke: source.to_owned(),
|
||||
})?;
|
||||
};
|
||||
|
||||
let key = {
|
||||
let key = key.ok_or_else(error)?;
|
||||
if key.starts_with('[') && key.ends_with(']') {
|
||||
let scan_code = ScanCode::parse(&key).ok_or_else(error)?;
|
||||
keyboard_mapper
|
||||
.scan_code_to_key(scan_code, &mut modifiers)
|
||||
.context("Failed to convert scan code to key")
|
||||
.log_err()
|
||||
.ok_or_else(error)?
|
||||
} else {
|
||||
key
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Keystroke {
|
||||
modifiers,
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
use crate::PlatformKeyboardLayout;
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use collections::HashMap;
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use x11rb::{protocol::xkb::ConnectionExt, xcb_ffi::XCBConnection};
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use xkbcommon::xkb::{
|
||||
Keycode,
|
||||
x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION},
|
||||
};
|
||||
|
||||
use crate::{Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper, ScanCode};
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use crate::is_letter_key;
|
||||
|
||||
pub(crate) struct LinuxKeyboardLayout {
|
||||
id: String,
|
||||
@@ -19,3 +35,257 @@ impl LinuxKeyboardLayout {
|
||||
Self { id }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
pub(crate) struct LinuxKeyboardMapper {
|
||||
code_to_key: HashMap<Keycode, String>,
|
||||
code_to_shifted_key: HashMap<Keycode, String>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
impl PlatformKeyboardMapper for LinuxKeyboardMapper {
|
||||
fn scan_code_to_key(
|
||||
&self,
|
||||
scan_code: ScanCode,
|
||||
modifiers: &mut Modifiers,
|
||||
) -> anyhow::Result<String> {
|
||||
if let Some(key) = scan_code.try_to_key() {
|
||||
return Ok(key);
|
||||
}
|
||||
let native_scan_code = get_scan_code(scan_code)
|
||||
.map(Keycode::new)
|
||||
.ok_or_else(|| anyhow::anyhow!("Unsupported scan code: {:?}", scan_code))?;
|
||||
let key = self.code_to_key.get(&native_scan_code).ok_or_else(|| {
|
||||
anyhow::anyhow!("Key not found for scan code: {:?}", native_scan_code)
|
||||
})?;
|
||||
if modifiers.shift && !is_letter_key(key) {
|
||||
if let Some(key) = self.code_to_shifted_key.get(&native_scan_code) {
|
||||
modifiers.shift = false;
|
||||
return Ok(key.clone());
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Shifted key not found for scan code: {:?}",
|
||||
native_scan_code
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Ok(key.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
static XCB_CONNECTION: LazyLock<XCBConnection> =
|
||||
LazyLock::new(|| XCBConnection::connect(None).unwrap().0);
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
impl LinuxKeyboardMapper {
|
||||
pub(crate) fn new() -> Self {
|
||||
let _ = XCB_CONNECTION
|
||||
.xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION)
|
||||
.unwrap()
|
||||
.reply()
|
||||
.unwrap();
|
||||
let xkb_context = xkbcommon::xkb::Context::new(xkbcommon::xkb::CONTEXT_NO_FLAGS);
|
||||
let xkb_device_id = xkbcommon::xkb::x11::get_core_keyboard_device_id(&*XCB_CONNECTION);
|
||||
let xkb_state = {
|
||||
let xkb_keymap = xkbcommon::xkb::x11::keymap_new_from_device(
|
||||
&xkb_context,
|
||||
&*XCB_CONNECTION,
|
||||
xkb_device_id,
|
||||
xkbcommon::xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||
);
|
||||
xkbcommon::xkb::x11::state_new_from_device(&xkb_keymap, &*XCB_CONNECTION, xkb_device_id)
|
||||
};
|
||||
let mut code_to_key = HashMap::default();
|
||||
let mut code_to_shifted_key = HashMap::default();
|
||||
|
||||
let keymap = xkb_state.get_keymap();
|
||||
let mut shifted_state = xkbcommon::xkb::State::new(&keymap);
|
||||
|
||||
let shift_mod = keymap.mod_get_index(xkbcommon::xkb::MOD_NAME_SHIFT);
|
||||
let shift_mask = 1 << shift_mod;
|
||||
shifted_state.update_mask(shift_mask, 0, 0, 0, 0, 0);
|
||||
|
||||
for &scan_code in TYPEABLE_CODES {
|
||||
let keycode = Keycode::new(scan_code);
|
||||
let key = xkb_state.key_get_utf8(keycode);
|
||||
if !is_letter_key(&key) {
|
||||
let shifted_key = shifted_state.key_get_utf8(keycode);
|
||||
code_to_shifted_key.insert(keycode, shifted_key);
|
||||
}
|
||||
code_to_key.insert(keycode, key);
|
||||
}
|
||||
|
||||
Self {
|
||||
code_to_key,
|
||||
code_to_shifted_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All typeable scan codes for the standard US keyboard layout, ANSI104
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
const TYPEABLE_CODES: &[u32] = &[
|
||||
0x0026, // a
|
||||
0x0038, // b
|
||||
0x0036, // c
|
||||
0x0028, // d
|
||||
0x001a, // e
|
||||
0x0029, // f
|
||||
0x002a, // g
|
||||
0x002b, // h
|
||||
0x001f, // i
|
||||
0x002c, // j
|
||||
0x002d, // k
|
||||
0x002e, // l
|
||||
0x003a, // m
|
||||
0x0039, // n
|
||||
0x0020, // o
|
||||
0x0021, // p
|
||||
0x0018, // q
|
||||
0x001b, // r
|
||||
0x0027, // s
|
||||
0x001c, // t
|
||||
0x001e, // u
|
||||
0x0037, // v
|
||||
0x0019, // w
|
||||
0x0035, // x
|
||||
0x001d, // y
|
||||
0x0034, // z
|
||||
0x0013, // Digit 0
|
||||
0x000a, // Digit 1
|
||||
0x000b, // Digit 2
|
||||
0x000c, // Digit 3
|
||||
0x000d, // Digit 4
|
||||
0x000e, // Digit 5
|
||||
0x000f, // Digit 6
|
||||
0x0010, // Digit 7
|
||||
0x0011, // Digit 8
|
||||
0x0012, // Digit 9
|
||||
0x0031, // ` Backquote
|
||||
0x0014, // - Minus
|
||||
0x0015, // = Equal
|
||||
0x0022, // [ Left bracket
|
||||
0x0023, // ] Right bracket
|
||||
0x0033, // \ Backslash
|
||||
0x002f, // ; Semicolon
|
||||
0x0030, // ' Quote
|
||||
0x003b, // , Comma
|
||||
0x003c, // . Period
|
||||
0x003d, // / Slash
|
||||
];
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
fn get_scan_code(scan_code: ScanCode) -> Option<u32> {
|
||||
// https://github.com/microsoft/node-native-keymap/blob/main/deps/chromium/dom_code_data.inc
|
||||
Some(match scan_code {
|
||||
ScanCode::F1 => 0x0043,
|
||||
ScanCode::F2 => 0x0044,
|
||||
ScanCode::F3 => 0x0045,
|
||||
ScanCode::F4 => 0x0046,
|
||||
ScanCode::F5 => 0x0047,
|
||||
ScanCode::F6 => 0x0048,
|
||||
ScanCode::F7 => 0x0049,
|
||||
ScanCode::F8 => 0x004a,
|
||||
ScanCode::F9 => 0x004b,
|
||||
ScanCode::F10 => 0x004c,
|
||||
ScanCode::F11 => 0x005f,
|
||||
ScanCode::F12 => 0x0060,
|
||||
ScanCode::F13 => 0x00bf,
|
||||
ScanCode::F14 => 0x00c0,
|
||||
ScanCode::F15 => 0x00c1,
|
||||
ScanCode::F16 => 0x00c2,
|
||||
ScanCode::F17 => 0x00c3,
|
||||
ScanCode::F18 => 0x00c4,
|
||||
ScanCode::F19 => 0x00c5,
|
||||
ScanCode::F20 => 0x00c6,
|
||||
ScanCode::F21 => 0x00c7,
|
||||
ScanCode::F22 => 0x00c8,
|
||||
ScanCode::F23 => 0x00c9,
|
||||
ScanCode::F24 => 0x00ca,
|
||||
ScanCode::A => 0x0026,
|
||||
ScanCode::B => 0x0038,
|
||||
ScanCode::C => 0x0036,
|
||||
ScanCode::D => 0x0028,
|
||||
ScanCode::E => 0x001a,
|
||||
ScanCode::F => 0x0029,
|
||||
ScanCode::G => 0x002a,
|
||||
ScanCode::H => 0x002b,
|
||||
ScanCode::I => 0x001f,
|
||||
ScanCode::J => 0x002c,
|
||||
ScanCode::K => 0x002d,
|
||||
ScanCode::L => 0x002e,
|
||||
ScanCode::M => 0x003a,
|
||||
ScanCode::N => 0x0039,
|
||||
ScanCode::O => 0x0020,
|
||||
ScanCode::P => 0x0021,
|
||||
ScanCode::Q => 0x0018,
|
||||
ScanCode::R => 0x001b,
|
||||
ScanCode::S => 0x0027,
|
||||
ScanCode::T => 0x001c,
|
||||
ScanCode::U => 0x001e,
|
||||
ScanCode::V => 0x0037,
|
||||
ScanCode::W => 0x0019,
|
||||
ScanCode::X => 0x0035,
|
||||
ScanCode::Y => 0x001d,
|
||||
ScanCode::Z => 0x0034,
|
||||
ScanCode::Digit0 => 0x0013,
|
||||
ScanCode::Digit1 => 0x000a,
|
||||
ScanCode::Digit2 => 0x000b,
|
||||
ScanCode::Digit3 => 0x000c,
|
||||
ScanCode::Digit4 => 0x000d,
|
||||
ScanCode::Digit5 => 0x000e,
|
||||
ScanCode::Digit6 => 0x000f,
|
||||
ScanCode::Digit7 => 0x0010,
|
||||
ScanCode::Digit8 => 0x0011,
|
||||
ScanCode::Digit9 => 0x0012,
|
||||
ScanCode::Backquote => 0x0031,
|
||||
ScanCode::Minus => 0x0014,
|
||||
ScanCode::Equal => 0x0015,
|
||||
ScanCode::BracketLeft => 0x0022,
|
||||
ScanCode::BracketRight => 0x0023,
|
||||
ScanCode::Backslash => 0x0033,
|
||||
ScanCode::Semicolon => 0x002f,
|
||||
ScanCode::Quote => 0x0030,
|
||||
ScanCode::Comma => 0x003b,
|
||||
ScanCode::Period => 0x003c,
|
||||
ScanCode::Slash => 0x003d,
|
||||
ScanCode::Left => 0x0071,
|
||||
ScanCode::Up => 0x006f,
|
||||
ScanCode::Right => 0x0072,
|
||||
ScanCode::Down => 0x0074,
|
||||
ScanCode::PageUp => 0x0070,
|
||||
ScanCode::PageDown => 0x0075,
|
||||
ScanCode::End => 0x0073,
|
||||
ScanCode::Home => 0x006e,
|
||||
ScanCode::Tab => 0x0017,
|
||||
ScanCode::Enter => 0x0024,
|
||||
ScanCode::Escape => 0x0009,
|
||||
ScanCode::Space => 0x0041,
|
||||
ScanCode::Backspace => 0x0016,
|
||||
ScanCode::Delete => 0x0077,
|
||||
ScanCode::Insert => 0x0076,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "wayland", feature = "x11")))]
|
||||
pub(crate) struct LinuxKeyboardMapper;
|
||||
|
||||
#[cfg(not(any(feature = "wayland", feature = "x11")))]
|
||||
impl PlatformKeyboardMapper for LinuxKeyboardMapper {
|
||||
fn scan_code_to_key(
|
||||
&self,
|
||||
_scan_code: ScanCode,
|
||||
_modifiers: &mut Modifiers,
|
||||
) -> anyhow::Result<String> {
|
||||
Err(anyhow::anyhow!("LinuxKeyboardMapper not supported"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "wayland", feature = "x11")))]
|
||||
impl LinuxKeyboardMapper {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,9 @@ use xkbcommon::xkb::{self, Keycode, Keysym, State};
|
||||
use crate::{
|
||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||
ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
|
||||
Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow,
|
||||
Point, Result, ScreenCaptureSource, Task, WindowAppearance, WindowParams, px,
|
||||
Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper,
|
||||
PlatformTextSystem, PlatformWindow, Point, Result, ScreenCaptureSource, Task, WindowAppearance,
|
||||
WindowParams, px,
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
@@ -138,6 +139,10 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
self.with_common(|common| common.text_system.clone())
|
||||
}
|
||||
|
||||
fn keyboard_mapper(&self) -> Box<dyn PlatformKeyboardMapper> {
|
||||
Box::new(super::LinuxKeyboardMapper::new())
|
||||
}
|
||||
|
||||
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
|
||||
self.keyboard_layout()
|
||||
}
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
use crate::{
|
||||
KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
|
||||
MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels,
|
||||
PlatformInput, ScrollDelta, ScrollWheelEvent, TouchPhase,
|
||||
platform::mac::{
|
||||
LMGetKbdType, NSStringExt, TISCopyCurrentKeyboardLayoutInputSource,
|
||||
TISGetInputSourceProperty, UCKeyTranslate, kTISPropertyUnicodeKeyLayoutData,
|
||||
},
|
||||
point, px,
|
||||
CMD_MOD, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
|
||||
MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NO_MOD, NavigationDirection,
|
||||
OPTION_MOD, Pixels, PlatformInput, SHIFT_MOD, ScrollDelta, ScrollWheelEvent, TouchPhase,
|
||||
always_use_command_layout, chars_for_modified_key, platform::mac::NSStringExt, point, px,
|
||||
};
|
||||
use cocoa::{
|
||||
appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
|
||||
base::{YES, id},
|
||||
};
|
||||
use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
|
||||
use core_graphics::event::CGKeyCode;
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use std::{borrow::Cow, ffi::c_void};
|
||||
use std::borrow::Cow;
|
||||
|
||||
const BACKSPACE_KEY: u16 = 0x7f;
|
||||
const SPACE_KEY: u16 = b' ' as u16;
|
||||
@@ -452,80 +445,3 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn always_use_command_layout() -> bool {
|
||||
if chars_for_modified_key(0, NO_MOD).is_ascii() {
|
||||
return false;
|
||||
}
|
||||
|
||||
chars_for_modified_key(0, CMD_MOD).is_ascii()
|
||||
}
|
||||
|
||||
const NO_MOD: u32 = 0;
|
||||
const CMD_MOD: u32 = 1;
|
||||
const SHIFT_MOD: u32 = 2;
|
||||
const OPTION_MOD: u32 = 8;
|
||||
|
||||
fn chars_for_modified_key(code: CGKeyCode, modifiers: u32) -> String {
|
||||
// Values from: https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.6.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h#L126
|
||||
// shifted >> 8 for UCKeyTranslate
|
||||
const CG_SPACE_KEY: u16 = 49;
|
||||
// https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.6.sdk/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/Headers/UnicodeUtilities.h#L278
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kUCKeyActionDown: u16 = 0;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kUCKeyTranslateNoDeadKeysMask: u32 = 0;
|
||||
|
||||
let keyboard_type = unsafe { LMGetKbdType() as u32 };
|
||||
const BUFFER_SIZE: usize = 4;
|
||||
let mut dead_key_state = 0;
|
||||
let mut buffer: [u16; BUFFER_SIZE] = [0; BUFFER_SIZE];
|
||||
let mut buffer_size: usize = 0;
|
||||
|
||||
let keyboard = unsafe { TISCopyCurrentKeyboardLayoutInputSource() };
|
||||
if keyboard.is_null() {
|
||||
return "".to_string();
|
||||
}
|
||||
let layout_data = unsafe {
|
||||
TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData as *const c_void)
|
||||
as CFDataRef
|
||||
};
|
||||
if layout_data.is_null() {
|
||||
unsafe {
|
||||
let _: () = msg_send![keyboard, release];
|
||||
}
|
||||
return "".to_string();
|
||||
}
|
||||
let keyboard_layout = unsafe { CFDataGetBytePtr(layout_data) };
|
||||
|
||||
unsafe {
|
||||
UCKeyTranslate(
|
||||
keyboard_layout as *const c_void,
|
||||
code,
|
||||
kUCKeyActionDown,
|
||||
modifiers,
|
||||
keyboard_type,
|
||||
kUCKeyTranslateNoDeadKeysMask,
|
||||
&mut dead_key_state,
|
||||
BUFFER_SIZE,
|
||||
&mut buffer_size as *mut usize,
|
||||
&mut buffer as *mut u16,
|
||||
);
|
||||
if dead_key_state != 0 {
|
||||
UCKeyTranslate(
|
||||
keyboard_layout as *const c_void,
|
||||
CG_SPACE_KEY,
|
||||
kUCKeyActionDown,
|
||||
modifiers,
|
||||
keyboard_type,
|
||||
kUCKeyTranslateNoDeadKeysMask,
|
||||
&mut dead_key_state,
|
||||
BUFFER_SIZE,
|
||||
&mut buffer_size as *mut usize,
|
||||
&mut buffer as *mut u16,
|
||||
);
|
||||
}
|
||||
let _: () = msg_send![keyboard, release];
|
||||
}
|
||||
String::from_utf16(&buffer[..buffer_size]).unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
use std::ffi::{CStr, c_void};
|
||||
|
||||
use collections::HashMap;
|
||||
use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
|
||||
use core_graphics::event::CGKeyCode;
|
||||
use objc::{msg_send, runtime::Object, sel, sel_impl};
|
||||
|
||||
use crate::PlatformKeyboardLayout;
|
||||
use crate::{
|
||||
Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper, ScanCode, is_letter_key,
|
||||
platform::mac::{LMGetKbdType, UCKeyTranslate, kTISPropertyUnicodeKeyLayoutData},
|
||||
};
|
||||
|
||||
use super::{
|
||||
TISCopyCurrentKeyboardLayoutInputSource, TISGetInputSourceProperty, kTISPropertyInputSourceID,
|
||||
@@ -47,3 +53,300 @@ impl MacKeyboardLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MacKeyboardMapper {
|
||||
code_to_key: HashMap<u16, String>,
|
||||
code_to_shifted_key: HashMap<u16, String>,
|
||||
}
|
||||
|
||||
impl MacKeyboardMapper {
|
||||
pub(crate) fn new() -> Self {
|
||||
let mut code_to_key = HashMap::default();
|
||||
let mut code_to_shifted_key = HashMap::default();
|
||||
|
||||
let always_use_cmd_layout = always_use_command_layout();
|
||||
for &scan_code in TYPEABLE_CODES.iter() {
|
||||
let (key, shifted_key) = generate_key_pairs(scan_code, always_use_cmd_layout);
|
||||
if !is_letter_key(&key) {
|
||||
code_to_shifted_key.insert(scan_code, shifted_key);
|
||||
}
|
||||
code_to_key.insert(scan_code, key);
|
||||
}
|
||||
|
||||
Self {
|
||||
code_to_key,
|
||||
code_to_shifted_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformKeyboardMapper for MacKeyboardMapper {
|
||||
fn scan_code_to_key(
|
||||
&self,
|
||||
scan_code: ScanCode,
|
||||
modifiers: &mut Modifiers,
|
||||
) -> anyhow::Result<String> {
|
||||
if let Some(key) = scan_code.try_to_key() {
|
||||
return Ok(key);
|
||||
}
|
||||
let native_scan_code = get_scan_code(scan_code)
|
||||
.ok_or_else(|| anyhow::anyhow!("Unsupported scan code: {:?}", scan_code))?;
|
||||
let key = self.code_to_key.get(&native_scan_code).ok_or_else(|| {
|
||||
anyhow::anyhow!("Key not found for scan code: {:?}", native_scan_code)
|
||||
})?;
|
||||
if modifiers.shift && !is_letter_key(key) {
|
||||
if let Some(key) = self.code_to_shifted_key.get(&native_scan_code) {
|
||||
modifiers.shift = false;
|
||||
return Ok(key.clone());
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Shifted key not found for scan code: {:?}",
|
||||
native_scan_code
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Ok(key.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const NO_MOD: u32 = 0;
|
||||
pub(crate) const CMD_MOD: u32 = 1;
|
||||
pub(crate) const SHIFT_MOD: u32 = 2;
|
||||
pub(crate) const OPTION_MOD: u32 = 8;
|
||||
|
||||
pub(crate) fn chars_for_modified_key(code: CGKeyCode, modifiers: u32) -> String {
|
||||
// Values from: https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.6.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h#L126
|
||||
// shifted >> 8 for UCKeyTranslate
|
||||
const CG_SPACE_KEY: u16 = 49;
|
||||
// https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.6.sdk/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/Headers/UnicodeUtilities.h#L278
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kUCKeyActionDown: u16 = 0;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kUCKeyTranslateNoDeadKeysMask: u32 = 0;
|
||||
|
||||
let keyboard_type = unsafe { LMGetKbdType() as u32 };
|
||||
const BUFFER_SIZE: usize = 4;
|
||||
let mut dead_key_state = 0;
|
||||
let mut buffer: [u16; BUFFER_SIZE] = [0; BUFFER_SIZE];
|
||||
let mut buffer_size: usize = 0;
|
||||
|
||||
let keyboard = unsafe { TISCopyCurrentKeyboardLayoutInputSource() };
|
||||
if keyboard.is_null() {
|
||||
return "".to_string();
|
||||
}
|
||||
let layout_data = unsafe {
|
||||
TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData as *const c_void)
|
||||
as CFDataRef
|
||||
};
|
||||
if layout_data.is_null() {
|
||||
unsafe {
|
||||
let _: () = msg_send![keyboard, release];
|
||||
}
|
||||
return "".to_string();
|
||||
}
|
||||
let keyboard_layout = unsafe { CFDataGetBytePtr(layout_data) };
|
||||
|
||||
unsafe {
|
||||
UCKeyTranslate(
|
||||
keyboard_layout as *const c_void,
|
||||
code,
|
||||
kUCKeyActionDown,
|
||||
modifiers,
|
||||
keyboard_type,
|
||||
kUCKeyTranslateNoDeadKeysMask,
|
||||
&mut dead_key_state,
|
||||
BUFFER_SIZE,
|
||||
&mut buffer_size as *mut usize,
|
||||
&mut buffer as *mut u16,
|
||||
);
|
||||
if dead_key_state != 0 {
|
||||
UCKeyTranslate(
|
||||
keyboard_layout as *const c_void,
|
||||
CG_SPACE_KEY,
|
||||
kUCKeyActionDown,
|
||||
modifiers,
|
||||
keyboard_type,
|
||||
kUCKeyTranslateNoDeadKeysMask,
|
||||
&mut dead_key_state,
|
||||
BUFFER_SIZE,
|
||||
&mut buffer_size as *mut usize,
|
||||
&mut buffer as *mut u16,
|
||||
);
|
||||
}
|
||||
let _: () = msg_send![keyboard, release];
|
||||
}
|
||||
String::from_utf16(&buffer[..buffer_size]).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn always_use_command_layout() -> bool {
|
||||
if chars_for_modified_key(0, NO_MOD).is_ascii() {
|
||||
return false;
|
||||
}
|
||||
|
||||
chars_for_modified_key(0, CMD_MOD).is_ascii()
|
||||
}
|
||||
|
||||
fn generate_key_pairs(scan_code: u16, always_use_cmd_layout: bool) -> (String, String) {
|
||||
let mut chars_ignoring_modifiers = chars_for_modified_key(scan_code, NO_MOD);
|
||||
let mut chars_with_shift = chars_for_modified_key(scan_code, SHIFT_MOD);
|
||||
|
||||
// Handle Dvorak+QWERTY / Russian / Armenian
|
||||
if always_use_cmd_layout {
|
||||
let chars_with_cmd = chars_for_modified_key(scan_code, CMD_MOD);
|
||||
let chars_with_both = chars_for_modified_key(scan_code, CMD_MOD | SHIFT_MOD);
|
||||
|
||||
// We don't do this in the case that the shifted command key generates
|
||||
// the same character as the unshifted command key (Norwegian, e.g.)
|
||||
if chars_with_both != chars_with_cmd {
|
||||
chars_with_shift = chars_with_both;
|
||||
|
||||
// Handle edge-case where cmd-shift-s reports cmd-s instead of
|
||||
// cmd-shift-s (Ukrainian, etc.)
|
||||
} else if chars_with_cmd.to_ascii_uppercase() != chars_with_cmd {
|
||||
chars_with_shift = chars_with_cmd.to_ascii_uppercase();
|
||||
}
|
||||
chars_ignoring_modifiers = chars_with_cmd;
|
||||
}
|
||||
(chars_ignoring_modifiers, chars_with_shift)
|
||||
}
|
||||
|
||||
// All typeable scan codes for the standard US keyboard layout, ANSI104
|
||||
const TYPEABLE_CODES: &[u16] = &[
|
||||
0x0000, // a
|
||||
0x000b, // b
|
||||
0x0008, // c
|
||||
0x0002, // d
|
||||
0x000e, // e
|
||||
0x0003, // f
|
||||
0x0005, // g
|
||||
0x0004, // h
|
||||
0x0022, // i
|
||||
0x0026, // j
|
||||
0x0028, // k
|
||||
0x0025, // l
|
||||
0x002e, // m
|
||||
0x002d, // n
|
||||
0x001f, // o
|
||||
0x0023, // p
|
||||
0x000c, // q
|
||||
0x000f, // r
|
||||
0x0001, // s
|
||||
0x0011, // t
|
||||
0x0020, // u
|
||||
0x0009, // v
|
||||
0x000d, // w
|
||||
0x0007, // x
|
||||
0x0010, // y
|
||||
0x0006, // z
|
||||
0x001d, // Digit 0
|
||||
0x0012, // Digit 1
|
||||
0x0013, // Digit 2
|
||||
0x0014, // Digit 3
|
||||
0x0015, // Digit 4
|
||||
0x0017, // Digit 5
|
||||
0x0016, // Digit 6
|
||||
0x001a, // Digit 7
|
||||
0x001c, // Digit 8
|
||||
0x0019, // Digit 9
|
||||
0x0032, // ` Tilde
|
||||
0x001b, // - Minus
|
||||
0x0018, // = Equal
|
||||
0x0021, // [ Left bracket
|
||||
0x001e, // ] Right bracket
|
||||
0x002a, // \ Backslash
|
||||
0x0029, // ; Semicolon
|
||||
0x0027, // ' Quote
|
||||
0x002b, // , Comma
|
||||
0x002f, // . Period
|
||||
0x002c, // / Slash
|
||||
];
|
||||
|
||||
fn get_scan_code(scan_code: ScanCode) -> Option<u16> {
|
||||
// https://github.com/microsoft/node-native-keymap/blob/main/deps/chromium/dom_code_data.inc
|
||||
Some(match scan_code {
|
||||
ScanCode::F1 => 0x007a,
|
||||
ScanCode::F2 => 0x0078,
|
||||
ScanCode::F3 => 0x0063,
|
||||
ScanCode::F4 => 0x0076,
|
||||
ScanCode::F5 => 0x0060,
|
||||
ScanCode::F6 => 0x0061,
|
||||
ScanCode::F7 => 0x0062,
|
||||
ScanCode::F8 => 0x0064,
|
||||
ScanCode::F9 => 0x0065,
|
||||
ScanCode::F10 => 0x006d,
|
||||
ScanCode::F11 => 0x0067,
|
||||
ScanCode::F12 => 0x006f,
|
||||
ScanCode::F13 => 0x0069,
|
||||
ScanCode::F14 => 0x006b,
|
||||
ScanCode::F15 => 0x0071,
|
||||
ScanCode::F16 => 0x006a,
|
||||
ScanCode::F17 => 0x0040,
|
||||
ScanCode::F18 => 0x004f,
|
||||
ScanCode::F19 => 0x0050,
|
||||
ScanCode::F20 => 0x005a,
|
||||
ScanCode::F21 | ScanCode::F22 | ScanCode::F23 | ScanCode::F24 => return None,
|
||||
ScanCode::A => 0x0000,
|
||||
ScanCode::B => 0x000b,
|
||||
ScanCode::C => 0x0008,
|
||||
ScanCode::D => 0x0002,
|
||||
ScanCode::E => 0x000e,
|
||||
ScanCode::F => 0x0003,
|
||||
ScanCode::G => 0x0005,
|
||||
ScanCode::H => 0x0004,
|
||||
ScanCode::I => 0x0022,
|
||||
ScanCode::J => 0x0026,
|
||||
ScanCode::K => 0x0028,
|
||||
ScanCode::L => 0x0025,
|
||||
ScanCode::M => 0x002e,
|
||||
ScanCode::N => 0x002d,
|
||||
ScanCode::O => 0x001f,
|
||||
ScanCode::P => 0x0023,
|
||||
ScanCode::Q => 0x000c,
|
||||
ScanCode::R => 0x000f,
|
||||
ScanCode::S => 0x0001,
|
||||
ScanCode::T => 0x0011,
|
||||
ScanCode::U => 0x0020,
|
||||
ScanCode::V => 0x0009,
|
||||
ScanCode::W => 0x000d,
|
||||
ScanCode::X => 0x0007,
|
||||
ScanCode::Y => 0x0010,
|
||||
ScanCode::Z => 0x0006,
|
||||
ScanCode::Digit0 => 0x001d,
|
||||
ScanCode::Digit1 => 0x0012,
|
||||
ScanCode::Digit2 => 0x0013,
|
||||
ScanCode::Digit3 => 0x0014,
|
||||
ScanCode::Digit4 => 0x0015,
|
||||
ScanCode::Digit5 => 0x0017,
|
||||
ScanCode::Digit6 => 0x0016,
|
||||
ScanCode::Digit7 => 0x001a,
|
||||
ScanCode::Digit8 => 0x001c,
|
||||
ScanCode::Digit9 => 0x0019,
|
||||
ScanCode::Backquote => 0x0032,
|
||||
ScanCode::Minus => 0x001b,
|
||||
ScanCode::Equal => 0x0018,
|
||||
ScanCode::BracketLeft => 0x0021,
|
||||
ScanCode::BracketRight => 0x001e,
|
||||
ScanCode::Backslash => 0x002a,
|
||||
ScanCode::Semicolon => 0x0029,
|
||||
ScanCode::Quote => 0x0027,
|
||||
ScanCode::Comma => 0x002b,
|
||||
ScanCode::Period => 0x002f,
|
||||
ScanCode::Slash => 0x002c,
|
||||
ScanCode::Left => 0x007b,
|
||||
ScanCode::Up => 0x007e,
|
||||
ScanCode::Right => 0x007c,
|
||||
ScanCode::Down => 0x007d,
|
||||
ScanCode::PageUp => 0x0074,
|
||||
ScanCode::PageDown => 0x0079,
|
||||
ScanCode::End => 0x0077,
|
||||
ScanCode::Home => 0x0073,
|
||||
ScanCode::Tab => 0x0030,
|
||||
ScanCode::Enter => 0x0024,
|
||||
ScanCode::Escape => 0x0035,
|
||||
ScanCode::Space => 0x0031,
|
||||
ScanCode::Backspace => 0x0033,
|
||||
ScanCode::Delete => 0x0075,
|
||||
ScanCode::Insert => 0x0072,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ use super::{
|
||||
use crate::{
|
||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString,
|
||||
CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher,
|
||||
MacDisplay, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
|
||||
PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, ScreenCaptureSource,
|
||||
SemanticVersion, Task, WindowAppearance, WindowParams, hash,
|
||||
MacDisplay, MacKeyboardMapper, MacWindow, Menu, MenuItem, PathPromptOptions, Platform,
|
||||
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
|
||||
PlatformWindow, Result, ScreenCaptureSource, SemanticVersion, Task, WindowAppearance,
|
||||
WindowParams, hash,
|
||||
};
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use block::ConcreteBlock;
|
||||
@@ -846,6 +847,10 @@ impl Platform for MacPlatform {
|
||||
self.0.lock().validate_menu_command = Some(callback);
|
||||
}
|
||||
|
||||
fn keyboard_mapper(&self) -> Box<dyn PlatformKeyboardMapper> {
|
||||
Box::new(MacKeyboardMapper::new())
|
||||
}
|
||||
|
||||
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
|
||||
Box::new(MacKeyboardLayout::new())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::{
|
||||
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels,
|
||||
ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout,
|
||||
PlatformTextSystem, PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,
|
||||
Size, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams, size,
|
||||
PlatformKeyboardMapper, PlatformTextSystem, PromptButton, ScreenCaptureFrame,
|
||||
ScreenCaptureSource, ScreenCaptureStream, Size, Task, TestDisplay, TestKeyboardMapper,
|
||||
TestWindow, WindowAppearance, WindowParams, size,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::VecDeque;
|
||||
@@ -223,6 +224,10 @@ impl Platform for TestPlatform {
|
||||
self.text_system.clone()
|
||||
}
|
||||
|
||||
fn keyboard_mapper(&self) -> Box<dyn PlatformKeyboardMapper> {
|
||||
Box::new(TestKeyboardMapper::new())
|
||||
}
|
||||
|
||||
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
|
||||
Box::new(TestKeyboardLayout)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use windows::Win32::UI::{
|
||||
Input::KeyboardAndMouse::{
|
||||
GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MapVirtualKeyW, ToUnicode, VIRTUAL_KEY, VK_0,
|
||||
VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_ABNT_C1, VK_CONTROL, VK_MENU,
|
||||
VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_102,
|
||||
VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT,
|
||||
GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MAPVK_VSC_TO_VK, MapVirtualKeyW, ToUnicode,
|
||||
VIRTUAL_KEY, VK_0, VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_ABNT_C1,
|
||||
VK_CONTROL, VK_MENU, VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7,
|
||||
VK_OEM_8, VK_OEM_102, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT,
|
||||
},
|
||||
WindowsAndMessaging::KL_NAMELENGTH,
|
||||
};
|
||||
use windows_core::HSTRING;
|
||||
|
||||
use crate::{Modifiers, PlatformKeyboardLayout};
|
||||
use crate::{Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper, ScanCode};
|
||||
|
||||
pub(crate) struct WindowsKeyboardLayout {
|
||||
id: String,
|
||||
@@ -48,6 +48,29 @@ impl WindowsKeyboardLayout {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WindowsKeyboardMapper;
|
||||
|
||||
impl PlatformKeyboardMapper for WindowsKeyboardMapper {
|
||||
fn scan_code_to_key(&self, scan_code: ScanCode, modifiers: &mut Modifiers) -> Result<String> {
|
||||
if let Some(key) = scan_code.try_to_key() {
|
||||
return Ok(key);
|
||||
}
|
||||
let (win_scan_code, vkey) = get_virtual_key_from_scan_code(scan_code)?;
|
||||
get_keystroke_key(vkey, win_scan_code, modifiers).with_context(|| {
|
||||
format!(
|
||||
"Failed to get key from scan code: {:?}, vkey: {:?}",
|
||||
scan_code, vkey
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsKeyboardMapper {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_keystroke_key(
|
||||
vkey: VIRTUAL_KEY,
|
||||
scan_code: u32,
|
||||
@@ -82,15 +105,15 @@ fn need_to_convert_to_shifted_key(vkey: VIRTUAL_KEY) -> bool {
|
||||
| VK_OEM_MINUS
|
||||
| VK_OEM_PLUS
|
||||
| VK_OEM_4
|
||||
| VK_OEM_5
|
||||
| VK_OEM_6
|
||||
| VK_OEM_5
|
||||
| VK_OEM_1
|
||||
| VK_OEM_7
|
||||
| VK_OEM_COMMA
|
||||
| VK_OEM_PERIOD
|
||||
| VK_OEM_2
|
||||
| VK_OEM_102
|
||||
| VK_OEM_8
|
||||
| VK_OEM_8 // Same as VK_OEM_2
|
||||
| VK_ABNT_C1
|
||||
| VK_0
|
||||
| VK_1
|
||||
@@ -138,3 +161,66 @@ pub(crate) fn generate_key_char(
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_virtual_key_from_scan_code(gpui_scan_code: ScanCode) -> Result<(u32, VIRTUAL_KEY)> {
|
||||
// https://github.com/microsoft/node-native-keymap/blob/main/deps/chromium/dom_code_data.inc
|
||||
let scan_code = match gpui_scan_code {
|
||||
ScanCode::A => 0x001e,
|
||||
ScanCode::B => 0x0030,
|
||||
ScanCode::C => 0x002e,
|
||||
ScanCode::D => 0x0020,
|
||||
ScanCode::E => 0x0012,
|
||||
ScanCode::F => 0x0021,
|
||||
ScanCode::G => 0x0022,
|
||||
ScanCode::H => 0x0023,
|
||||
ScanCode::I => 0x0017,
|
||||
ScanCode::J => 0x0024,
|
||||
ScanCode::K => 0x0025,
|
||||
ScanCode::L => 0x0026,
|
||||
ScanCode::M => 0x0032,
|
||||
ScanCode::N => 0x0031,
|
||||
ScanCode::O => 0x0018,
|
||||
ScanCode::P => 0x0019,
|
||||
ScanCode::Q => 0x0010,
|
||||
ScanCode::R => 0x0013,
|
||||
ScanCode::S => 0x001f,
|
||||
ScanCode::T => 0x0014,
|
||||
ScanCode::U => 0x0016,
|
||||
ScanCode::V => 0x002f,
|
||||
ScanCode::W => 0x0011,
|
||||
ScanCode::X => 0x002d,
|
||||
ScanCode::Y => 0x0015,
|
||||
ScanCode::Z => 0x002c,
|
||||
ScanCode::Digit0 => 0x000b,
|
||||
ScanCode::Digit1 => 0x0002,
|
||||
ScanCode::Digit2 => 0x0003,
|
||||
ScanCode::Digit3 => 0x0004,
|
||||
ScanCode::Digit4 => 0x0005,
|
||||
ScanCode::Digit5 => 0x0006,
|
||||
ScanCode::Digit6 => 0x0007,
|
||||
ScanCode::Digit7 => 0x0008,
|
||||
ScanCode::Digit8 => 0x0009,
|
||||
ScanCode::Digit9 => 0x000a,
|
||||
ScanCode::Backquote => 0x0029,
|
||||
ScanCode::Minus => 0x000c,
|
||||
ScanCode::Equal => 0x000d,
|
||||
ScanCode::BracketLeft => 0x001a,
|
||||
ScanCode::BracketRight => 0x001b,
|
||||
ScanCode::Backslash => 0x002b,
|
||||
ScanCode::Semicolon => 0x0027,
|
||||
ScanCode::Quote => 0x0028,
|
||||
ScanCode::Comma => 0x0033,
|
||||
ScanCode::Period => 0x0034,
|
||||
ScanCode::Slash => 0x0035,
|
||||
_ => anyhow::bail!("Unsupported scan code: {:?}", gpui_scan_code),
|
||||
};
|
||||
let virtual_key = unsafe { MapVirtualKeyW(scan_code, MAPVK_VSC_TO_VK) };
|
||||
if virtual_key == 0 {
|
||||
anyhow::bail!(
|
||||
"Failed to get virtual key from scan code: {:?}, {}",
|
||||
gpui_scan_code,
|
||||
scan_code
|
||||
);
|
||||
}
|
||||
Ok((scan_code, VIRTUAL_KEY(virtual_key as u16)))
|
||||
}
|
||||
|
||||
@@ -310,6 +310,10 @@ impl Platform for WindowsPlatform {
|
||||
self.text_system.clone()
|
||||
}
|
||||
|
||||
fn keyboard_mapper(&self) -> Box<dyn PlatformKeyboardMapper> {
|
||||
Box::new(WindowsKeyboardMapper::new())
|
||||
}
|
||||
|
||||
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
|
||||
Box::new(
|
||||
WindowsKeyboardLayout::new()
|
||||
|
||||
@@ -387,7 +387,13 @@ impl KeymapFile {
|
||||
},
|
||||
};
|
||||
|
||||
let key_binding = match KeyBinding::load(keystrokes, action, context, key_equivalents) {
|
||||
let key_binding = match KeyBinding::load(
|
||||
keystrokes,
|
||||
action,
|
||||
context,
|
||||
key_equivalents,
|
||||
cx.keyboard_mapper(),
|
||||
) {
|
||||
Ok(key_binding) => key_binding,
|
||||
Err(InvalidKeystrokeError { keystroke }) => {
|
||||
return Err(format!(
|
||||
|
||||
@@ -270,10 +270,15 @@ mod test {
|
||||
fn test_scroll_keys() {
|
||||
//These keys should be handled by the scrolling element directly
|
||||
//Need to signify this by returning 'None'
|
||||
let shift_pageup = Keystroke::parse("shift-pageup").unwrap();
|
||||
let shift_pagedown = Keystroke::parse("shift-pagedown").unwrap();
|
||||
let shift_home = Keystroke::parse("shift-home").unwrap();
|
||||
let shift_end = Keystroke::parse("shift-end").unwrap();
|
||||
let shift_key = |key: &str| Keystroke {
|
||||
modifiers: Modifiers::shift(),
|
||||
key: key.to_owned(),
|
||||
key_char: None,
|
||||
};
|
||||
let shift_pageup = shift_key("pageup");
|
||||
let shift_pagedown = shift_key("pagedown");
|
||||
let shift_home = shift_key("home");
|
||||
let shift_end = shift_key("end");
|
||||
|
||||
let none = TermMode::NONE;
|
||||
assert_eq!(to_esc_str(&shift_pageup, &none, false), None);
|
||||
@@ -299,8 +304,13 @@ mod test {
|
||||
Some("\x1b[1;2F".into())
|
||||
);
|
||||
|
||||
let pageup = Keystroke::parse("pageup").unwrap();
|
||||
let pagedown = Keystroke::parse("pagedown").unwrap();
|
||||
let normal_key = |key: &str| Keystroke {
|
||||
modifiers: crate::Modifiers::none(),
|
||||
key: key.to_owned(),
|
||||
key_char: None,
|
||||
};
|
||||
let pageup = normal_key("pageup");
|
||||
let pagedown = normal_key("pagedown");
|
||||
let any = TermMode::ANY;
|
||||
|
||||
assert_eq!(to_esc_str(&pageup, &any, false), Some("\x1b[5~".into()));
|
||||
@@ -328,10 +338,15 @@ mod test {
|
||||
let app_cursor = TermMode::APP_CURSOR;
|
||||
let none = TermMode::NONE;
|
||||
|
||||
let up = Keystroke::parse("up").unwrap();
|
||||
let down = Keystroke::parse("down").unwrap();
|
||||
let left = Keystroke::parse("left").unwrap();
|
||||
let right = Keystroke::parse("right").unwrap();
|
||||
let generate_keystroke = |key: &str| Keystroke {
|
||||
modifiers: Modifiers::none(),
|
||||
key: key.to_owned(),
|
||||
key_char: None,
|
||||
};
|
||||
let up = generate_keystroke("up");
|
||||
let down = generate_keystroke("down");
|
||||
let left = generate_keystroke("left");
|
||||
let right = generate_keystroke("right");
|
||||
|
||||
assert_eq!(to_esc_str(&up, &none, false), Some("\x1b[A".into()));
|
||||
assert_eq!(to_esc_str(&down, &none, false), Some("\x1b[B".into()));
|
||||
@@ -356,12 +371,20 @@ mod test {
|
||||
for (lower, upper) in letters_lower.zip(letters_upper) {
|
||||
assert_eq!(
|
||||
to_esc_str(
|
||||
&Keystroke::parse(&format!("ctrl-shift-{}", lower)).unwrap(),
|
||||
&Keystroke {
|
||||
modifiers: Modifiers::control_shift(),
|
||||
key: lower.to_string(),
|
||||
key_char: None,
|
||||
},
|
||||
&mode,
|
||||
false
|
||||
),
|
||||
to_esc_str(
|
||||
&Keystroke::parse(&format!("ctrl-{}", upper)).unwrap(),
|
||||
&Keystroke {
|
||||
modifiers: Modifiers::control(),
|
||||
key: upper.to_string(),
|
||||
key_char: None,
|
||||
},
|
||||
&mode,
|
||||
false
|
||||
),
|
||||
@@ -378,7 +401,11 @@ mod test {
|
||||
for character in ascii_printable {
|
||||
assert_eq!(
|
||||
to_esc_str(
|
||||
&Keystroke::parse(&format!("alt-{}", character)).unwrap(),
|
||||
&Keystroke {
|
||||
modifiers: Modifiers::alt(),
|
||||
key: character.to_string(),
|
||||
key_char: None,
|
||||
},
|
||||
&TermMode::NONE,
|
||||
true
|
||||
)
|
||||
@@ -396,7 +423,11 @@ mod test {
|
||||
for key in gpui_keys {
|
||||
assert_ne!(
|
||||
to_esc_str(
|
||||
&Keystroke::parse(&format!("alt-{}", key)).unwrap(),
|
||||
&Keystroke {
|
||||
modifiers: Modifiers::alt(),
|
||||
key: key.to_string(),
|
||||
key_char: None,
|
||||
},
|
||||
&TermMode::NONE,
|
||||
true
|
||||
)
|
||||
@@ -419,15 +450,78 @@ mod test {
|
||||
// 8 | Shift + Alt + Control
|
||||
// ---------+---------------------------
|
||||
// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
|
||||
assert_eq!(2, modifier_code(&Keystroke::parse("shift-a").unwrap()));
|
||||
assert_eq!(3, modifier_code(&Keystroke::parse("alt-a").unwrap()));
|
||||
assert_eq!(4, modifier_code(&Keystroke::parse("shift-alt-a").unwrap()));
|
||||
assert_eq!(5, modifier_code(&Keystroke::parse("ctrl-a").unwrap()));
|
||||
assert_eq!(6, modifier_code(&Keystroke::parse("shift-ctrl-a").unwrap()));
|
||||
assert_eq!(7, modifier_code(&Keystroke::parse("alt-ctrl-a").unwrap()));
|
||||
assert_eq!(
|
||||
2,
|
||||
modifier_code(&Keystroke {
|
||||
modifiers: Modifiers::shift(),
|
||||
key: "a".into(),
|
||||
key_char: None
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
3,
|
||||
modifier_code(&Keystroke {
|
||||
modifiers: Modifiers::alt(),
|
||||
key: "a".into(),
|
||||
key_char: None
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
4,
|
||||
modifier_code(&Keystroke {
|
||||
modifiers: Modifiers {
|
||||
shift: true,
|
||||
alt: true,
|
||||
..Default::default()
|
||||
},
|
||||
key: "a".into(),
|
||||
key_char: None
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
5,
|
||||
modifier_code(&Keystroke {
|
||||
modifiers: Modifiers::control(),
|
||||
key: "a".into(),
|
||||
key_char: None
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
6,
|
||||
modifier_code(&Keystroke {
|
||||
modifiers: Modifiers {
|
||||
shift: true,
|
||||
control: true,
|
||||
..Default::default()
|
||||
},
|
||||
key: "a".into(),
|
||||
key_char: None
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
7,
|
||||
modifier_code(&Keystroke {
|
||||
modifiers: Modifiers {
|
||||
alt: true,
|
||||
control: true,
|
||||
..Default::default()
|
||||
},
|
||||
key: "a".into(),
|
||||
key_char: None
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
8,
|
||||
modifier_code(&Keystroke::parse("shift-ctrl-alt-a").unwrap())
|
||||
modifier_code(&Keystroke {
|
||||
modifiers: Modifiers {
|
||||
shift: true,
|
||||
control: true,
|
||||
alt: true,
|
||||
..Default::default()
|
||||
},
|
||||
key: "a".into(),
|
||||
key_char: None
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ use assistant_slash_command::SlashCommandRegistry;
|
||||
use editor::{Editor, EditorSettings, actions::SelectAll, scroll::ScrollbarAutoHide};
|
||||
use gpui::{
|
||||
AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, KeyContext,
|
||||
KeyDownEvent, Keystroke, MouseButton, MouseDownEvent, Pixels, Render, ScrollWheelEvent,
|
||||
Stateful, Styled, Subscription, Task, WeakEntity, actions, anchored, deferred, div,
|
||||
impl_actions,
|
||||
KeyDownEvent, Keystroke, Modifiers, MouseButton, MouseDownEvent, Pixels, Render,
|
||||
ScrollWheelEvent, Stateful, Styled, Subscription, Task, WeakEntity, actions, anchored,
|
||||
deferred, div, impl_actions,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use persistence::TERMINAL_DB;
|
||||
@@ -450,7 +450,15 @@ impl TerminalView {
|
||||
{
|
||||
self.terminal.update(cx, |term, cx| {
|
||||
term.try_keystroke(
|
||||
&Keystroke::parse("ctrl-cmd-space").unwrap(),
|
||||
&Keystroke {
|
||||
modifiers: Modifiers {
|
||||
control: true,
|
||||
platform: true,
|
||||
..Default::default()
|
||||
},
|
||||
key: "space".to_owned(),
|
||||
key_char: None,
|
||||
},
|
||||
TerminalSettings::get_global(cx).option_as_meta,
|
||||
)
|
||||
});
|
||||
@@ -726,7 +734,7 @@ impl TerminalView {
|
||||
}
|
||||
|
||||
fn send_keystroke(&mut self, text: &SendKeystroke, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(keystroke) = Keystroke::parse(&text.0).log_err() {
|
||||
if let Some(keystroke) = Keystroke::parse(&text.0, cx.keyboard_mapper()).log_err() {
|
||||
self.clear_bell(cx);
|
||||
self.terminal.update(cx, |term, cx| {
|
||||
let processed =
|
||||
|
||||
@@ -533,78 +533,61 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_text_for_keystroke() {
|
||||
let cmd_keystroke = |key: &str| Keystroke {
|
||||
modifiers: Modifiers::command(),
|
||||
key: key.to_owned(),
|
||||
key_char: None,
|
||||
};
|
||||
assert_eq!(
|
||||
keystroke_text(
|
||||
&Keystroke::parse("cmd-c").unwrap(),
|
||||
PlatformStyle::Mac,
|
||||
false
|
||||
),
|
||||
keystroke_text(&cmd_keystroke("c"), PlatformStyle::Mac, false),
|
||||
"Command-C".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
keystroke_text(
|
||||
&Keystroke::parse("cmd-c").unwrap(),
|
||||
PlatformStyle::Linux,
|
||||
false
|
||||
),
|
||||
keystroke_text(&cmd_keystroke("c"), PlatformStyle::Linux, false),
|
||||
"Super-C".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
keystroke_text(
|
||||
&Keystroke::parse("cmd-c").unwrap(),
|
||||
PlatformStyle::Windows,
|
||||
false
|
||||
),
|
||||
keystroke_text(&cmd_keystroke("c"), PlatformStyle::Windows, false),
|
||||
"Win-C".to_string()
|
||||
);
|
||||
|
||||
let ctrl_alt_keystroke = |key: &str| Keystroke {
|
||||
modifiers: Modifiers {
|
||||
control: true,
|
||||
alt: true,
|
||||
..Default::default()
|
||||
},
|
||||
key: key.to_owned(),
|
||||
key_char: None,
|
||||
};
|
||||
assert_eq!(
|
||||
keystroke_text(
|
||||
&Keystroke::parse("ctrl-alt-delete").unwrap(),
|
||||
PlatformStyle::Mac,
|
||||
false
|
||||
),
|
||||
keystroke_text(&ctrl_alt_keystroke("delete"), PlatformStyle::Mac, false),
|
||||
"Control-Option-Delete".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
keystroke_text(
|
||||
&Keystroke::parse("ctrl-alt-delete").unwrap(),
|
||||
PlatformStyle::Linux,
|
||||
false
|
||||
),
|
||||
keystroke_text(&ctrl_alt_keystroke("delete"), PlatformStyle::Linux, false),
|
||||
"Ctrl-Alt-Delete".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
keystroke_text(
|
||||
&Keystroke::parse("ctrl-alt-delete").unwrap(),
|
||||
PlatformStyle::Windows,
|
||||
false
|
||||
),
|
||||
keystroke_text(&ctrl_alt_keystroke("delete"), PlatformStyle::Windows, false),
|
||||
"Ctrl-Alt-Delete".to_string()
|
||||
);
|
||||
|
||||
let shift_keystroke = |key: &str| Keystroke {
|
||||
modifiers: Modifiers::shift(),
|
||||
key: key.to_owned(),
|
||||
key_char: None,
|
||||
};
|
||||
assert_eq!(
|
||||
keystroke_text(
|
||||
&Keystroke::parse("shift-pageup").unwrap(),
|
||||
PlatformStyle::Mac,
|
||||
false
|
||||
),
|
||||
keystroke_text(&shift_keystroke("pageup"), PlatformStyle::Mac, false),
|
||||
"Shift-PageUp".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
keystroke_text(
|
||||
&Keystroke::parse("shift-pageup").unwrap(),
|
||||
PlatformStyle::Linux,
|
||||
false,
|
||||
),
|
||||
keystroke_text(&shift_keystroke("pageup"), PlatformStyle::Linux, false,),
|
||||
"Shift-PageUp".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
keystroke_text(
|
||||
&Keystroke::parse("shift-pageup").unwrap(),
|
||||
PlatformStyle::Windows,
|
||||
false
|
||||
),
|
||||
keystroke_text(&shift_keystroke("pageup"), PlatformStyle::Windows, false),
|
||||
"Shift-PageUp".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ impl Vim {
|
||||
fn literal(&mut self, action: &Literal, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(Operator::Literal { prefix }) = self.active_operator() {
|
||||
if let Some(prefix) = prefix {
|
||||
if let Some(keystroke) = Keystroke::parse(&action.0).ok() {
|
||||
if let Some(keystroke) = Keystroke::parse(&action.0, cx.keyboard_mapper()).ok() {
|
||||
window.defer(cx, |window, cx| {
|
||||
window.dispatch_keystroke(keystroke, cx);
|
||||
});
|
||||
|
||||
@@ -222,7 +222,15 @@ impl NeovimBackedTestContext {
|
||||
pub async fn simulate_shared_keystrokes(&mut self, keystroke_texts: &str) {
|
||||
for keystroke_text in keystroke_texts.split(' ') {
|
||||
self.recent_keystrokes.push(keystroke_text.to_string());
|
||||
#[cfg(not(feature = "neovim"))]
|
||||
self.neovim.send_keystroke(keystroke_text).await;
|
||||
#[cfg(feature = "neovim")]
|
||||
{
|
||||
let keyboard_mapper = self.cx.keyboard_mapper();
|
||||
self.neovim
|
||||
.send_keystroke(keystroke_text, keyboard_mapper.as_ref())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
self.simulate_keystrokes(keystroke_texts);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use async_compat::Compat;
|
||||
#[cfg(feature = "neovim")]
|
||||
use async_trait::async_trait;
|
||||
#[cfg(feature = "neovim")]
|
||||
use gpui::Keystroke;
|
||||
use gpui::{Keystroke, PlatformKeyboardMapper};
|
||||
|
||||
#[cfg(feature = "neovim")]
|
||||
use language::Point;
|
||||
@@ -110,8 +110,12 @@ impl NeovimConnection {
|
||||
|
||||
// Sends a keystroke to the neovim process.
|
||||
#[cfg(feature = "neovim")]
|
||||
pub async fn send_keystroke(&mut self, keystroke_text: &str) {
|
||||
let mut keystroke = Keystroke::parse(keystroke_text).unwrap();
|
||||
pub async fn send_keystroke(
|
||||
&mut self,
|
||||
keystroke_text: &str,
|
||||
keyboard_mapper: &dyn PlatformKeyboardMapper,
|
||||
) {
|
||||
let mut keystroke = Keystroke::parse(keystroke_text, keyboard_mapper).unwrap();
|
||||
|
||||
if keystroke.key == "<" {
|
||||
keystroke.key = "lt".to_string()
|
||||
|
||||
@@ -2159,10 +2159,11 @@ impl Workspace {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
let keyboard_mapper = cx.keyboard_mapper();
|
||||
let mut keystrokes: Vec<Keystroke> = action
|
||||
.0
|
||||
.split(' ')
|
||||
.flat_map(|k| Keystroke::parse(k).log_err())
|
||||
.flat_map(|k| Keystroke::parse(k, keyboard_mapper).log_err())
|
||||
.collect();
|
||||
keystrokes.reverse();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user