feat: implement it

This commit is contained in:
Ax333l 2023-01-14 19:23:26 +01:00
commit 186adbe17c
Signed by: Ax333l
GPG key ID: D2B4D85271127D23
7 changed files with 1732 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

686
Cargo.lock generated Normal file
View file

@ -0,0 +1,686 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bindgen"
version = "0.63.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
"which",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "cc"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clang-sys"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2"
dependencies = [
"bitflags",
"clap_derive",
"clap_lex",
"is-terminal",
"once_cell",
"strsim",
"termcolor",
]
[[package]]
name = "clap_derive"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "directories"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "evdev"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bed59fcc8cfd6b190814a509018388462d3b203cf6dd10db5c00087e72a83f3"
dependencies = [
"bitvec",
"cfg-if",
"libc",
"nix",
"thiserror",
]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "io-lifetimes"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nix"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "nom"
version = "7.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "once_cell"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.36.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "shell-quote"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab31b1e46a3f14300977ece8c355009deddc6c531de49d55951e795bbad42957"
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "swhkd-gen"
version = "0.1.0"
dependencies = [
"clap",
"directories",
"evdev",
"itertools",
"shell-quote",
"xkb",
"xkbcommon-sys",
]
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "which"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
dependencies = [
"either",
"libc",
"once_cell",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "xkb"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2819e26f5465d84288b45c72864c40d55ac0684daea1973494caa3b2cea2e0f"
dependencies = [
"bitflags",
"libc",
"xkbcommon-sys",
]
[[package]]
name = "xkbcommon-sys"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f7dbb61bc8fd714a64f750e9c259952f079afe701256dd2118602c0ae15c90"
dependencies = [
"bindgen",
"libc",
"pkg-config",
]

15
Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "swhkd-gen"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.1.1", features = ["derive"] }
directories = "4.0.1"
evdev = "0.12.1"
itertools = "0.10.5"
shell-quote = "0.3.0"
xkb = "0.3.0"
xkbcommon-sys = "1.4.1"

22
LICENSE Normal file
View file

@ -0,0 +1,22 @@
Copyright (c) 2023, Axel & Contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

7
README.md Normal file
View file

@ -0,0 +1,7 @@
# Swhkd Generator
This project generates window manager configs from an sxhkd config file, thereby partially solving the global keybinds problem wayland has.
The [swhkd](https://github.com/waycrate/swhkd) does also let you use your sxhkd config on wayland, but the drawback is that it requires root.
Currently, the only supported window manager is hyprland.
## Credits
Massive thank you to all the [swhkd](https://github.com/waycrate/swhkd) contributors for making the parser.

899
src/config.rs Normal file
View file

@ -0,0 +1,899 @@
// https://github.com/waycrate/swhkd/blob/main/swhkd/src/config.rs
use itertools::Itertools;
use std::collections::HashMap;
use std::ffi::CString;
use std::fs::File;
use std::io::Read;
use std::{
fmt,
path::{Path, PathBuf},
};
#[derive(Debug)]
pub enum Error {
ConfigNotFound,
Io(std::io::Error),
InvalidConfig(ParseError),
}
impl std::error::Error for Error {}
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
// u32 is the line number where an error occured
UnknownSymbol(PathBuf, u32),
InvalidModifier(PathBuf, u32),
InvalidKeysym(PathBuf, u32),
}
impl From<std::io::Error> for Error {
fn from(val: std::io::Error) -> Self {
if val.kind() == std::io::ErrorKind::NotFound {
Error::ConfigNotFound
} else {
Error::Io(val)
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::ConfigNotFound => "Config file not found.".fmt(f),
Error::Io(io_err) => format!("I/O Error while parsing config file: {}", io_err).fmt(f),
Error::InvalidConfig(parse_err) => match parse_err {
ParseError::UnknownSymbol(path, line_nr) => format!(
"Error parsing config file {:?}. Unknown symbol at line {}.",
path, line_nr
)
.fmt(f),
ParseError::InvalidKeysym(path, line_nr) => format!(
"Error parsing config file {:?}. Invalid keysym at line {}.",
path, line_nr
)
.fmt(f),
ParseError::InvalidModifier(path, line_nr) => format!(
"Error parsing config file {:?}. Invalid modifier at line {}.",
path, line_nr
)
.fmt(f),
},
}
}
}
pub const IMPORT_STATEMENT: &str = "include";
pub const UNBIND_STATEMENT: &str = "ignore";
pub const MODE_STATEMENT: &str = "mode";
pub const MODE_END_STATEMENT: &str = "endmode";
pub const MODE_ENTER_STATEMENT: &str = "@enter";
pub const MODE_ESCAPE_STATEMENT: &str = "@escape";
pub const MODE_SWALLOW_STATEMENT: &str = "swallow";
pub const MODE_ONEOFF_STATEMENT: &str = "oneoff";
#[derive(Debug, PartialEq, Clone, Eq)]
pub struct Config {
pub path: PathBuf,
pub contents: String,
pub imports: Vec<PathBuf>,
}
pub fn load_file_contents(path: &Path) -> Result<String, Error> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
impl Config {
pub fn get_imports(contents: &str) -> Result<Vec<PathBuf>, Error> {
let mut imports = Vec::new();
for line in contents.lines() {
if line.split(' ').next().unwrap() == IMPORT_STATEMENT {
if let Some(import_path) = line.split(' ').nth(1) {
imports.push(Path::new(import_path).to_path_buf());
}
}
}
Ok(imports)
}
pub fn new(path: &Path) -> Result<Self, Error> {
let contents = load_file_contents(path)?;
let imports = Self::get_imports(&contents)?;
Ok(Config {
path: path.to_path_buf(),
contents,
imports,
})
}
pub fn load_to_configs(&self) -> Result<Vec<Self>, Error> {
let mut configs = Vec::new();
for import in &self.imports {
configs.push(Self::new(import)?)
}
Ok(configs)
}
pub fn load_and_merge(config: Self) -> Result<Vec<Self>, Error> {
let mut configs = vec![config];
let mut prev_count = 0;
let mut current_count = configs.len();
while prev_count != current_count {
prev_count = configs.len();
for config in configs.clone() {
for import in Self::load_to_configs(&config)? {
if !configs.contains(&import) {
configs.push(import);
}
}
}
current_count = configs.len();
}
Ok(configs)
}
}
pub fn load(path: &Path) -> Result<Vec<Mode>, Error> {
let config_self = Config::new(path)?;
let mut configs: Vec<Config> = Config::load_and_merge(config_self.clone())?;
configs.remove(0);
configs.push(config_self);
let mut modes: Vec<Mode> = vec![Mode::default()];
for config in configs {
let mut output = parse_contents(path.to_path_buf(), config.contents)?;
for hotkey in output[0].hotkeys.drain(..) {
modes[0]
.hotkeys
.retain(|hk| hk.keybinding != hotkey.keybinding);
modes[0].hotkeys.push(hotkey);
}
for unbind in output[0].unbinds.drain(..) {
modes[0].hotkeys.retain(|hk| hk.keybinding != unbind);
}
output.remove(0);
for mut mode in output {
mode.hotkeys
.retain(|x| !mode.unbinds.contains(&x.keybinding));
modes.push(mode);
}
}
Ok(modes)
}
#[derive(Debug, Clone)]
pub struct KeyBinding {
pub keysym: evdev::Key,
pub xkb_keysym: xkbcommon_sys::xkb_keysym_t,
pub xkb_keysym_name: String,
pub modifiers: Vec<Modifier>,
pub send: bool,
pub on_release: bool,
}
impl PartialEq for KeyBinding {
fn eq(&self, other: &Self) -> bool {
self.keysym == other.keysym
&& self
.modifiers
.iter()
.all(|modifier| other.modifiers.contains(modifier))
&& self.modifiers.len() == other.modifiers.len()
&& self.send == other.send
&& self.on_release == other.on_release
}
}
pub trait Prefix {
fn send(self) -> Self;
fn on_release(self) -> Self;
}
pub trait Value {
fn keysym(&self) -> evdev::Key;
fn modifiers(&self) -> Vec<Modifier>;
fn is_send(&self) -> bool;
fn is_on_release(&self) -> bool;
}
impl KeyBinding {
pub fn new(
keysym: evdev::Key,
xkb_keysym: xkbcommon_sys::xkb_keysym_t,
xkb_keysym_name: String,
modifiers: Vec<Modifier>,
) -> Self {
KeyBinding {
keysym,
xkb_keysym,
xkb_keysym_name,
modifiers,
send: false,
on_release: false,
}
}
pub fn on_release(mut self) -> Self {
self.on_release = true;
self
}
}
impl Prefix for KeyBinding {
fn send(mut self) -> Self {
self.send = true;
self
}
fn on_release(mut self) -> Self {
self.on_release = true;
self
}
}
impl Value for KeyBinding {
fn keysym(&self) -> evdev::Key {
self.keysym
}
fn modifiers(&self) -> Vec<Modifier> {
self.clone().modifiers
}
fn is_send(&self) -> bool {
self.send
}
fn is_on_release(&self) -> bool {
self.on_release
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Hotkey {
pub keybinding: KeyBinding,
pub command: String,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub enum Modifier {
Super,
Alt,
Control,
Shift,
Any,
}
impl Modifier {
pub fn to_caps(&self) -> &'static str {
match self {
Self::Super => "SUPER",
Self::Alt => "ALT",
Self::Control => "CTRL",
Self::Shift => "SHIFT",
Self::Any => "ANY",
}
}
pub fn to_normal(&self) -> &'static str {
match self {
Self::Super => "Super",
Self::Alt => "Alt",
Self::Control => "Ctrl",
Self::Shift => "Shift",
Self::Any => "Any",
}
}
}
impl Hotkey {
pub fn from_keybinding(keybinding: KeyBinding, command: String) -> Self {
Hotkey {
keybinding,
command,
}
}
#[cfg(test)]
pub fn new(keysym: evdev::Key, modifiers: Vec<Modifier>, command: String) -> Self {
Hotkey {
keybinding: KeyBinding::new(keysym, 0, modifiers),
command,
}
}
}
impl Prefix for Hotkey {
fn send(mut self) -> Self {
self.keybinding.send = true;
self
}
fn on_release(mut self) -> Self {
self.keybinding.on_release = true;
self
}
}
impl Value for &Hotkey {
fn keysym(&self) -> evdev::Key {
self.keybinding.keysym
}
fn modifiers(&self) -> Vec<Modifier> {
self.keybinding.clone().modifiers
}
fn is_send(&self) -> bool {
self.keybinding.send
}
fn is_on_release(&self) -> bool {
self.keybinding.on_release
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Mode {
pub name: String,
pub hotkeys: Vec<Hotkey>,
pub unbinds: Vec<KeyBinding>,
pub options: ModeOptions,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ModeOptions {
pub swallow: bool,
pub oneoff: bool,
}
impl Mode {
pub fn new(name: String) -> Self {
Self {
name,
hotkeys: Vec::new(),
unbinds: Vec::new(),
options: ModeOptions::default(),
}
}
pub fn default() -> Self {
Self::new("normal".to_string())
}
}
pub fn parse_contents(path: PathBuf, contents: String) -> Result<Vec<Mode>, Error> {
// Don't forget to update valid key list on the man page if you do change this list.
let key_to_evdev_key: HashMap<&str, evdev::Key> = HashMap::from([
("q", evdev::Key::KEY_Q),
("w", evdev::Key::KEY_W),
("e", evdev::Key::KEY_E),
("r", evdev::Key::KEY_R),
("t", evdev::Key::KEY_T),
("y", evdev::Key::KEY_Y),
("u", evdev::Key::KEY_U),
("i", evdev::Key::KEY_I),
("o", evdev::Key::KEY_O),
("p", evdev::Key::KEY_P),
("a", evdev::Key::KEY_A),
("s", evdev::Key::KEY_S),
("d", evdev::Key::KEY_D),
("f", evdev::Key::KEY_F),
("g", evdev::Key::KEY_G),
("h", evdev::Key::KEY_H),
("j", evdev::Key::KEY_J),
("k", evdev::Key::KEY_K),
("l", evdev::Key::KEY_L),
("z", evdev::Key::KEY_Z),
("x", evdev::Key::KEY_X),
("c", evdev::Key::KEY_C),
("v", evdev::Key::KEY_V),
("b", evdev::Key::KEY_B),
("n", evdev::Key::KEY_N),
("m", evdev::Key::KEY_M),
("1", evdev::Key::KEY_1),
("2", evdev::Key::KEY_2),
("3", evdev::Key::KEY_3),
("4", evdev::Key::KEY_4),
("5", evdev::Key::KEY_5),
("6", evdev::Key::KEY_6),
("7", evdev::Key::KEY_7),
("8", evdev::Key::KEY_8),
("9", evdev::Key::KEY_9),
("0", evdev::Key::KEY_0),
("escape", evdev::Key::KEY_ESC),
("backspace", evdev::Key::KEY_BACKSPACE),
("capslock", evdev::Key::KEY_CAPSLOCK),
("return", evdev::Key::KEY_ENTER),
("enter", evdev::Key::KEY_ENTER),
("tab", evdev::Key::KEY_TAB),
("space", evdev::Key::KEY_SPACE),
("plus", evdev::Key::KEY_KPPLUS), // Shouldn't this be kpplus?
("kp0", evdev::Key::KEY_KP0),
("kp1", evdev::Key::KEY_KP1),
("kp2", evdev::Key::KEY_KP2),
("kp3", evdev::Key::KEY_KP3),
("kp4", evdev::Key::KEY_KP4),
("kp5", evdev::Key::KEY_KP5),
("kp6", evdev::Key::KEY_KP6),
("kp7", evdev::Key::KEY_KP7),
("kp8", evdev::Key::KEY_KP8),
("kp9", evdev::Key::KEY_KP9),
("kpasterisk", evdev::Key::KEY_KPASTERISK),
("kpcomma", evdev::Key::KEY_KPCOMMA),
("kpdot", evdev::Key::KEY_KPDOT),
("kpenter", evdev::Key::KEY_KPENTER),
("kpequal", evdev::Key::KEY_KPEQUAL),
("kpjpcomma", evdev::Key::KEY_KPJPCOMMA),
("kpleftparen", evdev::Key::KEY_KPLEFTPAREN),
("kpminus", evdev::Key::KEY_KPMINUS),
("kpplusminus", evdev::Key::KEY_KPPLUSMINUS),
("kprightparen", evdev::Key::KEY_KPRIGHTPAREN),
("minus", evdev::Key::KEY_MINUS),
("-", evdev::Key::KEY_MINUS),
("equal", evdev::Key::KEY_EQUAL),
("=", evdev::Key::KEY_EQUAL),
("grave", evdev::Key::KEY_GRAVE),
("`", evdev::Key::KEY_GRAVE),
("print", evdev::Key::KEY_SYSRQ),
("volumeup", evdev::Key::KEY_VOLUMEUP),
("xf86audioraisevolume", evdev::Key::KEY_VOLUMEUP),
("volumedown", evdev::Key::KEY_VOLUMEDOWN),
("xf86audiolowervolume", evdev::Key::KEY_VOLUMEDOWN),
("mute", evdev::Key::KEY_MUTE),
("xf86audiomute", evdev::Key::KEY_MUTE),
("brightnessup", evdev::Key::KEY_BRIGHTNESSUP),
("xf86monbrightnessup", evdev::Key::KEY_BRIGHTNESSUP),
("brightnessdown", evdev::Key::KEY_BRIGHTNESSDOWN),
("xf86audiomedia", evdev::Key::KEY_MEDIA),
("xf86audiomicmute", evdev::Key::KEY_MICMUTE),
("micmute", evdev::Key::KEY_MICMUTE),
("xf86audionext", evdev::Key::KEY_NEXTSONG),
("xf86audioplay", evdev::Key::KEY_PLAYPAUSE),
("xf86audiopause", evdev::Key::KEY_PAUSE),
("xf86audioprev", evdev::Key::KEY_PREVIOUSSONG),
("xf86audiostop", evdev::Key::KEY_STOP),
("xf86audiorewind", evdev::Key::KEY_REWIND),
("xf86audioforward", evdev::Key::KEY_FORWARD),
("xf86monbrightnessdown", evdev::Key::KEY_BRIGHTNESSDOWN),
("xf86calculator", evdev::Key::KEY_CALC),
("xf86www", evdev::Key::KEY_WWW),
("xf86dos", evdev::Key::KEY_MSDOS),
("xf86screensaver", evdev::Key::KEY_SCREENSAVER),
("xf86taskpane", evdev::Key::KEY_TASKMANAGER),
("xf86mail", evdev::Key::KEY_MAIL),
("xf86mycomputer", evdev::Key::KEY_COMPUTER),
(",", evdev::Key::KEY_COMMA),
("comma", evdev::Key::KEY_COMMA),
(".", evdev::Key::KEY_DOT),
("dot", evdev::Key::KEY_DOT),
("period", evdev::Key::KEY_DOT),
("/", evdev::Key::KEY_SLASH),
("question", evdev::Key::KEY_QUESTION),
("slash", evdev::Key::KEY_SLASH),
("backslash", evdev::Key::KEY_BACKSLASH),
("leftbrace", evdev::Key::KEY_LEFTBRACE),
("[", evdev::Key::KEY_LEFTBRACE),
("bracketleft", evdev::Key::KEY_LEFTBRACE),
("rightbrace", evdev::Key::KEY_RIGHTBRACE),
("]", evdev::Key::KEY_RIGHTBRACE),
("bracketright", evdev::Key::KEY_RIGHTBRACE),
(";", evdev::Key::KEY_SEMICOLON),
("scroll_lock", evdev::Key::KEY_SCROLLLOCK),
("semicolon", evdev::Key::KEY_SEMICOLON),
("'", evdev::Key::KEY_APOSTROPHE),
("apostrophe", evdev::Key::KEY_APOSTROPHE),
("left", evdev::Key::KEY_LEFT),
("right", evdev::Key::KEY_RIGHT),
("up", evdev::Key::KEY_UP),
("down", evdev::Key::KEY_DOWN),
("pause", evdev::Key::KEY_PAUSE),
("home", evdev::Key::KEY_HOME),
("delete", evdev::Key::KEY_DELETE),
("insert", evdev::Key::KEY_INSERT),
("end", evdev::Key::KEY_END),
("pause", evdev::Key::KEY_PAUSE),
("prior", evdev::Key::KEY_PAGEDOWN),
("next", evdev::Key::KEY_PAGEUP),
("pagedown", evdev::Key::KEY_PAGEDOWN),
("pageup", evdev::Key::KEY_PAGEUP),
("f1", evdev::Key::KEY_F1),
("f2", evdev::Key::KEY_F2),
("f3", evdev::Key::KEY_F3),
("f4", evdev::Key::KEY_F4),
("f5", evdev::Key::KEY_F5),
("f6", evdev::Key::KEY_F6),
("f7", evdev::Key::KEY_F7),
("f8", evdev::Key::KEY_F8),
("f9", evdev::Key::KEY_F9),
("f10", evdev::Key::KEY_F10),
("f11", evdev::Key::KEY_F11),
("f12", evdev::Key::KEY_F12),
("f13", evdev::Key::KEY_F13),
("f14", evdev::Key::KEY_F14),
("f15", evdev::Key::KEY_F15),
("f16", evdev::Key::KEY_F16),
("f17", evdev::Key::KEY_F17),
("f18", evdev::Key::KEY_F18),
("f19", evdev::Key::KEY_F19),
("f20", evdev::Key::KEY_F20),
("f21", evdev::Key::KEY_F21),
("f22", evdev::Key::KEY_F22),
("f23", evdev::Key::KEY_F23),
("f24", evdev::Key::KEY_F24),
]);
// Don't forget to update modifier list on the man page if you do change this list.
let mod_to_mod_enum: HashMap<&str, Modifier> = HashMap::from([
("ctrl", Modifier::Control),
("control", Modifier::Control),
("super", Modifier::Super),
("mod4", Modifier::Super),
("alt", Modifier::Alt),
("mod1", Modifier::Alt),
("shift", Modifier::Shift),
("any", Modifier::Any),
]);
let lines: Vec<&str> = contents.split('\n').collect();
let mut modes: Vec<Mode> = vec![Mode::default()];
let mut current_mode: usize = 0;
// Go through each line, ignore comments and empty lines, mark lines starting with whitespace
// as commands, and mark the other lines as keysyms. Mark means storing a line's type and the
// line number in a vector.
let mut lines_with_types: Vec<(&str, u32)> = Vec::new();
for (line_number, line) in lines.iter().enumerate() {
if line.trim().starts_with('#')
|| line.split(' ').next().unwrap() == IMPORT_STATEMENT
|| line.trim().is_empty()
{
continue;
}
if line.starts_with(' ') || line.starts_with('\t') {
lines_with_types.push(("command", line_number as u32));
} else if line.starts_with(UNBIND_STATEMENT) {
lines_with_types.push(("unbind", line_number as u32));
} else if line.starts_with(MODE_STATEMENT) {
lines_with_types.push(("modestart", line_number as u32));
} else if line.starts_with(MODE_END_STATEMENT) {
lines_with_types.push(("modeend", line_number as u32));
} else {
lines_with_types.push(("keysym", line_number as u32));
}
}
// Edge case: return a blank vector if no lines detected
if lines_with_types.is_empty() {
return Ok(modes);
}
let mut actual_lines: Vec<(&str, u32, String)> = Vec::new();
if contents.contains('\\') {
// Go through lines_with_types, and add the next line over and over until the current line no
// longer ends with backslash. (Only if the lines have the same type)
let mut current_line_type = lines_with_types[0].0;
let mut current_line_number = lines_with_types[0].1;
let mut current_line_string = String::new();
let mut continue_backslash;
for (line_type, line_number) in lines_with_types {
if line_type != current_line_type {
current_line_type = line_type;
current_line_number = line_number;
current_line_string = String::new();
}
let line_to_add = lines[line_number as usize].trim();
continue_backslash = line_to_add.ends_with('\\');
let line_to_add = line_to_add.strip_suffix('\\').unwrap_or(line_to_add);
current_line_string.push_str(line_to_add);
if !continue_backslash {
actual_lines.push((current_line_type, current_line_number, current_line_string));
current_line_type = line_type;
current_line_number = line_number;
current_line_string = String::new();
}
}
} else {
for (line_type, line_number) in lines_with_types {
actual_lines.push((
line_type,
line_number,
lines[line_number as usize].trim().to_string(),
));
}
}
drop(lines);
for (i, item) in actual_lines.iter().enumerate() {
let line_type = item.0;
let line_number = item.1;
let line = &item.2;
if line_type == "unbind" {
let to_unbind = line.trim_start_matches(UNBIND_STATEMENT).trim();
modes[current_mode].unbinds.push(parse_keybind(
path.clone(),
to_unbind,
line_number + 1,
&key_to_evdev_key,
&mod_to_mod_enum,
)?);
}
if line_type == "modestart" {
let tokens = line.split(' ').collect_vec();
let modename = tokens[1];
let mut mode = Mode::new(modename.to_string());
mode.options.swallow = tokens.contains(&MODE_SWALLOW_STATEMENT);
mode.options.oneoff = tokens.contains(&MODE_ONEOFF_STATEMENT);
modes.push(mode);
current_mode = modes.len() - 1;
}
if line_type == "modeend" {
current_mode = 0;
}
if line_type != "keysym" {
continue;
}
let next_line = actual_lines.get(i + 1);
if next_line.is_none() {
break;
}
let next_line = next_line.unwrap();
if next_line.0 != "command" {
continue; // this should ignore keysyms that are not followed by a command
}
let extracted_keys = extract_curly_brace(line);
let extracted_commands = extract_curly_brace(&next_line.2);
for (key, command) in extracted_keys.iter().zip(extracted_commands.iter()) {
let keybinding = parse_keybind(
path.clone(),
key,
line_number + 1,
&key_to_evdev_key,
&mod_to_mod_enum,
)?;
let hotkey = Hotkey::from_keybinding(keybinding, command.to_string());
// Override latter
modes[current_mode]
.hotkeys
.retain(|h| h.keybinding != hotkey.keybinding);
modes[current_mode].hotkeys.push(hotkey);
}
}
Ok(modes)
}
// We need to get the reference to key_to_evdev_key
// and mod_to_mod enum instead of recreating them
// after each function call because it's too expensive
fn parse_keybind(
path: PathBuf,
line: &str,
line_nr: u32,
key_to_evdev_key: &HashMap<&str, evdev::Key>,
mod_to_mod_enum: &HashMap<&str, Modifier>,
) -> Result<KeyBinding, Error> {
let line = line.split('#').next().unwrap();
let unmodified_tokens = line.split('+').map(|s| s.trim()).filter(|s| s != &"_");
let mut raw_tokens = Vec::new();
for mut token in unmodified_tokens {
while token.trim().starts_with('_') {
token = token.trim().strip_prefix('_').unwrap();
}
raw_tokens.push(token.trim().to_string());
}
let tokens_new: Vec<String> = raw_tokens.iter().map(|s| s.to_lowercase()).collect();
let last_raw_token = strip_at(raw_tokens.last().unwrap().trim());
let last_token = tokens_new.last().unwrap().trim();
// Check if last_token is prefixed with @ or ~ or even both.
// If prefixed @, on_release = true; if prefixed ~, send = true
let send = last_token.starts_with('~') || last_token.starts_with("@~");
let on_release = last_token.starts_with('@') || last_token.starts_with("~@");
// Delete the @ and ~ in the last token
fn strip_at(token: &str) -> &str {
if token.starts_with('@') {
let token = token.strip_prefix('@').unwrap();
strip_tilde(token)
} else if token.starts_with('~') {
strip_tilde(token)
} else {
token
}
}
fn strip_tilde(token: &str) -> &str {
if token.starts_with('~') {
let token = token.strip_prefix('~').unwrap();
strip_at(token)
} else if token.starts_with('@') {
strip_at(token)
} else {
token
}
}
let last_token = strip_at(last_token);
// Check if each token is valid
for token in &tokens_new {
let token = strip_at(token);
if key_to_evdev_key.contains_key(token) {
// Can't have a keysym that's like a modifier
if token != last_token {
return Err(Error::InvalidConfig(ParseError::InvalidModifier(
path, line_nr,
)));
}
} else if mod_to_mod_enum.contains_key(token) {
// Can't have a modifier that's like a keysym
if token == last_token {
return Err(Error::InvalidConfig(ParseError::InvalidKeysym(
path, line_nr,
)));
}
} else {
return Err(Error::InvalidConfig(ParseError::UnknownSymbol(
path, line_nr,
)));
}
}
// Translate keypress into evdev key
let keysym = key_to_evdev_key.get(last_token).unwrap();
let cstr = CString::new(last_raw_token).unwrap();
let xkb_keysym = unsafe {
xkbcommon_sys::xkb_keysym_from_name(
cstr.as_ptr(),
0,
)
};
if xkb_keysym == 0 {
eprintln!("UNKNOWN: {}", last_raw_token);
return Err(Error::InvalidConfig(ParseError::InvalidKeysym(
path, line_nr,
)));
}
let modifiers: Vec<Modifier> = tokens_new[0..(tokens_new.len() - 1)]
.iter()
.map(|token| *mod_to_mod_enum.get(strip_at(token.as_str())).unwrap())
.collect();
let mut keybinding =
KeyBinding::new(*keysym, xkb_keysym, last_raw_token.to_string(), modifiers);
if send {
keybinding = keybinding.send();
}
if on_release {
keybinding = keybinding.on_release();
}
Ok(keybinding)
}
pub fn extract_curly_brace(line: &str) -> Vec<String> {
if !line.contains('{') || !line.contains('}') || !line.is_ascii() {
return vec![line.to_string()];
}
// go through each character in the line and mark the position of each { and }
// if a { is not followed by a }, return the line as is
let mut brace_positions: Vec<usize> = Vec::new();
let mut flag = false;
for (i, c) in line.chars().enumerate() {
if c == '{' {
if flag {
return vec![line.to_string()];
}
brace_positions.push(i);
flag = true;
} else if c == '}' {
if !flag {
return vec![line.to_string()];
}
brace_positions.push(i);
flag = false;
}
}
// now we have a list of positions of { and }
// we should extract the items between each pair of braces and store them in a vector
let mut items: Vec<String> = Vec::new();
let mut remaining_line: Vec<String> = Vec::new();
let mut start_index = 0;
for i in brace_positions.chunks(2) {
items.push(line[i[0] + 1..i[1]].to_string());
remaining_line.push(line[start_index..i[0]].to_string());
start_index = i[1] + 1;
}
// now we have a list of items between each pair of braces
// we should extract the items between each comma and store them in a vector
let mut tokens_vec: Vec<Vec<String>> = Vec::new();
for item in items {
// Edge case: escape periods
// example:
// ```
// super + {\,, .}
// riverctl focus-output {previous, next}
// ```
let item = item.replace("\\,", "comma");
let items: Vec<String> = item.split(',').map(|s| s.trim().to_string()).collect();
tokens_vec.push(handle_ranges(items));
}
fn handle_ranges(items: Vec<String>) -> Vec<String> {
let mut output: Vec<String> = Vec::new();
for item in items {
if !item.contains('-') {
output.push(item);
continue;
}
let mut range = item.split('-').map(|s| s.trim());
let begin_char: &str = if let Some(b) = range.next() {
b
} else {
output.push(item);
continue;
};
let end_char: &str = if let Some(e) = range.next() {
e
} else {
output.push(item);
continue;
};
// Do not accept range values that are longer than one char
// Example invalid: {ef-p} {3-56}
// Beginning of the range cannot be greater than end
// Example invalid: {9-4} {3-2}
if begin_char.len() != 1 || end_char.len() != 1 || begin_char > end_char {
output.push(item);
continue;
}
// In swhkd we will parse the full range using ASCII values.
let begin_ascii_val = begin_char.parse::<char>().unwrap() as u8;
let end_ascii_val = end_char.parse::<char>().unwrap() as u8;
for ascii_number in begin_ascii_val..=end_ascii_val {
output.push((ascii_number as char).to_string());
}
}
output
}
// now write the tokens back to the line and output a vector
let mut output: Vec<String> = Vec::new();
// generate a cartesian product iterator for all the vectors in tokens_vec
let cartesian_product_iter = tokens_vec.iter().multi_cartesian_product();
for tokens in cartesian_product_iter.collect_vec() {
let mut line_to_push = String::new();
for i in 0..remaining_line.len() {
line_to_push.push_str(&remaining_line[i]);
line_to_push.push_str(tokens[i]);
}
if brace_positions[brace_positions.len() - 1] < line.len() - 1 {
line_to_push.push_str(&line[brace_positions[brace_positions.len() - 1] + 1..]);
}
output.push(line_to_push);
}
output
}

102
src/main.rs Normal file
View file

@ -0,0 +1,102 @@
use std::error::Error;
use std::fs::File;
use std::io::Write;
use std::str;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use itertools::Itertools;
mod config;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Input sxhkd config file
#[arg(short, long)]
input: Option<PathBuf>,
/// Output file
#[arg(short, long)]
output: Option<PathBuf>,
/// Overwrite the output file if it exists
#[arg(short, long)]
force: bool,
#[command(subcommand)]
command: Commands,
}
fn check(mode: &config::Mode) {
for _ in &mode.unbinds {
unimplemented!();
}
if mode.name != "normal" {
panic!("Modes are not supported.");
}
}
#[derive(Subcommand)]
enum Commands {
/// Generate a config file for the Hyprland window manager.
Hyprland,
/// Generates a shell script for riverwm. Warning: this one is currently untested!
River,
}
fn main() -> Result<ExitCode, Box<dyn Error>> {
let cli = Cli::parse();
let input = cli.input.unwrap_or_else(|| {
directories::BaseDirs::new()
.expect("No base directory found.")
.config_dir()
.join(Path::new("sxhkd/sxhkdrc"))
});
let output_path = cli.output.unwrap_or_else(|| PathBuf::from("/dev/stdout"));
if !output_path.starts_with("/dev") && Path::try_exists(&output_path)? && !cli.force {
eprintln!("Output file already exists! Use --force if you want to overwrite it.");
return Ok(ExitCode::from(1));
}
let mut output = File::create(output_path)?;
let config = config::load(&input)?;
match cli.command {
Commands::Hyprland => {
for mode in &config {
check(mode);
for hotkey in &mode.hotkeys {
let bind = &hotkey.keybinding;
writeln!(
output,
"bind={},{},exec,{}",
bind.modifiers.iter().map(|m| m.to_caps()).join("_"),
bind.xkb_keysym_name,
hotkey.command
)?;
}
}
}
Commands::River => {
for mode in &config {
check(mode);
for hotkey in &mode.hotkeys {
let bind = &hotkey.keybinding;
writeln!(
output,
"riverctl map normal {} {} spawn {}",
bind.modifiers.iter().map(|m| m.to_normal()).join("+"),
bind.xkb_keysym_name,
&shell_quote::sh::quote(&hotkey.command).as_os_str().to_string_lossy(),
)?;
}
}
}
}
Ok(ExitCode::from(0))
}