drun:
* move drun into modes * add support for wrapping labels * make MenuItem a generic struct * add optional field to struct to pass data along * merge config and args
This commit is contained in:
parent
d8e64f28fb
commit
f398848dcf
10 changed files with 610 additions and 1005 deletions
594
Cargo.lock
generated
594
Cargo.lock
generated
|
|
@ -114,7 +114,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -123,7 +123,7 @@ version = "0.2.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.19",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
|
@ -161,26 +161,6 @@ version = "2.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
|
|
@ -210,54 +190,6 @@ dependencies = [
|
|||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"log",
|
||||
"polling",
|
||||
"rustix",
|
||||
"slab",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10929724661d1c43856fd87c7a127ae944ec55579134fb485e4136fb6a46fdcb"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"polling",
|
||||
"rustix",
|
||||
"slab",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop-wayland-source"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
|
||||
dependencies = [
|
||||
"calloop 0.13.0",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-expr"
|
||||
version = "0.17.2"
|
||||
|
|
@ -321,7 +253,7 @@ dependencies = [
|
|||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -345,15 +277,6 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "configparser"
|
||||
version = "1.0.0"
|
||||
|
|
@ -416,12 +339,6 @@ version = "0.8.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "cursor-icon"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "1.0.0"
|
||||
|
|
@ -439,15 +356,30 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
|
|
@ -484,16 +416,6 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "field-offset"
|
||||
version = "0.3.6"
|
||||
|
|
@ -510,7 +432,7 @@ version = "0.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6059d3997cc694ec3e9a378db855866233ef7edfeafd85afcb2239fd130e6e6b"
|
||||
dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"xdgkit",
|
||||
]
|
||||
|
||||
|
|
@ -564,7 +486,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -645,6 +567,17 @@ dependencies = [
|
|||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
|
|
@ -732,7 +665,7 @@ dependencies = [
|
|||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -868,7 +801,7 @@ dependencies = [
|
|||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -917,12 +850,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.11"
|
||||
|
|
@ -962,7 +889,7 @@ checksum = "69e3cbed6e560408051175d29a9ed6ad1e64a7ff443836addf797b0479f58983"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1027,7 +954,7 @@ checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1042,18 +969,22 @@ version = "0.2.171"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
|
|
@ -1066,24 +997,6 @@ version = "2.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
|
|
@ -1093,28 +1006,6 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "merge"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9"
|
||||
dependencies = [
|
||||
"merge_derive",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "merge_derive"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.7"
|
||||
|
|
@ -1135,15 +1026,6 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
|
|
@ -1153,15 +1035,6 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
|
|
@ -1177,6 +1050,12 @@ version = "1.21.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.6.1"
|
||||
|
|
@ -1243,7 +1122,7 @@ dependencies = [
|
|||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1273,21 +1152,6 @@ version = "0.3.32"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi 0.4.0",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
|
|
@ -1312,30 +1176,6 @@ dependencies = [
|
|||
"toml_edit",
|
||||
]
|
||||
|
||||
[[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 1.0.109",
|
||||
"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.94"
|
||||
|
|
@ -1355,15 +1195,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.37.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
|
|
@ -1388,6 +1219,17 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
|
|
@ -1432,19 +1274,6 @@ dependencies = [
|
|||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
|
|
@ -1474,7 +1303,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1497,7 +1326,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1509,12 +1338,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
|
|
@ -1536,34 +1359,6 @@ version = "1.15.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bytemuck",
|
||||
"calloop 0.13.0",
|
||||
"calloop-wayland-source",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap2 0.9.5",
|
||||
"pkg-config",
|
||||
"rustix",
|
||||
"thiserror 1.0.69",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-csd-frame",
|
||||
"wayland-cursor",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-scanner",
|
||||
"xkbcommon",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.9"
|
||||
|
|
@ -1586,17 +1381,6 @@ version = "0.11.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
|
|
@ -1608,19 +1392,6 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.34.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"objc2-core-foundation",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "7.0.3"
|
||||
|
|
@ -1655,33 +1426,13 @@ version = "0.16.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1692,7 +1443,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1725,7 +1476,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1762,23 +1513,6 @@ dependencies = [
|
|||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
|
|
@ -1815,98 +1549,6 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"rustix",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-csd-frame"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"cursor-icon",
|
||||
"wayland-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-cursor"
|
||||
version = "0.31.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"wayland-client",
|
||||
"xcursor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml 0.37.4",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
@ -1938,59 +1580,6 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-result",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
|
@ -2087,9 +1676,9 @@ name = "worf"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"calloop 0.14.2",
|
||||
"clap 4.5.35",
|
||||
"crossbeam",
|
||||
"dirs",
|
||||
"env_logger",
|
||||
"freedesktop-file-parser",
|
||||
"gdk4",
|
||||
|
|
@ -2100,25 +1689,14 @@ dependencies = [
|
|||
"ini",
|
||||
"libc",
|
||||
"log",
|
||||
"merge",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smithay-client-toolkit",
|
||||
"strsim 0.11.1",
|
||||
"sysinfo",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcursor"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61"
|
||||
|
||||
[[package]]
|
||||
name = "xdgkit"
|
||||
version = "3.2.5"
|
||||
|
|
@ -2126,31 +1704,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "5aeac9c0125f3c131c6a2898d2a9f25c11b7954c3ff644a018cb9e06fa92919b"
|
||||
dependencies = [
|
||||
"clap 3.2.25",
|
||||
"quick-xml 0.21.0",
|
||||
"quick-xml",
|
||||
"serde",
|
||||
"tini",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkbcommon"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"memmap2 0.8.0",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkeysym"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.25"
|
||||
|
|
@ -2183,5 +1741,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
"syn",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -13,19 +13,14 @@ home = "0.5.11"
|
|||
log = "0.4.27"
|
||||
regex = "1.11.1"
|
||||
hyprland = "0.4.0-beta.2"
|
||||
sysinfo = "0.34.2"
|
||||
ini = "1.3.0"
|
||||
clap = { version = "4.5.35", features = ["derive"] }
|
||||
thiserror = "2.0.12"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
toml = "0.8.20"
|
||||
merge = "0.1.0"
|
||||
serde_json = "1.0.140"
|
||||
wayland-client = "0.31.8"
|
||||
wayland-protocols = "0.32.6"
|
||||
smithay-client-toolkit = { version = "0.19.2", features = ["calloop"]}
|
||||
calloop = "0.14.2"
|
||||
crossbeam = "0.8.4"
|
||||
libc = "0.2.171"
|
||||
freedesktop-file-parser = "0.1.0"
|
||||
strsim = "0.11.1"
|
||||
dirs = "6.0.0"
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ layerrule = blur, worf
|
|||
because worf is build on GTK4 instead of GTK3 there will be differences in the look and feel.
|
||||
* Configuration files are not 100% compatible, Worf is using toml files instead, for most part this only means strings have to be quoted
|
||||
* Color files are not supported
|
||||
* `mode` dropped, use show
|
||||
* `D` argument dropped. Arguments are the same as config in worf, no need to have have this flag.
|
||||
|
||||
## Dropped configuration options
|
||||
* stylesheet -> use style instead
|
||||
|
|
|
|||
201
src/args.rs
201
src/args.rs
|
|
@ -1,201 +0,0 @@
|
|||
use crate::lib::config::{Align, MatchMethod, Orientation};
|
||||
use clap::Parser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
// Define a custom error type using the `thiserror` crate
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ArgsError {
|
||||
#[error("input is not valid {0}")]
|
||||
InvalidParameter(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum Mode {
|
||||
/// searches $PATH for executables and allows them to be run by selecting them.
|
||||
Run,
|
||||
/// searches $XDG_DATA_HOME/applications and $XDG_DATA_DIRS/applications for desktop files and allows them to be run by selecting them.
|
||||
Drun,
|
||||
|
||||
/// reads from stdin and displays options which when selected will be output to stdout.
|
||||
Dmenu,
|
||||
}
|
||||
|
||||
impl FromStr for Mode {
|
||||
type Err = ArgsError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"run" => Ok(Mode::Run),
|
||||
"drun" => Ok(Mode::Drun),
|
||||
"dmenu" => Ok(Mode::Dmenu),
|
||||
_ => Err(ArgsError::InvalidParameter(
|
||||
format!("{s} is not a valid argument show this, see help for details").to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Deserialize, Serialize)]
|
||||
#[clap(about = "Worf is a wofi clone written in rust, it aims to be a drop in replacement")]
|
||||
pub struct Args {
|
||||
/// Forks the menu so you can close the terminal
|
||||
#[clap(short = 'f', long = "fork")]
|
||||
fork: bool,
|
||||
|
||||
/// Selects a config file to use
|
||||
#[clap(short = 'c', long = "conf")]
|
||||
pub config: Option<String>,
|
||||
|
||||
/// Selects a stylesheet to use
|
||||
#[clap(short = 's', long = "style")]
|
||||
style: Option<String>,
|
||||
|
||||
/// Runs in dmenu mode
|
||||
#[clap(short = 'd', long = "dmenu")]
|
||||
dmenu: bool,
|
||||
|
||||
/// Specifies the mode to run in. A list can be found in wofi(7)
|
||||
#[clap(long = "show")]
|
||||
pub mode: Mode,
|
||||
|
||||
/// Specifies the surface width
|
||||
#[clap(short = 'W', long = "width")]
|
||||
width: Option<String>,
|
||||
|
||||
/// Specifies the surface height
|
||||
#[clap(short = 'H', long = "height")]
|
||||
height: Option<String>,
|
||||
|
||||
/// Prompt to display
|
||||
#[clap(short = 'p', long = "prompt")]
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// The x offset
|
||||
#[clap(short = 'x', long = "xoffset")]
|
||||
x: Option<i32>,
|
||||
|
||||
/// The y offset
|
||||
#[clap(short = 'y', long = "yoffset")]
|
||||
y: Option<i32>,
|
||||
|
||||
/// Render to a normal window
|
||||
#[clap(short = 'n', long = "normal-window")]
|
||||
normal_window: bool,
|
||||
|
||||
/// Allows images to be rendered
|
||||
#[clap(short = 'I', long = "allow-images")]
|
||||
allow_images: bool,
|
||||
|
||||
/// Allows pango markup
|
||||
#[clap(short = 'm', long = "allow-markup")]
|
||||
allow_markup: bool,
|
||||
|
||||
/// Sets the cache file to use
|
||||
#[clap(short = 'k', long = "cache-file")]
|
||||
cache_file: Option<String>,
|
||||
|
||||
/// Specifies the terminal to use when running in a term
|
||||
#[clap(short = 't', long = "term")]
|
||||
terminal: Option<String>,
|
||||
|
||||
/// Runs in password mode
|
||||
#[clap(short = 'P', long = "password")]
|
||||
password_char: Option<String>,
|
||||
|
||||
/// Makes enter always use the search contents, not the first result
|
||||
#[clap(short = 'e', long = "exec-search")]
|
||||
exec_search: bool,
|
||||
|
||||
/// Hides the scroll bars
|
||||
#[clap(short = 'b', long = "hide-scroll")]
|
||||
hide_scroll: bool,
|
||||
|
||||
/// Sets the matching method, default is contains
|
||||
#[clap(short = 'M', long = "matching")]
|
||||
matching: Option<MatchMethod>,
|
||||
|
||||
/// Allows case insensitive searching
|
||||
#[clap(short = 'i', long = "insensitive")]
|
||||
insensitive: bool,
|
||||
|
||||
/// Parses the search text removing image escapes and pango
|
||||
#[clap(short = 'q', long = "parse-search")]
|
||||
parse_search: bool,
|
||||
|
||||
/// Prints the version and then exits
|
||||
#[clap(short = 'v', long = "version")]
|
||||
version: bool,
|
||||
|
||||
/// Sets the location
|
||||
#[clap(short = 'l', long = "location")]
|
||||
location: Option<String>,
|
||||
|
||||
/// Disables multiple actions for modes that support it
|
||||
#[clap(short = 'a', long = "no-actions")]
|
||||
no_actions: bool,
|
||||
|
||||
/// Sets a config option
|
||||
#[clap(short = 'D', long = "define")]
|
||||
define: Option<String>,
|
||||
|
||||
/// Sets the height in number of lines
|
||||
#[clap(short = 'L', long = "lines")]
|
||||
lines: Option<String>,
|
||||
|
||||
/// Sets the number of columns to display
|
||||
#[clap(short = 'w', long = "columns")]
|
||||
columns: Option<u8>,
|
||||
|
||||
/// Sets the sort order
|
||||
#[clap(short = 'O', long = "sort-order")]
|
||||
sort_order: Option<String>,
|
||||
|
||||
/// Uses the dark variant of the current GTK theme
|
||||
#[clap(short = 'G', long = "gtk-dark")]
|
||||
gtk_dark: bool,
|
||||
|
||||
/// Search for something immediately on open
|
||||
#[clap(short = 'Q', long = "search")]
|
||||
search: Option<String>,
|
||||
|
||||
/// Sets the monitor to open on
|
||||
#[clap(short = 'o', long = "monitor")]
|
||||
monitor: Option<String>,
|
||||
|
||||
/// Runs command for the displayed entries, without changing the output. %s for the real string
|
||||
#[clap(short = 'r', long = "pre-display-cmd")]
|
||||
pre_display_cmd: Option<String>,
|
||||
|
||||
/// Defines how good a fuzzy match must be, to be shown.
|
||||
#[clap(long = "fuzzy-min-score")]
|
||||
fuzzy_min_score: Option<f64>,
|
||||
|
||||
/// Size of displayed images
|
||||
#[clap(long = "image-size")]
|
||||
image_size: Option<i32>,
|
||||
|
||||
/// Orientation of main window
|
||||
#[clap(long = "orientation")]
|
||||
orientation: Option<Orientation>,
|
||||
|
||||
/// Orientation of the row box, defining if label is below or at the side.
|
||||
#[clap(long = "row-box-orientation")]
|
||||
row_bow_orientation: Option<Orientation>,
|
||||
|
||||
/// Specifies the horizontal align for the entire scrolled area,
|
||||
/// it can be any of fill, start, end, or center, default is fill.
|
||||
#[clap(long = "halign")]
|
||||
pub halign: Option<Align>,
|
||||
//// Specifies the horizontal align for the individual entries,
|
||||
// it can be any of fill, start, end, or center, default is fill.
|
||||
#[clap(long = "content-halign")]
|
||||
pub content_halign: Option<Align>,
|
||||
|
||||
/// Specifies the vertical align for the entire scrolled area, it can be any of fill, start, e
|
||||
/// nd, or center, the default is orientation dependent. If vertical then it defaults to
|
||||
/// start, if horizontal it defaults to center.
|
||||
#[clap(long = "valign")]
|
||||
pub valign: Option<Align>,
|
||||
}
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
use crate::args::Args;
|
||||
use crate::lib::system;
|
||||
use anyhow::anyhow;
|
||||
use clap::ValueEnum;
|
||||
use anyhow::{anyhow, Context};
|
||||
use clap::builder::TypedValueParser;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use gtk4::prelude::ToValue;
|
||||
use merge::Merge;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::env;
|
||||
use std::collections::HashMap;
|
||||
use std::env::Args;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::{env, fs};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Serialize, Deserialize)]
|
||||
pub enum MatchMethod {
|
||||
|
|
@ -29,74 +32,180 @@ pub enum Align {
|
|||
Center,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Merge, Clone)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum Mode {
|
||||
/// searches $PATH for executables and allows them to be run by selecting them.
|
||||
Run,
|
||||
/// searches $XDG_DATA_HOME/applications and $XDG_DATA_DIRS/applications f
|
||||
/// or desktop files and allows them to be run by selecting them.
|
||||
Drun,
|
||||
|
||||
/// reads from stdin and displays options which when selected will be output to stdout.
|
||||
Dmenu,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ArgsError {
|
||||
#[error("input is not valid {0}")]
|
||||
InvalidParameter(String),
|
||||
}
|
||||
|
||||
impl FromStr for Mode {
|
||||
type Err = ArgsError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"run" => Ok(Mode::Run),
|
||||
"drun" => Ok(Mode::Drun),
|
||||
"dmenu" => Ok(Mode::Dmenu),
|
||||
_ => Err(ArgsError::InvalidParameter(
|
||||
format!("{s} is not a valid argument show this, see help for details").to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Parser)]
|
||||
#[clap(about = "Worf is a wofi clone written in rust, it aims to be a drop-in replacement")]
|
||||
pub struct Config {
|
||||
/// Defines the path to the stylesheet being used.
|
||||
/// Defaults to XDG_CONFIG_DIR/worf/style.css
|
||||
/// If XDG_CONFIG_DIR is not defined $HOME/.config will be used instead
|
||||
/// Forks the menu so you can close the terminal
|
||||
#[clap(short = 'f', long = "fork")]
|
||||
pub fork: Option<bool>,
|
||||
|
||||
/// Selects a config file to use
|
||||
#[clap(short = 'c', long = "conf")]
|
||||
pub config: Option<String>,
|
||||
|
||||
/// Runs in dmenu mode
|
||||
#[clap(short = 'd', long = "dmenu")]
|
||||
pub dmenu: Option<bool>,
|
||||
|
||||
/// Prints the version and then exits
|
||||
#[clap(short = 'v', long = "version")]
|
||||
pub version: Option<bool>,
|
||||
|
||||
/// Defines the style sheet to be loaded.
|
||||
/// Defaults to $XDG_CONF_DIR/worf/style.css
|
||||
/// or $HOME/.config/worf/style.css if XDG_CONF_DIR is not set.
|
||||
#[serde(default = "default_style")]
|
||||
#[clap(long = "style")]
|
||||
pub style: Option<String>,
|
||||
pub show: Option<String>,
|
||||
pub mode: Option<String>,
|
||||
|
||||
/// Defines the mode worf is running in
|
||||
#[clap(long = "show")]
|
||||
pub show: Option<Mode>,
|
||||
|
||||
/// Default width of the window, defaults to 50% of the screen
|
||||
#[serde(default = "default_width")]
|
||||
#[clap(long = "width")]
|
||||
pub width: Option<String>,
|
||||
|
||||
/// Default height of the window, defaults to 40% of the screen
|
||||
#[serde(default = "default_height")]
|
||||
#[clap(long = "height")]
|
||||
pub height: Option<String>,
|
||||
|
||||
#[clap(short = 'p', long = "prompt")]
|
||||
pub prompt: Option<String>,
|
||||
|
||||
#[clap(short = 'x', long = "xoffset")]
|
||||
pub xoffset: Option<i32>,
|
||||
|
||||
#[clap(long = "x")]
|
||||
pub x: Option<i32>,
|
||||
|
||||
#[clap(short = 'y', long = "yoffset")]
|
||||
pub yoffset: Option<i32>,
|
||||
|
||||
#[clap(long = "y")]
|
||||
pub y: Option<i32>,
|
||||
|
||||
/// If true a normal window instead of a layer shell will be used
|
||||
#[serde(default = "default_normal_window")]
|
||||
pub normal_window: Option<bool>,
|
||||
#[clap(short = 'n', long = "normal-window")]
|
||||
pub normal_window: bool,
|
||||
|
||||
#[clap(short = 'I', long = "allow-images")]
|
||||
pub allow_images: Option<bool>,
|
||||
|
||||
#[clap(short = 'm', long = "allow-markup")]
|
||||
pub allow_markup: Option<bool>,
|
||||
|
||||
#[clap(short = 'k', long = "cache-file")]
|
||||
pub cache_file: Option<String>,
|
||||
|
||||
#[clap(short = 't', long = "term")]
|
||||
pub term: Option<String>,
|
||||
|
||||
#[serde(default = "default_password_char")]
|
||||
#[clap(short = 'P', long = "password")]
|
||||
pub password: Option<String>,
|
||||
|
||||
#[clap(short = 'e', long = "exec-search")]
|
||||
pub exec_search: Option<bool>,
|
||||
|
||||
#[clap(short = 'b', long = "hide-scroll")]
|
||||
pub hide_scroll: Option<bool>,
|
||||
|
||||
/// Defines how matching is done
|
||||
#[serde(default = "default_match_method")]
|
||||
#[clap(short = 'M', long = "matching")]
|
||||
pub matching: Option<MatchMethod>,
|
||||
|
||||
#[clap(short = 'i', long = "insensitive")]
|
||||
pub insensitive: Option<bool>,
|
||||
|
||||
#[clap(short = 'q', long = "parse-search")]
|
||||
pub parse_search: Option<bool>,
|
||||
|
||||
#[clap(short = 'l', long = "location")]
|
||||
pub location: Option<String>,
|
||||
|
||||
#[clap(short = 'a', long = "no-actions")]
|
||||
pub no_actions: Option<bool>,
|
||||
|
||||
#[clap(short = 'L', long = "lines")]
|
||||
pub lines: Option<u32>,
|
||||
/// Defines how many columns are shown per row
|
||||
|
||||
#[serde(default = "default_columns")]
|
||||
#[clap(short = 'w', long = "columns")]
|
||||
pub columns: Option<u32>,
|
||||
|
||||
#[clap(short = 'O', long = "sort-order")]
|
||||
pub sort_order: Option<String>,
|
||||
|
||||
#[clap(short = 'G', long = "gtk-dark")]
|
||||
pub gtk_dark: Option<bool>,
|
||||
|
||||
#[clap(short = 'Q', long = "search")]
|
||||
pub search: Option<String>,
|
||||
|
||||
#[clap(short = 'o', long = "monitor")]
|
||||
pub monitor: Option<String>,
|
||||
|
||||
#[clap(short = 'r', long = "pre-display-cmd")]
|
||||
pub pre_display_cmd: Option<String>,
|
||||
/// Defines how the entries root container are ordered
|
||||
/// Default is vertical
|
||||
|
||||
#[serde(default = "default_orientation")]
|
||||
#[clap(long = "orientation")]
|
||||
pub orientation: Option<Orientation>,
|
||||
/// Specifies the horizontal align for the entire scrolled area,
|
||||
/// it can be any of fill, start, end, or center, default is fill.
|
||||
|
||||
#[serde(default = "default_halign")]
|
||||
#[clap(long = "halign")]
|
||||
pub halign: Option<Align>,
|
||||
//// Specifies the horizontal align for the individual entries,
|
||||
// it can be any of fill, start, end, or center, default is fill.
|
||||
|
||||
#[serde(default = "default_content_halign")]
|
||||
#[clap(long = "content-halign")]
|
||||
pub content_halign: Option<Align>,
|
||||
|
||||
/// Specifies the vertical align for the entire scrolled area, it can be any of fill, start, e
|
||||
/// nd, or center, the default is orientation dependent. If vertical then it defaults to
|
||||
/// start, if horizontal it defaults to center.
|
||||
#[clap(long = "valign")]
|
||||
pub valign: Option<Align>,
|
||||
|
||||
pub filter_rate: Option<u32>,
|
||||
/// Specifies the image size when enabled.
|
||||
/// Defaults to 32.
|
||||
|
||||
#[serde(default = "default_image_size")]
|
||||
#[clap(long = "image-size")]
|
||||
pub image_size: Option<i32>,
|
||||
|
||||
pub key_up: Option<String>,
|
||||
pub key_down: Option<String>,
|
||||
pub key_left: Option<String>,
|
||||
|
|
@ -110,8 +219,10 @@ pub struct Config {
|
|||
pub key_expand: Option<String>,
|
||||
pub key_hide_search: Option<String>,
|
||||
pub key_copy: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub custom_keys: Option<std::collections::HashMap<String, String>>,
|
||||
|
||||
// todo re-add this
|
||||
// #[serde(flatten)]
|
||||
// pub key_custom: Option<HashMap<String, String>>,
|
||||
pub line_wrap: Option<String>,
|
||||
pub global_coords: Option<bool>,
|
||||
pub hide_search: Option<bool>,
|
||||
|
|
@ -121,25 +232,39 @@ pub struct Config {
|
|||
pub single_click: Option<bool>,
|
||||
pub pre_display_exec: Option<bool>,
|
||||
|
||||
// Exclusive options
|
||||
/// Minimum score for the fuzzy finder to accept a match.
|
||||
/// Must be a value between 0 and 1
|
||||
/// Defaults to 0.1.
|
||||
/// Minimum score for a fuzzy search to be shown
|
||||
#[serde(default = "default_fuzzy_min_score")]
|
||||
#[clap(long = "fuzzy-min-score")]
|
||||
pub fuzzy_min_score: Option<f64>,
|
||||
|
||||
/// Defines how the content in the row box is aligned
|
||||
/// Defaults to vertical
|
||||
/// Orientation of items in the row box where items are displayed
|
||||
#[serde(default = "default_row_box_orientation")]
|
||||
#[clap(long = "row-box-orientation")]
|
||||
pub row_bow_orientation: Option<Orientation>,
|
||||
|
||||
/// Set to to true to wrap text after a given amount of chars
|
||||
#[serde(default = "default_text_wrap")]
|
||||
#[clap(long = "text-wrap")]
|
||||
pub text_wrap: Option<bool>,
|
||||
|
||||
/// Defines after how many chars a line is broken over.
|
||||
/// Only cuts at spaces.
|
||||
#[serde(default = "default_text_wrap_length")]
|
||||
#[clap(long = "text-wrap-length")]
|
||||
pub text_wrap_length: Option<usize>,
|
||||
|
||||
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
fork: None,
|
||||
config: None,
|
||||
dmenu: None,
|
||||
version: None,
|
||||
style: default_style(),
|
||||
show: None,
|
||||
mode: None,
|
||||
width: default_width(),
|
||||
height: default_height(),
|
||||
prompt: None,
|
||||
|
|
@ -147,7 +272,7 @@ impl Default for Config {
|
|||
x: None,
|
||||
yoffset: None,
|
||||
y: None,
|
||||
normal_window: None,
|
||||
normal_window: default_normal_window(),
|
||||
allow_images: None,
|
||||
allow_markup: None,
|
||||
cache_file: None,
|
||||
|
|
@ -186,7 +311,7 @@ impl Default for Config {
|
|||
key_expand: None,
|
||||
key_hide_search: None,
|
||||
key_copy: None,
|
||||
custom_keys: None,
|
||||
//key_custom: None,
|
||||
line_wrap: None,
|
||||
global_coords: None,
|
||||
hide_search: None,
|
||||
|
|
@ -197,6 +322,8 @@ impl Default for Config {
|
|||
pre_display_exec: None,
|
||||
fuzzy_min_score: default_fuzzy_min_score(),
|
||||
row_bow_orientation: default_row_box_orientation(),
|
||||
text_wrap: default_text_wrap(),
|
||||
text_wrap_length: default_text_wrap_length(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -205,7 +332,7 @@ fn default_row_box_orientation() -> Option<Orientation> {
|
|||
Some(Orientation::Horizontal)
|
||||
}
|
||||
|
||||
fn default_orientation() -> Option<Orientation> {
|
||||
pub(crate) fn default_orientation() -> Option<Orientation> {
|
||||
Some(Orientation::Vertical)
|
||||
}
|
||||
|
||||
|
|
@ -221,8 +348,8 @@ fn default_columns() -> Option<u32> {
|
|||
Some(1)
|
||||
}
|
||||
|
||||
fn default_normal_window() -> Option<bool> {
|
||||
Some(false)
|
||||
fn default_normal_window() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
|
@ -303,7 +430,7 @@ fn default_normal_window() -> Option<bool> {
|
|||
// char* key_copy = (i == 0) ? key_default : config_get(config, "key_copy", key_default);
|
||||
|
||||
fn default_style() -> Option<String> {
|
||||
system::config_path(None)
|
||||
style_path(None)
|
||||
.ok()
|
||||
.and_then(|pb| Some(pb.display().to_string()))
|
||||
.or_else(|| {
|
||||
|
|
@ -340,7 +467,89 @@ pub fn default_image_size() -> Option<i32> {
|
|||
Some(32)
|
||||
}
|
||||
|
||||
pub fn merge_config_with_args(config: &mut Config, args: &Args) -> anyhow::Result<Config> {
|
||||
pub fn default_text_wrap_length() -> Option<usize> {
|
||||
Some(15)
|
||||
}
|
||||
|
||||
pub fn default_text_wrap() -> Option<bool> {
|
||||
Some(false)
|
||||
}
|
||||
|
||||
pub fn parse_args() -> Config {
|
||||
Config::parse()
|
||||
}
|
||||
|
||||
|
||||
pub fn style_path(full_path: Option<String>) -> Result<PathBuf, anyhow::Error> {
|
||||
let alternative_paths = path_alternatives(vec![dirs::config_dir()], PathBuf::from("worf").join("style.css"));
|
||||
resolve_path(
|
||||
full_path,
|
||||
alternative_paths
|
||||
.into_iter()
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn path_alternatives(base_paths: Vec<Option<PathBuf>>, sub_path: PathBuf) -> Vec<PathBuf> {
|
||||
base_paths
|
||||
.into_iter()
|
||||
.filter_map(|s| s)
|
||||
.map(|pb| pb.join(&sub_path))
|
||||
.filter_map(|pb| pb.canonicalize().ok())
|
||||
.filter(|c| c.exists())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn resolve_path(
|
||||
full_path: Option<String>,
|
||||
alternatives: Vec<PathBuf>,
|
||||
) -> Result<PathBuf, anyhow::Error> {
|
||||
full_path
|
||||
.map(PathBuf::from)
|
||||
.and_then(|p| p.canonicalize().ok().filter(|c| c.exists()))
|
||||
.or_else(|| {
|
||||
alternatives
|
||||
.into_iter()
|
||||
.filter(|p| p.exists())
|
||||
.find_map(|pb| pb.canonicalize().ok().filter(|c| c.exists()))
|
||||
})
|
||||
.ok_or_else(|| anyhow!("Could not find a valid config file."))
|
||||
}
|
||||
|
||||
pub fn load_config(args_opt: Option<Config>) -> Result<Config, anyhow::Error> {
|
||||
let home_dir = env::var("HOME")?;
|
||||
let config_path = args_opt.as_ref().map(|c| {
|
||||
c.config
|
||||
.as_ref()
|
||||
.and_then(|p| Some(PathBuf::from(p)))
|
||||
.unwrap_or_else(|| {
|
||||
env::var("XDG_CONF_HOME")
|
||||
.map_or(
|
||||
PathBuf::from(home_dir.clone()).join(".config"),
|
||||
|xdg_conf_home| PathBuf::from(&xdg_conf_home),
|
||||
)
|
||||
.join("worf")
|
||||
.join("config")
|
||||
})
|
||||
});
|
||||
|
||||
match config_path {
|
||||
Some(path) => {
|
||||
let toml_content = fs::read_to_string(path)?;
|
||||
let mut config: Config = toml::from_str(&toml_content)?;
|
||||
|
||||
if let Some(args) = args_opt {
|
||||
let merge_result = merge_config_with_args(&mut config, &args)?;
|
||||
Ok(merge_result)
|
||||
} else {
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
None => Err(anyhow!("No config file found")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge_config_with_args(config: &mut Config, args: &Config) -> anyhow::Result<Config> {
|
||||
let args_json = serde_json::to_value(args)?;
|
||||
let mut config_json = serde_json::to_value(config)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ use log::{debug, error, info};
|
|||
use std::process::exit;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
type ArcMenuMap = Arc<Mutex<HashMap<FlowBoxChild, MenuItem>>>;
|
||||
type MenuItemSender = Sender<Result<MenuItem, anyhow::Error>>;
|
||||
type ArcMenuMap<T> = Arc<Mutex<HashMap<FlowBoxChild, MenuItem<T>>>>;
|
||||
type MenuItemSender<T> = Sender<Result<MenuItem<T>, anyhow::Error>>;
|
||||
|
||||
impl Into<Orientation> for config::Orientation {
|
||||
fn into(self) -> Orientation {
|
||||
|
|
@ -49,17 +49,20 @@ impl Into<Align> for config::Align {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MenuItem {
|
||||
pub struct MenuItem<T> {
|
||||
pub label: String, // todo support empty label?
|
||||
pub icon_path: Option<String>,
|
||||
pub action: Option<String>,
|
||||
pub sub_elements: Vec<MenuItem>,
|
||||
pub sub_elements: Vec<MenuItem<T>>,
|
||||
pub working_dir: Option<String>,
|
||||
pub initial_sort_score: i64,
|
||||
pub search_sort_score: f64,
|
||||
|
||||
/// Allows to store arbitrary additional information
|
||||
pub data: Option<T>,
|
||||
}
|
||||
|
||||
pub fn show(config: Config, elements: Vec<MenuItem>) -> Result<MenuItem, anyhow::Error> {
|
||||
pub fn show<T>(config: Config, elements: Vec<MenuItem<T>>) -> Result<MenuItem<T>, anyhow::Error> where T: Clone + 'static {
|
||||
if let Some(ref css) = config.style {
|
||||
let provider = CssProvider::new();
|
||||
let css_file_path = File::for_path(css);
|
||||
|
|
@ -85,12 +88,12 @@ pub fn show(config: Config, elements: Vec<MenuItem>) -> Result<MenuItem, anyhow:
|
|||
selection
|
||||
}
|
||||
|
||||
fn build_ui(
|
||||
fn build_ui<T>(
|
||||
config: &Config,
|
||||
elements: &Vec<MenuItem>,
|
||||
sender: Sender<Result<MenuItem, anyhow::Error>>,
|
||||
elements: &Vec<MenuItem<T>>,
|
||||
sender: Sender<Result<MenuItem<T>, anyhow::Error>>,
|
||||
app: &Application,
|
||||
) {
|
||||
) where T: Clone + 'static {
|
||||
// Create a toplevel undecorated window
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(app)
|
||||
|
|
@ -102,15 +105,13 @@ fn build_ui(
|
|||
|
||||
window.set_widget_name("window");
|
||||
|
||||
config.normal_window.map(|normal| {
|
||||
if !normal {
|
||||
// Initialize the window as a layer
|
||||
window.init_layer_shell();
|
||||
window.set_layer(gtk4_layer_shell::Layer::Overlay);
|
||||
window.set_keyboard_mode(KeyboardMode::Exclusive);
|
||||
window.set_namespace(Some("worf"));
|
||||
}
|
||||
});
|
||||
if !config.normal_window {
|
||||
// Initialize the window as a layer
|
||||
window.init_layer_shell();
|
||||
window.set_layer(gtk4_layer_shell::Layer::Overlay);
|
||||
window.set_keyboard_mode(KeyboardMode::Exclusive);
|
||||
window.set_namespace(Some("worf"));
|
||||
}
|
||||
|
||||
let outer_box = gtk4::Box::new(config.orientation.unwrap().into(), 0);
|
||||
outer_box.set_widget_name("outer-box");
|
||||
|
|
@ -159,7 +160,7 @@ fn build_ui(
|
|||
inner_box.set_max_children_per_line(config.columns.unwrap());
|
||||
inner_box.set_activate_on_single_click(true);
|
||||
|
||||
let mut list_items: ArcMenuMap = Arc::new(Mutex::new(HashMap::new()));
|
||||
let mut list_items: ArcMenuMap<T> = Arc::new(Mutex::new(HashMap::new()));
|
||||
for entry in elements {
|
||||
list_items
|
||||
.lock()
|
||||
|
|
@ -221,13 +222,13 @@ fn build_ui(
|
|||
});
|
||||
}
|
||||
|
||||
fn setup_key_event_handler(
|
||||
fn setup_key_event_handler<T: Clone + 'static>(
|
||||
window: &ApplicationWindow,
|
||||
entry_clone: SearchEntry,
|
||||
inner_box: FlowBox,
|
||||
app: Application,
|
||||
sender: MenuItemSender,
|
||||
list_items: Arc<Mutex<HashMap<FlowBoxChild, MenuItem>>>,
|
||||
sender: MenuItemSender<T>,
|
||||
list_items: Arc<Mutex<HashMap<FlowBoxChild, MenuItem<T>>>>,
|
||||
config: Config,
|
||||
) {
|
||||
let key_controller = EventControllerKey::new();
|
||||
|
|
@ -269,10 +270,10 @@ fn setup_key_event_handler(
|
|||
window.add_controller(key_controller);
|
||||
}
|
||||
|
||||
fn sort_menu_items(
|
||||
fn sort_menu_items<T>(
|
||||
child1: &FlowBoxChild,
|
||||
child2: &FlowBoxChild,
|
||||
items_lock: &Mutex<HashMap<FlowBoxChild, MenuItem>>,
|
||||
items_lock: &Mutex<HashMap<FlowBoxChild, MenuItem<T>>>,
|
||||
) -> Ordering {
|
||||
let lock = items_lock.lock().unwrap();
|
||||
let m1 = lock.get(child1);
|
||||
|
|
@ -281,13 +282,13 @@ fn sort_menu_items(
|
|||
match (m1, m2) {
|
||||
(Some(menu1), Some(menu2)) => {
|
||||
if menu1.search_sort_score != 0.0 || menu2.search_sort_score != 0.0 {
|
||||
if menu1.search_sort_score > menu2.search_sort_score {
|
||||
if menu1.search_sort_score < menu2.search_sort_score {
|
||||
Ordering::Smaller
|
||||
} else {
|
||||
Ordering::Larger
|
||||
}
|
||||
} else {
|
||||
if menu1.initial_sort_score > menu2.initial_sort_score {
|
||||
if menu1.initial_sort_score < menu2.initial_sort_score {
|
||||
Ordering::Smaller
|
||||
} else {
|
||||
Ordering::Larger
|
||||
|
|
@ -300,12 +301,12 @@ fn sort_menu_items(
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_selected_item(
|
||||
sender: &MenuItemSender,
|
||||
fn handle_selected_item<T>(
|
||||
sender: &MenuItemSender<T>,
|
||||
app: &Application,
|
||||
inner_box: &FlowBox,
|
||||
lock_arc: &ArcMenuMap,
|
||||
) -> Result<(), String> {
|
||||
lock_arc: &ArcMenuMap<T>,
|
||||
) -> Result<(), String> where T: Clone {
|
||||
for s in inner_box.selected_children() {
|
||||
let list_items = lock_arc.lock().unwrap();
|
||||
let item = list_items.get(&s);
|
||||
|
|
@ -320,12 +321,12 @@ fn handle_selected_item(
|
|||
Err("selected item cannot be resolved".to_owned())
|
||||
}
|
||||
|
||||
fn add_menu_item(
|
||||
fn add_menu_item<T: Clone + 'static>(
|
||||
inner_box: &FlowBox,
|
||||
entry_element: &MenuItem,
|
||||
entry_element: &MenuItem<T>,
|
||||
config: &Config,
|
||||
sender: MenuItemSender,
|
||||
lock_arc: ArcMenuMap,
|
||||
sender: MenuItemSender<T>,
|
||||
lock_arc: ArcMenuMap<T>,
|
||||
app: Application,
|
||||
) -> FlowBoxChild {
|
||||
let parent: Widget = if !entry_element.sub_elements.is_empty() {
|
||||
|
|
@ -390,11 +391,11 @@ fn add_menu_item(
|
|||
child
|
||||
}
|
||||
|
||||
fn create_menu_row(
|
||||
menu_item: &MenuItem,
|
||||
fn create_menu_row<T: Clone + 'static>(
|
||||
menu_item: &MenuItem<T>,
|
||||
config: &Config,
|
||||
lock_arc: ArcMenuMap,
|
||||
sender: MenuItemSender,
|
||||
lock_arc: ArcMenuMap<T>,
|
||||
sender: MenuItemSender<T>,
|
||||
app: Application,
|
||||
inner_box: FlowBox,
|
||||
) -> Widget {
|
||||
|
|
@ -434,7 +435,13 @@ fn create_menu_row(
|
|||
}
|
||||
|
||||
// todo make max length configurable
|
||||
let label = Label::new(Some(&wrap_text(&menu_item.label, 15)));
|
||||
let text = if config.text_wrap.is_some_and(|x| x == true) {
|
||||
&wrap_text(&menu_item.label, config.text_wrap_length)
|
||||
} else {
|
||||
menu_item.label.as_str()
|
||||
};
|
||||
|
||||
let label = Label::new(Some(text));
|
||||
label.set_hexpand(true);
|
||||
label.set_widget_name("label");
|
||||
label.set_wrap(true);
|
||||
|
|
@ -448,9 +455,9 @@ fn create_menu_row(
|
|||
row.upcast()
|
||||
}
|
||||
|
||||
fn filter_widgets(
|
||||
fn filter_widgets<T>(
|
||||
query: &str,
|
||||
items: &mut HashMap<FlowBoxChild, MenuItem>,
|
||||
items: &mut HashMap<FlowBoxChild, MenuItem<T>>,
|
||||
config: &Config,
|
||||
inner_box: &FlowBox,
|
||||
) {
|
||||
|
|
@ -537,9 +544,9 @@ fn percent_or_absolute(value: &String, base_value: i32) -> Option<i32> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn initialize_sort_scores(items: &mut Vec<MenuItem>) {
|
||||
pub fn initialize_sort_scores<T>(items: &mut Vec<MenuItem<T>>) {
|
||||
let mut regular_score = items.len() as i64;
|
||||
items.sort_by(|l, r| r.label.cmp(&l.label));
|
||||
items.sort_by(|l, r| l.label.cmp(&r.label));
|
||||
|
||||
for item in items.iter_mut() {
|
||||
if item.initial_sort_score == 0 {
|
||||
|
|
@ -549,12 +556,13 @@ pub fn initialize_sort_scores(items: &mut Vec<MenuItem>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn wrap_text(text: &str, line_length: usize) -> String {
|
||||
fn wrap_text(text: &str, line_length: Option<usize>) -> String {
|
||||
let mut result = String::new();
|
||||
let mut line = String::new();
|
||||
let len = line_length.unwrap_or(text.len());
|
||||
|
||||
for word in text.split_whitespace() {
|
||||
if line.len() + word.len() + 1 > line_length {
|
||||
if line.len() + word.len() + 1 > len {
|
||||
if !line.is_empty() {
|
||||
result.push_str(&line.trim_end());
|
||||
result.push('\n');
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@ pub mod config;
|
|||
pub mod desktop;
|
||||
pub mod gui;
|
||||
pub mod system;
|
||||
pub mod mode;
|
||||
|
|
|
|||
211
src/lib/mode.rs
Normal file
211
src/lib/mode.rs
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
use crate::lib::config::Config;
|
||||
use crate::lib::desktop::{default_icon, find_desktop_files, get_locale_variants};
|
||||
use crate::lib::gui;
|
||||
use crate::lib::gui::MenuItem;
|
||||
use crate::lookup_name_with_locale;
|
||||
use anyhow::{Context, anyhow};
|
||||
use freedesktop_file_parser::EntryType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::os::unix::prelude::CommandExt;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::{env, fs, io};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
struct DRunCache {
|
||||
desktop_entry: String,
|
||||
run_count: usize,
|
||||
}
|
||||
|
||||
pub fn d_run(mut config: Config) -> anyhow::Result<()> {
|
||||
let locale_variants = get_locale_variants();
|
||||
let default_icon = default_icon();
|
||||
|
||||
let cache_path = dirs::cache_dir().map(|x| x.join("worf-drun"));
|
||||
let mut d_run_cache = {
|
||||
if let Some(ref cache_path) = cache_path {
|
||||
if let Err(e) = create_file_if_not_exists(cache_path) {
|
||||
log::warn!("No drun cache file and cannot create: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
load_cache_file(&cache_path).unwrap_or_default()
|
||||
};
|
||||
|
||||
let mut entries: Vec<MenuItem<String>> = Vec::new();
|
||||
for file in find_desktop_files().iter().filter(|f| {
|
||||
f.entry.hidden.map_or(true, |hidden| !hidden)
|
||||
&& f.entry.no_display.map_or(true, |no_display| !no_display)
|
||||
}) {
|
||||
let (action, working_dir) = match &file.entry.entry_type {
|
||||
EntryType::Application(app) => (app.exec.clone(), app.path.clone()),
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
let name = match lookup_name_with_locale(
|
||||
&locale_variants,
|
||||
&file.entry.name.variants,
|
||||
&file.entry.name.default,
|
||||
) {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
log::debug!("Skipping desktop entry without name {file:?}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let icon = file
|
||||
.entry
|
||||
.icon
|
||||
.as_ref()
|
||||
.map(|s| s.content.clone())
|
||||
.or(Some(default_icon.clone()));
|
||||
log::debug!("file, name={name:?}, icon={icon:?}, action={action:?}");
|
||||
let sort_score = d_run_cache.get(&name).unwrap_or(&0);
|
||||
|
||||
let mut entry: MenuItem<String> = MenuItem {
|
||||
label: name,
|
||||
icon_path: icon.clone(),
|
||||
action,
|
||||
sub_elements: Vec::default(),
|
||||
working_dir: working_dir.clone(),
|
||||
initial_sort_score: -(*sort_score),
|
||||
search_sort_score: 0.0,
|
||||
data: None,
|
||||
};
|
||||
|
||||
file.actions.iter().for_each(|(_, action)| {
|
||||
let action_name = lookup_name_with_locale(
|
||||
&locale_variants,
|
||||
&action.name.variants,
|
||||
&action.name.default,
|
||||
);
|
||||
let action_icon = action
|
||||
.icon
|
||||
.as_ref()
|
||||
.map(|s| s.content.clone())
|
||||
.or(icon.as_ref().map(|s| s.clone()));
|
||||
|
||||
log::debug!("sub, action_name={action_name:?}, action_icon={action_icon:?}");
|
||||
|
||||
let sub_entry = MenuItem {
|
||||
label: action_name.unwrap().trim().to_owned(),
|
||||
icon_path: action_icon,
|
||||
action: action.exec.clone(),
|
||||
sub_elements: Vec::default(),
|
||||
working_dir: working_dir.clone(),
|
||||
initial_sort_score: 0, // subitems are never sorted right now.
|
||||
search_sort_score: 0.0,
|
||||
data: None,
|
||||
};
|
||||
entry.sub_elements.push(sub_entry);
|
||||
});
|
||||
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
gui::initialize_sort_scores(&mut entries);
|
||||
|
||||
// todo ues a arc instead of cloning the config
|
||||
let selection_result = gui::show(config.clone(), entries.clone());
|
||||
match selection_result {
|
||||
Ok(selected_item) => {
|
||||
if let Some(cache) = cache_path {
|
||||
*d_run_cache.entry(selected_item.label).or_insert(0) += 1;
|
||||
if let Err(e) = save_cache_file(&cache, d_run_cache) {
|
||||
log::warn!("cannot save drun cache {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(action) = selected_item.action {
|
||||
spawn_fork(&action, &selected_item.working_dir)?
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save_cache_file(path: &PathBuf, data: HashMap<String, i64>) -> anyhow::Result<()> {
|
||||
// Convert the HashMap to TOML string
|
||||
let toml_string = toml::ser::to_string(&data).map_err(|e| anyhow::anyhow!(e))?;
|
||||
fs::write(path, toml_string).map_err(|e| anyhow::anyhow!(e))
|
||||
}
|
||||
|
||||
fn load_cache_file(cache_path: &Option<PathBuf>) -> anyhow::Result<HashMap<String, i64>> {
|
||||
let path = match cache_path {
|
||||
Some(p) => p,
|
||||
None => return Err(anyhow!("Cache is missing")),
|
||||
};
|
||||
|
||||
let toml_content = fs::read_to_string(path)?;
|
||||
let parsed: toml::Value = toml_content.parse().expect("Failed to parse TOML");
|
||||
|
||||
let mut result: HashMap<String, i64> = HashMap::new();
|
||||
if let toml::Value::Table(table) = parsed {
|
||||
for (key, val) in table {
|
||||
if let toml::Value::Integer(i) = val {
|
||||
result.insert(key, i);
|
||||
} else {
|
||||
log::warn!("Skipping key '{}' because it's not an integer", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn create_file_if_not_exists(path: &PathBuf) -> anyhow::Result<()> {
|
||||
let file = fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&path);
|
||||
|
||||
match file {
|
||||
Ok(_) => Ok(()),
|
||||
|
||||
Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()),
|
||||
Err(e) => Err(e).context(format!("Failed to create file {}", path.display()))?,
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_fork(cmd: &str, working_dir: &Option<String>) -> anyhow::Result<()> {
|
||||
// todo probably remove arguments?
|
||||
// todo support working dir
|
||||
// todo fix actions
|
||||
// todo graphical disk map icon not working
|
||||
// Unix-like systems (Linux, macOS)
|
||||
|
||||
let parts = cmd.split(' ').collect::<Vec<_>>();
|
||||
if parts.is_empty() {
|
||||
return Err(anyhow!("empty command passed"));
|
||||
}
|
||||
|
||||
if let Some(dir) = working_dir {
|
||||
env::set_current_dir(dir)?;
|
||||
}
|
||||
|
||||
let exec = parts[0];
|
||||
let args: Vec<_> = parts
|
||||
.iter()
|
||||
.skip(1)
|
||||
.filter(|arg| !arg.starts_with("%"))
|
||||
.collect();
|
||||
|
||||
unsafe {
|
||||
let _ = Command::new(exec)
|
||||
.args(args)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.pre_exec(|| {
|
||||
libc::setsid();
|
||||
Ok(())
|
||||
})
|
||||
.spawn();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,31 +1,3 @@
|
|||
use anyhow::anyhow;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn home_dir() -> Result<String, anyhow::Error> {
|
||||
env::var("HOME").map_err(|e| anyhow::anyhow!("$HOME not set: {e}"))
|
||||
}
|
||||
|
||||
pub fn conf_home() -> Result<String, anyhow::Error> {
|
||||
env::var("XDG_CONF_HOME").map_err(|e| anyhow::anyhow!("XDG_CONF_HOME not set: {e}"))
|
||||
}
|
||||
|
||||
pub fn config_path(config_path: Option<String>) -> Result<PathBuf, anyhow::Error> {
|
||||
config_path
|
||||
.map(PathBuf::from)
|
||||
.and_then(|p| p.canonicalize().ok().filter(|c| c.exists()))
|
||||
.or_else(|| {
|
||||
[
|
||||
conf_home().ok().map(PathBuf::from),
|
||||
home_dir()
|
||||
.ok()
|
||||
.map(PathBuf::from)
|
||||
.map(|c| c.join(".config")),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(|base| base.join("worf").join("style.css"))
|
||||
.find_map(|p| p.canonicalize().ok())
|
||||
})
|
||||
.ok_or_else(|| anyhow!("Could not find a valid config file."))
|
||||
}
|
||||
|
|
|
|||
182
src/main.rs
182
src/main.rs
|
|
@ -3,10 +3,9 @@
|
|||
|
||||
// todo resolve paths like ~/
|
||||
|
||||
use crate::args::{Args, Mode};
|
||||
use crate::lib::config::{Config, merge_config_with_args};
|
||||
use crate::lib::config::Config;
|
||||
use crate::lib::desktop::{default_icon, find_desktop_files, get_locale_variants};
|
||||
use crate::lib::gui;
|
||||
use crate::lib::{config, gui, mode};
|
||||
use crate::lib::gui::MenuItem;
|
||||
use anyhow::{Error, anyhow};
|
||||
use clap::Parser;
|
||||
|
|
@ -18,7 +17,6 @@ use gtk4::prelude::{
|
|||
};
|
||||
use gtk4_layer_shell::LayerShell;
|
||||
use log::{debug, info, warn};
|
||||
use merge::Merge;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
|
@ -28,9 +26,9 @@ use std::sync::Arc;
|
|||
use std::thread::sleep;
|
||||
use std::{env, fs, time};
|
||||
|
||||
mod args;
|
||||
mod lib;
|
||||
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
gtk4::init()?;
|
||||
|
||||
|
|
@ -38,43 +36,23 @@ fn main() -> anyhow::Result<()> {
|
|||
// todo change to error as default
|
||||
.parse_filters(&env::var("RUST_LOG").unwrap_or_else(|_| "debug".to_owned()))
|
||||
.init();
|
||||
let args = Args::parse();
|
||||
|
||||
let home_dir = env::var("HOME")?;
|
||||
let config_path = args
|
||||
.config
|
||||
.as_ref()
|
||||
.map(|c| PathBuf::from(c))
|
||||
.unwrap_or_else(|| {
|
||||
env::var("XDG_CONF_HOME")
|
||||
.map_or(
|
||||
PathBuf::from(home_dir.clone()).join(".config"),
|
||||
|xdg_conf_home| PathBuf::from(&xdg_conf_home),
|
||||
)
|
||||
.join("worf")
|
||||
.join("config")
|
||||
});
|
||||
let args = config::parse_args();
|
||||
let config = config::load_config(Some(args))?;
|
||||
|
||||
let drun_cache = env::var("XDG_CACHE_HOME")
|
||||
.map_or(
|
||||
PathBuf::from(home_dir.clone()).join(".cache"),
|
||||
|xdg_conf_home| PathBuf::from(&xdg_conf_home),
|
||||
)
|
||||
.join("worf-drun");
|
||||
|
||||
let toml_content = fs::read_to_string(config_path)?;
|
||||
let mut config: Config = toml::from_str(&toml_content)?; // todo bail out properly
|
||||
let config = merge_config_with_args(&mut config, &args)?;
|
||||
|
||||
match args.mode {
|
||||
Mode::Run => {}
|
||||
Mode::Drun => {
|
||||
drun(config)?;
|
||||
if let Some(show) = &config.show {
|
||||
match show {
|
||||
config::Mode::Run => {}
|
||||
config::Mode::Drun => {
|
||||
mode::d_run(config)?;
|
||||
}
|
||||
config::Mode::Dmenu => {}
|
||||
}
|
||||
Mode::Dmenu => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("No mode provided"))
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_name_with_locale(
|
||||
|
|
@ -90,135 +68,7 @@ fn lookup_name_with_locale(
|
|||
.or_else(|| Some(fallback.to_owned()))
|
||||
}
|
||||
|
||||
fn drun(mut config: Config) -> anyhow::Result<()> {
|
||||
let mut entries: Vec<MenuItem> = Vec::new();
|
||||
let locale_variants = get_locale_variants();
|
||||
let default_icon = default_icon();
|
||||
|
||||
for file in find_desktop_files().iter().filter(|f| {
|
||||
f.entry.hidden.map_or(true, |hidden| !hidden)
|
||||
&& f.entry.no_display.map_or(true, |no_display| !no_display)
|
||||
}) {
|
||||
let (action, working_dir) = match &file.entry.entry_type {
|
||||
EntryType::Application(app) => (app.exec.clone(), app.path.clone()),
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
let name = lookup_name_with_locale(
|
||||
&locale_variants,
|
||||
&file.entry.name.variants,
|
||||
&file.entry.name.default,
|
||||
);
|
||||
if name.is_none() {
|
||||
debug!("Skipping desktop entry without name {file:?}")
|
||||
}
|
||||
|
||||
let icon = file
|
||||
.entry
|
||||
.icon
|
||||
.as_ref()
|
||||
.map(|s| s.content.clone())
|
||||
.or(Some(default_icon.clone()));
|
||||
debug!("file, name={name:?}, icon={icon:?}, action={action:?}");
|
||||
let mut sort_score = 0.0;
|
||||
if name.as_ref().unwrap().contains("ox") {
|
||||
sort_score = 999.0;
|
||||
}
|
||||
|
||||
let mut entry = MenuItem {
|
||||
label: name.unwrap(),
|
||||
icon_path: icon.clone(),
|
||||
action,
|
||||
sub_elements: Vec::default(),
|
||||
working_dir: working_dir.clone(),
|
||||
initial_sort_score: 0,
|
||||
search_sort_score: sort_score,
|
||||
};
|
||||
|
||||
file.actions.iter().for_each(|(_, action)| {
|
||||
let action_name = lookup_name_with_locale(
|
||||
&locale_variants,
|
||||
&action.name.variants,
|
||||
&action.name.default,
|
||||
);
|
||||
let action_icon = action
|
||||
.icon
|
||||
.as_ref()
|
||||
.map(|s| s.content.clone())
|
||||
.or(icon.as_ref().map(|s| s.clone()));
|
||||
|
||||
debug!("sub, action_name={action_name:?}, action_icon={action_icon:?}");
|
||||
|
||||
let sub_entry = MenuItem {
|
||||
label: action_name.unwrap().trim().to_owned(),
|
||||
icon_path: action_icon,
|
||||
action: action.exec.clone(),
|
||||
sub_elements: Vec::default(),
|
||||
working_dir: working_dir.clone(),
|
||||
initial_sort_score: 0,
|
||||
search_sort_score: 0.0,
|
||||
};
|
||||
entry.sub_elements.push(sub_entry);
|
||||
});
|
||||
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
gui::initialize_sort_scores(&mut entries);
|
||||
|
||||
// todo ues a arc instead of cloning the config
|
||||
let selection_result = gui::show(config.clone(), entries.clone());
|
||||
match selection_result {
|
||||
Ok(selected_item) => {
|
||||
if let Some(action) = selected_item.action {
|
||||
spawn_fork(&action, &selected_item.working_dir)?
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_fork(cmd: &str, working_dir: &Option<String>) -> anyhow::Result<()> {
|
||||
// todo probably remove arguments?
|
||||
// todo support working dir
|
||||
// todo fix actions
|
||||
// todo graphical disk map icon not working
|
||||
// Unix-like systems (Linux, macOS)
|
||||
|
||||
let parts = cmd.split(' ').collect::<Vec<_>>();
|
||||
if parts.is_empty() {
|
||||
return Err(anyhow!("empty command passed"));
|
||||
}
|
||||
|
||||
if let Some(dir) = working_dir {
|
||||
env::set_current_dir(dir)?;
|
||||
}
|
||||
|
||||
let exec = parts[0];
|
||||
let args: Vec<_> = parts
|
||||
.iter()
|
||||
.skip(1)
|
||||
.filter(|arg| !arg.starts_with("%"))
|
||||
.collect();
|
||||
|
||||
unsafe {
|
||||
let _ = Command::new(exec)
|
||||
.args(args)
|
||||
.stdin(Stdio::null()) // Disconnect stdin
|
||||
.stdout(Stdio::null()) // Disconnect stdout
|
||||
.stderr(Stdio::null()) // Disconnect stderr
|
||||
.pre_exec(|| {
|
||||
libc::setsid();
|
||||
Ok(())
|
||||
})
|
||||
.spawn();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
//
|
||||
// fn main() -> anyhow::Result<()> {
|
||||
// env_logger::Builder::new()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue