feat: implement it
This commit is contained in:
commit
186adbe17c
7 changed files with 1732 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
686
Cargo.lock
generated
Normal file
686
Cargo.lock
generated
Normal 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
15
Cargo.toml
Normal 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
22
LICENSE
Normal 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
7
README.md
Normal 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
899
src/config.rs
Normal 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
102
src/main.rs
Normal 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))
|
||||
}
|
Loading…
Reference in a new issue