From d2d1779e0d9f3c1f41d09b02ff7ceab6101db6b6 Mon Sep 17 00:00:00 2001 From: tims <0xtimsb@gmail.com> Date: Tue, 7 Jan 2025 05:40:00 +0530 Subject: [PATCH] linux: Add `keyboard_layout` and `on_keyboard_layout_change` support (#22736) No issue, as the functionality is currently not being used in Zed. This is more of a GPUI improvement. Currently, `keyboard_layout` and `on_keyboard_layout_change` are already handled on macOS. This PR implements the same for X11 and Wayland. Linux supports up to 4 keyboard layout groups (e.g., Group 0: English US, Group 1: Bulgarian, etc). On X11 and Wayland, `event` provides a new active group, which maps to the `layout_index`. We already store keymap state from where we can get the current `layout_index`. By comparing them, we determine if the layout has changed. X11: x11 Wayland: wayland Release Notes: - N/A --- .../src/platform/linux/headless/client.rs | 4 +++ crates/gpui/src/platform/linux/platform.rs | 8 +++-- .../gpui/src/platform/linux/wayland/client.rs | 32 +++++++++++++++++++ crates/gpui/src/platform/linux/x11/client.rs | 25 ++++++++++++++- 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/platform/linux/headless/client.rs b/crates/gpui/src/platform/linux/headless/client.rs index d0cfaa9fbb37fd515d2607a198b5741a3f334224..71fdc26d9e0a9a30fa81af2310cfe8e6526ed3d9 100644 --- a/crates/gpui/src/platform/linux/headless/client.rs +++ b/crates/gpui/src/platform/linux/headless/client.rs @@ -47,6 +47,10 @@ impl LinuxClient for HeadlessClient { f(&mut self.0.borrow_mut().common) } + fn keyboard_layout(&self) -> String { + "unknown".to_string() + } + fn displays(&self) -> Vec> { vec![] } diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index b9f301a1651b930fed68ef37a16bdc196ea63f8a..52b7e7e8d3f4199f5a0da83f87ee9dea4daf40b8 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -46,6 +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 displays(&self) -> Vec>; #[allow(unused)] fn display(&self, id: DisplayId) -> Option>; @@ -76,6 +77,7 @@ pub(crate) struct PlatformHandlers { pub(crate) app_menu_action: Option>, pub(crate) will_open_app_menu: Option>, pub(crate) validate_app_menu_command: Option bool>>, + pub(crate) keyboard_layout_change: Option>, } pub(crate) struct LinuxCommon { @@ -133,11 +135,11 @@ impl Platform for P { } fn keyboard_layout(&self) -> String { - "unknown".into() + self.keyboard_layout() } - fn on_keyboard_layout_change(&self, _callback: Box) { - // todo(linux) + fn on_keyboard_layout_change(&self, callback: Box) { + self.with_common(|common| common.callbacks.keyboard_layout_change = Some(callback)); } fn run(&self, on_finish_launching: Box) { diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 622f4587542b0903a9135b22e5a48f7c27f686bb..624114b089bcf2cc9e1cf7b2646ed99c9613a9f4 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -588,6 +588,19 @@ impl WaylandClient { } impl LinuxClient for WaylandClient { + fn keyboard_layout(&self) -> String { + let state = self.0.borrow(); + if let Some(keymap_state) = &state.keymap_state { + let layout_idx = keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE); + keymap_state + .get_keymap() + .layout_get_name(layout_idx) + .to_string() + } else { + "unknown".to_string() + } + } + fn displays(&self) -> Vec> { self.0 .borrow() @@ -1139,6 +1152,13 @@ impl Dispatch for WaylandClientStatePtr { }; state.keymap_state = Some(xkb::State::new(&keymap)); state.compose_state = get_xkb_compose_state(&xkb_context); + + if let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take() { + drop(state); + callback(); + state = client.borrow_mut(); + state.common.callbacks.keyboard_layout_change = Some(callback); + } } wl_keyboard::Event::Enter { surface, .. } => { state.keyboard_focused_window = get_window(&mut state, &surface.id()); @@ -1176,9 +1196,21 @@ impl Dispatch for WaylandClientStatePtr { let focused_window = state.keyboard_focused_window.clone(); let keymap_state = state.keymap_state.as_mut().unwrap(); + let old_layout = + keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE); keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group); state.modifiers = Modifiers::from_xkb(keymap_state); + if group != old_layout { + if let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take() + { + drop(state); + callback(); + state = client.borrow_mut(); + state.common.callbacks.keyboard_layout_change = Some(callback); + } + } + let Some(focused_window) = focused_window else { return; }; diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 58f53be020e8034a7e889d9e574707b4eb72588c..1bd7d9a305ca714193d923f74bb5f16a6bbcc166 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -37,7 +37,7 @@ use x11rb::{ }; use xim::{x11rb::X11rbClient, AttributeName, Client, InputStyle}; use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION}; -use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask}; +use xkbcommon::xkb::{self as xkbc, LayoutIndex, ModMask, STATE_LAYOUT_EFFECTIVE}; use super::{ button_or_scroll_from_event_detail, get_valuator_axis_index, modifiers_from_state, @@ -840,6 +840,8 @@ impl X11Client { } Event::XkbStateNotify(event) => { let mut state = self.0.borrow_mut(); + let old_layout = state.xkb.serialize_layout(STATE_LAYOUT_EFFECTIVE); + let new_layout = u32::from(event.group); state.xkb.update_mask( event.base_mods.into(), event.latched_mods.into(), @@ -853,6 +855,17 @@ impl X11Client { latched_layout: event.latched_group as u32, locked_layout: event.locked_group.into(), }; + + if new_layout != old_layout { + if let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take() + { + drop(state); + callback(); + state = self.0.borrow_mut(); + state.common.callbacks.keyboard_layout_change = Some(callback); + } + } + let modifiers = Modifiers::from_xkb(&state.xkb); if state.modifiers == modifiers { drop(state); @@ -1265,6 +1278,16 @@ impl LinuxClient for X11Client { f(&mut self.0.borrow_mut().common) } + fn keyboard_layout(&self) -> String { + 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() + } + fn displays(&self) -> Vec> { let state = self.0.borrow(); let setup = state.xcb_connection.setup();