Inline some clones to make code more readable, add config, implement dimming -> locking -> sleeping into idle daemon, fix waiting on network thread, implement run menu with application launching and math evaluation,

This commit is contained in:
jmb05
2026-11-01 16:21:46 +01:00
parent 80b5cafb8c
commit a8fe99b345
14 changed files with 975 additions and 484 deletions

285
Cargo.lock generated
View File

@@ -377,9 +377,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "aws-lc-rs"
version = "1.18.2"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faac5829c2b74c28f830747e7818ccfb684261b5f48a1118b1e2a13d36dfab13"
checksum = "048006790404103c488e6c5616b3e7e702a71292aadda08e8f7698a9e0d2eb53"
dependencies = [
"aws-lc-sys",
"untrusted 0.7.1",
@@ -388,9 +388,9 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
version = "0.46.0"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1622d8446a2d4b2ce0c7eefc73dd43a99779028d5ee5c2dd8073a658ba8a2bc"
checksum = "8bfbe32ac36b3c428696ce315464181bc4029d689fd58e3f2a4e58c252242f52"
dependencies = [
"cc",
"cmake",
@@ -550,6 +550,12 @@ dependencies = [
"digest",
]
[[package]]
name = "block"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "block-buffer"
version = "0.12.1"
@@ -607,6 +613,17 @@ dependencies = [
"zbus",
]
[[package]]
name = "bstr"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
dependencies = [
"memchr",
"regex-automata",
"serde_core",
]
[[package]]
name = "bumpalo"
version = "3.20.3"
@@ -1029,9 +1046,9 @@ dependencies = [
[[package]]
name = "crypto-bigint"
version = "0.7.7"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc907c45bb7e9ef8fc3778defd0f3cd8c81288f1b4e748eba064948f20c47122"
checksum = "9c035cb64cba03dad2fb921f3e218785e8cf9a41790da5eb3a1400f62fa2be11"
dependencies = [
"cpubits",
"ctutils",
@@ -1076,9 +1093,9 @@ dependencies = [
[[package]]
name = "ctutils"
version = "0.4.5"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d28fb6aa7dd680b3ffd1a3e64e115c177bdba63c0ae7f9cd48129ff05c4fa0"
checksum = "a2376ffd6f96d8a9db59875b4b6b0237d6900b5baf64279714e05493ce64828a"
dependencies = [
"cmov",
"subtle",
@@ -1242,9 +1259,9 @@ dependencies = [
[[package]]
name = "der"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a"
checksum = "6a996a108feb8b427a6e12fcf6af4aa83409b79d72c15337d7741368131ee004"
dependencies = [
"const-oid",
"pem-rfc7468",
@@ -1417,7 +1434,7 @@ dependencies = [
[[package]]
name = "edid"
version = "0.4.0"
source = "git+https://github.com/jmb05/rust-edid#509d792617f521ea3797b70b547ac64a5fa35935"
source = "git+https://github.com/jmb05/rust-edid#ab42e7fa8139b35ace725dad55ef8e698d4feb52"
dependencies = [
"nom 8.0.0",
]
@@ -1430,9 +1447,9 @@ checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
[[package]]
name = "elliptic-curve"
version = "0.14.2"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4496221fcfc21015fd8c0e26fcfab00504832dc0604f52acdd7bad2a8dc81"
checksum = "900f37e7e18d74a2347389af6cd69e3a30d5cc9e697f40004865679aa75e9c7c"
dependencies = [
"base16ct",
"crypto-bigint",
@@ -1458,9 +1475,9 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "encoding_rs"
version = "0.8.42"
version = "0.8.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679"
checksum = "89253175e704ab869b04f9db224d38630e0211a53841a79181deae0889e169f5"
dependencies = [
"cfg-if",
"core_detect",
@@ -1678,6 +1695,21 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "freedesktop-desktop-entry"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a69ad6111a2b572cb41e0f3542d546386e841013cee7c45d705b61a15a87dd"
dependencies = [
"bstr",
"gettext-rs",
"log",
"memchr",
"thiserror 2.0.21",
"unicase",
"xdg",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
@@ -1897,6 +1929,26 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "gettext-rs"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df5e3fa8c077b15fdfa8ce130e2bc73d663b8aaa6d51b71421b8659980b1d80"
dependencies = [
"gettext-sys",
"locale_config",
]
[[package]]
name = "gettext-sys"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4caa7e4460b196107cc1bb2629488e8b6dbb7f34f6683ea6753c4ac22f590785"
dependencies = [
"cc",
"temp-dir",
]
[[package]]
name = "ghash"
version = "0.6.0"
@@ -2415,8 +2467,8 @@ dependencies = [
[[package]]
name = "hyprland"
version = "0.4.0"
source = "git+https://github.com/romanstingler/hyprland-rs-next?branch=master#80c1fc217c77873dc9f76c64419d1ed568410c06"
version = "0.7.0"
source = "git+https://github.com/romanstingler/hyprland-rs-next?branch=master#7c2f69f8be78d89b58720bce7cad03b82e99baa3"
dependencies = [
"async-stream",
"chardetng",
@@ -2434,8 +2486,8 @@ dependencies = [
[[package]]
name = "hyprland-macros"
version = "0.4.0"
source = "git+https://github.com/romanstingler/hyprland-rs-next?branch=master#80c1fc217c77873dc9f76c64419d1ed568410c06"
version = "0.7.0"
source = "git+https://github.com/romanstingler/hyprland-rs-next?branch=master#7c2f69f8be78d89b58720bce7cad03b82e99baa3"
dependencies = [
"proc-macro2",
"quote",
@@ -2487,7 +2539,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.61.2",
"windows-core 0.62.2",
]
[[package]]
@@ -2709,6 +2761,7 @@ dependencies = [
"crossbeam-channel",
"ddc-hi",
"eventfd",
"freedesktop-desktop-entry",
"futures-util",
"glib",
"glib-build-tools",
@@ -2723,15 +2776,19 @@ dependencies = [
"jmb-logger-rs",
"libc",
"libpulse-binding",
"meval",
"mpris",
"nmrs",
"notify",
"nucleo-matcher",
"rustsni",
"serde",
"serde_json",
"sysinfo 0.40.0",
"sysinfo 0.40.1",
"thiserror 2.0.21",
"time-format",
"tokio",
"toml",
"wayland-client",
"wayland-protocols",
"zbus",
@@ -2807,9 +2864,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.106"
version = "0.3.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a"
dependencies = [
"cfg-if",
"futures-util",
@@ -2982,6 +3039,19 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4744e383959f0db86ede514b809b1c53251889093803c05267acc7d4e7030d70"
[[package]]
name = "locale_config"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d2c35b16f4483f6c26f0e4e9550717a2f6575bcd6f12a53ff0c490a94a6934"
dependencies = [
"lazy_static",
"objc",
"objc-foundation",
"regex",
"winapi",
]
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -2993,9 +3063,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.34"
version = "0.4.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c"
[[package]]
name = "lru-slab"
@@ -3079,6 +3149,15 @@ dependencies = [
"libc",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]
[[package]]
name = "matchers"
version = "0.2.0"
@@ -3179,6 +3258,16 @@ 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 = "mime"
version = "0.3.17"
@@ -3352,6 +3441,12 @@ dependencies = [
"zvariant",
]
[[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"
@@ -3417,6 +3512,16 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "nucleo-matcher"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85"
dependencies = [
"memchr",
"unicode-segmentation",
]
[[package]]
name = "num-bigint"
version = "0.5.1"
@@ -3525,6 +3630,26 @@ dependencies = [
"libloading 0.8.9",
]
[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]]
name = "objc2"
version = "0.6.5"
@@ -3601,6 +3726,15 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
"objc",
]
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -3615,9 +3749,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "openssl-probe"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
checksum = "7e036b411754132fd6b74e94b74e9a13c5cf606fa93d1885c35860d3060820b1"
[[package]]
name = "option-ext"
@@ -3678,9 +3812,9 @@ dependencies = [
[[package]]
name = "pageant"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0786409b5aaa884655c9b4065681db0835db0cc185a6fb19fb17a5d38656866"
checksum = "e0bc922d9aa8cfcf598de27b3c6122cb2eeda9d6fe71527a22ebcb7f633540ff"
dependencies = [
"base16ct",
"byteorder",
@@ -4242,9 +4376,9 @@ checksum = "263a3f48f01e7309e857138bd47f785585b4a005e8e56c6d2824ce91195999c3"
[[package]]
name = "pulldown-cmark"
version = "0.13.5"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "278e9268c93b9e498a2da46c34d90ba93a3255eb99009646398f34bc73ab36eb"
checksum = "3db2be46835f013dd3f4fa7f5f7402c537787b54ebb6fe2b94bbdd7e2669bb7b"
dependencies = [
"bitflags 2.13.2",
"memchr",
@@ -4707,9 +4841,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.1.5"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa"
dependencies = [
"bitflags 2.13.2",
"errno",
@@ -5353,9 +5487,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.40.0"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "110e37d98880fd804cd47509b90a8444f7aaaab655ae625e83cd442a8bc2ebf2"
checksum = "0e8a3edbe0394ce115f3917a00349dd242be09eb5a091ed509eaf67b55cfb8a8"
dependencies = [
"libc",
"memchr",
@@ -5430,6 +5564,12 @@ version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
[[package]]
name = "temp-dir"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964"
[[package]]
name = "tempfile"
version = "3.27.0"
@@ -5660,9 +5800,9 @@ checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770"
[[package]]
name = "tonic"
version = "0.14.6"
version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef"
checksum = "e2cdfc673aabd30fe8fee92e65837b644c37c5d47f0f97ae6d5e0f1237d15e1b"
dependencies = [
"async-trait",
"axum",
@@ -5681,6 +5821,7 @@ dependencies = [
"sync_wrapper",
"tokio",
"tokio-stream",
"tokio-util",
"tower",
"tower-layer",
"tower-service",
@@ -5689,9 +5830,9 @@ dependencies = [
[[package]]
name = "tonic-build"
version = "0.14.6"
version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c68f61875ac5293cf72e6c8cf0158086428c82c37229e98c840878f1706b0322"
checksum = "ac6d916d206ed7bdda1778ddff9bda43303789c258877e7bc22fb44ee5c984b4"
dependencies = [
"prettyplease",
"proc-macro2",
@@ -5701,9 +5842,9 @@ dependencies = [
[[package]]
name = "tonic-prost"
version = "0.14.6"
version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0"
checksum = "eba464054e2226f182a34489b8b079bf4c6e3e7ab5fd7fc30e024f24aa92a484"
dependencies = [
"bytes",
"prost 0.14.4",
@@ -5712,9 +5853,9 @@ dependencies = [
[[package]]
name = "tonic-prost-build"
version = "0.14.6"
version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "654e5643eff75d7f8c99197ce1440ed19a3474eada74c12bbac488b2cafdae27"
checksum = "2ea216ddc448e08e7839ab09e20e60b06b6a1c32745ccb212c0ebd161c199f1b"
dependencies = [
"prettyplease",
"proc-macro2",
@@ -5920,9 +6061,9 @@ checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
name = "unicode-width"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
checksum = "ca718471876992059f251e33aafc334346ada08042b7661dd1fe313a9bced557"
[[package]]
name = "unicode-xid"
@@ -5988,9 +6129,12 @@ dependencies = [
[[package]]
name = "utf8_iter"
version = "1.0.4"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
checksum = "af3bd9d3e571c26fcce9d033b87ea5ab061b8d2ffe9b7a5675d810d68f03edf0"
dependencies = [
"utf_types",
]
[[package]]
name = "utf8parse"
@@ -5998,6 +6142,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "utf_types"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3b425286980a367278453f3b6d28cdcb134b574e5a35824a6ff5eebad50bb6"
[[package]]
name = "uuid"
version = "1.29.0"
@@ -6089,12 +6239,11 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.129"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
@@ -6102,9 +6251,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.79"
version = "0.4.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e"
checksum = "5e14e0327cb5368ad60ccb22d04a9827a693c95a926d8d125e9fa39e0baec501"
dependencies = [
"js-sys",
"tokio",
@@ -6113,9 +6262,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.129"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -6123,9 +6272,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.129"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a"
dependencies = [
"bumpalo",
"proc-macro2",
@@ -6136,9 +6285,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.129"
version = "0.2.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e"
dependencies = [
"unicode-ident",
]
@@ -6202,9 +6351,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.106"
version = "0.3.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4"
checksum = "d7d179596ff5ec0f6de4db64d54011bde6289cc49603d3a6e4ce3f329207ab88"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -6684,7 +6833,7 @@ dependencies = [
"serde",
"thiserror 2.0.21",
"windows 0.62.2",
"windows-core 0.61.2",
"windows-core 0.62.2",
]
[[package]]
@@ -6719,14 +6868,20 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
[[package]]
name = "xattr"
version = "1.6.1"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
checksum = "963d6fab92d5504b971dc8bd90ca743c2575e590bc8071ea5bc571598667ae08"
dependencies = [
"libc",
"rustix",
]
[[package]]
name = "xdg"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5"
[[package]]
name = "xml-rs"
version = "0.8.29"
@@ -6819,9 +6974,9 @@ dependencies = [
[[package]]
name = "zcheapstr"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473"
checksum = "939f186d9ecebe044315a5cb6d6f78568bbc21152adc954d5a0c2f58bdbc9920"
dependencies = [
"serde",
]

View File

@@ -43,6 +43,11 @@ ddc-hi = { git = "https://github.com/jmb05/ddc-hi-rs" }
wayland-protocols = { version = "0.32.13", features = ["staging", "client"] }
wayland-client = "0.31.15"
zbus = "5.19.0"
serde = { version = "1.0.229", features = ["derive"] }
freedesktop-desktop-entry = "0.8.4"
nucleo-matcher = "0.3.1"
meval = "0.2.0"
toml = "1.1.8"
#zbus = "6.1.0"
[build-dependencies]

4
jmb-bar.conf Normal file
View File

@@ -0,0 +1,4 @@
idle_timeout = 20
dim_timeout = 60
lock_timeout = 300
sleep_timeout = 320

View File

@@ -1,12 +1,13 @@
use crate::backend::{BackendThread, Registrator};
use crate::config::Config;
use crate::{APP_NAME, LOGGER};
use crossbeam_channel::{Receiver, Sender};
use jmb_logger_rs::*;
use nmrs::raw::zbus::interface;
use std::collections::HashMap;
use std::error::Error;
use std::process::Command;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use std::time::Duration;
use tokio::sync::watch;
use tokio::time::sleep;
use wayland_client::globals::registry_queue_init;
@@ -17,12 +18,11 @@ use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notification_v1::{
Event, ExtIdleNotificationV1,
};
use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notifier_v1::ExtIdleNotifierV1;
use zbus::conn::Builder;
use zbus::interface;
use zbus::message::Header;
const SERVICE: &str = "org.freedesktop.ScreenSaver";
const OBJECT_PATH: &str = "/org/freedesktop/ScreenSaver";
const LEGACY_OBJECT_PATH: &str = "/ScreenSaver";
struct WlState {
idle: Option<bool>,
@@ -53,14 +53,8 @@ impl Dispatch<ExtIdleNotificationV1, ()> for WlState {
_: &QueueHandle<Self>,
) {
match event {
Event::Idled => {
println!("Idled: {:?}", Instant::now());
state.idle = Some(true);
}
Event::Resumed => {
println!("Resumed: {:?}", Instant::now());
state.idle = Some(false);
}
Event::Idled => state.idle = Some(true),
Event::Resumed => state.idle = Some(false),
_ => {}
}
if let Some(idle) = state.idle {
@@ -85,15 +79,15 @@ impl Dispatch<ExtIdleNotifierV1, (), WlState> for WlState {
struct Inhibitor {
owner: String,
application: String,
_reason: String,
reason: String,
}
#[derive(Debug, Clone)]
pub enum IdleState {
Init(Sender<IdleMessage>),
Dimmed,
Inhibited,
Caffeine,
Inhibited(bool),
Caffeine(bool),
Active,
}
@@ -128,73 +122,92 @@ impl BackendThread<IdleState, Sender<IdleMessage>> for IdleDaemon {
}
fn init(registration_rx: Receiver<Sender<IdleState>>) {
let config = Config::load();
let (tx, rx) = crossbeam_channel::unbounded::<IdleMessage>();
let tx_clone = tx.clone();
let instance = Arc::new(Mutex::new(Self {
subscribers: Vec::new(),
}));
let instance_clone = instance.clone();
std::thread::spawn(move || {
while let Ok(sender) = registration_rx.recv() {
if sender.send(IdleState::Init(tx_clone.clone())).is_ok() {
trace!(&LOGGER, "Added IdleDaemon Subscriber");
instance_clone
.lock()
.expect("Couldn't get lock")
.subscribers
.push(sender);
std::thread::spawn({
let instance = instance.clone();
let tx = tx.clone();
move || {
while let Ok(sender) = registration_rx.recv() {
if sender.send(IdleState::Init(tx.clone())).is_ok() {
trace!(&LOGGER, "Added IdleDaemon Subscriber");
instance
.lock()
.expect("Couldn't get lock")
.subscribers
.push(sender);
}
}
}
});
let (wl_tx, wl_rx) = watch::channel(false);
let (dbus_tx, dbus_rx) = watch::channel((false, false));
std::thread::spawn(move || {
if let Err(err) = Self::start_idle_notifier(wl_tx) {
error!(&LOGGER, "Idle Daemon unexpectedly quit, reason: {err}");
let (dbus_tx, dbus_rx) = watch::channel(false);
std::thread::spawn({
let config = config.clone();
move || {
if let Err(err) = Self::start_idle_notifier(wl_tx, &config) {
error!(&LOGGER, "Idle Daemon unexpectedly quit, reason: {err}");
}
}
});
std::thread::spawn(move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
runtime.block_on(async move {
let mut idle_inhibitor = None;
while let Ok(msg) = rx.recv() {
match msg {
IdleMessage::Inhibit => {
if idle_inhibitor.is_none()
&& let Ok(ii) = IdleInhibitor::inhibit_idle().await
{
idle_inhibitor = Some(ii);
std::thread::spawn({
let instance = instance.clone();
move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
runtime.block_on(async move {
let mut idle_inhibitor = None;
while let Ok(msg) = rx.recv() {
match msg {
IdleMessage::Inhibit => {
if idle_inhibitor.is_none()
&& let Ok(ii) = IdleInhibitor::inhibit_idle().await
{
instance.lock().unwrap().publish(IdleState::Caffeine(true));
idle_inhibitor = Some(ii);
}
}
}
IdleMessage::Release => {
if let Some(ii) = idle_inhibitor.take()
&& let Err(e) = ii.uninhibit_idle().await
{
warn!(&LOGGER, "Error uninhibiting {e}");
IdleMessage::Release => {
if let Some(ii) = idle_inhibitor.take() {
match ii.uninhibit_idle().await {
Ok(_) => {
instance
.lock()
.unwrap()
.publish(IdleState::Caffeine(false));
}
Err(e) => warn!(&LOGGER, "Error uninhibiting {e}"),
}
}
}
}
}
}
});
});
}
});
std::thread::spawn(move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
std::thread::spawn({
let instance_clone = instance.clone();
move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
runtime.block_on(async move {
let res = IdleDaemon::start_dbus(dbus_tx).await;
match res {
Ok((_, _)) => {
Self::run_idle_timer(instance, wl_rx, dbus_rx).await;
runtime.block_on(async move {
let res = IdleDaemon::start_dbus(dbus_tx, instance_clone).await;
match res {
Ok((_, _)) => {
Self::run_idle_timer(instance, wl_rx, dbus_rx, &config).await;
}
Err(err) => error!(&LOGGER, "Error running D-Bus Screensaver: {err}"),
}
Err(err) => error!(&LOGGER, "Error running D-Bus Screensaver: {err}"),
}
});
});
}
});
}
}
@@ -215,20 +228,21 @@ impl IdleStage {
}
}
fn timeout(self) -> Duration {
fn timeout(self, cfg: &Config) -> Duration {
match self {
Self::Dim => Duration::from_secs(20),
Self::TurnOff => Duration::from_secs(20),
Self::Sleep => Duration::from_secs(20),
Self::Dim => Duration::from_secs(cfg.dim_timeout_delta()),
Self::TurnOff => Duration::from_secs(cfg.lock_timeout_delta()),
Self::Sleep => Duration::from_secs(cfg.sleep_timeout_delta()),
}
}
}
impl IdleDaemon {
async fn run_idle_timer(
_instance: Arc<Mutex<Self>>,
instance: Arc<Mutex<Self>>,
mut wl_rx: watch::Receiver<bool>,
mut dbus_rx: watch::Receiver<(bool, bool)>,
mut dbus_rx: watch::Receiver<bool>,
config: &Config,
) {
loop {
if let Err(e) = wl_rx.changed().await {
@@ -237,22 +251,26 @@ impl IdleDaemon {
}
let wl_idle = *wl_rx.borrow_and_update();
let dbus_state = *dbus_rx.borrow_and_update();
println!("Wayland idle state changed {wl_idle} (DBus state: {dbus_state:?})");
if wl_idle && !dbus_state.0 {
println!("Going into idle cycle");
if wl_idle && !dbus_state {
let mut stage = IdleStage::Dim;
loop {
tokio::select! {
_ = sleep(stage.timeout()) => {
_ = sleep(stage.timeout(config)) => {
match stage {
IdleStage::Dim => instance.lock().unwrap().publish(IdleState::Dimmed),
IdleStage::TurnOff => _ = Command::new("hyprlock").spawn(),
IdleStage::Sleep => _ = Command::new("systemctl").arg("sleep").spawn(),
}
stage = stage.next();
println!("Reached next stage {stage:?}");
}
res = wl_rx.changed() => {
if let Err(e) = res {
warn!(&LOGGER, "Error getting Wayland idle state {e}");
return;
}
println!("WL state change");
if !*wl_rx.borrow_and_update() {
instance.lock().unwrap().publish(IdleState::Active);
}
break;
}
res = dbus_rx.changed() => {
@@ -260,7 +278,6 @@ impl IdleDaemon {
warn!(&LOGGER, "Error getting D-Bus idle state {e}");
return;
}
println!("D-Bus state change");
break;
}
}
@@ -269,7 +286,7 @@ impl IdleDaemon {
}
}
fn start_idle_notifier(tx: watch::Sender<bool>) -> Result<(), Box<dyn Error>> {
fn start_idle_notifier(tx: watch::Sender<bool>, config: &Config) -> Result<(), Box<dyn Error>> {
let conn = Connection::connect_to_env()?;
let (globals, mut event_queue) = registry_queue_init::<WlState>(&conn)?;
let queue_handle = event_queue.handle();
@@ -280,7 +297,12 @@ impl IdleDaemon {
let mut state = WlState { idle: None, tx };
let _notification = notifier.get_idle_notification(10_000, &seat, &queue_handle, ());
let _notification = notifier.get_idle_notification(
(config.idle_timeout() * 1000) as u32,
&seat,
&queue_handle,
(),
);
loop {
event_queue.blocking_dispatch(&mut state)?;
@@ -288,16 +310,18 @@ impl IdleDaemon {
}
async fn start_dbus(
tx: watch::Sender<(bool, bool)>,
tx: watch::Sender<bool>,
daemon: Arc<Mutex<IdleDaemon>>,
) -> zbus::Result<(ScreenSaver, zbus::connection::Connection)> {
let service = ScreenSaver::new(tx);
let service = ScreenSaver::new(tx, daemon);
let connection = Builder::session()?
.name(SERVICE)?
.serve_at(OBJECT_PATH, service.clone())?
.serve_at(LEGACY_OBJECT_PATH, service.clone())?
.build()
let connection = zbus::Connection::session().await?;
connection
.object_server()
.at(OBJECT_PATH, service.clone())
.await?;
connection.request_name(SERVICE).await?;
info!(&LOGGER, "Providing {SERVICE}");
@@ -347,22 +371,24 @@ impl IdleInhibitor {
#[derive(Clone)]
struct ScreenSaver {
inner: Arc<Mutex<Inner>>,
daemon: Arc<Mutex<IdleDaemon>>,
}
struct Inner {
next_cookie: u32,
inhibitors: HashMap<u32, Inhibitor>,
tx: watch::Sender<(bool, bool)>,
tx: watch::Sender<bool>,
}
impl ScreenSaver {
fn new(tx: watch::Sender<(bool, bool)>) -> Self {
fn new(tx: watch::Sender<bool>, daemon: Arc<Mutex<IdleDaemon>>) -> Self {
Self {
inner: Arc::new(Mutex::new(Inner {
next_cookie: 1,
inhibitors: HashMap::new(),
tx,
})),
daemon,
}
}
}
@@ -399,21 +425,26 @@ impl ScreenSaver {
}
};
let caffeine_flag = application_name.eq(APP_NAME);
debug!(&LOGGER, "Inhibitor added: {application_name} {cookie}");
debug!(
&LOGGER,
"Inhibitor added - Name: '{application_name}' Reason: '{reason_for_inhibit}' Value: {cookie}"
);
inner.inhibitors.insert(
cookie,
Inhibitor {
owner,
application: application_name,
_reason: reason_for_inhibit,
reason: reason_for_inhibit,
},
);
self.daemon
.lock()
.unwrap()
.publish(IdleState::Inhibited(true));
inner.tx.send_modify(|state| {
state.0 = !inner.inhibitors.is_empty();
state.1 = caffeine_flag;
*state = !inner.inhibitors.is_empty();
});
Ok(cookie)
}
@@ -432,25 +463,25 @@ impl ScreenSaver {
if let Some(removed) = inner.inhibitors.remove(&cookie) {
debug!(
&LOGGER,
"Inhibitor removed: {} {cookie}", removed.application
"Inhibitor removed - Name: '{}' Reason: '{}' Value: {cookie}",
removed.application,
removed.reason
);
}
let mut caffeine_flag = false;
for inhibitor in inner.inhibitors.values() {
if inhibitor.application.eq(APP_NAME) {
caffeine_flag = true;
break;
}
}
self.daemon
.lock()
.unwrap()
.publish(IdleState::Inhibited(!inner.inhibitors.is_empty()));
inner.tx.send_modify(|state| {
state.0 = !inner.inhibitors.is_empty();
state.1 = caffeine_flag;
*state = !inner.inhibitors.is_empty();
});
Ok(())
}
Some(_) => Err(zbus::fdo::Error::AccessDenied(
"Cookie belongs to another D-Bus client".into(),
)),
Some(_) => Err(zbus::fdo::Error::AccessDenied(format!(
"Cookie {cookie} belongs to another D-Bus client"
))),
None => Err(zbus::fdo::Error::Failed("Unknown inhibition cookie".into())),
}
}

View File

@@ -161,6 +161,26 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
}
};
std::thread::spawn({
let nm = nm.clone();
let mut instance = instance.clone();
move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create Tokio runtime");
runtime.block_on(async move {
while let Ok(tx) = registration_rx.recv() {
if tx.send(NetworkState::Init(instance.command_tx.clone())).is_ok() {
instance.subscribers.push(tx);
let _ = instance.send_all_networks(&nm).await;
let _ = instance.send_active_connection(&nm).await;
}
}
});
}});
let mut events = match nm.network_events().await {
Ok(events) => events,
Err(err) => {
@@ -169,7 +189,6 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
}
};
let mut debouncer = EventDebouncer {
duration: Duration::from_millis(200),
deadline: None,
@@ -177,13 +196,6 @@ impl BackendThread<NetworkState, UnboundedSender<NetworkCommand>> for NetworkBac
};
debouncer.sleep.as_mut().reset(Instant::now() + Duration::from_secs(86400));
loop {
while let Ok(tx) = registration_rx.try_recv() {
if tx.send(NetworkState::Init(instance.command_tx.clone())).is_ok() {
instance.subscribers.push(tx);
let _ = instance.send_all_networks(&nm).await;
let _ = instance.send_active_connection(&nm).await;
}
}
tokio::select! {
command = command_rx.recv() => {
let Some(command) = command else {

View File

@@ -1,10 +1,10 @@
use crate::LOGGER;
use crate::backend::{BackendThread, SimpleBackendThread};
use crossbeam_channel::{Receiver, Sender};
use hyprland::data::{Client, Monitor, Workspaces};
use hyprland::data::{Client, CursorPosition, Monitor, Monitors, Workspaces};
use hyprland::event_listener::EventListener;
use hyprland::prelude::*;
use hyprland::shared::{MonitorId, WorkspaceId, WorkspaceType};
use hyprland::shared::{MonitorId, WorkspaceId};
use jmb_logger_rs::*;
use std::io;
use std::process::{Command, Output};
@@ -24,7 +24,6 @@ pub struct HyprlandComms {
}
impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
fn subscribers(&self) -> &Vec<Sender<WindowManagerEvent>> {
&self.subscribers
}
@@ -38,9 +37,7 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
let inst_clone = instance.clone();
listener.add_workspace_changed_handler(move |workspace| {
if let WorkspaceType::Regular(name) = workspace.name
&& let Ok(id) = name.parse::<i32>()
{
if let Ok(id) = workspace.name.parse::<i32>() {
inst_clone
.lock()
.expect("Couldn't get Subscribers")
@@ -50,26 +47,24 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
let inst_clone = instance.clone();
listener.add_workspace_added_handler(move |workspace| {
if let WorkspaceType::Regular(name) = workspace.name {
//unpacks the name of the workspace
if let Ok(wss) = Workspaces::get() {
//gets all workspaces
if let Some(ws) = wss
.to_vec()
.iter()
.find(|ws| ws.id().unwrap() == workspace.id)
{
//find the current workspace Object from its id
if let Ok(id) = name.parse::<i32>() {
// parse the id as an int
inst_clone
.lock()
.expect("Couldn't get Subscribers")
.publish(WindowManagerEvent::WorkspaceAdded(
id,
ws.monitor_id.unwrap(),
));
}
//unpacks the name of the workspace
if let Ok(wss) = Workspaces::get() {
//gets all workspaces
if let Some(ws) = wss
.to_vec()
.iter()
.find(|ws| ws.id().unwrap() == workspace.id().unwrap())
{
//find the current workspace Object from its id
if let Ok(id) = workspace.name.parse::<i32>() {
// parse the id as an int
inst_clone
.lock()
.expect("Couldn't get Subscribers")
.publish(WindowManagerEvent::WorkspaceAdded(
id,
ws.monitor_id.unwrap(),
));
}
}
}
@@ -77,9 +72,7 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
let inst_clone = instance.clone();
listener.add_workspace_deleted_handler(move |workspace| {
if let WorkspaceType::Regular(name) = workspace.name
&& let Ok(id) = name.parse::<i32>()
{
if let Ok(id) = workspace.name.parse::<i32>() {
inst_clone
.lock()
.expect("Couldn't get Subscribers")
@@ -92,7 +85,9 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
inst_clone
.lock()
.expect("Couldn't get Subscribers")
.publish(WindowManagerEvent::WorkspaceChanged(evt.workspace_id));
.publish(WindowManagerEvent::WorkspaceChanged(
evt.workspace.id().unwrap(),
));
});
let inst_clone = instance.clone();
@@ -106,15 +101,22 @@ impl SimpleBackendThread<WindowManagerEvent> for HyprlandComms {
let inst_clone = instance.clone();
listener.add_workspace_moved_handler(move |event| {
if let Some(monitor_id) = Self::monitor_id_from_workspace_id(event.id) {
if let Some(monitor_id) =
Self::monitor_id_from_workspace_id(event.workspace.id().unwrap())
{
inst_clone
.lock()
.expect("Couldn't get Subscribers")
.publish(WindowManagerEvent::WorkspaceRemoved(event.id));
.publish(WindowManagerEvent::WorkspaceRemoved(
event.workspace.id().unwrap(),
));
inst_clone
.lock()
.expect("Couldn't get Subscribers")
.publish(WindowManagerEvent::WorkspaceAdded(event.id, monitor_id));
.publish(WindowManagerEvent::WorkspaceAdded(
event.workspace.id().unwrap(),
monitor_id,
));
}
});
@@ -160,7 +162,7 @@ impl HyprlandComms {
None
}
pub fn _current_monitor() -> MonitorId {
pub fn current_monitor() -> MonitorId {
if let Ok(monitor) = Monitor::get_active() {
return monitor.id;
}
@@ -197,4 +199,22 @@ impl HyprlandComms {
.arg(format!("hyprctl dispatch \"{dispatcher}\""))
.output()
}
pub fn cursor_monitor() -> Option<MonitorId> {
if let Ok(pos) = CursorPosition::get()
&& let Ok(monitors) = Monitors::get()
{
for monitor in monitors {
if pos.x > monitor.x as i64
&& pos.x < (monitor.x as i64 + monitor.width as i64)
&& pos.y > monitor.y as i64
&& pos.y < (monitor.y as i64 + monitor.height as i64)
{
return Some(monitor.id);
}
}
}
None
}
}

59
src/config.rs Normal file
View File

@@ -0,0 +1,59 @@
use serde::Deserialize;
use std::fs;
#[derive(Deserialize, Debug, Clone)]
pub(crate) struct Config {
idle_timeout: u64,
dim_timeout: u64,
lock_timeout: u64,
sleep_timeout: u64,
}
impl Default for Config {
fn default() -> Self {
Self {
idle_timeout: 20,
dim_timeout: 60,
lock_timeout: 300,
sleep_timeout: 320,
}
}
}
impl Config {
pub fn load() -> Config {
fs::read_to_string("jmb-bar.conf")
.ok()
.map(|s| toml::from_str::<Config>(&s).ok())
.flatten()
.unwrap_or_default()
}
pub fn idle_timeout(&self) -> u64 {
self.idle_timeout
}
pub fn dim_timeout(&self) -> u64 {
self.dim_timeout
}
pub fn lock_timeout(&self) -> u64 {
self.lock_timeout
}
pub fn sleep_timeout(&self) -> u64 {
self.sleep_timeout
}
pub fn dim_timeout_delta(&self) -> u64 {
self.dim_timeout - self.idle_timeout
}
pub fn lock_timeout_delta(&self) -> u64 {
self.lock_timeout - self.dim_timeout_delta()
}
pub fn sleep_timeout_delta(&self) -> u64 {
self.sleep_timeout - self.lock_timeout_delta()
}
}

View File

@@ -1,9 +1,11 @@
use crate::backend::Backend;
mod backend;
mod config;
mod css;
mod ctl;
mod ui;
use crate::config::Config;
use glib::clone;
use gtk::prelude::*;
use gtk::{Application, CssProvider, gdk, gio};

View File

@@ -39,7 +39,7 @@ pub fn create_bars(app: &Application, backend: Backend) {
monitor,
create_and_show_bar(
app_clone.clone(),
&display,
display.clone(),
monitor,
action_centre.clone(),
channels,
@@ -61,11 +61,11 @@ pub fn create_bars(app: &Application, backend: Backend) {
let action_centre = action_centre.clone();
let channels = backend_clone.subscribe_all();
let bars = bars.clone();
let display = display.clone();
glib::idle_add_local_once(move || {
let display = Display::default().expect("Couldn't find GDK Display");
bars.lock().expect("Couldn't get active bars").insert(
pos,
create_and_show_bar(app_clone, &display, pos, action_centre, channels),
create_and_show_bar(app_clone, display, pos, action_centre, channels),
);
});
}
@@ -74,7 +74,7 @@ pub fn create_bars(app: &Application, backend: Backend) {
let mut ctl_channel = backend.subscribe(&[ChannelType::Control]);
let ctl_rx = ctl_channel.take_ctl_rx().unwrap();
let app_clone = app.clone();
let menu = RunMenu::create(app_clone);
let mut menu = RunMenu::create(app_clone);
glib::timeout_add_local(Duration::from_millis(100), move || {
while let Ok(cmd) = ctl_rx.try_recv() {
match cmd {
@@ -89,7 +89,7 @@ pub fn create_bars(app: &Application, backend: Backend) {
fn create_and_show_bar(
app: Application,
display: &Display,
display: Display,
monitor_idx: u32,
action_centre: ActionCentre,
mut channels: BackendChannels,
@@ -138,7 +138,7 @@ fn create_and_show_bar(
let title_button: Option<Button>;
let title = title_panel::build();
let utilities = utilities_panel::build(&app, action_centre, channels, monitor_idx, display);
let utilities = utilities_panel::build(&app, action_centre, channels, monitor_idx, &display);
let dock = gtk::Box::new(Orientation::Horizontal, 10);
dock.add_css_class("dock");

View File

@@ -111,106 +111,111 @@ impl ActionCentre {
window.set_child(Some(&scrolled));
let instance_clone = instance.clone();
let key_controller = gtk::EventControllerKey::new();
key_controller.connect_key_pressed(move |_, key, _, _| {
if key == gdk::Key::Escape {
instance_clone.hide_action_centre();
glib::Propagation::Stop
} else {
glib::Propagation::Proceed
key_controller.connect_key_pressed({
let instance = instance.clone();
move |_, key, _, _| {
if key == gdk::Key::Escape {
instance.hide_action_centre();
glib::Propagation::Stop
} else {
glib::Propagation::Proceed
}
}
});
window.add_controller(key_controller);
let wifi_networks: Rc<RefCell<HashMap<String, WifiWidget>>> =
Rc::new(RefCell::new(HashMap::new()));
let wifi_networks_clone = wifi_networks.clone();
window.connect_focus_widget_notify(move |window| {
Self::focus_callback(&wifi_networks_clone, window)
window.connect_focus_widget_notify({
let wifi_networks = wifi_networks.clone();
move |window| Self::focus_callback(&wifi_networks, window)
});
let net_rx = channels.take_net_rx().unwrap();
let window_clone = window.clone();
let wifi_networks_clone = wifi_networks.clone();
glib::timeout_add_local(Duration::from_millis(100), move || {
while let Ok(message) = net_rx.try_recv() {
match message {
NetworkState::ChangeNetwork(Some(info)) => {
for (name, widget) in wifi_networks_clone.borrow().iter() {
if name.eq(&info.name) {
widget.add_css_class("connected");
} else {
widget.remove_css_class("connected");
glib::timeout_add_local(Duration::from_millis(100), {
let window = window.clone();
let wifi_networks = wifi_networks.clone();
let net_rx = channels.take_net_rx().unwrap();
move || {
while let Ok(message) = net_rx.try_recv() {
match message {
NetworkState::ChangeNetwork(Some(info)) => {
for (name, widget) in wifi_networks.borrow().iter() {
if name.eq(&info.name) {
widget.add_css_class("connected");
} else {
widget.remove_css_class("connected");
}
}
}
}
NetworkState::Networks(networks) => {
for network in &networks {
let borrowed_wifi_networks_clone = wifi_networks_clone.borrow_mut();
let widget = borrowed_wifi_networks_clone.get(&network.name);
if let Some(widget) = widget {
let idx = ((network.signal as f32 / 100.0)
* (WIFI_ICONS.len() - 1) as f32
+ 0.5) as usize;
widget.set_wifi_icon_from_name(WIFI_ICONS[idx]);
} else {
let widget = Self::create_wifi_widget(
&channels,
network,
borrowed_wifi_networks_clone,
);
content.append(&widget);
NetworkState::Networks(networks) => {
for network in &networks {
let borrowed_wifi_networks = wifi_networks.borrow_mut();
let widget = borrowed_wifi_networks.get(&network.name);
if let Some(widget) = widget {
let idx = ((network.signal as f32 / 100.0)
* (WIFI_ICONS.len() - 1) as f32
+ 0.5)
as usize;
widget.set_wifi_icon_from_name(WIFI_ICONS[idx]);
} else {
let widget = Self::create_wifi_widget(
&channels,
network,
borrowed_wifi_networks,
);
content.append(&widget);
}
}
let mut removal: Vec<String> = Vec::new();
for (ssid, widget) in wifi_networks.borrow().iter() {
let network = &networks.iter().find(|n| n.name.eq(ssid));
if network.is_none() {
content.remove(widget);
removal.push(ssid.clone());
}
}
for ssid in removal {
wifi_networks.borrow_mut().remove(&ssid);
}
}
let mut removal: Vec<String> = Vec::new();
for (ssid, widget) in wifi_networks_clone.borrow().iter() {
let network = &networks.iter().find(|n| n.name.eq(ssid));
if network.is_none() {
content.remove(widget);
removal.push(ssid.clone());
}
}
for ssid in removal {
wifi_networks_clone.borrow_mut().remove(&ssid);
}
}
NetworkState::Auth(ssid, auth) => {
if let Some(widget) = Self::find_focused_widget(window_clone.as_ref()) {
match auth {
AuthType::Password => {
if widget.ssid().eq(&ssid) {
widget.show_password_entry();
widget.stop_spinner();
widget.grab_focus_password_entry();
NetworkState::Auth(ssid, auth) => {
if let Some(widget) = Self::find_focused_widget(window.as_ref()) {
match auth {
AuthType::Password => {
if widget.ssid().eq(&ssid) {
widget.show_password_entry();
widget.stop_spinner();
widget.grab_focus_password_entry();
}
}
}
}
}
}
NetworkState::ConnectFail(ssid, err) => {
let mut networks = wifi_networks_clone.borrow_mut();
if let Some(widget) = networks.get(&ssid) {
widget.stop_spinner();
match err {
ConnectError::AuthFailed => widget.show_password_entry(),
ConnectError::NotFound => {
content.remove(widget);
networks.remove(&ssid);
NetworkState::ConnectFail(ssid, err) => {
let mut networks = wifi_networks.borrow_mut();
if let Some(widget) = networks.get(&ssid) {
widget.stop_spinner();
match err {
ConnectError::AuthFailed => widget.show_password_entry(),
ConnectError::NotFound => {
content.remove(widget);
networks.remove(&ssid);
}
ConnectError::Unknown => {}
}
ConnectError::Unknown => {}
}
}
}
NetworkState::ConnectSuccess(ssid) => {
if let Some(widget) = wifi_networks_clone.borrow().get(&ssid) {
widget.stop_spinner();
NetworkState::ConnectSuccess(ssid) => {
if let Some(widget) = wifi_networks.borrow().get(&ssid) {
widget.stop_spinner();
}
}
_ => {}
}
_ => {}
}
ControlFlow::Continue
}
ControlFlow::Continue
});
instance
@@ -219,7 +224,7 @@ impl ActionCentre {
fn create_wifi_widget(
channels: &BackendChannels,
network: &NetworkInfo,
mut borrowed_wifi_networks_clone: RefMut<HashMap<String, WifiWidget>>,
mut borrowed_wifi_networks: RefMut<HashMap<String, WifiWidget>>,
) -> WifiWidget {
let ssid = network.name.clone();
let widget = WifiWidget::new();
@@ -229,7 +234,7 @@ impl ActionCentre {
widget.set_known(network.known);
let idx = ((network.signal as f32 / 100.0) * (WIFI_ICONS.len() - 1) as f32 + 0.5) as usize;
widget.set_wifi_icon_from_name(WIFI_ICONS[idx]);
borrowed_wifi_networks_clone.insert(ssid.clone(), widget.clone());
borrowed_wifi_networks.insert(ssid.clone(), widget.clone());
let net_tx = channels.net_tx().unwrap();
widget.connect_clicked(clone!(
#[strong]

View File

@@ -1,27 +1,44 @@
use gtk::prelude::{BoxExt, GtkWindowExt, WidgetExt};
use crate::LOGGER;
use crate::backend::window_manager_hypr::HyprlandComms;
use freedesktop_desktop_entry::{default_paths, get_languages_from_env};
use glib::clone;
use glib::prelude::Cast;
use gtk::gdk::{Display, Monitor};
use gtk::prelude::{
BoxExt, ButtonExt, DisplayExt, EditableExt, EntryExt, GtkWindowExt, ListModelExt, MonitorExt,
WidgetExt,
};
use gtk::{Align, Application, ApplicationWindow, Orientation, PolicyType, gdk};
use gtk4_layer_shell::{KeyboardMode, Layer, LayerShell};
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
use jmb_logger_rs::*;
use nucleo_matcher::pattern::{CaseMatching, Normalization, Pattern};
use nucleo_matcher::{Config, Matcher};
use std::cell::RefCell;
use std::collections::HashMap;
use std::process::Command;
use std::rc::Rc;
#[derive(Clone)]
pub struct RunMenu {
window: ApplicationWindow,
entry: gtk::Entry,
entries: Rc<RefCell<HashMap<String, Vec<String>>>>,
}
impl RunMenu {
pub fn create(application: Application) -> Self {
let window = gtk::ApplicationWindow::builder()
let window = ApplicationWindow::builder()
.application(&application)
.title("JmbRunMenu")
.default_width(500)
.default_height(300)
.css_classes(["run-menu"])
.build();
window.init_layer_shell();
window.set_layer(Layer::Overlay);
window.set_halign(Align::Center);
window.set_valign(Align::Center);
window.set_size_request(500, 300);
window.set_width_request(500);
window.set_anchor(Edge::Top, true);
window.set_keyboard_mode(KeyboardMode::Exclusive);
let v_box = gtk::Box::new(Orientation::Vertical, 0);
@@ -31,28 +48,109 @@ impl RunMenu {
let scrolled = gtk::ScrolledWindow::new();
scrolled.set_policy(PolicyType::Never, PolicyType::Automatic);
scrolled.set_vexpand(true);
scrolled.set_valign(Align::Fill);
scrolled.set_vexpand(false);
scrolled.set_propagate_natural_height(true);
scrolled.set_min_content_height(0);
scrolled.set_max_content_height(300);
let content_box = gtk::Box::new(Orientation::Vertical, 0);
content_box.add_css_class("content-box");
for _ in 0..10 {
let label = gtk::Label::new(Some("Test"));
content_box.append(&label);
}
content_box.set_hexpand(true);
scrolled.set_child(Some(&content_box));
v_box.set_vexpand(false);
v_box.set_valign(Align::Start);
v_box.append(&entry);
v_box.append(&scrolled);
window.set_child(Some(&v_box));
let display = Display::default().expect("Couldn't get display");
let monitor = display
.monitors()
.item(HyprlandComms::current_monitor() as u32)
.expect("Couldn't get monitor")
.downcast::<Monitor>()
.ok();
window.set_monitor(monitor.as_ref());
if let Some(monitor) = monitor {
let geometry = monitor.geometry();
let (_, natural_height, _, _) =
window.child().unwrap().measure(Orientation::Vertical, -1);
println!("Monitor height: {}", geometry.height());
let top_margin = (geometry.height()) / 2;
window.set_margin(Edge::Top, top_margin - 100);
} else {
window.set_valign(Align::Center);
}
content_box
.observe_children()
.connect_items_changed(move |model, _, _, _| {
if model.n_items() == 0 {
println!("Hide");
} else {
println!("Reveal");
}
});
let window_clone = window.clone();
let instance = Self {
window: window_clone,
entry: entry.clone(),
entries: Rc::new(RefCell::new(HashMap::new())),
};
let instance_clone = instance.clone();
let matcher_rc = Rc::new(RefCell::new(Matcher::new(Config::DEFAULT)));
entry.connect_changed(clone!(
#[strong]
content_box,
move |e| {
while let Some(child) = content_box.first_child() {
content_box.remove(&child);
}
if e.text_length() > 0 {
let text = e.text();
if text.starts_with("=") {
if let Ok(eval) = meval::eval_str(&text[1..]) {
let label = gtk::Label::new(Some(&format!("{eval}")));
label.add_css_class("exec-button");
label.set_halign(Align::Start);
label.set_hexpand(true);
content_box.append(&label);
}
} else {
instance_clone.list_matching(e, &content_box, &mut matcher_rc.borrow_mut());
instance_clone.window.set_default_size(500, -1);
}
}
}
));
entry.connect_has_focus_notify(clone!(
#[strong]
content_box,
move |e| {
if let Some(child) = content_box.first_child() {
if e.has_focus() {
child.add_css_class("suggested");
} else {
child.remove_css_class("suggested");
}
}
}
));
entry.connect_activate(clone!(
#[strong]
content_box,
move |_| {
if let Some(child) = content_box.first_child()
&& let Ok(button) = child.downcast::<gtk::Button>()
{
button.activate();
}
}
));
let instance_clone = instance.clone();
let key_controller = gtk::EventControllerKey::new();
key_controller.connect_key_pressed(move |_, key, _, _| {
@@ -69,9 +167,82 @@ impl RunMenu {
pub fn hide(&self) {
self.window.set_visible(false);
self.entry.set_text("");
}
pub fn show(&self) {
pub fn show(&mut self) {
self.load_desktop_files();
self.window.present();
}
fn create_entry(&self, content_box: &gtk::Box, entry: &String) -> gtk::Button {
let entry_clone = entry.clone();
let button = gtk::Button::new();
button.set_halign(Align::Fill);
button.set_hexpand(true);
let label = gtk::Label::new(Some(entry));
label.set_halign(Align::Start);
button.set_child(Some(&label));
button.add_css_class("exec-button");
let entry_map = self.entries.clone();
let instance = self.clone();
button.connect_clicked(move |_| {
let map = entry_map.borrow();
let exec = map.get(&entry_clone);
if let Some(exec) = exec {
let executable = &exec[0];
if let Err(err) = Command::new(executable).args(&exec[1..]).spawn() {
warn!(&LOGGER, "Couldn't execute app: {err}");
}
instance.hide();
}
});
content_box.append(&button);
button
}
fn list_matching(
&self,
entry: &gtk::Entry,
content_box: &gtk::Box,
matcher: &mut Matcher,
) -> bool {
let keys: Vec<String> = self.entries.borrow().keys().map(|i| i.clone()).collect();
let mut first = true;
let mut added = false;
for (entry, _) in Pattern::parse(&entry.text(), CaseMatching::Ignore, Normalization::Smart)
.match_list(keys, matcher)
.iter()
{
let button = self.create_entry(content_box, &entry);
if first {
button.add_css_class("suggested");
first = false;
}
added = true;
}
added
}
fn load_desktop_files(&mut self) {
let locales = get_languages_from_env();
let entries = freedesktop_desktop_entry::Iter::new(default_paths())
.entries(Some(&locales))
.collect::<Vec<_>>();
let mut entry_map = self.entries.borrow_mut();
entry_map.clear();
for entry in entries {
if let Ok(exec) = entry.parse_exec() {
let name = entry.full_name(&locales);
if let Some(name) = name {
entry_map.insert(name.to_string(), exec);
}
}
}
}
}

View File

@@ -78,14 +78,16 @@ pub fn build(
let media_tx = channels.media_tx().unwrap();
let changed_progr = Rc::new(Cell::new(false));
let changed_progr_clone = changed_progr.clone();
volume.connect_value_changed(move |slider, button, _| {
if !changed_progr_clone.get() {
let _ = media_tx.send(MediaCommand::Cmd(
slider.value() as u8,
button.is_active(),
false,
));
volume.connect_value_changed({
let changed_progr = changed_progr.clone();
move |slider, button, _| {
if !changed_progr.get() {
let _ = media_tx.send(MediaCommand::Cmd(
slider.value() as u8,
button.is_active(),
false,
));
}
}
});
let media_tx = channels.media_tx().unwrap();
@@ -189,7 +191,7 @@ pub fn build(
let network_button = Button::new();
network_button.add_css_class("network");
let network_button_content = gtk::Box::new(Orientation::Horizontal, 0);
let network_icon = Image::from_file("../../icons/disconnected.svg");
let network_icon = Image::from_file("icons/disconnected.svg");
network_icon.add_css_class("network-icon");
network_button_content.append(&network_icon);
let network_label = Label::new(None);
@@ -312,184 +314,188 @@ pub fn build(
ControlFlow::Continue
});
let media_rx = channels.take_media_rx().unwrap();
let br_rx = channels.take_br_rx().unwrap();
let net_rx = channels.take_net_rx().unwrap();
let stats_rx = channels.take_stats_rx().unwrap();
let netbird_rx = channels.take_netbird_rx().unwrap();
let idle_rx = channels.take_idle_rx().unwrap();
let monitor_clone = monitor.clone();
let br_tx = channels.br_tx().unwrap();
glib::timeout_add_local(Duration::from_millis(100), move || {
while let Ok(command) = media_rx.try_recv() {
if let MediaCommand::Cmd(vol, mute, bt) = command {
changed_progr.set(true);
volume.set_value(vol as f64);
changed_progr.set(false);
volume.show_scale(!mute);
volume.imp().button.set_active(mute);
if mute {
volume.imp().button.add_css_class("muted");
volume.imp().second_icon.add_child_css_class("muted");
volume
glib::timeout_add_local(Duration::from_millis(100), {
let media_rx = channels.take_media_rx().unwrap();
let br_rx = channels.take_br_rx().unwrap();
let net_rx = channels.take_net_rx().unwrap();
let stats_rx = channels.take_stats_rx().unwrap();
let netbird_rx = channels.take_netbird_rx().unwrap();
let idle_rx = channels.take_idle_rx().unwrap();
let monitor = monitor.clone();
let br_tx = channels.br_tx().unwrap();
move || {
while let Ok(command) = media_rx.try_recv() {
if let MediaCommand::Cmd(vol, mute, bt) = command {
changed_progr.set(true);
volume.set_value(vol as f64);
changed_progr.set(false);
volume.show_scale(!mute);
volume.imp().button.set_active(mute);
if mute {
volume.imp().button.add_css_class("muted");
volume.imp().second_icon.add_child_css_class("muted");
volume
.imp()
.main_icon
.set_icon_path("icons/volume-mute.svg");
} else {
volume.imp().button.remove_css_class("muted");
volume.imp().second_icon.remove_child_css_class("muted");
let idx = ((volume.value() / 100.0) * (VOLUME_ICON_PATHS.len() - 1) as f64
+ 0.5) as usize;
volume.imp().main_icon.set_icon_path(VOLUME_ICON_PATHS[idx]);
}
volume.imp().second_icon.set_visible(bt);
}
}
while let Ok(message) = br_rx.try_recv() {
if let BrightnessMessage::Value(mon, value, _) = message
&& monitor.eq(&mon)
{
brightness.set_value(value as f64);
let idx = ((value as f32 / 100.0) * (BRIGHTNESS_ICONS.len() - 1) as f32 + 0.5)
as usize;
brightness
.imp()
.main_icon
.set_icon_path("icons/volume-mute.svg");
} else {
volume.imp().button.remove_css_class("muted");
volume.imp().second_icon.remove_child_css_class("muted");
let idx = ((volume.value() / 100.0) * (VOLUME_ICON_PATHS.len() - 1) as f64
+ 0.5) as usize;
volume.imp().main_icon.set_icon_path(VOLUME_ICON_PATHS[idx]);
.set_font_icon(BRIGHTNESS_ICONS[idx]);
}
volume.imp().second_icon.set_visible(bt);
}
}
while let Ok(message) = br_rx.try_recv() {
if let BrightnessMessage::Value(mon, value, _) = message
&& monitor.eq(&mon)
{
brightness.set_value(value as f64);
let idx =
((value as f32 / 100.0) * (BRIGHTNESS_ICONS.len() - 1) as f32 + 0.5) as usize;
brightness
.imp()
.main_icon
.set_font_icon(BRIGHTNESS_ICONS[idx]);
}
}
while let Ok(state) = idle_rx.try_recv() {
match state {
IdleState::Dimmed => {
if !caffeine_button.has_css_class("idle") && monitor_valid {
let _ =
br_tx.send(BrightnessMessage::Value(monitor_clone.clone(), 20, true));
brightness.set_value(20.0);
while let Ok(state) = idle_rx.try_recv() {
match state {
IdleState::Dimmed => {
if !caffeine_button.has_css_class("idle") && monitor_valid {
let _ = br_tx.send(BrightnessMessage::Value(monitor.clone(), 20, true));
brightness.set_value(20.0);
}
caffeine_button.add_css_class("idle");
caffeine_button.remove_css_class("inhibit");
caffeine_button.remove_css_class("on");
}
caffeine_button.add_css_class("idle");
caffeine_button.remove_css_class("inhibit");
caffeine_button.remove_css_class("on");
}
other => {
caffeine_button.remove_css_class("idle");
caffeine_button.remove_css_class("inhibit");
caffeine_button.remove_css_class("on");
if monitor_valid {
let _ = br_tx.send(BrightnessMessage::Revert(monitor_clone.clone()));
}
match other {
IdleState::Caffeine => caffeine_button.add_css_class("on"),
IdleState::Inhibited => caffeine_button.add_css_class("inhibit"),
IdleState::Active => {}
_ => {}
other => {
caffeine_button.remove_css_class("idle");
if monitor_valid {
let _ = br_tx.send(BrightnessMessage::Revert(monitor.clone()));
}
match other {
IdleState::Caffeine(true) => caffeine_button.add_css_class("on"),
IdleState::Inhibited(true) => caffeine_button.add_css_class("inhibit"),
IdleState::Caffeine(false) => caffeine_button.remove_css_class("on"),
IdleState::Inhibited(false) => {
caffeine_button.remove_css_class("inhibit")
}
IdleState::Active => {}
_ => {}
}
}
}
}
}
while let Ok(result) = net_rx.try_recv() {
trace!(&LOGGER, "NetworkResult: {:?}", result);
match result {
NetworkState::ChangeNetwork(cfg) => {
match cfg {
Some(network) => {
match network.network_type {
NetworkType::Wifi => {
//let idx = ((20.0 / 100.0) * (WIFI_ICON_PATHS.len() - 1) as f32 + 0.5) as usize;
let idx = ((network.signal as f32 / 100.0)
* (WIFI_ICON_PATHS.len() - 1) as f32
+ 0.5)
as usize;
network_icon.set_from_file(Some(WIFI_ICON_PATHS[idx]));
network_label.set_label(&network.name);
network_label.set_visible(true);
}
NetworkType::Wired => {
network_icon.set_from_file(Some("icons/ethernet.svg"));
network_label.set_visible(false);
while let Ok(result) = net_rx.try_recv() {
trace!(&LOGGER, "NetworkResult: {:?}", result);
match result {
NetworkState::ChangeNetwork(cfg) => {
match cfg {
Some(network) => {
match network.network_type {
NetworkType::Wifi => {
//let idx = ((20.0 / 100.0) * (WIFI_ICON_PATHS.len() - 1) as f32 + 0.5) as usize;
let idx = ((network.signal as f32 / 100.0)
* (WIFI_ICON_PATHS.len() - 1) as f32
+ 0.5)
as usize;
network_icon.set_from_file(Some(WIFI_ICON_PATHS[idx]));
network_label.set_label(&network.name);
network_label.set_visible(true);
}
NetworkType::Wired => {
network_icon.set_from_file(Some("icons/ethernet.svg"));
network_label.set_visible(false);
}
}
}
}
None => {
network_icon.set_from_file(Some("icons/disconnected.svg"));
network_label.set_visible(false);
None => {
network_icon.set_from_file(Some("icons/disconnected.svg"));
network_label.set_visible(false);
}
}
}
NetworkState::VpnState { connected, profile } if profile.as_str() == "wg" => {
if connected {
wg_button.add_css_class("on");
} else {
wg_button.remove_css_class("on");
}
}
_ => {}
}
NetworkState::VpnState { connected, profile } if profile.as_str() == "wg" => {
if connected {
wg_button.add_css_class("on");
} else {
wg_button.remove_css_class("on");
}
while let Ok(tailscale_state) = netbird_rx.try_recv() {
match tailscale_state {
json_socket::State::Disconnected => {
netbird_button.remove_css_class("on");
netbird_button.remove_css_class("starting");
netbird_button.remove_css_class("unknown");
netbird_button.remove_css_class("login");
}
json_socket::State::Connecting => {
netbird_button.remove_css_class("on");
netbird_button.add_css_class("starting");
netbird_button.remove_css_class("unknown");
netbird_button.remove_css_class("login");
}
json_socket::State::Connected => {
netbird_button.add_css_class("on");
netbird_button.remove_css_class("starting");
netbird_button.remove_css_class("unknown");
netbird_button.remove_css_class("login");
}
json_socket::State::NeedsLogin => {
netbird_button.remove_css_class("on");
netbird_button.remove_css_class("starting");
netbird_button.remove_css_class("unknown");
netbird_button.add_css_class("login");
}
}
_ => {}
}
}
while let Ok(tailscale_state) = netbird_rx.try_recv() {
match tailscale_state {
json_socket::State::Disconnected => {
netbird_button.remove_css_class("on");
netbird_button.remove_css_class("starting");
netbird_button.remove_css_class("unknown");
netbird_button.remove_css_class("login");
}
json_socket::State::Connecting => {
netbird_button.remove_css_class("on");
netbird_button.add_css_class("starting");
netbird_button.remove_css_class("unknown");
netbird_button.remove_css_class("login");
}
json_socket::State::Connected => {
netbird_button.add_css_class("on");
netbird_button.remove_css_class("starting");
netbird_button.remove_css_class("unknown");
netbird_button.remove_css_class("login");
}
json_socket::State::NeedsLogin => {
netbird_button.remove_css_class("on");
netbird_button.remove_css_class("starting");
netbird_button.remove_css_class("unknown");
netbird_button.add_css_class("login");
}
if wg_button.has_css_class("on") || netbird_button.has_css_class("on") {
vpn_button.add_css_class("on");
vpn_button.remove_css_class("login");
} else if wg_button.has_css_class("login") || netbird_button.has_css_class("login") {
vpn_button.add_css_class("login");
vpn_button.remove_css_class("on");
} else {
vpn_button.remove_css_class("on");
vpn_button.remove_css_class("login");
}
}
if wg_button.has_css_class("on") || netbird_button.has_css_class("on") {
vpn_button.add_css_class("on");
vpn_button.remove_css_class("login");
} else if wg_button.has_css_class("login") || netbird_button.has_css_class("login") {
vpn_button.add_css_class("login");
vpn_button.remove_css_class("on");
} else {
vpn_button.remove_css_class("on");
vpn_button.remove_css_class("login");
}
while let Ok(stats) = stats_rx.try_recv() {
if let Some(cpu) = stats.cpu {
cpu_label.set_label(&format!("{}%", cpu));
}
if let Some(ram) = stats.ram {
ram_label.set_label(&format!("{}%", ram));
}
for disk in stats.disks {
if disk.0.eq("/") {
disk_label.set_label(&format!("{}%", disk.1));
break;
while let Ok(stats) = stats_rx.try_recv() {
if let Some(cpu) = stats.cpu {
cpu_label.set_label(&format!("{}%", cpu));
}
if let Some(ram) = stats.ram {
ram_label.set_label(&format!("{}%", ram));
}
for disk in stats.disks {
if disk.0.eq("/") {
disk_label.set_label(&format!("{}%", disk.1));
break;
}
}
}
if let Some(gpu) = stats.gpus.into_iter().next() {
gpu_label.set_label(&format!("{}%", gpu.1));
}
if let Some(gpu) = stats.gpus.into_iter().next() {
gpu_label.set_label(&format!("{}%", gpu.1));
}
set_battery_css_classes(&battery_widget, stats.battery_state, stats.battery_charge);
let idx = ((stats.battery_charge as f32 / 100.0) * (BATTERY_ICONS.len() - 1) as f32
+ 0.5) as usize;
let idx = idx.min(BATTERY_ICONS.len() - 1);
battery_widget.set_title(&format!("{} {}%", BATTERY_ICONS[idx], stats.battery_charge));
wattage.set_label(&format!("{:.1}W", stats.battery_draw));
remaining_time.set_label(&format!(" {}h", format_remaining(stats.battery_time)));
set_battery_css_classes(&battery_widget, stats.battery_state, stats.battery_charge);
let idx = ((stats.battery_charge as f32 / 100.0) * (BATTERY_ICONS.len() - 1) as f32
+ 0.5) as usize;
let idx = idx.min(BATTERY_ICONS.len() - 1);
battery_widget
.set_title(&format!("{} {}%", BATTERY_ICONS[idx], stats.battery_charge));
wattage.set_label(&format!(" {:.1}W", stats.battery_draw));
remaining_time.set_label(&format!("{}h", format_remaining(stats.battery_time)));
}
ControlFlow::Continue
}
ControlFlow::Continue
});
utilities
}

View File

@@ -125,10 +125,6 @@ button {
background: $background1;
}
.caffeine.on {
background: $red;
}
.caffeine.idle {
background: $yellow;
}
@@ -137,6 +133,10 @@ button {
background: $green;
}
.caffeine.on {
background: $red;
}
.stats {
padding: 0 6px;
}

View File

@@ -2,4 +2,25 @@
.run-menu {
background: $background;
padding: 5px;
border-radius: 10px;
border: 1px solid $orange;
}
.exec-button {
padding: 3px;
border-radius: 5px;
border: 1px solid $background1;
}
.exec-button:focus {
border-color: $orange;
}
.suggested {
border-color: $orange;
}
.exec-button:active {
border-color: $orange;
}