* 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:
Alexander Mohr 2025-04-16 23:14:45 +02:00
parent d8e64f28fb
commit f398848dcf
10 changed files with 610 additions and 1005 deletions

594
Cargo.lock generated
View file

@ -114,7 +114,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -123,7 +123,7 @@ version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [ dependencies = [
"hermit-abi 0.1.19", "hermit-abi",
"libc", "libc",
"winapi", "winapi",
] ]
@ -161,26 +161,6 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 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]] [[package]]
name = "bytes" name = "bytes"
version = "1.10.1" version = "1.10.1"
@ -210,54 +190,6 @@ dependencies = [
"system-deps", "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]] [[package]]
name = "cfg-expr" name = "cfg-expr"
version = "0.17.2" version = "0.17.2"
@ -321,7 +253,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -345,15 +277,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 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]] [[package]]
name = "configparser" name = "configparser"
version = "1.0.0" version = "1.0.0"
@ -416,12 +339,6 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "cursor-icon"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "1.0.0" version = "1.0.0"
@ -439,15 +356,30 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
"unicode-xid", "unicode-xid",
] ]
[[package]] [[package]]
name = "downcast-rs" name = "dirs"
version = "1.2.1" version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "either" name = "either"
@ -484,16 +416,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 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]] [[package]]
name = "field-offset" name = "field-offset"
version = "0.3.6" version = "0.3.6"
@ -510,7 +432,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6059d3997cc694ec3e9a378db855866233ef7edfeafd85afcb2239fd130e6e6b" checksum = "6059d3997cc694ec3e9a378db855866233ef7edfeafd85afcb2239fd130e6e6b"
dependencies = [ dependencies = [
"thiserror 2.0.12", "thiserror",
"xdgkit", "xdgkit",
] ]
@ -564,7 +486,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -645,6 +567,17 @@ dependencies = [
"system-deps", "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]] [[package]]
name = "gimli" name = "gimli"
version = "0.31.1" version = "0.31.1"
@ -732,7 +665,7 @@ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -868,7 +801,7 @@ dependencies = [
"proc-macro-crate", "proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -917,12 +850,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hermit-abi"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]] [[package]]
name = "home" name = "home"
version = "0.5.11" version = "0.5.11"
@ -962,7 +889,7 @@ checksum = "69e3cbed6e560408051175d29a9ed6ad1e64a7ff443836addf797b0479f58983"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -1027,7 +954,7 @@ checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -1042,18 +969,22 @@ version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 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]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.5.6" version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.27" version = "0.4.27"
@ -1066,24 +997,6 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 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]] [[package]]
name = "memoffset" name = "memoffset"
version = "0.9.1" version = "0.9.1"
@ -1093,28 +1006,6 @@ dependencies = [
"autocfg", "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]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.7" version = "0.8.7"
@ -1135,15 +1026,6 @@ dependencies = [
"windows-sys 0.52.0", "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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@ -1153,15 +1035,6 @@ dependencies = [
"autocfg", "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]] [[package]]
name = "object" name = "object"
version = "0.36.7" version = "0.36.7"
@ -1177,6 +1050,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.6.1" version = "6.6.1"
@ -1243,7 +1122,7 @@ dependencies = [
"phf_shared", "phf_shared",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -1273,21 +1152,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 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]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.11.0" version = "1.11.0"
@ -1312,30 +1176,6 @@ dependencies = [
"toml_edit", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.94" version = "1.0.94"
@ -1355,15 +1195,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "quick-xml"
version = "0.37.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.40" version = "1.0.40"
@ -1388,6 +1219,17 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 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]] [[package]]
name = "regex" name = "regex"
version = "1.11.1" version = "1.11.1"
@ -1432,19 +1274,6 @@ dependencies = [
"semver", "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]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.20" version = "1.0.20"
@ -1474,7 +1303,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -1497,7 +1326,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -1509,12 +1338,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "1.0.1" version = "1.0.1"
@ -1536,34 +1359,6 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 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]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.9" version = "0.5.9"
@ -1586,17 +1381,6 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 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]] [[package]]
name = "syn" name = "syn"
version = "2.0.100" version = "2.0.100"
@ -1608,19 +1392,6 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "system-deps" name = "system-deps"
version = "7.0.3" version = "7.0.3"
@ -1655,33 +1426,13 @@ version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" 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]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.12" version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [ dependencies = [
"thiserror-impl 2.0.12", "thiserror-impl",
]
[[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",
] ]
[[package]] [[package]]
@ -1692,7 +1443,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -1725,7 +1476,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]
[[package]] [[package]]
@ -1762,23 +1513,6 @@ dependencies = [
"winnow", "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]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.18" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 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]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -1938,59 +1580,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"
@ -2087,9 +1676,9 @@ name = "worf"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"calloop 0.14.2",
"clap 4.5.35", "clap 4.5.35",
"crossbeam", "crossbeam",
"dirs",
"env_logger", "env_logger",
"freedesktop-file-parser", "freedesktop-file-parser",
"gdk4", "gdk4",
@ -2100,25 +1689,14 @@ dependencies = [
"ini", "ini",
"libc", "libc",
"log", "log",
"merge",
"regex", "regex",
"serde", "serde",
"serde_json", "serde_json",
"smithay-client-toolkit",
"strsim 0.11.1", "strsim 0.11.1",
"sysinfo", "thiserror",
"thiserror 2.0.12",
"toml", "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]] [[package]]
name = "xdgkit" name = "xdgkit"
version = "3.2.5" version = "3.2.5"
@ -2126,31 +1704,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeac9c0125f3c131c6a2898d2a9f25c11b7954c3ff644a018cb9e06fa92919b" checksum = "5aeac9c0125f3c131c6a2898d2a9f25c11b7954c3ff644a018cb9e06fa92919b"
dependencies = [ dependencies = [
"clap 3.2.25", "clap 3.2.25",
"quick-xml 0.21.0", "quick-xml",
"serde", "serde",
"tini", "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]] [[package]]
name = "xml-rs" name = "xml-rs"
version = "0.8.25" version = "0.8.25"
@ -2183,5 +1741,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.100", "syn",
] ]

View file

@ -13,19 +13,14 @@ home = "0.5.11"
log = "0.4.27" log = "0.4.27"
regex = "1.11.1" regex = "1.11.1"
hyprland = "0.4.0-beta.2" hyprland = "0.4.0-beta.2"
sysinfo = "0.34.2"
ini = "1.3.0" ini = "1.3.0"
clap = { version = "4.5.35", features = ["derive"] } clap = { version = "4.5.35", features = ["derive"] }
thiserror = "2.0.12" thiserror = "2.0.12"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
toml = "0.8.20" toml = "0.8.20"
merge = "0.1.0"
serde_json = "1.0.140" 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" crossbeam = "0.8.4"
libc = "0.2.171" libc = "0.2.171"
freedesktop-file-parser = "0.1.0" freedesktop-file-parser = "0.1.0"
strsim = "0.11.1" strsim = "0.11.1"
dirs = "6.0.0"

View file

@ -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. 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 * 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 * 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 ## Dropped configuration options
* stylesheet -> use style instead * stylesheet -> use style instead

View file

@ -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>,
}

View file

@ -1,13 +1,16 @@
use crate::args::Args;
use crate::lib::system; use crate::lib::system;
use anyhow::anyhow; use anyhow::{anyhow, Context};
use clap::ValueEnum; use clap::builder::TypedValueParser;
use clap::{Parser, ValueEnum};
use gtk4::prelude::ToValue; use gtk4::prelude::ToValue;
use merge::Merge;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use std::env; use std::collections::HashMap;
use std::env::Args;
use std::path::PathBuf; 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)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Serialize, Deserialize)]
pub enum MatchMethod { pub enum MatchMethod {
@ -29,74 +32,180 @@ pub enum Align {
Center, 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 { pub struct Config {
/// Defines the path to the stylesheet being used. /// Forks the menu so you can close the terminal
/// Defaults to XDG_CONFIG_DIR/worf/style.css #[clap(short = 'f', long = "fork")]
/// If XDG_CONFIG_DIR is not defined $HOME/.config will be used instead 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")] #[serde(default = "default_style")]
#[clap(long = "style")]
pub style: Option<String>, 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")] #[serde(default = "default_width")]
#[clap(long = "width")]
pub width: Option<String>, pub width: Option<String>,
/// Default height of the window, defaults to 40% of the screen
#[serde(default = "default_height")] #[serde(default = "default_height")]
#[clap(long = "height")]
pub height: Option<String>, pub height: Option<String>,
#[clap(short = 'p', long = "prompt")]
pub prompt: Option<String>, pub prompt: Option<String>,
#[clap(short = 'x', long = "xoffset")]
pub xoffset: Option<i32>, pub xoffset: Option<i32>,
#[clap(long = "x")]
pub x: Option<i32>, pub x: Option<i32>,
#[clap(short = 'y', long = "yoffset")]
pub yoffset: Option<i32>, pub yoffset: Option<i32>,
#[clap(long = "y")]
pub y: Option<i32>, pub y: Option<i32>,
/// If true a normal window instead of a layer shell will be used
#[serde(default = "default_normal_window")] #[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>, pub allow_images: Option<bool>,
#[clap(short = 'm', long = "allow-markup")]
pub allow_markup: Option<bool>, pub allow_markup: Option<bool>,
#[clap(short = 'k', long = "cache-file")]
pub cache_file: Option<String>, pub cache_file: Option<String>,
#[clap(short = 't', long = "term")]
pub term: Option<String>, pub term: Option<String>,
#[serde(default = "default_password_char")] #[serde(default = "default_password_char")]
#[clap(short = 'P', long = "password")]
pub password: Option<String>, pub password: Option<String>,
#[clap(short = 'e', long = "exec-search")]
pub exec_search: Option<bool>, pub exec_search: Option<bool>,
#[clap(short = 'b', long = "hide-scroll")]
pub hide_scroll: Option<bool>, pub hide_scroll: Option<bool>,
/// Defines how matching is done
#[serde(default = "default_match_method")] #[serde(default = "default_match_method")]
#[clap(short = 'M', long = "matching")]
pub matching: Option<MatchMethod>, pub matching: Option<MatchMethod>,
#[clap(short = 'i', long = "insensitive")]
pub insensitive: Option<bool>, pub insensitive: Option<bool>,
#[clap(short = 'q', long = "parse-search")]
pub parse_search: Option<bool>, pub parse_search: Option<bool>,
#[clap(short = 'l', long = "location")]
pub location: Option<String>, pub location: Option<String>,
#[clap(short = 'a', long = "no-actions")]
pub no_actions: Option<bool>, pub no_actions: Option<bool>,
#[clap(short = 'L', long = "lines")]
pub lines: Option<u32>, pub lines: Option<u32>,
/// Defines how many columns are shown per row
#[serde(default = "default_columns")] #[serde(default = "default_columns")]
#[clap(short = 'w', long = "columns")]
pub columns: Option<u32>, pub columns: Option<u32>,
#[clap(short = 'O', long = "sort-order")]
pub sort_order: Option<String>, pub sort_order: Option<String>,
#[clap(short = 'G', long = "gtk-dark")]
pub gtk_dark: Option<bool>, pub gtk_dark: Option<bool>,
#[clap(short = 'Q', long = "search")]
pub search: Option<String>, pub search: Option<String>,
#[clap(short = 'o', long = "monitor")]
pub monitor: Option<String>, pub monitor: Option<String>,
#[clap(short = 'r', long = "pre-display-cmd")]
pub pre_display_cmd: Option<String>, pub pre_display_cmd: Option<String>,
/// Defines how the entries root container are ordered
/// Default is vertical
#[serde(default = "default_orientation")] #[serde(default = "default_orientation")]
#[clap(long = "orientation")]
pub orientation: Option<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")] #[serde(default = "default_halign")]
#[clap(long = "halign")]
pub halign: Option<Align>, 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")] #[serde(default = "default_content_halign")]
#[clap(long = "content-halign")]
pub content_halign: Option<Align>, pub content_halign: Option<Align>,
/// Specifies the vertical align for the entire scrolled area, it can be any of fill, start, e #[clap(long = "valign")]
/// nd, or center, the default is orientation dependent. If vertical then it defaults to
/// start, if horizontal it defaults to center.
pub valign: Option<Align>, pub valign: Option<Align>,
pub filter_rate: Option<u32>, pub filter_rate: Option<u32>,
/// Specifies the image size when enabled.
/// Defaults to 32.
#[serde(default = "default_image_size")] #[serde(default = "default_image_size")]
#[clap(long = "image-size")]
pub image_size: Option<i32>, pub image_size: Option<i32>,
pub key_up: Option<String>, pub key_up: Option<String>,
pub key_down: Option<String>, pub key_down: Option<String>,
pub key_left: Option<String>, pub key_left: Option<String>,
@ -110,8 +219,10 @@ pub struct Config {
pub key_expand: Option<String>, pub key_expand: Option<String>,
pub key_hide_search: Option<String>, pub key_hide_search: Option<String>,
pub key_copy: 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 line_wrap: Option<String>,
pub global_coords: Option<bool>, pub global_coords: Option<bool>,
pub hide_search: Option<bool>, pub hide_search: Option<bool>,
@ -121,25 +232,39 @@ pub struct Config {
pub single_click: Option<bool>, pub single_click: Option<bool>,
pub pre_display_exec: Option<bool>, pub pre_display_exec: Option<bool>,
// Exclusive options /// Minimum score for a fuzzy search to be shown
/// Minimum score for the fuzzy finder to accept a match.
/// Must be a value between 0 and 1
/// Defaults to 0.1.
#[serde(default = "default_fuzzy_min_score")] #[serde(default = "default_fuzzy_min_score")]
#[clap(long = "fuzzy-min-score")]
pub fuzzy_min_score: Option<f64>, pub fuzzy_min_score: Option<f64>,
/// Defines how the content in the row box is aligned /// Orientation of items in the row box where items are displayed
/// Defaults to vertical
#[serde(default = "default_row_box_orientation")] #[serde(default = "default_row_box_orientation")]
#[clap(long = "row-box-orientation")]
pub row_bow_orientation: Option<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 { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Config { Config {
fork: None,
config: None,
dmenu: None,
version: None,
style: default_style(), style: default_style(),
show: None, show: None,
mode: None,
width: default_width(), width: default_width(),
height: default_height(), height: default_height(),
prompt: None, prompt: None,
@ -147,7 +272,7 @@ impl Default for Config {
x: None, x: None,
yoffset: None, yoffset: None,
y: None, y: None,
normal_window: None, normal_window: default_normal_window(),
allow_images: None, allow_images: None,
allow_markup: None, allow_markup: None,
cache_file: None, cache_file: None,
@ -186,7 +311,7 @@ impl Default for Config {
key_expand: None, key_expand: None,
key_hide_search: None, key_hide_search: None,
key_copy: None, key_copy: None,
custom_keys: None, //key_custom: None,
line_wrap: None, line_wrap: None,
global_coords: None, global_coords: None,
hide_search: None, hide_search: None,
@ -197,6 +322,8 @@ impl Default for Config {
pre_display_exec: None, pre_display_exec: None,
fuzzy_min_score: default_fuzzy_min_score(), fuzzy_min_score: default_fuzzy_min_score(),
row_bow_orientation: default_row_box_orientation(), 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) Some(Orientation::Horizontal)
} }
fn default_orientation() -> Option<Orientation> { pub(crate) fn default_orientation() -> Option<Orientation> {
Some(Orientation::Vertical) Some(Orientation::Vertical)
} }
@ -221,8 +348,8 @@ fn default_columns() -> Option<u32> {
Some(1) Some(1)
} }
fn default_normal_window() -> Option<bool> { fn default_normal_window() -> bool {
Some(false) false
} }
// TODO // 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); // char* key_copy = (i == 0) ? key_default : config_get(config, "key_copy", key_default);
fn default_style() -> Option<String> { fn default_style() -> Option<String> {
system::config_path(None) style_path(None)
.ok() .ok()
.and_then(|pb| Some(pb.display().to_string())) .and_then(|pb| Some(pb.display().to_string()))
.or_else(|| { .or_else(|| {
@ -340,7 +467,89 @@ pub fn default_image_size() -> Option<i32> {
Some(32) 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 args_json = serde_json::to_value(args)?;
let mut config_json = serde_json::to_value(config)?; let mut config_json = serde_json::to_value(config)?;

View file

@ -26,8 +26,8 @@ use log::{debug, error, info};
use std::process::exit; use std::process::exit;
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
type ArcMenuMap = Arc<Mutex<HashMap<FlowBoxChild, MenuItem>>>; type ArcMenuMap<T> = Arc<Mutex<HashMap<FlowBoxChild, MenuItem<T>>>>;
type MenuItemSender = Sender<Result<MenuItem, anyhow::Error>>; type MenuItemSender<T> = Sender<Result<MenuItem<T>, anyhow::Error>>;
impl Into<Orientation> for config::Orientation { impl Into<Orientation> for config::Orientation {
fn into(self) -> Orientation { fn into(self) -> Orientation {
@ -49,17 +49,20 @@ impl Into<Align> for config::Align {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct MenuItem { pub struct MenuItem<T> {
pub label: String, // todo support empty label? pub label: String, // todo support empty label?
pub icon_path: Option<String>, pub icon_path: Option<String>,
pub action: Option<String>, pub action: Option<String>,
pub sub_elements: Vec<MenuItem>, pub sub_elements: Vec<MenuItem<T>>,
pub working_dir: Option<String>, pub working_dir: Option<String>,
pub initial_sort_score: i64, pub initial_sort_score: i64,
pub search_sort_score: f64, 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 { if let Some(ref css) = config.style {
let provider = CssProvider::new(); let provider = CssProvider::new();
let css_file_path = File::for_path(css); let css_file_path = File::for_path(css);
@ -85,12 +88,12 @@ pub fn show(config: Config, elements: Vec<MenuItem>) -> Result<MenuItem, anyhow:
selection selection
} }
fn build_ui( fn build_ui<T>(
config: &Config, config: &Config,
elements: &Vec<MenuItem>, elements: &Vec<MenuItem<T>>,
sender: Sender<Result<MenuItem, anyhow::Error>>, sender: Sender<Result<MenuItem<T>, anyhow::Error>>,
app: &Application, app: &Application,
) { ) where T: Clone + 'static {
// Create a toplevel undecorated window // Create a toplevel undecorated window
let window = ApplicationWindow::builder() let window = ApplicationWindow::builder()
.application(app) .application(app)
@ -102,15 +105,13 @@ fn build_ui(
window.set_widget_name("window"); window.set_widget_name("window");
config.normal_window.map(|normal| { if !config.normal_window {
if !normal {
// Initialize the window as a layer // Initialize the window as a layer
window.init_layer_shell(); window.init_layer_shell();
window.set_layer(gtk4_layer_shell::Layer::Overlay); window.set_layer(gtk4_layer_shell::Layer::Overlay);
window.set_keyboard_mode(KeyboardMode::Exclusive); window.set_keyboard_mode(KeyboardMode::Exclusive);
window.set_namespace(Some("worf")); window.set_namespace(Some("worf"));
} }
});
let outer_box = gtk4::Box::new(config.orientation.unwrap().into(), 0); let outer_box = gtk4::Box::new(config.orientation.unwrap().into(), 0);
outer_box.set_widget_name("outer-box"); 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_max_children_per_line(config.columns.unwrap());
inner_box.set_activate_on_single_click(true); 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 { for entry in elements {
list_items list_items
.lock() .lock()
@ -221,13 +222,13 @@ fn build_ui(
}); });
} }
fn setup_key_event_handler( fn setup_key_event_handler<T: Clone + 'static>(
window: &ApplicationWindow, window: &ApplicationWindow,
entry_clone: SearchEntry, entry_clone: SearchEntry,
inner_box: FlowBox, inner_box: FlowBox,
app: Application, app: Application,
sender: MenuItemSender, sender: MenuItemSender<T>,
list_items: Arc<Mutex<HashMap<FlowBoxChild, MenuItem>>>, list_items: Arc<Mutex<HashMap<FlowBoxChild, MenuItem<T>>>>,
config: Config, config: Config,
) { ) {
let key_controller = EventControllerKey::new(); let key_controller = EventControllerKey::new();
@ -269,10 +270,10 @@ fn setup_key_event_handler(
window.add_controller(key_controller); window.add_controller(key_controller);
} }
fn sort_menu_items( fn sort_menu_items<T>(
child1: &FlowBoxChild, child1: &FlowBoxChild,
child2: &FlowBoxChild, child2: &FlowBoxChild,
items_lock: &Mutex<HashMap<FlowBoxChild, MenuItem>>, items_lock: &Mutex<HashMap<FlowBoxChild, MenuItem<T>>>,
) -> Ordering { ) -> Ordering {
let lock = items_lock.lock().unwrap(); let lock = items_lock.lock().unwrap();
let m1 = lock.get(child1); let m1 = lock.get(child1);
@ -281,13 +282,13 @@ fn sort_menu_items(
match (m1, m2) { match (m1, m2) {
(Some(menu1), Some(menu2)) => { (Some(menu1), Some(menu2)) => {
if menu1.search_sort_score != 0.0 || menu2.search_sort_score != 0.0 { 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 Ordering::Smaller
} else { } else {
Ordering::Larger Ordering::Larger
} }
} else { } else {
if menu1.initial_sort_score > menu2.initial_sort_score { if menu1.initial_sort_score < menu2.initial_sort_score {
Ordering::Smaller Ordering::Smaller
} else { } else {
Ordering::Larger Ordering::Larger
@ -300,12 +301,12 @@ fn sort_menu_items(
} }
} }
fn handle_selected_item( fn handle_selected_item<T>(
sender: &MenuItemSender, sender: &MenuItemSender<T>,
app: &Application, app: &Application,
inner_box: &FlowBox, inner_box: &FlowBox,
lock_arc: &ArcMenuMap, lock_arc: &ArcMenuMap<T>,
) -> Result<(), String> { ) -> Result<(), String> where T: Clone {
for s in inner_box.selected_children() { for s in inner_box.selected_children() {
let list_items = lock_arc.lock().unwrap(); let list_items = lock_arc.lock().unwrap();
let item = list_items.get(&s); let item = list_items.get(&s);
@ -320,12 +321,12 @@ fn handle_selected_item(
Err("selected item cannot be resolved".to_owned()) Err("selected item cannot be resolved".to_owned())
} }
fn add_menu_item( fn add_menu_item<T: Clone + 'static>(
inner_box: &FlowBox, inner_box: &FlowBox,
entry_element: &MenuItem, entry_element: &MenuItem<T>,
config: &Config, config: &Config,
sender: MenuItemSender, sender: MenuItemSender<T>,
lock_arc: ArcMenuMap, lock_arc: ArcMenuMap<T>,
app: Application, app: Application,
) -> FlowBoxChild { ) -> FlowBoxChild {
let parent: Widget = if !entry_element.sub_elements.is_empty() { let parent: Widget = if !entry_element.sub_elements.is_empty() {
@ -390,11 +391,11 @@ fn add_menu_item(
child child
} }
fn create_menu_row( fn create_menu_row<T: Clone + 'static>(
menu_item: &MenuItem, menu_item: &MenuItem<T>,
config: &Config, config: &Config,
lock_arc: ArcMenuMap, lock_arc: ArcMenuMap<T>,
sender: MenuItemSender, sender: MenuItemSender<T>,
app: Application, app: Application,
inner_box: FlowBox, inner_box: FlowBox,
) -> Widget { ) -> Widget {
@ -434,7 +435,13 @@ fn create_menu_row(
} }
// todo make max length configurable // 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_hexpand(true);
label.set_widget_name("label"); label.set_widget_name("label");
label.set_wrap(true); label.set_wrap(true);
@ -448,9 +455,9 @@ fn create_menu_row(
row.upcast() row.upcast()
} }
fn filter_widgets( fn filter_widgets<T>(
query: &str, query: &str,
items: &mut HashMap<FlowBoxChild, MenuItem>, items: &mut HashMap<FlowBoxChild, MenuItem<T>>,
config: &Config, config: &Config,
inner_box: &FlowBox, 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; 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() { for item in items.iter_mut() {
if item.initial_sort_score == 0 { 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 result = String::new();
let mut line = String::new(); let mut line = String::new();
let len = line_length.unwrap_or(text.len());
for word in text.split_whitespace() { 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() { if !line.is_empty() {
result.push_str(&line.trim_end()); result.push_str(&line.trim_end());
result.push('\n'); result.push('\n');

View file

@ -2,3 +2,4 @@ pub mod config;
pub mod desktop; pub mod desktop;
pub mod gui; pub mod gui;
pub mod system; pub mod system;
pub mod mode;

211
src/lib/mode.rs Normal file
View 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(())
}

View file

@ -1,31 +1,3 @@
use anyhow::anyhow; use anyhow::anyhow;
use std::env; use std::env;
use std::path::PathBuf; 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."))
}

View file

@ -3,10 +3,9 @@
// todo resolve paths like ~/ // todo resolve paths like ~/
use crate::args::{Args, Mode}; use crate::lib::config::Config;
use crate::lib::config::{Config, merge_config_with_args};
use crate::lib::desktop::{default_icon, find_desktop_files, get_locale_variants}; 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 crate::lib::gui::MenuItem;
use anyhow::{Error, anyhow}; use anyhow::{Error, anyhow};
use clap::Parser; use clap::Parser;
@ -18,7 +17,6 @@ use gtk4::prelude::{
}; };
use gtk4_layer_shell::LayerShell; use gtk4_layer_shell::LayerShell;
use log::{debug, info, warn}; use log::{debug, info, warn};
use merge::Merge;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref; use std::ops::Deref;
use std::os::unix::process::CommandExt; use std::os::unix::process::CommandExt;
@ -28,9 +26,9 @@ use std::sync::Arc;
use std::thread::sleep; use std::thread::sleep;
use std::{env, fs, time}; use std::{env, fs, time};
mod args;
mod lib; mod lib;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
gtk4::init()?; gtk4::init()?;
@ -38,43 +36,23 @@ fn main() -> anyhow::Result<()> {
// todo change to error as default // todo change to error as default
.parse_filters(&env::var("RUST_LOG").unwrap_or_else(|_| "debug".to_owned())) .parse_filters(&env::var("RUST_LOG").unwrap_or_else(|_| "debug".to_owned()))
.init(); .init();
let args = Args::parse();
let home_dir = env::var("HOME")?; let args = config::parse_args();
let config_path = args let config = config::load_config(Some(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 drun_cache = env::var("XDG_CACHE_HOME") if let Some(show) = &config.show {
.map_or( match show {
PathBuf::from(home_dir.clone()).join(".cache"), config::Mode::Run => {}
|xdg_conf_home| PathBuf::from(&xdg_conf_home), config::Mode::Drun => {
) mode::d_run(config)?;
.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)?;
} }
Mode::Dmenu => {} config::Mode::Dmenu => {}
} }
Ok(()) Ok(())
} else {
Err(anyhow!("No mode provided"))
}
} }
fn lookup_name_with_locale( fn lookup_name_with_locale(
@ -90,135 +68,7 @@ fn lookup_name_with_locale(
.or_else(|| Some(fallback.to_owned())) .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<()> { // fn main() -> anyhow::Result<()> {
// env_logger::Builder::new() // env_logger::Builder::new()