mirror of https://github.com/helix-editor/helix
Compare commits
15 Commits
71ed503012
...
f649177ffd
Author | SHA1 | Date |
---|---|---|
|
f649177ffd | |
|
4281228da3 | |
|
cd2bb2530c | |
|
77656e27bd | |
|
6de385df51 | |
|
3295f2bd32 | |
|
88b1a0d3d8 | |
|
8ec463a0f5 | |
|
e1e0dd00a6 | |
|
0567bae0f3 | |
|
4c6b0ff447 | |
|
abb1c60afd | |
|
94b5e0c210 | |
|
bb62dad76f | |
|
77c1c0e0e1 |
|
@ -104,6 +104,18 @@ version = "2.9.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
|
@ -163,6 +175,12 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chardetng"
|
||||
version = "0.1.17"
|
||||
|
@ -275,7 +293,7 @@ dependencies = [
|
|||
"rustix 0.38.44",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -284,7 +302,7 @@ version = "0.9.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -401,6 +419,19 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "evdev"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3c10865aeab1a7399b3c2d6046e8dcc7f5227b656f235ed63ef5ee45a47b8f8"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"nix",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "faster-hex"
|
||||
version = "0.10.0"
|
||||
|
@ -434,7 +465,7 @@ checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
|
|||
dependencies = [
|
||||
"libc",
|
||||
"thiserror 1.0.69",
|
||||
"winapi",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -481,6 +512,12 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
|
@ -1626,6 +1663,7 @@ dependencies = [
|
|||
"chardetng",
|
||||
"clipboard-win",
|
||||
"crossterm",
|
||||
"evdev",
|
||||
"futures-util",
|
||||
"helix-core",
|
||||
"helix-dap",
|
||||
|
@ -1635,6 +1673,8 @@ dependencies = [
|
|||
"helix-stdx",
|
||||
"helix-tui",
|
||||
"helix-vcs",
|
||||
"hidapi",
|
||||
"keyboard_query",
|
||||
"kstring",
|
||||
"libc",
|
||||
"log",
|
||||
|
@ -1658,6 +1698,19 @@ version = "0.3.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "hidapi"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03b876ecf37e86b359573c16c8366bc3eba52b689884a0fc42ba3f67203d2a8b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
|
@ -1966,6 +2019,17 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyboard_query"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0107e577edabb13df9c65e4bbd07d4359ddfa80e4ef2c1471f75de783d2df71"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
"user32-sys",
|
||||
"x11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kstring"
|
||||
version = "2.0.2"
|
||||
|
@ -2102,6 +2166,18 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nucleo"
|
||||
version = "0.5.0"
|
||||
|
@ -2215,6 +2291,12 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
|
@ -2278,6 +2360,12 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
|
@ -2637,6 +2725,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
|
@ -2933,6 +3027,16 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "user32-sys"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47"
|
||||
dependencies = [
|
||||
"winapi 0.2.8",
|
||||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf16_iter"
|
||||
version = "1.0.5"
|
||||
|
@ -3042,6 +3146,12 @@ dependencies = [
|
|||
"winsafe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
@ -3052,6 +3162,12 @@ dependencies = [
|
|||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
@ -3088,6 +3204,15 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
@ -3115,6 +3240,21 @@ dependencies = [
|
|||
"windows-targets 0.53.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
|
@ -3147,6 +3287,12 @@ dependencies = [
|
|||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
|
@ -3159,6 +3305,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
|
@ -3171,6 +3323,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
|
@ -3195,6 +3353,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
|
@ -3207,6 +3371,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
|
@ -3219,6 +3389,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
|
@ -3231,6 +3407,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
@ -3279,6 +3461,25 @@ version = "0.5.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11"
|
||||
version = "2.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "25.7.1"
|
||||
|
|
|
@ -35,6 +35,9 @@ default = ["git"]
|
|||
unicode-lines = ["helix-core/unicode-lines", "helix-view/unicode-lines"]
|
||||
integration = ["helix-event/integration_test"]
|
||||
git = ["helix-vcs/git"]
|
||||
scancode-query = ["helix-view/scancode-query"]
|
||||
scancode-evdev = ["helix-view/scancode-evdev"]
|
||||
scancode-hidapi = ["helix-view/scancode-hidapi"]
|
||||
|
||||
[[bin]]
|
||||
name = "hx"
|
||||
|
|
|
@ -9,6 +9,15 @@ fn main() {
|
|||
|
||||
#[cfg(windows)]
|
||||
windows_rc::link_icon_in_windows_exe("../contrib/helix-256p.ico");
|
||||
|
||||
// alias scancode feature flag
|
||||
println!("cargo::rustc-check-cfg=cfg(scancode)");
|
||||
#[cfg(any(
|
||||
feature = "scancode-query",
|
||||
feature = "scancode-evdev",
|
||||
feature = "scancode-hidapi"
|
||||
))]
|
||||
println!("cargo:rustc-cfg=scancode")
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
|
|
@ -6692,6 +6692,8 @@ fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
|
|||
let view = view.id;
|
||||
let doc = doc.id();
|
||||
cx.on_next_key(move |cx, event| {
|
||||
#[cfg(scancode)]
|
||||
let event = cx.editor.scancode_apply(event);
|
||||
let alphabet = &cx.editor.config().jump_label_alphabet;
|
||||
let Some(i) = event
|
||||
.char()
|
||||
|
@ -6709,6 +6711,8 @@ fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
|
|||
}
|
||||
cx.on_next_key(move |cx, event| {
|
||||
doc_mut!(cx.editor, &doc).remove_jump_labels(view);
|
||||
#[cfg(scancode)]
|
||||
let event = cx.editor.scancode_apply(event);
|
||||
let alphabet = &cx.editor.config().jump_label_alphabet;
|
||||
let Some(inner) = event
|
||||
.char()
|
||||
|
|
|
@ -837,7 +837,34 @@ impl EditorView {
|
|||
) -> Option<KeymapResult> {
|
||||
let mut last_mode = mode;
|
||||
self.pseudo_pending.extend(self.keymaps.pending());
|
||||
|
||||
let key_result = self.keymaps.get(mode, event);
|
||||
|
||||
#[cfg(scancode)]
|
||||
let key_result = {
|
||||
if !matches!(
|
||||
key_result,
|
||||
KeymapResult::NotFound | KeymapResult::Cancelled(_)
|
||||
) {
|
||||
key_result
|
||||
} else {
|
||||
match key_result {
|
||||
KeymapResult::NotFound => {
|
||||
self.keymaps.get(mode, cxt.editor.scancode_apply(event))
|
||||
}
|
||||
KeymapResult::Cancelled(mut keys) => {
|
||||
// replay previous keys and try again by scancode
|
||||
let _ = keys.pop();
|
||||
for key in keys {
|
||||
let _ = self.keymaps.get(mode, key);
|
||||
}
|
||||
self.keymaps.get(mode, cxt.editor.scancode_apply(event))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox());
|
||||
|
||||
let mut execute_command = |command: &commands::MappableCommand| {
|
||||
|
@ -910,6 +937,13 @@ impl EditorView {
|
|||
}
|
||||
|
||||
fn command_mode(&mut self, mode: Mode, cxt: &mut commands::Context, event: KeyEvent) {
|
||||
#[cfg(scancode)]
|
||||
// dont use scancode on macros replaying
|
||||
let event = if cxt.editor.macro_replaying.is_empty() {
|
||||
cxt.editor.scancode_apply(event)
|
||||
} else {
|
||||
event
|
||||
};
|
||||
match (event, cxt.editor.count) {
|
||||
// If the count is already started and the input is a number, always continue the count.
|
||||
(key!(i @ '0'..='9'), Some(count)) => {
|
||||
|
|
|
@ -14,6 +14,9 @@ homepage.workspace = true
|
|||
default = []
|
||||
term = ["crossterm"]
|
||||
unicode-lines = []
|
||||
scancode-query = ["keyboard_query"]
|
||||
scancode-evdev = ["evdev"]
|
||||
scancode-hidapi = ["hidapi"]
|
||||
|
||||
[dependencies]
|
||||
helix-stdx = { path = "../helix-stdx" }
|
||||
|
@ -54,6 +57,11 @@ thiserror.workspace = true
|
|||
|
||||
kstring = "2.0"
|
||||
|
||||
keyboard_query = { version = "0.1", optional = true }
|
||||
evdev = { version = "0.13", features = ["tokio"], optional = true }
|
||||
hidapi = { version = "2.6", optional = true }
|
||||
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
clipboard-win = { version = "5.4", features = ["std"] }
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
fn main() {
|
||||
// alias scancode feature flag
|
||||
println!("cargo::rustc-check-cfg=cfg(scancode)");
|
||||
#[cfg(any(
|
||||
feature = "scancode-query",
|
||||
feature = "scancode-evdev",
|
||||
feature = "scancode-hidapi"
|
||||
))]
|
||||
println!("cargo:rustc-cfg=scancode")
|
||||
}
|
|
@ -20,6 +20,10 @@ use helix_vcs::DiffProviderRegistry;
|
|||
use futures_util::stream::select_all::SelectAll;
|
||||
use futures_util::{future, StreamExt};
|
||||
use helix_lsp::{Call, LanguageServerId};
|
||||
|
||||
#[cfg(scancode)]
|
||||
use crate::scancode::{deserialize_scancode, KeyboardState, ScanCodeMap};
|
||||
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
use std::{
|
||||
|
@ -379,6 +383,9 @@ pub struct Config {
|
|||
/// Whether to read settings from [EditorConfig](https://editorconfig.org) files. Defaults to
|
||||
/// `true`.
|
||||
pub editor_config: bool,
|
||||
#[cfg(scancode)]
|
||||
#[serde(skip_serializing, deserialize_with = "deserialize_scancode")]
|
||||
pub scancode: ScanCodeMap,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
|
||||
|
@ -960,6 +967,7 @@ impl From<LineEndingConfig> for LineEnding {
|
|||
LineEndingConfig::Crlf => LineEnding::Crlf,
|
||||
#[cfg(feature = "unicode-lines")]
|
||||
LineEndingConfig::FF => LineEnding::FF,
|
||||
|
||||
#[cfg(feature = "unicode-lines")]
|
||||
LineEndingConfig::CR => LineEnding::CR,
|
||||
#[cfg(feature = "unicode-lines")]
|
||||
|
@ -1055,6 +1063,8 @@ impl Default for Config {
|
|||
end_of_line_diagnostics: DiagnosticFilter::Disable,
|
||||
clipboard_provider: ClipboardProvider::default(),
|
||||
editor_config: true,
|
||||
#[cfg(scancode)]
|
||||
scancode: ScanCodeMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1156,6 +1166,8 @@ pub struct Editor {
|
|||
|
||||
pub mouse_down_range: Option<Range>,
|
||||
pub cursor_cache: CursorCache,
|
||||
#[cfg(scancode)]
|
||||
pub keyboard_state: KeyboardState,
|
||||
}
|
||||
|
||||
pub type Motion = Box<dyn Fn(&mut Editor)>;
|
||||
|
@ -1277,6 +1289,8 @@ impl Editor {
|
|||
handlers,
|
||||
mouse_down_range: None,
|
||||
cursor_cache: CursorCache::default(),
|
||||
#[cfg(scancode)]
|
||||
keyboard_state: KeyboardState::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2294,6 +2308,13 @@ impl Editor {
|
|||
pub fn get_last_cwd(&mut self) -> Option<&Path> {
|
||||
self.last_cwd.as_deref()
|
||||
}
|
||||
|
||||
#[cfg(scancode)]
|
||||
pub fn scancode_apply(&mut self, event: KeyEvent) -> KeyEvent {
|
||||
self.config()
|
||||
.scancode
|
||||
.apply(event, &mut self.keyboard_state)
|
||||
}
|
||||
}
|
||||
|
||||
fn try_restore_indent(doc: &mut Document, view: &mut View) {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
//! Input event handling, currently backed by crossterm.
|
||||
pub use crate::keyboard::{KeyCode, KeyModifiers, MediaKeyCode, ModifierKeyCode};
|
||||
use anyhow::{anyhow, Error};
|
||||
use helix_core::unicode::{segmentation::UnicodeSegmentation, width::UnicodeWidthStr};
|
||||
use serde::de::{self, Deserialize, Deserializer};
|
||||
use std::fmt;
|
||||
|
||||
pub use crate::keyboard::{KeyCode, KeyModifiers, MediaKeyCode, ModifierKeyCode};
|
||||
|
||||
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
|
||||
pub enum Event {
|
||||
FocusGained,
|
||||
|
@ -334,7 +333,64 @@ impl std::str::FromStr for KeyEvent {
|
|||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut tokens: Vec<_> = s.split('-').collect();
|
||||
let mut code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
|
||||
|
||||
let mut code = if let Ok(code) =
|
||||
KeyCode::from_str(tokens.pop().ok_or_else(|| anyhow!("Missing key code"))?)
|
||||
{
|
||||
code
|
||||
} else if s.ends_with('-') && tokens.last().is_some_and(|t| t.is_empty()) {
|
||||
if s == "-" {
|
||||
return Ok(KeyEvent {
|
||||
code: KeyCode::Char('-'),
|
||||
modifiers: KeyModifiers::empty(),
|
||||
});
|
||||
} else {
|
||||
let suggestion = format!("{}-{}", s.trim_end_matches('-'), keys::MINUS);
|
||||
anyhow::bail!(
|
||||
"Key '-' cannot be used with modifiers, use '{}' instead",
|
||||
suggestion
|
||||
)
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("Invalid key code '{}'", s)
|
||||
};
|
||||
let mut modifiers = KeyModifiers::empty();
|
||||
for token in tokens {
|
||||
let flag = match token {
|
||||
"S" => KeyModifiers::SHIFT,
|
||||
"A" => KeyModifiers::ALT,
|
||||
"C" => KeyModifiers::CONTROL,
|
||||
"Meta" | "Cmd" | "Win" => KeyModifiers::SUPER,
|
||||
_ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
|
||||
};
|
||||
|
||||
if modifiers.contains(flag) {
|
||||
return Err(anyhow!("Repeated key modifier '{}-'", token));
|
||||
}
|
||||
modifiers.insert(flag);
|
||||
}
|
||||
|
||||
// Normalize character keys so that characters like C-S-r and C-R
|
||||
// are represented by equal KeyEvents.
|
||||
match code {
|
||||
KeyCode::Char(ch)
|
||||
if ch.is_ascii_lowercase() && modifiers.contains(KeyModifiers::SHIFT) =>
|
||||
{
|
||||
code = KeyCode::Char(ch.to_ascii_uppercase());
|
||||
modifiers.remove(KeyModifiers::SHIFT);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(KeyEvent { code, modifiers })
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for KeyCode {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(match s {
|
||||
keys::BACKSPACE => KeyCode::Backspace,
|
||||
keys::ENTER => KeyCode::Enter,
|
||||
keys::LEFT => KeyCode::Left,
|
||||
|
@ -396,55 +452,8 @@ impl std::str::FromStr for KeyEvent {
|
|||
.then_some(KeyCode::F(function))
|
||||
.ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
|
||||
}
|
||||
// Checking that the last token is empty ensures that this branch is only taken if
|
||||
// `-` is used as a code. For example this branch will not be taken for `S-` (which is
|
||||
// missing a code).
|
||||
_ if s.ends_with('-') && tokens.last().is_some_and(|t| t.is_empty()) => {
|
||||
if s == "-" {
|
||||
return Ok(KeyEvent {
|
||||
code: KeyCode::Char('-'),
|
||||
modifiers: KeyModifiers::empty(),
|
||||
});
|
||||
} else {
|
||||
let suggestion = format!("{}-{}", s.trim_end_matches('-'), keys::MINUS);
|
||||
return Err(anyhow!(
|
||||
"Key '-' cannot be used with modifiers, use '{}' instead",
|
||||
suggestion
|
||||
));
|
||||
}
|
||||
}
|
||||
invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
|
||||
};
|
||||
|
||||
let mut modifiers = KeyModifiers::empty();
|
||||
for token in tokens {
|
||||
let flag = match token {
|
||||
"S" => KeyModifiers::SHIFT,
|
||||
"A" => KeyModifiers::ALT,
|
||||
"C" => KeyModifiers::CONTROL,
|
||||
"Meta" | "Cmd" | "Win" => KeyModifiers::SUPER,
|
||||
_ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
|
||||
};
|
||||
|
||||
if modifiers.contains(flag) {
|
||||
return Err(anyhow!("Repeated key modifier '{}-'", token));
|
||||
}
|
||||
modifiers.insert(flag);
|
||||
}
|
||||
|
||||
// Normalize character keys so that characters like C-S-r and C-R
|
||||
// are represented by equal KeyEvents.
|
||||
match code {
|
||||
KeyCode::Char(ch)
|
||||
if ch.is_ascii_lowercase() && modifiers.contains(KeyModifiers::SHIFT) =>
|
||||
{
|
||||
code = KeyCode::Char(ch.to_ascii_uppercase());
|
||||
modifiers.remove(KeyModifiers::SHIFT);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(KeyEvent { code, modifiers })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ pub mod info;
|
|||
pub mod input;
|
||||
pub mod keyboard;
|
||||
pub mod register;
|
||||
#[cfg(scancode)]
|
||||
pub mod scancode;
|
||||
pub mod theme;
|
||||
pub mod tree;
|
||||
pub mod view;
|
||||
|
|
|
@ -0,0 +1,749 @@
|
|||
use crate::input::KeyEvent;
|
||||
use crate::keyboard::{KeyCode, ModifierKeyCode};
|
||||
use anyhow;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::collections::HashMap;
|
||||
|
||||
type ScanCodeKeyCodeMap = HashMap<u16, (KeyCode, Option<KeyCode>)>;
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone)]
|
||||
pub struct ScanCodeMap {
|
||||
// {<code>: (char, shifted char)}
|
||||
map: ScanCodeKeyCodeMap,
|
||||
modifiers: Vec<u16>,
|
||||
shift_modifiers: Vec<u16>,
|
||||
}
|
||||
|
||||
pub use keyboard_state::KeyboardState;
|
||||
|
||||
impl Default for KeyboardState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "scancode-query")]
|
||||
mod keyboard_state {
|
||||
use keyboard_query::{DeviceQuery, DeviceState};
|
||||
|
||||
pub struct KeyboardState {
|
||||
device_state: DeviceState,
|
||||
previous_codes: Vec<u16>,
|
||||
}
|
||||
|
||||
impl KeyboardState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
previous_codes: Vec::new(),
|
||||
device_state: DeviceState::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_scancodes(&mut self) -> Vec<u16> {
|
||||
let codes = self.device_state.get_keys();
|
||||
let new_codes = if codes.len() <= 1 {
|
||||
codes.clone()
|
||||
} else {
|
||||
codes
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|c| !self.previous_codes.contains(c))
|
||||
.collect()
|
||||
};
|
||||
self.previous_codes = codes.clone();
|
||||
new_codes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "scancode-evdev")]
|
||||
mod keyboard_state {
|
||||
use evdev::{Device, KeyCode};
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
struct DeviceHandle {
|
||||
_path: std::path::PathBuf,
|
||||
_handle: tokio::task::JoinHandle<()>,
|
||||
}
|
||||
|
||||
pub struct KeyboardState {
|
||||
codes: [Arc<AtomicU16>; 2],
|
||||
_handle: Vec<DeviceHandle>,
|
||||
}
|
||||
|
||||
fn is_keyboard(device: &Device) -> bool {
|
||||
device
|
||||
.supported_keys()
|
||||
.map_or(false, |keys| keys.contains(KeyCode::KEY_ENTER))
|
||||
}
|
||||
|
||||
impl KeyboardState {
|
||||
pub fn new() -> Self {
|
||||
let key1 = Arc::new(AtomicU16::new(0));
|
||||
let key2 = Arc::new(AtomicU16::new(0));
|
||||
|
||||
// find keyboards
|
||||
let keyboards = evdev::enumerate()
|
||||
.filter(|(_, dev)| is_keyboard(dev))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut handles = Vec::new();
|
||||
|
||||
// evdev constant
|
||||
const KEY_STATE_RELEASE: i32 = 0;
|
||||
|
||||
for (path, mut item) in keyboards {
|
||||
// skip already grabbed keyboards
|
||||
let is_grabbed = item.grab().is_err();
|
||||
if !is_grabbed {
|
||||
if let Err(e) = item.ungrab() {
|
||||
log::error!("Failed to ungrab input: {e}");
|
||||
}
|
||||
}
|
||||
if is_grabbed {
|
||||
continue;
|
||||
}
|
||||
let k1 = Arc::clone(&key1);
|
||||
let k2 = Arc::clone(&key2);
|
||||
let mut codes = [0, 0];
|
||||
let device_path = path.to_str().unwrap_or_default().to_owned();
|
||||
let handle = tokio::task::spawn(async move {
|
||||
let device_name = item.name().unwrap_or_default().to_owned();
|
||||
log::info!("Start listen events from: {device_name} ({device_path})");
|
||||
let Ok(mut events) = item.into_event_stream() else {
|
||||
log::error!("Failed to stream events from: {device_name} ({device_path})");
|
||||
return;
|
||||
};
|
||||
|
||||
while let Ok(event) = events.next_event().await {
|
||||
if evdev::EventType::KEY != event.event_type() {
|
||||
continue;
|
||||
};
|
||||
let scancode: u16 = event.code();
|
||||
if event.value() == KEY_STATE_RELEASE {
|
||||
// reset state
|
||||
codes = match (codes[0] == scancode, codes[1] == scancode) {
|
||||
(true, false) => [0, codes[1]],
|
||||
(false, true) => [0, codes[0]],
|
||||
_ => [0, 0],
|
||||
}
|
||||
} else {
|
||||
// don't repeat
|
||||
if !codes.contains(&scancode) {
|
||||
codes = [codes[1], scancode];
|
||||
}
|
||||
}
|
||||
k1.store(codes[0], Ordering::Relaxed);
|
||||
k2.store(codes[1], Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
|
||||
handles.push(DeviceHandle {
|
||||
_path: path,
|
||||
_handle: handle,
|
||||
})
|
||||
}
|
||||
|
||||
Self {
|
||||
_handle: handles,
|
||||
codes: [key1, key2],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_scancodes(&mut self) -> [u16; 2] {
|
||||
[
|
||||
self.codes[1].swap(0, Ordering::Relaxed),
|
||||
self.codes[0].swap(0, Ordering::Relaxed),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "scancode-hidapi")]
|
||||
mod keyboard_state {
|
||||
use hidapi::HidApi;
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct KeyboardState {
|
||||
codes: [Arc<AtomicU16>; 2],
|
||||
_handles: Vec<std::thread::JoinHandle<()>>,
|
||||
}
|
||||
const HID_KEYBOARD_USAGE_PAGE: u16 = 0x01;
|
||||
const HID_KEYBOARD_USAGE_ID: u16 = 0x06;
|
||||
const HID_MODIFIERS_MASK: [(u8, u16); 4] = [
|
||||
(0x01, 29), // Left Control
|
||||
(0x02, 42), // Left Shift
|
||||
(0x04, 56), // Left Alt
|
||||
(0x20, 54), // Right Shift
|
||||
];
|
||||
|
||||
// https://usb.org/sites/default/files/hut1_22.pdf
|
||||
// 10 Keyboard/Keypad Page (0x07)
|
||||
fn hid_keycode_to_scancode(hid_keycode: &u8) -> Option<u16> {
|
||||
Some(match hid_keycode {
|
||||
4 => 30, // A
|
||||
5 => 48, // B
|
||||
6 => 46, // C
|
||||
7 => 32, // D
|
||||
8 => 18, // E
|
||||
9 => 33, // F
|
||||
10 => 34, // G
|
||||
11 => 35, // H
|
||||
12 => 23, // I
|
||||
13 => 36, // J
|
||||
14 => 37, // K
|
||||
15 => 38, // L
|
||||
16 => 50, // M
|
||||
17 => 49, // N
|
||||
18 => 24, // O
|
||||
19 => 25, // P
|
||||
20 => 16, // Q
|
||||
21 => 19, // R
|
||||
22 => 31, // S
|
||||
23 => 20, // T
|
||||
24 => 22, // U
|
||||
25 => 47, // V
|
||||
26 => 17, // W
|
||||
27 => 45, // X
|
||||
28 => 21, // Y
|
||||
29 => 44, // Z
|
||||
30 => 2, // 1
|
||||
31 => 3, // 2
|
||||
32 => 4, // 3
|
||||
33 => 5, // 4
|
||||
34 => 6, // 5
|
||||
35 => 7, // 6
|
||||
36 => 8, // 7
|
||||
37 => 9, // 8
|
||||
38 => 10, // 9
|
||||
39 => 11, // 0
|
||||
40 => 28, // Enter
|
||||
41 => 1, // Escape
|
||||
42 => 14, // Backspace
|
||||
43 => 15, // Tab
|
||||
44 => 57, // Space
|
||||
45 => 12, // Minus (-)
|
||||
46 => 13, // Equal (=)
|
||||
47 => 26, // Left Bracket ([)
|
||||
48 => 27, // Right Bracket (])
|
||||
49 => 43, // Backslash (\)
|
||||
50 => 43, // Non-US Hash (#)
|
||||
51 => 39, // Semicolon (;)
|
||||
52 => 40, // Apostrophe (')
|
||||
53 => 41, // Grave (`)
|
||||
54 => 51, // Comma (,)
|
||||
55 => 52, // Period (.)
|
||||
56 => 53, // Slash (/)
|
||||
57 => 58, // Caps Lock
|
||||
58 => 59, // F1
|
||||
59 => 60, // F2
|
||||
60 => 61, // F3
|
||||
61 => 62, // F4
|
||||
62 => 63, // F5
|
||||
63 => 64, // F6
|
||||
64 => 65, // F7
|
||||
65 => 66, // F8
|
||||
66 => 67, // F9
|
||||
67 => 68, // F10
|
||||
68 => 87, // F11
|
||||
69 => 88, // F12
|
||||
70 => 99, // Print Screen
|
||||
71 => 70, // Scroll Lock
|
||||
72 => 119, // Pause
|
||||
73 => 110, // Insert
|
||||
74 => 102, // Home
|
||||
75 => 104, // Page Up
|
||||
76 => 111, // Delete
|
||||
77 => 107, // End
|
||||
78 => 109, // Page Down
|
||||
79 => 106, // Right Arrow
|
||||
80 => 105, // Left Arrow
|
||||
81 => 108, // Down Arrow
|
||||
82 => 103, // Up Arrow
|
||||
83 => 69, // Num Lock
|
||||
84 => 98, // Keypad Slash (/)
|
||||
85 => 55, // Keypad Asterisk (*)
|
||||
86 => 74, // Keypad Minus (-)
|
||||
87 => 78, // Keypad Plus (+)
|
||||
88 => 96, // Keypad Enter
|
||||
89 => 79, // Keypad 1
|
||||
90 => 80, // Keypad 2
|
||||
91 => 81, // Keypad 3
|
||||
92 => 75, // Keypad 4
|
||||
93 => 76, // Keypad 5
|
||||
94 => 77, // Keypad 6
|
||||
95 => 71, // Keypad 7
|
||||
96 => 72, // Keypad 8
|
||||
97 => 73, // Keypad 9
|
||||
98 => 82, // Keypad 0
|
||||
99 => 83, // Keypad Period (.)
|
||||
100 => 127, // Non-US Backslash (|)
|
||||
101 => 115, // Application
|
||||
102 => 128, // Power
|
||||
103 => 129, // Keypad Equal (=)
|
||||
104 => 130, // F13
|
||||
105 => 131, // F14
|
||||
106 => 132, // F15
|
||||
107 => 133, // F16
|
||||
108 => 134, // F17
|
||||
109 => 135, // F18
|
||||
110 => 136, // F19
|
||||
111 => 137, // F20
|
||||
112 => 138, // F21
|
||||
113 => 139, // F22
|
||||
114 => 140, // F23
|
||||
115 => 141, // F24
|
||||
116 => 142, // Execute
|
||||
117 => 143, // Help
|
||||
118 => 144, // Menu
|
||||
119 => 145, // Select
|
||||
120 => 146, // Stop
|
||||
121 => 147, // Again
|
||||
122 => 148, // Undo
|
||||
123 => 149, // Cut
|
||||
124 => 150, // Copy
|
||||
125 => 151, // Paste
|
||||
126 => 152, // Find
|
||||
127 => 153, // Mute
|
||||
128 => 154, // Volume Up
|
||||
129 => 155, // Volume Down
|
||||
130 => 156, // Locking Caps Lock
|
||||
131 => 157, // Locking Num Lock
|
||||
132 => 158, // Locking Scroll Lock
|
||||
133 => 159, // Keypad Comma (,)
|
||||
134 => 160, // Keypad Equal Sign (=)
|
||||
135 => 161, // International1 (Ro)
|
||||
136 => 162, // International2 (Katakana/Hiragana)
|
||||
137 => 163, // International3 (Yen)
|
||||
138 => 164, // International4 (Henkan)
|
||||
139 => 165, // International5 (Muhenkan)
|
||||
140 => 166, // International6 (PC9800 Keypad ,)
|
||||
141 => 167, // International7
|
||||
142 => 168, // International8
|
||||
143 => 169, // International9
|
||||
144 => 170, // Lang1 (Hangul/English)
|
||||
145 => 171, // Lang2 (Hanja)
|
||||
146 => 172, // Lang3 (Katakana)
|
||||
147 => 173, // Lang4 (Hiragana)
|
||||
148 => 174, // Lang5 (Zenkaku/Hankaku)
|
||||
149 => 175, // Lang6
|
||||
150 => 176, // Lang7
|
||||
151 => 177, // Lang8
|
||||
152 => 178, // Lang9
|
||||
153 => 179, // Alternate Erase
|
||||
154 => 180, // SysReq/Attention
|
||||
155 => 181, // Cancel
|
||||
156 => 182, // Clear
|
||||
157 => 183, // Prior
|
||||
158 => 184, // Return
|
||||
159 => 185, // Separator
|
||||
160 => 186, // Out
|
||||
161 => 187, // Oper
|
||||
162 => 188, // Clear/Again
|
||||
163 => 189, // CrSel/Props
|
||||
164 => 190, // ExSel
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn hid_modifier_to_scancode(modifier_byte: &u8) -> Option<u16> {
|
||||
for (mask, scancode) in HID_MODIFIERS_MASK {
|
||||
if modifier_byte & mask != 0 {
|
||||
return Some(scancode);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl KeyboardState {
|
||||
pub fn new() -> Self {
|
||||
let key1 = Arc::new(AtomicU16::new(0));
|
||||
let key2 = Arc::new(AtomicU16::new(0));
|
||||
|
||||
let mut handles = Vec::new();
|
||||
|
||||
match HidApi::new() {
|
||||
Ok(api) => {
|
||||
for device in api.device_list() {
|
||||
let device_name = format!(
|
||||
"{:?} ({:04x}:{:04x}) {} {}",
|
||||
device.path(),
|
||||
device.vendor_id(),
|
||||
device.product_id(),
|
||||
device.manufacturer_string().unwrap_or("-"),
|
||||
device.product_string().unwrap_or("-")
|
||||
);
|
||||
|
||||
if !(device.usage_page() == HID_KEYBOARD_USAGE_PAGE
|
||||
&& device.usage() == HID_KEYBOARD_USAGE_ID)
|
||||
{
|
||||
log::trace!("{device_name} isn't keyboard. skip");
|
||||
continue;
|
||||
}
|
||||
|
||||
let device = match device.open_device(&api) {
|
||||
Ok(device) => {
|
||||
log::info!("{device_name} start listen input reports");
|
||||
device
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{device_name} error on open device: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let k1 = Arc::clone(&key1);
|
||||
let k2 = Arc::clone(&key2);
|
||||
handles.push(std::thread::spawn(move || {
|
||||
let mut report = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
loop {
|
||||
match device.read(&mut report) {
|
||||
Ok(read) if read < 8 => {
|
||||
log::warn!("{device_name} partial read of input report");
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{device_name} read event error: {e}");
|
||||
continue;
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
let mut pressed= 0;
|
||||
|
||||
// use last pressed key
|
||||
for i in 0..6 {
|
||||
let hid_keycode = report[7 - i];
|
||||
if hid_keycode == 0 {
|
||||
continue;
|
||||
};
|
||||
let Some(scancode) = hid_keycode_to_scancode(&hid_keycode)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
log::trace!(
|
||||
"{device_name} hid_keycode: {hid_keycode} scancode: {scancode}"
|
||||
);
|
||||
pressed = scancode;
|
||||
break;
|
||||
}
|
||||
|
||||
k1.store(pressed, Ordering::Relaxed);
|
||||
k2.store(hid_modifier_to_scancode(&report[0]).unwrap_or(0), Ordering::Relaxed);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Error on initialize hidapi: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
_handles: handles,
|
||||
codes: [key1, key2],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_scancodes(&mut self) -> [u16; 2] {
|
||||
[
|
||||
self.codes[0].load(Ordering::Relaxed), // key
|
||||
self.codes[1].load(Ordering::Relaxed), // modifier
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScanCodeMap {
|
||||
pub fn new(map: HashMap<u16, (KeyCode, Option<KeyCode>)>) -> Self {
|
||||
let modifiers = map
|
||||
.iter()
|
||||
.filter_map(|(code, (key, _))| {
|
||||
if matches!(key, KeyCode::Modifier(_)) {
|
||||
Some(*code)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let shift_modifiers = map
|
||||
.iter()
|
||||
.filter_map(|(code, (key, _))| {
|
||||
if matches!(
|
||||
key,
|
||||
KeyCode::Modifier(ModifierKeyCode::LeftShift)
|
||||
| KeyCode::Modifier(ModifierKeyCode::RightShift)
|
||||
) {
|
||||
Some(*code)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
map,
|
||||
modifiers,
|
||||
shift_modifiers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply(&self, event: KeyEvent, keyboard: &mut KeyboardState) -> KeyEvent {
|
||||
let codes = keyboard.get_scancodes();
|
||||
|
||||
// get first non modifier key code
|
||||
let Some(scancode) = codes
|
||||
.iter()
|
||||
.find(|c| **c != 0 || !self.modifiers.contains(c))
|
||||
.cloned()
|
||||
else {
|
||||
return event;
|
||||
};
|
||||
|
||||
let Some((key, shifted_key)) = self.map.get(&scancode) else {
|
||||
return event;
|
||||
};
|
||||
|
||||
let event_before = event;
|
||||
|
||||
let mut is_shifted = false;
|
||||
for c in &self.shift_modifiers {
|
||||
if codes.contains(c) {
|
||||
is_shifted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let event = KeyEvent {
|
||||
code: match key {
|
||||
KeyCode::Char(c) => {
|
||||
if is_shifted | c.is_ascii_uppercase() {
|
||||
(*shifted_key).unwrap_or(*key)
|
||||
} else {
|
||||
*key
|
||||
}
|
||||
}
|
||||
_ => *key,
|
||||
},
|
||||
..event
|
||||
};
|
||||
|
||||
log::trace!(
|
||||
"{:?} map to {:?} by scancode {codes:?} (code: {scancode}, key: {key:?}, shifted key: {shifted_key:?})",
|
||||
event_before.code,
|
||||
event.code,
|
||||
);
|
||||
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_scancode<'de, D>(deserializer: D) -> Result<ScanCodeMap, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ScanCodeRawConfig {
|
||||
layout: String,
|
||||
map: Option<HashMap<String, Vec<(u16, Vec<String>)>>>,
|
||||
}
|
||||
|
||||
let value = ScanCodeRawConfig::deserialize(deserializer)?;
|
||||
|
||||
// load only specified in user settings layout
|
||||
let map = if let Some(map) = value
|
||||
.map
|
||||
.and_then(|m| m.into_iter().find(|(k, _)| k == &value.layout))
|
||||
{
|
||||
HashMap::from_iter(
|
||||
map.1
|
||||
.into_iter()
|
||||
.map(|(scancode, chars)| {
|
||||
if chars.is_empty() {
|
||||
anyhow::bail!(
|
||||
"Invalid scancode. Empty map for scancode: {scancode} on layout: {}",
|
||||
value.layout
|
||||
);
|
||||
}
|
||||
if chars.len() > 2 {
|
||||
anyhow::bail!(
|
||||
"Invalid scancode. To many variants for scancode: {scancode} on layout: {}",
|
||||
value.layout
|
||||
);
|
||||
}
|
||||
let keycode = str::parse::<KeyCode>(&chars[0]).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"On parse scancode: {scancode} on layout: {} - {e}",
|
||||
value.layout
|
||||
)
|
||||
})?;
|
||||
let shifted_keycode = if let Some(c) = chars.get(1) {
|
||||
Some(str::parse::<KeyCode>(c).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"On parse scancode: {scancode} on layout: {} - {e}",
|
||||
value.layout
|
||||
)
|
||||
})?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok((scancode, (keycode, shifted_keycode)))
|
||||
})
|
||||
.collect::<anyhow::Result<Vec<_>>>()
|
||||
.map_err(|e| <D::Error as Error>::custom(e))?,
|
||||
)
|
||||
} else {
|
||||
// lookup in hardcoded defaults
|
||||
let Some(map) = defaults::LAYOUTS.get(value.layout.as_str()) else {
|
||||
return Err(<D::Error as Error>::custom(format!(
|
||||
"Scancode layout not found for: {}",
|
||||
value.layout
|
||||
)));
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
"User defined scancode layout not found: {}. Use default",
|
||||
value.layout
|
||||
);
|
||||
|
||||
map.to_owned()
|
||||
};
|
||||
|
||||
Ok(ScanCodeMap::new(map))
|
||||
}
|
||||
|
||||
mod defaults {
|
||||
|
||||
use super::ScanCodeKeyCodeMap;
|
||||
use crate::keyboard::KeyCode;
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
macro_rules! entry {
|
||||
($scancode:expr, $keycode:literal) => {
|
||||
(
|
||||
$scancode,
|
||||
(
|
||||
KeyCode::from_str($keycode).expect("Failed to parse {$keycode} as KeyCode"),
|
||||
None,
|
||||
),
|
||||
)
|
||||
};
|
||||
($scancode:expr, $keycode:literal, $shifted_keycode:literal) => {
|
||||
(
|
||||
$scancode,
|
||||
(
|
||||
KeyCode::from_str($keycode).expect("Failed to parse {$keycode} as KeyCode"),
|
||||
Some(
|
||||
KeyCode::from_str($shifted_keycode)
|
||||
.expect("Failed to parse {$shifted_keycode} as KeyCode"),
|
||||
),
|
||||
),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
pub static LAYOUTS: once_cell::sync::Lazy<HashMap<&'static str, ScanCodeKeyCodeMap>> =
|
||||
once_cell::sync::Lazy::new(init);
|
||||
|
||||
fn init() -> HashMap<&'static str, ScanCodeKeyCodeMap> {
|
||||
HashMap::from_iter([qwerty()])
|
||||
}
|
||||
|
||||
fn qwerty() -> (&'static str, ScanCodeKeyCodeMap) {
|
||||
// https://github.com/emberian/evdev/blob/8feea0685b0acb8153e394ffc393cf560d30a16f/src/scancodes.rs#L30
|
||||
(
|
||||
"qwerty",
|
||||
HashMap::from_iter([
|
||||
entry!(1, "esc"),
|
||||
entry!(2, "1", "!"),
|
||||
entry!(3, "2", "@"),
|
||||
entry!(4, "3", "#"),
|
||||
entry!(5, "4", "$"),
|
||||
entry!(5, "4", "$"),
|
||||
entry!(6, "5", "%"),
|
||||
entry!(7, "6", "^"),
|
||||
entry!(8, "7", "&"),
|
||||
entry!(9, "8", "*"),
|
||||
entry!(10, "9", "("),
|
||||
entry!(11, "0", ")"),
|
||||
entry!(12, "-", "_"),
|
||||
entry!(13, "=", "+"),
|
||||
entry!(14, "backspace"),
|
||||
entry!(15, "tab"),
|
||||
entry!(16, "q", "Q"),
|
||||
entry!(17, "w", "W"),
|
||||
entry!(18, "e", "E"),
|
||||
entry!(19, "r", "R"),
|
||||
entry!(20, "t", "T"),
|
||||
entry!(21, "y", "Y"),
|
||||
entry!(22, "u", "U"),
|
||||
entry!(23, "i", "I"),
|
||||
entry!(24, "o", "O"),
|
||||
entry!(25, "p", "P"),
|
||||
entry!(26, "[", "{"),
|
||||
entry!(27, "]", "}"),
|
||||
entry!(28, "ret"),
|
||||
entry!(29, "leftcontrol"),
|
||||
entry!(30, "a", "A"),
|
||||
entry!(31, "s", "S"),
|
||||
entry!(32, "d", "D"),
|
||||
entry!(33, "f", "F"),
|
||||
entry!(34, "g", "G"),
|
||||
entry!(35, "h", "H"),
|
||||
entry!(36, "j", "J"),
|
||||
entry!(37, "k", "K"),
|
||||
entry!(38, "l", "L"),
|
||||
entry!(39, ";", ":"),
|
||||
entry!(40, "'", "\""),
|
||||
entry!(41, "`", "~"),
|
||||
entry!(42, "leftshift"),
|
||||
entry!(43, "\\", "|"),
|
||||
entry!(44, "z", "Z"),
|
||||
entry!(45, "x", "X"),
|
||||
entry!(46, "c", "C"),
|
||||
entry!(47, "v", "V"),
|
||||
entry!(48, "b", "B"),
|
||||
entry!(49, "n", "N"),
|
||||
entry!(50, "m", "M"),
|
||||
entry!(51, ",", "<"),
|
||||
entry!(52, ".", ">"),
|
||||
entry!(53, "/", "|"),
|
||||
entry!(54, "rightshift"),
|
||||
entry!(55, "printscreen"),
|
||||
entry!(56, "leftalt"),
|
||||
entry!(57, "space"),
|
||||
entry!(58, "capslock"),
|
||||
entry!(59, "F1"),
|
||||
entry!(60, "F2"),
|
||||
entry!(61, "F3"),
|
||||
entry!(62, "F4"),
|
||||
entry!(63, "F5"),
|
||||
entry!(64, "F6"),
|
||||
entry!(65, "F7"),
|
||||
entry!(66, "F8"),
|
||||
entry!(67, "F9"),
|
||||
entry!(68, "F10"),
|
||||
entry!(74, "-"),
|
||||
entry!(78, "+"),
|
||||
// Not processes by Helix
|
||||
// entry!(69, "numlock"),
|
||||
// entry!(70, "scrolllock"),
|
||||
// entry!(71, "home"),
|
||||
// entry!(72, "up"),
|
||||
// entry!(73, "pageup"),
|
||||
// entry!(75, "left"),
|
||||
// entry!(77, "right"),
|
||||
// entry!(79, "end"),
|
||||
// entry!(80, "down"),
|
||||
// entry!(81, "pagedown"),
|
||||
// entry!(82, "ins"),
|
||||
// entry!(83, "del"),
|
||||
]),
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue