diff --git a/Cargo.lock b/Cargo.lock index 950e04e..46ceedf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,7 +47,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -58,7 +58,7 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -121,7 +121,7 @@ dependencies = [ "rustix 0.38.44", "slab", "tracing", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -180,7 +180,7 @@ dependencies = [ "rustix 0.38.44", "signal-hook-registry", "slab", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -206,29 +206,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.9.0" @@ -263,7 +246,7 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae50b5510d86cf96ac2370e66d8dc960882f3df179d6a5a1e52bd94a1416c0f7" dependencies = [ - "bitflags 2.9.0", + "bitflags", "cairo-sys-rs", "glib", "libc", @@ -313,25 +296,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "3.2.25" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex 0.2.4", - "indexmap 1.9.3", - "strsim 0.10.0", - "termcolor", - "textwrap", - "yaml-rust", -] - -[[package]] -name = "clap" -version = "4.5.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", "clap_derive", @@ -339,14 +306,14 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.4", - "strsim 0.11.1", + "clap_lex", + "strsim", ] [[package]] @@ -361,15 +328,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - [[package]] name = "clap_lex" version = "0.7.4" @@ -456,13 +414,34 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", ] [[package]] @@ -473,8 +452,8 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", - "windows-sys", + "redox_users 0.5.0", + "windows-sys 0.59.0", ] [[package]] @@ -483,7 +462,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.0", + "bitflags", "objc2", ] @@ -580,7 +559,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -604,6 +583,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fasteval3" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ebd4dfc97a204b81366c95809cddaf586b713c7b3dc7ccec2e0c9c2bd5a62c0" + [[package]] name = "fastrand" version = "2.3.0" @@ -634,12 +619,26 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "freedesktop-file-parser" -version = "0.1.3" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9874624824ee3ca96cf728451815bd93ae3978cdcd715c1d098edef7130fb0da" +dependencies = [ + "freedesktop-icons", + "thiserror 2.0.12", +] + +[[package]] +name = "freedesktop-icons" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6059d3997cc694ec3e9a378db855866233ef7edfeafd85afcb2239fd130e6e6b" +checksum = "95f87364ea709292a3b3f74014ce3ee78412c89807eea75a358c8e029b000994" dependencies = [ - "thiserror", - "xdgkit", + "dirs 5.0.1", + "ini_core", + "once_cell", + "thiserror 1.0.69", + "tracing", + "xdg", ] [[package]] @@ -846,7 +845,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -875,7 +874,7 @@ version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707b819af8059ee5395a2de9f2317d87a53dbad8846a2f089f0bb44703f37686" dependencies = [ - "bitflags 2.9.0", + "bitflags", "futures-channel", "futures-core", "futures-executor", @@ -1005,7 +1004,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aec4fd3226bb6aa8dda5370142e14a4d15f00bba99bfb355b6ef7bb49d100758" dependencies = [ - "bitflags 2.9.0", + "bitflags", "gdk4", "glib", "glib-sys", @@ -1058,12 +1057,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.15.3" @@ -1076,15 +1069,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.4.0" @@ -1099,22 +1083,21 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "indexmap" -version = "1.9.3" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "equivalent", + "hashbrown", ] [[package]] -name = "indexmap" -version = "2.9.0" +name = "ini_core" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "7a467a31a9f439b5262fa99c17084537bff57f24703d5a09a2b5c9657ec73a61" dependencies = [ - "equivalent", - "hashbrown 0.15.3", + "cfg-if", ] [[package]] @@ -1186,16 +1169,10 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.9.0", + "bitflags", "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" @@ -1241,16 +1218,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "meval" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79496a5651c8d57cd033c5add8ca7ee4e3d5f7587a4777484640d9cb60392d9" -dependencies = [ - "fnv", - "nom 1.2.4", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1263,7 +1230,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.0", + "bitflags", "cfg-if", "cfg_aliases", "libc", @@ -1276,18 +1243,12 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.0", + "bitflags", "cfg-if", "cfg_aliases", "libc", ] -[[package]] -name = "nom" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" - [[package]] name = "nom" version = "7.1.3" @@ -1333,7 +1294,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.0", + "bitflags", "dispatch2", "objc2", ] @@ -1350,7 +1311,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.0", + "bitflags", "block2", "libc", "objc2", @@ -1386,15 +1347,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.59.0", ] -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - [[package]] name = "pango" version = "0.20.9" @@ -1432,7 +1387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.9.0", + "indexmap", ] [[package]] @@ -1516,11 +1471,11 @@ checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", "rustix 0.38.44", "tracing", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1577,16 +1532,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "quick-xml" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0452695941410a58c8ce4391707ba9bad26a247173bd9886a05a5e8a8babec75" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "quick-xml" version = "0.37.5" @@ -1682,6 +1627,17 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.0" @@ -1690,7 +1646,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror", + "thiserror 2.0.12", ] [[package]] @@ -1737,11 +1693,11 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags", "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1750,11 +1706,11 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags", "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1863,12 +1819,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -1922,8 +1872,8 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" dependencies = [ - "quick-xml 0.37.5", - "thiserror", + "quick-xml", + "thiserror 2.0.12", "windows", "windows-version", ] @@ -1938,31 +1888,36 @@ dependencies = [ "getrandom 0.3.2", "once_cell", "rustix 1.0.7", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] -name = "termcolor" -version = "1.4.1" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "winapi-util", + "thiserror-impl 1.0.69", ] -[[package]] -name = "textwrap" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" - [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "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.101", ] [[package]] @@ -2005,12 +1960,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" -[[package]] -name = "tini" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e004df4c5f0805eb5f55883204a514cfa43a6d924741be29e871753a53d5565a" - [[package]] name = "toml" version = "0.8.22" @@ -2038,7 +1987,7 @@ version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ - "indexmap 2.9.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -2091,7 +2040,7 @@ checksum = "aac5e8971f245c3389a5a76e648bfc80803ae066a1243a75db0064d7c1129d63" dependencies = [ "fnv", "memchr", - "nom 7.1.3", + "nom", "once_cell", "petgraph", ] @@ -2165,7 +2114,7 @@ version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" dependencies = [ - "bitflags 2.9.0", + "bitflags", "rustix 0.38.44", "wayland-backend", "wayland-scanner", @@ -2177,7 +2126,7 @@ version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" dependencies = [ - "bitflags 2.9.0", + "bitflags", "wayland-backend", "wayland-client", "wayland-scanner", @@ -2189,7 +2138,7 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" dependencies = [ - "bitflags 2.9.0", + "bitflags", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2203,7 +2152,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" dependencies = [ "proc-macro2", - "quick-xml 0.37.5", + "quick-xml", "quote", ] @@ -2244,15 +2193,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2360,13 +2300,37 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -2375,14 +2339,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2394,18 +2358,36 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2418,24 +2400,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2463,7 +2469,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags", ] [[package]] @@ -2477,7 +2483,7 @@ dependencies = [ "os_pipe", "rustix 0.38.44", "tempfile", - "thiserror", + "thiserror 2.0.12", "tree_magic_mini", "wayland-backend", "wayland-client", @@ -2490,26 +2496,27 @@ name = "worf" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.37", + "clap", "crossbeam", - "dirs", + "dirs 6.0.0", "emoji", "env_logger", + "fasteval3", "freedesktop-file-parser", + "freedesktop-icons", "gdk4", "gtk4", "gtk4-layer-shell", "libc", "log", - "meval", "nix 0.30.1", "notify-rust", "rayon", "regex", "serde", "serde_json", - "strsim 0.11.1", - "thiserror", + "strsim", + "thiserror 2.0.12", "toml", "tree_magic_mini", "which", @@ -2526,16 +2533,10 @@ dependencies = [ ] [[package]] -name = "xdgkit" -version = "3.2.5" +name = "xdg" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeac9c0125f3c131c6a2898d2a9f25c11b7954c3ff644a018cb9e06fa92919b" -dependencies = [ - "clap 3.2.25", - "quick-xml 0.21.0", - "serde", - "tini", -] +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "xml-rs" @@ -2543,15 +2544,6 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "zbus" version = "5.6.0" @@ -2578,7 +2570,7 @@ dependencies = [ "serde_repr", "tracing", "uds_windows", - "windows-sys", + "windows-sys 0.59.0", "winnow", "zbus_macros", "zbus_names", diff --git a/README.md b/README.md index 350c96f..cb90e99 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ The standard view will show `ssh` and `drun`, for other modes the following pref * `ssh` (optional) * `?` web search * `/`, `$` or `~` for files -* `emoji` for emojis @@ -66,6 +65,9 @@ Styling names and classes are inspired by wofi, so most of the documentation and | `row` | Row containing the entry, used to control hover effects. | | `custom-key-label-text` | The label for custom keys | | `custom-key-label-box` | Box containing the label, can be used for borders etc. | +| `custom-key-hint-text` | The label for custom keys hint | +| `custom-key-hint-box` | Box containing the hint, can be used for borders etc. | + Checkout more showcases in the [styles directory of this repo](styles). @@ -97,6 +99,7 @@ This library is not available publicly yet as the interface is not stable enough * Color files are not supported * `line_wrap` is now called `line-wrap` * Wofi has a C-API, that is not and won't be supported, but Worf can be used as a rust library. +* Most boolean options now need `true` or `false` as argument, as Worf is using the same struct for config and command line arguments and this is the only way to merge both data sources ### Dropped arguments / config values * `mode`, use show diff --git a/examples/worf-warden/src/main.rs b/examples/worf-warden/src/main.rs index 13c2b41..6f05733 100644 --- a/examples/worf-warden/src/main.rs +++ b/examples/worf-warden/src/main.rs @@ -3,9 +3,9 @@ use std::env; use std::process::Command; use std::thread::sleep; use std::time::Duration; -use worf_lib::config::Config; +use worf_lib::config::{Config, CustomKeyHintLocation}; use worf_lib::desktop::{copy_to_clipboard, spawn_fork}; -use worf_lib::gui::{ItemProvider, Key, KeyBinding, MenuItem, Modifier}; +use worf_lib::gui::{CustomKeyHint, CustomKeys, ItemProvider, Key, KeyBinding, MenuItem, Modifier}; use worf_lib::{config, gui}; #[derive(Clone)] @@ -24,51 +24,34 @@ fn split_at_tab(input: &str) -> Option<(&str, &str)> { } impl PasswordProvider { - fn new(config: &Config) -> Self { - let output = rbw("list", Some(vec!["--fields", "id,name"])); - let items = match output { - Ok(output) => { - let mut items = output - .lines() - .filter_map(|s| split_at_tab(s)) - .fold( - HashMap::new(), - |mut acc: HashMap>, (id, name)| { - acc.entry(name.to_owned()).or_default().push(id.to_owned()); - acc - }, - ) - .iter() - .map(|(key, value)| { - MenuItem::new( - key.clone(), - None, - None, - vec![], - None, - 0.0, - Some(MenuItemMetaData { ids: value.clone() }), - ) - }) - .collect::>(); - gui::apply_sort(&mut items, &config.sort_order()); - items - } - Err(error) => { - let item = MenuItem::new( - format!("Error from rbw: {error}"), + fn new(config: &Config) -> Result { + let output = rbw("list", Some(vec!["--fields", "id,name"]))?; + let mut items = output + .lines() + .filter_map(|s| split_at_tab(s)) + .fold( + HashMap::new(), + |mut acc: HashMap>, (id, name)| { + acc.entry(name.to_owned()).or_default().push(id.to_owned()); + acc + }, + ) + .iter() + .map(|(key, value)| { + MenuItem::new( + key.clone(), None, None, - vec![], + vec![].into_iter().collect(), None, 0.0, - None, - ); - vec![item] - } - }; + Some(MenuItemMetaData { ids: value.clone() }), + ) + }) + .collect::>(); + gui::apply_sort(&mut items, &config.sort_order()); - Self { items } + Ok(Self { items }) } fn sub_provider(ids: Vec) -> Result { @@ -79,11 +62,11 @@ impl PasswordProvider { rbw_get_user(id, false)?, None, None, - vec![], + vec![].into_iter().collect(), None, 0.0, Some(MenuItemMetaData { - ids: vec![id.clone()], + ids: vec![id.clone()].into_iter().collect(), }), )) }) @@ -125,7 +108,16 @@ fn keyboard_type(text: &str) { fn keyboard_tab() { Command::new("ydotool") - .arg("TAB") + .arg("type") + .arg("\t") + .output() + .expect("Failed to execute ydotool"); +} + +fn keyboard_return() { + Command::new("ydotool") + .arg("type") + .arg("\n") .output() .expect("Failed to execute ydotool"); } @@ -178,40 +170,81 @@ fn rbw_get_totp(id: &str, copy: bool) -> Result { fn key_type_all() -> KeyBinding { KeyBinding { key: Key::Num1, - modifiers: Modifier::Alt, + modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+1 Type All".to_string(), + visible: true, + } +} + +fn key_type_all_and_enter() -> KeyBinding { + KeyBinding { + key: Key::Exclamation, + modifiers: vec![Modifier::Alt, Modifier::Shift].into_iter().collect(), + label: String::new(), + visible: false, } } fn key_type_user() -> KeyBinding { KeyBinding { key: Key::Num2, - modifiers: Modifier::Alt, + modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+2 Type User".to_string(), + visible: true, + } +} + +fn key_type_user_and_enter() -> KeyBinding { + KeyBinding { + key: Key::At, + modifiers: vec![Modifier::Alt, Modifier::Shift].into_iter().collect(), + label: String::new(), + visible: false, } } fn key_type_password() -> KeyBinding { KeyBinding { key: Key::Num3, - modifiers: Modifier::Alt, + modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+3 Type Password".to_string(), + visible: true, + } +} + +fn key_type_password_and_enter() -> KeyBinding { + KeyBinding { + key: Key::Hash, + modifiers: vec![Modifier::Alt, Modifier::Shift].into_iter().collect(), + label: String::new(), + visible: false, } } fn key_type_totp() -> KeyBinding { KeyBinding { key: Key::Num4, - modifiers: Modifier::Alt, + modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+4 Type Totp".to_string(), + visible: true, + } +} + +fn key_type_totp_and_enter() -> KeyBinding { + KeyBinding { + key: Key::Dollar, + modifiers: vec![Modifier::Alt, Modifier::Shift].into_iter().collect(), + label: String::new(), + visible: false, } } fn key_sync() -> KeyBinding { KeyBinding { key: Key::R, - modifiers: Modifier::Alt, + modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+r Sync".to_string(), + visible: true, } } @@ -219,16 +252,18 @@ fn key_sync() -> KeyBinding { fn key_totp() -> KeyBinding { KeyBinding { key: Key::T, - modifiers: Modifier::Alt, + modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+t Totp".to_string(), + visible: true, } } fn key_lock() -> KeyBinding { KeyBinding { key: Key::L, - modifiers: Modifier::Alt, + modifiers: vec![Modifier::Alt].into_iter().collect(), label: "Alt+l Lock".to_string(), + visible: true, } } @@ -238,15 +273,25 @@ fn show(config: Config, provider: PasswordProvider) -> Result<(), String> { provider, false, None, - Some(vec![ - key_type_all(), - key_type_user(), - key_type_password(), - key_type_totp(), - key_sync(), - key_totp(), - key_lock(), - ]), + Some(CustomKeys { + bindings: vec![ + key_type_all(), + key_type_all_and_enter(), + key_type_user(), + key_type_user_and_enter(), + key_type_password(), + key_type_password_and_enter(), + key_type_totp(), + key_type_totp_and_enter(), + key_sync(), + key_totp(), + key_lock(), + ], + hint: Some(CustomKeyHint { + label: "Use Shift as additional modifier to send enter".to_string(), + location: CustomKeyHintLocation::Top, + }), + }), ) { Ok(selection) => { if let Some(meta) = selection.menu.data { @@ -256,17 +301,17 @@ fn show(config: Config, provider: PasswordProvider) -> Result<(), String> { let id = meta.ids.first().unwrap_or(&selection.menu.label); - sleep(Duration::from_millis(250)); + sleep(Duration::from_millis(500)); if let Some(key) = selection.custom_key { - if key == key_type_all() { + if key == key_type_all() || key == key_type_all_and_enter() { keyboard_type(&rbw_get_user(id, false)?); keyboard_tab(); keyboard_type(&rbw_get_password(id, false)?); - } else if key == key_type_user() { + } else if key == key_type_user() || key == key_type_user_and_enter() { keyboard_type(&rbw_get_user(id, false)?); - } else if key == key_type_password() { + } else if key == key_type_password() || key == key_type_password_and_enter() { keyboard_type(&rbw_get_password(id, false)?); - } else if key == key_type_totp() { + } else if key == key_type_totp() || key == key_type_totp_and_enter() { keyboard_type(&rbw_get_totp(id, false)?); } else if key == key_lock() { rbw("lock", None)?; @@ -275,6 +320,10 @@ fn show(config: Config, provider: PasswordProvider) -> Result<(), String> { } else if key == key_totp() { rbw_get_totp(id, true)?; } + + if key.modifiers.contains(&Modifier::Shift) { + keyboard_return(); + } } else { let pw = rbw_get_password(id, true)?; if let Err(e) = copy_to_clipboard(pw, None) { @@ -310,7 +359,6 @@ fn main() -> Result<(), String> { spawn_fork("ydotoold", None).expect("failed to spawn ydotoold"); // todo eventually use a propper rust client for this, for now rbw is good enough - let provider = PasswordProvider::new(&config); - + let provider = PasswordProvider::new(&config)?; show(config, provider) } diff --git a/styles/compact/style.css b/styles/compact/style.css index 81837dd..83d3a16 100644 --- a/styles/compact/style.css +++ b/styles/compact/style.css @@ -74,8 +74,15 @@ font-family: DejaVu; margin-bottom: 0.25em; border-right: 1px solid rgba(214, 174, 0, 1); border-left: 1px solid rgba(214, 174, 0, 1); + border-bottom: 1px solid rgba(214, 174, 0, 1); + padding-left: 1em; +} + +#custom-key-hint-box { + margin-top: 0.25em; + margin-bottom: 0.25em; padding-left: 1em; } #custom-key-label-text { - } +} diff --git a/styles/dmenu/config.toml b/styles/dmenu/config.toml index 4dbb0d9..ebd10ee 100644 --- a/styles/dmenu/config.toml +++ b/styles/dmenu/config.toml @@ -2,9 +2,9 @@ image_size=0 columns=999 allow_images=false orientation="Horizontal" -row_bow_orientation="Horizontal" +row_box_orientation="Horizontal" content_halign="Start" -height="0" width="100%" hide_scroll=true location=["Top"] +lines=1 diff --git a/styles/emoji/config b/styles/emoji/config new file mode 100644 index 0000000..6bfec0b --- /dev/null +++ b/styles/emoji/config @@ -0,0 +1,9 @@ +image_size=64 +columns=6 +orientation="Vertical" +row_box_orientation="Vertical" +content_halign="Center" +height="70%" +width="60%" +valign="Start" +emoji_hide_label=true diff --git a/styles/emoji/style.css b/styles/emoji/style.css new file mode 100644 index 0000000..115dce8 --- /dev/null +++ b/styles/emoji/style.css @@ -0,0 +1,69 @@ +* { + font-family: DejaVu; +} + +#window { + all: unset; + background-color: rgba(33, 33, 33, 0.8); /* Matches #212121BB */ + border-radius: 0; +} + +#window #outer-box { + /* The name of the search bar */ + /* The name of the scrolled window containing all of the entries */ + border: 2px solid rgba(63, 81, 181, 1); + border-radius: 6px; +} + +#window #outer-box #input { + background-color: rgba(32, 32, 32, 0.6); + color: #f2f2f2; + border-bottom: 2px solid rgba(214, 174, 0, 1); + padding: 0.8rem 1rem; + font-size: 1rem; +} + +#window #outer-box #input:focus, #window #outer-box #input:focus-visible, #window #outer-box #input:active { + all: unset; + background-color: rgba(32, 32, 32, 0.6); + color: #f2f2f2; + border-bottom: 2px solid rgba(214, 174, 2, 1); + font-size: 1rem; +} + +#window #outer-box #scroll #inner-box #entry { + color: #fff; + background-color: rgba(32, 32, 32, 0.1); + padding: 1rem; + margin: 1rem; + border-radius: 0.5rem; + border-bottom: 5px solid rgba(32, 32, 32, 0.1); + font-size: 4rem; + +} +#window #outer-box #scroll #inner-box #entry #img { + margin-right: 0.5rem; +} + +#window #outer-box #scroll #inner-box #entry:selected { + color: #fff; + background-color: rgba(255, 255, 255, 0.1); + outline: none; + border-bottom: 5px solid rgba(214, 174, 0, 1); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +#row:hover { + background-color: rgba(255, 255, 255, 0); + outline: inherit; +} +#window #outer-box #scroll #inner-box #entry:hover { + background-color: rgba(255, 255, 255, 0.1); + outline: inherit; +} + +#text { + margin-top: 1rem; + margin-bottom: 0; +} diff --git a/styles/fullscreen/config.toml b/styles/fullscreen/config.toml index 80bab36..c7c3564 100644 --- a/styles/fullscreen/config.toml +++ b/styles/fullscreen/config.toml @@ -1,7 +1,7 @@ image_size=64 columns=6 orientation="Vertical" -row_bow_orientation="Vertical" +row_box_orientation="Vertical" content_halign="Center" height="105%" width="100%" diff --git a/styles/fullscreen/style.css b/styles/fullscreen/style.css index fb8b944..7b782d3 100644 --- a/styles/fullscreen/style.css +++ b/styles/fullscreen/style.css @@ -29,15 +29,15 @@ } #window #outer-box #scroll #inner-box { - padding: 0 25rem; + padding: 0 20rem; } #window #outer-box #scroll #inner-box #entry { color: #fff; background-color: rgba(32, 32, 32, 0.1); - padding: 1rem; - margin: 1rem; + margin: 2em; + padding: 1em; border-radius: 0.5rem; border-bottom: 5px solid rgba(32, 32, 32, 0.1); diff --git a/styles/launcher/config.toml b/styles/launcher/config.toml index bae4b84..b8471ad 100644 --- a/styles/launcher/config.toml +++ b/styles/launcher/config.toml @@ -1,7 +1,7 @@ image_size=64 columns=6 orientation="Vertical" -row_bow_orientation="Vertical" +row_box_orientation="Vertical" content_halign="Center" height="70%" width="60%" diff --git a/worf/Cargo.toml b/worf/Cargo.toml index bd1681a..3348b9f 100644 --- a/worf/Cargo.toml +++ b/worf/Cargo.toml @@ -34,21 +34,22 @@ anyhow = "1.0.97" env_logger = "0.11.8" log = "0.4.27" regex = "1.11.1" -clap = { version = "4.5.35", features = ["derive"] } -thiserror = "2.0.12" +clap = { version = "4.5.38", features = ["derive"] } +freedesktop-icons = "0.4.0" serde = { version = "1.0.219", features = ["derive"] } toml = "0.8.20" serde_json = "1.0.140" crossbeam = "0.8.4" libc = "0.2.171" -freedesktop-file-parser = "0.1.3" +freedesktop-file-parser = "0.2.0" strsim = "0.11.1" dirs = "6.0.0" which = "7.0.3" -meval = "0.2.0" +fasteval3 = "3.0.1" tree_magic_mini = "3.1.6" rayon = "1.10.0" nix = { version = "0.30.0", features = ["process"] } emoji = "0.2.1" wl-clipboard-rs = "0.9.2" notify-rust="4.11.7" +thiserror = "2.0.12" diff --git a/worf/src/lib/config.rs b/worf/src/lib/config.rs index 294a97e..35a192c 100644 --- a/worf/src/lib/config.rs +++ b/worf/src/lib/config.rs @@ -1,11 +1,12 @@ -use std::path::PathBuf; -use std::str::FromStr; -use std::{env, fs}; - use crate::Error; use clap::{Parser, ValueEnum}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::os::unix::process::CommandExt; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::str::FromStr; +use std::{env, fs}; use thiserror::Error; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] @@ -49,6 +50,20 @@ pub enum SortOrder { Alphabetical, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum CustomKeyHintLocation { + Top, + Bottom, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum KeyDetectionType { + /// Raw keyboard value, might not be correct all layouts + Code, + /// The value of the key, but note that shift+3 != 3 (as shift+3 = #) + Value, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Mode { /// searches `$PATH` for executables and allows them to be run by selecting them. @@ -145,17 +160,31 @@ impl FromStr for SortOrder { } } +impl FromStr for KeyDetectionType { + type Err = ArgsError; + + fn from_str(s: &str) -> Result { + match s { + "value" => Ok(KeyDetectionType::Value), + "code" => Ok(KeyDetectionType::Code), + _ => Err(ArgsError::InvalidParameter( + format!("{s} is not a valid argument, 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")] #[derive(Default)] pub struct Config { /// Forks the menu so you can close the terminal #[clap(short = 'f', long = "fork")] - fork: Option, // todo support fork + fork: Option, /// Selects a config file to use #[clap(short = 'c', long = "conf")] - config: Option, + cfg_path: Option, /// Prints the version and then exits #[clap(short = 'v', long = "version")] @@ -252,8 +281,13 @@ pub struct Config { #[clap(short = 'a', long = "no-actions")] no_actions: Option, + /// If set, the given amount tof lines will be shown #[clap(short = 'L', long = "lines")] - lines: Option, // todo support this + lines: Option, + + /// Additional space to add to the window when `lines` is used. + #[clap(long = "line-additional-space")] + lines_additional_space: Option, #[clap(short = 'w', long = "columns")] columns: Option, @@ -287,7 +321,7 @@ pub struct Config { /// Defines the image size in pixels #[clap(long = "image-size")] - image_size: Option, + image_size: Option, key_up: Option, // todo support this key_down: Option, // todo support this @@ -308,13 +342,16 @@ pub struct Config { // key_custom: Option>, global_coords: Option, // todo support this - /// If set to `true` the search field will be hidden. + /// If set to `true` the search field willOption<> be hidden. #[clap(long = "hide-search")] hide_search: Option, - dynamic_lines: Option, // todo support this - layer: Option, // todo support this - copy_exec: Option, // todo support this - single_click: Option, // todo support this + #[clap(long = "dynamic-lines")] + dynamic_lines: Option, // todo support this + layer: Option, // todo support this + copy_exec: Option, // todo support this + #[clap(long = "single_click")] + single_click: Option, // todo support this + #[clap(long = "pre-display-exec")] pre_display_exec: Option, // todo support this /// Minimum score for a fuzzy search to be shown @@ -323,15 +360,27 @@ pub struct Config { /// Orientation of items in the row box where items are displayed #[clap(long = "row-box-orientation")] - row_bow_orientation: Option, + row_box_orientation: Option, #[clap(long = "line-wrap")] line_wrap: Option, + + /// Display only icon in emoji mode + #[clap(long = "emoji-hide-string")] + emoji_hide_label: Option, + + #[clap(long = "keyboard-detection-type")] + key_detection_type: Option, } impl Config { #[must_use] - pub fn image_size(&self) -> i32 { + pub fn fork(&self) -> bool { + self.fork.unwrap_or(false) + } + + #[must_use] + pub fn image_size(&self) -> u16 { self.image_size.unwrap_or(32) } @@ -347,7 +396,7 @@ impl Config { #[must_use] pub fn style(&self) -> Option { - style_path(None) + style_path(self.style.as_ref()) .ok() .map(|pb| pb.display().to_string()) .or_else(|| { @@ -427,8 +476,8 @@ impl Config { } #[must_use] - pub fn row_bow_orientation(&self) -> Orientation { - self.row_bow_orientation.unwrap_or(Orientation::Horizontal) + pub fn row_box_orientation(&self) -> Orientation { + self.row_box_orientation.unwrap_or(Orientation::Horizontal) } #[must_use] @@ -504,6 +553,28 @@ impl Config { pub fn sort_order(&self) -> SortOrder { self.sort_order.clone().unwrap_or(SortOrder::Alphabetical) } + + #[must_use] + pub fn emoji_hide_label(&self) -> bool { + self.emoji_hide_label.unwrap_or(false) + } + + #[must_use] + pub fn key_detection_type(&self) -> KeyDetectionType { + self.key_detection_type + .clone() + .unwrap_or(KeyDetectionType::Value) + } + + #[must_use] + pub fn lines(&self) -> Option { + self.lines + } + + #[must_use] + pub fn lines_additional_space(&self) -> i32 { + self.lines_additional_space.unwrap_or(0) + } } fn default_false() -> bool { @@ -599,7 +670,7 @@ pub fn parse_args() -> Config { /// # Errors /// /// Will return Err when it cannot resolve any path or no style is found -fn style_path(full_path: Option) -> Result { +fn style_path(full_path: Option<&String>) -> Result { let alternative_paths = path_alternatives( vec![dirs::config_dir()], &PathBuf::from("worf").join("style.css"), @@ -610,7 +681,7 @@ fn style_path(full_path: Option) -> Result { /// # Errors /// /// Will return Err when it cannot resolve any path or no style is found -pub fn conf_path(full_path: Option) -> Result { +pub fn conf_path(full_path: Option<&String>) -> Result { let alternative_paths = path_alternatives( vec![dirs::config_dir()], &PathBuf::from("worf").join("config"), @@ -633,9 +704,10 @@ pub fn path_alternatives(base_paths: Vec>, sub_path: &PathBuf) - /// /// Will return `Err` if it is not able to find any valid path pub fn resolve_path( - full_path: Option, + full_path: Option<&String>, alternatives: Vec, ) -> Result { + log::debug!("resolving path for {full_path:?}, with alternatives: {alternatives:?}"); full_path .map(PathBuf::from) .and_then(|p| p.canonicalize().ok().filter(|c| c.exists())) @@ -656,9 +728,10 @@ pub fn resolve_path( /// * no config file exists /// * config file and args cannot be merged pub fn load_config(args_opt: Option<&Config>) -> Result { - let config_path = conf_path(args_opt.as_ref().and_then(|c| c.config.clone())); + let config_path = conf_path(args_opt.as_ref().and_then(|c| c.cfg_path.as_ref())); match config_path { Ok(path) => { + log::debug!("loading config from {}", path.display()); let toml_content = fs::read_to_string(path).map_err(|e| Error::Io(format!("{e}")))?; let mut config: Config = toml::from_str(&toml_content).map_err(|e| Error::ParsingError(format!("{e}")))?; @@ -725,3 +798,32 @@ fn merge_json(a: &mut Value, b: &Value) { } } } + +/// Fork into background if configured +/// # Panics +/// Panics if preexec and or setsid do not work +pub fn fork_if_configured(config: &Config) { + let fork_env_var = "WORF_PROCESS_IS_FORKED"; + if config.fork() && env::var(fork_env_var).is_err() { + let mut cmd = Command::new(env::current_exe().expect("Failed to get current executable")); + + for arg in env::args().skip(1) { + cmd.arg(arg); + } + + cmd.env(fork_env_var, "1"); + cmd.stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + + unsafe { + cmd.pre_exec(|| { + libc::setsid(); + Ok(()) + }); + } + + cmd.spawn().expect("Failed to fork to background"); + std::process::exit(0); + } +} diff --git a/worf/src/lib/desktop.rs b/worf/src/lib/desktop.rs index 6746b8f..cdb498c 100644 --- a/worf/src/lib/desktop.rs +++ b/worf/src/lib/desktop.rs @@ -26,46 +26,8 @@ pub fn known_image_extension_regex_pattern() -> Regex { .expect("Internal image regex is not valid anymore.") } -/// Read an icon from a shared directory -/// * /usr/local/share/icon -/// * /usr/share/icons -/// * /usr/share/pixmaps -/// * $HOME/.local/share/icon (if exists) -/// # Errors -/// -/// Will return `Err` -/// * if it was not able to find any icon -pub fn fetch_icon_from_common_dirs(icon_name: &str) -> Result { - let mut paths = vec![ - PathBuf::from("/usr/local/share/icons"), - PathBuf::from("/usr/share/icons"), - PathBuf::from("/usr/share/pixmaps"), - ]; - - if let Some(home) = dirs::home_dir() { - paths.push(home.join(".local/share/icons")); - } - - let formatted_name = Regex::new(icon_name); - if let Ok(formatted_name) = formatted_name { - paths - .into_iter() - .filter(|dir| dir.exists()) - .find_map(|dir| { - find_file_via_regex(dir.as_path(), &formatted_name) - .and_then(|files| files.first().map(|f| f.to_string_lossy().into_owned())) - }) - .ok_or(Error::MissingIcon) - } else { - Err(Error::ParsingError( - "Failed to get formatted icon, likely the internal regex did not parse properly" - .to_string(), - )) - } -} - /// Helper function to retrieve a file with given regex. -fn find_file_via_regex(folder: &Path, file_name: &Regex) -> Option> { +fn find_files(folder: &Path, file_name: &Regex) -> Option> { if !folder.exists() || !folder.is_dir() { return None; } @@ -120,7 +82,7 @@ pub fn find_desktop_files() -> Vec { let p: Vec<_> = paths .into_par_iter() .filter(|desktop_dir| desktop_dir.exists()) - .filter_map(|icon_dir| find_file_via_regex(&icon_dir, regex)) + .filter_map(|desktop_dir| find_files(&desktop_dir, regex)) .flat_map(|desktop_files| { desktop_files.into_par_iter().filter_map(|desktop_file| { fs::read_to_string(desktop_file) diff --git a/worf/src/lib/gui.rs b/worf/src/lib/gui.rs index 342ab94..3435317 100644 --- a/worf/src/lib/gui.rs +++ b/worf/src/lib/gui.rs @@ -1,6 +1,6 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::rc::Rc; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; use std::thread; use std::time::Instant; @@ -25,11 +25,15 @@ use gtk4_layer_shell::{Edge, KeyboardMode, LayerShell}; use log; use regex::Regex; -use crate::config::{Anchor, Config, MatchMethod, SortOrder, WrapMode}; -use crate::desktop::known_image_extension_regex_pattern; -use crate::{Error, config, desktop}; +use crate::{ + Error, config, + config::{ + Anchor, Config, CustomKeyHintLocation, KeyDetectionType, MatchMethod, SortOrder, WrapMode, + }, + desktop::known_image_extension_regex_pattern, +}; -type ArcMenuMap = Arc>>>; +type ArcMenuMap = Arc>>>; type ArcProvider = Arc + Send>>; pub struct Selection { @@ -83,6 +87,14 @@ impl From for Align { } } +fn into_core_order(gtk_order: Ordering) -> core::cmp::Ordering { + match gtk_order { + Ordering::Smaller => core::cmp::Ordering::Less, + Ordering::Larger => core::cmp::Ordering::Greater, + _ => core::cmp::Ordering::Equal, + } +} + /// An entry in the list of selectable items in the UI. /// Supports nested items but these cannot nested again (only nesting with depth == 1 is supported) #[derive(Clone, PartialEq)] @@ -327,6 +339,109 @@ impl From for Key { } } +impl From for Key { + fn from(value: u32) -> Self { + match value { + // Letters + 38 => Key::A, + 56 => Key::B, + 54 => Key::C, + 40 => Key::D, + 26 => Key::E, + 41 => Key::F, + 42 => Key::G, + 43 => Key::H, + 31 => Key::I, + 44 => Key::J, + 45 => Key::K, + 46 => Key::L, + 58 => Key::M, + 57 => Key::N, + 32 => Key::O, + 33 => Key::P, + 24 => Key::Q, + 27 => Key::R, + 39 => Key::S, + 28 => Key::T, + 30 => Key::U, + 55 => Key::V, + 25 => Key::W, + 53 => Key::X, + 29 => Key::Y, + 52 => Key::Z, + + // Numbers + 10 => Key::Num0, + 11 => Key::Num1, + 12 => Key::Num2, + 13 => Key::Num3, + 14 => Key::Num4, + 15 => Key::Num5, + 16 => Key::Num6, + 17 => Key::Num7, + 18 => Key::Num8, + 19 => Key::Num9, + + // Function Keys + 67 => Key::F1, + 68 => Key::F2, + 69 => Key::F3, + 70 => Key::F4, + 71 => Key::F5, + 72 => Key::F6, + 73 => Key::F7, + 74 => Key::F8, + 75 => Key::F9, + 76 => Key::F10, + 77 => Key::F11, + 78 => Key::F12, + + // Navigation / Editing + 9 => Key::Escape, + 36 => Key::Enter, + 65 => Key::Space, + 23 => Key::Tab, + 22 => Key::Backspace, + 118 => Key::Insert, + 119 => Key::Delete, + 110 => Key::Home, + 115 => Key::End, + 112 => Key::PageUp, + 117 => Key::PageDown, + 113 => Key::Left, + 114 => Key::Right, + 111 => Key::Up, + 116 => Key::Down, + + // Special characters + 20 => Key::Exclamation, + 63 => Key::At, + 3 => Key::Hash, + 4 => Key::Dollar, + 5 => Key::Percent, + 6 => Key::Caret, + 7 => Key::Ampersand, + 8 => Key::Asterisk, + 34 => Key::LeftParen, + 35 => Key::RightParen, + 48 => Key::Minus, + 47 => Key::Underscore, + 21 => Key::Equal, + 49 => Key::Plus, + 51 => Key::Backslash, + 94 => Key::Pipe, + 50 => Key::Quote, + 59 => Key::Comma, + 60 => Key::Period, + 61 => Key::Slash, + 62 => Key::Question, + 96 => Key::Grave, + 97 => Key::Tilde, + _ => Key::None, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Modifier { Shift, @@ -338,25 +453,53 @@ pub enum Modifier { None, } -impl From for Modifier { - fn from(value: gdk4::ModifierType) -> Self { - match value { - gdk4::ModifierType::SHIFT_MASK => Modifier::Shift, - gdk4::ModifierType::CONTROL_MASK => Modifier::Control, - gdk4::ModifierType::ALT_MASK => Modifier::Alt, - gdk4::ModifierType::SUPER_MASK => Modifier::Super, - gdk4::ModifierType::META_MASK => Modifier::Meta, - gdk4::ModifierType::LOCK_MASK => Modifier::CapsLock, - _ => Modifier::None, - } +fn modifiers_from_mask(mask: gdk4::ModifierType) -> HashSet { + let mut modifiers = HashSet::new(); + + if mask.contains(gdk4::ModifierType::SHIFT_MASK) { + modifiers.insert(Modifier::Shift); + } + if mask.contains(gdk4::ModifierType::CONTROL_MASK) { + modifiers.insert(Modifier::Control); } + if mask.contains(gdk4::ModifierType::ALT_MASK) { + modifiers.insert(Modifier::Alt); + } + if mask.contains(gdk4::ModifierType::SUPER_MASK) { + modifiers.insert(Modifier::Super); + } + if mask.contains(gdk4::ModifierType::META_MASK) { + modifiers.insert(Modifier::Meta); + } + if mask.contains(gdk4::ModifierType::LOCK_MASK) { + modifiers.insert(Modifier::CapsLock); + } + + if modifiers.is_empty() { + modifiers.insert(Modifier::None); + } + + modifiers } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Debug)] pub struct KeyBinding { pub key: Key, - pub modifiers: Modifier, // todo support masks + pub modifiers: HashSet, + pub label: String, + pub visible: bool, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct CustomKeyHint { pub label: String, + pub location: CustomKeyHintLocation, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct CustomKeys { + pub bindings: Vec, + pub hint: Option, } impl MenuItem { @@ -416,7 +559,7 @@ pub fn show( item_provider: P, new_on_empty: bool, search_ignored_words: Option>, - custom_keys: Option>, + custom_keys: Option, ) -> Result, Error> where T: Clone + 'static + Send, @@ -425,6 +568,7 @@ where gtk4::init().map_err(|e| Error::Graphics(e.to_string()))?; log::debug!("Starting GUI"); if let Some(ref css) = config.style() { + log::debug!("loading css from {css}"); let provider = CssProvider::new(); let css_file_path = File::for_path(css); provider.load_from_file(&css_file_path); @@ -473,7 +617,7 @@ fn build_ui( app: Application, new_on_empty: bool, search_ignored_words: Option>, - custom_keys: Option<&Vec>, + custom_keys: Option<&CustomKeys>, ) where T: Clone + 'static + Send, P: ItemProvider + 'static + Send, @@ -498,8 +642,8 @@ fn build_ui( .application(&app) .decorated(false) .resizable(false) - .default_width(100) - .default_height(100) + .default_width(1) + .default_height(1) .build(); let ui_elements = Rc::new(UiElements { @@ -507,7 +651,7 @@ fn build_ui( window, search: SearchEntry::new(), main_box: FlowBox::new(), - menu_rows: Arc::new(Mutex::new(HashMap::new())), + menu_rows: Arc::new(RwLock::new(HashMap::new())), search_text: Arc::new(Mutex::new(String::new())), }); @@ -539,7 +683,9 @@ fn build_ui( let outer_box = gtk4::Box::new(config.orientation().into(), 0); outer_box.set_widget_name("outer-box"); outer_box.append(&ui_elements.search); - build_custom_key_view(custom_keys, &outer_box); + if let Some(custom_keys) = custom_keys { + build_custom_key_view(custom_keys, &outer_box); + } ui_elements.window.set_child(Some(&outer_box)); @@ -563,18 +709,15 @@ fn build_ui( let wait_for_items = Instant::now(); let (_changed, provider_elements) = get_provider_elements.join().unwrap(); log::debug!("got items after {:?}", wait_for_items.elapsed()); - build_ui_from_menu_items(&ui_elements, &meta, provider_elements); - - let animate_cfg = config.clone(); - let animate_window = ui_elements.window.clone(); - animate_window.connect_is_active_notify(move |w| { - w.set_opacity(1.0); - window_show_resize(&animate_cfg.clone(), w); + let cfg = config.clone(); + let ui = Rc::clone(&ui_elements); + ui_elements.window.connect_is_active_notify(move |_| { + window_show_resize(&cfg.clone(), &ui); }); - // hide the fact that we are starting with a small window - ui_elements.window.set_opacity(0.01); + build_ui_from_menu_items(&ui_elements, &meta, provider_elements); + let window_start = Instant::now(); ui_elements.window.present(); log::debug!("window show took {:?}", window_start.elapsed()); @@ -594,7 +737,6 @@ fn build_main_box(config: &Config, ui_elements: &Rc(config: &Config, ui_elements: &Rc( } } -fn build_custom_key_view(custom_keys: Option<&Vec>, outer_box: >k4::Box) { - let inner_box = FlowBox::new(); +fn build_custom_key_view(custom_keys: &CustomKeys, outer_box: >k4::Box) { + fn create_label(inner_box: &FlowBox, text: &str, label_css: &str, box_css: &str) { + let label_box = FlowBoxChild::new(); + label_box.set_halign(Align::Fill); + inner_box.set_valign(Align::Start); + label_box.set_widget_name(box_css); + inner_box.append(&label_box); + inner_box.set_vexpand(false); + inner_box.set_hexpand(false); + let label = Label::new(Some(text)); + label.set_halign(Align::Fill); + label.set_valign(Align::Start); + label.set_use_markup(true); + label.set_hexpand(true); + label.set_vexpand(false); + label.set_widget_name(label_css); + label.set_wrap(false); + label.set_xalign(0.0); + label_box.set_child(Some(&label)); + } + + let inner_box = gtk4::Box::new(Orientation::Vertical, 0); inner_box.set_halign(Align::Fill); - inner_box.set_widget_name("custom-key-box"); - if let Some(custom_keys) = custom_keys { - for key in custom_keys { - let label_box = FlowBoxChild::new(); - label_box.set_halign(Align::Fill); - inner_box.set_valign(Align::Start); - label_box.set_widget_name("custom-key-label-box"); - inner_box.append(&label_box); - inner_box.set_vexpand(false); - inner_box.set_hexpand(false); - let label = Label::new(Some(&key.label)); - label.set_halign(Align::Fill); - label.set_valign(Align::Start); - label.set_use_markup(true); - label.set_hexpand(true); - label.set_vexpand(false); - label.set_widget_name("custom-key-label-text"); - label.set_wrap(false); - label.set_xalign(0.0); - label_box.set_child(Some(&label)); + + let hint_box = FlowBox::new(); + hint_box.set_halign(Align::Fill); + hint_box.set_widget_name("custom-key-box"); + + let custom_key_box = FlowBox::new(); + custom_key_box.set_halign(Align::Fill); + custom_key_box.set_widget_name("custom-key-box"); + inner_box.append(&custom_key_box); + + let make_key_labels = || { + for key in custom_keys.bindings.iter().filter(|key| key.visible) { + create_label( + &custom_key_box, + key.label.as_ref(), + "custom-key-label-text", + "custom-key-label-box", + ); + } + }; + + if let Some(hint) = custom_keys.hint.as_ref() { + match hint.location { + CustomKeyHintLocation::Top => { + inner_box.append(&hint_box); + create_label( + &hint_box, + &hint.label, + "custom-key-hint-text", + "custom-key-hint-box", + ); + make_key_labels(); + } // todo this surely can be done better + CustomKeyHintLocation::Bottom => { + make_key_labels(); + create_label( + &hint_box, + &hint.label, + "custom-key-hint-text", + "custom-key-hint-box", + ); + inner_box.append(&hint_box); + } } } + outer_box.append(&inner_box); } @@ -679,24 +865,27 @@ fn build_ui_from_menu_items( meta: &Rc>, mut items: Vec>, ) { + if meta.config.sort_order() != SortOrder::Default { + items.reverse(); + } let start = Instant::now(); { while let Some(b) = ui.main_box.child_at_index(0) { ui.main_box.remove(&b); drop(b); } - ui.menu_rows.lock().unwrap().clear(); + ui.menu_rows.write().unwrap().clear(); let meta_clone = Rc::>::clone(meta); let ui_clone = Rc::>::clone(ui); glib::idle_add_local(move || { - ui_clone.main_box.unset_sort_func(); let mut done = false; { - let mut lock = ui_clone.menu_rows.lock().unwrap(); + ui_clone.main_box.unset_sort_func(); + let mut lock = ui_clone.menu_rows.write().unwrap(); - for _ in 0..100 { + for _ in 0..25 { if let Some(item) = items.pop() { lock.insert(add_menu_item(&ui_clone, &meta_clone, &item), item); } else { @@ -713,22 +902,22 @@ fn build_ui_from_menu_items( meta_clone.search_ignored_words.as_ref(), ); } - let items_sort = ArcMenuMap::clone(&ui_clone.menu_rows); ui_clone.main_box.set_sort_func(move |child1, child2| { - sort_menu_items_by_score(child1, child2, &items_sort) + sort_flow_box_childs(child1, child2, &items_sort) }); if done { - let mut lock = ui_clone.menu_rows.lock().unwrap(); - let menus = &mut *lock; - select_first_visible_child(menus, &ui_clone.main_box); + let lock = ui_clone.menu_rows.read().unwrap(); + + select_first_visible_child(&lock, &ui_clone.main_box); log::debug!( "Created {} menu items in {:?}", - menus.len(), + &lock.len(), start.elapsed() ); + ControlFlow::Break } else { ControlFlow::Continue @@ -740,18 +929,19 @@ fn build_ui_from_menu_items( fn setup_key_event_handler( ui: &Rc>, meta: &Rc>, - custom_keys: Option<&Vec>, + custom_keys: Option<&CustomKeys>, ) { let key_controller = EventControllerKey::new(); let ui_clone = Rc::clone(ui); let meta_clone = Rc::clone(meta); let keys_clone = custom_keys.cloned(); - key_controller.connect_key_pressed(move |_, key_value, _, modifier| { + key_controller.connect_key_pressed(move |_, key_value, key_code, modifier| { handle_key_press( &ui_clone, &meta_clone, key_value, + key_code, modifier, keys_clone.as_ref(), ) @@ -765,15 +955,15 @@ fn handle_key_press( ui: &Rc>, meta: &Rc>, keyboard_key: gdk4::Key, + key_code: u32, modifier_type: gdk4::ModifierType, - custom_keys: Option<&Vec>, + custom_keys: Option<&CustomKeys>, ) -> Propagation { let update_view = |query: &String| { - let mut lock = ui.menu_rows.lock().unwrap(); - let menus = &mut *lock; + let mut lock = ui.menu_rows.write().unwrap(); set_menu_visibility_for_search( query, - menus, + &mut lock, &meta.config, meta.search_ignored_words.as_ref(), ); @@ -789,9 +979,15 @@ fn handle_key_press( }; if let Some(custom_keys) = custom_keys { - for custom_key in custom_keys { - if custom_key.key == keyboard_key.into() && custom_key.modifiers == modifier_type.into() - { + let mods = modifiers_from_mask(modifier_type); + for custom_key in &custom_keys.bindings { + let custom_key_match = if meta.config.key_detection_type() == KeyDetectionType::Code { + custom_key.key == key_code.into() + } else { + custom_key.key == keyboard_key.to_upper().into() + } && mods.is_subset(&custom_key.modifiers); + + if custom_key_match { let search_lock = ui.search_text.lock().unwrap(); if let Err(e) = handle_selected_item( ui, @@ -844,7 +1040,7 @@ fn handle_key_press( expander.set_expanded(true); } else { let opt_changed = { - let lock = ui.menu_rows.lock().unwrap(); + let lock = ui.menu_rows.read().unwrap(); let menu_item = lock.get(fb); menu_item.map(|menu_item| { ( @@ -883,12 +1079,12 @@ fn handle_key_press( Propagation::Proceed } -fn sort_menu_items_by_score( +fn sort_flow_box_childs( child1: &FlowBoxChild, child2: &FlowBoxChild, items_lock: &ArcMenuMap, ) -> Ordering { - let lock = items_lock.lock().unwrap(); + let lock = items_lock.read().unwrap(); let m1 = lock.get(child1); let m2 = lock.get(child2); @@ -899,6 +1095,13 @@ fn sort_menu_items_by_score( return Ordering::Larger; } + sort_menu_items_by_score(m1, m2) +} + +fn sort_menu_items_by_score( + m1: Option<&MenuItem>, + m2: Option<&MenuItem>, +) -> Ordering { match (m1, m2) { (Some(menu1), Some(menu2)) => { fn compare(a: f64, b: f64) -> Ordering { @@ -923,28 +1126,54 @@ fn sort_menu_items_by_score( } } -fn window_show_resize(config: &Config, window: &ApplicationWindow) { - if let Some(surface) = window.surface() { - let display = surface.display(); - let monitor = display.monitor_at_surface(&surface); - if let Some(monitor) = monitor { - let geometry = monitor.geometry(); - let Some(target_width) = percent_or_absolute(&config.width(), geometry.width()) else { - return; - }; +fn window_show_resize(config: &Config, ui: &Rc>) { + // Get the surface and associated monitor geometry + let Some(surface) = ui.window.surface() else { + return; + }; - let Some(target_height) = percent_or_absolute(&config.height(), geometry.height()) - else { - return; - }; + let display = surface.display(); + let Some(monitor) = display.monitor_at_surface(&surface) else { + return; + }; + let geometry = monitor.geometry(); - log::debug!( - "monitor geometry: {geometry:?}, target_height {target_height}, target_width {target_width}" - ); + // Calculate target width from config, return early if not set + let Some(target_width) = percent_or_absolute(&config.width(), geometry.width()) else { + log::error!("width is not set"); + return; + }; - window.set_width_request(target_width); - window.set_height_request(target_height); + let target_height = if let Some(lines) = config.lines() { + let (_, _, _, height_search) = ui.search.measure(Orientation::Vertical, 10_000); + let height = { + let lock = ui.menu_rows.read().unwrap(); + lock.iter().find_map(|(fb, _)| { + let (_, _, _, baseline) = fb.measure(Orientation::Vertical, 10_000); + if baseline > 0 { Some(baseline) } else { None } + }) + }; + + if let Some(height) = height { + Some((height_search + height) * lines + config.lines_additional_space()) + } else { + log::warn!("No widget for height calculation available"); + Some(0) } + } else if let Some(height) = percent_or_absolute(&config.height(), geometry.height()) { + Some(height) + } else { + log::error!("Widget is none"); + Some(0) + }; + + // Apply the calculated size or log an error if height missing + if let Some(target_height) = target_height { + log::debug!("Setting width {target_width}, height {target_height}"); + ui.window.set_height_request(target_height); + ui.window.set_width_request(target_width); + } else { + log::error!("height is not set"); } } @@ -967,7 +1196,7 @@ where send_selected_item(ui, meta, custom_key.cloned(), selected_item); return Ok(()); } else if let Some(s) = ui.main_box.selected_children().into_iter().next() { - let list_items = ui.menu_rows.lock().unwrap(); + let list_items = ui.menu_rows.read().unwrap(); let item = list_items.get(&s); if let Some(selected_item) = item { if selected_item.visible { @@ -1074,7 +1303,7 @@ fn create_menu_row( row.set_halign(Align::Fill); row.set_widget_name("row"); - let row_box = gtk4::Box::new(meta.config.row_bow_orientation().into(), 0); + let row_box = gtk4::Box::new(meta.config.row_box_orientation().into(), 0); row_box.set_hexpand(true); row_box.set_vexpand(false); row_box.set_halign(Align::Fill); @@ -1187,7 +1416,11 @@ fn lookup_icon(icon_path: Option<&str>, config: &Config) -> Option { let image = if image_path.starts_with('/') { Image::from_file(image_path) } else if img_regex.unwrap().is_match(image_path) { - if let Ok(img) = desktop::fetch_icon_from_common_dirs(image_path) { + if let Some(img) = freedesktop_icons::lookup(image_path) + .with_size(config.image_size()) + .with_scale(1) + .find() + { Image::from_file(img) } else { Image::from_icon_name(image_path) @@ -1196,7 +1429,7 @@ fn lookup_icon(icon_path: Option<&str>, config: &Config) -> Option { Image::from_icon_name(image_path) }; - image.set_pixel_size(config.image_size()); + image.set_pixel_size(i32::from(config.image_size())); Some(image) } else { None @@ -1209,10 +1442,6 @@ fn set_menu_visibility_for_search( config: &Config, search_ignored_words: Option<&Vec>, ) { - if config.sort_order() == SortOrder::Default { - return; - } - { if query.is_empty() { for (fb, menu_item) in items.iter_mut() { @@ -1338,6 +1567,8 @@ pub fn apply_sort(items: &mut [MenuItem], order: &SortOrder) { item.initial_sort_score += special_score; } } + + items.sort_by(|l, r| into_core_order(sort_menu_items_by_score(Some(l), Some(r)))); } } } diff --git a/worf/src/lib/modes/auto.rs b/worf/src/lib/modes/auto.rs index 3413bbd..a223bed 100644 --- a/worf/src/lib/modes/auto.rs +++ b/worf/src/lib/modes/auto.rs @@ -1,8 +1,7 @@ use crate::config::Config; -use crate::desktop::{copy_to_clipboard, spawn_fork}; +use crate::desktop::spawn_fork; use crate::gui::{ItemProvider, MenuItem}; use crate::modes::drun::{DRunProvider, update_drun_cache_and_run}; -use crate::modes::emoji::EmojiProvider; use crate::modes::file::FileItemProvider; use crate::modes::math::MathProvider; use crate::modes::ssh; @@ -16,7 +15,6 @@ enum AutoRunType { DRun, File, Ssh, - Emoji, // WebSearch, } @@ -26,7 +24,6 @@ struct AutoItemProvider { file: FileItemProvider, math: MathProvider, ssh: SshProvider, - emoji: EmojiProvider, last_mode: Option, } @@ -37,7 +34,6 @@ impl AutoItemProvider { file: FileItemProvider::new(AutoRunType::File, config.sort_order()), math: MathProvider::new(AutoRunType::Math), ssh: SshProvider::new(AutoRunType::Ssh, &config.sort_order()), - emoji: EmojiProvider::new(AutoRunType::Emoji, &config.sort_order()), last_mode: None, } } @@ -84,8 +80,6 @@ impl ItemProvider for AutoItemProvider { (AutoRunType::File, self.file.get_elements(search_opt)) } else if search.starts_with("ssh") { (AutoRunType::Ssh, self.ssh.get_elements(search_opt)) - } else if search.starts_with("emoji") { - (AutoRunType::Emoji, self.emoji.get_elements(search_opt)) } else { return self.default_auto_elements(search_opt); }; @@ -156,14 +150,6 @@ pub fn show(config: &Config) -> Result<(), Error> { ssh::launch(&selection_result, config)?; break; } - AutoRunType::Emoji => { - if let Some(action) = selection_result.action { - copy_to_clipboard(action, None)?; - } else { - return Err(Error::MissingAction); - } - break; - } } } else if selection_result.label.starts_with("ssh") { selection_result.label = selection_result.label.chars().skip(4).collect(); diff --git a/worf/src/lib/modes/emoji.rs b/worf/src/lib/modes/emoji.rs index 16f12a0..3f648ae 100644 --- a/worf/src/lib/modes/emoji.rs +++ b/worf/src/lib/modes/emoji.rs @@ -11,15 +11,22 @@ pub(crate) struct EmojiProvider { } impl EmojiProvider { - pub(crate) fn new(data: T, sort_order: &SortOrder) -> Self { + pub(crate) fn new(data: T, sort_order: &SortOrder, hide_label: bool) -> Self { let emoji = emoji::search::search_annotation_all(""); let mut menus = emoji .into_iter() .map(|e| { MenuItem::new( - format!("{} — Category: {} — Name: {}", e.glyph, e.group, e.name), + if hide_label { + e.glyph.to_string() + } else { + format!("{} — Category: {} — Name: {}", e.glyph, e.group, e.name) + }, None, - Some(format!("emoji {}", e.glyph)), + Some(format!( + "emoji {} — Category: {} — Name: {}", + e.glyph, e.group, e.name + )), vec![], None, 0.0, @@ -51,7 +58,7 @@ impl ItemProvider for EmojiProvider { /// /// Forwards errors from the gui. See `gui::show` for details. pub fn show(config: &Config) -> Result<(), Error> { - let provider = EmojiProvider::new(0, &config.sort_order()); + let provider = EmojiProvider::new(0, &config.sort_order(), config.emoji_hide_label()); let selection_result = gui::show(config.clone(), provider, true, None, None)?; match selection_result.menu.action { None => Err(Error::MissingAction), diff --git a/worf/src/lib/modes/math.rs b/worf/src/lib/modes/math.rs index aa75ce6..52f9001 100644 --- a/worf/src/lib/modes/math.rs +++ b/worf/src/lib/modes/math.rs @@ -1,6 +1,7 @@ use crate::config::Config; use crate::gui; use crate::gui::{ItemProvider, MenuItem}; +use regex::Regex; #[derive(Clone)] pub(crate) struct MathProvider { @@ -21,10 +22,20 @@ impl MathProvider { } impl ItemProvider for MathProvider { + #[allow(clippy::cast_possible_truncation)] fn get_elements(&mut self, search: Option<&str>) -> (bool, Vec>) { if let Some(search_text) = search { - let result = match meval::eval_str(search_text) { - Ok(result) => result.to_string(), + let re = Regex::new(r"0x[0-9a-fA-F]+").unwrap(); + let result = re.replace_all(search_text, |caps: ®ex::Captures| { + let hex_str = &caps[0][2..]; // Skip "0x" + let decimal = u64::from_str_radix(hex_str, 16).unwrap(); + decimal.to_string() + }); + + // todo maybe we want to support variables later? + let mut ns = fasteval3::EmptyNamespace; + let result = match fasteval3::ez_eval(&result, &mut ns) { + Ok(result) => format!("{} (0x{:X})", result, result as i64), Err(e) => format!("failed to calculate {e:?}"), }; diff --git a/worf/src/main.rs b/worf/src/main.rs index 7a43ae3..9c5d6fa 100644 --- a/worf/src/main.rs +++ b/worf/src/main.rs @@ -1,7 +1,7 @@ use std::env; use anyhow::anyhow; -use worf_lib::config::Mode; +use worf_lib::config::{Mode, fork_if_configured}; use worf_lib::{Error, config, modes}; fn main() -> anyhow::Result<()> { @@ -11,7 +11,16 @@ fn main() -> anyhow::Result<()> { .init(); let args = config::parse_args(); - let config = config::load_config(Some(&args)).unwrap_or(args); + + let config = config::load_config(Some(&args)); + let config = match config { + Ok(c) => c, + Err(e) => { + log::error!("error during config load, skipping it, {e}"); + args + } + }; + fork_if_configured(&config); if let Some(show) = &config.show() { let result = match show {