diff --git a/Cargo.lock b/Cargo.lock index d6ff13e748..95b85b13a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6171,6 +6171,7 @@ dependencies = [ "windows 0.61.1", "windows-core 0.61.0", "windows-numerics", + "windows-registry 0.5.1", "workspace-hack", "x11-clipboard", "x11rb", @@ -11930,7 +11931,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "windows-registry", + "windows-registry 0.2.0", ] [[package]] @@ -17044,6 +17045,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1da3e436dc7653dfdf3da67332e22bff09bb0e28b0239e1624499c7830842e" +dependencies = [ + "windows-link", + "windows-result 0.3.2", + "windows-strings 0.4.0", +] + [[package]] name = "windows-result" version = "0.1.2" diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index ae4a61daeb..948eefafda 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -66,7 +66,7 @@ x11 = [ "x11-clipboard", "filedescriptor", "open", - "scap" + "scap", ] @@ -220,6 +220,7 @@ rand.workspace = true windows.workspace = true windows-core = "0.61" windows-numerics = "0.2" +windows-registry = "0.5" [dev-dependencies] backtrace = "0.3" diff --git a/crates/gpui/examples/input.rs b/crates/gpui/examples/input.rs index 7b280d7cae..2beee18658 100644 --- a/crates/gpui/examples/input.rs +++ b/crates/gpui/examples/input.rs @@ -635,7 +635,7 @@ impl Render for InputExample { .flex() .flex_row() .justify_between() - .child(format!("Keyboard {}", cx.keyboard_layout())) + .child(format!("Keyboard {}", cx.keyboard_layout().name())) .child( div() .border_1() diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index dfd5594773..6938c16c69 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -35,10 +35,10 @@ use crate::{ AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId, EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, - Point, PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation, - ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, - Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator, current_platform, hash, - init_app_menus, + PlatformKeyboardLayout, Point, PromptBuilder, PromptHandle, PromptLevel, Render, + RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet, + Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance, WindowHandle, WindowId, + WindowInvalidator, current_platform, hash, init_app_menus, }; mod async_context; @@ -248,7 +248,7 @@ pub struct App { pub(crate) window_handles: FxHashMap, pub(crate) focus_handles: Arc, pub(crate) keymap: Rc>, - pub(crate) keyboard_layout: SharedString, + pub(crate) keyboard_layout: Box, pub(crate) global_action_listeners: FxHashMap>>, pending_effects: VecDeque, @@ -289,7 +289,7 @@ impl App { let text_system = Arc::new(TextSystem::new(platform.text_system())); let entities = EntityMap::new(); - let keyboard_layout = SharedString::from(platform.keyboard_layout()); + let keyboard_layout = platform.keyboard_layout(); let app = Rc::new_cyclic(|this| AppCell { app: RefCell::new(App { @@ -345,7 +345,7 @@ impl App { move || { if let Some(app) = app.upgrade() { let cx = &mut app.borrow_mut(); - cx.keyboard_layout = SharedString::from(cx.platform.keyboard_layout()); + cx.keyboard_layout = cx.platform.keyboard_layout(); cx.keyboard_layout_observers .clone() .retain(&(), move |callback| (callback)(cx)); @@ -387,8 +387,8 @@ impl App { } /// Get the id of the current keyboard layout - pub fn keyboard_layout(&self) -> &SharedString { - &self.keyboard_layout + pub fn keyboard_layout(&self) -> &dyn PlatformKeyboardLayout { + self.keyboard_layout.as_ref() } /// Invokes a handler when the current keyboard layout changes diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 969e51e44d..6f9b615000 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -214,7 +214,7 @@ pub(crate) trait Platform: 'static { fn on_app_menu_action(&self, callback: Box); fn on_will_open_app_menu(&self, callback: Box); fn on_validate_app_menu_command(&self, callback: Box bool>); - fn keyboard_layout(&self) -> String; + fn keyboard_layout(&self) -> Box; fn compositor_name(&self) -> &'static str { "" @@ -1634,3 +1634,11 @@ impl From for ClipboardString { } } } + +/// A trait for platform-specific keyboard layouts +pub trait PlatformKeyboardLayout { + /// Get the keyboard layout ID, which should be unique to the layout + fn id(&self) -> &str; + /// Get the keyboard layout display name + fn name(&self) -> &str; +} diff --git a/crates/gpui/src/platform/linux/headless/client.rs b/crates/gpui/src/platform/linux/headless/client.rs index 46a3032fe0..fd0544a41c 100644 --- a/crates/gpui/src/platform/linux/headless/client.rs +++ b/crates/gpui/src/platform/linux/headless/client.rs @@ -9,7 +9,8 @@ use util::ResultExt; use crate::platform::linux::LinuxClient; use crate::platform::{LinuxCommon, PlatformWindow}; use crate::{ - AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, ScreenCaptureSource, WindowParams, + AnyWindowHandle, CursorStyle, DisplayId, LinuxKeyboardLayout, PlatformDisplay, + PlatformKeyboardLayout, ScreenCaptureSource, WindowParams, }; pub struct HeadlessClientState { @@ -50,8 +51,8 @@ impl LinuxClient for HeadlessClient { f(&mut self.0.borrow_mut().common) } - fn keyboard_layout(&self) -> String { - "unknown".to_string() + fn keyboard_layout(&self) -> Box { + Box::new(LinuxKeyboardLayout::new("unknown".to_string())) } fn displays(&self) -> Vec> { diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 445192f07a..417dc46a55 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -25,8 +25,8 @@ 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, PlatformTextSystem, PlatformWindow, Point, Result, - ScreenCaptureSource, Task, WindowAppearance, WindowParams, px, + Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, + Point, Result, ScreenCaptureSource, Task, WindowAppearance, WindowParams, px, }; #[cfg(any(feature = "wayland", feature = "x11"))] @@ -46,7 +46,7 @@ const FILE_PICKER_PORTAL_MISSING: &str = pub trait LinuxClient { fn compositor_name(&self) -> &'static str; fn with_common(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R; - fn keyboard_layout(&self) -> String; + fn keyboard_layout(&self) -> Box; fn displays(&self) -> Vec>; #[allow(unused)] fn display(&self, id: DisplayId) -> Option>; @@ -138,7 +138,7 @@ impl Platform for P { self.with_common(|common| common.text_system.clone()) } - fn keyboard_layout(&self) -> String { + fn keyboard_layout(&self) -> Box { self.keyboard_layout() } @@ -858,6 +858,26 @@ impl crate::Modifiers { } } +pub(crate) struct LinuxKeyboardLayout { + id: String, +} + +impl PlatformKeyboardLayout for LinuxKeyboardLayout { + fn id(&self) -> &str { + &self.id + } + + fn name(&self) -> &str { + &self.id + } +} + +impl LinuxKeyboardLayout { + pub(crate) fn new(id: String) -> Self { + Self { id } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 80da04794a..9cd20e76b6 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -66,8 +66,10 @@ use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blu use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1; use xkbcommon::xkb::{self, KEYMAP_COMPILE_NO_FLAGS, Keycode}; -use super::display::WaylandDisplay; -use super::window::{ImeInput, WaylandWindowStatePtr}; +use super::{ + display::WaylandDisplay, + window::{ImeInput, WaylandWindowStatePtr}, +}; use crate::platform::linux::{ LinuxClient, get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd, @@ -83,11 +85,11 @@ use crate::platform::linux::{ use crate::platform::{PlatformWindow, blade::BladeContext}; use crate::{ AnyWindowHandle, Bounds, CursorStyle, DOUBLE_CLICK_INTERVAL, DevicePixels, DisplayId, - FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon, Modifiers, - ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, - MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, SCROLL_LINES, - ScaledPixels, ScreenCaptureSource, ScrollDelta, ScrollWheelEvent, Size, TouchPhase, - WindowParams, point, px, size, + FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon, + LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, + MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, + PlatformInput, PlatformKeyboardLayout, Point, SCROLL_LINES, ScaledPixels, ScreenCaptureSource, + ScrollDelta, ScrollWheelEvent, Size, TouchPhase, WindowParams, point, px, size, }; /// Used to convert evdev scancode to xkb scancode @@ -587,9 +589,9 @@ impl WaylandClient { } impl LinuxClient for WaylandClient { - fn keyboard_layout(&self) -> String { + fn keyboard_layout(&self) -> Box { let state = self.0.borrow(); - if let Some(keymap_state) = &state.keymap_state { + let id = if let Some(keymap_state) = &state.keymap_state { let layout_idx = keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE); keymap_state .get_keymap() @@ -597,7 +599,8 @@ impl LinuxClient for WaylandClient { .to_string() } else { "unknown".to_string() - } + }; + Box::new(LinuxKeyboardLayout::new(id)) } fn displays(&self) -> Vec> { diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 3e375aac07..a59825b292 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -59,9 +59,10 @@ use crate::platform::{ }; use crate::{ AnyWindowHandle, Bounds, ClipboardItem, CursorStyle, DisplayId, FileDropEvent, Keystroke, - Modifiers, ModifiersChangedEvent, MouseButton, Pixels, Platform, PlatformDisplay, - PlatformInput, Point, RequestFrameOptions, ScaledPixels, ScreenCaptureSource, ScrollDelta, - Size, TouchPhase, WindowParams, X11Window, modifiers_from_xinput_info, point, px, + LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, Pixels, Platform, + PlatformDisplay, PlatformInput, PlatformKeyboardLayout, Point, RequestFrameOptions, + ScaledPixels, ScreenCaptureSource, ScrollDelta, Size, TouchPhase, WindowParams, X11Window, + modifiers_from_xinput_info, point, px, }; /// Value for DeviceId parameters which selects all devices. @@ -1282,14 +1283,16 @@ impl LinuxClient for X11Client { f(&mut self.0.borrow_mut().common) } - fn keyboard_layout(&self) -> String { + fn keyboard_layout(&self) -> Box { let state = self.0.borrow(); let layout_idx = state.xkb.serialize_layout(STATE_LAYOUT_EFFECTIVE); - state - .xkb - .get_keymap() - .layout_get_name(layout_idx) - .to_string() + Box::new(LinuxKeyboardLayout::new( + state + .xkb + .get_keymap() + .layout_get_name(layout_idx) + .to_string(), + )) } fn displays(&self) -> Vec> { diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 02c12ab8c2..16f87200c4 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -7,9 +7,9 @@ use super::{ use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString, CursorStyle, ForegroundExecutor, Image, ImageFormat, Keymap, MacDispatcher, MacDisplay, - MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, - PlatformWindow, Result, ScreenCaptureSource, SemanticVersion, Task, WindowAppearance, - WindowParams, hash, + MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, + PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, ScreenCaptureSource, + SemanticVersion, Task, WindowAppearance, WindowParams, hash, }; use anyhow::{Context as _, anyhow}; use block::ConcreteBlock; @@ -825,20 +825,8 @@ impl Platform for MacPlatform { self.0.lock().validate_menu_command = Some(callback); } - fn keyboard_layout(&self) -> String { - unsafe { - let current_keyboard = TISCopyCurrentKeyboardLayoutInputSource(); - - let input_source_id: *mut Object = TISGetInputSourceProperty( - current_keyboard, - kTISPropertyInputSourceID as *const c_void, - ); - let input_source_id: *const std::os::raw::c_char = - msg_send![input_source_id, UTF8String]; - let input_source_id = CStr::from_ptr(input_source_id).to_str().unwrap(); - - input_source_id.to_string() - } + fn keyboard_layout(&self) -> Box { + Box::new(MacKeyboardLayout::new()) } fn app_path(&self) -> Result { @@ -1501,6 +1489,7 @@ unsafe extern "C" { pub(super) fn LMGetKbdType() -> u16; pub(super) static kTISPropertyUnicodeKeyLayoutData: CFStringRef; pub(super) static kTISPropertyInputSourceID: CFStringRef; + pub(super) static kTISPropertyLocalizedName: CFStringRef; } mod security { @@ -1590,6 +1579,45 @@ impl UTType { } } +struct MacKeyboardLayout { + id: String, + name: String, +} + +impl PlatformKeyboardLayout for MacKeyboardLayout { + fn id(&self) -> &str { + &self.id + } + + fn name(&self) -> &str { + &self.name + } +} + +impl MacKeyboardLayout { + fn new() -> Self { + unsafe { + let current_keyboard = TISCopyCurrentKeyboardLayoutInputSource(); + + let id: *mut Object = TISGetInputSourceProperty( + current_keyboard, + kTISPropertyInputSourceID as *const c_void, + ); + let id: *const std::os::raw::c_char = msg_send![id, UTF8String]; + let id = CStr::from_ptr(id).to_str().unwrap().to_string(); + + let name: *mut Object = TISGetInputSourceProperty( + current_keyboard, + kTISPropertyLocalizedName as *const c_void, + ); + let name: *const std::os::raw::c_char = msg_send![name, UTF8String]; + let name = CStr::from_ptr(name).to_str().unwrap().to_string(); + + Self { id, name } + } + } +} + #[cfg(test)] mod tests { use crate::ClipboardItem; diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 3902a98a65..2c1f7e4ad5 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -1,8 +1,8 @@ use crate::{ AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels, - ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformTextSystem, - ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, Size, Task, TestDisplay, - TestWindow, WindowAppearance, WindowParams, size, + ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout, + PlatformTextSystem, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, Size, Task, + TestDisplay, TestWindow, WindowAppearance, WindowParams, size, }; use anyhow::Result; use collections::VecDeque; @@ -223,8 +223,8 @@ impl Platform for TestPlatform { self.text_system.clone() } - fn keyboard_layout(&self) -> String { - "zed.keyboard.example".to_string() + fn keyboard_layout(&self) -> Box { + Box::new(TestKeyboardLayout) } fn on_keyboard_layout_change(&self, _: Box) {} @@ -431,3 +431,15 @@ impl Drop for TestPlatform { } } } + +struct TestKeyboardLayout; + +impl PlatformKeyboardLayout for TestKeyboardLayout { + fn id(&self) -> &str { + "zed.keyboard.example" + } + + fn name(&self) -> &str { + "zed.keyboard.example" + } +} diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 7889c89a9e..f1a6a1dbc8 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -297,8 +297,12 @@ impl Platform for WindowsPlatform { self.text_system.clone() } - fn keyboard_layout(&self) -> String { - "unknown".into() + fn keyboard_layout(&self) -> Box { + Box::new( + KeyboardLayout::new() + .log_err() + .unwrap_or(KeyboardLayout::unknown()), + ) } fn on_keyboard_layout_change(&self, _callback: Box) { @@ -836,6 +840,42 @@ fn should_auto_hide_scrollbars() -> Result { Ok(ui_settings.AutoHideScrollBars()?) } +struct KeyboardLayout { + id: String, + name: String, +} + +impl PlatformKeyboardLayout for KeyboardLayout { + fn id(&self) -> &str { + &self.id + } + + fn name(&self) -> &str { + &self.name + } +} + +impl KeyboardLayout { + fn new() -> Result { + let mut buffer = [0u16; KL_NAMELENGTH as usize]; + unsafe { GetKeyboardLayoutNameW(&mut buffer)? }; + let id = HSTRING::from_wide(&buffer).to_string(); + let entry = windows_registry::LOCAL_MACHINE.open(format!( + "System\\CurrentControlSet\\Control\\Keyboard Layouts\\{}", + id + ))?; + let name = entry.get_hstring("Layout Text")?.to_string(); + Ok(Self { id, name }) + } + + fn unknown() -> Self { + Self { + id: "unknown".to_string(), + name: "unknown".to_string(), + } + } +} + #[cfg(test)] mod tests { use crate::{ClipboardItem, read_from_clipboard, write_to_clipboard}; diff --git a/crates/language_tools/src/key_context_view.rs b/crates/language_tools/src/key_context_view.rs index 9fec3b4cf2..1a52acfa3d 100644 --- a/crates/language_tools/src/key_context_view.rs +++ b/crates/language_tools/src/key_context_view.rs @@ -173,7 +173,7 @@ impl Item for KeyContextView { impl Render for KeyContextView { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl ui::IntoElement { use itertools::Itertools; - let key_equivalents = get_key_equivalents(cx.keyboard_layout()); + let key_equivalents = get_key_equivalents(cx.keyboard_layout().id()); v_flex() .id("key-context-view") .overflow_scroll() diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 937da91dfb..957276d355 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -195,7 +195,8 @@ impl KeymapFile { } pub fn load(content: &str, cx: &App) -> KeymapFileLoadResult { - let key_equivalents = crate::key_equivalents::get_key_equivalents(&cx.keyboard_layout()); + let key_equivalents = + crate::key_equivalents::get_key_equivalents(cx.keyboard_layout().id()); if content.is_empty() { return KeymapFileLoadResult::Success { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f5c3bfce0b..f65104b78f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -5439,7 +5439,7 @@ impl Render for Workspace { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let mut context = KeyContext::new_with_defaults(); context.add("Workspace"); - context.set("keyboard_layout", cx.keyboard_layout().clone()); + context.set("keyboard_layout", cx.keyboard_layout().name().to_string()); let centered_layout = self.centered_layout && self.center.panes().len() == 1 && self.active_item(cx).is_some(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 691de1edca..bb7d1d944e 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1224,9 +1224,9 @@ pub fn handle_keymap_file_changes( }) .detach(); - let mut current_mapping = settings::get_key_equivalents(cx.keyboard_layout()); + let mut current_mapping = settings::get_key_equivalents(cx.keyboard_layout().id()); cx.on_keyboard_layout_change(move |cx| { - let next_mapping = settings::get_key_equivalents(cx.keyboard_layout()); + let next_mapping = settings::get_key_equivalents(cx.keyboard_layout().id()); if next_mapping != current_mapping { current_mapping = next_mapping; keyboard_layout_tx.unbounded_send(()).ok();