linux: Only call on_keyboard_layout_change when layout name changes (#32784)

Release Notes:

- N/A
This commit is contained in:
Michael Sloan 2025-06-15 23:35:19 -06:00 committed by GitHub
parent 1660438a2a
commit 4733f188da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 101 additions and 79 deletions

View file

@ -52,7 +52,7 @@ impl LinuxClient for HeadlessClient {
} }
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> { fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
Box::new(LinuxKeyboardLayout::new("unknown".to_string())) Box::new(LinuxKeyboardLayout::new("unknown".into()))
} }
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> { fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {

View file

@ -1,21 +1,22 @@
use crate::PlatformKeyboardLayout; use crate::{PlatformKeyboardLayout, SharedString};
#[derive(Clone)]
pub(crate) struct LinuxKeyboardLayout { pub(crate) struct LinuxKeyboardLayout {
id: String, name: SharedString,
} }
impl PlatformKeyboardLayout for LinuxKeyboardLayout { impl PlatformKeyboardLayout for LinuxKeyboardLayout {
fn id(&self) -> &str { fn id(&self) -> &str {
&self.id &self.name
} }
fn name(&self) -> &str { fn name(&self) -> &str {
&self.id &self.name
} }
} }
impl LinuxKeyboardLayout { impl LinuxKeyboardLayout {
pub(crate) fn new(id: String) -> Self { pub(crate) fn new(name: SharedString) -> Self {
Self { id } Self { name }
} }
} }

View file

@ -71,17 +71,6 @@ use super::{
window::{ImeInput, WaylandWindowStatePtr}, window::{ImeInput, WaylandWindowStatePtr},
}; };
use crate::platform::linux::{
LinuxClient, get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd,
reveal_path_internal,
wayland::{
clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPES},
cursor::Cursor,
serial::{SerialKind, SerialTracker},
window::WaylandWindow,
},
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
};
use crate::platform::{PlatformWindow, blade::BladeContext}; use crate::platform::{PlatformWindow, blade::BladeContext};
use crate::{ use crate::{
AnyWindowHandle, Bounds, CursorStyle, DOUBLE_CLICK_INTERVAL, DevicePixels, DisplayId, AnyWindowHandle, Bounds, CursorStyle, DOUBLE_CLICK_INTERVAL, DevicePixels, DisplayId,
@ -91,10 +80,26 @@ use crate::{
PlatformInput, PlatformKeyboardLayout, Point, SCROLL_LINES, ScaledPixels, ScreenCaptureSource, PlatformInput, PlatformKeyboardLayout, Point, SCROLL_LINES, ScaledPixels, ScreenCaptureSource,
ScrollDelta, ScrollWheelEvent, Size, TouchPhase, WindowParams, point, px, size, ScrollDelta, ScrollWheelEvent, Size, TouchPhase, WindowParams, point, px, size,
}; };
use crate::{
SharedString,
platform::linux::{
LinuxClient, get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd,
reveal_path_internal,
wayland::{
clipboard::{Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPES},
cursor::Cursor,
serial::{SerialKind, SerialTracker},
window::WaylandWindow,
},
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
},
};
/// Used to convert evdev scancode to xkb scancode /// Used to convert evdev scancode to xkb scancode
const MIN_KEYCODE: u32 = 8; const MIN_KEYCODE: u32 = 8;
const UNKNOWN_KEYBOARD_LAYOUT_NAME: SharedString = SharedString::new_static("unknown");
#[derive(Clone)] #[derive(Clone)]
pub struct Globals { pub struct Globals {
pub qh: QueueHandle<WaylandClientStatePtr>, pub qh: QueueHandle<WaylandClientStatePtr>,
@ -205,6 +210,7 @@ pub(crate) struct WaylandClientState {
// Output to scale mapping // Output to scale mapping
outputs: HashMap<ObjectId, Output>, outputs: HashMap<ObjectId, Output>,
in_progress_outputs: HashMap<ObjectId, InProgressOutput>, in_progress_outputs: HashMap<ObjectId, InProgressOutput>,
keyboard_layout: LinuxKeyboardLayout,
keymap_state: Option<xkb::State>, keymap_state: Option<xkb::State>,
compose_state: Option<xkb::compose::State>, compose_state: Option<xkb::compose::State>,
drag: DragState, drag: DragState,
@ -335,6 +341,35 @@ impl WaylandClientStatePtr {
text_input.commit(); text_input.commit();
} }
pub fn handle_keyboard_layout_change(&self) {
let client = self.get_client();
let mut state = client.borrow_mut();
let changed = if let Some(keymap_state) = &state.keymap_state {
let layout_idx = keymap_state.serialize_layout(xkbcommon::xkb::STATE_LAYOUT_EFFECTIVE);
let keymap = keymap_state.get_keymap();
let layout_name = keymap.layout_get_name(layout_idx);
let changed = layout_name != state.keyboard_layout.name();
if changed {
state.keyboard_layout = LinuxKeyboardLayout::new(layout_name.to_string().into());
}
changed
} else {
let changed = &UNKNOWN_KEYBOARD_LAYOUT_NAME != state.keyboard_layout.name();
if changed {
state.keyboard_layout = LinuxKeyboardLayout::new(UNKNOWN_KEYBOARD_LAYOUT_NAME);
}
changed
};
if changed {
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);
}
}
}
pub fn drop_window(&self, surface_id: &ObjectId) { pub fn drop_window(&self, surface_id: &ObjectId) {
let mut client = self.get_client(); let mut client = self.get_client();
let mut state = client.borrow_mut(); let mut state = client.borrow_mut();
@ -533,6 +568,7 @@ impl WaylandClient {
in_progress_outputs, in_progress_outputs,
windows: HashMap::default(), windows: HashMap::default(),
common, common,
keyboard_layout: LinuxKeyboardLayout::new(UNKNOWN_KEYBOARD_LAYOUT_NAME),
keymap_state: None, keymap_state: None,
compose_state: None, compose_state: None,
drag: DragState { drag: DragState {
@ -590,17 +626,7 @@ impl WaylandClient {
impl LinuxClient for WaylandClient { impl LinuxClient for WaylandClient {
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> { fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
let state = self.0.borrow(); Box::new(self.0.borrow().keyboard_layout.clone())
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()
.layout_get_name(layout_idx)
.to_string()
} else {
"unknown".to_string()
};
Box::new(LinuxKeyboardLayout::new(id))
} }
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> { fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
@ -1181,13 +1207,9 @@ 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);
drop(state);
if let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take() { this.handle_keyboard_layout_change();
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());
@ -1230,26 +1252,18 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
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(focused_window) = focused_window {
if let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take() let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
{ modifiers: state.modifiers,
drop(state); });
callback();
state = client.borrow_mut(); drop(state);
state.common.callbacks.keyboard_layout_change = Some(callback); focused_window.handle_input(input);
}
} }
let Some(focused_window) = focused_window else { if group != old_layout {
return; this.handle_keyboard_layout_change();
}; }
let input = PlatformInput::ModifiersChanged(ModifiersChangedEvent {
modifiers: state.modifiers,
});
drop(state);
focused_window.handle_input(input);
} }
wl_keyboard::Event::Key { wl_keyboard::Event::Key {
serial, serial,
@ -1374,6 +1388,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
} }
} }
} }
impl Dispatch<zwp_text_input_v3::ZwpTextInputV3, ()> for WaylandClientStatePtr { impl Dispatch<zwp_text_input_v3::ZwpTextInputV3, ()> for WaylandClientStatePtr {
fn event( fn event(
this: &mut Self, this: &mut Self,

View file

@ -198,6 +198,7 @@ pub struct X11ClientState {
pub(crate) keyboard_focused_window: Option<xproto::Window>, pub(crate) keyboard_focused_window: Option<xproto::Window>,
pub(crate) xkb: xkbc::State, pub(crate) xkb: xkbc::State,
previous_xkb_state: XKBStateNotiy, previous_xkb_state: XKBStateNotiy,
keyboard_layout: LinuxKeyboardLayout,
pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>, pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
pub(crate) xim_handler: Option<XimHandler>, pub(crate) xim_handler: Option<XimHandler>,
pub modifiers: Modifiers, pub modifiers: Modifiers,
@ -401,17 +402,22 @@ impl X11Client {
xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id) xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
}; };
let compose_state = get_xkb_compose_state(&xkb_context); let compose_state = get_xkb_compose_state(&xkb_context);
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap(); let layout_idx = xkb_state.serialize_layout(STATE_LAYOUT_EFFECTIVE);
let layout_name = xkb_state
.get_keymap()
.layout_get_name(layout_idx)
.to_string();
let keyboard_layout = LinuxKeyboardLayout::new(layout_name.into());
let gpu_context = BladeContext::new().expect("Unable to init GPU context"); let gpu_context = BladeContext::new().expect("Unable to init GPU context");
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap();
let scale_factor = resource_database let scale_factor = resource_database
.get_value("Xft.dpi", "Xft.dpi") .get_value("Xft.dpi", "Xft.dpi")
.ok() .ok()
.flatten() .flatten()
.map(|dpi: f32| dpi / 96.0) .map(|dpi: f32| dpi / 96.0)
.unwrap_or(1.0); .unwrap_or(1.0);
let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database) let cursor_handle = cursor::Handle::new(&xcb_connection, x_root_index, &resource_database)
.unwrap() .unwrap()
.reply() .reply()
@ -489,6 +495,7 @@ impl X11Client {
keyboard_focused_window: None, keyboard_focused_window: None,
xkb: xkb_state, xkb: xkb_state,
previous_xkb_state: XKBStateNotiy::default(), previous_xkb_state: XKBStateNotiy::default(),
keyboard_layout,
ximc, ximc,
xim_handler, xim_handler,
@ -931,12 +938,8 @@ impl X11Client {
locked_layout, locked_layout,
}; };
state.xkb = xkb_state; state.xkb = xkb_state;
if let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take() { drop(state);
drop(state); self.handle_keyboard_layout_change();
callback();
state = self.0.borrow_mut();
state.common.callbacks.keyboard_layout_change = Some(callback);
}
} }
Event::XkbStateNotify(event) => { Event::XkbStateNotify(event) => {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
@ -956,16 +959,6 @@ impl X11Client {
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.last_modifiers_changed_event == modifiers { if state.last_modifiers_changed_event == modifiers {
drop(state); drop(state);
@ -980,6 +973,10 @@ impl X11Client {
ModifiersChangedEvent { modifiers }, ModifiersChangedEvent { modifiers },
)); ));
} }
if new_layout != old_layout {
self.handle_keyboard_layout_change();
}
} }
Event::KeyPress(event) => { Event::KeyPress(event) => {
let window = self.get_window(event.event)?; let window = self.get_window(event.event)?;
@ -1368,6 +1365,22 @@ impl X11Client {
drop(state); drop(state);
Some(()) Some(())
} }
fn handle_keyboard_layout_change(&self) {
let mut state = self.0.borrow_mut();
let layout_idx = state.xkb.serialize_layout(STATE_LAYOUT_EFFECTIVE);
let keymap = state.xkb.get_keymap();
let layout_name = keymap.layout_get_name(layout_idx);
if layout_name != state.keyboard_layout.name() {
state.keyboard_layout = LinuxKeyboardLayout::new(layout_name.to_string().into());
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);
}
}
}
} }
impl LinuxClient for X11Client { impl LinuxClient for X11Client {
@ -1381,14 +1394,7 @@ impl LinuxClient for X11Client {
fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> { fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
let state = self.0.borrow(); let state = self.0.borrow();
let layout_idx = state.xkb.serialize_layout(STATE_LAYOUT_EFFECTIVE); Box::new(state.keyboard_layout.clone())
Box::new(LinuxKeyboardLayout::new(
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>> {