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: <img src="https://github.com/user-attachments/assets/b528db77-1ff2-4f17-aac5-7654837edeb9" alt="x11" width="300px" /> Wayland: <img src="https://github.com/user-attachments/assets/2b4e2a30-b0f4-495c-96bb-7bca41365d56" alt="wayland" width="300px" /> Release Notes: - N/A
This commit is contained in:
parent
76d18f3cd2
commit
d2d1779e0d
4 changed files with 65 additions and 4 deletions
|
@ -47,6 +47,10 @@ impl LinuxClient for HeadlessClient {
|
||||||
f(&mut self.0.borrow_mut().common)
|
f(&mut self.0.borrow_mut().common)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keyboard_layout(&self) -> String {
|
||||||
|
"unknown".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ const FILE_PICKER_PORTAL_MISSING: &str =
|
||||||
pub trait LinuxClient {
|
pub trait LinuxClient {
|
||||||
fn compositor_name(&self) -> &'static str;
|
fn compositor_name(&self) -> &'static str;
|
||||||
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
|
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
|
||||||
|
fn keyboard_layout(&self) -> String;
|
||||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
||||||
|
@ -76,6 +77,7 @@ pub(crate) struct PlatformHandlers {
|
||||||
pub(crate) app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
|
pub(crate) app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
|
||||||
pub(crate) will_open_app_menu: Option<Box<dyn FnMut()>>,
|
pub(crate) will_open_app_menu: Option<Box<dyn FnMut()>>,
|
||||||
pub(crate) validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
pub(crate) validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
||||||
|
pub(crate) keyboard_layout_change: Option<Box<dyn FnMut()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct LinuxCommon {
|
pub(crate) struct LinuxCommon {
|
||||||
|
@ -133,11 +135,11 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keyboard_layout(&self) -> String {
|
fn keyboard_layout(&self) -> String {
|
||||||
"unknown".into()
|
self.keyboard_layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_keyboard_layout_change(&self, _callback: Box<dyn FnMut()>) {
|
fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>) {
|
||||||
// todo(linux)
|
self.with_common(|common| common.callbacks.keyboard_layout_change = Some(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
|
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
|
||||||
|
|
|
@ -588,6 +588,19 @@ impl WaylandClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LinuxClient for 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<Rc<dyn PlatformDisplay>> {
|
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||||
self.0
|
self.0
|
||||||
.borrow()
|
.borrow()
|
||||||
|
@ -1139,6 +1152,13 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||||
};
|
};
|
||||||
state.keymap_state = Some(xkb::State::new(&keymap));
|
state.keymap_state = Some(xkb::State::new(&keymap));
|
||||||
state.compose_state = get_xkb_compose_state(&xkb_context);
|
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, .. } => {
|
wl_keyboard::Event::Enter { surface, .. } => {
|
||||||
state.keyboard_focused_window = get_window(&mut state, &surface.id());
|
state.keyboard_focused_window = get_window(&mut state, &surface.id());
|
||||||
|
@ -1176,9 +1196,21 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||||
let focused_window = state.keyboard_focused_window.clone();
|
let focused_window = state.keyboard_focused_window.clone();
|
||||||
|
|
||||||
let keymap_state = state.keymap_state.as_mut().unwrap();
|
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);
|
keymap_state.update_mask(mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
||||||
state.modifiers = Modifiers::from_xkb(keymap_state);
|
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 {
|
let Some(focused_window) = focused_window else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,7 +37,7 @@ use x11rb::{
|
||||||
};
|
};
|
||||||
use xim::{x11rb::X11rbClient, AttributeName, Client, InputStyle};
|
use xim::{x11rb::X11rbClient, AttributeName, Client, InputStyle};
|
||||||
use xkbc::x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION};
|
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::{
|
use super::{
|
||||||
button_or_scroll_from_event_detail, get_valuator_axis_index, modifiers_from_state,
|
button_or_scroll_from_event_detail, get_valuator_axis_index, modifiers_from_state,
|
||||||
|
@ -840,6 +840,8 @@ impl X11Client {
|
||||||
}
|
}
|
||||||
Event::XkbStateNotify(event) => {
|
Event::XkbStateNotify(event) => {
|
||||||
let mut state = self.0.borrow_mut();
|
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(
|
state.xkb.update_mask(
|
||||||
event.base_mods.into(),
|
event.base_mods.into(),
|
||||||
event.latched_mods.into(),
|
event.latched_mods.into(),
|
||||||
|
@ -853,6 +855,17 @@ impl X11Client {
|
||||||
latched_layout: event.latched_group as u32,
|
latched_layout: event.latched_group as u32,
|
||||||
locked_layout: event.locked_group.into(),
|
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);
|
let modifiers = Modifiers::from_xkb(&state.xkb);
|
||||||
if state.modifiers == modifiers {
|
if state.modifiers == modifiers {
|
||||||
drop(state);
|
drop(state);
|
||||||
|
@ -1265,6 +1278,16 @@ impl LinuxClient for X11Client {
|
||||||
f(&mut self.0.borrow_mut().common)
|
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<Rc<dyn PlatformDisplay>> {
|
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||||
let state = self.0.borrow();
|
let state = self.0.borrow();
|
||||||
let setup = state.xcb_connection.setup();
|
let setup = state.xcb_connection.setup();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue