1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-19 02:37:51 +03:00

x11/wayland: improve non-latin ctrl/alt keypresses

For eg: RU layout, CTRL-S shouldn't result in ы in the context
of a terminal.

The approach taken here is similar to kitty; when the key combination
doesn't produce a definitive composed output, and when any of
ctrl/alt/super are present, we treat the keypress as though it were
the same as the one from the system default keymap.

The result is that ctrl-c now works like ctrl-c and alt-b and alt-f work
like their latin counterparts.

Hopefully there are no downsides to this!

refs: https://github.com/wez/wezterm/issues/2845
refs: https://github.com/kovidgoyal/kitty/issues/606
This commit is contained in:
Wez Furlong 2023-04-19 09:37:58 -07:00
parent db720bd78c
commit 21e19ca091
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
2 changed files with 72 additions and 14 deletions

View File

@ -62,6 +62,7 @@ As features stabilize some brief notes about them will accumulate here.
* Windows: inconsistencies with win32 input mode. Thanks to @kreudom! #2235
* macOS: font size is zoomed or window appears empty when first launched on a
secondary monitor with different scaling settings from the primary monitor. #3503
* X11/Wayland: CTRL/ALT didn't work as expected for non-latin keyboard layouts. #2845
### 20230408-112425-69ae8472

View File

@ -16,9 +16,11 @@ use xkbcommon::xkb;
pub struct Keyboard {
context: xkb::Context,
keymap: RefCell<xkb::Keymap>,
_default_keymap: RefCell<xkb::Keymap>,
device_id: i32,
state: RefCell<xkb::State>,
default_state: RefCell<xkb::State>,
compose_state: RefCell<Compose>,
phys_code_map: RefCell<HashMap<xkb::Keycode, PhysKeyCode>>,
mods_leds: RefCell<(Modifiers, KeyboardLedStatus)>,
@ -109,17 +111,7 @@ impl Compose {
}
ComposeStatus::Nothing => {
let utf8 = key_state.borrow().key_get_utf8(xcode);
// CTRL-<ALPHA> is helpfully encoded in the form that we would
// send to the terminal, however, we do want the chance to
// distinguish between eg: CTRL-i and Tab, so if we ended up
// with a control code representation from the xkeyboard layer,
// discard it.
// <https://github.com/wez/wezterm/issues/1851>
if utf8.len() == 1 && utf8.as_bytes()[0] < 0x20 {
FeedResult::Nothing(String::new(), xsym)
} else {
FeedResult::Nothing(utf8, xsym)
}
FeedResult::Nothing(utf8, xsym)
}
ComposeStatus::Cancelled => {
self.state.reset();
@ -129,6 +121,27 @@ impl Compose {
}
}
fn default_keymap(context: &xkb::Context) -> Option<xkb::Keymap> {
// use $XKB_DEFAULT_RULES or system default
let system_default_rules = "";
// use $XKB_DEFAULT_MODEL or system default
let system_default_model = "";
// use $XKB_DEFAULT_LAYOUT or system default
let system_default_layout = "";
// use $XKB_DEFAULT_VARIANT or system default
let system_default_variant = "";
xkb::Keymap::new_from_names(
context,
system_default_rules,
system_default_model,
system_default_layout,
system_default_variant,
None,
xkb::KEYMAP_COMPILE_NO_FLAGS,
)
}
impl Keyboard {
pub fn new_from_string(s: String) -> anyhow::Result<Self> {
let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
@ -150,11 +163,17 @@ impl Keyboard {
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 {
context,
device_id: -1,
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(),
@ -215,11 +234,18 @@ impl Keyboard {
}
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(),
@ -331,14 +357,45 @@ impl Keyboard {
sym
}
FeedResult::Nothing(utf8, sym) => {
if !utf8.is_empty() {
// Composition had no special expansion.
// Xkb will return a textual representation of the key even when
// it is not generally useful; for example, when CTRL, ALT or SUPER
// are held, we don't want its mapping as it can be counterproductive:
// CTRL-<ALPHA> is helpfully encoded in the form that we would
// send to the terminal, however, we do want the chance to
// distinguish between eg: CTRL-i and Tab.
//
// This logic excludes that textual expansion for this situation.
//
// <https://github.com/wez/wezterm/issues/1851>
// <https://github.com/wez/wezterm/issues/2845>
if !utf8.is_empty()
&& !raw_modifiers
.intersects(Modifiers::CTRL | Modifiers::ALT | Modifiers::SUPER)
{
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!(
"process_key_event: RawKeyEvent FeedResult::Nothing: \
{utf8:?}, {sym:?}. kc -> {kc:?}"
{utf8:?}, {sym:?}. kc -> {kc:?} def_sym={default_xsym:?}"
);
sym
if kc.is_none() {
// Use the default key layout symbol instead
default_xsym
} else {
sym
}
}
FeedResult::Cancelled => {
log::trace!("process_key_event: RawKeyEvent FeedResult::Cancelled");