mirror of
https://github.com/wez/wezterm.git
synced 2024-11-23 06:54:45 +03:00
x11/wayland: fix ctrl-key for latin layouts
This is fixing a regression introduced by the fix for #2845. The resolution for this is relatively straightforward, but took a bit of effort to plumb. Previously: * CTRL/ALT/SUPER-modified keys with no explicit expansion would end up just taking the US layout version of the key. That worked well for the intended problem with non-latin layouts, but for eg: German layouts it caused expansion to totally the wrong thing Now: * CTRL/ALT/SUPER-modified keys which effectively expand to non-ascii text (eg: cyrillic "Es") now take the equivalent key press from the US layout (which would be "c" in the "Es" case). For European layouts this heuristic seems to avoid unexpected effects, but could do with some validation from native users. To support this, the xkb code splits the `Keyboard` struct out from some of the higher level logic and introduces a `KeyboardWithFallback` struct that is built out of the user-selected keyboard layout, and the fallback keyboard. Now the fallback keyboard is fed the same key inputs as the selected keyboard to correctly model the key combinations. refs: #3610 refs: #3933
This commit is contained in:
parent
1bfaf8522a
commit
f09992f704
@ -57,6 +57,9 @@ As features stabilize some brief notes about them will accumulate here.
|
|||||||
|
|
||||||
* Modals, such as `CharSelect` and `CommandPalette` did not respect alternative
|
* Modals, such as `CharSelect` and `CommandPalette` did not respect alternative
|
||||||
OS-level key maps. #3470
|
OS-level key maps. #3470
|
||||||
|
* X11/Wayland: CTRL-key presses for non-US latin keymaps regressed due to
|
||||||
|
changes to [improve handling of CTRL-key presses for non-latin
|
||||||
|
layouts](https://github.com/wez/wezterm/issues/2845). #3610
|
||||||
* Numerous issues with the kitty keyboard protocol implementation #2546 #3220
|
* Numerous issues with the kitty keyboard protocol implementation #2546 #3220
|
||||||
#3315 #3473 #3474 #3476 #3478 #3479 #3484 #3526
|
#3315 #3473 #3474 #3476 #3478 #3479 #3484 #3526
|
||||||
* mux: Attempting to spawn into an ad-hoc SSH domain after the last tab could
|
* mux: Attempting to spawn into an ad-hoc SSH domain after the last tab could
|
||||||
|
@ -4,7 +4,7 @@ use super::window::*;
|
|||||||
use crate::connection::ConnectionOps;
|
use crate::connection::ConnectionOps;
|
||||||
use crate::os::wayland::inputhandler::InputHandler;
|
use crate::os::wayland::inputhandler::InputHandler;
|
||||||
use crate::os::wayland::output::OutputHandler;
|
use crate::os::wayland::output::OutputHandler;
|
||||||
use crate::os::x11::keyboard::Keyboard;
|
use crate::os::x11::keyboard::KeyboardWithFallback;
|
||||||
use crate::screen::{ScreenInfo, Screens};
|
use crate::screen::{ScreenInfo, Screens};
|
||||||
use crate::spawn::*;
|
use crate::spawn::*;
|
||||||
use crate::{Appearance, Connection, ScreenRect, WindowEvent};
|
use crate::{Appearance, Connection, ScreenRect, WindowEvent};
|
||||||
@ -56,7 +56,7 @@ pub struct WaylandConnection {
|
|||||||
// must be ahead of the rest.
|
// must be ahead of the rest.
|
||||||
pub(crate) gl_connection: RefCell<Option<Rc<crate::egl::GlConnection>>>,
|
pub(crate) gl_connection: RefCell<Option<Rc<crate::egl::GlConnection>>>,
|
||||||
pub(crate) pointer: RefCell<PointerDispatcher>,
|
pub(crate) pointer: RefCell<PointerDispatcher>,
|
||||||
pub(crate) keyboard_mapper: RefCell<Option<Keyboard>>,
|
pub(crate) keyboard_mapper: RefCell<Option<KeyboardWithFallback>>,
|
||||||
pub(crate) keyboard_window_id: RefCell<Option<usize>>,
|
pub(crate) keyboard_window_id: RefCell<Option<usize>>,
|
||||||
pub(crate) surface_to_window_id: RefCell<HashMap<u32, usize>>,
|
pub(crate) surface_to_window_id: RefCell<HashMap<u32, usize>>,
|
||||||
pub(crate) active_surface_id: RefCell<u32>,
|
pub(crate) active_surface_id: RefCell<u32>,
|
||||||
@ -265,7 +265,7 @@ impl WaylandConnection {
|
|||||||
data.pop();
|
data.pop();
|
||||||
}
|
}
|
||||||
let s = String::from_utf8(data)?;
|
let s = String::from_utf8(data)?;
|
||||||
match Keyboard::new_from_string(s) {
|
match KeyboardWithFallback::new_from_string(s) {
|
||||||
Ok(k) => {
|
Ok(k) => {
|
||||||
self.keyboard_mapper.replace(Some(k));
|
self.keyboard_mapper.replace(Some(k));
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use super::pointer::*;
|
|||||||
use crate::connection::ConnectionOps;
|
use crate::connection::ConnectionOps;
|
||||||
use crate::os::wayland::connection::WaylandConnection;
|
use crate::os::wayland::connection::WaylandConnection;
|
||||||
use crate::os::wayland::wl_id;
|
use crate::os::wayland::wl_id;
|
||||||
use crate::os::x11::keyboard::Keyboard;
|
use crate::os::x11::keyboard::KeyboardWithFallback;
|
||||||
use crate::{
|
use crate::{
|
||||||
Appearance, Clipboard, Connection, Dimensions, MouseCursor, Point, Rect,
|
Appearance, Clipboard, Connection, Dimensions, MouseCursor, Point, Rect,
|
||||||
RequestedWindowGeometry, ResolvedGeometry, ScreenPoint, Window, WindowEvent, WindowEventSender,
|
RequestedWindowGeometry, ResolvedGeometry, ScreenPoint, Window, WindowEvent, WindowEventSender,
|
||||||
@ -484,7 +484,7 @@ impl WaylandWindowInner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_focus(&mut self, mapper: &mut Keyboard, focused: bool) {
|
fn emit_focus(&mut self, mapper: &mut KeyboardWithFallback, focused: bool) {
|
||||||
// Clear the modifiers when we change focus, otherwise weird
|
// Clear the modifiers when we change focus, otherwise weird
|
||||||
// things can happen. For instance, if we lost focus because
|
// things can happen. For instance, if we lost focus because
|
||||||
// CTRL+SHIFT+N was pressed to spawn a new window, we'd be
|
// CTRL+SHIFT+N was pressed to spawn a new window, we'd be
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::keyboard::Keyboard;
|
use super::keyboard::{Keyboard, KeyboardWithFallback};
|
||||||
use crate::connection::ConnectionOps;
|
use crate::connection::ConnectionOps;
|
||||||
use crate::os::x11::window::XWindowInner;
|
use crate::os::x11::window::XWindowInner;
|
||||||
use crate::os::x11::xsettings::*;
|
use crate::os::x11::xsettings::*;
|
||||||
@ -25,7 +25,7 @@ pub struct XConnection {
|
|||||||
pub(crate) xsettings: RefCell<XSettingsMap>,
|
pub(crate) xsettings: RefCell<XSettingsMap>,
|
||||||
pub screen_num: i32,
|
pub screen_num: i32,
|
||||||
pub root: xcb::x::Window,
|
pub root: xcb::x::Window,
|
||||||
pub keyboard: Keyboard,
|
pub keyboard: KeyboardWithFallback,
|
||||||
pub kbd_ev: u8,
|
pub kbd_ev: u8,
|
||||||
pub atom_protocols: Atom,
|
pub atom_protocols: Atom,
|
||||||
pub cursor_font_id: xcb::x::Font,
|
pub cursor_font_id: xcb::x::Font,
|
||||||
@ -639,6 +639,7 @@ impl XConnection {
|
|||||||
visual.blue_mask()
|
visual.blue_mask()
|
||||||
);
|
);
|
||||||
let (keyboard, kbd_ev) = Keyboard::new(&conn)?;
|
let (keyboard, kbd_ev) = Keyboard::new(&conn)?;
|
||||||
|
let keyboard = KeyboardWithFallback::new(keyboard)?;
|
||||||
|
|
||||||
let cursor_font_id = conn.generate_id();
|
let cursor_font_id = conn.generate_id();
|
||||||
let cursor_font_name = "cursor";
|
let cursor_font_name = "cursor";
|
||||||
|
@ -16,21 +16,25 @@ use xkbcommon::xkb;
|
|||||||
pub struct Keyboard {
|
pub struct Keyboard {
|
||||||
context: xkb::Context,
|
context: xkb::Context,
|
||||||
keymap: RefCell<xkb::Keymap>,
|
keymap: RefCell<xkb::Keymap>,
|
||||||
_default_keymap: RefCell<xkb::Keymap>,
|
|
||||||
device_id: i32,
|
device_id: i32,
|
||||||
|
|
||||||
state: RefCell<xkb::State>,
|
state: RefCell<xkb::State>,
|
||||||
default_state: RefCell<xkb::State>,
|
|
||||||
compose_state: RefCell<Compose>,
|
compose_state: RefCell<Compose>,
|
||||||
phys_code_map: RefCell<HashMap<xkb::Keycode, PhysKeyCode>>,
|
phys_code_map: RefCell<HashMap<xkb::Keycode, PhysKeyCode>>,
|
||||||
mods_leds: RefCell<(Modifiers, KeyboardLedStatus)>,
|
mods_leds: RefCell<(Modifiers, KeyboardLedStatus)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct KeyboardWithFallback {
|
||||||
|
selected: Keyboard,
|
||||||
|
fallback: Keyboard,
|
||||||
|
}
|
||||||
|
|
||||||
struct Compose {
|
struct Compose {
|
||||||
state: xkb::compose::State,
|
state: xkb::compose::State,
|
||||||
composition: String,
|
composition: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
enum FeedResult {
|
enum FeedResult {
|
||||||
Composing(String),
|
Composing(String),
|
||||||
Composed(String, xkb::Keysym),
|
Composed(String, xkb::Keysym),
|
||||||
@ -131,7 +135,7 @@ fn default_keymap(context: &xkb::Context) -> Option<xkb::Keymap> {
|
|||||||
// use $XKB_DEFAULT_VARIANT or system default
|
// use $XKB_DEFAULT_VARIANT or system default
|
||||||
let system_default_variant = "";
|
let system_default_variant = "";
|
||||||
|
|
||||||
xkb::Keymap::new_from_names(
|
let map = xkb::Keymap::new_from_names(
|
||||||
context,
|
context,
|
||||||
system_default_rules,
|
system_default_rules,
|
||||||
system_default_model,
|
system_default_model,
|
||||||
@ -139,126 +143,28 @@ fn default_keymap(context: &xkb::Context) -> Option<xkb::Keymap> {
|
|||||||
system_default_variant,
|
system_default_variant,
|
||||||
None,
|
None,
|
||||||
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
if let Some(map) = &map {
|
||||||
|
for layout in map.layouts() {
|
||||||
|
log::debug!("default_keymap layout {layout}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keyboard {
|
impl KeyboardWithFallback {
|
||||||
pub fn new_from_string(s: String) -> anyhow::Result<Self> {
|
pub fn new(selected: Keyboard) -> anyhow::Result<Self> {
|
||||||
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
|
||||||
let keymap = xkb::Keymap::new_from_string(
|
|
||||||
&context,
|
|
||||||
s,
|
|
||||||
xkbcommon::xkb::KEYMAP_FORMAT_TEXT_V1,
|
|
||||||
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
|
||||||
)
|
|
||||||
.ok_or_else(|| anyhow!("Failed to parse keymap state from file"))?;
|
|
||||||
|
|
||||||
let state = xkb::State::new(&keymap);
|
|
||||||
let locale = query_lc_ctype()?;
|
|
||||||
|
|
||||||
let table =
|
|
||||||
xkb::compose::Table::new_from_locale(&context, locale, xkb::compose::COMPILE_NO_FLAGS)
|
|
||||||
.map_err(|_| anyhow!("Failed to acquire compose table from locale"))?;
|
|
||||||
let compose_state = xkb::compose::State::new(&table, xkb::compose::STATE_NO_FLAGS);
|
|
||||||
|
|
||||||
let phys_code_map = build_physkeycode_map(&keymap);
|
|
||||||
|
|
||||||
let default_keymap = default_keymap(&context)
|
|
||||||
.ok_or_else(|| anyhow!("Failed to load system default keymap"))?;
|
|
||||||
let default_state = xkb::State::new(&default_keymap);
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
context,
|
selected,
|
||||||
device_id: -1,
|
fallback: Keyboard::new_default()?,
|
||||||
keymap: RefCell::new(keymap),
|
|
||||||
state: RefCell::new(state),
|
|
||||||
_default_keymap: RefCell::new(default_keymap),
|
|
||||||
default_state: RefCell::new(default_state),
|
|
||||||
compose_state: RefCell::new(Compose {
|
|
||||||
state: compose_state,
|
|
||||||
composition: String::new(),
|
|
||||||
}),
|
|
||||||
phys_code_map: RefCell::new(phys_code_map),
|
|
||||||
mods_leds: RefCell::new(Default::default()),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(connection: &xcb::Connection) -> anyhow::Result<(Keyboard, u8)> {
|
pub fn new_from_string(s: String) -> anyhow::Result<Self> {
|
||||||
let first_ev = xcb::xkb::get_extension_data(connection)
|
let selected = Keyboard::new_from_string(s)?;
|
||||||
.ok_or_else(|| anyhow!("could not get xkb extension data"))?
|
Self::new(selected)
|
||||||
.first_event;
|
|
||||||
|
|
||||||
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
|
||||||
let device_id = xkb::x11::get_core_keyboard_device_id(&connection);
|
|
||||||
ensure!(device_id != -1, "Couldn't find core keyboard device");
|
|
||||||
|
|
||||||
let keymap = xkb::x11::keymap_new_from_device(
|
|
||||||
&context,
|
|
||||||
&connection,
|
|
||||||
device_id,
|
|
||||||
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
|
||||||
);
|
|
||||||
|
|
||||||
let state = xkb::x11::state_new_from_device(&keymap, connection, device_id);
|
|
||||||
|
|
||||||
let locale = query_lc_ctype()?;
|
|
||||||
|
|
||||||
let table =
|
|
||||||
xkb::compose::Table::new_from_locale(&context, locale, xkb::compose::COMPILE_NO_FLAGS)
|
|
||||||
.map_err(|_| anyhow!("Failed to acquire compose table from locale"))?;
|
|
||||||
let compose_state = xkb::compose::State::new(&table, xkb::compose::STATE_NO_FLAGS);
|
|
||||||
|
|
||||||
{
|
|
||||||
let map_parts = xcb::xkb::MapPart::KEY_TYPES
|
|
||||||
| xcb::xkb::MapPart::KEY_SYMS
|
|
||||||
| xcb::xkb::MapPart::MODIFIER_MAP
|
|
||||||
| xcb::xkb::MapPart::EXPLICIT_COMPONENTS
|
|
||||||
| xcb::xkb::MapPart::KEY_ACTIONS
|
|
||||||
| xcb::xkb::MapPart::KEY_BEHAVIORS
|
|
||||||
| xcb::xkb::MapPart::VIRTUAL_MODS
|
|
||||||
| xcb::xkb::MapPart::VIRTUAL_MOD_MAP;
|
|
||||||
|
|
||||||
let events = xcb::xkb::EventType::NEW_KEYBOARD_NOTIFY
|
|
||||||
| xcb::xkb::EventType::MAP_NOTIFY
|
|
||||||
| xcb::xkb::EventType::STATE_NOTIFY;
|
|
||||||
|
|
||||||
connection.check_request(connection.send_request_checked(&xcb::xkb::SelectEvents {
|
|
||||||
device_spec: device_id as u16,
|
|
||||||
affect_which: events,
|
|
||||||
clear: xcb::xkb::EventType::empty(),
|
|
||||||
select_all: events,
|
|
||||||
affect_map: map_parts,
|
|
||||||
map: map_parts,
|
|
||||||
details: &[],
|
|
||||||
}))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let phys_code_map = build_physkeycode_map(&keymap);
|
|
||||||
|
|
||||||
let default_keymap = default_keymap(&context)
|
|
||||||
.ok_or_else(|| anyhow!("Failed to load system default keymap"))?;
|
|
||||||
let default_state = xkb::State::new(&default_keymap);
|
|
||||||
|
|
||||||
let kbd = Keyboard {
|
|
||||||
context,
|
|
||||||
device_id,
|
|
||||||
keymap: RefCell::new(keymap),
|
|
||||||
state: RefCell::new(state),
|
|
||||||
_default_keymap: RefCell::new(default_keymap),
|
|
||||||
default_state: RefCell::new(default_state),
|
|
||||||
compose_state: RefCell::new(Compose {
|
|
||||||
state: compose_state,
|
|
||||||
composition: String::new(),
|
|
||||||
}),
|
|
||||||
phys_code_map: RefCell::new(phys_code_map),
|
|
||||||
mods_leds: RefCell::new(Default::default()),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((kbd, first_ev))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wayland_key_repeats(&self, code: u32) -> bool {
|
|
||||||
self.keymap.borrow().key_repeats(code + 8)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_wayland_key(
|
pub fn process_wayland_key(
|
||||||
@ -267,7 +173,7 @@ impl Keyboard {
|
|||||||
pressed: bool,
|
pressed: bool,
|
||||||
events: &mut WindowEventSender,
|
events: &mut WindowEventSender,
|
||||||
) -> Option<WindowKeyEvent> {
|
) -> Option<WindowKeyEvent> {
|
||||||
let want_repeat = self.wayland_key_repeats(code);
|
let want_repeat = self.selected.wayland_key_repeats(code);
|
||||||
self.process_key_event_impl(code + 8, pressed, events, want_repeat)
|
self.process_key_event_impl(code + 8, pressed, events, want_repeat)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,11 +202,12 @@ impl Keyboard {
|
|||||||
events: &mut WindowEventSender,
|
events: &mut WindowEventSender,
|
||||||
want_repeat: bool,
|
want_repeat: bool,
|
||||||
) -> Option<WindowKeyEvent> {
|
) -> Option<WindowKeyEvent> {
|
||||||
let phys_code = self.phys_code_map.borrow().get(&xcode).copied();
|
let phys_code = self.selected.phys_code_map.borrow().get(&xcode).copied();
|
||||||
let raw_modifiers = self.get_key_modifiers();
|
let raw_modifiers = self.get_key_modifiers();
|
||||||
let leds = self.get_led_status();
|
let leds = self.get_led_status();
|
||||||
|
|
||||||
let xsym = self.state.borrow().key_get_one_sym(xcode);
|
let xsym = self.selected.state.borrow().key_get_one_sym(xcode);
|
||||||
|
let fallback_xsym = self.fallback.state.borrow().key_get_one_sym(xcode);
|
||||||
let handled = Handled::new();
|
let handled = Handled::new();
|
||||||
|
|
||||||
let raw_key_event = RawKeyEvent {
|
let raw_key_event = RawKeyEvent {
|
||||||
@ -318,10 +225,12 @@ impl Keyboard {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut kc = None;
|
let mut kc = None;
|
||||||
|
|
||||||
let ksym = if pressed {
|
let ksym = if pressed {
|
||||||
events.dispatch(WindowEvent::RawKeyEvent(raw_key_event.clone()));
|
events.dispatch(WindowEvent::RawKeyEvent(raw_key_event.clone()));
|
||||||
if handled.is_handled() {
|
if handled.is_handled() {
|
||||||
self.compose_state.borrow_mut().reset();
|
self.selected.compose_clear();
|
||||||
|
self.fallback.compose_clear();
|
||||||
log::trace!("process_key_event: raw key was handled; not processing further");
|
log::trace!("process_key_event: raw key was handled; not processing further");
|
||||||
|
|
||||||
if want_repeat {
|
if want_repeat {
|
||||||
@ -330,11 +239,10 @@ impl Keyboard {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self
|
let fallback_feed = self.fallback.compose_feed(xcode, fallback_xsym);
|
||||||
.compose_state
|
let selected_feed = self.selected.compose_feed(xcode, xsym);
|
||||||
.borrow_mut()
|
|
||||||
.feed(xcode, xsym, &self.state)
|
match selected_feed {
|
||||||
{
|
|
||||||
FeedResult::Composing(composition) => {
|
FeedResult::Composing(composition) => {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"process_key_event: RawKeyEvent FeedResult::Composing: {:?}",
|
"process_key_event: RawKeyEvent FeedResult::Composing: {:?}",
|
||||||
@ -369,6 +277,7 @@ impl Keyboard {
|
|||||||
//
|
//
|
||||||
// <https://github.com/wez/wezterm/issues/1851>
|
// <https://github.com/wez/wezterm/issues/1851>
|
||||||
// <https://github.com/wez/wezterm/issues/2845>
|
// <https://github.com/wez/wezterm/issues/2845>
|
||||||
|
|
||||||
if !utf8.is_empty()
|
if !utf8.is_empty()
|
||||||
&& !raw_modifiers
|
&& !raw_modifiers
|
||||||
.intersects(Modifiers::CTRL | Modifiers::ALT | Modifiers::SUPER)
|
.intersects(Modifiers::CTRL | Modifiers::ALT | Modifiers::SUPER)
|
||||||
@ -376,23 +285,39 @@ impl Keyboard {
|
|||||||
kc.replace(crate::KeyCode::composed(&utf8));
|
kc.replace(crate::KeyCode::composed(&utf8));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't have a textual expansion in this case, we will
|
|
||||||
// consider the equivalent key from the system default / base
|
|
||||||
// layout.
|
|
||||||
// For example, if RU is active and they pressed CTRL-S that
|
|
||||||
// will produce utf8=ы here, which is not useful.
|
|
||||||
// Looking up in the default keymap will resolve us to the S
|
|
||||||
// key which is more desirable in the context of a terminal.
|
|
||||||
// default_xsym is that base key
|
|
||||||
let default_xsym = self.default_state.borrow().key_get_one_sym(xcode);
|
|
||||||
|
|
||||||
log::trace!(
|
log::trace!(
|
||||||
"process_key_event: RawKeyEvent FeedResult::Nothing: \
|
"process_key_event: RawKeyEvent FeedResult::Nothing: \
|
||||||
{utf8:?}, {sym:?}. kc -> {kc:?} def_sym={default_xsym:?}"
|
{utf8:?}, {sym:?}. kc -> {kc:?} fallback_feed={fallback_feed:?}"
|
||||||
);
|
);
|
||||||
if kc.is_none() {
|
|
||||||
// Use the default key layout symbol instead
|
// If we have a modified key, and its expansion is non-ascii, such as cyrillic
|
||||||
default_xsym
|
// "Es" (which appears visually similar to "c" in latin texts), then consider
|
||||||
|
// this key expansion against the default latin layout.
|
||||||
|
// This allows "CTRL-C" to work for users of cyrillic layouts
|
||||||
|
|
||||||
|
if kc.is_none()
|
||||||
|
&& raw_modifiers
|
||||||
|
.intersects(Modifiers::CTRL | Modifiers::ALT | Modifiers::SUPER)
|
||||||
|
{
|
||||||
|
match keysym_to_keycode(sym).or_else(|| keysym_to_keycode(xsym)) {
|
||||||
|
Some(crate::KeyCode::Char(c)) if !c.is_ascii() => {
|
||||||
|
// Potentially a Cyrillic or other non-european layout.
|
||||||
|
// Consider shortcuts like CTRL-C against the default
|
||||||
|
// latin layout
|
||||||
|
match fallback_feed {
|
||||||
|
FeedResult::Nothing(_fb_utf8, fb_sym) => {
|
||||||
|
log::trace!(
|
||||||
|
"process_key_event: RawKeyEvent using fallback \
|
||||||
|
sym {fb_sym} because layout would expand to \
|
||||||
|
non-ascii text {c:?}"
|
||||||
|
);
|
||||||
|
fb_sym
|
||||||
|
}
|
||||||
|
_ => sym,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => sym,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
sym
|
sym
|
||||||
}
|
}
|
||||||
@ -441,12 +366,13 @@ impl Keyboard {
|
|||||||
|
|
||||||
fn mod_is_active(&self, modifier: &str) -> bool {
|
fn mod_is_active(&self, modifier: &str) -> bool {
|
||||||
// [TODO] consider state Depressed & consumed mods
|
// [TODO] consider state Depressed & consumed mods
|
||||||
self.state
|
self.selected
|
||||||
|
.state
|
||||||
.borrow()
|
.borrow()
|
||||||
.mod_name_is_active(modifier, xkb::STATE_MODS_EFFECTIVE)
|
.mod_name_is_active(modifier, xkb::STATE_MODS_EFFECTIVE)
|
||||||
}
|
}
|
||||||
fn led_is_active(&self, led: &str) -> bool {
|
fn led_is_active(&self, led: &str) -> bool {
|
||||||
self.state.borrow().led_name_is_active(led)
|
self.selected.state.borrow().led_name_is_active(led)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_led_status(&self) -> KeyboardLedStatus {
|
pub fn get_led_status(&self) -> KeyboardLedStatus {
|
||||||
@ -487,7 +413,7 @@ impl Keyboard {
|
|||||||
connection: &xcb::Connection,
|
connection: &xcb::Connection,
|
||||||
event: &xcb::Event,
|
event: &xcb::Event,
|
||||||
) -> anyhow::Result<Option<(Modifiers, KeyboardLedStatus)>> {
|
) -> anyhow::Result<Option<(Modifiers, KeyboardLedStatus)>> {
|
||||||
let before = self.mods_leds.borrow().clone();
|
let before = self.selected.mods_leds.borrow().clone();
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
xcb::Event::Xkb(xcb::xkb::Event::StateNotify(e)) => {
|
xcb::Event::Xkb(xcb::xkb::Event::StateNotify(e)) => {
|
||||||
@ -503,13 +429,199 @@ impl Keyboard {
|
|||||||
|
|
||||||
let after = (self.get_key_modifiers(), self.get_led_status());
|
let after = (self.get_key_modifiers(), self.get_led_status());
|
||||||
if after != before {
|
if after != before {
|
||||||
*self.mods_leds.borrow_mut() = after.clone();
|
*self.selected.mods_leds.borrow_mut() = after.clone();
|
||||||
Ok(Some(after))
|
Ok(Some(after))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_modifier_state(
|
||||||
|
&self,
|
||||||
|
mods_depressed: u32,
|
||||||
|
mods_latched: u32,
|
||||||
|
mods_locked: u32,
|
||||||
|
group: u32,
|
||||||
|
) {
|
||||||
|
self.selected
|
||||||
|
.update_modifier_state(mods_depressed, mods_latched, mods_locked, group);
|
||||||
|
self.fallback
|
||||||
|
.update_modifier_state(mods_depressed, mods_latched, mods_locked, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_state(&self, ev: &xcb::xkb::StateNotifyEvent) {
|
||||||
|
self.selected.update_state(ev);
|
||||||
|
self.fallback.update_state(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_keymap(&self, connection: &xcb::Connection) -> anyhow::Result<()> {
|
||||||
|
self.selected.update_keymap(connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keyboard {
|
||||||
|
pub fn new_default() -> anyhow::Result<Self> {
|
||||||
|
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||||
|
let keymap = default_keymap(&context)
|
||||||
|
.ok_or_else(|| anyhow!("Failed to load system default keymap"))?;
|
||||||
|
|
||||||
|
for layout in keymap.layouts() {
|
||||||
|
log::debug!("loaded default keymap with layout: {layout}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = xkb::State::new(&keymap);
|
||||||
|
let locale = query_lc_ctype()?;
|
||||||
|
|
||||||
|
let table =
|
||||||
|
xkb::compose::Table::new_from_locale(&context, locale, xkb::compose::COMPILE_NO_FLAGS)
|
||||||
|
.map_err(|_| anyhow!("Failed to acquire compose table from locale"))?;
|
||||||
|
let compose_state = xkb::compose::State::new(&table, xkb::compose::STATE_NO_FLAGS);
|
||||||
|
|
||||||
|
let phys_code_map = build_physkeycode_map(&keymap);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
context,
|
||||||
|
device_id: -1,
|
||||||
|
keymap: RefCell::new(keymap),
|
||||||
|
state: RefCell::new(state),
|
||||||
|
compose_state: RefCell::new(Compose {
|
||||||
|
state: compose_state,
|
||||||
|
composition: String::new(),
|
||||||
|
}),
|
||||||
|
phys_code_map: RefCell::new(phys_code_map),
|
||||||
|
mods_leds: RefCell::new(Default::default()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_from_string(s: String) -> anyhow::Result<Self> {
|
||||||
|
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||||
|
let keymap = xkb::Keymap::new_from_string(
|
||||||
|
&context,
|
||||||
|
s,
|
||||||
|
xkbcommon::xkb::KEYMAP_FORMAT_TEXT_V1,
|
||||||
|
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||||
|
)
|
||||||
|
.ok_or_else(|| anyhow!("Failed to parse keymap state from file"))?;
|
||||||
|
|
||||||
|
for layout in keymap.layouts() {
|
||||||
|
log::debug!("loaded new keymap with layout: {layout}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = xkb::State::new(&keymap);
|
||||||
|
let locale = query_lc_ctype()?;
|
||||||
|
|
||||||
|
let table =
|
||||||
|
xkb::compose::Table::new_from_locale(&context, locale, xkb::compose::COMPILE_NO_FLAGS)
|
||||||
|
.map_err(|_| anyhow!("Failed to acquire compose table from locale"))?;
|
||||||
|
let compose_state = xkb::compose::State::new(&table, xkb::compose::STATE_NO_FLAGS);
|
||||||
|
|
||||||
|
let phys_code_map = build_physkeycode_map(&keymap);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
context,
|
||||||
|
device_id: -1,
|
||||||
|
keymap: RefCell::new(keymap),
|
||||||
|
state: RefCell::new(state),
|
||||||
|
compose_state: RefCell::new(Compose {
|
||||||
|
state: compose_state,
|
||||||
|
composition: String::new(),
|
||||||
|
}),
|
||||||
|
phys_code_map: RefCell::new(phys_code_map),
|
||||||
|
mods_leds: RefCell::new(Default::default()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(connection: &xcb::Connection) -> anyhow::Result<(Keyboard, u8)> {
|
||||||
|
let first_ev = xcb::xkb::get_extension_data(connection)
|
||||||
|
.ok_or_else(|| anyhow!("could not get xkb extension data"))?
|
||||||
|
.first_event;
|
||||||
|
|
||||||
|
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||||
|
let device_id = xkb::x11::get_core_keyboard_device_id(&connection);
|
||||||
|
ensure!(device_id != -1, "Couldn't find core keyboard device");
|
||||||
|
|
||||||
|
let keymap = xkb::x11::keymap_new_from_device(
|
||||||
|
&context,
|
||||||
|
&connection,
|
||||||
|
device_id,
|
||||||
|
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||||
|
);
|
||||||
|
|
||||||
|
for layout in keymap.layouts() {
|
||||||
|
log::debug!("loaded initial keymap with layout: {layout}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = xkb::x11::state_new_from_device(&keymap, connection, device_id);
|
||||||
|
|
||||||
|
let locale = query_lc_ctype()?;
|
||||||
|
|
||||||
|
let table =
|
||||||
|
xkb::compose::Table::new_from_locale(&context, locale, xkb::compose::COMPILE_NO_FLAGS)
|
||||||
|
.map_err(|_| anyhow!("Failed to acquire compose table from locale"))?;
|
||||||
|
let compose_state = xkb::compose::State::new(&table, xkb::compose::STATE_NO_FLAGS);
|
||||||
|
|
||||||
|
{
|
||||||
|
let map_parts = xcb::xkb::MapPart::KEY_TYPES
|
||||||
|
| xcb::xkb::MapPart::KEY_SYMS
|
||||||
|
| xcb::xkb::MapPart::MODIFIER_MAP
|
||||||
|
| xcb::xkb::MapPart::EXPLICIT_COMPONENTS
|
||||||
|
| xcb::xkb::MapPart::KEY_ACTIONS
|
||||||
|
| xcb::xkb::MapPart::KEY_BEHAVIORS
|
||||||
|
| xcb::xkb::MapPart::VIRTUAL_MODS
|
||||||
|
| xcb::xkb::MapPart::VIRTUAL_MOD_MAP;
|
||||||
|
|
||||||
|
let events = xcb::xkb::EventType::NEW_KEYBOARD_NOTIFY
|
||||||
|
| xcb::xkb::EventType::MAP_NOTIFY
|
||||||
|
| xcb::xkb::EventType::STATE_NOTIFY;
|
||||||
|
|
||||||
|
connection.check_request(connection.send_request_checked(&xcb::xkb::SelectEvents {
|
||||||
|
device_spec: device_id as u16,
|
||||||
|
affect_which: events,
|
||||||
|
clear: xcb::xkb::EventType::empty(),
|
||||||
|
select_all: events,
|
||||||
|
affect_map: map_parts,
|
||||||
|
map: map_parts,
|
||||||
|
details: &[],
|
||||||
|
}))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let phys_code_map = build_physkeycode_map(&keymap);
|
||||||
|
|
||||||
|
let kbd = Keyboard {
|
||||||
|
context,
|
||||||
|
device_id,
|
||||||
|
keymap: RefCell::new(keymap),
|
||||||
|
state: RefCell::new(state),
|
||||||
|
compose_state: RefCell::new(Compose {
|
||||||
|
state: compose_state,
|
||||||
|
composition: String::new(),
|
||||||
|
}),
|
||||||
|
phys_code_map: RefCell::new(phys_code_map),
|
||||||
|
mods_leds: RefCell::new(Default::default()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((kbd, first_ev))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if a given wayland keycode allows for automatic key repeats
|
||||||
|
pub fn wayland_key_repeats(&self, code: u32) -> bool {
|
||||||
|
self.keymap.borrow().key_repeats(code + 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_device_id(&self) -> i32 {
|
||||||
|
self.device_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compose_feed(&self, xcode: xkb::Keycode, xsym: xkb::Keysym) -> FeedResult {
|
||||||
|
self.compose_state
|
||||||
|
.borrow_mut()
|
||||||
|
.feed(xcode, xsym, &self.state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compose_clear(&self) {
|
||||||
|
self.compose_state.borrow_mut().reset();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_modifier_state(
|
pub fn update_modifier_state(
|
||||||
&self,
|
&self,
|
||||||
mods_depressed: u32,
|
mods_depressed: u32,
|
||||||
@ -539,6 +651,8 @@ impl Keyboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_keymap(&self, connection: &xcb::Connection) -> anyhow::Result<()> {
|
pub fn update_keymap(&self, connection: &xcb::Connection) -> anyhow::Result<()> {
|
||||||
|
log::debug!("update_keymap was called");
|
||||||
|
|
||||||
let new_keymap = xkb::x11::keymap_new_from_device(
|
let new_keymap = xkb::x11::keymap_new_from_device(
|
||||||
&self.context,
|
&self.context,
|
||||||
&connection,
|
&connection,
|
||||||
@ -549,6 +663,9 @@ impl Keyboard {
|
|||||||
!new_keymap.get_raw_ptr().is_null(),
|
!new_keymap.get_raw_ptr().is_null(),
|
||||||
"problem with new keymap"
|
"problem with new keymap"
|
||||||
);
|
);
|
||||||
|
for layout in new_keymap.layouts() {
|
||||||
|
log::debug!("loaded changed keymap with layout: {layout}");
|
||||||
|
}
|
||||||
|
|
||||||
let new_state = xkb::x11::state_new_from_device(&new_keymap, connection, self.device_id);
|
let new_state = xkb::x11::state_new_from_device(&new_keymap, connection, self.device_id);
|
||||||
ensure!(!new_state.get_raw_ptr().is_null(), "problem with new state");
|
ensure!(!new_state.get_raw_ptr().is_null(), "problem with new state");
|
||||||
@ -559,10 +676,6 @@ impl Keyboard {
|
|||||||
self.phys_code_map.replace(phys_code_map);
|
self.phys_code_map.replace(phys_code_map);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_device_id(&self) -> i32 {
|
|
||||||
self.device_id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn query_lc_ctype() -> anyhow::Result<&'static OsStr> {
|
fn query_lc_ctype() -> anyhow::Result<&'static OsStr> {
|
||||||
|
Loading…
Reference in New Issue
Block a user