├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix ├── lefthk-core ├── Cargo.toml ├── README.md └── src │ ├── child.rs │ ├── config │ ├── command │ │ ├── chord.rs │ │ ├── execute.rs │ │ ├── exit_chord.rs │ │ ├── kill.rs │ │ ├── mod.rs │ │ ├── reload.rs │ │ └── utils │ │ │ ├── denormalize_function.rs │ │ │ ├── mod.rs │ │ │ └── normalized_command.rs │ ├── keybind.rs │ └── mod.rs │ ├── errors.rs │ ├── ipc.rs │ ├── lib.rs │ ├── tests.rs │ ├── worker │ ├── context │ │ ├── chord.rs │ │ └── mod.rs │ └── mod.rs │ ├── xkeysym_lookup.rs │ └── xwrap.rs └── lefthk ├── Cargo.toml ├── README.md └── src ├── config ├── command.rs ├── key.rs ├── keybind.rs └── mod.rs ├── errors.rs ├── main.rs └── tests.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | RUSTFLAGS: -Dwarnings 12 | RUSTDOCFLAGS: -Dwarnings 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: apt update 20 | run: sudo apt update 21 | - name: apt install libsystemd-dev 22 | run: sudo apt install -y --no-install-recommends libsystemd-dev 23 | - name: Build 24 | run: cargo build --all-targets --all-features 25 | - name: Run tests 26 | run: cargo test --all-targets --all-features 27 | 28 | clippy: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v2 32 | - name: apt update 33 | run: sudo apt update 34 | - name: apt install libsystemd-dev 35 | run: sudo apt install -y --no-install-recommends libsystemd-dev 36 | - name: Clippy 37 | run: cargo clippy --all-targets --all-features 38 | 39 | fmt: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v2 43 | - name: Fmt 44 | run: cargo fmt -- --check 45 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Upload release artifacts 2 | 3 | on: 4 | release: 5 | types: [created] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | binaries: 10 | strategy: 11 | matrix: 12 | include: 13 | - os: ubuntu-latest 14 | rust_target: x86_64-unknown-linux-gnu 15 | asset_name: lefthk_x86_64_linux_gnu 16 | 17 | runs-on: ${{ matrix.os }} 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | with: 23 | submodules: true 24 | 25 | - name: Cache 26 | uses: actions/cache@v3 27 | with: 28 | path: | 29 | ~/.cargo 30 | ~/.rustup 31 | target/ 32 | key: ${{ runner.os }}-${{ steps.rust-install.outputs.cachekey }}-${{ matrix.rust_target }}-binary-release 33 | 34 | - name: Install rust 35 | id: rust-install 36 | uses: dtolnay/rust-toolchain@stable 37 | with: 38 | targets: ${{ matrix.rust_target }} 39 | 40 | - name: Set Cargo.toml version 41 | if: github.event_name == 'release' 42 | shell: bash 43 | env: 44 | RELEASE_TAG: ${{ github.ref }} 45 | run: | 46 | mv Cargo.toml Cargo.toml.origl2 47 | sed "s/[0-9]*\\.[0-9]*\\.[0-9]*-git/${RELEASE_TAG##*\/v}/" Cargo.toml.origl2 > Cargo.toml 48 | mv Cargo.lock Cargo.lock.origl2 49 | sed "s/[0-9]*\\.[0-9]*\\.[0-9]*-git/${RELEASE_TAG##*\/v}/" Cargo.lock.origl2 > Cargo.lock 50 | - name: Install cross-compile linker for aarch64-unknown-linux-musl 51 | if: matrix.rust_target == 'aarch64-unknown-linux-musl' 52 | run: | 53 | sudo apt update 54 | sudo apt install gcc-aarch64-linux-gnu 55 | - name: Install openssl 56 | run: | 57 | sudo apt update 58 | sudo apt install openssl pkg-config libssl-dev 59 | cargo clean 60 | OPENSSL_LIB_DIR="/usr/lib/x86_64-linux-gnu" 61 | OPENSSL_INCLUDE_DIR="/usr/include/openssl" 62 | 63 | - name: Build 64 | env: 65 | CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: "/usr/bin/aarch64-linux-gnu-ld" 66 | run: | 67 | cargo +${{ steps.rust-install.outputs.name }} build --target ${{ matrix.rust_target }} --release --locked 68 | 69 | - name: Upload 70 | uses: svenstaro/upload-release-action@v2 71 | with: 72 | repo_token: ${{ secrets.GITHUB_TOKEN }} 73 | tag: ${{ github.ref }} 74 | file: target/${{ matrix.rust_target }}/release/lefthk 75 | asset_name: ${{ matrix.asset_name }} 76 | 77 | crate: 78 | runs-on: ubuntu-latest 79 | 80 | steps: 81 | - name: Checkout 82 | uses: actions/checkout@v3 83 | with: 84 | submodules: true 85 | 86 | - name: Cache 87 | uses: actions/cache@v3 88 | with: 89 | path: | 90 | ~/.cargo 91 | ~/.rustup 92 | target/ 93 | key: ${{ runner.os }}-${{ steps.rust-install.outputs.cachekey }}-crate-release 94 | 95 | - name: Install rust 96 | id: rust-install 97 | uses: dtolnay/rust-toolchain@stable 98 | 99 | - name: Install openssl 100 | run: | 101 | sudo apt update 102 | sudo apt install openssl pkg-config libssl-dev 103 | cargo clean 104 | OPENSSL_LIB_DIR="/usr/lib/x86_64-linux-gnu" 105 | OPENSSL_INCLUDE_DIR="/usr/include/openssl" 106 | 107 | - name: Set Cargo.toml version 108 | if: github.event_name == 'release' 109 | shell: bash 110 | env: 111 | RELEASE_TAG: ${{ github.ref }} 112 | run: | 113 | mv Cargo.toml Cargo.toml.origl2 114 | sed "s/[0-9]*\\.[0-9]*\\.[0-9]*-git/${RELEASE_TAG##*\/v}/" Cargo.toml.origl2 > Cargo.toml 115 | mv Cargo.lock Cargo.lock.origl2 116 | sed "s/[0-9]*\\.[0-9]*\\.[0-9]*-git/${RELEASE_TAG##*\/v}/" Cargo.lock.origl2 > Cargo.lock 117 | - name: Publish core crate 118 | if: github.event_name == 'release' 119 | env: 120 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 121 | run: | 122 | cargo publish --allow-dirty -p lefthk-core 123 | - name: Publish binary crate 124 | if: github.event_name == 'release' 125 | env: 126 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 127 | run: | 128 | cargo publish --allow-dirty -p lefthk 129 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/*.rs.bk 3 | leftwm.logs 4 | .idea/ 5 | 6 | result 7 | .direnv 8 | .envrc 9 | 10 | sweep.timestamptarget 11 | 12 | .toggletasks.json 13 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "atty" 31 | version = "0.2.14" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 34 | dependencies = [ 35 | "hermit-abi 0.1.19", 36 | "libc", 37 | "winapi", 38 | ] 39 | 40 | [[package]] 41 | name = "autocfg" 42 | version = "1.4.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 45 | 46 | [[package]] 47 | name = "backtrace" 48 | version = "0.3.74" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 51 | dependencies = [ 52 | "addr2line", 53 | "cfg-if", 54 | "libc", 55 | "miniz_oxide", 56 | "object", 57 | "rustc-demangle", 58 | "windows-targets", 59 | ] 60 | 61 | [[package]] 62 | name = "base64" 63 | version = "0.21.7" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 66 | 67 | [[package]] 68 | name = "bitflags" 69 | version = "1.3.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 72 | 73 | [[package]] 74 | name = "bitflags" 75 | version = "2.6.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 78 | dependencies = [ 79 | "serde", 80 | ] 81 | 82 | [[package]] 83 | name = "bytes" 84 | version = "1.7.2" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" 87 | 88 | [[package]] 89 | name = "cfg-if" 90 | version = "1.0.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 93 | 94 | [[package]] 95 | name = "cfg_aliases" 96 | version = "0.2.1" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 99 | 100 | [[package]] 101 | name = "clap" 102 | version = "3.2.25" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" 105 | dependencies = [ 106 | "atty", 107 | "bitflags 1.3.2", 108 | "clap_lex", 109 | "indexmap", 110 | "once_cell", 111 | "strsim", 112 | "termcolor", 113 | "textwrap", 114 | ] 115 | 116 | [[package]] 117 | name = "clap_lex" 118 | version = "0.2.4" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 121 | dependencies = [ 122 | "os_str_bytes", 123 | ] 124 | 125 | [[package]] 126 | name = "errno" 127 | version = "0.3.9" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 130 | dependencies = [ 131 | "libc", 132 | "windows-sys 0.52.0", 133 | ] 134 | 135 | [[package]] 136 | name = "fastrand" 137 | version = "2.1.1" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 140 | 141 | [[package]] 142 | name = "gimli" 143 | version = "0.31.1" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 146 | 147 | [[package]] 148 | name = "hashbrown" 149 | version = "0.12.3" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 152 | 153 | [[package]] 154 | name = "hermit-abi" 155 | version = "0.1.19" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 158 | dependencies = [ 159 | "libc", 160 | ] 161 | 162 | [[package]] 163 | name = "hermit-abi" 164 | version = "0.3.9" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 167 | 168 | [[package]] 169 | name = "indexmap" 170 | version = "1.9.3" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 173 | dependencies = [ 174 | "autocfg", 175 | "hashbrown", 176 | ] 177 | 178 | [[package]] 179 | name = "inventory" 180 | version = "0.3.15" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" 183 | 184 | [[package]] 185 | name = "lazy_static" 186 | version = "1.5.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 189 | 190 | [[package]] 191 | name = "lefthk" 192 | version = "0.2.2" 193 | dependencies = [ 194 | "clap", 195 | "lefthk-core", 196 | "ron", 197 | "serde", 198 | "tempfile", 199 | "thiserror", 200 | "tokio", 201 | "tracing", 202 | "tracing-subscriber", 203 | "xdg", 204 | ] 205 | 206 | [[package]] 207 | name = "lefthk-core" 208 | version = "0.2.2" 209 | dependencies = [ 210 | "inventory", 211 | "mio", 212 | "nix", 213 | "ron", 214 | "serde", 215 | "signal-hook", 216 | "tempfile", 217 | "thiserror", 218 | "tokio", 219 | "tracing", 220 | "x11-dl", 221 | "xdg", 222 | ] 223 | 224 | [[package]] 225 | name = "libc" 226 | version = "0.2.160" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f" 229 | 230 | [[package]] 231 | name = "linux-raw-sys" 232 | version = "0.4.14" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 235 | 236 | [[package]] 237 | name = "log" 238 | version = "0.4.22" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 241 | 242 | [[package]] 243 | name = "matchers" 244 | version = "0.1.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 247 | dependencies = [ 248 | "regex-automata 0.1.10", 249 | ] 250 | 251 | [[package]] 252 | name = "memchr" 253 | version = "2.7.4" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 256 | 257 | [[package]] 258 | name = "miniz_oxide" 259 | version = "0.8.0" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 262 | dependencies = [ 263 | "adler2", 264 | ] 265 | 266 | [[package]] 267 | name = "mio" 268 | version = "1.0.2" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 271 | dependencies = [ 272 | "hermit-abi 0.3.9", 273 | "libc", 274 | "log", 275 | "wasi", 276 | "windows-sys 0.52.0", 277 | ] 278 | 279 | [[package]] 280 | name = "nix" 281 | version = "0.29.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 284 | dependencies = [ 285 | "bitflags 2.6.0", 286 | "cfg-if", 287 | "cfg_aliases", 288 | "libc", 289 | ] 290 | 291 | [[package]] 292 | name = "nu-ansi-term" 293 | version = "0.46.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 296 | dependencies = [ 297 | "overload", 298 | "winapi", 299 | ] 300 | 301 | [[package]] 302 | name = "object" 303 | version = "0.36.5" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 306 | dependencies = [ 307 | "memchr", 308 | ] 309 | 310 | [[package]] 311 | name = "once_cell" 312 | version = "1.20.2" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 315 | 316 | [[package]] 317 | name = "os_str_bytes" 318 | version = "6.6.1" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" 321 | 322 | [[package]] 323 | name = "overload" 324 | version = "0.1.1" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 327 | 328 | [[package]] 329 | name = "pin-project-lite" 330 | version = "0.2.14" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 333 | 334 | [[package]] 335 | name = "pkg-config" 336 | version = "0.3.31" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 339 | 340 | [[package]] 341 | name = "proc-macro2" 342 | version = "1.0.88" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" 345 | dependencies = [ 346 | "unicode-ident", 347 | ] 348 | 349 | [[package]] 350 | name = "quote" 351 | version = "1.0.37" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 354 | dependencies = [ 355 | "proc-macro2", 356 | ] 357 | 358 | [[package]] 359 | name = "regex" 360 | version = "1.11.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" 363 | dependencies = [ 364 | "aho-corasick", 365 | "memchr", 366 | "regex-automata 0.4.8", 367 | "regex-syntax 0.8.5", 368 | ] 369 | 370 | [[package]] 371 | name = "regex-automata" 372 | version = "0.1.10" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 375 | dependencies = [ 376 | "regex-syntax 0.6.29", 377 | ] 378 | 379 | [[package]] 380 | name = "regex-automata" 381 | version = "0.4.8" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 384 | dependencies = [ 385 | "aho-corasick", 386 | "memchr", 387 | "regex-syntax 0.8.5", 388 | ] 389 | 390 | [[package]] 391 | name = "regex-syntax" 392 | version = "0.6.29" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 395 | 396 | [[package]] 397 | name = "regex-syntax" 398 | version = "0.8.5" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 401 | 402 | [[package]] 403 | name = "ron" 404 | version = "0.8.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" 407 | dependencies = [ 408 | "base64", 409 | "bitflags 2.6.0", 410 | "serde", 411 | "serde_derive", 412 | ] 413 | 414 | [[package]] 415 | name = "rustc-demangle" 416 | version = "0.1.24" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 419 | 420 | [[package]] 421 | name = "rustix" 422 | version = "0.38.37" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 425 | dependencies = [ 426 | "bitflags 2.6.0", 427 | "errno", 428 | "libc", 429 | "linux-raw-sys", 430 | "windows-sys 0.52.0", 431 | ] 432 | 433 | [[package]] 434 | name = "serde" 435 | version = "1.0.210" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 438 | dependencies = [ 439 | "serde_derive", 440 | ] 441 | 442 | [[package]] 443 | name = "serde_derive" 444 | version = "1.0.210" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 447 | dependencies = [ 448 | "proc-macro2", 449 | "quote", 450 | "syn", 451 | ] 452 | 453 | [[package]] 454 | name = "sharded-slab" 455 | version = "0.1.7" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 458 | dependencies = [ 459 | "lazy_static", 460 | ] 461 | 462 | [[package]] 463 | name = "signal-hook" 464 | version = "0.3.17" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 467 | dependencies = [ 468 | "libc", 469 | "signal-hook-registry", 470 | ] 471 | 472 | [[package]] 473 | name = "signal-hook-registry" 474 | version = "1.4.2" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 477 | dependencies = [ 478 | "libc", 479 | ] 480 | 481 | [[package]] 482 | name = "smallvec" 483 | version = "1.13.2" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 486 | 487 | [[package]] 488 | name = "socket2" 489 | version = "0.5.7" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 492 | dependencies = [ 493 | "libc", 494 | "windows-sys 0.52.0", 495 | ] 496 | 497 | [[package]] 498 | name = "strsim" 499 | version = "0.10.0" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 502 | 503 | [[package]] 504 | name = "syn" 505 | version = "2.0.79" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 508 | dependencies = [ 509 | "proc-macro2", 510 | "quote", 511 | "unicode-ident", 512 | ] 513 | 514 | [[package]] 515 | name = "tempfile" 516 | version = "3.13.0" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" 519 | dependencies = [ 520 | "cfg-if", 521 | "fastrand", 522 | "once_cell", 523 | "rustix", 524 | "windows-sys 0.59.0", 525 | ] 526 | 527 | [[package]] 528 | name = "termcolor" 529 | version = "1.4.1" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 532 | dependencies = [ 533 | "winapi-util", 534 | ] 535 | 536 | [[package]] 537 | name = "textwrap" 538 | version = "0.16.1" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" 541 | 542 | [[package]] 543 | name = "thiserror" 544 | version = "1.0.64" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 547 | dependencies = [ 548 | "thiserror-impl", 549 | ] 550 | 551 | [[package]] 552 | name = "thiserror-impl" 553 | version = "1.0.64" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 556 | dependencies = [ 557 | "proc-macro2", 558 | "quote", 559 | "syn", 560 | ] 561 | 562 | [[package]] 563 | name = "thread_local" 564 | version = "1.1.8" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 567 | dependencies = [ 568 | "cfg-if", 569 | "once_cell", 570 | ] 571 | 572 | [[package]] 573 | name = "tokio" 574 | version = "1.40.0" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" 577 | dependencies = [ 578 | "backtrace", 579 | "bytes", 580 | "libc", 581 | "mio", 582 | "pin-project-lite", 583 | "socket2", 584 | "tokio-macros", 585 | "windows-sys 0.52.0", 586 | ] 587 | 588 | [[package]] 589 | name = "tokio-macros" 590 | version = "2.4.0" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 593 | dependencies = [ 594 | "proc-macro2", 595 | "quote", 596 | "syn", 597 | ] 598 | 599 | [[package]] 600 | name = "tracing" 601 | version = "0.1.40" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 604 | dependencies = [ 605 | "pin-project-lite", 606 | "tracing-attributes", 607 | "tracing-core", 608 | ] 609 | 610 | [[package]] 611 | name = "tracing-attributes" 612 | version = "0.1.27" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 615 | dependencies = [ 616 | "proc-macro2", 617 | "quote", 618 | "syn", 619 | ] 620 | 621 | [[package]] 622 | name = "tracing-core" 623 | version = "0.1.32" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 626 | dependencies = [ 627 | "once_cell", 628 | "valuable", 629 | ] 630 | 631 | [[package]] 632 | name = "tracing-log" 633 | version = "0.2.0" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 636 | dependencies = [ 637 | "log", 638 | "once_cell", 639 | "tracing-core", 640 | ] 641 | 642 | [[package]] 643 | name = "tracing-subscriber" 644 | version = "0.3.18" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 647 | dependencies = [ 648 | "matchers", 649 | "nu-ansi-term", 650 | "once_cell", 651 | "regex", 652 | "sharded-slab", 653 | "smallvec", 654 | "thread_local", 655 | "tracing", 656 | "tracing-core", 657 | "tracing-log", 658 | ] 659 | 660 | [[package]] 661 | name = "unicode-ident" 662 | version = "1.0.13" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 665 | 666 | [[package]] 667 | name = "valuable" 668 | version = "0.1.0" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 671 | 672 | [[package]] 673 | name = "wasi" 674 | version = "0.11.0+wasi-snapshot-preview1" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 677 | 678 | [[package]] 679 | name = "winapi" 680 | version = "0.3.9" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 683 | dependencies = [ 684 | "winapi-i686-pc-windows-gnu", 685 | "winapi-x86_64-pc-windows-gnu", 686 | ] 687 | 688 | [[package]] 689 | name = "winapi-i686-pc-windows-gnu" 690 | version = "0.4.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 693 | 694 | [[package]] 695 | name = "winapi-util" 696 | version = "0.1.9" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 699 | dependencies = [ 700 | "windows-sys 0.59.0", 701 | ] 702 | 703 | [[package]] 704 | name = "winapi-x86_64-pc-windows-gnu" 705 | version = "0.4.0" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 708 | 709 | [[package]] 710 | name = "windows-sys" 711 | version = "0.52.0" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 714 | dependencies = [ 715 | "windows-targets", 716 | ] 717 | 718 | [[package]] 719 | name = "windows-sys" 720 | version = "0.59.0" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 723 | dependencies = [ 724 | "windows-targets", 725 | ] 726 | 727 | [[package]] 728 | name = "windows-targets" 729 | version = "0.52.6" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 732 | dependencies = [ 733 | "windows_aarch64_gnullvm", 734 | "windows_aarch64_msvc", 735 | "windows_i686_gnu", 736 | "windows_i686_gnullvm", 737 | "windows_i686_msvc", 738 | "windows_x86_64_gnu", 739 | "windows_x86_64_gnullvm", 740 | "windows_x86_64_msvc", 741 | ] 742 | 743 | [[package]] 744 | name = "windows_aarch64_gnullvm" 745 | version = "0.52.6" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 748 | 749 | [[package]] 750 | name = "windows_aarch64_msvc" 751 | version = "0.52.6" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 754 | 755 | [[package]] 756 | name = "windows_i686_gnu" 757 | version = "0.52.6" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 760 | 761 | [[package]] 762 | name = "windows_i686_gnullvm" 763 | version = "0.52.6" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 766 | 767 | [[package]] 768 | name = "windows_i686_msvc" 769 | version = "0.52.6" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 772 | 773 | [[package]] 774 | name = "windows_x86_64_gnu" 775 | version = "0.52.6" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 778 | 779 | [[package]] 780 | name = "windows_x86_64_gnullvm" 781 | version = "0.52.6" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 784 | 785 | [[package]] 786 | name = "windows_x86_64_msvc" 787 | version = "0.52.6" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 790 | 791 | [[package]] 792 | name = "x11-dl" 793 | version = "2.21.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" 796 | dependencies = [ 797 | "libc", 798 | "once_cell", 799 | "pkg-config", 800 | ] 801 | 802 | [[package]] 803 | name = "xdg" 804 | version = "2.5.2" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" 807 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | default-members = ["lefthk", "lefthk-core"] 3 | members = ["lefthk", "lefthk-core"] 4 | resolver = "2" 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, leftwm 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeftHK 2 | LeftHK - A hotkey daemon written in Rust 3 | 4 | *THIS IS BETA SOFTWARE* 5 | 6 | The configuration file should be created in ~/.config/lefthk/ and called config.ron. If the configuration file is not created the program will exit. 7 | Example config: 8 | ```ron 9 | #![enable(implicit_some)] 10 | Config( 11 | default_modifier: ["Mod4", "Shift"], 12 | keybinds: [ 13 | Keybind( 14 | command: Execute("st -e htop"), 15 | key: Key("x"), 16 | ), 17 | Keybind( 18 | command: Executes(["st -e htop", "st -e bpytop"]), 19 | key: Keys(["x", "m"]), 20 | ), 21 | Keybind( 22 | command: Chord([ 23 | Keybind( 24 | command: Execute("st -e htop"), 25 | modifier: ["Mod4"], 26 | key: Key("c"), 27 | ), 28 | ]), 29 | modifier: ["Mod4"], 30 | key: Key("c"), 31 | ), 32 | ] 33 | ) 34 | ``` 35 | Reload, Kill, Chord, and ExitChord are the only internal commands. To run a normal command you need 36 | to call Execute or Executes, with the added value or values of the command. A chord can accept any amount and type of extra 37 | keybinds, which when started blocks previous keybinds and will exit once a sub-keybind is 38 | executed. A Chord will take the ExitChord set within it first, then if not set it will take the 39 | ExitChord from its parent (e.g. a Chord within a Chord will take the ExitChord from the previous Chord). 40 | There is a pipe which receives commands through $XDG_RUNTIME_DIR/lefthk/commands.pipe, currently 41 | only accepts Reload and Kill. 42 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "fenix": { 4 | "inputs": { 5 | "nixpkgs": [ 6 | "nixpkgs" 7 | ], 8 | "rust-analyzer-src": "rust-analyzer-src" 9 | }, 10 | "locked": { 11 | "lastModified": 1730442928, 12 | "narHash": "sha256-U1DWb5c3EfkA7pqx5V1H4AWRA+EaE6UJ0lIRvK1RxgM=", 13 | "owner": "nix-community", 14 | "repo": "fenix", 15 | "rev": "87b4d20f896c99018dde4702a9c6157b516f2a76", 16 | "type": "github" 17 | }, 18 | "original": { 19 | "owner": "nix-community", 20 | "ref": "monthly", 21 | "repo": "fenix", 22 | "type": "github" 23 | } 24 | }, 25 | "flake-utils": { 26 | "inputs": { 27 | "systems": "systems" 28 | }, 29 | "locked": { 30 | "lastModified": 1731533236, 31 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 32 | "owner": "numtide", 33 | "repo": "flake-utils", 34 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 35 | "type": "github" 36 | }, 37 | "original": { 38 | "owner": "numtide", 39 | "repo": "flake-utils", 40 | "type": "github" 41 | } 42 | }, 43 | "naersk": { 44 | "inputs": { 45 | "nixpkgs": [ 46 | "nixpkgs" 47 | ] 48 | }, 49 | "locked": { 50 | "lastModified": 1721727458, 51 | "narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=", 52 | "owner": "nix-community", 53 | "repo": "naersk", 54 | "rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11", 55 | "type": "github" 56 | }, 57 | "original": { 58 | "owner": "nix-community", 59 | "repo": "naersk", 60 | "type": "github" 61 | } 62 | }, 63 | "nixpkgs": { 64 | "locked": { 65 | "lastModified": 1731763621, 66 | "narHash": "sha256-ddcX4lQL0X05AYkrkV2LMFgGdRvgap7Ho8kgon3iWZk=", 67 | "owner": "nixos", 68 | "repo": "nixpkgs", 69 | "rev": "c69a9bffbecde46b4b939465422ddc59493d3e4d", 70 | "type": "github" 71 | }, 72 | "original": { 73 | "owner": "nixos", 74 | "ref": "nixpkgs-unstable", 75 | "repo": "nixpkgs", 76 | "type": "github" 77 | } 78 | }, 79 | "root": { 80 | "inputs": { 81 | "fenix": "fenix", 82 | "flake-utils": "flake-utils", 83 | "naersk": "naersk", 84 | "nixpkgs": "nixpkgs" 85 | } 86 | }, 87 | "rust-analyzer-src": { 88 | "flake": false, 89 | "locked": { 90 | "lastModified": 1730386175, 91 | "narHash": "sha256-0Uq+/B8eu7pw8B8pxuGdFYKjcVLwNMcHfDxU9sXh7rg=", 92 | "owner": "rust-lang", 93 | "repo": "rust-analyzer", 94 | "rev": "0ba893e1a00d92557ac91efb771d72eee36ca687", 95 | "type": "github" 96 | }, 97 | "original": { 98 | "owner": "rust-lang", 99 | "ref": "nightly", 100 | "repo": "rust-analyzer", 101 | "type": "github" 102 | } 103 | }, 104 | "systems": { 105 | "locked": { 106 | "lastModified": 1681028828, 107 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 108 | "owner": "nix-systems", 109 | "repo": "default", 110 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 111 | "type": "github" 112 | }, 113 | "original": { 114 | "owner": "nix-systems", 115 | "repo": "default", 116 | "type": "github" 117 | } 118 | } 119 | }, 120 | "root": "root", 121 | "version": 7 122 | } 123 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | fenix = { 6 | url = "github:nix-community/fenix/monthly"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | naersk = { 10 | url = "github:nix-community/naersk"; 11 | inputs.nixpkgs.follows = "nixpkgs"; 12 | }; 13 | }; 14 | 15 | outputs = { self, fenix, flake-utils, naersk, nixpkgs }: 16 | (flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system: 17 | let 18 | pkgs = nixpkgs.legacyPackages.${system}; 19 | deps = with pkgs; [ 20 | xorg.libX11 21 | ]; 22 | 23 | devToolchain = fenix.packages.${system}.stable; 24 | 25 | lefthk = ((naersk.lib.${system}.override { 26 | inherit (fenix.packages.${system}.minimal) cargo rustc; 27 | }).buildPackage { 28 | name = "lefthk"; 29 | src = ./.; 30 | buildInputs = deps; 31 | postFixup = '' 32 | for p in $out/bin/left*; do 33 | patchelf --set-rpath "${pkgs.lib.makeLibraryPath deps}" $p 34 | done 35 | ''; 36 | }); 37 | in 38 | rec { 39 | # `nix build` 40 | packages.lefthk = lefthk; 41 | defaultPackage = packages.lefthk; 42 | 43 | # `nix run` 44 | apps.lefthk = flake-utils.lib.mkApp { 45 | drv = packages.lefthk; 46 | }; 47 | defaultApp = apps.lefthk; 48 | 49 | # `nix develop` 50 | devShell = pkgs.mkShell 51 | { 52 | buildInputs = deps ++ [ pkgs.pkg-config ]; 53 | nativeBuildInputs = with pkgs; [ 54 | (devToolchain.withComponents [ 55 | "cargo" 56 | "clippy" 57 | "rust-src" 58 | "rustc" 59 | "rustfmt" 60 | ]) 61 | fenix.packages.${system}.rust-analyzer 62 | xorg.xinit 63 | ]; 64 | }; 65 | })) // { 66 | overlay = final: prev: { 67 | lefthk = self.packages.${final.system}.lefthk; 68 | }; 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /lefthk-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lefthk-core" 3 | version = "0.2.2" 4 | edition = "2021" 5 | license = "BSD-3-Clause" 6 | readme = "README.md" 7 | repository = "https://github.com/leftwm/lefthk" 8 | description = "A hotkey daemon for Adventurers" 9 | 10 | [dependencies] 11 | mio = "1.0.2" 12 | nix = {version = "0.29.0", features = ["fs", "signal"]} 13 | signal-hook = "0.3.4" 14 | thiserror = "1.0.30" 15 | tokio = { version = "1.14.0", features = ["fs", "io-util", "macros", "net", "rt-multi-thread", "sync", "time"] } 16 | x11-dl = "2.19.1" 17 | xdg = "2.4.0" 18 | ron = "0.8.0" 19 | serde = { version = "1.0.145", features= ["derive"]} 20 | inventory = "0.3.2" 21 | 22 | # logging 23 | tracing = "0.1.36" 24 | 25 | [dev-dependencies] 26 | tempfile = "3.2.0" 27 | -------------------------------------------------------------------------------- /lefthk-core/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /lefthk-core/src/child.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Extend; 2 | use std::pin::Pin; 3 | use std::process::Child; 4 | use std::sync::Arc; 5 | use std::time::Duration; 6 | use std::{collections::HashMap, future::Future}; 7 | 8 | use signal_hook::consts::signal; 9 | use signal_hook::iterator::Signals; 10 | use tokio::sync::{oneshot, Notify}; 11 | 12 | /// A struct managing children processes. 13 | /// 14 | /// The `reap` method could be called at any place the user wants to. 15 | /// `register_child_hook` provides a hook that sets a flag. User may use the 16 | /// flag to do a epoch-based reaping. 17 | #[derive(Debug)] 18 | pub struct Children { 19 | inner: HashMap, 20 | pub task_notify: Arc, 21 | _task_guard: oneshot::Receiver<()>, 22 | } 23 | 24 | impl Default for Children { 25 | fn default() -> Self { 26 | Self::new() 27 | } 28 | } 29 | 30 | impl Children { 31 | /// Initiate new children 32 | /// 33 | /// # Panics 34 | /// - Panics if SIGCHLD cannot be created. 35 | pub fn new() -> Self { 36 | let (guard, task_guard) = oneshot::channel(); 37 | let task_notify = Arc::new(Notify::new()); 38 | let notify = task_notify.clone(); 39 | let mut signals = Signals::new([signal::SIGCHLD]).expect("Couldn't setup signals."); 40 | tokio::task::spawn_blocking(move || loop { 41 | if guard.is_closed() { 42 | return; 43 | } 44 | for _ in signals.pending() { 45 | notify.notify_one(); 46 | } 47 | std::thread::sleep(Duration::from_millis(100)); 48 | }); 49 | 50 | Self { 51 | task_notify, 52 | _task_guard: task_guard, 53 | inner: HashMap::default(), 54 | } 55 | } 56 | 57 | #[must_use] 58 | pub fn len(&self) -> usize { 59 | self.inner.len() 60 | } 61 | 62 | #[must_use] 63 | pub fn is_empty(&self) -> bool { 64 | self.inner.len() == 0 65 | } 66 | 67 | /// Add another child-process. 68 | /// ## Return value 69 | /// `false` if it's already registered, otherwise `true` 70 | pub fn insert(&mut self, child: Child) -> bool { 71 | // Not possible to have duplication! 72 | self.inner.insert(child.id(), child).is_none() 73 | } 74 | 75 | /// Merge another `Children` into this `Children`. 76 | pub fn merge(&mut self, reaper: Self) { 77 | self.inner.extend(reaper.inner); 78 | } 79 | 80 | /// Remove all children which finished 81 | pub fn reap(&mut self) { 82 | self.inner 83 | .retain(|_, child| child.try_wait().map_or(true, |ret| ret.is_none())); 84 | } 85 | 86 | pub fn wait_readable(&mut self) -> Pin>> { 87 | let task_notify = self.task_notify.clone(); 88 | Box::pin(async move { 89 | task_notify.notified().await; 90 | }) 91 | } 92 | } 93 | 94 | impl Extend for Children { 95 | fn extend>(&mut self, iter: T) { 96 | self.inner 97 | .extend(iter.into_iter().map(|child| (child.id(), child))); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lefthk-core/src/config/command/chord.rs: -------------------------------------------------------------------------------- 1 | use ron::ser::PrettyConfig; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::{ 5 | config::{command::utils::denormalize_function::DenormalizeCommandFunction, Keybind}, 6 | errors::Error, 7 | worker::Worker, 8 | }; 9 | 10 | use super::{Command, NormalizedCommand}; 11 | 12 | inventory::submit! {DenormalizeCommandFunction::new::()} 13 | 14 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 15 | pub struct Chord(Vec); 16 | 17 | impl Chord { 18 | pub fn new(keybinds: Vec) -> Self { 19 | Self(keybinds) 20 | } 21 | } 22 | 23 | impl Command for Chord { 24 | fn normalize(&self) -> NormalizedCommand { 25 | let serialized_string = 26 | ron::ser::to_string_pretty(self, PrettyConfig::new().struct_names(true)).unwrap(); 27 | NormalizedCommand(serialized_string) 28 | } 29 | 30 | fn denormalize(generalized: &NormalizedCommand) -> Option> { 31 | ron::from_str(&generalized.0).ok() 32 | } 33 | 34 | fn execute(&self, worker: &mut Worker) -> Error { 35 | worker.xwrap.grab_keys(&self.0); 36 | worker.chord_ctx.keybinds = Some(self.0.clone()); 37 | Ok(()) 38 | } 39 | 40 | fn get_name(&self) -> &'static str { 41 | "Chord" 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use crate::config::{command::Reload, Command, Keybind}; 48 | 49 | use super::Chord; 50 | 51 | #[test] 52 | fn normalize_process() { 53 | let command = Chord::new(vec![Keybind { 54 | command: Reload::new().normalize(), 55 | modifier: vec![], 56 | key: String::new(), 57 | }]); 58 | 59 | let normalized = command.normalize(); 60 | let denormalized = Chord::denormalize(&normalized).unwrap(); 61 | 62 | assert_eq!( 63 | Box::new(command.clone()), 64 | denormalized, 65 | "{:?}, {:?}", 66 | command, 67 | denormalized, 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lefthk-core/src/config/command/execute.rs: -------------------------------------------------------------------------------- 1 | use std::process::Stdio; 2 | 3 | use ron::ser::PrettyConfig; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::{ 7 | config::command::utils::denormalize_function::DenormalizeCommandFunction, errors::Error, 8 | worker::Worker, 9 | }; 10 | 11 | use super::{Command, NormalizedCommand}; 12 | 13 | inventory::submit! {DenormalizeCommandFunction::new::()} 14 | 15 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 16 | pub struct Execute(String); 17 | 18 | impl Execute { 19 | pub fn new(shell_command: &T) -> Self { 20 | Self(shell_command.to_string()) 21 | } 22 | } 23 | 24 | impl Command for Execute { 25 | fn normalize(&self) -> NormalizedCommand { 26 | let serialized_string = 27 | ron::ser::to_string_pretty(self, PrettyConfig::new().struct_names(true)).unwrap(); 28 | NormalizedCommand(serialized_string) 29 | } 30 | 31 | fn denormalize(generalized: &NormalizedCommand) -> Option> { 32 | ron::from_str(&generalized.0).ok() 33 | } 34 | 35 | fn execute(&self, worker: &mut Worker) -> Error { 36 | worker.chord_ctx.elapsed = worker.chord_ctx.keybinds.is_some(); 37 | let child = std::process::Command::new("sh") 38 | .arg("-c") 39 | .arg(&self.0) 40 | .stdin(Stdio::null()) 41 | .stdout(Stdio::null()) 42 | .stderr(Stdio::null()) 43 | .spawn()?; 44 | 45 | worker.children.insert(child); 46 | 47 | Ok(()) 48 | } 49 | 50 | fn get_name(&self) -> &'static str { 51 | "Execute" 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use crate::config::Command; 58 | 59 | use super::Execute; 60 | 61 | #[test] 62 | fn normalize_process() { 63 | let command = Execute::new(&"echo 'I use Arch by the way'"); 64 | 65 | let normalized = command.normalize(); 66 | let denormalized = Execute::denormalize(&normalized).unwrap(); 67 | 68 | assert_eq!( 69 | Box::new(command), 70 | denormalized, 71 | "{:?}, {:?}", 72 | normalized, 73 | denormalized 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lefthk-core/src/config/command/exit_chord.rs: -------------------------------------------------------------------------------- 1 | use ron::ser::PrettyConfig; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::{ 5 | config::command::utils::denormalize_function::DenormalizeCommandFunction, errors::Error, 6 | worker::Worker, 7 | }; 8 | 9 | use super::{Command, NormalizedCommand}; 10 | 11 | inventory::submit! {DenormalizeCommandFunction::new::()} 12 | 13 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, Default)] 14 | pub struct ExitChord; 15 | 16 | impl ExitChord { 17 | pub fn new() -> Self { 18 | Self 19 | } 20 | } 21 | 22 | impl Command for ExitChord { 23 | fn normalize(&self) -> NormalizedCommand { 24 | let serialized_string = 25 | ron::ser::to_string_pretty(self, PrettyConfig::new().struct_names(true)).unwrap(); 26 | NormalizedCommand(serialized_string) 27 | } 28 | 29 | fn denormalize(generalized: &NormalizedCommand) -> Option> { 30 | ron::from_str(&generalized.0).ok() 31 | } 32 | 33 | fn execute(&self, worker: &mut Worker) -> Error { 34 | if worker.chord_ctx.keybinds.is_some() { 35 | worker.chord_ctx.elapsed = true; 36 | } 37 | 38 | Ok(()) 39 | } 40 | 41 | fn get_name(&self) -> &'static str { 42 | "ExitChord" 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use crate::config::Command; 49 | 50 | use super::ExitChord; 51 | 52 | #[test] 53 | fn normalize_process() { 54 | let command = ExitChord::new(); 55 | 56 | let normalized = command.normalize(); 57 | let denormalized = ExitChord::denormalize(&normalized).unwrap(); 58 | 59 | assert_eq!( 60 | Box::new(command), 61 | denormalized, 62 | "{:?}, {:?}", 63 | normalized, 64 | denormalized, 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lefthk-core/src/config/command/kill.rs: -------------------------------------------------------------------------------- 1 | use std::hash::Hash; 2 | 3 | use ron::ser::PrettyConfig; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::{ 7 | config::command::utils::denormalize_function::DenormalizeCommandFunction, 8 | errors::Error, 9 | worker::{self, Worker}, 10 | }; 11 | 12 | use super::{Command, NormalizedCommand}; 13 | 14 | inventory::submit! {DenormalizeCommandFunction::new::()} 15 | 16 | #[derive(Debug, Clone, PartialEq, Hash, Eq, Serialize, Deserialize, Default)] 17 | pub struct Kill; 18 | 19 | impl Kill { 20 | pub fn new() -> Self { 21 | Self 22 | } 23 | } 24 | 25 | impl Command for Kill { 26 | fn normalize(&self) -> NormalizedCommand { 27 | let serialized_string = 28 | ron::ser::to_string_pretty(self, PrettyConfig::new().struct_names(true)).unwrap(); 29 | 30 | NormalizedCommand(serialized_string) 31 | } 32 | 33 | fn denormalize(generalized: &NormalizedCommand) -> Option> { 34 | ron::from_str(&generalized.0).ok() 35 | } 36 | 37 | fn execute(&self, worker: &mut Worker) -> Error { 38 | worker.status = worker::Status::Kill; 39 | Ok(()) 40 | } 41 | 42 | fn get_name(&self) -> &'static str { 43 | "Kill" 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod testes { 49 | use crate::config::Command; 50 | 51 | use super::Kill; 52 | 53 | #[test] 54 | fn normalize_process() { 55 | let command = Kill::new(); 56 | 57 | let normalized = command.normalize(); 58 | let denormalized = Kill::denormalize(&normalized).unwrap(); 59 | 60 | assert_eq!( 61 | Box::new(command.clone()), 62 | denormalized, 63 | "{:?}, {:?}", 64 | command, 65 | denormalized 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lefthk-core/src/config/command/mod.rs: -------------------------------------------------------------------------------- 1 | mod chord; 2 | mod execute; 3 | mod exit_chord; 4 | mod kill; 5 | mod reload; 6 | 7 | pub mod utils; 8 | 9 | use self::utils::{ 10 | denormalize_function::DenormalizeCommandFunction, normalized_command::NormalizedCommand, 11 | }; 12 | use crate::errors::{Error, LeftError, Result}; 13 | use crate::worker::Worker; 14 | 15 | pub use self::{chord::Chord, execute::Execute, exit_chord::ExitChord, kill::Kill, reload::Reload}; 16 | 17 | inventory::collect!(DenormalizeCommandFunction); 18 | 19 | /// When adding a command: 20 | /// - a command has to submit itself to the inventory 21 | /// - write a test that it's conversion between normalizel and denormalize works 22 | pub trait Command: std::fmt::Debug { 23 | fn normalize(&self) -> NormalizedCommand; 24 | 25 | fn denormalize(generalized: &NormalizedCommand) -> Option> 26 | where 27 | Self: Sized; 28 | 29 | /// # Errors 30 | /// 31 | /// This errors when the command cannot be executed by the worker 32 | fn execute(&self, worker: &mut Worker) -> Error; 33 | 34 | fn get_name(&self) -> &'static str; 35 | } 36 | 37 | /// # Errors 38 | /// 39 | /// This errors when the command cannot be matched with the known commands 40 | pub fn denormalize(normalized_command: &NormalizedCommand) -> Result> { 41 | for denormalizer in inventory::iter:: { 42 | if let Some(denormalized_command) = (denormalizer.0)(normalized_command) { 43 | return Ok(denormalized_command); 44 | } 45 | } 46 | Err(LeftError::UnmatchingCommand) 47 | } 48 | -------------------------------------------------------------------------------- /lefthk-core/src/config/command/reload.rs: -------------------------------------------------------------------------------- 1 | use ron::ser::PrettyConfig; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::{ 5 | config::command::utils::denormalize_function::DenormalizeCommandFunction, 6 | errors::Error, 7 | worker::{self, Worker}, 8 | }; 9 | 10 | use super::{Command, NormalizedCommand}; 11 | 12 | inventory::submit! {DenormalizeCommandFunction::new::()} 13 | 14 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] 15 | pub struct Reload; 16 | 17 | impl Reload { 18 | pub fn new() -> Self { 19 | Self 20 | } 21 | } 22 | 23 | impl Command for Reload { 24 | fn normalize(&self) -> NormalizedCommand { 25 | let serialized_string = 26 | ron::ser::to_string_pretty(self, PrettyConfig::new().struct_names(true)).unwrap(); 27 | NormalizedCommand(serialized_string) 28 | } 29 | 30 | fn denormalize(generalized: &NormalizedCommand) -> Option> { 31 | ron::from_str(&generalized.0).ok() 32 | } 33 | 34 | fn execute(&self, worker: &mut Worker) -> Error { 35 | worker.status = worker::Status::Reload; 36 | Ok(()) 37 | } 38 | 39 | fn get_name(&self) -> &'static str { 40 | "Reload" 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use crate::config::Command; 47 | 48 | use super::Reload; 49 | 50 | #[test] 51 | fn normalize_process() { 52 | let command = Reload::new(); 53 | 54 | let normalized = command.normalize(); 55 | let denormalized = Reload::denormalize(&normalized).unwrap(); 56 | 57 | assert_eq!( 58 | Box::new(command.clone()), 59 | denormalized, 60 | "{:?}, {:?}", 61 | command, 62 | denormalized 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lefthk-core/src/config/command/utils/denormalize_function.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Command; 2 | 3 | use super::normalized_command::NormalizedCommand; 4 | 5 | pub struct DenormalizeCommandFunction(pub fn(&NormalizedCommand) -> Option>); 6 | 7 | impl DenormalizeCommandFunction { 8 | pub const fn new() -> Self { 9 | DenormalizeCommandFunction(|normalized: &NormalizedCommand| { 10 | T::denormalize(normalized).map(|cmd| cmd as Box) 11 | }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lefthk-core/src/config/command/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod denormalize_function; 2 | pub mod normalized_command; 3 | -------------------------------------------------------------------------------- /lefthk-core/src/config/command/utils/normalized_command.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | type Content = String; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 8 | pub struct NormalizedCommand(pub Content); 9 | 10 | impl TryFrom for NormalizedCommand { 11 | type Error = (); 12 | 13 | fn try_from(value: String) -> Result { 14 | Ok(Self(value)) 15 | } 16 | } 17 | 18 | impl Display for NormalizedCommand { 19 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 20 | write!(f, "{}", self.0) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lefthk-core/src/config/keybind.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use super::command::utils::normalized_command::NormalizedCommand; 4 | 5 | #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] 6 | pub struct Keybind { 7 | pub command: NormalizedCommand, 8 | pub modifier: Vec, 9 | pub key: String, 10 | } 11 | -------------------------------------------------------------------------------- /lefthk-core/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod command; 2 | mod keybind; 3 | 4 | pub use command::Command; 5 | pub use keybind::Keybind; 6 | 7 | pub trait Config { 8 | fn mapped_bindings(&self) -> Vec; 9 | } 10 | 11 | pub trait CommandAdapter { 12 | fn convert(&self) -> Vec>; 13 | } 14 | -------------------------------------------------------------------------------- /lefthk-core/src/errors.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | macro_rules! log_on_error { 4 | ($a: expr) => { 5 | match $a { 6 | Ok(value) => value, 7 | Err(err) => tracing::error!("{}", LeftError::from(err)), 8 | } 9 | }; 10 | } 11 | 12 | macro_rules! exit_on_error { 13 | ($a: expr) => { 14 | match $a { 15 | Ok(value) => value, 16 | Err(err) => { 17 | tracing::error!("Exiting due to error: {}", LeftError::from(err)); 18 | std::process::exit(1); 19 | } 20 | } 21 | }; 22 | } 23 | 24 | pub(crate) use exit_on_error; 25 | pub(crate) use log_on_error; 26 | 27 | pub type Result = std::result::Result; 28 | pub type Error = std::result::Result<(), LeftError>; 29 | 30 | #[derive(Debug, Error)] 31 | pub enum LeftError { 32 | #[error("IO error: {0}.")] 33 | IoError(#[from] std::io::Error), 34 | #[error("Nix errno: {0}.")] 35 | NixErrno(#[from] nix::errno::Errno), 36 | #[error("Xlib error: {0}.")] 37 | XlibError(#[from] x11_dl::error::OpenError), 38 | #[error("XDG error: {0}.")] 39 | XdgBaseDirError(#[from] xdg::BaseDirectoriesError), 40 | 41 | #[error("Given String doesn't match with a command.")] 42 | UnmatchingCommand, 43 | #[error("No command found for keybind.")] 44 | CommandNotFound, 45 | #[error("No key found for keybind.")] 46 | KeyNotFound, 47 | #[error("No modifier found for keybind.")] 48 | ModifierNotFound, 49 | #[error("No config file found.")] 50 | NoConfigFound, 51 | #[error("No value set for execution.")] 52 | ValueNotFound, 53 | #[error("X failed status error.")] 54 | XFailedStatus, 55 | } 56 | -------------------------------------------------------------------------------- /lefthk-core/src/ipc.rs: -------------------------------------------------------------------------------- 1 | use crate::config::command; 2 | use crate::config::command::utils::normalized_command::NormalizedCommand; 3 | use crate::config::Command; 4 | use crate::errors::Result; 5 | use std::path::{Path, PathBuf}; 6 | use tokio::{ 7 | fs, 8 | io::{AsyncBufReadExt, BufReader}, 9 | sync::mpsc, 10 | }; 11 | 12 | pub struct Pipe { 13 | pipe_file: PathBuf, 14 | rx: mpsc::UnboundedReceiver, 15 | } 16 | 17 | impl Drop for Pipe { 18 | fn drop(&mut self) { 19 | use std::os::unix::fs::OpenOptionsExt; 20 | self.rx.close(); 21 | 22 | // Open fifo for write to unblock pending open for read operation that prevents tokio runtime 23 | // from shutting down. 24 | let _unplock_pending_open = std::fs::OpenOptions::new() 25 | .write(true) 26 | .custom_flags(nix::fcntl::OFlag::O_NONBLOCK.bits()) 27 | .open(self.pipe_file.clone()); 28 | } 29 | } 30 | 31 | impl Pipe { 32 | /// Create and listen to the named pipe. 33 | /// # Errors 34 | /// 35 | /// Will error if unable to `mkfifo`, likely a filesystem issue 36 | /// such as inadequate permissions. 37 | pub async fn new(pipe_file: PathBuf) -> Result { 38 | let _pipe_reset = fs::remove_file(pipe_file.as_path()).await; 39 | nix::unistd::mkfifo(&pipe_file, nix::sys::stat::Mode::S_IRWXU)?; 40 | 41 | let path = pipe_file.clone(); 42 | let (tx, rx) = mpsc::unbounded_channel(); 43 | tokio::spawn(async move { 44 | while !tx.is_closed() { 45 | read_from_pipe(&path, &tx).await; 46 | } 47 | fs::remove_file(path).await.ok(); 48 | }); 49 | 50 | Ok(Self { pipe_file, rx }) 51 | } 52 | 53 | pub fn pipe_name() -> PathBuf { 54 | let display = std::env::var("DISPLAY") 55 | .ok() 56 | .and_then(|d| d.rsplit_once(':').map(|(_, r)| r.to_owned())) 57 | .unwrap_or_else(|| "0".to_string()); 58 | 59 | PathBuf::from(format!("command-{display}.pipe")) 60 | } 61 | 62 | pub async fn get_next_command(&mut self) -> Option> { 63 | if let Some(normalized_command) = self.rx.recv().await { 64 | return command::denormalize(&normalized_command).ok(); 65 | } 66 | None 67 | } 68 | } 69 | 70 | async fn read_from_pipe<'a>(pipe_file: &Path, tx: &mpsc::UnboundedSender) { 71 | if let Ok(file) = fs::File::open(pipe_file).await { 72 | let mut lines = BufReader::new(file).lines(); 73 | 74 | while let Ok(line) = lines.next_line().await { 75 | if let Some(content) = line { 76 | if let Ok(normalized_command) = NormalizedCommand::try_from(content) { 77 | if command::denormalize(&normalized_command.clone()).is_ok() { 78 | if let Err(err) = tx.send(normalized_command) { 79 | tracing::error!("{}", err); 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lefthk-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod tests; 2 | 3 | pub mod child; 4 | pub mod config; 5 | pub mod errors; 6 | pub mod ipc; 7 | pub mod worker; 8 | pub mod xkeysym_lookup; 9 | pub mod xwrap; 10 | 11 | /// The directory name for xdg 12 | pub const LEFTHK_DIR_NAME: &str = "lefthk"; 13 | -------------------------------------------------------------------------------- /lefthk-core/src/tests.rs: -------------------------------------------------------------------------------- 1 | /// Test Helpers 2 | #[cfg(test)] 3 | pub(crate) mod test { 4 | pub fn temp_path() -> std::io::Result { 5 | tempfile::Builder::new() 6 | .tempfile_in("../target") 7 | .expect("Blocking task joined") 8 | .into_temp_path() 9 | .keep() 10 | .map_err(Into::into) 11 | } 12 | } 13 | 14 | /// IPC Testing 15 | #[cfg(test)] 16 | mod ipc { 17 | use tokio::fs; 18 | use tokio::io::AsyncWriteExt; 19 | 20 | use crate::config::command::Reload; 21 | use crate::config::Command; 22 | use crate::ipc::Pipe; 23 | 24 | use super::test::temp_path; 25 | 26 | #[tokio::test] 27 | async fn simulate_command_sending() { 28 | let pipe_file = temp_path().unwrap(); 29 | let mut command_pipe = Pipe::new(pipe_file.clone()).await.unwrap(); 30 | let mut pipe = fs::OpenOptions::new() 31 | .write(true) 32 | .open(&pipe_file) 33 | .await 34 | .unwrap(); 35 | 36 | let command = Reload::new(); 37 | 38 | let normalized = command.normalize(); 39 | pipe.write_all(format!("{}\n", normalized).as_bytes()) 40 | .await 41 | .unwrap(); 42 | pipe.flush().await.unwrap(); 43 | let denormalized = command_pipe.get_next_command().await.unwrap(); 44 | 45 | assert_eq!(command.normalize(), denormalized.normalize()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lefthk-core/src/worker/context/chord.rs: -------------------------------------------------------------------------------- 1 | use crate::{config::Keybind, worker::Worker}; 2 | 3 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 4 | pub struct Chord { 5 | pub keybinds: Option>, 6 | pub elapsed: bool, 7 | } 8 | 9 | impl Chord { 10 | pub fn new() -> Self { 11 | Self { 12 | keybinds: None, 13 | elapsed: false, 14 | } 15 | } 16 | } 17 | 18 | impl Worker { 19 | pub fn evaluate_chord(&mut self) { 20 | if self.chord_ctx.elapsed { 21 | self.xwrap.grab_keys(&self.keybinds); 22 | self.chord_ctx.keybinds = None; 23 | self.chord_ctx.elapsed = false; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lefthk-core/src/worker/context/mod.rs: -------------------------------------------------------------------------------- 1 | mod chord; 2 | 3 | pub use chord::Chord; 4 | -------------------------------------------------------------------------------- /lefthk-core/src/worker/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod context; 2 | 3 | use crate::child::Children; 4 | use crate::config::{command, Keybind}; 5 | use crate::errors::{self, Error, LeftError}; 6 | use crate::ipc::Pipe; 7 | use crate::xkeysym_lookup; 8 | use crate::xwrap::XWrap; 9 | use x11_dl::xlib; 10 | use xdg::BaseDirectories; 11 | 12 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 13 | pub enum Status { 14 | Reload, 15 | Kill, 16 | Continue, 17 | } 18 | 19 | pub struct Worker { 20 | keybinds: Vec, 21 | base_directory: BaseDirectories, 22 | 23 | pub xwrap: XWrap, 24 | pub children: Children, 25 | pub status: Status, 26 | 27 | /// "Chord Context": Holds the relevant data for chording 28 | pub chord_ctx: context::Chord, 29 | } 30 | 31 | impl Worker { 32 | pub fn new(keybinds: Vec, base_directory: BaseDirectories) -> Self { 33 | Self { 34 | status: Status::Continue, 35 | keybinds, 36 | base_directory, 37 | xwrap: XWrap::new(), 38 | children: Children::default(), 39 | chord_ctx: context::Chord::new(), 40 | } 41 | } 42 | 43 | pub async fn event_loop(mut self) -> Status { 44 | self.xwrap.grab_keys(&self.keybinds); 45 | let mut pipe = self.get_pipe().await; 46 | 47 | while self.status == Status::Continue { 48 | self.xwrap.flush(); 49 | 50 | self.evaluate_chord(); 51 | 52 | tokio::select! { 53 | () = self.children.wait_readable() => { 54 | self.children.reap(); 55 | } 56 | () = self.xwrap.wait_readable() => { 57 | let event_in_queue = self.xwrap.queue_len(); 58 | for _ in 0..event_in_queue { 59 | let xlib_event = self.xwrap.get_next_event(); 60 | self.handle_event(&xlib_event); 61 | } 62 | } 63 | Some(command) = pipe.get_next_command() => { 64 | errors::log_on_error!(command.execute(&mut self)); 65 | } 66 | }; 67 | } 68 | 69 | self.status 70 | } 71 | 72 | async fn get_pipe(&self) -> Pipe { 73 | let pipe_name = Pipe::pipe_name(); 74 | let pipe_file = errors::exit_on_error!(self.base_directory.place_runtime_file(pipe_name)); 75 | errors::exit_on_error!(Pipe::new(pipe_file).await) 76 | } 77 | 78 | fn handle_event(&mut self, xlib_event: &xlib::XEvent) { 79 | let error = match xlib_event.get_type() { 80 | xlib::KeyPress => self.handle_key_press(&xlib::XKeyEvent::from(xlib_event)), 81 | xlib::MappingNotify => { 82 | self.handle_mapping_notify(&mut xlib::XMappingEvent::from(xlib_event)) 83 | } 84 | _ => return, 85 | }; 86 | errors::log_on_error!(error); 87 | } 88 | 89 | fn handle_key_press(&mut self, event: &xlib::XKeyEvent) -> Error { 90 | let key = self.xwrap.keycode_to_keysym(event.keycode); 91 | let mask = xkeysym_lookup::clean_mask(event.state); 92 | if let Some(keybind) = self.get_keybind((mask, key)) { 93 | if let Ok(command) = command::denormalize(&keybind.command) { 94 | return command.execute(self); 95 | } 96 | } else { 97 | return Err(LeftError::CommandNotFound); 98 | } 99 | Ok(()) 100 | } 101 | 102 | fn get_keybind(&self, mask_key_pair: (u32, u32)) -> Option { 103 | let keybinds = if let Some(keybinds) = &self.chord_ctx.keybinds { 104 | keybinds 105 | } else { 106 | &self.keybinds 107 | }; 108 | keybinds 109 | .iter() 110 | .find(|keybind| { 111 | if let Some(key) = xkeysym_lookup::into_keysym(&keybind.key) { 112 | let mask = xkeysym_lookup::into_modmask(&keybind.modifier); 113 | return mask_key_pair == (mask, key); 114 | } 115 | false 116 | }) 117 | .cloned() 118 | } 119 | 120 | fn handle_mapping_notify(&self, event: &mut xlib::XMappingEvent) -> Error { 121 | if event.request == xlib::MappingModifier || event.request == xlib::MappingKeyboard { 122 | return self.xwrap.refresh_keyboard(event); 123 | } 124 | Ok(()) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lefthk-core/src/xkeysym_lookup.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::wildcard_imports)] 2 | use std::os::raw::c_uint; 3 | use x11_dl::keysym::*; 4 | use x11_dl::xlib; 5 | 6 | pub type XKeysym = c_uint; 7 | pub type ModMask = c_uint; 8 | pub type Button = c_uint; 9 | 10 | pub fn clean_mask(mut mask: ModMask) -> ModMask { 11 | mask &= !(xlib::Mod2Mask | xlib::LockMask); 12 | mask & (xlib::ShiftMask 13 | | xlib::ControlMask 14 | | xlib::Mod1Mask 15 | | xlib::Mod3Mask 16 | | xlib::Mod4Mask 17 | | xlib::Mod5Mask) 18 | } 19 | 20 | #[must_use] 21 | pub fn into_modmask(keys: &[String]) -> ModMask { 22 | let mut mask = 0; 23 | for s in keys { 24 | mask |= into_mod(s); 25 | } 26 | clean_mask(mask) 27 | } 28 | 29 | #[must_use] 30 | pub fn into_mod(key: &str) -> ModMask { 31 | match key { 32 | "None" => xlib::AnyModifier, 33 | "Shift" => xlib::ShiftMask, 34 | "Control" => xlib::ControlMask, 35 | "Mod1" | "Alt" => xlib::Mod1Mask, 36 | //"Mod2" => xlib::Mod2Mask, // NOTE: we are ignoring the state of Numlock 37 | //"NumLock" => xlib::Mod2Mask, // this is left here as a reminder 38 | "Mod3" => xlib::Mod3Mask, 39 | "Mod4" | "Super" => xlib::Mod4Mask, 40 | "Mod5" => xlib::Mod5Mask, 41 | _ => 0, 42 | } 43 | } 44 | 45 | // We allow this because this function is simply a mapping wrapper. 46 | #[allow(clippy::too_many_lines)] 47 | #[must_use] 48 | pub fn into_keysym(key: &str) -> Option { 49 | match key { 50 | "BackSpace" => Some(XK_BackSpace), 51 | "Tab" => Some(XK_Tab), 52 | "Linefeed" => Some(XK_Linefeed), 53 | "Clear" => Some(XK_Clear), 54 | "Return" => Some(XK_Return), 55 | "Pause" => Some(XK_Pause), 56 | "Scroll_Lock" => Some(XK_Scroll_Lock), 57 | "Sys_Req" => Some(XK_Sys_Req), 58 | "Escape" => Some(XK_Escape), 59 | "Delete" => Some(XK_Delete), 60 | "Multi_key" => Some(XK_Multi_key), 61 | "Kanji" => Some(XK_Kanji), 62 | "Muhenkan" => Some(XK_Muhenkan), 63 | "Henkan_Mode" => Some(XK_Henkan_Mode), 64 | "Henkan" => Some(XK_Henkan), 65 | "Romaji" => Some(XK_Romaji), 66 | "Hiragana" => Some(XK_Hiragana), 67 | "Katakana" => Some(XK_Katakana), 68 | "Hiragana_Katakana" => Some(XK_Hiragana_Katakana), 69 | "Zenkaku" => Some(XK_Zenkaku), 70 | "Hankaku" => Some(XK_Hankaku), 71 | "Zenkaku_Hankaku" => Some(XK_Zenkaku_Hankaku), 72 | "Touroku" => Some(XK_Touroku), 73 | "Massyo" => Some(XK_Massyo), 74 | "Kana_Lock" => Some(XK_Kana_Lock), 75 | "Kana_Shift" => Some(XK_Kana_Shift), 76 | "Eisu_Shift" => Some(XK_Eisu_Shift), 77 | "Eisu_toggle" => Some(XK_Eisu_toggle), 78 | "Home" => Some(XK_Home), 79 | "Left" => Some(XK_Left), 80 | "Up" => Some(XK_Up), 81 | "Right" => Some(XK_Right), 82 | "Down" => Some(XK_Down), 83 | "Prior" => Some(XK_Prior), 84 | "Page_Up" => Some(XK_Page_Up), 85 | "Next" => Some(XK_Next), 86 | "Page_Down" => Some(XK_Page_Down), 87 | "End" => Some(XK_End), 88 | "Begin" => Some(XK_Begin), 89 | "Win_L" => Some(XK_Win_L), 90 | "Win_R" => Some(XK_Win_R), 91 | "App" => Some(XK_App), 92 | "Select" => Some(XK_Select), 93 | "Print" => Some(XK_Print), 94 | "Execute" => Some(XK_Execute), 95 | "Insert" => Some(XK_Insert), 96 | "Undo" => Some(XK_Undo), 97 | "Redo" => Some(XK_Redo), 98 | "Menu" => Some(XK_Menu), 99 | "Find" => Some(XK_Find), 100 | "Cancel" => Some(XK_Cancel), 101 | "Help" => Some(XK_Help), 102 | "Break" => Some(XK_Break), 103 | "Mode_switch" => Some(XK_Mode_switch), 104 | "script_switch" => Some(XK_script_switch), 105 | "Num_Lock" => Some(XK_Num_Lock), 106 | "KP_Space" => Some(XK_KP_Space), 107 | "KP_Tab" => Some(XK_KP_Tab), 108 | "KP_Enter" => Some(XK_KP_Enter), 109 | "KP_F1" => Some(XK_KP_F1), 110 | "KP_F2" => Some(XK_KP_F2), 111 | "KP_F3" => Some(XK_KP_F3), 112 | "KP_F4" => Some(XK_KP_F4), 113 | "KP_Home" => Some(XK_KP_Home), 114 | "KP_Left" => Some(XK_KP_Left), 115 | "KP_Up" => Some(XK_KP_Up), 116 | "KP_Right" => Some(XK_KP_Right), 117 | "KP_Down" => Some(XK_KP_Down), 118 | "KP_Prior" => Some(XK_KP_Prior), 119 | "KP_Page_Up" => Some(XK_KP_Page_Up), 120 | "KP_Next" => Some(XK_KP_Next), 121 | "KP_Page_Down" => Some(XK_KP_Page_Down), 122 | "KP_End" => Some(XK_KP_End), 123 | "KP_Begin" => Some(XK_KP_Begin), 124 | "KP_Insert" => Some(XK_KP_Insert), 125 | "KP_Delete" => Some(XK_KP_Delete), 126 | "KP_Equal" => Some(XK_KP_Equal), 127 | "KP_Multiply" => Some(XK_KP_Multiply), 128 | "KP_Add" => Some(XK_KP_Add), 129 | "KP_Separator" => Some(XK_KP_Separator), 130 | "KP_Subtract" => Some(XK_KP_Subtract), 131 | "KP_Decimal" => Some(XK_KP_Decimal), 132 | "KP_Divide" => Some(XK_KP_Divide), 133 | "KP_0" => Some(XK_KP_0), 134 | "KP_1" => Some(XK_KP_1), 135 | "KP_2" => Some(XK_KP_2), 136 | "KP_3" => Some(XK_KP_3), 137 | "KP_4" => Some(XK_KP_4), 138 | "KP_5" => Some(XK_KP_5), 139 | "KP_6" => Some(XK_KP_6), 140 | "KP_7" => Some(XK_KP_7), 141 | "KP_8" => Some(XK_KP_8), 142 | "KP_9" => Some(XK_KP_9), 143 | "F1" => Some(XK_F1), 144 | "F2" => Some(XK_F2), 145 | "F3" => Some(XK_F3), 146 | "F4" => Some(XK_F4), 147 | "F5" => Some(XK_F5), 148 | "F6" => Some(XK_F6), 149 | "F7" => Some(XK_F7), 150 | "F8" => Some(XK_F8), 151 | "F9" => Some(XK_F9), 152 | "F10" => Some(XK_F10), 153 | "F11" => Some(XK_F11), 154 | "L1" => Some(XK_L1), 155 | "F12" => Some(XK_F12), 156 | "L2" => Some(XK_L2), 157 | "F13" => Some(XK_F13), 158 | "L3" => Some(XK_L3), 159 | "F14" => Some(XK_F14), 160 | "L4" => Some(XK_L4), 161 | "F15" => Some(XK_F15), 162 | "L5" => Some(XK_L5), 163 | "F16" => Some(XK_F16), 164 | "L6" => Some(XK_L6), 165 | "F17" => Some(XK_F17), 166 | "L7" => Some(XK_L7), 167 | "F18" => Some(XK_F18), 168 | "L8" => Some(XK_L8), 169 | "F19" => Some(XK_F19), 170 | "L9" => Some(XK_L9), 171 | "F20" => Some(XK_F20), 172 | "L10" => Some(XK_L10), 173 | "F21" => Some(XK_F21), 174 | "R1" => Some(XK_R1), 175 | "F22" => Some(XK_F22), 176 | "R2" => Some(XK_R2), 177 | "F23" => Some(XK_F23), 178 | "R3" => Some(XK_R3), 179 | "F24" => Some(XK_F24), 180 | "R4" => Some(XK_R4), 181 | "F25" => Some(XK_F25), 182 | "R5" => Some(XK_R5), 183 | "F26" => Some(XK_F26), 184 | "R6" => Some(XK_R6), 185 | "F27" => Some(XK_F27), 186 | "R7" => Some(XK_R7), 187 | "F28" => Some(XK_F28), 188 | "R8" => Some(XK_R8), 189 | "F29" => Some(XK_F29), 190 | "R9" => Some(XK_R9), 191 | "F30" => Some(XK_F30), 192 | "R10" => Some(XK_R10), 193 | "F31" => Some(XK_F31), 194 | "R11" => Some(XK_R11), 195 | "F32" => Some(XK_F32), 196 | "R12" => Some(XK_R12), 197 | "F33" => Some(XK_F33), 198 | "R13" => Some(XK_R13), 199 | "F34" => Some(XK_F34), 200 | "R14" => Some(XK_R14), 201 | "F35" => Some(XK_F35), 202 | "R15" => Some(XK_R15), 203 | "Shift_L" => Some(XK_Shift_L), 204 | "Shift_R" => Some(XK_Shift_R), 205 | "Control_L" => Some(XK_Control_L), 206 | "Control_R" => Some(XK_Control_R), 207 | "Caps_Lock" => Some(XK_Caps_Lock), 208 | "Shift_Lock" => Some(XK_Shift_Lock), 209 | "Meta_L" => Some(XK_Meta_L), 210 | "Meta_R" => Some(XK_Meta_R), 211 | "Alt_L" => Some(XK_Alt_L), 212 | "Alt_R" => Some(XK_Alt_R), 213 | "Super_L" => Some(XK_Super_L), 214 | "Super_R" => Some(XK_Super_R), 215 | "Hyper_L" => Some(XK_Hyper_L), 216 | "Hyper_R" => Some(XK_Hyper_R), 217 | "space" => Some(XK_space), 218 | "exclam" => Some(XK_exclam), 219 | "quotedbl" => Some(XK_quotedbl), 220 | "numbersign" => Some(XK_numbersign), 221 | "dollar" => Some(XK_dollar), 222 | "percent" => Some(XK_percent), 223 | "ampersand" => Some(XK_ampersand), 224 | "apostrophe" => Some(XK_apostrophe), 225 | "quoteright" => Some(XK_quoteright), 226 | "parenleft" => Some(XK_parenleft), 227 | "parenright" => Some(XK_parenright), 228 | "asterisk" => Some(XK_asterisk), 229 | "plus" => Some(XK_plus), 230 | "comma" => Some(XK_comma), 231 | "minus" => Some(XK_minus), 232 | "period" => Some(XK_period), 233 | "slash" => Some(XK_slash), 234 | "0" => Some(XK_0), 235 | "1" => Some(XK_1), 236 | "2" => Some(XK_2), 237 | "3" => Some(XK_3), 238 | "4" => Some(XK_4), 239 | "5" => Some(XK_5), 240 | "6" => Some(XK_6), 241 | "7" => Some(XK_7), 242 | "8" => Some(XK_8), 243 | "9" => Some(XK_9), 244 | "colon" => Some(XK_colon), 245 | "semicolon" => Some(XK_semicolon), 246 | "less" => Some(XK_less), 247 | "equal" => Some(XK_equal), 248 | "greater" => Some(XK_greater), 249 | "question" => Some(XK_question), 250 | "at" => Some(XK_at), 251 | "A" => Some(XK_A), 252 | "B" => Some(XK_B), 253 | "C" => Some(XK_C), 254 | "D" => Some(XK_D), 255 | "E" => Some(XK_E), 256 | "F" => Some(XK_F), 257 | "G" => Some(XK_G), 258 | "H" => Some(XK_H), 259 | "I" => Some(XK_I), 260 | "J" => Some(XK_J), 261 | "K" => Some(XK_K), 262 | "L" => Some(XK_L), 263 | "M" => Some(XK_M), 264 | "N" => Some(XK_N), 265 | "O" => Some(XK_O), 266 | "P" => Some(XK_P), 267 | "Q" => Some(XK_Q), 268 | "R" => Some(XK_R), 269 | "S" => Some(XK_S), 270 | "T" => Some(XK_T), 271 | "U" => Some(XK_U), 272 | "V" => Some(XK_V), 273 | "W" => Some(XK_W), 274 | "X" => Some(XK_X), 275 | "Y" => Some(XK_Y), 276 | "Z" => Some(XK_Z), 277 | "bracketleft" => Some(XK_bracketleft), 278 | "backslash" => Some(XK_backslash), 279 | "bracketright" => Some(XK_bracketright), 280 | "asciicircum" => Some(XK_asciicircum), 281 | "underscore" => Some(XK_underscore), 282 | "grave" => Some(XK_grave), 283 | "quoteleft" => Some(XK_quoteleft), 284 | "a" => Some(XK_a), 285 | "b" => Some(XK_b), 286 | "c" => Some(XK_c), 287 | "d" => Some(XK_d), 288 | "e" => Some(XK_e), 289 | "f" => Some(XK_f), 290 | "g" => Some(XK_g), 291 | "h" => Some(XK_h), 292 | "i" => Some(XK_i), 293 | "j" => Some(XK_j), 294 | "k" => Some(XK_k), 295 | "l" => Some(XK_l), 296 | "m" => Some(XK_m), 297 | "n" => Some(XK_n), 298 | "o" => Some(XK_o), 299 | "p" => Some(XK_p), 300 | "q" => Some(XK_q), 301 | "r" => Some(XK_r), 302 | "s" => Some(XK_s), 303 | "t" => Some(XK_t), 304 | "u" => Some(XK_u), 305 | "v" => Some(XK_v), 306 | "w" => Some(XK_w), 307 | "x" => Some(XK_x), 308 | "y" => Some(XK_y), 309 | "z" => Some(XK_z), 310 | "braceleft" => Some(XK_braceleft), 311 | "bar" => Some(XK_bar), 312 | "braceright" => Some(XK_braceright), 313 | "asciitilde" => Some(XK_asciitilde), 314 | "nobreakspace" => Some(XK_nobreakspace), 315 | "exclamdown" => Some(XK_exclamdown), 316 | "cent" => Some(XK_cent), 317 | "sterling" => Some(XK_sterling), 318 | "currency" => Some(XK_currency), 319 | "yen" => Some(XK_yen), 320 | "brokenbar" => Some(XK_brokenbar), 321 | "section" => Some(XK_section), 322 | "diaeresis" => Some(XK_diaeresis), 323 | "copyright" => Some(XK_copyright), 324 | "ordfeminine" => Some(XK_ordfeminine), 325 | "guillemotleft" => Some(XK_guillemotleft), 326 | "notsign" => Some(XK_notsign), 327 | "hyphen" => Some(XK_hyphen), 328 | "registered" => Some(XK_registered), 329 | "macron" => Some(XK_macron), 330 | "degree" => Some(XK_degree), 331 | "plusminus" => Some(XK_plusminus), 332 | "twosuperior" => Some(XK_twosuperior), 333 | "threesuperior" => Some(XK_threesuperior), 334 | "acute" => Some(XK_acute), 335 | "mu" => Some(XK_mu), 336 | "paragraph" => Some(XK_paragraph), 337 | "periodcentered" => Some(XK_periodcentered), 338 | "cedilla" => Some(XK_cedilla), 339 | "onesuperior" => Some(XK_onesuperior), 340 | "masculine" => Some(XK_masculine), 341 | "guillemotright" => Some(XK_guillemotright), 342 | "onequarter" => Some(XK_onequarter), 343 | "onehalf" => Some(XK_onehalf), 344 | "threequarters" => Some(XK_threequarters), 345 | "questiondown" => Some(XK_questiondown), 346 | "Agrave" => Some(XK_Agrave), 347 | "Aacute" => Some(XK_Aacute), 348 | "Acircumflex" => Some(XK_Acircumflex), 349 | "Atilde" => Some(XK_Atilde), 350 | "Adiaeresis" => Some(XK_Adiaeresis), 351 | "Aring" => Some(XK_Aring), 352 | "AE" => Some(XK_AE), 353 | "Ccedilla" => Some(XK_Ccedilla), 354 | "Egrave" => Some(XK_Egrave), 355 | "Eacute" => Some(XK_Eacute), 356 | "Ecircumflex" => Some(XK_Ecircumflex), 357 | "Ediaeresis" => Some(XK_Ediaeresis), 358 | "Igrave" => Some(XK_Igrave), 359 | "Iacute" => Some(XK_Iacute), 360 | "Icircumflex" => Some(XK_Icircumflex), 361 | "Idiaeresis" => Some(XK_Idiaeresis), 362 | "ETH" => Some(XK_ETH), 363 | "Eth" => Some(XK_Eth), 364 | "Ntilde" => Some(XK_Ntilde), 365 | "Ograve" => Some(XK_Ograve), 366 | "Oacute" => Some(XK_Oacute), 367 | "Ocircumflex" => Some(XK_Ocircumflex), 368 | "Otilde" => Some(XK_Otilde), 369 | "Odiaeresis" => Some(XK_Odiaeresis), 370 | "multiply" => Some(XK_multiply), 371 | "Ooblique" => Some(XK_Ooblique), 372 | "Ugrave" => Some(XK_Ugrave), 373 | "Uacute" => Some(XK_Uacute), 374 | "Ucircumflex" => Some(XK_Ucircumflex), 375 | "Udiaeresis" => Some(XK_Udiaeresis), 376 | "Yacute" => Some(XK_Yacute), 377 | "THORN" => Some(XK_THORN), 378 | "Thorn" => Some(XK_Thorn), 379 | "ssharp" => Some(XK_ssharp), 380 | "agrave" => Some(XK_agrave), 381 | "aacute" => Some(XK_aacute), 382 | "acircumflex" => Some(XK_acircumflex), 383 | "atilde" => Some(XK_atilde), 384 | "adiaeresis" => Some(XK_adiaeresis), 385 | "aring" => Some(XK_aring), 386 | "ae" => Some(XK_ae), 387 | "ccedilla" => Some(XK_ccedilla), 388 | "egrave" => Some(XK_egrave), 389 | "eacute" => Some(XK_eacute), 390 | "ecircumflex" => Some(XK_ecircumflex), 391 | "ediaeresis" => Some(XK_ediaeresis), 392 | "igrave" => Some(XK_igrave), 393 | "iacute" => Some(XK_iacute), 394 | "icircumflex" => Some(XK_icircumflex), 395 | "idiaeresis" => Some(XK_idiaeresis), 396 | "eth" => Some(XK_eth), 397 | "ntilde" => Some(XK_ntilde), 398 | "ograve" => Some(XK_ograve), 399 | "oacute" => Some(XK_oacute), 400 | "ocircumflex" => Some(XK_ocircumflex), 401 | "otilde" => Some(XK_otilde), 402 | "odiaeresis" => Some(XK_odiaeresis), 403 | "division" => Some(XK_division), 404 | "oslash" => Some(XK_oslash), 405 | "ugrave" => Some(XK_ugrave), 406 | "uacute" => Some(XK_uacute), 407 | "ucircumflex" => Some(XK_ucircumflex), 408 | "udiaeresis" => Some(XK_udiaeresis), 409 | "yacute" => Some(XK_yacute), 410 | "thorn" => Some(XK_thorn), 411 | "ydiaeresis" => Some(XK_ydiaeresis), 412 | "Aogonek" => Some(XK_Aogonek), 413 | "breve" => Some(XK_breve), 414 | "Lstroke" => Some(XK_Lstroke), 415 | "Lcaron" => Some(XK_Lcaron), 416 | "Sacute" => Some(XK_Sacute), 417 | "Scaron" => Some(XK_Scaron), 418 | "Scedilla" => Some(XK_Scedilla), 419 | "Tcaron" => Some(XK_Tcaron), 420 | "Zacute" => Some(XK_Zacute), 421 | "Zcaron" => Some(XK_Zcaron), 422 | "Zabovedot" => Some(XK_Zabovedot), 423 | "aogonek" => Some(XK_aogonek), 424 | "ogonek" => Some(XK_ogonek), 425 | "lstroke" => Some(XK_lstroke), 426 | "lcaron" => Some(XK_lcaron), 427 | "sacute" => Some(XK_sacute), 428 | "caron" => Some(XK_caron), 429 | "scaron" => Some(XK_scaron), 430 | "scedilla" => Some(XK_scedilla), 431 | "tcaron" => Some(XK_tcaron), 432 | "zacute" => Some(XK_zacute), 433 | "doubleacute" => Some(XK_doubleacute), 434 | "zcaron" => Some(XK_zcaron), 435 | "zabovedot" => Some(XK_zabovedot), 436 | "Racute" => Some(XK_Racute), 437 | "Abreve" => Some(XK_Abreve), 438 | "Lacute" => Some(XK_Lacute), 439 | "Cacute" => Some(XK_Cacute), 440 | "Ccaron" => Some(XK_Ccaron), 441 | "Eogonek" => Some(XK_Eogonek), 442 | "Ecaron" => Some(XK_Ecaron), 443 | "Dcaron" => Some(XK_Dcaron), 444 | "Dstroke" => Some(XK_Dstroke), 445 | "Nacute" => Some(XK_Nacute), 446 | "Ncaron" => Some(XK_Ncaron), 447 | "Odoubleacute" => Some(XK_Odoubleacute), 448 | "Rcaron" => Some(XK_Rcaron), 449 | "Uring" => Some(XK_Uring), 450 | "Udoubleacute" => Some(XK_Udoubleacute), 451 | "Tcedilla" => Some(XK_Tcedilla), 452 | "racute" => Some(XK_racute), 453 | "abreve" => Some(XK_abreve), 454 | "lacute" => Some(XK_lacute), 455 | "cacute" => Some(XK_cacute), 456 | "ccaron" => Some(XK_ccaron), 457 | "eogonek" => Some(XK_eogonek), 458 | "ecaron" => Some(XK_ecaron), 459 | "dcaron" => Some(XK_dcaron), 460 | "dstroke" => Some(XK_dstroke), 461 | "nacute" => Some(XK_nacute), 462 | "ncaron" => Some(XK_ncaron), 463 | "odoubleacute" => Some(XK_odoubleacute), 464 | "udoubleacute" => Some(XK_udoubleacute), 465 | "rcaron" => Some(XK_rcaron), 466 | "uring" => Some(XK_uring), 467 | "tcedilla" => Some(XK_tcedilla), 468 | "abovedot" => Some(XK_abovedot), 469 | "Hstroke" => Some(XK_Hstroke), 470 | "Hcircumflex" => Some(XK_Hcircumflex), 471 | "Iabovedot" => Some(XK_Iabovedot), 472 | "Gbreve" => Some(XK_Gbreve), 473 | "Jcircumflex" => Some(XK_Jcircumflex), 474 | "hstroke" => Some(XK_hstroke), 475 | "hcircumflex" => Some(XK_hcircumflex), 476 | "idotless" => Some(XK_idotless), 477 | "gbreve" => Some(XK_gbreve), 478 | "jcircumflex" => Some(XK_jcircumflex), 479 | "Cabovedot" => Some(XK_Cabovedot), 480 | "Ccircumflex" => Some(XK_Ccircumflex), 481 | "Gabovedot" => Some(XK_Gabovedot), 482 | "Gcircumflex" => Some(XK_Gcircumflex), 483 | "Ubreve" => Some(XK_Ubreve), 484 | "Scircumflex" => Some(XK_Scircumflex), 485 | "cabovedot" => Some(XK_cabovedot), 486 | "ccircumflex" => Some(XK_ccircumflex), 487 | "gabovedot" => Some(XK_gabovedot), 488 | "gcircumflex" => Some(XK_gcircumflex), 489 | "ubreve" => Some(XK_ubreve), 490 | "scircumflex" => Some(XK_scircumflex), 491 | "kra" => Some(XK_kra), 492 | "kappa" => Some(XK_kappa), 493 | "Rcedilla" => Some(XK_Rcedilla), 494 | "Itilde" => Some(XK_Itilde), 495 | "Lcedilla" => Some(XK_Lcedilla), 496 | "Emacron" => Some(XK_Emacron), 497 | "Gcedilla" => Some(XK_Gcedilla), 498 | "Tslash" => Some(XK_Tslash), 499 | "rcedilla" => Some(XK_rcedilla), 500 | "itilde" => Some(XK_itilde), 501 | "lcedilla" => Some(XK_lcedilla), 502 | "emacron" => Some(XK_emacron), 503 | "gcedilla" => Some(XK_gcedilla), 504 | "tslash" => Some(XK_tslash), 505 | "ENG" => Some(XK_ENG), 506 | "eng" => Some(XK_eng), 507 | "Amacron" => Some(XK_Amacron), 508 | "Iogonek" => Some(XK_Iogonek), 509 | "Eabovedot" => Some(XK_Eabovedot), 510 | "Imacron" => Some(XK_Imacron), 511 | "Ncedilla" => Some(XK_Ncedilla), 512 | "Omacron" => Some(XK_Omacron), 513 | "Kcedilla" => Some(XK_Kcedilla), 514 | "Uogonek" => Some(XK_Uogonek), 515 | "Utilde" => Some(XK_Utilde), 516 | "Umacron" => Some(XK_Umacron), 517 | "amacron" => Some(XK_amacron), 518 | "iogonek" => Some(XK_iogonek), 519 | "eabovedot" => Some(XK_eabovedot), 520 | "imacron" => Some(XK_imacron), 521 | "ncedilla" => Some(XK_ncedilla), 522 | "omacron" => Some(XK_omacron), 523 | "kcedilla" => Some(XK_kcedilla), 524 | "uogonek" => Some(XK_uogonek), 525 | "utilde" => Some(XK_utilde), 526 | "umacron" => Some(XK_umacron), 527 | "overline" => Some(XK_overline), 528 | "kana_fullstop" => Some(XK_kana_fullstop), 529 | "kana_openingbracket" => Some(XK_kana_openingbracket), 530 | "kana_closingbracket" => Some(XK_kana_closingbracket), 531 | "kana_comma" => Some(XK_kana_comma), 532 | "kana_conjunctive" => Some(XK_kana_conjunctive), 533 | "kana_middledot" => Some(XK_kana_middledot), 534 | "kana_WO" => Some(XK_kana_WO), 535 | "kana_a" => Some(XK_kana_a), 536 | "kana_i" => Some(XK_kana_i), 537 | "kana_u" => Some(XK_kana_u), 538 | "kana_e" => Some(XK_kana_e), 539 | "kana_o" => Some(XK_kana_o), 540 | "kana_ya" => Some(XK_kana_ya), 541 | "kana_yu" => Some(XK_kana_yu), 542 | "kana_yo" => Some(XK_kana_yo), 543 | "kana_tsu" => Some(XK_kana_tsu), 544 | "kana_tu" => Some(XK_kana_tu), 545 | "prolongedsound" => Some(XK_prolongedsound), 546 | "kana_A" => Some(XK_kana_A), 547 | "kana_I" => Some(XK_kana_I), 548 | "kana_U" => Some(XK_kana_U), 549 | "kana_E" => Some(XK_kana_E), 550 | "kana_O" => Some(XK_kana_O), 551 | "kana_KA" => Some(XK_kana_KA), 552 | "kana_KI" => Some(XK_kana_KI), 553 | "kana_KU" => Some(XK_kana_KU), 554 | "kana_KE" => Some(XK_kana_KE), 555 | "kana_KO" => Some(XK_kana_KO), 556 | "kana_SA" => Some(XK_kana_SA), 557 | "kana_SHI" => Some(XK_kana_SHI), 558 | "kana_SU" => Some(XK_kana_SU), 559 | "kana_SE" => Some(XK_kana_SE), 560 | "kana_SO" => Some(XK_kana_SO), 561 | "kana_TA" => Some(XK_kana_TA), 562 | "kana_CHI" => Some(XK_kana_CHI), 563 | "kana_TI" => Some(XK_kana_TI), 564 | "kana_TSU" => Some(XK_kana_TSU), 565 | "kana_TU" => Some(XK_kana_TU), 566 | "kana_TE" => Some(XK_kana_TE), 567 | "kana_TO" => Some(XK_kana_TO), 568 | "kana_NA" => Some(XK_kana_NA), 569 | "kana_NI" => Some(XK_kana_NI), 570 | "kana_NU" => Some(XK_kana_NU), 571 | "kana_NE" => Some(XK_kana_NE), 572 | "kana_NO" => Some(XK_kana_NO), 573 | "kana_HA" => Some(XK_kana_HA), 574 | "kana_HI" => Some(XK_kana_HI), 575 | "kana_FU" => Some(XK_kana_FU), 576 | "kana_HU" => Some(XK_kana_HU), 577 | "kana_HE" => Some(XK_kana_HE), 578 | "kana_HO" => Some(XK_kana_HO), 579 | "kana_MA" => Some(XK_kana_MA), 580 | "kana_MI" => Some(XK_kana_MI), 581 | "kana_MU" => Some(XK_kana_MU), 582 | "kana_ME" => Some(XK_kana_ME), 583 | "kana_MO" => Some(XK_kana_MO), 584 | "kana_YA" => Some(XK_kana_YA), 585 | "kana_YU" => Some(XK_kana_YU), 586 | "kana_YO" => Some(XK_kana_YO), 587 | "kana_RA" => Some(XK_kana_RA), 588 | "kana_RI" => Some(XK_kana_RI), 589 | "kana_RU" => Some(XK_kana_RU), 590 | "kana_RE" => Some(XK_kana_RE), 591 | "kana_RO" => Some(XK_kana_RO), 592 | "kana_WA" => Some(XK_kana_WA), 593 | "kana_N" => Some(XK_kana_N), 594 | "voicedsound" => Some(XK_voicedsound), 595 | "semivoicedsound" => Some(XK_semivoicedsound), 596 | "kana_switch" => Some(XK_kana_switch), 597 | "Arabic_comma" => Some(XK_Arabic_comma), 598 | "Arabic_semicolon" => Some(XK_Arabic_semicolon), 599 | "Arabic_question_mark" => Some(XK_Arabic_question_mark), 600 | "Arabic_hamza" => Some(XK_Arabic_hamza), 601 | "Arabic_maddaonalef" => Some(XK_Arabic_maddaonalef), 602 | "Arabic_hamzaonalef" => Some(XK_Arabic_hamzaonalef), 603 | "Arabic_hamzaonwaw" => Some(XK_Arabic_hamzaonwaw), 604 | "Arabic_hamzaunderalef" => Some(XK_Arabic_hamzaunderalef), 605 | "Arabic_hamzaonyeh" => Some(XK_Arabic_hamzaonyeh), 606 | "Arabic_alef" => Some(XK_Arabic_alef), 607 | "Arabic_beh" => Some(XK_Arabic_beh), 608 | "Arabic_tehmarbuta" => Some(XK_Arabic_tehmarbuta), 609 | "Arabic_teh" => Some(XK_Arabic_teh), 610 | "Arabic_theh" => Some(XK_Arabic_theh), 611 | "Arabic_jeem" => Some(XK_Arabic_jeem), 612 | "Arabic_hah" => Some(XK_Arabic_hah), 613 | "Arabic_khah" => Some(XK_Arabic_khah), 614 | "Arabic_dal" => Some(XK_Arabic_dal), 615 | "Arabic_thal" => Some(XK_Arabic_thal), 616 | "Arabic_ra" => Some(XK_Arabic_ra), 617 | "Arabic_zain" => Some(XK_Arabic_zain), 618 | "Arabic_seen" => Some(XK_Arabic_seen), 619 | "Arabic_sheen" => Some(XK_Arabic_sheen), 620 | "Arabic_sad" => Some(XK_Arabic_sad), 621 | "Arabic_dad" => Some(XK_Arabic_dad), 622 | "Arabic_tah" => Some(XK_Arabic_tah), 623 | "Arabic_zah" => Some(XK_Arabic_zah), 624 | "Arabic_ain" => Some(XK_Arabic_ain), 625 | "Arabic_ghain" => Some(XK_Arabic_ghain), 626 | "Arabic_tatweel" => Some(XK_Arabic_tatweel), 627 | "Arabic_feh" => Some(XK_Arabic_feh), 628 | "Arabic_qaf" => Some(XK_Arabic_qaf), 629 | "Arabic_kaf" => Some(XK_Arabic_kaf), 630 | "Arabic_lam" => Some(XK_Arabic_lam), 631 | "Arabic_meem" => Some(XK_Arabic_meem), 632 | "Arabic_noon" => Some(XK_Arabic_noon), 633 | "Arabic_ha" => Some(XK_Arabic_ha), 634 | "Arabic_heh" => Some(XK_Arabic_heh), 635 | "Arabic_waw" => Some(XK_Arabic_waw), 636 | "Arabic_alefmaksura" => Some(XK_Arabic_alefmaksura), 637 | "Arabic_yeh" => Some(XK_Arabic_yeh), 638 | "Arabic_fathatan" => Some(XK_Arabic_fathatan), 639 | "Arabic_dammatan" => Some(XK_Arabic_dammatan), 640 | "Arabic_kasratan" => Some(XK_Arabic_kasratan), 641 | "Arabic_fatha" => Some(XK_Arabic_fatha), 642 | "Arabic_damma" => Some(XK_Arabic_damma), 643 | "Arabic_kasra" => Some(XK_Arabic_kasra), 644 | "Arabic_shadda" => Some(XK_Arabic_shadda), 645 | "Arabic_sukun" => Some(XK_Arabic_sukun), 646 | "Arabic_switch" => Some(XK_Arabic_switch), 647 | "Serbian_dje" => Some(XK_Serbian_dje), 648 | "Macedonia_gje" => Some(XK_Macedonia_gje), 649 | "Cyrillic_io" => Some(XK_Cyrillic_io), 650 | "Ukrainian_ie" => Some(XK_Ukrainian_ie), 651 | "Ukranian_je" => Some(XK_Ukranian_je), 652 | "Macedonia_dse" => Some(XK_Macedonia_dse), 653 | "Ukrainian_i" => Some(XK_Ukrainian_i), 654 | "Ukranian_i" => Some(XK_Ukranian_i), 655 | "Ukrainian_yi" => Some(XK_Ukrainian_yi), 656 | "Ukranian_yi" => Some(XK_Ukranian_yi), 657 | "Cyrillic_je" => Some(XK_Cyrillic_je), 658 | "Serbian_je" => Some(XK_Serbian_je), 659 | "Cyrillic_lje" => Some(XK_Cyrillic_lje), 660 | "Serbian_lje" => Some(XK_Serbian_lje), 661 | "Cyrillic_nje" => Some(XK_Cyrillic_nje), 662 | "Serbian_nje" => Some(XK_Serbian_nje), 663 | "Serbian_tshe" => Some(XK_Serbian_tshe), 664 | "Macedonia_kje" => Some(XK_Macedonia_kje), 665 | "Byelorussian_shortu" => Some(XK_Byelorussian_shortu), 666 | "Cyrillic_dzhe" => Some(XK_Cyrillic_dzhe), 667 | "Serbian_dze" => Some(XK_Serbian_dze), 668 | "numerosign" => Some(XK_numerosign), 669 | "Serbian_DJE" => Some(XK_Serbian_DJE), 670 | "Macedonia_GJE" => Some(XK_Macedonia_GJE), 671 | "Cyrillic_IO" => Some(XK_Cyrillic_IO), 672 | "Ukrainian_IE" => Some(XK_Ukrainian_IE), 673 | "Ukranian_JE" => Some(XK_Ukranian_JE), 674 | "Macedonia_DSE" => Some(XK_Macedonia_DSE), 675 | "Ukrainian_I" => Some(XK_Ukrainian_I), 676 | "Ukranian_I" => Some(XK_Ukranian_I), 677 | "Ukrainian_YI" => Some(XK_Ukrainian_YI), 678 | "Ukranian_YI" => Some(XK_Ukranian_YI), 679 | "Cyrillic_JE" => Some(XK_Cyrillic_JE), 680 | "Serbian_JE" => Some(XK_Serbian_JE), 681 | "Cyrillic_LJE" => Some(XK_Cyrillic_LJE), 682 | "Serbian_LJE" => Some(XK_Serbian_LJE), 683 | "Cyrillic_NJE" => Some(XK_Cyrillic_NJE), 684 | "Serbian_NJE" => Some(XK_Serbian_NJE), 685 | "Serbian_TSHE" => Some(XK_Serbian_TSHE), 686 | "Macedonia_KJE" => Some(XK_Macedonia_KJE), 687 | "Byelorussian_SHORTU" => Some(XK_Byelorussian_SHORTU), 688 | "Cyrillic_DZHE" => Some(XK_Cyrillic_DZHE), 689 | "Serbian_DZE" => Some(XK_Serbian_DZE), 690 | "Cyrillic_yu" => Some(XK_Cyrillic_yu), 691 | "Cyrillic_a" => Some(XK_Cyrillic_a), 692 | "Cyrillic_be" => Some(XK_Cyrillic_be), 693 | "Cyrillic_tse" => Some(XK_Cyrillic_tse), 694 | "Cyrillic_de" => Some(XK_Cyrillic_de), 695 | "Cyrillic_ie" => Some(XK_Cyrillic_ie), 696 | "Cyrillic_ef" => Some(XK_Cyrillic_ef), 697 | "Cyrillic_ghe" => Some(XK_Cyrillic_ghe), 698 | "Cyrillic_ha" => Some(XK_Cyrillic_ha), 699 | "Cyrillic_i" => Some(XK_Cyrillic_i), 700 | "Cyrillic_shorti" => Some(XK_Cyrillic_shorti), 701 | "Cyrillic_ka" => Some(XK_Cyrillic_ka), 702 | "Cyrillic_el" => Some(XK_Cyrillic_el), 703 | "Cyrillic_em" => Some(XK_Cyrillic_em), 704 | "Cyrillic_en" => Some(XK_Cyrillic_en), 705 | "Cyrillic_o" => Some(XK_Cyrillic_o), 706 | "Cyrillic_pe" => Some(XK_Cyrillic_pe), 707 | "Cyrillic_ya" => Some(XK_Cyrillic_ya), 708 | "Cyrillic_er" => Some(XK_Cyrillic_er), 709 | "Cyrillic_es" => Some(XK_Cyrillic_es), 710 | "Cyrillic_te" => Some(XK_Cyrillic_te), 711 | "Cyrillic_u" => Some(XK_Cyrillic_u), 712 | "Cyrillic_zhe" => Some(XK_Cyrillic_zhe), 713 | "Cyrillic_ve" => Some(XK_Cyrillic_ve), 714 | "Cyrillic_softsign" => Some(XK_Cyrillic_softsign), 715 | "Cyrillic_yeru" => Some(XK_Cyrillic_yeru), 716 | "Cyrillic_ze" => Some(XK_Cyrillic_ze), 717 | "Cyrillic_sha" => Some(XK_Cyrillic_sha), 718 | "Cyrillic_e" => Some(XK_Cyrillic_e), 719 | "Cyrillic_shcha" => Some(XK_Cyrillic_shcha), 720 | "Cyrillic_che" => Some(XK_Cyrillic_che), 721 | "Cyrillic_hardsign" => Some(XK_Cyrillic_hardsign), 722 | "Cyrillic_YU" => Some(XK_Cyrillic_YU), 723 | "Cyrillic_A" => Some(XK_Cyrillic_A), 724 | "Cyrillic_BE" => Some(XK_Cyrillic_BE), 725 | "Cyrillic_TSE" => Some(XK_Cyrillic_TSE), 726 | "Cyrillic_DE" => Some(XK_Cyrillic_DE), 727 | "Cyrillic_IE" => Some(XK_Cyrillic_IE), 728 | "Cyrillic_EF" => Some(XK_Cyrillic_EF), 729 | "Cyrillic_GHE" => Some(XK_Cyrillic_GHE), 730 | "Cyrillic_HA" => Some(XK_Cyrillic_HA), 731 | "Cyrillic_I" => Some(XK_Cyrillic_I), 732 | "Cyrillic_SHORTI" => Some(XK_Cyrillic_SHORTI), 733 | "Cyrillic_KA" => Some(XK_Cyrillic_KA), 734 | "Cyrillic_EL" => Some(XK_Cyrillic_EL), 735 | "Cyrillic_EM" => Some(XK_Cyrillic_EM), 736 | "Cyrillic_EN" => Some(XK_Cyrillic_EN), 737 | "Cyrillic_O" => Some(XK_Cyrillic_O), 738 | "Cyrillic_PE" => Some(XK_Cyrillic_PE), 739 | "Cyrillic_YA" => Some(XK_Cyrillic_YA), 740 | "Cyrillic_ER" => Some(XK_Cyrillic_ER), 741 | "Cyrillic_ES" => Some(XK_Cyrillic_ES), 742 | "Cyrillic_TE" => Some(XK_Cyrillic_TE), 743 | "Cyrillic_U" => Some(XK_Cyrillic_U), 744 | "Cyrillic_ZHE" => Some(XK_Cyrillic_ZHE), 745 | "Cyrillic_VE" => Some(XK_Cyrillic_VE), 746 | "Cyrillic_SOFTSIGN" => Some(XK_Cyrillic_SOFTSIGN), 747 | "Cyrillic_YERU" => Some(XK_Cyrillic_YERU), 748 | "Cyrillic_ZE" => Some(XK_Cyrillic_ZE), 749 | "Cyrillic_SHA" => Some(XK_Cyrillic_SHA), 750 | "Cyrillic_E" => Some(XK_Cyrillic_E), 751 | "Cyrillic_SHCHA" => Some(XK_Cyrillic_SHCHA), 752 | "Cyrillic_CHE" => Some(XK_Cyrillic_CHE), 753 | "Cyrillic_HARDSIGN" => Some(XK_Cyrillic_HARDSIGN), 754 | "Greek_ALPHAaccent" => Some(XK_Greek_ALPHAaccent), 755 | "Greek_EPSILONaccent" => Some(XK_Greek_EPSILONaccent), 756 | "Greek_ETAaccent" => Some(XK_Greek_ETAaccent), 757 | "Greek_IOTAaccent" => Some(XK_Greek_IOTAaccent), 758 | "Greek_IOTAdiaeresis" => Some(XK_Greek_IOTAdiaeresis), 759 | "Greek_OMICRONaccent" => Some(XK_Greek_OMICRONaccent), 760 | "Greek_UPSILONaccent" => Some(XK_Greek_UPSILONaccent), 761 | "Greek_UPSILONdieresis" => Some(XK_Greek_UPSILONdieresis), 762 | "Greek_OMEGAaccent" => Some(XK_Greek_OMEGAaccent), 763 | "Greek_accentdieresis" => Some(XK_Greek_accentdieresis), 764 | "Greek_horizbar" => Some(XK_Greek_horizbar), 765 | "Greek_alphaaccent" => Some(XK_Greek_alphaaccent), 766 | "Greek_epsilonaccent" => Some(XK_Greek_epsilonaccent), 767 | "Greek_etaaccent" => Some(XK_Greek_etaaccent), 768 | "Greek_iotaaccent" => Some(XK_Greek_iotaaccent), 769 | "Greek_iotadieresis" => Some(XK_Greek_iotadieresis), 770 | "Greek_iotaaccentdieresis" => Some(XK_Greek_iotaaccentdieresis), 771 | "Greek_omicronaccent" => Some(XK_Greek_omicronaccent), 772 | "Greek_upsilonaccent" => Some(XK_Greek_upsilonaccent), 773 | "Greek_upsilondieresis" => Some(XK_Greek_upsilondieresis), 774 | "Greek_upsilonaccentdieresis" => Some(XK_Greek_upsilonaccentdieresis), 775 | "Greek_omegaaccent" => Some(XK_Greek_omegaaccent), 776 | "Greek_ALPHA" => Some(XK_Greek_ALPHA), 777 | "Greek_BETA" => Some(XK_Greek_BETA), 778 | "Greek_GAMMA" => Some(XK_Greek_GAMMA), 779 | "Greek_DELTA" => Some(XK_Greek_DELTA), 780 | "Greek_EPSILON" => Some(XK_Greek_EPSILON), 781 | "Greek_ZETA" => Some(XK_Greek_ZETA), 782 | "Greek_ETA" => Some(XK_Greek_ETA), 783 | "Greek_THETA" => Some(XK_Greek_THETA), 784 | "Greek_IOTA" => Some(XK_Greek_IOTA), 785 | "Greek_KAPPA" => Some(XK_Greek_KAPPA), 786 | "Greek_LAMDA" => Some(XK_Greek_LAMDA), 787 | "Greek_LAMBDA" => Some(XK_Greek_LAMBDA), 788 | "Greek_MU" => Some(XK_Greek_MU), 789 | "Greek_NU" => Some(XK_Greek_NU), 790 | "Greek_XI" => Some(XK_Greek_XI), 791 | "Greek_OMICRON" => Some(XK_Greek_OMICRON), 792 | "Greek_PI" => Some(XK_Greek_PI), 793 | "Greek_RHO" => Some(XK_Greek_RHO), 794 | "Greek_SIGMA" => Some(XK_Greek_SIGMA), 795 | "Greek_TAU" => Some(XK_Greek_TAU), 796 | "Greek_UPSILON" => Some(XK_Greek_UPSILON), 797 | "Greek_PHI" => Some(XK_Greek_PHI), 798 | "Greek_CHI" => Some(XK_Greek_CHI), 799 | "Greek_PSI" => Some(XK_Greek_PSI), 800 | "Greek_OMEGA" => Some(XK_Greek_OMEGA), 801 | "Greek_alpha" => Some(XK_Greek_alpha), 802 | "Greek_beta" => Some(XK_Greek_beta), 803 | "Greek_gamma" => Some(XK_Greek_gamma), 804 | "Greek_delta" => Some(XK_Greek_delta), 805 | "Greek_epsilon" => Some(XK_Greek_epsilon), 806 | "Greek_zeta" => Some(XK_Greek_zeta), 807 | "Greek_eta" => Some(XK_Greek_eta), 808 | "Greek_theta" => Some(XK_Greek_theta), 809 | "Greek_iota" => Some(XK_Greek_iota), 810 | "Greek_kappa" => Some(XK_Greek_kappa), 811 | "Greek_lamda" => Some(XK_Greek_lamda), 812 | "Greek_lambda" => Some(XK_Greek_lambda), 813 | "Greek_mu" => Some(XK_Greek_mu), 814 | "Greek_nu" => Some(XK_Greek_nu), 815 | "Greek_xi" => Some(XK_Greek_xi), 816 | "Greek_omicron" => Some(XK_Greek_omicron), 817 | "Greek_pi" => Some(XK_Greek_pi), 818 | "Greek_rho" => Some(XK_Greek_rho), 819 | "Greek_sigma" => Some(XK_Greek_sigma), 820 | "Greek_finalsmallsigma" => Some(XK_Greek_finalsmallsigma), 821 | "Greek_tau" => Some(XK_Greek_tau), 822 | "Greek_upsilon" => Some(XK_Greek_upsilon), 823 | "Greek_phi" => Some(XK_Greek_phi), 824 | "Greek_chi" => Some(XK_Greek_chi), 825 | "Greek_psi" => Some(XK_Greek_psi), 826 | "Greek_omega" => Some(XK_Greek_omega), 827 | "Greek_switch" => Some(XK_Greek_switch), 828 | "leftradical" => Some(XK_leftradical), 829 | "topleftradical" => Some(XK_topleftradical), 830 | "horizconnector" => Some(XK_horizconnector), 831 | "topintegral" => Some(XK_topintegral), 832 | "botintegral" => Some(XK_botintegral), 833 | "vertconnector" => Some(XK_vertconnector), 834 | "topleftsqbracket" => Some(XK_topleftsqbracket), 835 | "botleftsqbracket" => Some(XK_botleftsqbracket), 836 | "toprightsqbracket" => Some(XK_toprightsqbracket), 837 | "botrightsqbracket" => Some(XK_botrightsqbracket), 838 | "topleftparens" => Some(XK_topleftparens), 839 | "botleftparens" => Some(XK_botleftparens), 840 | "toprightparens" => Some(XK_toprightparens), 841 | "botrightparens" => Some(XK_botrightparens), 842 | "leftmiddlecurlybrace" => Some(XK_leftmiddlecurlybrace), 843 | "rightmiddlecurlybrace" => Some(XK_rightmiddlecurlybrace), 844 | "topleftsummation" => Some(XK_topleftsummation), 845 | "botleftsummation" => Some(XK_botleftsummation), 846 | "topvertsummationconnector" => Some(XK_topvertsummationconnector), 847 | "botvertsummationconnector" => Some(XK_botvertsummationconnector), 848 | "toprightsummation" => Some(XK_toprightsummation), 849 | "botrightsummation" => Some(XK_botrightsummation), 850 | "rightmiddlesummation" => Some(XK_rightmiddlesummation), 851 | "lessthanequal" => Some(XK_lessthanequal), 852 | "notequal" => Some(XK_notequal), 853 | "greaterthanequal" => Some(XK_greaterthanequal), 854 | "integral" => Some(XK_integral), 855 | "therefore" => Some(XK_therefore), 856 | "variation" => Some(XK_variation), 857 | "infinity" => Some(XK_infinity), 858 | "nabla" => Some(XK_nabla), 859 | "approximate" => Some(XK_approximate), 860 | "similarequal" => Some(XK_similarequal), 861 | "ifonlyif" => Some(XK_ifonlyif), 862 | "implies" => Some(XK_implies), 863 | "identical" => Some(XK_identical), 864 | "radical" => Some(XK_radical), 865 | "includedin" => Some(XK_includedin), 866 | "includes" => Some(XK_includes), 867 | "intersection" => Some(XK_intersection), 868 | "union" => Some(XK_union), 869 | "logicaland" => Some(XK_logicaland), 870 | "logicalor" => Some(XK_logicalor), 871 | "partialderivative" => Some(XK_partialderivative), 872 | "function" => Some(XK_function), 873 | "leftarrow" => Some(XK_leftarrow), 874 | "uparrow" => Some(XK_uparrow), 875 | "rightarrow" => Some(XK_rightarrow), 876 | "downarrow" => Some(XK_downarrow), 877 | "blank" => Some(XK_blank), 878 | "soliddiamond" => Some(XK_soliddiamond), 879 | "checkerboard" => Some(XK_checkerboard), 880 | "ht" => Some(XK_ht), 881 | "ff" => Some(XK_ff), 882 | "cr" => Some(XK_cr), 883 | "lf" => Some(XK_lf), 884 | "nl" => Some(XK_nl), 885 | "vt" => Some(XK_vt), 886 | "lowrightcorner" => Some(XK_lowrightcorner), 887 | "uprightcorner" => Some(XK_uprightcorner), 888 | "upleftcorner" => Some(XK_upleftcorner), 889 | "lowleftcorner" => Some(XK_lowleftcorner), 890 | "crossinglines" => Some(XK_crossinglines), 891 | "horizlinescan1" => Some(XK_horizlinescan1), 892 | "horizlinescan3" => Some(XK_horizlinescan3), 893 | "horizlinescan5" => Some(XK_horizlinescan5), 894 | "horizlinescan7" => Some(XK_horizlinescan7), 895 | "horizlinescan9" => Some(XK_horizlinescan9), 896 | "leftt" => Some(XK_leftt), 897 | "rightt" => Some(XK_rightt), 898 | "bott" => Some(XK_bott), 899 | "topt" => Some(XK_topt), 900 | "vertbar" => Some(XK_vertbar), 901 | "emspace" => Some(XK_emspace), 902 | "enspace" => Some(XK_enspace), 903 | "em3space" => Some(XK_em3space), 904 | "em4space" => Some(XK_em4space), 905 | "digitspace" => Some(XK_digitspace), 906 | "punctspace" => Some(XK_punctspace), 907 | "thinspace" => Some(XK_thinspace), 908 | "hairspace" => Some(XK_hairspace), 909 | "emdash" => Some(XK_emdash), 910 | "endash" => Some(XK_endash), 911 | "signifblank" => Some(XK_signifblank), 912 | "ellipsis" => Some(XK_ellipsis), 913 | "doubbaselinedot" => Some(XK_doubbaselinedot), 914 | "onethird" => Some(XK_onethird), 915 | "twothirds" => Some(XK_twothirds), 916 | "onefifth" => Some(XK_onefifth), 917 | "twofifths" => Some(XK_twofifths), 918 | "threefifths" => Some(XK_threefifths), 919 | "fourfifths" => Some(XK_fourfifths), 920 | "onesixth" => Some(XK_onesixth), 921 | "fivesixths" => Some(XK_fivesixths), 922 | "careof" => Some(XK_careof), 923 | "figdash" => Some(XK_figdash), 924 | "leftanglebracket" => Some(XK_leftanglebracket), 925 | "decimalpoint" => Some(XK_decimalpoint), 926 | "rightanglebracket" => Some(XK_rightanglebracket), 927 | "marker" => Some(XK_marker), 928 | "oneeighth" => Some(XK_oneeighth), 929 | "threeeighths" => Some(XK_threeeighths), 930 | "fiveeighths" => Some(XK_fiveeighths), 931 | "seveneighths" => Some(XK_seveneighths), 932 | "trademark" => Some(XK_trademark), 933 | "signaturemark" => Some(XK_signaturemark), 934 | "trademarkincircle" => Some(XK_trademarkincircle), 935 | "leftopentriangle" => Some(XK_leftopentriangle), 936 | "rightopentriangle" => Some(XK_rightopentriangle), 937 | "emopencircle" => Some(XK_emopencircle), 938 | "emopenrectangle" => Some(XK_emopenrectangle), 939 | "leftsinglequotemark" => Some(XK_leftsinglequotemark), 940 | "rightsinglequotemark" => Some(XK_rightsinglequotemark), 941 | "leftdoublequotemark" => Some(XK_leftdoublequotemark), 942 | "rightdoublequotemark" => Some(XK_rightdoublequotemark), 943 | "prescription" => Some(XK_prescription), 944 | "minutes" => Some(XK_minutes), 945 | "seconds" => Some(XK_seconds), 946 | "latincross" => Some(XK_latincross), 947 | "hexagram" => Some(XK_hexagram), 948 | "filledrectbullet" => Some(XK_filledrectbullet), 949 | "filledlefttribullet" => Some(XK_filledlefttribullet), 950 | "filledrighttribullet" => Some(XK_filledrighttribullet), 951 | "emfilledcircle" => Some(XK_emfilledcircle), 952 | "emfilledrect" => Some(XK_emfilledrect), 953 | "enopencircbullet" => Some(XK_enopencircbullet), 954 | "enopensquarebullet" => Some(XK_enopensquarebullet), 955 | "openrectbullet" => Some(XK_openrectbullet), 956 | "opentribulletup" => Some(XK_opentribulletup), 957 | "opentribulletdown" => Some(XK_opentribulletdown), 958 | "openstar" => Some(XK_openstar), 959 | "enfilledcircbullet" => Some(XK_enfilledcircbullet), 960 | "enfilledsqbullet" => Some(XK_enfilledsqbullet), 961 | "filledtribulletup" => Some(XK_filledtribulletup), 962 | "filledtribulletdown" => Some(XK_filledtribulletdown), 963 | "leftpointer" => Some(XK_leftpointer), 964 | "rightpointer" => Some(XK_rightpointer), 965 | "club" => Some(XK_club), 966 | "diamond" => Some(XK_diamond), 967 | "heart" => Some(XK_heart), 968 | "maltesecross" => Some(XK_maltesecross), 969 | "dagger" => Some(XK_dagger), 970 | "doubledagger" => Some(XK_doubledagger), 971 | "checkmark" => Some(XK_checkmark), 972 | "ballotcross" => Some(XK_ballotcross), 973 | "musicalsharp" => Some(XK_musicalsharp), 974 | "musicalflat" => Some(XK_musicalflat), 975 | "malesymbol" => Some(XK_malesymbol), 976 | "femalesymbol" => Some(XK_femalesymbol), 977 | "telephone" => Some(XK_telephone), 978 | "telephonerecorder" => Some(XK_telephonerecorder), 979 | "phonographcopyright" => Some(XK_phonographcopyright), 980 | "caret" => Some(XK_caret), 981 | "singlelowquotemark" => Some(XK_singlelowquotemark), 982 | "doublelowquotemark" => Some(XK_doublelowquotemark), 983 | "cursor" => Some(XK_cursor), 984 | "leftcaret" => Some(XK_leftcaret), 985 | "rightcaret" => Some(XK_rightcaret), 986 | "downcaret" => Some(XK_downcaret), 987 | "upcaret" => Some(XK_upcaret), 988 | "overbar" => Some(XK_overbar), 989 | "downtack" => Some(XK_downtack), 990 | "upshoe" => Some(XK_upshoe), 991 | "downstile" => Some(XK_downstile), 992 | "underbar" => Some(XK_underbar), 993 | "jot" => Some(XK_jot), 994 | "quad" => Some(XK_quad), 995 | "uptack" => Some(XK_uptack), 996 | "circle" => Some(XK_circle), 997 | "upstile" => Some(XK_upstile), 998 | "downshoe" => Some(XK_downshoe), 999 | "rightshoe" => Some(XK_rightshoe), 1000 | "leftshoe" => Some(XK_leftshoe), 1001 | "lefttack" => Some(XK_lefttack), 1002 | "righttack" => Some(XK_righttack), 1003 | "hebrew_doublelowline" => Some(XK_hebrew_doublelowline), 1004 | "hebrew_aleph" => Some(XK_hebrew_aleph), 1005 | "hebrew_bet" => Some(XK_hebrew_bet), 1006 | "hebrew_beth" => Some(XK_hebrew_beth), 1007 | "hebrew_gimel" => Some(XK_hebrew_gimel), 1008 | "hebrew_gimmel" => Some(XK_hebrew_gimmel), 1009 | "hebrew_dalet" => Some(XK_hebrew_dalet), 1010 | "hebrew_daleth" => Some(XK_hebrew_daleth), 1011 | "hebrew_he" => Some(XK_hebrew_he), 1012 | "hebrew_waw" => Some(XK_hebrew_waw), 1013 | "hebrew_zain" => Some(XK_hebrew_zain), 1014 | "hebrew_zayin" => Some(XK_hebrew_zayin), 1015 | "hebrew_chet" => Some(XK_hebrew_chet), 1016 | "hebrew_het" => Some(XK_hebrew_het), 1017 | "hebrew_tet" => Some(XK_hebrew_tet), 1018 | "hebrew_teth" => Some(XK_hebrew_teth), 1019 | "hebrew_yod" => Some(XK_hebrew_yod), 1020 | "hebrew_finalkaph" => Some(XK_hebrew_finalkaph), 1021 | "hebrew_kaph" => Some(XK_hebrew_kaph), 1022 | "hebrew_lamed" => Some(XK_hebrew_lamed), 1023 | "hebrew_finalmem" => Some(XK_hebrew_finalmem), 1024 | "hebrew_mem" => Some(XK_hebrew_mem), 1025 | "hebrew_finalnun" => Some(XK_hebrew_finalnun), 1026 | "hebrew_nun" => Some(XK_hebrew_nun), 1027 | "hebrew_samech" => Some(XK_hebrew_samech), 1028 | "hebrew_samekh" => Some(XK_hebrew_samekh), 1029 | "hebrew_ayin" => Some(XK_hebrew_ayin), 1030 | "hebrew_finalpe" => Some(XK_hebrew_finalpe), 1031 | "hebrew_pe" => Some(XK_hebrew_pe), 1032 | "hebrew_finalzade" => Some(XK_hebrew_finalzade), 1033 | "hebrew_finalzadi" => Some(XK_hebrew_finalzadi), 1034 | "hebrew_zade" => Some(XK_hebrew_zade), 1035 | "hebrew_zadi" => Some(XK_hebrew_zadi), 1036 | "hebrew_qoph" => Some(XK_hebrew_qoph), 1037 | "hebrew_kuf" => Some(XK_hebrew_kuf), 1038 | "hebrew_resh" => Some(XK_hebrew_resh), 1039 | "hebrew_shin" => Some(XK_hebrew_shin), 1040 | "hebrew_taw" => Some(XK_hebrew_taw), 1041 | "hebrew_taf" => Some(XK_hebrew_taf), 1042 | "Hebrew_switch" => Some(XK_Hebrew_switch), 1043 | "XF86XK_ModeLock" | "XF86ModeLock" => Some(XF86XK_ModeLock), 1044 | "XF86XK_MonBrightnessUp" | "XF86MonBrightnessUp" => Some(XF86XK_MonBrightnessUp), 1045 | "XF86XK_MonBrightnessDown" | "XF86MonBrightnessDown" => Some(XF86XK_MonBrightnessDown), 1046 | "XF86XK_KbdLightOnOff" | "XF86KbdLightOnOff" => Some(XF86XK_KbdLightOnOff), 1047 | "XF86XK_KbdBrightnessUp" | "XF86KbdBrightnessUp" => Some(XF86XK_KbdBrightnessUp), 1048 | "XF86XK_KbdBrightnessDown" | "XF86KbdBrightnessDown" => Some(XF86XK_KbdBrightnessDown), 1049 | "XF86XK_Standby" | "XF86Standby" => Some(XF86XK_Standby), 1050 | "XF86XK_AudioLowerVolume" | "XF86AudioLowerVolume" => Some(XF86XK_AudioLowerVolume), 1051 | "XF86XK_AudioMute" | "XF86AudioMute" => Some(XF86XK_AudioMute), 1052 | "XF86XK_AudioRaiseVolume" | "XF86AudioRaiseVolume" => Some(XF86XK_AudioRaiseVolume), 1053 | "XF86XK_AudioPlay" | "XF86AudioPlay" => Some(XF86XK_AudioPlay), 1054 | "XF86XK_AudioStop" | "XF86AudioStop" => Some(XF86XK_AudioStop), 1055 | "XF86XK_AudioPrev" | "XF86AudioPrev" => Some(XF86XK_AudioPrev), 1056 | "XF86XK_AudioNext" | "XF86AudioNext" => Some(XF86XK_AudioNext), 1057 | "XF86XK_HomePage" | "XF86HomePage" => Some(XF86XK_HomePage), 1058 | "XF86XK_Mail" | "XF86Mail" => Some(XF86XK_Mail), 1059 | "XF86XK_Start" | "XF86Start" => Some(XF86XK_Start), 1060 | "XF86XK_Search" | "XF86Search" => Some(XF86XK_Search), 1061 | "XF86XK_AudioRecord" | "XF86AudioRecord" => Some(XF86XK_AudioRecord), 1062 | "XF86XK_Calculator" | "XF86Calculator" => Some(XF86XK_Calculator), 1063 | "XF86XK_Memo" | "XF86Memo" => Some(XF86XK_Memo), 1064 | "XF86XK_ToDoList" | "XF86ToDoList" => Some(XF86XK_ToDoList), 1065 | "XF86XK_Calendar" | "XF86Calendar" => Some(XF86XK_Calendar), 1066 | "XF86XK_PowerDown" | "XF86PowerDown" => Some(XF86XK_PowerDown), 1067 | "XF86XK_ContrastAdjust" | "XF86ContrastAdjust" => Some(XF86XK_ContrastAdjust), 1068 | "XF86XK_RockerUp" | "XF86RockerUp" => Some(XF86XK_RockerUp), 1069 | "XF86XK_RockerDown" | "XF86RockerDown" => Some(XF86XK_RockerDown), 1070 | "XF86XK_RockerEnter" | "XF86RockerEnter" => Some(XF86XK_RockerEnter), 1071 | "XF86XK_Back" | "XF86Back" => Some(XF86XK_Back), 1072 | "XF86XK_Forward" | "XF86Forward" => Some(XF86XK_Forward), 1073 | "XF86XK_Stop" | "XF86Stop" => Some(XF86XK_Stop), 1074 | "XF86XK_Refresh" | "XF86Refresh" => Some(XF86XK_Refresh), 1075 | "XF86XK_PowerOff" | "XF86PowerOff" => Some(XF86XK_PowerOff), 1076 | "XF86XK_WakeUp" | "XF86WakeUp" => Some(XF86XK_WakeUp), 1077 | "XF86XK_Eject" | "XF86Eject" => Some(XF86XK_Eject), 1078 | "XF86XK_ScreenSaver" | "XF86ScreenSaver" => Some(XF86XK_ScreenSaver), 1079 | "XF86XK_WWW" | "XF86WWW" => Some(XF86XK_WWW), 1080 | "XF86XK_Sleep" | "XF86Sleep" => Some(XF86XK_Sleep), 1081 | "XF86XK_Favorites" | "XF86Favorites" => Some(XF86XK_Favorites), 1082 | "XF86XK_AudioPause" | "XF86AudioPause" => Some(XF86XK_AudioPause), 1083 | "XF86XK_AudioMedia" | "XF86AudioMedia" => Some(XF86XK_AudioMedia), 1084 | "XF86XK_MyComputer" | "XF86MyComputer" => Some(XF86XK_MyComputer), 1085 | "XF86XK_VendorHome" | "XF86VendorHome" => Some(XF86XK_VendorHome), 1086 | "XF86XK_LightBulb" | "XF86LightBulb" => Some(XF86XK_LightBulb), 1087 | "XF86XK_Shop" | "XF86Shop" => Some(XF86XK_Shop), 1088 | "XF86XK_History" | "XF86History" => Some(XF86XK_History), 1089 | "XF86XK_OpenURL" | "XF86OpenURL" => Some(XF86XK_OpenURL), 1090 | "XF86XK_AddFavorite" | "XF86AddFavorite" => Some(XF86XK_AddFavorite), 1091 | "XF86XK_HotLinks" | "XF86HotLinks" => Some(XF86XK_HotLinks), 1092 | "XF86XK_BrightnessAdjust" | "XF86BrightnessAdjust" => Some(XF86XK_BrightnessAdjust), 1093 | "XF86XK_Finance" | "XF86Finance" => Some(XF86XK_Finance), 1094 | "XF86XK_Community" | "XF86Community" => Some(XF86XK_Community), 1095 | "XF86XK_AudioRewind" | "XF86AudioRewind" => Some(XF86XK_AudioRewind), 1096 | "XF86XK_BackForward" | "XF86BackForward" => Some(XF86XK_BackForward), 1097 | "XF86XK_Launch0" | "XF86Launch0" => Some(XF86XK_Launch0), 1098 | "XF86XK_Launch1" | "XF86Launch1" => Some(XF86XK_Launch1), 1099 | "XF86XK_Launch2" | "XF86Launch2" => Some(XF86XK_Launch2), 1100 | "XF86XK_Launch3" | "XF86Launch3" => Some(XF86XK_Launch3), 1101 | "XF86XK_Launch4" | "XF86Launch4" => Some(XF86XK_Launch4), 1102 | "XF86XK_Launch5" | "XF86Launch5" => Some(XF86XK_Launch5), 1103 | "XF86XK_Launch6" | "XF86Launch6" => Some(XF86XK_Launch6), 1104 | "XF86XK_Launch7" | "XF86Launch7" => Some(XF86XK_Launch7), 1105 | "XF86XK_Launch8" | "XF86Launch8" => Some(XF86XK_Launch8), 1106 | "XF86XK_Launch9" | "XF86Launch9" => Some(XF86XK_Launch9), 1107 | "XF86XK_LaunchA" | "XF86LaunchA" => Some(XF86XK_LaunchA), 1108 | "XF86XK_LaunchB" | "XF86LaunchB" => Some(XF86XK_LaunchB), 1109 | "XF86XK_LaunchC" | "XF86LaunchC" => Some(XF86XK_LaunchC), 1110 | "XF86XK_LaunchD" | "XF86LaunchD" => Some(XF86XK_LaunchD), 1111 | "XF86XK_LaunchE" | "XF86LaunchE" => Some(XF86XK_LaunchE), 1112 | "XF86XK_LaunchF" | "XF86LaunchF" => Some(XF86XK_LaunchF), 1113 | "XF86XK_ApplicationLeft" | "XF86ApplicationLeft" => Some(XF86XK_ApplicationLeft), 1114 | "XF86XK_ApplicationRight" | "XF86ApplicationRight" => Some(XF86XK_ApplicationRight), 1115 | "XF86XK_Book" | "XF86Book" => Some(XF86XK_Book), 1116 | "XF86XK_CD" | "XF86CD" => Some(XF86XK_CD), 1117 | "XF86XK_Calculater" | "XF86Calculater" => Some(XF86XK_Calculater), 1118 | "XF86XK_Clear" | "XF86Clear" => Some(XF86XK_Clear), 1119 | "XF86XK_Close" | "XF86Close" => Some(XF86XK_Close), 1120 | "XF86XK_Copy" | "XF86Copy" => Some(XF86XK_Copy), 1121 | "XF86XK_Cut" | "XF86Cut" => Some(XF86XK_Cut), 1122 | "XF86XK_Display" | "XF86Display" => Some(XF86XK_Display), 1123 | "XF86XK_DOS" | "XF86DOS" => Some(XF86XK_DOS), 1124 | "XF86XK_Documents" | "XF86Documents" => Some(XF86XK_Documents), 1125 | "XF86XK_Excel" | "XF86Excel" => Some(XF86XK_Excel), 1126 | "XF86XK_Explorer" | "XF86Explorer" => Some(XF86XK_Explorer), 1127 | "XF86XK_Game" | "XF86Game" => Some(XF86XK_Game), 1128 | "XF86XK_Go" | "XF86Go" => Some(XF86XK_Go), 1129 | "XF86XK_iTouch" | "XF86iTouch" => Some(XF86XK_iTouch), 1130 | "XF86XK_LogOff" | "XF86LogOff" => Some(XF86XK_LogOff), 1131 | "XF86XK_Market" | "XF86Market" => Some(XF86XK_Market), 1132 | "XF86XK_Meeting" | "XF86Meeting" => Some(XF86XK_Meeting), 1133 | "XF86XK_MenuKB" | "XF86MenuKB" => Some(XF86XK_MenuKB), 1134 | "XF86XK_MenuPB" | "XF86MenuPB" => Some(XF86XK_MenuPB), 1135 | "XF86XK_MySites" | "XF86MySites" => Some(XF86XK_MySites), 1136 | "XF86XK_New" | "XF86New" => Some(XF86XK_New), 1137 | "XF86XK_News" | "XF86News" => Some(XF86XK_News), 1138 | "XF86XK_OfficeHome" | "XF86OfficeHome" => Some(XF86XK_OfficeHome), 1139 | "XF86XK_Open" | "XF86Open" => Some(XF86XK_Open), 1140 | "XF86XK_Option" | "XF86Option" => Some(XF86XK_Option), 1141 | "XF86XK_Paste" | "XF86Paste" => Some(XF86XK_Paste), 1142 | "XF86XK_Phone" | "XF86Phone" => Some(XF86XK_Phone), 1143 | "XF86XK_Q" | "XF86Q" => Some(XF86XK_Q), 1144 | "XF86XK_Reply" | "XF86Reply" => Some(XF86XK_Reply), 1145 | "XF86XK_Reload" | "XF86Reload" => Some(XF86XK_Reload), 1146 | "XF86XK_RotateWindows" | "XF86RotateWindows" => Some(XF86XK_RotateWindows), 1147 | "XF86XK_RotationPB" | "XF86RotationPB" => Some(XF86XK_RotationPB), 1148 | "XF86XK_RotationKB" | "XF86RotationKB" => Some(XF86XK_RotationKB), 1149 | "XF86XK_Save" | "XF86Save" => Some(XF86XK_Save), 1150 | "XF86XK_ScrollUp" | "XF86ScrollUp" => Some(XF86XK_ScrollUp), 1151 | "XF86XK_ScrollDown" | "XF86ScrollDown" => Some(XF86XK_ScrollDown), 1152 | "XF86XK_ScrollClick" | "XF86ScrollClick" => Some(XF86XK_ScrollClick), 1153 | "XF86XK_Send" | "XF86Send" => Some(XF86XK_Send), 1154 | "XF86XK_Spell" | "XF86Spell" => Some(XF86XK_Spell), 1155 | "XF86XK_SplitScreen" | "XF86SplitScreen" => Some(XF86XK_SplitScreen), 1156 | "XF86XK_Support" | "XF86Support" => Some(XF86XK_Support), 1157 | "XF86XK_TaskPane" | "XF86TaskPane" => Some(XF86XK_TaskPane), 1158 | "XF86XK_Terminal" | "XF86Terminal" => Some(XF86XK_Terminal), 1159 | "XF86XK_Tools" | "XF86Tools" => Some(XF86XK_Tools), 1160 | "XF86XK_Travel" | "XF86Travel" => Some(XF86XK_Travel), 1161 | "XF86XK_UserPB" | "XF86UserPB" => Some(XF86XK_UserPB), 1162 | "XF86XK_User1KB" | "XF86User1KB" => Some(XF86XK_User1KB), 1163 | "XF86XK_User2KB" | "XF86User2KB" => Some(XF86XK_User2KB), 1164 | "XF86XK_Video" | "XF86Video" => Some(XF86XK_Video), 1165 | "XF86XK_WheelButton" | "XF86WheelButton" => Some(XF86XK_WheelButton), 1166 | "XF86XK_Word" | "XF86Word" => Some(XF86XK_Word), 1167 | "XF86XK_Xfer" | "XF86Xfer" => Some(XF86XK_Xfer), 1168 | "XF86XK_ZoomIn" | "XF86ZoomIn" => Some(XF86XK_ZoomIn), 1169 | "XF86XK_ZoomOut" | "XF86ZoomOut" => Some(XF86XK_ZoomOut), 1170 | "XF86XK_Away" | "XF86Away" => Some(XF86XK_Away), 1171 | "XF86XK_Messenger" | "XF86Messenger" => Some(XF86XK_Messenger), 1172 | "XF86XK_WebCam" | "XF86WebCam" => Some(XF86XK_WebCam), 1173 | "XF86XK_MailForward" | "XF86MailForward" => Some(XF86XK_MailForward), 1174 | "XF86XK_Pictures" | "XF86Pictures" => Some(XF86XK_Pictures), 1175 | "XF86XK_Music" | "XF86Music" => Some(XF86XK_Music), 1176 | "XF86XK_Battery" | "XF86Battery" => Some(XF86XK_Battery), 1177 | "XF86XK_Bluetooth" | "XF86Bluetooth" => Some(XF86XK_Bluetooth), 1178 | "XF86XK_WLAN" | "XF86WLAN" => Some(XF86XK_WLAN), 1179 | "XF86XK_UWB" | "XF86UWB" => Some(XF86XK_UWB), 1180 | "XF86XK_AudioForward" | "XF86AudioForward" => Some(XF86XK_AudioForward), 1181 | "XF86XK_AudioRepeat" | "XF86AudioRepeat" => Some(XF86XK_AudioRepeat), 1182 | "XF86XK_AudioRandomPlay" | "XF86AudioRandomPlay" => Some(XF86XK_AudioRandomPlay), 1183 | "XF86XK_Subtitle" | "XF86Subtitle" => Some(XF86XK_Subtitle), 1184 | "XF86XK_AudioCycleTrack" | "XF86AudioCycleTrack" => Some(XF86XK_AudioCycleTrack), 1185 | "XF86XK_CycleAngle" | "XF86CycleAngle" => Some(XF86XK_CycleAngle), 1186 | "XF86XK_FrameBack" | "XF86FrameBack" => Some(XF86XK_FrameBack), 1187 | "XF86XK_FrameForward" | "XF86FrameForward" => Some(XF86XK_FrameForward), 1188 | "XF86XK_Time" | "XF86Time" => Some(XF86XK_Time), 1189 | "XF86XK_Select" | "XF86Select" => Some(XF86XK_Select), 1190 | "XF86XK_View" | "XF86View" => Some(XF86XK_View), 1191 | "XF86XK_TopMenu" | "XF86TopMenu" => Some(XF86XK_TopMenu), 1192 | "XF86XK_Red" | "XF86Red" => Some(XF86XK_Red), 1193 | "XF86XK_Green" | "XF86Green" => Some(XF86XK_Green), 1194 | "XF86XK_Yellow" | "XF86Yellow" => Some(XF86XK_Yellow), 1195 | "XF86XK_Blue" | "XF86Blue" => Some(XF86XK_Blue), 1196 | "XF86XK_Suspend" | "XF86Suspend" => Some(XF86XK_Suspend), 1197 | "XF86XK_Hibernate" | "XF86Hibernate" => Some(XF86XK_Hibernate), 1198 | "XF86XK_TouchpadToggle" | "XF86TouchpadToggle" => Some(XF86XK_TouchpadToggle), 1199 | "XF86XK_TouchpadOn" | "XF86TouchpadOn" => Some(XF86XK_TouchpadOn), 1200 | "XF86XK_TouchpadOff" | "XF86TouchpadOff" => Some(XF86XK_TouchpadOff), 1201 | "XF86XK_AudioMicMute" | "XF86AudioMicMute" => Some(XF86XK_AudioMicMute), 1202 | "XF86XK_Switch_VT_1" | "XF86Switch_VT_1" => Some(XF86XK_Switch_VT_1), 1203 | "XF86XK_Switch_VT_2" | "XF86Switch_VT_2" => Some(XF86XK_Switch_VT_2), 1204 | "XF86XK_Switch_VT_3" | "XF86Switch_VT_3" => Some(XF86XK_Switch_VT_3), 1205 | "XF86XK_Switch_VT_4" | "XF86Switch_VT_4" => Some(XF86XK_Switch_VT_4), 1206 | "XF86XK_Switch_VT_5" | "XF86Switch_VT_5" => Some(XF86XK_Switch_VT_5), 1207 | "XF86XK_Switch_VT_6" | "XF86Switch_VT_6" => Some(XF86XK_Switch_VT_6), 1208 | "XF86XK_Switch_VT_7" | "XF86Switch_VT_7" => Some(XF86XK_Switch_VT_7), 1209 | "XF86XK_Switch_VT_8" | "XF86Switch_VT_8" => Some(XF86XK_Switch_VT_8), 1210 | "XF86XK_Switch_VT_9" | "XF86Switch_VT_9" => Some(XF86XK_Switch_VT_9), 1211 | "XF86XK_Switch_VT_10" | "XF86Switch_VT_10" => Some(XF86XK_Switch_VT_10), 1212 | "XF86XK_Switch_VT_11" | "XF86Switch_VT_11" => Some(XF86XK_Switch_VT_11), 1213 | "XF86XK_Switch_VT_12" | "XF86Switch_VT_12" => Some(XF86XK_Switch_VT_12), 1214 | "XF86XK_Ungrab" | "XF86Ungrab" => Some(XF86XK_Ungrab), 1215 | "XF86XK_ClearGrab" | "XF86ClearGrab" => Some(XF86XK_ClearGrab), 1216 | "XF86XK_Next_VMode" | "XF86Next_VMode" => Some(XF86XK_Next_VMode), 1217 | "XF86XK_Prev_VMode" | "XF86Prev_VMode" => Some(XF86XK_Prev_VMode), 1218 | "XF86XK_LogWindowTree" | "XF86LogWindowTree" => Some(XF86XK_LogWindowTree), 1219 | "XF86XK_LogGrabInfo" | "XF86LogGrabInfo" => Some(XF86XK_LogGrabInfo), 1220 | "ISO_Lock" => Some(XK_ISO_Lock), 1221 | "ISO_Level2_Latch" => Some(XK_ISO_Level2_Latch), 1222 | "ISO_Level3_Shift" => Some(XK_ISO_Level3_Shift), 1223 | "ISO_Level3_Latch" => Some(XK_ISO_Level3_Latch), 1224 | "ISO_Level3_Lock" => Some(XK_ISO_Level3_Lock), 1225 | "ISO_Level5_Shift" => Some(XK_ISO_Level5_Shift), 1226 | "ISO_Level5_Latch" => Some(XK_ISO_Level5_Latch), 1227 | "ISO_Level5_Lock" => Some(XK_ISO_Level5_Lock), 1228 | "ISO_Group_Shift" => Some(XK_ISO_Group_Shift), 1229 | "ISO_Group_Latch" => Some(XK_ISO_Group_Latch), 1230 | "ISO_Group_Lock" => Some(XK_ISO_Group_Lock), 1231 | "ISO_Next_Group" => Some(XK_ISO_Next_Group), 1232 | "ISO_Next_Group_Lock" => Some(XK_ISO_Next_Group_Lock), 1233 | "ISO_Prev_Group" => Some(XK_ISO_Prev_Group), 1234 | "ISO_Prev_Group_Lock" => Some(XK_ISO_Prev_Group_Lock), 1235 | "ISO_First_Group" => Some(XK_ISO_First_Group), 1236 | "ISO_First_Group_Lock" => Some(XK_ISO_First_Group_Lock), 1237 | "ISO_Last_Group" => Some(XK_ISO_Last_Group), 1238 | "ISO_Last_Group_Lock" => Some(XK_ISO_Last_Group_Lock), 1239 | "ISO_Left_Tab" => Some(XK_ISO_Left_Tab), 1240 | "ISO_Move_Line_Up" => Some(XK_ISO_Move_Line_Up), 1241 | "ISO_Move_Line_Down" => Some(XK_ISO_Move_Line_Down), 1242 | "ISO_Partial_Line_Up" => Some(XK_ISO_Partial_Line_Up), 1243 | "ISO_Partial_Line_Down" => Some(XK_ISO_Partial_Line_Down), 1244 | "ISO_Partial_Space_Left" => Some(XK_ISO_Partial_Space_Left), 1245 | "ISO_Partial_Space_Right" => Some(XK_ISO_Partial_Space_Right), 1246 | "ISO_Set_Margin_Left" => Some(XK_ISO_Set_Margin_Left), 1247 | "ISO_Set_Margin_Right" => Some(XK_ISO_Set_Margin_Right), 1248 | "ISO_Release_Margin_Left" => Some(XK_ISO_Release_Margin_Left), 1249 | "ISO_Release_Margin_Right" => Some(XK_ISO_Release_Margin_Right), 1250 | "ISO_Release_Both_Margins" => Some(XK_ISO_Release_Both_Margins), 1251 | "ISO_Fast_Cursor_Left" => Some(XK_ISO_Fast_Cursor_Left), 1252 | "ISO_Fast_Cursor_Right" => Some(XK_ISO_Fast_Cursor_Right), 1253 | "ISO_Fast_Cursor_Up" => Some(XK_ISO_Fast_Cursor_Up), 1254 | "ISO_Fast_Cursor_Down" => Some(XK_ISO_Fast_Cursor_Down), 1255 | "ISO_Continuous_Underline" => Some(XK_ISO_Continuous_Underline), 1256 | "ISO_Discontinuous_Underline" => Some(XK_ISO_Discontinuous_Underline), 1257 | "ISO_Emphasize" => Some(XK_ISO_Emphasize), 1258 | "ISO_Center_Object" => Some(XK_ISO_Center_Object), 1259 | "ISO_Enter" => Some(XK_ISO_Enter), 1260 | "dead_grave" => Some(XK_dead_grave), 1261 | "dead_acute" => Some(XK_dead_acute), 1262 | "dead_circumflex" => Some(XK_dead_circumflex), 1263 | "dead_tilde" => Some(XK_dead_tilde), 1264 | "dead_perispomeni" => Some(XK_dead_perispomeni), 1265 | "dead_macron" => Some(XK_dead_macron), 1266 | "dead_breve" => Some(XK_dead_breve), 1267 | "dead_abovedot" => Some(XK_dead_abovedot), 1268 | "dead_diaeresis" => Some(XK_dead_diaeresis), 1269 | "dead_abovering" => Some(XK_dead_abovering), 1270 | "dead_doubleacute" => Some(XK_dead_doubleacute), 1271 | "dead_caron" => Some(XK_dead_caron), 1272 | "dead_cedilla" => Some(XK_dead_cedilla), 1273 | "dead_ogonek" => Some(XK_dead_ogonek), 1274 | "dead_iota" => Some(XK_dead_iota), 1275 | "dead_voiced_sound" => Some(XK_dead_voiced_sound), 1276 | "dead_semivoiced_sound" => Some(XK_dead_semivoiced_sound), 1277 | "dead_belowdot" => Some(XK_dead_belowdot), 1278 | "dead_hook" => Some(XK_dead_hook), 1279 | "dead_horn" => Some(XK_dead_horn), 1280 | "dead_stroke" => Some(XK_dead_stroke), 1281 | "dead_abovecomma" => Some(XK_dead_abovecomma), 1282 | "dead_psili" => Some(XK_dead_psili), 1283 | "dead_abovereversedcomma" => Some(XK_dead_abovereversedcomma), 1284 | "dead_dasia" => Some(XK_dead_dasia), 1285 | "dead_doublegrave" => Some(XK_dead_doublegrave), 1286 | "dead_belowring" => Some(XK_dead_belowring), 1287 | "dead_belowmacron" => Some(XK_dead_belowmacron), 1288 | "dead_belowcircumflex" => Some(XK_dead_belowcircumflex), 1289 | "dead_belowtilde" => Some(XK_dead_belowtilde), 1290 | "dead_belowbreve" => Some(XK_dead_belowbreve), 1291 | "dead_belowdiaeresis" => Some(XK_dead_belowdiaeresis), 1292 | "dead_invertedbreve" => Some(XK_dead_invertedbreve), 1293 | "dead_belowcomma" => Some(XK_dead_belowcomma), 1294 | "dead_currency" => Some(XK_dead_currency), 1295 | "dead_lowline" => Some(XK_dead_lowline), 1296 | "dead_aboveverticalline" => Some(XK_dead_aboveverticalline), 1297 | "dead_belowverticalline" => Some(XK_dead_belowverticalline), 1298 | "dead_longsolidusoverlay" => Some(XK_dead_longsolidusoverlay), 1299 | "dead_a" => Some(XK_dead_a), 1300 | "dead_A" => Some(XK_dead_A), 1301 | "dead_e" => Some(XK_dead_e), 1302 | "dead_E" => Some(XK_dead_E), 1303 | "dead_i" => Some(XK_dead_i), 1304 | "dead_I" => Some(XK_dead_I), 1305 | "dead_o" => Some(XK_dead_o), 1306 | "dead_O" => Some(XK_dead_O), 1307 | "dead_u" => Some(XK_dead_u), 1308 | "dead_U" => Some(XK_dead_U), 1309 | "dead_small_schwa" => Some(XK_dead_small_schwa), 1310 | "dead_capital_schwa" => Some(XK_dead_capital_schwa), 1311 | "dead_greek" => Some(XK_dead_greek), 1312 | "First_Virtual_Screen" => Some(XK_First_Virtual_Screen), 1313 | "Prev_Virtual_Screen" => Some(XK_Prev_Virtual_Screen), 1314 | "Next_Virtual_Screen" => Some(XK_Next_Virtual_Screen), 1315 | "Last_Virtual_Screen" => Some(XK_Last_Virtual_Screen), 1316 | "Terminate_Server" => Some(XK_Terminate_Server), 1317 | "AccessX_Enable" => Some(XK_AccessX_Enable), 1318 | "AccessX_Feedback_Enable" => Some(XK_AccessX_Feedback_Enable), 1319 | "RepeatKeys_Enable" => Some(XK_RepeatKeys_Enable), 1320 | "SlowKeys_Enable" => Some(XK_SlowKeys_Enable), 1321 | "BounceKeys_Enable" => Some(XK_BounceKeys_Enable), 1322 | "StickyKeys_Enable" => Some(XK_StickyKeys_Enable), 1323 | "MouseKeys_Enable" => Some(XK_MouseKeys_Enable), 1324 | "MouseKeys_Accel_Enable" => Some(XK_MouseKeys_Accel_Enable), 1325 | "Overlay1_Enable" => Some(XK_Overlay1_Enable), 1326 | "Overlay2_Enable" => Some(XK_Overlay2_Enable), 1327 | "AudibleBell_Enable" => Some(XK_AudibleBell_Enable), 1328 | "Pointer_Left" => Some(XK_Pointer_Left), 1329 | "Pointer_Right" => Some(XK_Pointer_Right), 1330 | "Pointer_Up" => Some(XK_Pointer_Up), 1331 | "Pointer_Down" => Some(XK_Pointer_Down), 1332 | "Pointer_UpLeft" => Some(XK_Pointer_UpLeft), 1333 | "Pointer_UpRight" => Some(XK_Pointer_UpRight), 1334 | "Pointer_DownLeft" => Some(XK_Pointer_DownLeft), 1335 | "Pointer_DownRight" => Some(XK_Pointer_DownRight), 1336 | "Pointer_Button_Dflt" => Some(XK_Pointer_Button_Dflt), 1337 | "Pointer_Button1" => Some(XK_Pointer_Button1), 1338 | "Pointer_Button2" => Some(XK_Pointer_Button2), 1339 | "Pointer_Button3" => Some(XK_Pointer_Button3), 1340 | "Pointer_Button4" => Some(XK_Pointer_Button4), 1341 | "Pointer_Button5" => Some(XK_Pointer_Button5), 1342 | "Pointer_DblClick_Dflt" => Some(XK_Pointer_DblClick_Dflt), 1343 | "Pointer_DblClick1" => Some(XK_Pointer_DblClick1), 1344 | "Pointer_DblClick2" => Some(XK_Pointer_DblClick2), 1345 | "Pointer_DblClick3" => Some(XK_Pointer_DblClick3), 1346 | "Pointer_DblClick4" => Some(XK_Pointer_DblClick4), 1347 | "Pointer_DblClick5" => Some(XK_Pointer_DblClick5), 1348 | "Pointer_Drag_Dflt" => Some(XK_Pointer_Drag_Dflt), 1349 | "Pointer_Drag1" => Some(XK_Pointer_Drag1), 1350 | "Pointer_Drag2" => Some(XK_Pointer_Drag2), 1351 | "Pointer_Drag3" => Some(XK_Pointer_Drag3), 1352 | "Pointer_Drag4" => Some(XK_Pointer_Drag4), 1353 | "Pointer_Drag5" => Some(XK_Pointer_Drag5), 1354 | "Pointer_EnableKeys" => Some(XK_Pointer_EnableKeys), 1355 | "Pointer_Accelerate" => Some(XK_Pointer_Accelerate), 1356 | "Pointer_DfltBtnNext" => Some(XK_Pointer_DfltBtnNext), 1357 | "Pointer_DfltBtnPrev" => Some(XK_Pointer_DfltBtnPrev), 1358 | "ch" => Some(XK_ch), 1359 | "Ch" => Some(XK_Ch), 1360 | "CH" => Some(XK_CH), 1361 | "c_h" => Some(XK_c_h), 1362 | "C_h" => Some(XK_C_h), 1363 | "C_H" => Some(XK_C_H), 1364 | _ => None, 1365 | } 1366 | } 1367 | -------------------------------------------------------------------------------- /lefthk-core/src/xwrap.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Keybind; 2 | use crate::errors::{self, Error, LeftError}; 3 | use crate::xkeysym_lookup; 4 | use std::future::Future; 5 | use std::os::raw::{c_int, c_ulong}; 6 | use std::pin::Pin; 7 | use std::ptr; 8 | use std::sync::Arc; 9 | use tokio::sync::{oneshot, Notify}; 10 | use tokio::time::Duration; 11 | use x11_dl::xlib; 12 | 13 | pub struct XWrap { 14 | pub xlib: xlib::Xlib, 15 | pub display: *mut xlib::Display, 16 | pub root: xlib::Window, 17 | pub task_notify: Arc, 18 | _task_guard: oneshot::Receiver<()>, 19 | } 20 | 21 | impl Default for XWrap { 22 | fn default() -> Self { 23 | Self::new() 24 | } 25 | } 26 | 27 | impl XWrap { 28 | /// # Panics 29 | /// 30 | /// Panics if unable to contact xorg. 31 | #[must_use] 32 | #[allow(clippy::items_after_statements)] 33 | pub fn new() -> Self { 34 | const SERVER: mio::Token = mio::Token(0); 35 | let xlib = errors::exit_on_error!(xlib::Xlib::open()); 36 | let display = unsafe { (xlib.XOpenDisplay)(ptr::null()) }; 37 | assert!(!display.is_null(), "Null pointer in display"); 38 | 39 | let fd = unsafe { (xlib.XConnectionNumber)(display) }; 40 | let (guard, task_guard) = oneshot::channel(); 41 | let notify = Arc::new(Notify::new()); 42 | let task_notify = notify.clone(); 43 | let mut poll = errors::exit_on_error!(mio::Poll::new()); 44 | let mut events = mio::Events::with_capacity(1); 45 | errors::exit_on_error!(poll.registry().register( 46 | &mut mio::unix::SourceFd(&fd), 47 | SERVER, 48 | mio::Interest::READABLE, 49 | )); 50 | let timeout = Duration::from_millis(100); 51 | tokio::task::spawn_blocking(move || loop { 52 | if guard.is_closed() { 53 | return; 54 | } 55 | 56 | if let Err(err) = poll.poll(&mut events, Some(timeout)) { 57 | tracing::warn!("Xlib socket poll failed with {:?}", err); 58 | continue; 59 | } 60 | 61 | events 62 | .iter() 63 | .filter(|event| SERVER == event.token()) 64 | .for_each(|_| notify.notify_one()); 65 | }); 66 | let root = unsafe { (xlib.XDefaultRootWindow)(display) }; 67 | 68 | let xw = Self { 69 | xlib, 70 | display, 71 | root, 72 | task_notify, 73 | _task_guard: task_guard, 74 | }; 75 | 76 | // Setup cached keymap/modifier information, otherwise MappingNotify might never be called 77 | // from: 78 | // https://stackoverflow.com/questions/35569562/how-to-catch-keyboard-layout-change-event-and-get-current-new-keyboard-layout-on 79 | xw.keysym_to_keycode(x11_dl::keysym::XK_F1); 80 | 81 | // This is allowed for now as const extern fns 82 | // are not yet stable (1.56.0, 16 Sept 2021) 83 | // see issue #64926 for more information 84 | // also this is the reason for #[allow(clippy::items_after_statements)] above 85 | #[allow(clippy::missing_const_for_fn)] 86 | extern "C" fn on_error_from_xlib( 87 | _: *mut xlib::Display, 88 | er: *mut xlib::XErrorEvent, 89 | ) -> c_int { 90 | let err = unsafe { *er }; 91 | //ignore bad window errors 92 | if err.error_code == xlib::BadWindow { 93 | return 0; 94 | } 95 | 1 96 | } 97 | unsafe { 98 | (xw.xlib.XSetErrorHandler)(Some(on_error_from_xlib)); 99 | (xw.xlib.XSync)(xw.display, xlib::False); 100 | }; 101 | xw 102 | } 103 | 104 | /// Shutdown connections to the xserver. 105 | pub fn shutdown(&self) { 106 | unsafe { 107 | (self.xlib.XUngrabKey)(self.display, xlib::AnyKey, xlib::AnyModifier, self.root); 108 | (self.xlib.XCloseDisplay)(self.display); 109 | } 110 | } 111 | 112 | /// Grabs a list of keybindings. 113 | pub fn grab_keys(&self, keybinds: &[Keybind]) { 114 | // Cleanup key grabs. 115 | unsafe { 116 | (self.xlib.XUngrabKey)(self.display, xlib::AnyKey, xlib::AnyModifier, self.root); 117 | } 118 | 119 | // Grab all the key combos from the config file. 120 | for kb in keybinds { 121 | if let Some(keysym) = xkeysym_lookup::into_keysym(&kb.key) { 122 | let modmask = xkeysym_lookup::into_modmask(&kb.modifier); 123 | self.grab_key(self.root, keysym, modmask); 124 | } 125 | } 126 | } 127 | 128 | /// Grabs the keysym with the modifier for a window. 129 | pub fn grab_key(&self, root: xlib::Window, keysym: u32, modifiers: u32) { 130 | let code = unsafe { (self.xlib.XKeysymToKeycode)(self.display, c_ulong::from(keysym)) }; 131 | // Grab the keys with and without numlock (Mod2). 132 | let mods: Vec = vec![ 133 | modifiers, 134 | modifiers | xlib::Mod2Mask, 135 | modifiers | xlib::LockMask, 136 | ]; 137 | for m in mods { 138 | unsafe { 139 | (self.xlib.XGrabKey)( 140 | self.display, 141 | i32::from(code), 142 | m, 143 | root, 144 | 1, 145 | xlib::GrabModeAsync, 146 | xlib::GrabModeAsync, 147 | ); 148 | } 149 | } 150 | } 151 | 152 | /// Updates the keyboard mapping. 153 | /// # Errors 154 | /// 155 | /// Will error if updating the keyboard failed. 156 | pub fn refresh_keyboard(&self, evt: &mut xlib::XMappingEvent) -> Error { 157 | let status = unsafe { (self.xlib.XRefreshKeyboardMapping)(evt) }; 158 | if status == 0 { 159 | Err(LeftError::XFailedStatus) 160 | } else { 161 | Ok(()) 162 | } 163 | } 164 | 165 | /// Converts a keycode to a keysym. 166 | #[must_use] 167 | pub fn keycode_to_keysym(&self, keycode: u32) -> xkeysym_lookup::XKeysym { 168 | // Not using XKeysymToKeycode because deprecated. 169 | let sym = unsafe { (self.xlib.XkbKeycodeToKeysym)(self.display, keycode as u8, 0, 0) }; 170 | sym as u32 171 | } 172 | 173 | /// Converts a keysym to a keycode. 174 | pub fn keysym_to_keycode(&self, keysym: xkeysym_lookup::XKeysym) -> u32 { 175 | let code = unsafe { (self.xlib.XKeysymToKeycode)(self.display, keysym.into()) }; 176 | u32::from(code) 177 | } 178 | 179 | /// Returns the next `Xevent` of the xserver. 180 | #[must_use] 181 | pub fn get_next_event(&self) -> xlib::XEvent { 182 | unsafe { 183 | let mut event: xlib::XEvent = std::mem::zeroed(); 184 | (self.xlib.XNextEvent)(self.display, &mut event); 185 | event 186 | } 187 | } 188 | 189 | /// Returns how many events are waiting. 190 | #[must_use] 191 | pub fn queue_len(&self) -> i32 { 192 | unsafe { (self.xlib.XPending)(self.display) } 193 | } 194 | 195 | pub fn flush(&self) { 196 | unsafe { (self.xlib.XFlush)(self.display) }; 197 | } 198 | 199 | pub fn wait_readable(&mut self) -> Pin>> { 200 | let task_notify = self.task_notify.clone(); 201 | Box::pin(async move { 202 | task_notify.notified().await; 203 | }) 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /lefthk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lefthk" 3 | version = "0.2.2" 4 | edition = "2021" 5 | license = "BSD-3-Clause" 6 | readme = "README.md" 7 | repository = "https://github.com/leftwm/lefthk" 8 | description = "A hotkey daemon for Adventurers" 9 | 10 | [dependencies] 11 | clap = {version = "3.2.20", features = ["cargo"]} 12 | lefthk-core = { path = "../lefthk-core", version = '0.2' } 13 | ron = "0.8" 14 | serde = { version = "1.0", features = ["derive"] } 15 | thiserror = "1.0.30" 16 | tokio = { version = "1.14.0", features = ["rt-multi-thread"] } 17 | xdg = "2.4.0" 18 | 19 | # logging 20 | tracing = "0.1.36" 21 | tracing-subscriber = {version = "0.3.15", features = ["env-filter"]} 22 | 23 | [dev-dependencies] 24 | tempfile = "3.2.0" 25 | -------------------------------------------------------------------------------- /lefthk/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /lefthk/src/config/command.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::config::keybind::Keybind; 4 | 5 | #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] 6 | pub enum Command { 7 | Chord(Vec), 8 | Execute(String), 9 | Executes(Vec), 10 | ExitChord, 11 | Reload, 12 | Kill, 13 | } 14 | -------------------------------------------------------------------------------- /lefthk/src/config/key.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] 4 | pub enum Key { 5 | Key(String), 6 | Keys(Vec), 7 | } 8 | -------------------------------------------------------------------------------- /lefthk/src/config/keybind.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::{LeftError, Result}; 2 | use lefthk_core::config::{ 3 | command as command_mod, Command as core_command, Keybind as core_keybind, 4 | }; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::config::{command::Command, key::Key}; 8 | 9 | macro_rules! get_key { 10 | ($expr:expr $(,)?) => { 11 | match $expr { 12 | Key::Key(key) => key, 13 | Key::Keys(_) => return Err(LeftError::SingleKeyNeeded), 14 | } 15 | }; 16 | } 17 | 18 | macro_rules! get_keys { 19 | ($expr:expr $(,)?) => { 20 | match $expr { 21 | Key::Key(_) => return Err(LeftError::MultipleKeysNeeded), 22 | Key::Keys(keys) => keys, 23 | } 24 | }; 25 | } 26 | 27 | pub type Keybinds = Vec; 28 | 29 | #[derive(Debug, PartialEq, Clone, Eq, Serialize, Deserialize)] 30 | pub struct Keybind { 31 | pub command: Command, 32 | pub modifier: Option>, 33 | pub key: Key, 34 | } 35 | 36 | pub(crate) fn try_from(kb: Keybind, default_modifier: &[String]) -> Result> { 37 | let command_key_pairs: Vec<(Box, String)> = match kb.command { 38 | Command::Chord(children) if !children.is_empty() => { 39 | let key = get_key!(kb.key); 40 | let children = children 41 | .iter() 42 | .filter_map(|kb| match try_from(kb.clone(), default_modifier) { 43 | Ok(keybinds) => Some::>(keybinds), 44 | Err(err) => { 45 | tracing::error!("Invalid key binding: {}\n{:?}", err, kb); 46 | None 47 | } 48 | }) 49 | .flatten() 50 | .collect(); 51 | 52 | vec![(Box::new(command_mod::Chord::new(children)), key)] 53 | } 54 | Command::Chord(_) => return Err(LeftError::ChildrenNotFound), 55 | Command::Execute(value) if !value.is_empty() => { 56 | let keys = get_key!(kb.key); 57 | vec![((Box::new(command_mod::Execute::new(&value))), keys)] 58 | } 59 | Command::Execute(_) => return Err(LeftError::ValueNotFound), 60 | Command::Executes(values) if !values.is_empty() => { 61 | let keys = get_keys!(kb.key); 62 | if keys.len() != values.len() { 63 | return Err(LeftError::NumberOfKeysDiffersFromValues); 64 | } 65 | values 66 | .iter() 67 | .enumerate() 68 | .map(|(i, v)| { 69 | ( 70 | Box::new(command_mod::Execute::new(&v)) as Box, 71 | keys[i].clone(), 72 | ) 73 | }) 74 | .collect() 75 | } 76 | Command::Executes(_) => return Err(LeftError::ValuesNotFound), 77 | Command::ExitChord => { 78 | let keys = get_key!(kb.key); 79 | vec![((Box::new(command_mod::ExitChord::new())), keys)] 80 | } 81 | Command::Reload => { 82 | let keys = get_key!(kb.key); 83 | vec![((Box::new(command_mod::Reload::new())), keys)] 84 | } 85 | Command::Kill => { 86 | let keys = get_key!(kb.key); 87 | vec![((Box::new(command_mod::Kill::new())), keys)] 88 | } 89 | }; 90 | let keybinds = command_key_pairs 91 | .iter() 92 | .map(|(c, k)| core_keybind { 93 | command: c.normalize(), 94 | modifier: kb 95 | .modifier 96 | .clone() 97 | .unwrap_or_else(|| default_modifier.to_vec()), 98 | key: k.clone(), 99 | }) 100 | .collect(); 101 | Ok(keybinds) 102 | } 103 | -------------------------------------------------------------------------------- /lefthk/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod command; 2 | pub mod key; 3 | pub mod keybind; 4 | 5 | use crate::errors::{LeftError, Result}; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | use std::{fs, path::Path}; 9 | use xdg::BaseDirectories; 10 | 11 | use self::{ 12 | command::Command, 13 | keybind::{Keybind, Keybinds}, 14 | }; 15 | 16 | #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] 17 | pub struct Config { 18 | pub(crate) default_modifier: Vec, 19 | keybinds: Keybinds, 20 | } 21 | 22 | impl lefthk_core::config::Config for Config { 23 | fn mapped_bindings(&self) -> Vec { 24 | self.keybinds 25 | .iter() 26 | .filter_map( 27 | |kb| match keybind::try_from(kb.clone(), &self.default_modifier.clone()) { 28 | Ok(keybinds) => Some::>(keybinds), 29 | Err(err) => { 30 | tracing::error!("Invalid key binding: {}\n{:?}", err, kb); 31 | None 32 | } 33 | }, 34 | ) 35 | .flatten() 36 | .collect() 37 | } 38 | } 39 | 40 | impl TryFrom for Config { 41 | type Error = LeftError; 42 | /// # Errors 43 | /// 44 | /// Thes will error when no config file is found, most propably as system or 45 | /// user error for provideng a wrong path 46 | fn try_from(contents: String) -> Result { 47 | let mut config: Config = ron::from_str(&contents)?; 48 | let global_exit_chord = config 49 | .keybinds 50 | .iter() 51 | .find(|kb| matches!(kb.command, Command::ExitChord)) 52 | .cloned(); 53 | let chords: Vec<&mut Keybind> = config 54 | .keybinds 55 | .iter_mut() 56 | .filter(|kb| matches!(kb.command, Command::Chord(_))) 57 | .collect(); 58 | propagate_exit_chord(chords, &global_exit_chord); 59 | 60 | Ok(config) 61 | } 62 | } 63 | 64 | /// # Errors 65 | /// 66 | /// This errors, when no Config is found at the path 67 | pub fn load() -> Result { 68 | let path = BaseDirectories::with_prefix(lefthk_core::LEFTHK_DIR_NAME)?; 69 | fs::create_dir_all(path.get_config_home())?; 70 | let file_name = path.place_config_file("config.ron")?; 71 | if !Path::new(&file_name).exists() { 72 | return Err(LeftError::NoConfigFound); 73 | } 74 | let contents = fs::read_to_string(file_name)?; 75 | Config::try_from(contents) 76 | } 77 | 78 | fn propagate_exit_chord(chords: Vec<&mut Keybind>, exit_chord: &Option) { 79 | for chord in chords { 80 | if let Command::Chord(children) = &mut chord.command { 81 | if !children.iter().any(|kb| kb.command == Command::ExitChord) { 82 | if let Some(ref exit_chord) = exit_chord { 83 | children.push(exit_chord.clone()); 84 | } 85 | } 86 | let parent_exit_chord = children 87 | .iter() 88 | .find(|kb| matches!(kb.command, Command::ExitChord)) 89 | .cloned(); 90 | let sub_chords = children 91 | .iter_mut() 92 | .filter(|kb| matches!(kb.command, Command::Chord(_))) 93 | .collect(); 94 | propagate_exit_chord(sub_chords, &parent_exit_chord); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lefthk/src/errors.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | macro_rules! return_on_error { 4 | ($a: expr) => { 5 | match $a { 6 | Ok(value) => value, 7 | Err(err) => { 8 | tracing::error!("Returning due to error: {}", LeftError::from(err)); 9 | return; 10 | } 11 | } 12 | }; 13 | } 14 | 15 | macro_rules! exit_on_error { 16 | ($a: expr) => { 17 | match $a { 18 | Ok(value) => value, 19 | Err(err) => { 20 | tracing::error!("Exiting due to error: {}", LeftError::from(err)); 21 | std::process::exit(1); 22 | } 23 | } 24 | }; 25 | } 26 | 27 | pub(crate) use exit_on_error; 28 | pub(crate) use return_on_error; 29 | 30 | pub type Result = std::result::Result; 31 | pub type Error = std::result::Result<(), LeftError>; 32 | 33 | #[derive(Debug, Error)] 34 | pub enum LeftError { 35 | #[error("IO error: {0}.")] 36 | IoError(#[from] std::io::Error), 37 | #[error("RON error: {0}.")] 38 | RonError(#[from] ron::error::Error), 39 | #[error("RON spanned error {0}.")] 40 | SpannedError(#[from] ron::error::SpannedError), 41 | #[error("XDG error: {0}.")] 42 | XdgBaseDirError(#[from] xdg::BaseDirectoriesError), 43 | 44 | #[error("No chrildren found for chord.")] 45 | ChildrenNotFound, 46 | #[error("No command found for keybind.")] 47 | CommandNotFound, 48 | #[error("No key found for keybind.")] 49 | KeyNotFound, 50 | #[error("No modifier found for keybind.")] 51 | ModifierNotFound, 52 | #[error("Command requires multiple keys.")] 53 | MultipleKeysNeeded, 54 | #[error("No config file found.")] 55 | NoConfigFound, 56 | #[error("The incorrect amount of keys is set for the number of values.")] 57 | NumberOfKeysDiffersFromValues, 58 | #[error("Command requires a single key.")] 59 | SingleKeyNeeded, 60 | #[error("No value set for execution.")] 61 | ValueNotFound, 62 | #[error("No values set for executions.")] 63 | ValuesNotFound, 64 | #[error("X failed status error.")] 65 | XFailedStatus, 66 | } 67 | -------------------------------------------------------------------------------- /lefthk/src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::LeftError; 2 | use clap::{App, Arg}; 3 | use lefthk_core::{ 4 | config::{command, Command, Config}, 5 | ipc::Pipe, 6 | worker::{Status, Worker}, 7 | }; 8 | use std::{ 9 | fs, 10 | io::Write, 11 | sync::atomic::{AtomicBool, Ordering}, 12 | }; 13 | use xdg::BaseDirectories; 14 | 15 | use tracing_subscriber::{filter::EnvFilter, filter::LevelFilter, fmt, layer::SubscriberExt}; 16 | 17 | pub mod config; 18 | pub mod errors; 19 | mod tests; 20 | 21 | const QUIT_COMMAND: &str = "quit"; 22 | const RELOAD_COMMAND: &str = "reload"; 23 | 24 | fn main() { 25 | setup_logging(); 26 | let app = get_app(); 27 | let matches = app.get_matches(); 28 | tracing::info!("lefthk booted!"); 29 | 30 | if matches.contains_id(QUIT_COMMAND) { 31 | send_command(&command::Kill::new()); 32 | } else if matches.contains_id(RELOAD_COMMAND) { 33 | send_command(&command::Reload::new()); 34 | } else { 35 | let mut old_config = None; 36 | let path = 37 | errors::exit_on_error!(BaseDirectories::with_prefix(lefthk_core::LEFTHK_DIR_NAME)); 38 | loop { 39 | let config = match config::load() { 40 | Ok(config) => config, 41 | Err(err) => { 42 | if let Some(config) = old_config { 43 | config 44 | } else { 45 | tracing::error!("Unable to load new config due to error: {}", err); 46 | return; 47 | } 48 | } 49 | }; 50 | let kill_requested = AtomicBool::new(false); 51 | let completed = std::panic::catch_unwind(|| { 52 | let rt = errors::return_on_error!(tokio::runtime::Runtime::new()); 53 | let _rt_guard = rt.enter(); 54 | 55 | let status = 56 | rt.block_on(Worker::new(config.mapped_bindings(), path.clone()).event_loop()); 57 | kill_requested.store(status == Status::Kill, Ordering::SeqCst); 58 | }); 59 | 60 | match completed { 61 | Ok(()) => tracing::info!("Completed"), 62 | Err(err) => tracing::error!("Completed with error: {:?}", err), 63 | } 64 | if kill_requested.load(Ordering::SeqCst) { 65 | return; 66 | } 67 | old_config = Some(config); 68 | } 69 | } 70 | } 71 | 72 | fn send_command(command: &impl Command) { 73 | let path = errors::exit_on_error!(BaseDirectories::with_prefix(lefthk_core::LEFTHK_DIR_NAME)); 74 | let pipe_name = Pipe::pipe_name(); 75 | let pipe_file = errors::exit_on_error!(path.place_runtime_file(pipe_name)); 76 | let mut pipe = fs::OpenOptions::new().write(true).open(pipe_file).unwrap(); 77 | writeln!(pipe, "{}", command.normalize()).unwrap(); 78 | } 79 | 80 | fn get_app() -> App<'static> { 81 | clap::command!() 82 | .arg( 83 | Arg::with_name(QUIT_COMMAND) 84 | .short('q') 85 | .long(QUIT_COMMAND) 86 | .help("Quit a running daemon instance"), 87 | ) 88 | .arg( 89 | Arg::with_name(RELOAD_COMMAND) 90 | .short('r') 91 | .long(RELOAD_COMMAND) 92 | .help("Reload daemon to apply changes to config"), 93 | ) 94 | } 95 | 96 | fn setup_logging() { 97 | let subscriber = fmt::Layer::new().with_writer(std::io::stdout); 98 | let log_level = EnvFilter::builder() 99 | .with_default_directive(LevelFilter::DEBUG.into()) 100 | .from_env_lossy(); 101 | 102 | let collector = tracing_subscriber::registry() 103 | .with(log_level) 104 | .with(subscriber); 105 | 106 | tracing::subscriber::set_global_default(collector).expect("Couldn't setup logging"); 107 | } 108 | -------------------------------------------------------------------------------- /lefthk/src/tests.rs: -------------------------------------------------------------------------------- 1 | /// Config Testing 2 | #[cfg(test)] 3 | mod config { 4 | use lefthk_core::config::command::utils::normalized_command::NormalizedCommand; 5 | use lefthk_core::config::Config; 6 | 7 | use crate::config::Config as Cfg; 8 | 9 | #[test] 10 | fn parse_config() { 11 | let config = r#"#![enable(implicit_some)] 12 | Config( 13 | default_modifier: ["Mod4", "Shift"], 14 | keybinds: [ 15 | Keybind( 16 | command: Execute("st -e htop"), 17 | key: Key("x"), 18 | ), 19 | Keybind( 20 | command: Execute("st -e btm"), 21 | modifier: ["Mod4"], 22 | key: Key("c"), 23 | ), 24 | ] 25 | )"#; 26 | let conf = Cfg::try_from(config.to_string()); 27 | assert!(conf.is_ok()); 28 | let conf = conf.unwrap(); 29 | assert_eq!(conf.default_modifier.len(), 2); 30 | assert_eq!( 31 | conf.default_modifier, 32 | vec!["Mod4".to_string(), "Shift".to_string()] 33 | ); 34 | let conf_mapped = conf.mapped_bindings(); 35 | 36 | // Verify default modifier implementation 37 | let default_keybind = conf_mapped.first().unwrap(); 38 | assert_eq!(default_keybind.modifier.len(), 2); 39 | assert_eq!(default_keybind.modifier, conf.default_modifier); 40 | 41 | // Verify own implementation 42 | let custom_keybind = conf_mapped.last().unwrap(); 43 | assert_eq!(custom_keybind.modifier.len(), 1); 44 | assert_eq!(custom_keybind.modifier, vec!["Mod4".to_string()]); 45 | } 46 | 47 | #[test] 48 | fn parse_empty_config() { 49 | let config = r#"Config( 50 | default_modifier: ["Mod4", "Shift"], 51 | keybinds: [] 52 | )"#; 53 | let conf = Cfg::try_from(config.to_string()); 54 | assert!(conf.is_ok()); 55 | let conf = conf.unwrap(); 56 | assert_eq!(conf.default_modifier.len(), 2); 57 | assert_eq!( 58 | conf.default_modifier, 59 | vec!["Mod4".to_string(), "Shift".to_string()] 60 | ); 61 | let conf_mapped = conf.mapped_bindings(); 62 | 63 | // Verify implementation 64 | assert_eq!(conf_mapped.len(), 0); 65 | } 66 | 67 | #[test] 68 | fn parse_none_config() { 69 | // Define empty string 70 | let conf = Cfg::try_from(String::new()); 71 | assert!(conf.is_err()); 72 | } 73 | 74 | #[test] 75 | fn parse_sub_keybind_config() { 76 | let config = r#"#![enable(implicit_some)] 77 | Config( 78 | default_modifier: ["Mod4", "Shift"], 79 | keybinds: [ 80 | Keybind( 81 | command: Chord([ 82 | Keybind( 83 | command: Execute("st -e htop"), 84 | modifier: ["Mod4"], 85 | key: Key("c"), 86 | ), 87 | ]), 88 | modifier: ["Mod4"], 89 | key: Key("c"), 90 | ), 91 | Keybind( 92 | command: Chord([ 93 | Keybind( 94 | command: Execute("st -e htop"), 95 | key: Key("c"), 96 | ), 97 | ]), 98 | key: Key("c"), 99 | ), 100 | ] 101 | )"#; 102 | let conf = Cfg::try_from(config.to_string()); 103 | assert!(conf.is_ok()); 104 | let conf = conf.unwrap(); 105 | assert_eq!(conf.default_modifier.len(), 2); 106 | assert_eq!( 107 | conf.default_modifier, 108 | vec!["Mod4".to_string(), "Shift".to_string()] 109 | ); 110 | let conf_mapped = conf.mapped_bindings(); 111 | 112 | // Verify default modifier implementation 113 | let default_keybind = conf_mapped.last().unwrap(); 114 | assert_eq!(default_keybind.modifier.len(), 2); 115 | assert_eq!(default_keybind.modifier, conf.default_modifier); 116 | assert_eq!( 117 | default_keybind.command, 118 | NormalizedCommand( 119 | r#"Chord([ 120 | Keybind( 121 | command: NormalizedCommand("Execute(\"st -e htop\")"), 122 | modifier: [ 123 | "Mod4", 124 | "Shift", 125 | ], 126 | key: "c", 127 | ), 128 | ])"# 129 | .to_string() 130 | ) 131 | ); 132 | 133 | // Verify custom modifier implementation 134 | let custom_keybind = conf_mapped.first().unwrap(); 135 | assert_eq!(custom_keybind.modifier.len(), 1); 136 | assert_eq!(custom_keybind.modifier, vec!["Mod4".to_string()]); 137 | assert_eq!( 138 | custom_keybind.command, 139 | NormalizedCommand( 140 | r#"Chord([ 141 | Keybind( 142 | command: NormalizedCommand("Execute(\"st -e htop\")"), 143 | modifier: [ 144 | "Mod4", 145 | ], 146 | key: "c", 147 | ), 148 | ])"# 149 | .to_string() 150 | ) 151 | ); 152 | } 153 | } 154 | --------------------------------------------------------------------------------