Compare commits
48 commits
main
...
linux/keys
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8276554222 | ||
![]() |
2400ed34e0 | ||
![]() |
ce23637dc1 | ||
![]() |
7bcd6d839f | ||
![]() |
20b9989b79 | ||
![]() |
3269029a3e | ||
![]() |
ea0e908714 | ||
![]() |
8b69ccb488 | ||
![]() |
2d6d53219b | ||
![]() |
de615870cc | ||
![]() |
f1f9470a14 | ||
![]() |
9433c381eb | ||
![]() |
6ede6a1573 | ||
![]() |
7e0d74db8a | ||
![]() |
e019ce8260 | ||
![]() |
7ab7eab54e | ||
![]() |
7da2e2af50 | ||
![]() |
92a986cffd | ||
![]() |
8b6658c650 | ||
![]() |
9bc4c0f7f5 | ||
![]() |
f829a178f6 | ||
![]() |
2df80de880 | ||
![]() |
2976b99b14 | ||
![]() |
ad3d44119a | ||
![]() |
0b5a264f88 | ||
![]() |
f22d06cf6e | ||
![]() |
26718da9bb | ||
![]() |
671a91e69d | ||
![]() |
bbda5d4f78 | ||
![]() |
2fb674088f | ||
![]() |
5c4ea49793 | ||
![]() |
21e7cc3fed | ||
![]() |
f53168c56c | ||
![]() |
3182f14972 | ||
![]() |
eca211e0f1 | ||
![]() |
d6add799dc | ||
![]() |
e9649dc25c | ||
![]() |
71daf47ad5 | ||
![]() |
66c6f5066e | ||
![]() |
be79ccde07 | ||
![]() |
061ba9b6d2 | ||
![]() |
1ba3c2f589 | ||
![]() |
bbee877ca8 | ||
![]() |
c6e697fc7f | ||
![]() |
b7b83105fc | ||
![]() |
7d901c5e47 | ||
![]() |
1a8d1944b0 | ||
![]() |
35db644beb |
6 changed files with 633 additions and 168 deletions
13
.github/actions/run_tests/action.yml
vendored
13
.github/actions/run_tests/action.yml
vendored
|
@ -1,6 +1,12 @@
|
|||
name: "Run tests"
|
||||
description: "Runs the tests"
|
||||
|
||||
inputs:
|
||||
use-xvfb:
|
||||
description: "Whether to run tests with xvfb"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
|
@ -20,4 +26,9 @@ runs:
|
|||
|
||||
- name: Run tests
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: cargo nextest run --workspace --no-fail-fast
|
||||
run: |
|
||||
if [ "${{ inputs.use-xvfb }}" == "true" ]; then
|
||||
xvfb-run --auto-servernum --server-args="-screen 0 1024x768x24 -nolisten tcp" cargo nextest run --workspace --no-fail-fast
|
||||
else
|
||||
cargo nextest run --workspace --no-fail-fast
|
||||
fi
|
||||
|
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -334,6 +334,8 @@ jobs:
|
|||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests
|
||||
with:
|
||||
use-xvfb: true
|
||||
|
||||
- name: Build other binaries and features
|
||||
run: |
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use collections::{HashMap, HashSet};
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use strum::{EnumIter, IntoEnumIterator as _};
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use xkbcommon::xkb::{Keycode, Keymap, Keysym, MOD_NAME_SHIFT, State};
|
||||
|
||||
use crate::{PlatformKeyboardLayout, SharedString};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct LinuxKeyboardLayout {
|
||||
name: SharedString,
|
||||
}
|
||||
|
@ -20,3 +27,449 @@ impl LinuxKeyboardLayout {
|
|||
Self { name }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
pub(crate) struct LinuxKeyboardMapper {
|
||||
letters: HashMap<Keycode, String>,
|
||||
code_to_key: HashMap<Keycode, String>,
|
||||
code_to_shifted_key: HashMap<Keycode, String>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
impl LinuxKeyboardMapper {
|
||||
pub(crate) fn new(
|
||||
keymap: &Keymap,
|
||||
base_group: u32,
|
||||
latched_group: u32,
|
||||
locked_group: u32,
|
||||
) -> Self {
|
||||
let mut xkb_state = State::new(keymap);
|
||||
xkb_state.update_mask(0, 0, 0, base_group, latched_group, locked_group);
|
||||
|
||||
let mut shifted_state = State::new(&keymap);
|
||||
let shift_mod = keymap.mod_get_index(MOD_NAME_SHIFT);
|
||||
let shift_mask = 1 << shift_mod;
|
||||
shifted_state.update_mask(shift_mask, 0, 0, base_group, latched_group, locked_group);
|
||||
|
||||
let mut letters = HashMap::default();
|
||||
let mut code_to_key = HashMap::default();
|
||||
let mut code_to_shifted_key = HashMap::default();
|
||||
let mut inserted_letters = HashSet::default();
|
||||
|
||||
for scan_code in LinuxScanCodes::iter() {
|
||||
let keycode = Keycode::new(scan_code as u32);
|
||||
|
||||
let key = xkb_state.key_get_utf8(keycode);
|
||||
if !key.is_empty() {
|
||||
if key_is_a_letter(&key) {
|
||||
letters.insert(keycode, key.clone());
|
||||
inserted_letters.insert(key);
|
||||
} else {
|
||||
code_to_key.insert(keycode, key.clone());
|
||||
}
|
||||
} else {
|
||||
// keycode might be a dead key
|
||||
let keysym = xkb_state.key_get_one_sym(keycode);
|
||||
if let Some(key) = underlying_dead_key(keysym) {
|
||||
code_to_key.insert(keycode, key.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let shifted_key = shifted_state.key_get_utf8(keycode);
|
||||
if !shifted_key.is_empty() {
|
||||
code_to_shifted_key.insert(keycode, shifted_key);
|
||||
} else {
|
||||
// keycode might be a dead key
|
||||
let shifted_keysym = shifted_state.key_get_one_sym(keycode);
|
||||
if let Some(shifted_key) = underlying_dead_key(shifted_keysym) {
|
||||
code_to_shifted_key.insert(keycode, shifted_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
insert_letters_if_missing(&inserted_letters, &mut letters);
|
||||
|
||||
Self {
|
||||
letters,
|
||||
code_to_key,
|
||||
code_to_shifted_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_key(
|
||||
&self,
|
||||
keycode: Keycode,
|
||||
modifiers: &mut crate::Modifiers,
|
||||
) -> Option<String> {
|
||||
if let Some(key) = self.letters.get(&keycode) {
|
||||
return Some(key.clone());
|
||||
}
|
||||
if modifiers.shift {
|
||||
modifiers.shift = false;
|
||||
self.code_to_shifted_key.get(&keycode).cloned()
|
||||
} else {
|
||||
self.code_to_key.get(&keycode).cloned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
fn key_is_a_letter(key: &str) -> bool {
|
||||
matches!(
|
||||
key,
|
||||
"a" | "b"
|
||||
| "c"
|
||||
| "d"
|
||||
| "e"
|
||||
| "f"
|
||||
| "g"
|
||||
| "h"
|
||||
| "i"
|
||||
| "j"
|
||||
| "k"
|
||||
| "l"
|
||||
| "m"
|
||||
| "n"
|
||||
| "o"
|
||||
| "p"
|
||||
| "q"
|
||||
| "r"
|
||||
| "s"
|
||||
| "t"
|
||||
| "u"
|
||||
| "v"
|
||||
| "w"
|
||||
| "x"
|
||||
| "y"
|
||||
| "z"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns which symbol the dead key represents
|
||||
* <https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#dead_keycodes_for_linux>
|
||||
*/
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
pub(crate) fn underlying_dead_key(keysym: Keysym) -> Option<String> {
|
||||
match keysym {
|
||||
Keysym::dead_grave => Some("`".to_owned()),
|
||||
Keysym::dead_acute => Some("´".to_owned()),
|
||||
Keysym::dead_circumflex => Some("^".to_owned()),
|
||||
Keysym::dead_tilde => Some("~".to_owned()),
|
||||
Keysym::dead_macron => Some("¯".to_owned()),
|
||||
Keysym::dead_breve => Some("˘".to_owned()),
|
||||
Keysym::dead_abovedot => Some("˙".to_owned()),
|
||||
Keysym::dead_diaeresis => Some("¨".to_owned()),
|
||||
Keysym::dead_abovering => Some("˚".to_owned()),
|
||||
Keysym::dead_doubleacute => Some("˝".to_owned()),
|
||||
Keysym::dead_caron => Some("ˇ".to_owned()),
|
||||
Keysym::dead_cedilla => Some("¸".to_owned()),
|
||||
Keysym::dead_ogonek => Some("˛".to_owned()),
|
||||
Keysym::dead_iota => Some("ͅ".to_owned()),
|
||||
Keysym::dead_voiced_sound => Some("゙".to_owned()),
|
||||
Keysym::dead_semivoiced_sound => Some("゚".to_owned()),
|
||||
Keysym::dead_belowdot => Some("̣̣".to_owned()),
|
||||
Keysym::dead_hook => Some("̡".to_owned()),
|
||||
Keysym::dead_horn => Some("̛".to_owned()),
|
||||
Keysym::dead_stroke => Some("̶̶".to_owned()),
|
||||
Keysym::dead_abovecomma => Some("̓̓".to_owned()),
|
||||
Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()),
|
||||
Keysym::dead_doublegrave => Some("̏".to_owned()),
|
||||
Keysym::dead_belowring => Some("˳".to_owned()),
|
||||
Keysym::dead_belowmacron => Some("̱".to_owned()),
|
||||
Keysym::dead_belowcircumflex => Some("ꞈ".to_owned()),
|
||||
Keysym::dead_belowtilde => Some("̰".to_owned()),
|
||||
Keysym::dead_belowbreve => Some("̮".to_owned()),
|
||||
Keysym::dead_belowdiaeresis => Some("̤".to_owned()),
|
||||
Keysym::dead_invertedbreve => Some("̯".to_owned()),
|
||||
Keysym::dead_belowcomma => Some("̦".to_owned()),
|
||||
Keysym::dead_currency => None,
|
||||
Keysym::dead_lowline => None,
|
||||
Keysym::dead_aboveverticalline => None,
|
||||
Keysym::dead_belowverticalline => None,
|
||||
Keysym::dead_longsolidusoverlay => None,
|
||||
Keysym::dead_a => None,
|
||||
Keysym::dead_A => None,
|
||||
Keysym::dead_e => None,
|
||||
Keysym::dead_E => None,
|
||||
Keysym::dead_i => None,
|
||||
Keysym::dead_I => None,
|
||||
Keysym::dead_o => None,
|
||||
Keysym::dead_O => None,
|
||||
Keysym::dead_u => None,
|
||||
Keysym::dead_U => None,
|
||||
Keysym::dead_small_schwa => Some("ə".to_owned()),
|
||||
Keysym::dead_capital_schwa => Some("Ə".to_owned()),
|
||||
Keysym::dead_greek => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
fn insert_letters_if_missing(inserted: &HashSet<String>, letters: &mut HashMap<Keycode, String>) {
|
||||
for scan_code in LinuxScanCodes::LETTERS.iter() {
|
||||
let keycode = Keycode::new(*scan_code as u32);
|
||||
let key = scan_code.to_str();
|
||||
if !inserted.contains(key) {
|
||||
letters.insert(keycode, key.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)]
|
||||
enum LinuxScanCodes {
|
||||
A = 0x0026,
|
||||
B = 0x0038,
|
||||
C = 0x0036,
|
||||
D = 0x0028,
|
||||
E = 0x001a,
|
||||
F = 0x0029,
|
||||
G = 0x002a,
|
||||
H = 0x002b,
|
||||
I = 0x001f,
|
||||
J = 0x002c,
|
||||
K = 0x002d,
|
||||
L = 0x002e,
|
||||
M = 0x003a,
|
||||
N = 0x0039,
|
||||
O = 0x0020,
|
||||
P = 0x0021,
|
||||
Q = 0x0018,
|
||||
R = 0x001b,
|
||||
S = 0x0027,
|
||||
T = 0x001c,
|
||||
U = 0x001e,
|
||||
V = 0x0037,
|
||||
W = 0x0019,
|
||||
X = 0x0035,
|
||||
Y = 0x001d,
|
||||
Z = 0x0034,
|
||||
Digit0 = 0x0013,
|
||||
Digit1 = 0x000a,
|
||||
Digit2 = 0x000b,
|
||||
Digit3 = 0x000c,
|
||||
Digit4 = 0x000d,
|
||||
Digit5 = 0x000e,
|
||||
Digit6 = 0x000f,
|
||||
Digit7 = 0x0010,
|
||||
Digit8 = 0x0011,
|
||||
Digit9 = 0x0012,
|
||||
Backquote = 0x0031,
|
||||
Minus = 0x0014,
|
||||
Equal = 0x0015,
|
||||
LeftBracket = 0x0022,
|
||||
RightBracket = 0x0023,
|
||||
Backslash = 0x0033,
|
||||
Semicolon = 0x002f,
|
||||
Quote = 0x0030,
|
||||
Comma = 0x003b,
|
||||
Period = 0x003c,
|
||||
Slash = 0x003d,
|
||||
// This key is typically located near LeftShift key, varies on international keyboards: Dan: <> Dutch: ][ Ger: <> UK: \|
|
||||
IntlBackslash = 0x005e,
|
||||
// Used for Brazilian /? and Japanese _ 'ro'.
|
||||
IntlRo = 0x0061,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
impl LinuxScanCodes {
|
||||
const LETTERS: &'static [LinuxScanCodes] = &[
|
||||
LinuxScanCodes::A,
|
||||
LinuxScanCodes::B,
|
||||
LinuxScanCodes::C,
|
||||
LinuxScanCodes::D,
|
||||
LinuxScanCodes::E,
|
||||
LinuxScanCodes::F,
|
||||
LinuxScanCodes::G,
|
||||
LinuxScanCodes::H,
|
||||
LinuxScanCodes::I,
|
||||
LinuxScanCodes::J,
|
||||
LinuxScanCodes::K,
|
||||
LinuxScanCodes::L,
|
||||
LinuxScanCodes::M,
|
||||
LinuxScanCodes::N,
|
||||
LinuxScanCodes::O,
|
||||
LinuxScanCodes::P,
|
||||
LinuxScanCodes::Q,
|
||||
LinuxScanCodes::R,
|
||||
LinuxScanCodes::S,
|
||||
LinuxScanCodes::T,
|
||||
LinuxScanCodes::U,
|
||||
LinuxScanCodes::V,
|
||||
LinuxScanCodes::W,
|
||||
LinuxScanCodes::X,
|
||||
LinuxScanCodes::Y,
|
||||
LinuxScanCodes::Z,
|
||||
];
|
||||
|
||||
fn to_str(&self) -> &str {
|
||||
match self {
|
||||
LinuxScanCodes::A => "a",
|
||||
LinuxScanCodes::B => "b",
|
||||
LinuxScanCodes::C => "c",
|
||||
LinuxScanCodes::D => "d",
|
||||
LinuxScanCodes::E => "e",
|
||||
LinuxScanCodes::F => "f",
|
||||
LinuxScanCodes::G => "g",
|
||||
LinuxScanCodes::H => "h",
|
||||
LinuxScanCodes::I => "i",
|
||||
LinuxScanCodes::J => "j",
|
||||
LinuxScanCodes::K => "k",
|
||||
LinuxScanCodes::L => "l",
|
||||
LinuxScanCodes::M => "m",
|
||||
LinuxScanCodes::N => "n",
|
||||
LinuxScanCodes::O => "o",
|
||||
LinuxScanCodes::P => "p",
|
||||
LinuxScanCodes::Q => "q",
|
||||
LinuxScanCodes::R => "r",
|
||||
LinuxScanCodes::S => "s",
|
||||
LinuxScanCodes::T => "t",
|
||||
LinuxScanCodes::U => "u",
|
||||
LinuxScanCodes::V => "v",
|
||||
LinuxScanCodes::W => "w",
|
||||
LinuxScanCodes::X => "x",
|
||||
LinuxScanCodes::Y => "y",
|
||||
LinuxScanCodes::Z => "z",
|
||||
LinuxScanCodes::Digit0 => "0",
|
||||
LinuxScanCodes::Digit1 => "1",
|
||||
LinuxScanCodes::Digit2 => "2",
|
||||
LinuxScanCodes::Digit3 => "3",
|
||||
LinuxScanCodes::Digit4 => "4",
|
||||
LinuxScanCodes::Digit5 => "5",
|
||||
LinuxScanCodes::Digit6 => "6",
|
||||
LinuxScanCodes::Digit7 => "7",
|
||||
LinuxScanCodes::Digit8 => "8",
|
||||
LinuxScanCodes::Digit9 => "9",
|
||||
LinuxScanCodes::Backquote => "`",
|
||||
LinuxScanCodes::Minus => "-",
|
||||
LinuxScanCodes::Equal => "=",
|
||||
LinuxScanCodes::LeftBracket => "[",
|
||||
LinuxScanCodes::RightBracket => "]",
|
||||
LinuxScanCodes::Backslash => "\\",
|
||||
LinuxScanCodes::Semicolon => ";",
|
||||
LinuxScanCodes::Quote => "'",
|
||||
LinuxScanCodes::Comma => ",",
|
||||
LinuxScanCodes::Period => ".",
|
||||
LinuxScanCodes::Slash => "/",
|
||||
LinuxScanCodes::IntlBackslash => "unknown",
|
||||
LinuxScanCodes::IntlRo => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn to_shifted(&self) -> &str {
|
||||
match self {
|
||||
LinuxScanCodes::A => "a",
|
||||
LinuxScanCodes::B => "b",
|
||||
LinuxScanCodes::C => "c",
|
||||
LinuxScanCodes::D => "d",
|
||||
LinuxScanCodes::E => "e",
|
||||
LinuxScanCodes::F => "f",
|
||||
LinuxScanCodes::G => "g",
|
||||
LinuxScanCodes::H => "h",
|
||||
LinuxScanCodes::I => "i",
|
||||
LinuxScanCodes::J => "j",
|
||||
LinuxScanCodes::K => "k",
|
||||
LinuxScanCodes::L => "l",
|
||||
LinuxScanCodes::M => "m",
|
||||
LinuxScanCodes::N => "n",
|
||||
LinuxScanCodes::O => "o",
|
||||
LinuxScanCodes::P => "p",
|
||||
LinuxScanCodes::Q => "q",
|
||||
LinuxScanCodes::R => "r",
|
||||
LinuxScanCodes::S => "s",
|
||||
LinuxScanCodes::T => "t",
|
||||
LinuxScanCodes::U => "u",
|
||||
LinuxScanCodes::V => "v",
|
||||
LinuxScanCodes::W => "w",
|
||||
LinuxScanCodes::X => "x",
|
||||
LinuxScanCodes::Y => "y",
|
||||
LinuxScanCodes::Z => "z",
|
||||
LinuxScanCodes::Digit0 => ")",
|
||||
LinuxScanCodes::Digit1 => "!",
|
||||
LinuxScanCodes::Digit2 => "@",
|
||||
LinuxScanCodes::Digit3 => "#",
|
||||
LinuxScanCodes::Digit4 => "$",
|
||||
LinuxScanCodes::Digit5 => "%",
|
||||
LinuxScanCodes::Digit6 => "^",
|
||||
LinuxScanCodes::Digit7 => "&",
|
||||
LinuxScanCodes::Digit8 => "*",
|
||||
LinuxScanCodes::Digit9 => "(",
|
||||
LinuxScanCodes::Backquote => "~",
|
||||
LinuxScanCodes::Minus => "_",
|
||||
LinuxScanCodes::Equal => "+",
|
||||
LinuxScanCodes::LeftBracket => "{",
|
||||
LinuxScanCodes::RightBracket => "}",
|
||||
LinuxScanCodes::Backslash => "|",
|
||||
LinuxScanCodes::Semicolon => ":",
|
||||
LinuxScanCodes::Quote => "\"",
|
||||
LinuxScanCodes::Comma => "<",
|
||||
LinuxScanCodes::Period => ">",
|
||||
LinuxScanCodes::Slash => "?",
|
||||
LinuxScanCodes::IntlBackslash => "unknown",
|
||||
LinuxScanCodes::IntlRo => "unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, any(feature = "wayland", feature = "x11")))]
|
||||
mod tests {
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
use x11rb::{protocol::xkb::ConnectionExt, xcb_ffi::XCBConnection};
|
||||
use xkbcommon::xkb::{
|
||||
CONTEXT_NO_FLAGS, KEYMAP_COMPILE_NO_FLAGS, Keymap,
|
||||
x11::ffi::{XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION},
|
||||
};
|
||||
|
||||
use crate::platform::linux::keyboard::LinuxScanCodes;
|
||||
|
||||
use super::LinuxKeyboardMapper;
|
||||
|
||||
fn get_keymap() -> Keymap {
|
||||
static XCB_CONNECTION: LazyLock<XCBConnection> =
|
||||
LazyLock::new(|| XCBConnection::connect(None).unwrap().0);
|
||||
|
||||
let _ = XCB_CONNECTION
|
||||
.xkb_use_extension(XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION)
|
||||
.unwrap()
|
||||
.reply()
|
||||
.unwrap();
|
||||
let xkb_context = xkbcommon::xkb::Context::new(CONTEXT_NO_FLAGS);
|
||||
let xkb_device_id = xkbcommon::xkb::x11::get_core_keyboard_device_id(&*XCB_CONNECTION);
|
||||
xkbcommon::xkb::x11::keymap_new_from_device(
|
||||
&xkb_context,
|
||||
&*XCB_CONNECTION,
|
||||
xkb_device_id,
|
||||
KEYMAP_COMPILE_NO_FLAGS,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_us_layout_mapper() {
|
||||
let keymap = get_keymap();
|
||||
let mapper = LinuxKeyboardMapper::new(&keymap, 0, 0, 0);
|
||||
for scan_code in super::LinuxScanCodes::iter() {
|
||||
if scan_code == LinuxScanCodes::IntlBackslash || scan_code == LinuxScanCodes::IntlRo {
|
||||
continue;
|
||||
}
|
||||
let keycode = xkbcommon::xkb::Keycode::new(scan_code as u32);
|
||||
let key = mapper
|
||||
.get_key(keycode, &mut crate::Modifiers::default())
|
||||
.unwrap();
|
||||
assert_eq!(key.as_str(), scan_code.to_str());
|
||||
|
||||
let shifted_key = mapper
|
||||
.get_key(
|
||||
keycode,
|
||||
&mut crate::Modifiers {
|
||||
shift: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(shifted_key.as_str(), scan_code.to_shifted());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ use crate::{
|
|||
Point, Result, Task, WindowAppearance, WindowParams, px,
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
use super::LinuxKeyboardMapper;
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
pub(crate) const SCROLL_LINES: f32 = 3.0;
|
||||
|
||||
|
@ -710,6 +713,7 @@ pub(super) fn log_cursor_icon_warning(message: impl std::fmt::Display) {
|
|||
impl crate::Keystroke {
|
||||
pub(super) fn from_xkb(
|
||||
state: &State,
|
||||
keyboard_mapper: &LinuxKeyboardMapper,
|
||||
mut modifiers: crate::Modifiers,
|
||||
keycode: Keycode,
|
||||
) -> Self {
|
||||
|
@ -718,76 +722,67 @@ impl crate::Keystroke {
|
|||
let key_sym = state.key_get_one_sym(keycode);
|
||||
|
||||
let key = match key_sym {
|
||||
Keysym::space => "space".to_owned(),
|
||||
Keysym::BackSpace => "backspace".to_owned(),
|
||||
Keysym::Return => "enter".to_owned(),
|
||||
Keysym::Prior => "pageup".to_owned(),
|
||||
Keysym::Next => "pagedown".to_owned(),
|
||||
// Keysym::Tab => "tab".to_owned(),
|
||||
Keysym::ISO_Left_Tab => "tab".to_owned(),
|
||||
Keysym::KP_Prior => "pageup".to_owned(),
|
||||
Keysym::KP_Next => "pagedown".to_owned(),
|
||||
Keysym::uparrow => "up".to_owned(),
|
||||
Keysym::downarrow => "down".to_owned(),
|
||||
Keysym::leftarrow => "left".to_owned(),
|
||||
Keysym::rightarrow => "right".to_owned(),
|
||||
Keysym::Home | Keysym::KP_Home => "home".to_owned(),
|
||||
Keysym::End | Keysym::KP_End => "end".to_owned(),
|
||||
Keysym::Prior | Keysym::KP_Prior => "pageup".to_owned(),
|
||||
Keysym::Next | Keysym::KP_Next => "pagedown".to_owned(),
|
||||
Keysym::XF86_Back => "back".to_owned(),
|
||||
Keysym::XF86_Forward => "forward".to_owned(),
|
||||
Keysym::Escape => "escape".to_owned(),
|
||||
Keysym::Insert | Keysym::KP_Insert => "insert".to_owned(),
|
||||
Keysym::Delete | Keysym::KP_Delete => "delete".to_owned(),
|
||||
Keysym::Menu => "menu".to_owned(),
|
||||
Keysym::XF86_Cut => "cut".to_owned(),
|
||||
Keysym::XF86_Copy => "copy".to_owned(),
|
||||
Keysym::XF86_Paste => "paste".to_owned(),
|
||||
Keysym::XF86_New => "new".to_owned(),
|
||||
Keysym::XF86_Open => "open".to_owned(),
|
||||
Keysym::XF86_Save => "save".to_owned(),
|
||||
|
||||
Keysym::comma => ",".to_owned(),
|
||||
Keysym::period => ".".to_owned(),
|
||||
Keysym::less => "<".to_owned(),
|
||||
Keysym::greater => ">".to_owned(),
|
||||
Keysym::slash => "/".to_owned(),
|
||||
Keysym::question => "?".to_owned(),
|
||||
|
||||
Keysym::semicolon => ";".to_owned(),
|
||||
Keysym::colon => ":".to_owned(),
|
||||
Keysym::apostrophe => "'".to_owned(),
|
||||
Keysym::quotedbl => "\"".to_owned(),
|
||||
|
||||
Keysym::bracketleft => "[".to_owned(),
|
||||
Keysym::braceleft => "{".to_owned(),
|
||||
Keysym::bracketright => "]".to_owned(),
|
||||
Keysym::braceright => "}".to_owned(),
|
||||
Keysym::backslash => "\\".to_owned(),
|
||||
Keysym::bar => "|".to_owned(),
|
||||
|
||||
Keysym::grave => "`".to_owned(),
|
||||
Keysym::asciitilde => "~".to_owned(),
|
||||
Keysym::exclam => "!".to_owned(),
|
||||
Keysym::at => "@".to_owned(),
|
||||
Keysym::numbersign => "#".to_owned(),
|
||||
Keysym::dollar => "$".to_owned(),
|
||||
Keysym::percent => "%".to_owned(),
|
||||
Keysym::asciicircum => "^".to_owned(),
|
||||
Keysym::ampersand => "&".to_owned(),
|
||||
Keysym::asterisk => "*".to_owned(),
|
||||
Keysym::parenleft => "(".to_owned(),
|
||||
Keysym::parenright => ")".to_owned(),
|
||||
Keysym::minus => "-".to_owned(),
|
||||
Keysym::underscore => "_".to_owned(),
|
||||
Keysym::equal => "=".to_owned(),
|
||||
Keysym::plus => "+".to_owned(),
|
||||
|
||||
_ => {
|
||||
let name = xkb::keysym_get_name(key_sym).to_lowercase();
|
||||
if key_sym.is_keypad_key() {
|
||||
name.replace("kp_", "")
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
Keysym::F1 => "f1".to_owned(),
|
||||
Keysym::F2 => "f2".to_owned(),
|
||||
Keysym::F3 => "f3".to_owned(),
|
||||
Keysym::F4 => "f4".to_owned(),
|
||||
Keysym::F5 => "f5".to_owned(),
|
||||
Keysym::F6 => "f6".to_owned(),
|
||||
Keysym::F7 => "f7".to_owned(),
|
||||
Keysym::F8 => "f8".to_owned(),
|
||||
Keysym::F9 => "f9".to_owned(),
|
||||
Keysym::F10 => "f10".to_owned(),
|
||||
Keysym::F11 => "f11".to_owned(),
|
||||
Keysym::F12 => "f12".to_owned(),
|
||||
Keysym::F13 => "f13".to_owned(),
|
||||
Keysym::F14 => "f14".to_owned(),
|
||||
Keysym::F15 => "f15".to_owned(),
|
||||
Keysym::F16 => "f16".to_owned(),
|
||||
Keysym::F17 => "f17".to_owned(),
|
||||
Keysym::F18 => "f18".to_owned(),
|
||||
Keysym::F19 => "f19".to_owned(),
|
||||
Keysym::F20 => "f20".to_owned(),
|
||||
Keysym::F21 => "f21".to_owned(),
|
||||
Keysym::F22 => "f22".to_owned(),
|
||||
Keysym::F23 => "f23".to_owned(),
|
||||
Keysym::F24 => "f24".to_owned(),
|
||||
_ => keyboard_mapper
|
||||
.get_key(keycode, &mut modifiers)
|
||||
.unwrap_or_else(|| {
|
||||
let name = xkb::keysym_get_name(key_sym).to_lowercase();
|
||||
if key_sym.is_keypad_key() {
|
||||
name.replace("kp_", "")
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
if modifiers.shift {
|
||||
// we only include the shift for upper-case letters by convention,
|
||||
// so don't include for numbers and symbols, but do include for
|
||||
// tab/enter, etc.
|
||||
if key.chars().count() == 1 && key.to_lowercase() == key.to_uppercase() {
|
||||
modifiers.shift = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore control characters (and DEL) for the purposes of key_char
|
||||
let key_char =
|
||||
(key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8);
|
||||
|
@ -798,65 +793,6 @@ impl crate::Keystroke {
|
|||
key_char,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns which symbol the dead key represents
|
||||
* <https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#dead_keycodes_for_linux>
|
||||
*/
|
||||
pub fn underlying_dead_key(keysym: Keysym) -> Option<String> {
|
||||
match keysym {
|
||||
Keysym::dead_grave => Some("`".to_owned()),
|
||||
Keysym::dead_acute => Some("´".to_owned()),
|
||||
Keysym::dead_circumflex => Some("^".to_owned()),
|
||||
Keysym::dead_tilde => Some("~".to_owned()),
|
||||
Keysym::dead_macron => Some("¯".to_owned()),
|
||||
Keysym::dead_breve => Some("˘".to_owned()),
|
||||
Keysym::dead_abovedot => Some("˙".to_owned()),
|
||||
Keysym::dead_diaeresis => Some("¨".to_owned()),
|
||||
Keysym::dead_abovering => Some("˚".to_owned()),
|
||||
Keysym::dead_doubleacute => Some("˝".to_owned()),
|
||||
Keysym::dead_caron => Some("ˇ".to_owned()),
|
||||
Keysym::dead_cedilla => Some("¸".to_owned()),
|
||||
Keysym::dead_ogonek => Some("˛".to_owned()),
|
||||
Keysym::dead_iota => Some("ͅ".to_owned()),
|
||||
Keysym::dead_voiced_sound => Some("゙".to_owned()),
|
||||
Keysym::dead_semivoiced_sound => Some("゚".to_owned()),
|
||||
Keysym::dead_belowdot => Some("̣̣".to_owned()),
|
||||
Keysym::dead_hook => Some("̡".to_owned()),
|
||||
Keysym::dead_horn => Some("̛".to_owned()),
|
||||
Keysym::dead_stroke => Some("̶̶".to_owned()),
|
||||
Keysym::dead_abovecomma => Some("̓̓".to_owned()),
|
||||
Keysym::dead_abovereversedcomma => Some("ʽ".to_owned()),
|
||||
Keysym::dead_doublegrave => Some("̏".to_owned()),
|
||||
Keysym::dead_belowring => Some("˳".to_owned()),
|
||||
Keysym::dead_belowmacron => Some("̱".to_owned()),
|
||||
Keysym::dead_belowcircumflex => Some("ꞈ".to_owned()),
|
||||
Keysym::dead_belowtilde => Some("̰".to_owned()),
|
||||
Keysym::dead_belowbreve => Some("̮".to_owned()),
|
||||
Keysym::dead_belowdiaeresis => Some("̤".to_owned()),
|
||||
Keysym::dead_invertedbreve => Some("̯".to_owned()),
|
||||
Keysym::dead_belowcomma => Some("̦".to_owned()),
|
||||
Keysym::dead_currency => None,
|
||||
Keysym::dead_lowline => None,
|
||||
Keysym::dead_aboveverticalline => None,
|
||||
Keysym::dead_belowverticalline => None,
|
||||
Keysym::dead_longsolidusoverlay => None,
|
||||
Keysym::dead_a => None,
|
||||
Keysym::dead_A => None,
|
||||
Keysym::dead_e => None,
|
||||
Keysym::dead_E => None,
|
||||
Keysym::dead_i => None,
|
||||
Keysym::dead_I => None,
|
||||
Keysym::dead_o => None,
|
||||
Keysym::dead_O => None,
|
||||
Keysym::dead_u => None,
|
||||
Keysym::dead_U => None,
|
||||
Keysym::dead_small_schwa => Some("ə".to_owned()),
|
||||
Keysym::dead_capital_schwa => Some("Ə".to_owned()),
|
||||
Keysym::dead_greek => None,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "wayland", feature = "x11"))]
|
||||
|
|
|
@ -61,22 +61,20 @@ use wayland_protocols::xdg::decoration::zv1::client::{
|
|||
};
|
||||
use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
|
||||
use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blur_manager};
|
||||
use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
|
||||
use xkbcommon::xkb::{self, KEYMAP_COMPILE_NO_FLAGS, Keycode};
|
||||
use xkbcommon::xkb::{self, KEYMAP_COMPILE_NO_FLAGS, ffi::XKB_KEYMAP_FORMAT_TEXT_V1};
|
||||
|
||||
use super::{
|
||||
display::WaylandDisplay,
|
||||
window::{ImeInput, WaylandWindowStatePtr},
|
||||
};
|
||||
|
||||
use crate::platform::{PlatformWindow, blade::BladeContext};
|
||||
use crate::{
|
||||
AnyWindowHandle, Bounds, Capslock, CursorStyle, DOUBLE_CLICK_INTERVAL, DevicePixels, DisplayId,
|
||||
FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon,
|
||||
LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||
MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay,
|
||||
PlatformInput, PlatformKeyboardLayout, Point, SCROLL_LINES, ScaledPixels, ScrollDelta,
|
||||
ScrollWheelEvent, Size, TouchPhase, WindowParams, point, px, size,
|
||||
LinuxKeyboardLayout, LinuxKeyboardMapper, Modifiers, ModifiersChangedEvent, MouseButton,
|
||||
MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels,
|
||||
PlatformDisplay, PlatformInput, PlatformKeyboardLayout, Point, SCROLL_LINES, ScaledPixels,
|
||||
ScrollDelta, ScrollWheelEvent, Size, TouchPhase, WindowParams, point, px, size,
|
||||
};
|
||||
use crate::{
|
||||
SharedString,
|
||||
|
@ -92,6 +90,10 @@ use crate::{
|
|||
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
|
||||
},
|
||||
};
|
||||
use crate::{
|
||||
platform::{PlatformWindow, blade::BladeContext},
|
||||
underlying_dead_key,
|
||||
};
|
||||
|
||||
/// Used to convert evdev scancode to xkb scancode
|
||||
const MIN_KEYCODE: u32 = 8;
|
||||
|
@ -208,9 +210,11 @@ pub(crate) struct WaylandClientState {
|
|||
// Output to scale mapping
|
||||
outputs: HashMap<ObjectId, Output>,
|
||||
in_progress_outputs: HashMap<ObjectId, InProgressOutput>,
|
||||
keyboard_layout: LinuxKeyboardLayout,
|
||||
keymap_state: Option<xkb::State>,
|
||||
compose_state: Option<xkb::compose::State>,
|
||||
keyboard_layout: LinuxKeyboardLayout,
|
||||
keyboard_mapper: Option<Rc<LinuxKeyboardMapper>>,
|
||||
keyboard_mapper_cache: HashMap<String, Rc<LinuxKeyboardMapper>>,
|
||||
drag: DragState,
|
||||
click: ClickState,
|
||||
repeat: KeyRepeat,
|
||||
|
@ -340,7 +344,7 @@ impl WaylandClientStatePtr {
|
|||
text_input.commit();
|
||||
}
|
||||
|
||||
pub fn handle_keyboard_layout_change(&self) {
|
||||
pub fn handle_keyboard_layout_change(&self, locked_group: u32) {
|
||||
let client = self.get_client();
|
||||
let mut state = client.borrow_mut();
|
||||
let changed = if let Some(keymap_state) = &state.keymap_state {
|
||||
|
@ -350,6 +354,17 @@ impl WaylandClientStatePtr {
|
|||
let changed = layout_name != state.keyboard_layout.name();
|
||||
if changed {
|
||||
state.keyboard_layout = LinuxKeyboardLayout::new(layout_name.to_string().into());
|
||||
let mapper = state
|
||||
.keyboard_mapper_cache
|
||||
.entry(layout_name.to_string())
|
||||
.or_insert(Rc::new(LinuxKeyboardMapper::new(
|
||||
&keymap,
|
||||
0,
|
||||
0,
|
||||
locked_group,
|
||||
)))
|
||||
.clone();
|
||||
state.keyboard_mapper = Some(mapper);
|
||||
}
|
||||
changed
|
||||
} else {
|
||||
|
@ -447,6 +462,7 @@ impl WaylandClient {
|
|||
pub(crate) fn new() -> Self {
|
||||
let conn = Connection::connect_to_env().unwrap();
|
||||
|
||||
let keyboard_layout = LinuxKeyboardLayout::new(UNKNOWN_KEYBOARD_LAYOUT_NAME);
|
||||
let (globals, mut event_queue) =
|
||||
registry_queue_init::<WaylandClientStatePtr>(&conn).unwrap();
|
||||
let qh = event_queue.handle();
|
||||
|
@ -567,9 +583,11 @@ impl WaylandClient {
|
|||
in_progress_outputs,
|
||||
windows: HashMap::default(),
|
||||
common,
|
||||
keyboard_layout: LinuxKeyboardLayout::new(UNKNOWN_KEYBOARD_LAYOUT_NAME),
|
||||
keymap_state: None,
|
||||
compose_state: None,
|
||||
keyboard_layout,
|
||||
keyboard_mapper: None,
|
||||
keyboard_mapper_cache: HashMap::default(),
|
||||
drag: DragState {
|
||||
data_offer: None,
|
||||
window: None,
|
||||
|
@ -1214,7 +1232,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
|||
state.compose_state = get_xkb_compose_state(&xkb_context);
|
||||
drop(state);
|
||||
|
||||
this.handle_keyboard_layout_change();
|
||||
this.handle_keyboard_layout_change(0);
|
||||
}
|
||||
wl_keyboard::Event::Enter { surface, .. } => {
|
||||
state.keyboard_focused_window = get_window(&mut state, &surface.id());
|
||||
|
@ -1270,7 +1288,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
|||
}
|
||||
|
||||
if group != old_layout {
|
||||
this.handle_keyboard_layout_change();
|
||||
this.handle_keyboard_layout_change(group);
|
||||
}
|
||||
}
|
||||
wl_keyboard::Event::Key {
|
||||
|
@ -1288,20 +1306,25 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
|||
let focused_window = focused_window.clone();
|
||||
|
||||
let keymap_state = state.keymap_state.as_ref().unwrap();
|
||||
let keycode = Keycode::from(key + MIN_KEYCODE);
|
||||
let keyboard_mapper = state.keyboard_mapper.as_ref().unwrap();
|
||||
let keycode = xkb::Keycode::from(key + MIN_KEYCODE);
|
||||
let keysym = keymap_state.key_get_one_sym(keycode);
|
||||
|
||||
match key_state {
|
||||
wl_keyboard::KeyState::Pressed if !keysym.is_modifier_key() => {
|
||||
let mut keystroke =
|
||||
Keystroke::from_xkb(&keymap_state, state.modifiers, keycode);
|
||||
let mut keystroke = Keystroke::from_xkb(
|
||||
keymap_state,
|
||||
keyboard_mapper,
|
||||
state.modifiers,
|
||||
keycode,
|
||||
);
|
||||
if let Some(mut compose) = state.compose_state.take() {
|
||||
compose.feed(keysym);
|
||||
match compose.status() {
|
||||
xkb::Status::Composing => {
|
||||
keystroke.key_char = None;
|
||||
state.pre_edit_text =
|
||||
compose.utf8().or(Keystroke::underlying_dead_key(keysym));
|
||||
compose.utf8().or(underlying_dead_key(keysym));
|
||||
let pre_edit =
|
||||
state.pre_edit_text.clone().unwrap_or(String::default());
|
||||
drop(state);
|
||||
|
@ -1318,7 +1341,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
|||
}
|
||||
xkb::Status::Cancelled => {
|
||||
let pre_edit = state.pre_edit_text.take();
|
||||
let new_pre_edit = Keystroke::underlying_dead_key(keysym);
|
||||
let new_pre_edit = underlying_dead_key(keysym);
|
||||
state.pre_edit_text = new_pre_edit.clone();
|
||||
drop(state);
|
||||
if let Some(pre_edit) = pre_edit {
|
||||
|
@ -1379,7 +1402,12 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
|||
}
|
||||
wl_keyboard::KeyState::Released if !keysym.is_modifier_key() => {
|
||||
let input = PlatformInput::KeyUp(KeyUpEvent {
|
||||
keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
|
||||
keystroke: Keystroke::from_xkb(
|
||||
keymap_state,
|
||||
keyboard_mapper,
|
||||
state.modifiers,
|
||||
keycode,
|
||||
),
|
||||
});
|
||||
|
||||
if state.repeat.current_keycode == Some(keycode) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::{Capslock, xcb_flush};
|
||||
use core::str;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
|
@ -49,7 +48,7 @@ use super::{
|
|||
};
|
||||
|
||||
use crate::platform::{
|
||||
LinuxCommon, PlatformWindow,
|
||||
Capslock, LinuxCommon, PlatformWindow,
|
||||
blade::BladeContext,
|
||||
linux::{
|
||||
DEFAULT_CURSOR_ICON_NAME, LinuxClient, get_xkb_compose_state, is_within_click_distance,
|
||||
|
@ -58,13 +57,14 @@ use crate::platform::{
|
|||
reveal_path_internal,
|
||||
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
|
||||
},
|
||||
xcb_flush,
|
||||
};
|
||||
use crate::{
|
||||
AnyWindowHandle, Bounds, ClipboardItem, CursorStyle, DisplayId, FileDropEvent, Keystroke,
|
||||
LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, Pixels, Platform,
|
||||
PlatformDisplay, PlatformInput, PlatformKeyboardLayout, Point, RequestFrameOptions,
|
||||
ScaledPixels, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
|
||||
modifiers_from_xinput_info, point, px,
|
||||
LinuxKeyboardLayout, LinuxKeyboardMapper, Modifiers, ModifiersChangedEvent, MouseButton,
|
||||
Pixels, Platform, PlatformDisplay, PlatformInput, PlatformKeyboardLayout, Point,
|
||||
RequestFrameOptions, ScaledPixels, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
|
||||
modifiers_from_xinput_info, point, px, underlying_dead_key,
|
||||
};
|
||||
|
||||
/// Value for DeviceId parameters which selects all devices.
|
||||
|
@ -200,6 +200,8 @@ pub struct X11ClientState {
|
|||
pub(crate) xkb: xkbc::State,
|
||||
previous_xkb_state: XKBStateNotiy,
|
||||
keyboard_layout: LinuxKeyboardLayout,
|
||||
keyboard_mapper: Rc<LinuxKeyboardMapper>,
|
||||
keyboard_mapper_cache: HashMap<String, Rc<LinuxKeyboardMapper>>,
|
||||
pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
|
||||
pub(crate) xim_handler: Option<XimHandler>,
|
||||
pub modifiers: Modifiers,
|
||||
|
@ -403,22 +405,24 @@ impl X11Client {
|
|||
|
||||
let xkb_context = xkbc::Context::new(xkbc::CONTEXT_NO_FLAGS);
|
||||
let xkb_device_id = xkbc::x11::get_core_keyboard_device_id(&xcb_connection);
|
||||
let xkb_state = {
|
||||
let xkb_keymap = xkbc::x11::keymap_new_from_device(
|
||||
&xkb_context,
|
||||
&xcb_connection,
|
||||
xkb_device_id,
|
||||
xkbc::KEYMAP_COMPILE_NO_FLAGS,
|
||||
);
|
||||
xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
|
||||
};
|
||||
let xkb_keymap = xkbc::x11::keymap_new_from_device(
|
||||
&xkb_context,
|
||||
&xcb_connection,
|
||||
xkb_device_id,
|
||||
xkbc::KEYMAP_COMPILE_NO_FLAGS,
|
||||
);
|
||||
let xkb_state =
|
||||
xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id);
|
||||
let compose_state = get_xkb_compose_state(&xkb_context);
|
||||
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 keyboard_layout = LinuxKeyboardLayout::new(layout_name.clone().into());
|
||||
let keyboard_mapper = Rc::new(LinuxKeyboardMapper::new(&xkb_keymap, 0, 0, 0));
|
||||
let mut keyboard_mapper_cache = HashMap::default();
|
||||
keyboard_mapper_cache.insert(layout_name, keyboard_mapper.clone());
|
||||
|
||||
let gpu_context = BladeContext::new().context("Unable to init GPU context")?;
|
||||
|
||||
|
@ -512,6 +516,8 @@ impl X11Client {
|
|||
xkb: xkb_state,
|
||||
previous_xkb_state: XKBStateNotiy::default(),
|
||||
keyboard_layout,
|
||||
keyboard_mapper,
|
||||
keyboard_mapper_cache,
|
||||
ximc,
|
||||
xim_handler,
|
||||
|
||||
|
@ -972,24 +978,27 @@ impl X11Client {
|
|||
};
|
||||
state.xkb = xkb_state;
|
||||
drop(state);
|
||||
self.handle_keyboard_layout_change();
|
||||
self.handle_keyboard_layout_change(depressed_layout, latched_layout, locked_layout);
|
||||
}
|
||||
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);
|
||||
let base_group = event.base_group as u32;
|
||||
let latched_group = event.latched_group as u32;
|
||||
let locked_group = event.locked_group.into();
|
||||
state.xkb.update_mask(
|
||||
event.base_mods.into(),
|
||||
event.latched_mods.into(),
|
||||
event.locked_mods.into(),
|
||||
event.base_group as u32,
|
||||
event.latched_group as u32,
|
||||
event.locked_group.into(),
|
||||
base_group,
|
||||
latched_group,
|
||||
locked_group,
|
||||
);
|
||||
state.previous_xkb_state = XKBStateNotiy {
|
||||
depressed_layout: event.base_group as u32,
|
||||
latched_layout: event.latched_group as u32,
|
||||
locked_layout: event.locked_group.into(),
|
||||
depressed_layout: base_group,
|
||||
latched_layout: latched_group,
|
||||
locked_layout: locked_group,
|
||||
};
|
||||
|
||||
let modifiers = Modifiers::from_xkb(&state.xkb);
|
||||
|
@ -1016,7 +1025,7 @@ impl X11Client {
|
|||
}
|
||||
|
||||
if new_layout != old_layout {
|
||||
self.handle_keyboard_layout_change();
|
||||
self.handle_keyboard_layout_change(base_group, latched_group, locked_group);
|
||||
}
|
||||
}
|
||||
Event::KeyPress(event) => {
|
||||
|
@ -1037,7 +1046,12 @@ impl X11Client {
|
|||
xkb_state.latched_layout,
|
||||
xkb_state.locked_layout,
|
||||
);
|
||||
let mut keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
let mut keystroke = crate::Keystroke::from_xkb(
|
||||
&state.xkb,
|
||||
&state.keyboard_mapper,
|
||||
modifiers,
|
||||
code,
|
||||
);
|
||||
let keysym = state.xkb.key_get_one_sym(code);
|
||||
if keysym.is_modifier_key() {
|
||||
return Some(());
|
||||
|
@ -1054,9 +1068,8 @@ impl X11Client {
|
|||
}
|
||||
xkbc::Status::Composing => {
|
||||
keystroke.key_char = None;
|
||||
state.pre_edit_text = compose_state
|
||||
.utf8()
|
||||
.or(crate::Keystroke::underlying_dead_key(keysym));
|
||||
state.pre_edit_text =
|
||||
compose_state.utf8().or(underlying_dead_key(keysym));
|
||||
let pre_edit =
|
||||
state.pre_edit_text.clone().unwrap_or(String::default());
|
||||
drop(state);
|
||||
|
@ -1069,7 +1082,7 @@ impl X11Client {
|
|||
if let Some(pre_edit) = pre_edit {
|
||||
window.handle_ime_commit(pre_edit);
|
||||
}
|
||||
if let Some(current_key) = Keystroke::underlying_dead_key(keysym) {
|
||||
if let Some(current_key) = underlying_dead_key(keysym) {
|
||||
window.handle_ime_preedit(current_key);
|
||||
}
|
||||
state = self.0.borrow_mut();
|
||||
|
@ -1105,7 +1118,12 @@ impl X11Client {
|
|||
xkb_state.latched_layout,
|
||||
xkb_state.locked_layout,
|
||||
);
|
||||
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
let keystroke = crate::Keystroke::from_xkb(
|
||||
&state.xkb,
|
||||
&state.keyboard_mapper,
|
||||
modifiers,
|
||||
code,
|
||||
);
|
||||
let keysym = state.xkb.key_get_one_sym(code);
|
||||
if keysym.is_modifier_key() {
|
||||
return Some(());
|
||||
|
@ -1327,6 +1345,7 @@ impl X11Client {
|
|||
let mut state = self.0.borrow_mut();
|
||||
state.pre_key_char_down = Some(Keystroke::from_xkb(
|
||||
&state.xkb,
|
||||
&state.keyboard_mapper,
|
||||
state.modifiers,
|
||||
event.detail.into(),
|
||||
));
|
||||
|
@ -1412,13 +1431,29 @@ impl X11Client {
|
|||
Some(())
|
||||
}
|
||||
|
||||
fn handle_keyboard_layout_change(&self) {
|
||||
fn handle_keyboard_layout_change(
|
||||
&self,
|
||||
base_group: u32,
|
||||
latched_group: u32,
|
||||
locked_group: u32,
|
||||
) {
|
||||
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());
|
||||
let mapper = state
|
||||
.keyboard_mapper_cache
|
||||
.entry(layout_name.to_string())
|
||||
.or_insert(Rc::new(LinuxKeyboardMapper::new(
|
||||
&keymap,
|
||||
base_group,
|
||||
latched_group,
|
||||
locked_group,
|
||||
)))
|
||||
.clone();
|
||||
state.keyboard_mapper = mapper;
|
||||
if let Some(mut callback) = state.common.callbacks.keyboard_layout_change.take() {
|
||||
drop(state);
|
||||
callback();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue