add keyboard mapper
This commit is contained in:
parent
ca13d025ed
commit
1d088ecebe
6 changed files with 156 additions and 16 deletions
|
@ -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,
|
||||
SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance,
|
||||
WindowHandle, WindowId, WindowInvalidator,
|
||||
PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, PromptBuilder,
|
||||
PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle,
|
||||
Reservation, ScreenCaptureSource, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem,
|
||||
Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator,
|
||||
colors::{Colors, GlobalColors},
|
||||
current_platform, hash, init_app_menus,
|
||||
};
|
||||
|
@ -263,6 +263,7 @@ pub struct App {
|
|||
pub(crate) focus_handles: Arc<FocusMap>,
|
||||
pub(crate) keymap: Rc<RefCell<Keymap>>,
|
||||
pub(crate) keyboard_layout: Box<dyn PlatformKeyboardLayout>,
|
||||
pub(crate) keyboard_mapper: Rc<dyn PlatformKeyboardMapper>,
|
||||
pub(crate) global_action_listeners:
|
||||
FxHashMap<TypeId, Vec<Rc<dyn Fn(&dyn Any, DispatchPhase, &mut Self)>>>,
|
||||
pending_effects: VecDeque<Effect>,
|
||||
|
@ -312,6 +313,7 @@ impl App {
|
|||
let text_system = Arc::new(TextSystem::new(platform.text_system()));
|
||||
let entities = EntityMap::new();
|
||||
let keyboard_layout = platform.keyboard_layout();
|
||||
let keyboard_mapper = platform.keyboard_mapper();
|
||||
|
||||
let app = Rc::new_cyclic(|this| AppCell {
|
||||
app: RefCell::new(App {
|
||||
|
@ -337,6 +339,7 @@ impl App {
|
|||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
keymap: Rc::new(RefCell::new(Keymap::default())),
|
||||
keyboard_layout,
|
||||
keyboard_mapper,
|
||||
global_action_listeners: FxHashMap::default(),
|
||||
pending_effects: VecDeque::new(),
|
||||
pending_notifications: FxHashSet::default(),
|
||||
|
@ -376,6 +379,7 @@ impl App {
|
|||
if let Some(app) = app.upgrade() {
|
||||
let cx = &mut app.borrow_mut();
|
||||
cx.keyboard_layout = cx.platform.keyboard_layout();
|
||||
cx.keyboard_mapper = cx.platform.keyboard_mapper();
|
||||
cx.keyboard_layout_observers
|
||||
.clone()
|
||||
.retain(&(), move |callback| (callback)(cx));
|
||||
|
@ -424,6 +428,11 @@ impl App {
|
|||
self.keyboard_layout.as_ref()
|
||||
}
|
||||
|
||||
/// Get the current keyboard mapper.
|
||||
pub fn keyboard_mapper(&self) -> &dyn PlatformKeyboardMapper {
|
||||
self.keyboard_mapper.as_ref()
|
||||
}
|
||||
|
||||
/// Invokes a handler when the current keyboard layout changes
|
||||
pub fn on_keyboard_layout_change<F>(&self, mut callback: F) -> Subscription
|
||||
where
|
||||
|
|
|
@ -231,7 +231,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>> {
|
||||
|
@ -251,7 +250,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 {
|
||||
""
|
||||
|
@ -272,6 +270,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_layout(&self) -> Box<dyn PlatformKeyboardLayout>;
|
||||
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper>;
|
||||
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,5 @@
|
|||
use crate::{KeybindingKeystroke, Keystroke};
|
||||
|
||||
/// A trait for platform-specific keyboard layouts
|
||||
pub trait PlatformKeyboardLayout {
|
||||
/// Get the keyboard layout ID, which should be unique to the layout
|
||||
|
@ -5,3 +7,9 @@ pub trait PlatformKeyboardLayout {
|
|||
/// Get the keyboard layout display name
|
||||
fn name(&self) -> &str;
|
||||
}
|
||||
|
||||
/// A trait for platform-specific keyboard mappings
|
||||
pub trait PlatformKeyboardMapper {
|
||||
/// Map a key equivalent to its platform-specific representation
|
||||
fn map_key_equivalent(&self, keystroke: Keystroke) -> KeybindingKeystroke;
|
||||
}
|
||||
|
|
|
@ -300,13 +300,11 @@ impl Keystroke {
|
|||
|
||||
impl KeybindingKeystroke {
|
||||
/// Create a new keybinding keystroke from the given keystroke
|
||||
pub fn new(keystroke: Keystroke) -> Self {
|
||||
let (key, modifiers) = to_unshifted_key(&keystroke.key, &keystroke.modifiers);
|
||||
let inner = keystroke.into_shifted();
|
||||
pub fn new(inner: Keystroke) -> Self {
|
||||
KeybindingKeystroke {
|
||||
inner,
|
||||
key,
|
||||
modifiers,
|
||||
modifiers: inner.modifiers,
|
||||
key: inner.key.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,28 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::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_VK_TO_VSC, 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::{
|
||||
KeybindingKeystroke, Keystroke, Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper,
|
||||
};
|
||||
|
||||
pub(crate) struct WindowsKeyboardLayout {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub(crate) struct WindowsKeyboardMapper;
|
||||
|
||||
impl PlatformKeyboardLayout for WindowsKeyboardLayout {
|
||||
fn id(&self) -> &str {
|
||||
&self.id
|
||||
|
@ -27,6 +33,65 @@ impl PlatformKeyboardLayout for WindowsKeyboardLayout {
|
|||
}
|
||||
}
|
||||
|
||||
impl PlatformKeyboardMapper for WindowsKeyboardMapper {
|
||||
fn map_key_equivalent(&self, mut keystroke: Keystroke) -> KeybindingKeystroke {
|
||||
let Some((vkey, shift)) = key_needs_processing(&keystroke.key) else {
|
||||
return KeybindingKeystroke::new(keystroke);
|
||||
};
|
||||
if shift && keystroke.modifiers.shift {
|
||||
log::warn!(
|
||||
"Keystroke '{}' has both shift and a shifted key, this is likely a bug",
|
||||
keystroke.key
|
||||
);
|
||||
keystroke.modifiers.shift = false;
|
||||
}
|
||||
// translate to unshifted key first
|
||||
let Some(key) = get_key_from_vkey(vkey) else {
|
||||
log::error!(
|
||||
"Failed to map key equivalent '{:?}' to a valid key",
|
||||
keystroke
|
||||
);
|
||||
return KeybindingKeystroke::new(keystroke);
|
||||
};
|
||||
let modifiers = Modifiers {
|
||||
control: keystroke.modifiers.control,
|
||||
alt: keystroke.modifiers.alt,
|
||||
shift,
|
||||
platform: keystroke.modifiers.platform,
|
||||
function: keystroke.modifiers.function,
|
||||
};
|
||||
|
||||
keystroke.key = if shift {
|
||||
let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) };
|
||||
if scan_code == 0 {
|
||||
log::error!(
|
||||
"Failed to map keystroke {:?} with virtual key '{:?}' to a scan code",
|
||||
keystroke,
|
||||
vkey
|
||||
);
|
||||
return KeybindingKeystroke::new(keystroke);
|
||||
}
|
||||
let Some(shifted_key) = get_shifted_key(vkey, scan_code) else {
|
||||
log::error!(
|
||||
"Failed to map keystroke {:?} with virtual key '{:?}' to a shifted key",
|
||||
keystroke,
|
||||
vkey
|
||||
);
|
||||
return KeybindingKeystroke::new(keystroke);
|
||||
};
|
||||
shifted_key
|
||||
} else {
|
||||
key.clone()
|
||||
};
|
||||
|
||||
KeybindingKeystroke {
|
||||
inner: keystroke,
|
||||
modifiers,
|
||||
key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsKeyboardLayout {
|
||||
pub(crate) fn new() -> Result<Self> {
|
||||
let mut buffer = [0u16; KL_NAMELENGTH as usize];
|
||||
|
@ -48,6 +113,12 @@ impl WindowsKeyboardLayout {
|
|||
}
|
||||
}
|
||||
|
||||
impl WindowsKeyboardMapper {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_keystroke_key(
|
||||
vkey: VIRTUAL_KEY,
|
||||
scan_code: u32,
|
||||
|
@ -140,3 +211,51 @@ pub(crate) fn generate_key_char(
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn key_needs_processing(key: &str) -> Option<(VIRTUAL_KEY, bool)> {
|
||||
match key {
|
||||
"`" => Some((VK_OEM_3, false)),
|
||||
"~" => Some((VK_OEM_3, true)),
|
||||
"1" => Some((VK_1, false)),
|
||||
"!" => Some((VK_1, true)),
|
||||
"2" => Some((VK_2, false)),
|
||||
"@" => Some((VK_2, true)),
|
||||
"3" => Some((VK_3, false)),
|
||||
"#" => Some((VK_3, true)),
|
||||
"4" => Some((VK_4, false)),
|
||||
"$" => Some((VK_4, true)),
|
||||
"5" => Some((VK_5, false)),
|
||||
"%" => Some((VK_5, true)),
|
||||
"6" => Some((VK_6, false)),
|
||||
"^" => Some((VK_6, true)),
|
||||
"7" => Some((VK_7, false)),
|
||||
"&" => Some((VK_7, true)),
|
||||
"8" => Some((VK_8, false)),
|
||||
"*" => Some((VK_8, true)),
|
||||
"9" => Some((VK_9, false)),
|
||||
"(" => Some((VK_9, true)),
|
||||
"0" => Some((VK_0, false)),
|
||||
")" => Some((VK_0, true)),
|
||||
"-" => Some((VK_OEM_MINUS, false)),
|
||||
"_" => Some((VK_OEM_MINUS, true)),
|
||||
"=" => Some((VK_OEM_PLUS, false)),
|
||||
"+" => Some((VK_OEM_PLUS, true)),
|
||||
"[" => Some((VK_OEM_4, false)),
|
||||
"{" => Some((VK_OEM_4, true)),
|
||||
"]" => Some((VK_OEM_6, false)),
|
||||
"}" => Some((VK_OEM_6, true)),
|
||||
"\\" => Some((VK_OEM_5, false)),
|
||||
"|" => Some((VK_OEM_5, true)),
|
||||
";" => Some((VK_OEM_1, false)),
|
||||
":" => Some((VK_OEM_1, true)),
|
||||
"'" => Some((VK_OEM_7, false)),
|
||||
"\"" => Some((VK_OEM_7, true)),
|
||||
"," => Some((VK_OEM_COMMA, false)),
|
||||
"<" => Some((VK_OEM_COMMA, true)),
|
||||
"." => Some((VK_OEM_PERIOD, false)),
|
||||
">" => Some((VK_OEM_PERIOD, true)),
|
||||
"/" => Some((VK_OEM_2, false)),
|
||||
"?" => Some((VK_OEM_2, true)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -351,6 +351,10 @@ impl Platform for WindowsPlatform {
|
|||
)
|
||||
}
|
||||
|
||||
fn keyboard_mapper(&self) -> Rc<dyn PlatformKeyboardMapper> {
|
||||
Rc::new(WindowsKeyboardMapper::new())
|
||||
}
|
||||
|
||||
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
|
||||
self.state.borrow_mut().callbacks.keyboard_layout_change = Some(callback);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue