diff --git a/Cargo.lock b/Cargo.lock index 4f787b8..4ca2cbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,34 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "serde", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -75,7 +47,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -86,7 +58,7 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -95,28 +67,6 @@ version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "atty" version = "0.2.14" @@ -134,21 +84,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -161,12 +96,6 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - [[package]] name = "cairo-rs" version = "0.20.7" @@ -333,27 +262,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - [[package]] name = "dirs" version = "6.0.0" @@ -372,7 +280,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -423,7 +331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -458,21 +366,6 @@ dependencies = [ "xdgkit", ] -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.31" @@ -480,7 +373,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -506,16 +398,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" -[[package]] -name = "futures-lite" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" -dependencies = [ - "futures-core", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.31" @@ -527,12 +409,6 @@ dependencies = [ "syn", ] -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - [[package]] name = "futures-task" version = "0.3.31" @@ -545,13 +421,9 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", "futures-macro", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -626,12 +498,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "gio" version = "0.20.9" @@ -659,7 +525,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -898,39 +764,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hyprland" -version = "0.4.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc9c1413b6f0fd10b2e4463479490e30b2497ae4449f044da16053f5f2cb03b8" -dependencies = [ - "ahash", - "async-stream", - "derive_more", - "either", - "futures-lite", - "hyprland-macros", - "num-traits", - "once_cell", - "paste", - "phf", - "serde", - "serde_json", - "serde_repr", - "tokio", -] - -[[package]] -name = "hyprland-macros" -version = "0.4.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e3cbed6e560408051175d29a9ed6ad1e64a7ff443836addf797b0479f58983" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -1021,16 +854,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.27" @@ -1068,26 +891,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.52.0", -] - [[package]] name = "nom" version = "1.2.4" @@ -1104,24 +907,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -1164,35 +949,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "petgraph" version = "0.6.5" @@ -1203,48 +959,6 @@ dependencies = [ "indexmap 2.9.0", ] -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1315,21 +1029,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - [[package]] name = "rayon" version = "1.10.0" @@ -1350,15 +1049,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" -dependencies = [ - "bitflags 2.9.0", -] - [[package]] name = "redox_users" version = "0.5.0" @@ -1399,12 +1089,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustc_version" version = "0.4.1" @@ -1424,7 +1108,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -1433,12 +1117,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "semver" version = "1.0.26" @@ -1477,17 +1155,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "serde_spanned" version = "0.6.8" @@ -1497,21 +1164,6 @@ dependencies = [ "serde", ] -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "siphasher" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" - [[package]] name = "slab" version = "0.4.9" @@ -1527,16 +1179,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" -[[package]] -name = "socket2" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "strsim" version = "0.10.0" @@ -1620,35 +1262,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e004df4c5f0805eb5f55883204a514cfa43a6d924741be29e871753a53d5565a" -[[package]] -name = "tokio" -version = "1.44.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "toml" version = "0.8.20" @@ -1702,12 +1315,6 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "utf8parse" version = "0.2.2" @@ -1720,12 +1327,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1766,7 +1367,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -1775,15 +1376,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -1882,11 +1474,9 @@ dependencies = [ "dirs", "env_logger", "freedesktop-file-parser", - "futures", "gdk4", "gtk4", "gtk4-layer-shell", - "hyprland", "libc", "log", "meval", @@ -1896,7 +1486,6 @@ dependencies = [ "serde_json", "strsim 0.11.1", "thiserror", - "tokio", "toml", "tree_magic_mini", "which", @@ -1928,23 +1517,3 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index 08c3539..30c5517 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,9 @@ name = "worf" path = "src/main.rs" +[package.metadata.docs.rs] +no-deps = true + [dependencies] gtk4 = { version = "0.9.5", default-features = true, features = ["v4_6"] } gtk4-layer-shell = "0.5.0" @@ -29,7 +32,6 @@ anyhow = "1.0.97" env_logger = "0.11.8" log = "0.4.27" regex = "1.11.1" -hyprland = "0.4.0-beta.2" clap = { version = "4.5.35", features = ["derive"] } thiserror = "2.0.12" serde = { version = "1.0.219", features = ["derive"] } @@ -44,5 +46,3 @@ which = "7.0.3" meval = "0.2.0" tree_magic_mini = "3.1.6" rayon = "1.10.0" -tokio = { version = "1.44.2", features = ["full"] } -futures = "0.3.31" diff --git a/src/lib/desktop.rs b/src/lib/desktop.rs index 9e35b22..e574c8c 100644 --- a/src/lib/desktop.rs +++ b/src/lib/desktop.rs @@ -1,63 +1,17 @@ -use std::collections::HashMap; -use std::path::Path; -use std::path::PathBuf; -use std::time::Instant; -use std::{env, fs}; - +use crate::Error; +use crate::config::expand_path; use freedesktop_file_parser::DesktopFile; use rayon::prelude::*; use regex::Regex; +use std::collections::HashMap; +use std::os::unix::prelude::CommandExt; +use std::path::Path; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::time::Instant; +use std::{env, fs, io}; -#[derive(Debug)] -pub enum DesktopError { - MissingIcon, - ParsingError(String), -} -// -// /// # Errors -// /// -// /// Will return `Err` if no icon can be found -// pub fn default_icon() -> Result { -// fetch_icon_from_theme("image-missing").map_err(|_| DesktopError::MissingIcon) -// } -// -// fn fetch_icon_from_theme(icon_name: &str) -> Result { -// let display = Display::default(); -// if display.is_none() { -// log::error!("Failed to get display"); -// } -// -// let display = Display::default().expect("Failed to get default display"); -// let theme = IconTheme::for_display(&display); -// -// let icon = theme.lookup_icon( -// icon_name, -// &[], -// 32, -// 1, -// TextDirection::None, -// IconLookupFlags::empty(), -// ); -// -// match icon -// .file() -// .and_then(|file| file.path()) -// .and_then(|path| path.to_str().map(string::ToString::to_string)) -// { -// None => { -// let path = PathBuf::from("/usr/share/icons") -// .join(theme.theme_name()) -// .join(format!("{icon_name}.svg")); -// if path.exists() { -// Ok(path.display().to_string()) -// } else { -// Err(DesktopError::MissingIcon) -// } -// } -// Some(i) => Ok(i), -// } -// }use futures::StreamExt; - +/// Returns a regex with supported image extensions /// # Panics /// /// When it cannot parse the internal regex @@ -67,11 +21,16 @@ 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 { +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"), @@ -88,19 +47,20 @@ pub fn fetch_icon_from_common_dirs(icon_name: &str) -> Result Option> { +/// Helper function to retrieve a file with given regex. +fn find_file_via_regex(folder: &Path, file_name: &Regex) -> Option> { if !folder.exists() || !folder.is_dir() { return None; } @@ -119,6 +79,10 @@ fn find_file_case_insensitive(folder: &Path, file_name: &Regex) -> Option Vec { let p: Vec<_> = paths .into_par_iter() .filter(|desktop_dir| desktop_dir.exists()) - .filter_map(|icon_dir| find_file_case_insensitive(&icon_dir, regex)) + .filter_map(|icon_dir| find_file_via_regex(&icon_dir, regex)) .flat_map(|desktop_files| { desktop_files.into_par_iter().filter_map(|desktop_file| { fs::read_to_string(desktop_file) @@ -164,30 +128,7 @@ pub fn find_desktop_files() -> Vec { p } -/// # Panics -/// -/// When it cannot parse the internal regex -#[must_use] -pub fn lookup_icon(name: &str, size: i32) -> gtk4::Image { - let img_regex = Regex::new(&format!( - r"((?i).*{})|(^/.*)", - known_image_extension_regex_pattern() - )); - let image = if img_regex.expect("invalid icon regex").is_match(name) { - if let Ok(img) = fetch_icon_from_common_dirs(name) { - gtk4::Image::from_file(img) - } else { - gtk4::Image::from_icon_name(name) - } - } else { - gtk4::Image::from_icon_name(name) - }; - - image.set_pixel_size(size); - - image -} - +/// Return all possible locales based on the users preferences #[must_use] pub fn get_locale_variants() -> Vec { let locale = env::var("LC_ALL") @@ -208,6 +149,7 @@ pub fn get_locale_variants() -> Vec { variants } +/// Lookup a value from a hashmap with respect to current locale // implicit hasher does not make sense here, it is only for desktop files #[allow(clippy::implicit_hasher)] #[must_use] @@ -222,3 +164,107 @@ pub fn lookup_name_with_locale( .map(std::borrow::ToOwned::to_owned) .or_else(|| Some(fallback.to_owned())) } + +/// Spawn a new process and forks it away from the current worf process +/// # Errors +/// * No action in menu item +/// * Cannot run command (i.e. not found) +pub fn spawn_fork(cmd: &str, working_dir: Option<&String>) -> Result<(), Error> { + // todo fix actions ?? + // todo graphical disk map icon not working + + let parts = cmd.split(' ').collect::>(); + if parts.is_empty() { + return Err(Error::MissingAction); + } + + if let Some(dir) = working_dir { + env::set_current_dir(dir) + .map_err(|e| Error::RunFailed(format!("cannot set workdir {e}")))?; + } + + let exec = parts[0].replace('"', ""); + let args: Vec<_> = parts + .iter() + .skip(1) + .filter(|arg| !arg.starts_with('%')) + .map(|arg| expand_path(arg)) + .collect(); + + unsafe { + let _ = Command::new(exec) + .args(args) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .pre_exec(|| { + libc::setsid(); + Ok(()) + }) + .spawn(); + } + Ok(()) +} + +/// Parse a simple toml cache file from the format below +/// "Key"=score +/// i.e. +/// "Firefox"=42 +/// "Chrome"=12 +/// "Files"=50 +/// # Errors +/// Returns an Error when the given file is not found or did not parse. +pub fn load_cache_file(cache_path: Option<&PathBuf>) -> Result, Error> { + let Some(path) = cache_path else { + return Err(Error::MissingFile); + }; + + let toml_content = + fs::read_to_string(path).map_err(|e| Error::UpdateCacheError(format!("{e}")))?; + let parsed: toml::Value = toml_content + .parse() + .map_err(|_| Error::ParsingError("failed to parse cache".to_owned()))?; + + 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 '{key}' because it's not an integer"); + } + } + } + Ok(result) +} + +/// Stores a cache file in the cache format. See `load_cache_file` for details. +/// # Errors +/// `Error::Parsing` if converting into toml was not possible +/// `Error::Io` if storing the file failed. +// implicit hasher does not make sense here, it is only for desktop files +#[allow(clippy::implicit_hasher)] +pub fn save_cache_file(path: &PathBuf, data: &HashMap) -> Result<(), Error> { + // Convert the HashMap to TOML string + let toml_string = + toml::ser::to_string(&data).map_err(|e| Error::ParsingError(e.to_string()))?; + fs::write(path, toml_string).map_err(|e| Error::Io(e.to_string()))?; + Ok(()) +} + +/// Crates a new file if it does not exist yet. +/// # Errors +/// `Errors::Io` if creating the file failed +pub fn create_file_if_not_exists(path: &PathBuf) -> Result<(), Error> { + 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(Error::Io(e.to_string())), + } +} diff --git a/src/lib/gui.rs b/src/lib/gui.rs index 9208214..7fd3fda 100644 --- a/src/lib/gui.rs +++ b/src/lib/gui.rs @@ -78,19 +78,29 @@ impl From for Align { } } +/// 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)] pub struct MenuItem { - pub label: String, // todo support empty label? + /// text to show in the UI + pub label: String, + /// optional icon, will use fallback icon if None is given pub icon_path: Option, + /// the action to run when this is selected. pub action: Option, + /// Sub elements of this entry. If this already has a parent entry, nesting is not supported pub sub_elements: Vec>, + /// Working directory to run the action in. pub working_dir: Option, + /// Initial sort score to display favourites at the top pub initial_sort_score: f64, /// Allows to store arbitrary additional information pub data: Option, + /// Score the item got in the current search search_sort_score: f64, + /// True if the item is visible visible: bool, } @@ -140,6 +150,7 @@ struct UiElements { menu_rows: ArcMenuMap, } +/// Shows the user interface and **blocks** until the user selected an entry /// # Errors /// /// Will return Err when the channel between the UI and this is broken @@ -953,6 +964,7 @@ fn percent_or_absolute(value: Option<&String>, base_value: i32) -> Option { } } +/// Sorts menu items in alphabetical order, while maintaining the initial score // highly unlikely that we are dealing with > i64 items #[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_possible_truncation)] diff --git a/src/lib/mod.rs b/src/lib/mod.rs index 6b1769e..d18dd69 100644 --- a/src/lib/mod.rs +++ b/src/lib/mod.rs @@ -1,4 +1,60 @@ +use std::fmt; + +/// Defines error the lib can encounter +#[derive(Debug)] +pub enum Error { + /// Failed to update a cache file with the given reason. + UpdateCacheError(String), + /// A given or configured file was not found, will also be used when + /// cache files are missing. + MissingFile, + /// Failed to read form standard input. I.e. used for dmenu. + StdInReadFail, + /// The selection was invalid or looking up the element failed or another reason. + InvalidSelection, + /// The given parameters did not yield an icon. + MissingIcon, + /// Parsing a configuration or cache file failed. + ParsingError(String), + /// A menu item was expected to have an action but none was found. + MissingAction, + /// Running the action failed with the given reason. + RunFailed(String), + /// An IO operation failed + Io(String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::UpdateCacheError(s) => write!(f, "UpdateCacheError {s}"), + Error::MissingAction => write!(f, "MissingAction"), + Error::StdInReadFail => write!(f, "StdInReadFail"), + Error::InvalidSelection => write!(f, "InvalidSelection"), + Error::MissingFile => { + write!(f, "MissingFile") + } + Error::MissingIcon => { + write!(f, "MissingIcon") + } + Error::ParsingError(s) => { + write!(f, "ParsingError {s}") + } + Error::RunFailed(s) => { + write!(f, "RunFailed {s}") + } + Error::Io(s) => { + write!(f, "IO {s}") + } + } + } +} + +/// Configuration and command line parsing pub mod config; +/// Desktop action like parsing desktop files and launching programs pub mod desktop; +/// All things related to the user interface pub mod gui; +/// Out of the box supported modes, like drun, dmenu, etc... pub mod mode; diff --git a/src/lib/mode.rs b/src/lib/mode.rs index 815b47e..f8fce1d 100644 --- a/src/lib/mode.rs +++ b/src/lib/mode.rs @@ -1,42 +1,19 @@ use crate::config::{Config, expand_path}; -use crate::desktop::{find_desktop_files, get_locale_variants, lookup_name_with_locale}; -use crate::gui; +use crate::desktop::{ + create_file_if_not_exists, find_desktop_files, get_locale_variants, load_cache_file, + lookup_name_with_locale, save_cache_file, spawn_fork, +}; use crate::gui::{ItemProvider, MenuItem}; -use anyhow::Context; +use crate::{Error, gui}; use freedesktop_file_parser::EntryType; use rayon::prelude::*; use regex::Regex; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::io::Read; -use std::os::unix::prelude::CommandExt; use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; use std::time::Instant; -use std::{env, fmt, fs, io}; - -#[derive(Debug)] -pub enum ModeError { - UpdateCacheError(String), - MissingAction, - RunError(String), - MissingCache, - StdInReadFail, - InvalidSelection, -} - -impl fmt::Display for ModeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ModeError::UpdateCacheError(s) => write!(f, "UpdateCacheError {s}"), - ModeError::MissingAction => write!(f, "MissingAction"), - ModeError::RunError(s) => write!(f, "RunError, {s}"), - ModeError::MissingCache => write!(f, "MissingCache"), - ModeError::StdInReadFail => write!(f, "StdInReadFail"), - &ModeError::InvalidSelection => write!(f, "InvalidSelection"), - } - } -} +use std::{fs, io}; #[derive(Debug, Deserialize, Serialize, Clone)] struct DRunCache { @@ -434,11 +411,11 @@ struct DMenuProvider { } impl DMenuProvider { - fn new() -> Result { + fn new() -> Result { let mut input = String::new(); io::stdin() .read_to_string(&mut input) - .map_err(|_| ModeError::StdInReadFail)?; + .map_err(|_| Error::StdInReadFail)?; let items: Vec> = input .lines() @@ -523,10 +500,11 @@ impl ItemProvider for AutoItemProvider { } } +/// Shows the drun mode /// # Errors /// /// Will return `Err` if it was not able to spawn the process -pub fn d_run(config: &Config) -> Result<(), ModeError> { +pub fn d_run(config: &Config) -> Result<(), Error> { let provider = DRunProvider::new(String::new()); let cache_path = provider.cache_path.clone(); let mut cache = provider.cache.clone(); @@ -543,11 +521,12 @@ pub fn d_run(config: &Config) -> Result<(), ModeError> { Ok(()) } +/// Shows the auto mode /// # Errors /// /// Will return `Err` /// * if it was not able to spawn the process -pub fn auto(config: &Config) -> Result<(), ModeError> { +pub fn auto(config: &Config) -> Result<(), Error> { let mut provider = AutoItemProvider::new(config); let cache_path = provider.drun.cache_path.clone(); let mut cache = provider.drun.cache.clone(); @@ -592,11 +571,12 @@ pub fn auto(config: &Config) -> Result<(), ModeError> { Ok(()) } +/// Shows the file browser mode /// # Errors /// /// Will return `Err` /// * if it was not able to spawn the process -pub fn file(config: &Config) -> Result<(), ModeError> { +pub fn file(config: &Config) -> Result<(), Error> { let provider = FileItemProvider::new(String::new()); // todo ues a arc instead of cloning the config @@ -615,7 +595,7 @@ pub fn file(config: &Config) -> Result<(), ModeError> { Ok(()) } -fn ssh_launch(menu_item: &MenuItem, config: &Config) -> Result<(), ModeError> { +fn ssh_launch(menu_item: &MenuItem, config: &Config) -> Result<(), Error> { if let Some(action) = &menu_item.action { spawn_fork(action, None)?; } else { @@ -627,15 +607,16 @@ fn ssh_launch(menu_item: &MenuItem, config: &Config) -> Result<(), spawn_fork(&cmd, None)?; } } - Err(ModeError::MissingAction) + Err(Error::MissingAction) } +/// Shows the ssh mode /// # Errors /// /// Will return `Err` /// * if it was not able to spawn the process /// * if it didn't find a terminal -pub fn ssh(config: &Config) -> Result<(), ModeError> { +pub fn ssh(config: &Config) -> Result<(), Error> { let provider = SshProvider::new(String::new(), config); let selection_result = gui::show(config.clone(), provider, true); if let Ok(mi) = selection_result { @@ -646,6 +627,7 @@ pub fn ssh(config: &Config) -> Result<(), ModeError> { Ok(()) } +/// Shows the math mode pub fn math(config: &Config) { let mut cfg_clone = config.clone(); let mut calc: Vec> = vec![]; @@ -663,10 +645,11 @@ pub fn math(config: &Config) { } } +/// Shows the dmenu mode /// # Errors /// -/// todo -pub fn dmenu(config: &Config) -> Result<(), ModeError> { +/// Forwards errors from the gui. See `gui::show` for details. +pub fn dmenu(config: &Config) -> Result<(), Error> { let provider = DMenuProvider::new()?; let selection_result = gui::show(config.clone(), provider, true); @@ -675,7 +658,7 @@ pub fn dmenu(config: &Config) -> Result<(), ModeError> { println!("{}", s.label); Ok(()) } - Err(_) => Err(ModeError::InvalidSelection), + Err(_) => Err(Error::InvalidSelection), } } @@ -683,7 +666,7 @@ fn update_drun_cache_and_run( cache_path: Option, cache: &mut HashMap, selection_result: MenuItem, -) -> Result<(), ModeError> { +) -> Result<(), Error> { if let Some(cache_path) = cache_path { *cache.entry(selection_result.label).or_insert(0) += 1; if let Err(e) = save_cache_file(&cache_path, cache) { @@ -694,7 +677,7 @@ fn update_drun_cache_and_run( if let Some(action) = selection_result.action { spawn_fork(&action, selection_result.working_dir.as_ref()) } else { - Err(ModeError::MissingAction) + Err(Error::MissingAction) } } @@ -711,82 +694,3 @@ fn load_d_run_cache() -> (Option, HashMap) { }; (cache_path, d_run_cache) } - -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<&PathBuf>) -> Result, ModeError> { - let Some(path) = cache_path else { - return Err(ModeError::MissingCache); - }; - - let toml_content = - fs::read_to_string(path).map_err(|e| ModeError::UpdateCacheError(format!("{e}")))?; - 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 '{key}' because it's not an integer"); - } - } - } - Ok(result) -} - -fn create_file_if_not_exists(path: &PathBuf) -> anyhow::Result<()> { - let file = fs::OpenOptions::new() - .write(true) - .create_new(true) - .open(path); - - match file { - Ok(_) => Ok(()), - - Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()), - Err(e) => Err(e).context(format!("Failed to create file {}", path.display()))?, - } -} - -fn spawn_fork(cmd: &str, working_dir: Option<&String>) -> Result<(), ModeError> { - // todo fix actions ?? - // todo graphical disk map icon not working - - let parts = cmd.split(' ').collect::>(); - if parts.is_empty() { - return Err(ModeError::MissingAction); - } - - if let Some(dir) = working_dir { - env::set_current_dir(dir) - .map_err(|e| ModeError::RunError(format!("cannot set workdir {e}")))?; - } - - let exec = parts[0].replace('"', ""); - let args: Vec<_> = parts - .iter() - .skip(1) - .filter(|arg| !arg.starts_with('%')) - .map(|arg| expand_path(arg)) - .collect(); - - unsafe { - let _ = Command::new(exec) - .args(args) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .pre_exec(|| { - libc::setsid(); - Ok(()) - }) - .spawn(); - } - Ok(()) -}