diff --git a/Cargo.lock b/Cargo.lock index 8c4684d..b1c3533 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 8af58ad..b3486b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index a221feb..df84cbf 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/args.rs b/src/args.rs deleted file mode 100644 index 94002a4..0000000 --- a/src/args.rs +++ /dev/null @@ -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 { - 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, - - /// Selects a stylesheet to use - #[clap(short = 's', long = "style")] - style: Option, - - /// 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, - - /// Specifies the surface height - #[clap(short = 'H', long = "height")] - height: Option, - - /// Prompt to display - #[clap(short = 'p', long = "prompt")] - pub prompt: Option, - - /// The x offset - #[clap(short = 'x', long = "xoffset")] - x: Option, - - /// The y offset - #[clap(short = 'y', long = "yoffset")] - y: Option, - - /// 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, - - /// Specifies the terminal to use when running in a term - #[clap(short = 't', long = "term")] - terminal: Option, - - /// Runs in password mode - #[clap(short = 'P', long = "password")] - password_char: Option, - - /// 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, - - /// 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, - - /// 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, - - /// Sets the height in number of lines - #[clap(short = 'L', long = "lines")] - lines: Option, - - /// Sets the number of columns to display - #[clap(short = 'w', long = "columns")] - columns: Option, - - /// Sets the sort order - #[clap(short = 'O', long = "sort-order")] - sort_order: Option, - - /// 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, - - /// Sets the monitor to open on - #[clap(short = 'o', long = "monitor")] - monitor: Option, - - /// 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, - - /// Defines how good a fuzzy match must be, to be shown. - #[clap(long = "fuzzy-min-score")] - fuzzy_min_score: Option, - - /// Size of displayed images - #[clap(long = "image-size")] - image_size: Option, - - /// Orientation of main window - #[clap(long = "orientation")] - orientation: Option, - - /// Orientation of the row box, defining if label is below or at the side. - #[clap(long = "row-box-orientation")] - row_bow_orientation: Option, - - /// 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, - //// 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, - - /// 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, -} diff --git a/src/lib/config.rs b/src/lib/config.rs index c98db63..805d414 100644 --- a/src/lib/config.rs +++ b/src/lib/config.rs @@ -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 { + 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, + + /// Selects a config file to use + #[clap(short = 'c', long = "conf")] + pub config: Option, + + /// Runs in dmenu mode + #[clap(short = 'd', long = "dmenu")] + pub dmenu: Option, + + /// Prints the version and then exits + #[clap(short = 'v', long = "version")] + pub version: Option, + + /// 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, - pub show: Option, - pub mode: Option, + + /// Defines the mode worf is running in + #[clap(long = "show")] + pub show: Option, + + /// Default width of the window, defaults to 50% of the screen #[serde(default = "default_width")] + #[clap(long = "width")] pub width: Option, + + /// Default height of the window, defaults to 40% of the screen #[serde(default = "default_height")] + #[clap(long = "height")] pub height: Option, + + #[clap(short = 'p', long = "prompt")] pub prompt: Option, + + #[clap(short = 'x', long = "xoffset")] pub xoffset: Option, + + #[clap(long = "x")] pub x: Option, + + #[clap(short = 'y', long = "yoffset")] pub yoffset: Option, + + #[clap(long = "y")] pub y: Option, + + /// If true a normal window instead of a layer shell will be used #[serde(default = "default_normal_window")] - pub normal_window: Option, + #[clap(short = 'n', long = "normal-window")] + pub normal_window: bool, + + #[clap(short = 'I', long = "allow-images")] pub allow_images: Option, + + #[clap(short = 'm', long = "allow-markup")] pub allow_markup: Option, + + #[clap(short = 'k', long = "cache-file")] pub cache_file: Option, + + #[clap(short = 't', long = "term")] pub term: Option, + #[serde(default = "default_password_char")] + #[clap(short = 'P', long = "password")] pub password: Option, + + #[clap(short = 'e', long = "exec-search")] pub exec_search: Option, + + #[clap(short = 'b', long = "hide-scroll")] pub hide_scroll: Option, - /// Defines how matching is done #[serde(default = "default_match_method")] + #[clap(short = 'M', long = "matching")] pub matching: Option, + + #[clap(short = 'i', long = "insensitive")] pub insensitive: Option, + + #[clap(short = 'q', long = "parse-search")] pub parse_search: Option, + + #[clap(short = 'l', long = "location")] pub location: Option, + + #[clap(short = 'a', long = "no-actions")] pub no_actions: Option, + + #[clap(short = 'L', long = "lines")] pub lines: Option, - /// Defines how many columns are shown per row + #[serde(default = "default_columns")] + #[clap(short = 'w', long = "columns")] pub columns: Option, + + #[clap(short = 'O', long = "sort-order")] pub sort_order: Option, + + #[clap(short = 'G', long = "gtk-dark")] pub gtk_dark: Option, + + #[clap(short = 'Q', long = "search")] pub search: Option, + + #[clap(short = 'o', long = "monitor")] pub monitor: Option, + + #[clap(short = 'r', long = "pre-display-cmd")] pub pre_display_cmd: Option, - /// Defines how the entries root container are ordered - /// Default is vertical + #[serde(default = "default_orientation")] + #[clap(long = "orientation")] pub orientation: Option, - /// 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, - //// 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, - /// 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, pub filter_rate: Option, - /// Specifies the image size when enabled. - /// Defaults to 32. + #[serde(default = "default_image_size")] + #[clap(long = "image-size")] pub image_size: Option, + pub key_up: Option, pub key_down: Option, pub key_left: Option, @@ -110,8 +219,10 @@ pub struct Config { pub key_expand: Option, pub key_hide_search: Option, pub key_copy: Option, - #[serde(flatten)] - pub custom_keys: Option>, + + // todo re-add this + // #[serde(flatten)] + // pub key_custom: Option>, pub line_wrap: Option, pub global_coords: Option, pub hide_search: Option, @@ -121,25 +232,39 @@ pub struct Config { pub single_click: Option, pub pre_display_exec: Option, - // 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, - /// 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, + + /// 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, + + /// 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, + + } 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 { Some(Orientation::Horizontal) } -fn default_orientation() -> Option { +pub(crate) fn default_orientation() -> Option { Some(Orientation::Vertical) } @@ -221,8 +348,8 @@ fn default_columns() -> Option { Some(1) } -fn default_normal_window() -> Option { - Some(false) +fn default_normal_window() -> bool { + false } // TODO @@ -303,7 +430,7 @@ fn default_normal_window() -> Option { // char* key_copy = (i == 0) ? key_default : config_get(config, "key_copy", key_default); fn default_style() -> Option { - 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 { Some(32) } -pub fn merge_config_with_args(config: &mut Config, args: &Args) -> anyhow::Result { +pub fn default_text_wrap_length() -> Option { + Some(15) +} + +pub fn default_text_wrap() -> Option { + Some(false) +} + +pub fn parse_args() -> Config { + Config::parse() +} + + +pub fn style_path(full_path: Option) -> Result { + 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>, sub_path: PathBuf) -> Vec { + 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, + alternatives: Vec, +) -> Result { + 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) -> Result { + 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 { let args_json = serde_json::to_value(args)?; let mut config_json = serde_json::to_value(config)?; diff --git a/src/lib/gui.rs b/src/lib/gui.rs index 1ef39e7..d9d6bcc 100644 --- a/src/lib/gui.rs +++ b/src/lib/gui.rs @@ -26,8 +26,8 @@ use log::{debug, error, info}; use std::process::exit; use std::sync::{Arc, Mutex, MutexGuard}; -type ArcMenuMap = Arc>>; -type MenuItemSender = Sender>; +type ArcMenuMap = Arc>>>; +type MenuItemSender = Sender, anyhow::Error>>; impl Into for config::Orientation { fn into(self) -> Orientation { @@ -49,17 +49,20 @@ impl Into for config::Align { } #[derive(Clone)] -pub struct MenuItem { +pub struct MenuItem { pub label: String, // todo support empty label? pub icon_path: Option, pub action: Option, - pub sub_elements: Vec, + pub sub_elements: Vec>, pub working_dir: Option, pub initial_sort_score: i64, pub search_sort_score: f64, + + /// Allows to store arbitrary additional information + pub data: Option, } -pub fn show(config: Config, elements: Vec) -> Result { +pub fn show(config: Config, elements: Vec>) -> Result, 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) -> Result( config: &Config, - elements: &Vec, - sender: Sender>, + elements: &Vec>, + sender: Sender, 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 = 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( window: &ApplicationWindow, entry_clone: SearchEntry, inner_box: FlowBox, app: Application, - sender: MenuItemSender, - list_items: Arc>>, + sender: MenuItemSender, + list_items: Arc>>>, 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( child1: &FlowBoxChild, child2: &FlowBoxChild, - items_lock: &Mutex>, + items_lock: &Mutex>>, ) -> 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( + sender: &MenuItemSender, app: &Application, inner_box: &FlowBox, - lock_arc: &ArcMenuMap, -) -> Result<(), String> { + lock_arc: &ArcMenuMap, +) -> 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( inner_box: &FlowBox, - entry_element: &MenuItem, + entry_element: &MenuItem, config: &Config, - sender: MenuItemSender, - lock_arc: ArcMenuMap, + sender: MenuItemSender, + lock_arc: ArcMenuMap, 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( + menu_item: &MenuItem, config: &Config, - lock_arc: ArcMenuMap, - sender: MenuItemSender, + lock_arc: ArcMenuMap, + sender: MenuItemSender, 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( query: &str, - items: &mut HashMap, + items: &mut HashMap>, config: &Config, inner_box: &FlowBox, ) { @@ -537,9 +544,9 @@ fn percent_or_absolute(value: &String, base_value: i32) -> Option { } } -pub fn initialize_sort_scores(items: &mut Vec) { +pub fn initialize_sort_scores(items: &mut Vec>) { 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) { } } -fn wrap_text(text: &str, line_length: usize) -> String { +fn wrap_text(text: &str, line_length: Option) -> 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'); diff --git a/src/lib/mod.rs b/src/lib/mod.rs index cc32f75..e02e7e3 100644 --- a/src/lib/mod.rs +++ b/src/lib/mod.rs @@ -2,3 +2,4 @@ pub mod config; pub mod desktop; pub mod gui; pub mod system; +pub mod mode; diff --git a/src/lib/mode.rs b/src/lib/mode.rs new file mode 100644 index 0000000..c57b9ba --- /dev/null +++ b/src/lib/mode.rs @@ -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> = 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 = 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) -> 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) -> anyhow::Result> { + 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 = 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) -> 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::>(); + 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(()) +} diff --git a/src/lib/system.rs b/src/lib/system.rs index a1a4fcf..0d5e58e 100644 --- a/src/lib/system.rs +++ b/src/lib/system.rs @@ -1,31 +1,3 @@ use anyhow::anyhow; use std::env; use std::path::PathBuf; - -pub fn home_dir() -> Result { - env::var("HOME").map_err(|e| anyhow::anyhow!("$HOME not set: {e}")) -} - -pub fn conf_home() -> Result { - env::var("XDG_CONF_HOME").map_err(|e| anyhow::anyhow!("XDG_CONF_HOME not set: {e}")) -} - -pub fn config_path(config_path: Option) -> Result { - 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.")) -} diff --git a/src/main.rs b/src/main.rs index fee407c..fad1824 100644 --- a/src/main.rs +++ b/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 = 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) -> 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::>(); - 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()