Compare commits
4 commits
e8d5fd5a52
...
5ec8b69d37
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ec8b69d37 | ||
|
|
2cd2fbc7f6 | ||
|
|
86432d718a | ||
|
|
1f2d58d97f |
438
Cargo.lock
generated
|
|
@ -8,12 +8,6 @@ version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anyhow"
|
|
||||||
version = "1.0.102"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
@ -22,9 +16,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.6"
|
version = "0.7.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
|
|
@ -102,9 +96,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.13.0"
|
version = "2.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bread-theme"
|
name = "bread-theme"
|
||||||
|
|
@ -119,7 +113,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "breadbar"
|
name = "breadbar"
|
||||||
version = "0.2.4"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bread-theme",
|
"bread-theme",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
|
|
@ -142,15 +136,15 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.25.0"
|
version = "1.25.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cairo-rs"
|
name = "cairo-rs"
|
||||||
|
|
@ -158,7 +152,7 @@ version = "0.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95"
|
checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.1",
|
||||||
"cairo-sys-rs",
|
"cairo-sys-rs",
|
||||||
"glib",
|
"glib",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -220,9 +214,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.21"
|
version = "0.8.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "data-url"
|
name = "data-url"
|
||||||
|
|
@ -409,12 +403,6 @@ dependencies = [
|
||||||
"spin",
|
"spin",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foldhash"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fragile"
|
name = "fragile"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
|
@ -426,9 +414,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.32"
|
version = "0.3.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
|
checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
|
@ -441,9 +429,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.32"
|
version = "0.3.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
|
|
@ -451,15 +439,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.32"
|
version = "0.3.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-executor"
|
name = "futures-executor"
|
||||||
version = "0.3.32"
|
version = "0.3.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
|
checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
|
@ -468,9 +456,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.32"
|
version = "0.3.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-lite"
|
name = "futures-lite"
|
||||||
|
|
@ -487,9 +475,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.32"
|
version = "0.3.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -498,21 +486,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.32"
|
version = "0.3.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
|
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.32"
|
version = "0.3.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.32"
|
version = "0.3.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
|
@ -552,9 +540,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gdk4"
|
name = "gdk4"
|
||||||
version = "0.11.2"
|
version = "0.11.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd42fdbbf48612c6e8f47c65fb92d2e8f39c25aecd6af047e83897c1a22d2a4e"
|
checksum = "d81e2a6c6ecba2aab60633a98df1868b03fa0bfdce8105edc27c1bccf71f0e39"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cairo-rs",
|
"cairo-rs",
|
||||||
"gdk-pixbuf",
|
"gdk-pixbuf",
|
||||||
|
|
@ -568,9 +556,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gdk4-sys"
|
name = "gdk4-sys"
|
||||||
version = "0.11.2"
|
version = "0.11.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d974ac4f15e67472c3a9728daf612590b4a5762a4b33f0edd298df0b80d043c"
|
checksum = "3d8f608d8d7d229975c4d0d026f5d3071598c4ddab3c5262b0a31840fec78d13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cairo-sys-rs",
|
"cairo-sys-rs",
|
||||||
"gdk-pixbuf-sys",
|
"gdk-pixbuf-sys",
|
||||||
|
|
@ -610,22 +598,20 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi 6.0.0",
|
"r-efi 6.0.0",
|
||||||
"wasip2",
|
|
||||||
"wasip3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gio"
|
name = "gio"
|
||||||
version = "0.22.6"
|
version = "0.22.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3848bcba3a35cc0a71df8ba8ecfd799d6bfb862342a53a4a915fb62213aa4e6"
|
checksum = "8b3e1f669909c326b9413bde5a742097b8c90a7d78f45326db13668984769ded"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
|
@ -640,9 +626,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gio-sys"
|
name = "gio-sys"
|
||||||
version = "0.22.0"
|
version = "0.22.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1"
|
checksum = "353fdc7da7cd16da916104b1e0e4e7de380ec9c8aaa20d4d742d66310ab4b0d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"glib-sys",
|
"glib-sys",
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
|
|
@ -673,11 +659,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glib"
|
name = "glib"
|
||||||
version = "0.22.7"
|
version = "0.22.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a"
|
checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.1",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-executor",
|
"futures-executor",
|
||||||
|
|
@ -706,9 +692,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glib-sys"
|
name = "glib-sys"
|
||||||
version = "0.22.6"
|
version = "0.22.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f7fbac234ed5bc2a28359b7bde8e1b9cdf1441cc2d7f068e4824672d7db9445"
|
checksum = "030967459f9f676851872c6304adea7825c6d462ec9b72554c733cf0c5952233"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps",
|
||||||
|
|
@ -727,32 +713,30 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "graphene-rs"
|
name = "graphene-rs"
|
||||||
version = "0.22.0"
|
version = "0.22.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1"
|
checksum = "eb856b9c558971c3f13ab692358926da710b046932a4e087aedcc35b040d7dff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"glib",
|
"glib",
|
||||||
"graphene-sys",
|
"graphene-sys",
|
||||||
"libc",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "graphene-sys"
|
name = "graphene-sys"
|
||||||
version = "0.22.0"
|
version = "0.22.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c"
|
checksum = "5c7ffdfde88f3570d3705e0d8a2433e036d387a1f2930bbf47eafcb5f569fd04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"glib-sys",
|
"glib-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"pkg-config",
|
|
||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gsk4"
|
name = "gsk4"
|
||||||
version = "0.11.1"
|
version = "0.11.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62"
|
checksum = "b867be1c5f14dcb8f552c0eff6e9a9b1da5f8b43943e8efc3a63c889d84952ff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cairo-rs",
|
"cairo-rs",
|
||||||
"gdk4",
|
"gdk4",
|
||||||
|
|
@ -765,9 +749,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gsk4-sys"
|
name = "gsk4-sys"
|
||||||
version = "0.11.1"
|
version = "0.11.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a"
|
checksum = "5b7c7eb2e681ee896646cfb8872b431f24d09f53ba9283289d9b10caa6707088"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cairo-sys-rs",
|
"cairo-sys-rs",
|
||||||
"gdk4-sys",
|
"gdk4-sys",
|
||||||
|
|
@ -781,9 +765,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gtk4"
|
name = "gtk4"
|
||||||
version = "0.11.3"
|
version = "0.11.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7181b837f04cbe93f79441475f7a00560a92cba7a72e38cc1a68b6f8b78eaae2"
|
checksum = "98a0a0466484f64b07b5b8184d43fa46be78eb0b8e04ae4e179af31d770b76d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cairo-rs",
|
"cairo-rs",
|
||||||
"field-offset",
|
"field-offset",
|
||||||
|
|
@ -806,7 +790,7 @@ version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4069987ff4793699511a251028cc336b438e46565b463f111250148d574752a"
|
checksum = "a4069987ff4793699511a251028cc336b438e46565b463f111250148d574752a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.1",
|
||||||
"gdk4",
|
"gdk4",
|
||||||
"glib",
|
"glib",
|
||||||
"glib-sys",
|
"glib-sys",
|
||||||
|
|
@ -830,9 +814,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gtk4-macros"
|
name = "gtk4-macros"
|
||||||
version = "0.11.0"
|
version = "0.11.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87"
|
checksum = "5ac7179400a36a04de039c24206bb841c5596992b907b43b23ee8d5bdc40d00e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -842,9 +826,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gtk4-sys"
|
name = "gtk4-sys"
|
||||||
version = "0.11.3"
|
version = "0.11.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20ba8e695e2640455561274e65e45f0a151619e450746007667f4b23ceae4e1b"
|
checksum = "82b8f954786af0b1984425c4446b77f5ff6594346181316be3f850caab1c6f01"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cairo-sys-rs",
|
"cairo-sys-rs",
|
||||||
"gdk-pixbuf-sys",
|
"gdk-pixbuf-sys",
|
||||||
|
|
@ -859,15 +843,6 @@ dependencies = [
|
||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.15.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
|
||||||
dependencies = [
|
|
||||||
"foldhash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
|
|
@ -915,12 +890,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "id-arena"
|
|
||||||
version = "2.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imagesize"
|
name = "imagesize"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
|
@ -934,9 +903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.17.1",
|
"hashbrown",
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -947,13 +914,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.99"
|
version = "0.3.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"once_cell",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -974,12 +940,6 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "leb128fmt"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.186"
|
version = "0.2.186"
|
||||||
|
|
@ -988,9 +948,9 @@ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.17"
|
version = "0.1.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
|
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
@ -1012,15 +972,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.32"
|
version = "0.4.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.1"
|
version = "2.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
|
|
@ -1043,9 +1003,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.2.1"
|
version = "1.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
|
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
|
|
@ -1085,13 +1045,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.22.6"
|
version = "0.22.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "251bdc6e6487b811be0e406a21e301e07e45c0aa8fa39e00c0c8e12a91752438"
|
checksum = "5d800d8d0de2ad5d0fb046f5344dbaba14a003cf3dd27cc21d85893d35ea316c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gio",
|
"gio",
|
||||||
"glib",
|
"glib",
|
||||||
"libc",
|
|
||||||
"pango-sys",
|
"pango-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1150,16 +1109,6 @@ dependencies = [
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "prettyplease"
|
|
||||||
version = "0.2.37"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "3.5.0"
|
version = "3.5.0"
|
||||||
|
|
@ -1180,9 +1129,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.45"
|
version = "1.0.46"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
@ -1288,7 +1237,7 @@ version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.13.0",
|
"bitflags 2.13.1",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
|
|
@ -1297,9 +1246,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.22"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
|
|
@ -1388,9 +1337,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.9"
|
version = "0.3.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simplecss"
|
name = "simplecss"
|
||||||
|
|
@ -1415,15 +1364,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.1"
|
version = "1.15.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.4"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
|
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
|
|
@ -1431,9 +1380,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.9.8"
|
version = "0.9.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
]
|
]
|
||||||
|
|
@ -1459,9 +1408,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.117"
|
version = "2.0.119"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1494,7 +1443,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.4.2",
|
"getrandom 0.4.3",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
|
|
@ -1548,9 +1497,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.52.3"
|
version = "1.53.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -1565,9 +1514,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.7.0"
|
version = "2.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1576,9 +1525,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "1.1.2+spec-1.1.0"
|
version = "1.1.3+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
|
checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
|
|
@ -1600,9 +1549,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.25.12+spec-1.1.0"
|
version = "0.25.13+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
|
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
|
|
@ -1621,9 +1570,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_writer"
|
name = "toml_writer"
|
||||||
version = "1.1.1+spec-1.1.0"
|
version = "1.1.2+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
|
|
@ -1709,9 +1658,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.23.2"
|
version = "1.24.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
|
checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
|
|
@ -1732,27 +1681,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.3+wasi-0.2.9"
|
version = "1.0.4+wasi-0.2.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen 0.57.1",
|
"wit-bindgen",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasip3"
|
|
||||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
|
||||||
dependencies = [
|
|
||||||
"wit-bindgen 0.51.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.122"
|
version = "0.2.126"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -1763,9 +1703,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.122"
|
version = "0.2.126"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
|
|
@ -1773,9 +1713,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.122"
|
version = "0.2.126"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -1786,47 +1726,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.122"
|
version = "0.2.126"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-encoder"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
|
||||||
dependencies = [
|
|
||||||
"leb128fmt",
|
|
||||||
"wasmparser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-metadata"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"indexmap",
|
|
||||||
"wasm-encoder",
|
|
||||||
"wasmparser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasmparser"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.13.0",
|
|
||||||
"hashbrown 0.15.5",
|
|
||||||
"indexmap",
|
|
||||||
"semver",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
@ -1910,107 +1816,19 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
|
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen"
|
|
||||||
version = "0.51.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
|
||||||
dependencies = [
|
|
||||||
"wit-bindgen-rust-macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.57.1"
|
version = "0.57.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen-core"
|
|
||||||
version = "0.51.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"heck",
|
|
||||||
"wit-parser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen-rust"
|
|
||||||
version = "0.51.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"heck",
|
|
||||||
"indexmap",
|
|
||||||
"prettyplease",
|
|
||||||
"syn",
|
|
||||||
"wasm-metadata",
|
|
||||||
"wit-bindgen-core",
|
|
||||||
"wit-component",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen-rust-macro"
|
|
||||||
version = "0.51.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"prettyplease",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wit-bindgen-core",
|
|
||||||
"wit-bindgen-rust",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-component"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"bitflags 2.13.0",
|
|
||||||
"indexmap",
|
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"wasm-encoder",
|
|
||||||
"wasm-metadata",
|
|
||||||
"wasmparser",
|
|
||||||
"wit-parser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-parser"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"id-arena",
|
|
||||||
"indexmap",
|
|
||||||
"log",
|
|
||||||
"semver",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"unicode-xid",
|
|
||||||
"wasmparser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xml-rs"
|
name = "xml-rs"
|
||||||
version = "0.8.28"
|
version = "0.8.28"
|
||||||
|
|
@ -2025,9 +1843,9 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus"
|
name = "zbus"
|
||||||
version = "5.16.0"
|
version = "5.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285"
|
checksum = "fe18fb60dc696039e738717b76eaea21e7a4489bbb1885020b43c94236d7e98a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-broadcast",
|
"async-broadcast",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
|
|
@ -2055,9 +1873,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus_macros"
|
name = "zbus_macros"
|
||||||
version = "5.16.0"
|
version = "5.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6"
|
checksum = "fe96480bed92df2b442a1a30df364e12d08eed03aeb061f2b8dc6afb2be91119"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -2070,9 +1888,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus_names"
|
name = "zbus_names"
|
||||||
version = "4.3.2"
|
version = "4.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d"
|
checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"winnow",
|
"winnow",
|
||||||
|
|
@ -2081,15 +1899,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant"
|
name = "zvariant"
|
||||||
version = "5.12.0"
|
version = "5.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0"
|
checksum = "bee2a0bcd2a907786a456fff45aaaaf54c9ba5f50b71ae9ec1a4edd200c94911"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"endi",
|
"endi",
|
||||||
"enumflags2",
|
"enumflags2",
|
||||||
|
|
@ -2101,9 +1919,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant_derive"
|
name = "zvariant_derive"
|
||||||
version = "5.12.0"
|
version = "5.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737"
|
checksum = "38a708216a18780796770bfe3f4739c7c83a3e8f789b755534bbbc06e4e23e12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -2114,9 +1932,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant_utils"
|
name = "zvariant_utils"
|
||||||
version = "3.4.0"
|
version = "3.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6"
|
checksum = "90cb9383f9b45290407a1258b202d3f8f01db719eb60b4e4055c6375af4fc7c7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "breadbar"
|
name = "breadbar"
|
||||||
version = "0.2.4"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Minimal status bar and notification daemon for Hyprland on Wayland"
|
description = "Minimal status bar and notification daemon for Hyprland on Wayland"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Breadway <rileyhorsham@gmail.com>"]
|
authors = ["Breadway <plasticbread849@gmail.com>"]
|
||||||
repository = "https://github.com/Breadway/breadbar"
|
repository = "https://git.breadway.dev/Breadway/breadbar"
|
||||||
keywords = ["wayland", "hyprland", "bar", "status-bar", "gtk4"]
|
keywords = ["wayland", "hyprland", "bar", "status-bar", "gtk4"]
|
||||||
categories = ["gui"]
|
categories = ["gui"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ A single Rust binary that provides a full-width top bar, a D-Bus notification da
|
||||||
- Live CPU%, GPU%, and network throughput (download/upload)
|
- Live CPU%, GPU%, and network throughput (download/upload)
|
||||||
- Audio output selector (lists PulseAudio sinks via `pactl`, switching takes effect immediately)
|
- Audio output selector (lists PulseAudio sinks via `pactl`, switching takes effect immediately)
|
||||||
- System tray (SNI): apps that register with `org.kde.StatusNotifierWatcher` appear as icon buttons
|
- System tray (SNI): apps that register with `org.kde.StatusNotifierWatcher` appear as icon buttons
|
||||||
- Power buttons: lock (`hyprlock`), suspend, reboot, poweroff
|
- Power buttons: lock (`breadlock`), suspend, reboot, poweroff
|
||||||
|
|
||||||
**Notification daemon**:
|
**Notification daemon**:
|
||||||
|
|
||||||
|
|
|
||||||
1
assets/Bluetooth Settings.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-settings-icon lucide-settings"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||||
|
After Width: | Height: | Size: 848 B |
1
assets/Brightness.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sun-icon lucide-sun"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
|
||||||
|
After Width: | Height: | Size: 471 B |
1
assets/Lock.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lock-icon lucide-lock"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
|
||||||
|
After Width: | Height: | Size: 325 B |
1
assets/Next.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-skip-forward-icon lucide-skip-forward"><polygon points="5 4 15 12 5 20 5 4"/><line x1="19" x2="19" y1="5" y2="19"/></svg>
|
||||||
|
After Width: | Height: | Size: 324 B |
1
assets/Pause.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pause-icon lucide-pause"><rect x="14" y="3" width="5" height="18" rx="1"/><rect x="6" y="3" width="5" height="18" rx="1"/></svg>
|
||||||
|
After Width: | Height: | Size: 331 B |
1
assets/Play.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-play-icon lucide-play"><polygon points="6 3 20 12 6 21 6 3"/></svg>
|
||||||
|
After Width: | Height: | Size: 270 B |
1
assets/Previous.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-skip-back-icon lucide-skip-back"><polygon points="19 20 9 12 19 4 19 20"/><line x1="5" x2="5" y1="19" y2="5"/></svg>
|
||||||
|
After Width: | Height: | Size: 319 B |
1
assets/Restart.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw-icon lucide-rotate-ccw"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
||||||
|
After Width: | Height: | Size: 325 B |
1
assets/Shutdown.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-power-icon lucide-power"><path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.77.04"/></svg>
|
||||||
|
After Width: | Height: | Size: 294 B |
1
assets/Sleep.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-moon-icon lucide-moon"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 278 B |
1
assets/Volume.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-volume-2-icon lucide-volume-2"><path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"/><path d="M16 9a5 5 0 0 1 0 6"/><path d="M19.364 18.364a9 9 0 0 0 0-12.728"/></svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
|
|
@ -1,48 +0,0 @@
|
||||||
SVG icons needed for breadbar
|
|
||||||
==============================
|
|
||||||
24×24 viewBox. Use `currentColor` for all fill/stroke so icons recolour
|
|
||||||
automatically with the bar theme. Drop finished files in this directory.
|
|
||||||
|
|
||||||
|
|
||||||
Control panel — slider row icons
|
|
||||||
---------------------------------
|
|
||||||
Volume.svg
|
|
||||||
Speaker / soundwave icon for the volume slider row.
|
|
||||||
Currently placeholder: 🔊 emoji label.
|
|
||||||
Usage: control panel, left of volume slider.
|
|
||||||
|
|
||||||
Brightness.svg
|
|
||||||
Sun / light bulb icon for the brightness slider row.
|
|
||||||
Currently placeholder: ☀ emoji label.
|
|
||||||
Usage: control panel, left of brightness slider.
|
|
||||||
|
|
||||||
|
|
||||||
Power section buttons
|
|
||||||
-----------------------
|
|
||||||
Lock.svg
|
|
||||||
Padlock icon — triggers hyprlock (lock screen).
|
|
||||||
Currently placeholder: 🔒
|
|
||||||
|
|
||||||
Sleep.svg
|
|
||||||
Crescent moon or Zzz icon — triggers systemctl suspend.
|
|
||||||
Currently placeholder: 💤
|
|
||||||
|
|
||||||
Restart.svg
|
|
||||||
Circular arrow icon — triggers systemctl reboot.
|
|
||||||
Currently placeholder: 🔄
|
|
||||||
|
|
||||||
Shutdown.svg
|
|
||||||
Power symbol (⏻) icon — triggers systemctl poweroff.
|
|
||||||
Currently placeholder: ⏻
|
|
||||||
|
|
||||||
|
|
||||||
How to wire up icons once SVGs are ready
|
|
||||||
-----------------------------------------
|
|
||||||
Each power button and slider row icon is currently a gtk4::Label with an emoji.
|
|
||||||
To replace with an SVG:
|
|
||||||
|
|
||||||
1. Add the SVG to this directory.
|
|
||||||
2. In main.rs, replace the emoji Label with:
|
|
||||||
gtk4::Image::from_paintable(Some(&svg_texture(asset!("Icon Name.svg"))))
|
|
||||||
3. For slider rows, replace the icon_lbl in build_slider_row() calls,
|
|
||||||
or add an overload that takes an image widget instead of a string.
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
# Maintainer: Breadway <rileyhorsham@gmail.com>
|
# Maintainer: Breadway <plasticbread849@gmail.com>
|
||||||
|
|
||||||
pkgname=breadbar
|
pkgname=breadbar
|
||||||
pkgver=0.2.0
|
pkgver=0.2.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Minimal status bar and notification daemon for Hyprland"
|
pkgdesc="Minimal status bar and notification daemon for Hyprland"
|
||||||
arch=('x86_64')
|
arch=('x86_64')
|
||||||
url="https://github.com/Breadway/breadbar"
|
url="https://git.breadway.dev/Breadway/breadbar"
|
||||||
license=('MIT')
|
license=('MIT')
|
||||||
# Some Rust deps (ring/mlua) build vendored C/asm into static archives; makepkg's
|
# Some Rust deps (ring/mlua) build vendored C/asm into static archives; makepkg's
|
||||||
# default -flto=auto emits GCC LTO bitcode the Rust (lld) link cannot read,
|
# default -flto=auto emits GCC LTO bitcode the Rust (lld) link cannot read,
|
||||||
|
|
|
||||||
123
src/bar/bluetooth.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
use crate::{App, AppInput};
|
||||||
|
use relm4::ComponentSender;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BtDevice {
|
||||||
|
pub address: String,
|
||||||
|
pub name: String,
|
||||||
|
pub connected: bool,
|
||||||
|
pub paired: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BtPopoverData {
|
||||||
|
pub powered: bool,
|
||||||
|
pub devices: Vec<BtDevice>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same rfkill scan `bar::stats` uses for the bar icon — kept independent
|
||||||
|
/// (rather than shared) since it's a two-line read and pulling in a shared
|
||||||
|
/// helper isn't worth the coupling.
|
||||||
|
fn powered() -> bool {
|
||||||
|
fs::read_dir("/sys/class/rfkill")
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.any(|e| {
|
||||||
|
let p = e.path();
|
||||||
|
fs::read_to_string(p.join("type"))
|
||||||
|
.map(|t| t.trim() == "bluetooth")
|
||||||
|
.unwrap_or(false)
|
||||||
|
&& fs::read_to_string(p.join("state"))
|
||||||
|
.map(|s| s.trim() == "1")
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_devices() -> Vec<BtDevice> {
|
||||||
|
try_fetch_devices().await.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_fetch_devices() -> Option<Vec<BtDevice>> {
|
||||||
|
let conn = zbus::Connection::system().await.ok()?;
|
||||||
|
let mgr = zbus::fdo::ObjectManagerProxy::builder(&conn)
|
||||||
|
.destination("org.bluez")
|
||||||
|
.ok()?
|
||||||
|
.path("/")
|
||||||
|
.ok()?
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
let objects = mgr.get_managed_objects().await.ok()?;
|
||||||
|
|
||||||
|
let mut devices: Vec<BtDevice> = objects
|
||||||
|
.values()
|
||||||
|
.filter_map(|ifaces| ifaces.get("org.bluez.Device1"))
|
||||||
|
.filter_map(|props| {
|
||||||
|
let paired = props
|
||||||
|
.get("Paired")
|
||||||
|
.and_then(|v| bool::try_from(v.clone()).ok())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !paired {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let address = props
|
||||||
|
.get("Address")
|
||||||
|
.and_then(|v| String::try_from(v.clone()).ok())?;
|
||||||
|
let name = props
|
||||||
|
.get("Alias")
|
||||||
|
.or_else(|| props.get("Name"))
|
||||||
|
.and_then(|v| String::try_from(v.clone()).ok())
|
||||||
|
.unwrap_or_else(|| address.clone());
|
||||||
|
let connected = props
|
||||||
|
.get("Connected")
|
||||||
|
.and_then(|v| bool::try_from(v.clone()).ok())
|
||||||
|
.unwrap_or(false);
|
||||||
|
Some(BtDevice { address, name, connected, paired })
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
devices.sort_by(|a, b| b.connected.cmp(&a.connected).then(a.name.cmp(&b.name)));
|
||||||
|
Some(devices)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_popover_load(sender: ComponentSender<App>) {
|
||||||
|
relm4::spawn(async move {
|
||||||
|
let devices = fetch_devices().await;
|
||||||
|
sender.input(AppInput::BtPopoverData(BtPopoverData {
|
||||||
|
powered: powered(),
|
||||||
|
devices,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fire-and-forget: toggle the adapter's rfkill soft-block.
|
||||||
|
pub fn spawn_set_powered(on: bool) {
|
||||||
|
relm4::spawn(async move {
|
||||||
|
let _ = tokio::process::Command::new("rfkill")
|
||||||
|
.args([if on { "unblock" } else { "block" }, "bluetooth"])
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fire-and-forget: connect a paired device by address via `bluetoothctl`.
|
||||||
|
pub fn spawn_connect(address: String) {
|
||||||
|
relm4::spawn(async move {
|
||||||
|
let _ = tokio::process::Command::new("bluetoothctl")
|
||||||
|
.args(["connect", &address])
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fire-and-forget: disconnect a device by address via `bluetoothctl`.
|
||||||
|
pub fn spawn_disconnect(address: String) {
|
||||||
|
relm4::spawn(async move {
|
||||||
|
let _ = tokio::process::Command::new("bluetoothctl")
|
||||||
|
.args(["disconnect", &address])
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod bluetooth;
|
||||||
pub mod clock;
|
pub mod clock;
|
||||||
pub mod control;
|
pub mod control;
|
||||||
pub mod media;
|
pub mod media;
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,22 @@ pub const BT_CONNECTED: &str = include_str!(concat!(
|
||||||
"/assets/Bluetooth Connected.svg"
|
"/assets/Bluetooth Connected.svg"
|
||||||
));
|
));
|
||||||
|
|
||||||
|
pub const ICON_VOLUME: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Volume.svg"));
|
||||||
|
pub const ICON_BRIGHTNESS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Brightness.svg"));
|
||||||
|
pub const ICON_LOCK: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Lock.svg"));
|
||||||
|
pub const ICON_SLEEP: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Sleep.svg"));
|
||||||
|
pub const ICON_RESTART: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Restart.svg"));
|
||||||
|
pub const ICON_SHUTDOWN: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Shutdown.svg"));
|
||||||
|
pub const ICON_BT_SETTINGS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/Bluetooth Settings.svg"));
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Stats {
|
pub struct Stats {
|
||||||
pub cpu: String,
|
pub cpu: String,
|
||||||
|
pub cpu_pct: f32,
|
||||||
pub mem: String,
|
pub mem: String,
|
||||||
|
pub mem_pct: f32,
|
||||||
pub power: String,
|
pub power: String,
|
||||||
|
pub power_watts: f32,
|
||||||
pub bat: String,
|
pub bat: String,
|
||||||
pub bat_icon: &'static str,
|
pub bat_icon: &'static str,
|
||||||
pub ac_connected: bool,
|
pub ac_connected: bool,
|
||||||
|
|
@ -99,7 +110,8 @@ fn read_cpu() -> f32 {
|
||||||
(dtotal - didle) as f32 / dtotal as f32 * 100.0
|
(dtotal - didle) as f32 / dtotal as f32 * 100.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_ram() -> u64 {
|
/// Returns (used_kb, total_kb).
|
||||||
|
fn read_ram() -> (u64, u64) {
|
||||||
let text = fs::read_to_string("/proc/meminfo").unwrap_or_default();
|
let text = fs::read_to_string("/proc/meminfo").unwrap_or_default();
|
||||||
let mut total = 0u64;
|
let mut total = 0u64;
|
||||||
let mut avail = 0u64;
|
let mut avail = 0u64;
|
||||||
|
|
@ -121,7 +133,7 @@ fn read_ram() -> u64 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
total.saturating_sub(avail)
|
(total.saturating_sub(avail), total)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bat_path() -> Option<&'static PathBuf> {
|
fn bat_path() -> Option<&'static PathBuf> {
|
||||||
|
|
@ -392,8 +404,14 @@ fn read_crumbs_profile() -> Option<String> {
|
||||||
|
|
||||||
pub async fn poll() -> Stats {
|
pub async fn poll() -> Stats {
|
||||||
let cpu = read_cpu();
|
let cpu = read_cpu();
|
||||||
let mem = read_ram();
|
let (mem, mem_total) = read_ram();
|
||||||
let power = read_power().map_or_else(|| "—W".into(), |w| format!("{w:.1}W"));
|
let mem_pct = if mem_total > 0 {
|
||||||
|
mem as f32 / mem_total as f32 * 100.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
let power_watts = read_power();
|
||||||
|
let power = power_watts.map_or_else(|| "—W".into(), |w| format!("{w:.1}W"));
|
||||||
let pct = read_battery();
|
let pct = read_battery();
|
||||||
let bat = pct.map_or_else(|| "—".into(), |p| format!("{p}%"));
|
let bat = pct.map_or_else(|| "—".into(), |p| format!("{p}%"));
|
||||||
let bat_icon = pct.map_or(BAT_MID, bat_level_icon);
|
let bat_icon = pct.map_or(BAT_MID, bat_level_icon);
|
||||||
|
|
@ -426,12 +444,15 @@ pub async fn poll() -> Stats {
|
||||||
let (net_rx_kbs, net_tx_kbs) = read_net_throughput();
|
let (net_rx_kbs, net_tx_kbs) = read_net_throughput();
|
||||||
Stats {
|
Stats {
|
||||||
cpu: format!("{cpu:.0}%"),
|
cpu: format!("{cpu:.0}%"),
|
||||||
|
cpu_pct: cpu,
|
||||||
mem: if mem >= 1024 * 1024 {
|
mem: if mem >= 1024 * 1024 {
|
||||||
format!("{:.1}G", mem as f32 / (1024.0 * 1024.0))
|
format!("{:.1}G", mem as f32 / (1024.0 * 1024.0))
|
||||||
} else {
|
} else {
|
||||||
format!("{}M", mem / 1024)
|
format!("{}M", mem / 1024)
|
||||||
},
|
},
|
||||||
|
mem_pct,
|
||||||
power,
|
power,
|
||||||
|
power_watts: power_watts.unwrap_or(0.0),
|
||||||
bat,
|
bat,
|
||||||
bat_icon,
|
bat_icon,
|
||||||
ac_connected,
|
ac_connected,
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,36 @@ use relm4::ComponentSender;
|
||||||
|
|
||||||
use crate::AppInput;
|
use crate::AppInput;
|
||||||
|
|
||||||
pub fn spawn_watcher(sender: ComponentSender<crate::App>) {
|
/// Fetches the current workspace list + active workspace and pushes both to
|
||||||
relm4::spawn(async move {
|
/// the app — used both for the initial state and to re-sync after the event
|
||||||
|
/// stream reconnects (state may have changed while we were disconnected).
|
||||||
|
async fn sync_state(sender: &ComponentSender<crate::App>) {
|
||||||
if let Ok(ws) = Workspaces::get_async().await {
|
if let Ok(ws) = Workspaces::get_async().await {
|
||||||
sender.input(AppInput::WorkspaceList(ws.to_vec()));
|
sender.input(AppInput::WorkspaceList(ws.to_vec()));
|
||||||
}
|
}
|
||||||
if let Ok(active) = Workspace::get_active_async().await {
|
if let Ok(active) = Workspace::get_active_async().await {
|
||||||
sender.input(AppInput::ActiveWorkspace(active.id));
|
sender.input(AppInput::ActiveWorkspace(active.id));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_watcher(sender: ComponentSender<crate::App>) {
|
||||||
|
relm4::spawn(async move {
|
||||||
|
sync_state(&sender).await;
|
||||||
|
|
||||||
|
// Hyprland's IPC event socket can drop out from under us — a
|
||||||
|
// Hyprland restart/reload, or just a transient hiccup — at which
|
||||||
|
// point `stream.next()` yields `None` (or an `Err`, also excluded
|
||||||
|
// by this `while let Some(Ok(..))` pattern). That used to just fall
|
||||||
|
// through and end this whole task permanently, freezing every
|
||||||
|
// workspace button for the rest of the bar's life. Reconnect with a
|
||||||
|
// capped exponential backoff instead of giving up.
|
||||||
|
let mut backoff = std::time::Duration::from_millis(500);
|
||||||
|
const MAX_BACKOFF: std::time::Duration = std::time::Duration::from_secs(30);
|
||||||
|
|
||||||
|
loop {
|
||||||
let mut stream = EventStream::new();
|
let mut stream = EventStream::new();
|
||||||
while let Some(Ok(event)) = stream.next().await {
|
while let Some(Ok(event)) = stream.next().await {
|
||||||
|
backoff = std::time::Duration::from_millis(500);
|
||||||
match event {
|
match event {
|
||||||
Event::WorkspaceChanged(data) => {
|
Event::WorkspaceChanged(data) => {
|
||||||
sender.input(AppInput::ActiveWorkspace(data.id));
|
sender.input(AppInput::ActiveWorkspace(data.id));
|
||||||
|
|
@ -33,6 +52,16 @@ pub fn spawn_watcher(sender: ComponentSender<crate::App>) {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"breadbar: Hyprland event stream ended (restart/reload/IPC hiccup); \
|
||||||
|
reconnecting in {:?}",
|
||||||
|
backoff
|
||||||
|
);
|
||||||
|
tokio::time::sleep(backoff).await;
|
||||||
|
backoff = (backoff * 2).min(MAX_BACKOFF);
|
||||||
|
sync_state(&sender).await;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
437
src/main.rs
|
|
@ -9,6 +9,12 @@ mod notifications;
|
||||||
mod osd;
|
mod osd;
|
||||||
mod theme;
|
mod theme;
|
||||||
|
|
||||||
|
/// Thresholds above which the bar's CPU/RAM/power-draw readouts appear at
|
||||||
|
/// all — see `AppInput::StatsUpdate`. Below these, the bar stays quiet.
|
||||||
|
const CPU_ATTENTION_THRESHOLD: f32 = 70.0;
|
||||||
|
const MEM_ATTENTION_THRESHOLD: f32 = 80.0;
|
||||||
|
const POWER_ATTENTION_THRESHOLD: f32 = 30.0;
|
||||||
|
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
||||||
use hyprland::data::Workspace;
|
use hyprland::data::Workspace;
|
||||||
|
|
@ -29,6 +35,14 @@ pub struct App {
|
||||||
clock_lbl: gtk4::Label,
|
clock_lbl: gtk4::Label,
|
||||||
|
|
||||||
// ── Stats bar ─────────────────────────────────────────────────────────
|
// ── Stats bar ─────────────────────────────────────────────────────────
|
||||||
|
// The system-stats trio (CPU/RAM/power draw) only shows up when a value
|
||||||
|
// crosses a "you probably want to know about this" threshold — otherwise
|
||||||
|
// the bar stays quiet. See AppInput::StatsUpdate.
|
||||||
|
system_stats_box: gtk4::Box,
|
||||||
|
system_sep: gtk4::Separator,
|
||||||
|
cpu_pair: gtk4::Box,
|
||||||
|
mem_pair: gtk4::Box,
|
||||||
|
pwr_pair: gtk4::Box,
|
||||||
cpu_lbl: gtk4::Label,
|
cpu_lbl: gtk4::Label,
|
||||||
mem_lbl: gtk4::Label,
|
mem_lbl: gtk4::Label,
|
||||||
pwr_lbl: gtk4::Label,
|
pwr_lbl: gtk4::Label,
|
||||||
|
|
@ -43,16 +57,20 @@ pub struct App {
|
||||||
wifi_textures: std::collections::HashMap<usize, gtk4::gdk::Texture>,
|
wifi_textures: std::collections::HashMap<usize, gtk4::gdk::Texture>,
|
||||||
|
|
||||||
// ── WiFi popover ──────────────────────────────────────────────────────
|
// ── WiFi popover ──────────────────────────────────────────────────────
|
||||||
wifi_popover_box: gtk4::Box,
|
wifi_pane: gtk4::Box,
|
||||||
crumbs_status: Option<bar::wifi::CrumbsStatus>,
|
crumbs_status: Option<bar::wifi::CrumbsStatus>,
|
||||||
wifi_popover_data: Option<bar::wifi::WifiPopoverData>,
|
wifi_popover_data: Option<bar::wifi::WifiPopoverData>,
|
||||||
wifi_profile: Option<String>,
|
wifi_profile: Option<String>,
|
||||||
current_ssid: String,
|
current_ssid: String,
|
||||||
|
|
||||||
|
// ── Bluetooth popover ────────────────────────────────────────────────
|
||||||
|
bt_pane: gtk4::Box,
|
||||||
|
bt_popover_data: Option<bar::bluetooth::BtPopoverData>,
|
||||||
|
|
||||||
// ── Media ─────────────────────────────────────────────────────────────
|
// ── Media ─────────────────────────────────────────────────────────────
|
||||||
media_widget: gtk4::Box,
|
media_widget: gtk4::Box,
|
||||||
media_track_lbl: gtk4::Label,
|
media_track_lbl: gtk4::Label,
|
||||||
media_play_btn: gtk4::Button,
|
media_play_icon: gtk4::Image,
|
||||||
media_last: Option<bar::media::MediaState>,
|
media_last: Option<bar::media::MediaState>,
|
||||||
media_paused_at: Option<std::time::Instant>,
|
media_paused_at: Option<std::time::Instant>,
|
||||||
|
|
||||||
|
|
@ -66,10 +84,14 @@ pub struct App {
|
||||||
panel_sink_signal: Option<gtk4::glib::SignalHandlerId>,
|
panel_sink_signal: Option<gtk4::glib::SignalHandlerId>,
|
||||||
panel_sinks: Vec<bar::control::AudioSink>,
|
panel_sinks: Vec<bar::control::AudioSink>,
|
||||||
panel_cpu_lbl: gtk4::Label,
|
panel_cpu_lbl: gtk4::Label,
|
||||||
|
panel_mem_lbl: gtk4::Label,
|
||||||
|
panel_pwr_lbl: gtk4::Label,
|
||||||
panel_gpu_lbl: gtk4::Label,
|
panel_gpu_lbl: gtk4::Label,
|
||||||
panel_net_lbl: gtk4::Label,
|
panel_net_lbl: gtk4::Label,
|
||||||
|
|
||||||
// ── Tray ──────────────────────────────────────────────────────────────
|
// ── Tray ──────────────────────────────────────────────────────────────
|
||||||
|
tray_section: gtk4::Box,
|
||||||
|
tray_sep: gtk4::Separator,
|
||||||
tray_box: gtk4::Box,
|
tray_box: gtk4::Box,
|
||||||
tray_items: std::collections::HashMap<String, gtk4::Button>,
|
tray_items: std::collections::HashMap<String, gtk4::Button>,
|
||||||
}
|
}
|
||||||
|
|
@ -84,6 +106,7 @@ pub enum AppInput {
|
||||||
CrumbsStatus(bar::wifi::CrumbsStatus),
|
CrumbsStatus(bar::wifi::CrumbsStatus),
|
||||||
WifiPopoverData(bar::wifi::WifiPopoverData),
|
WifiPopoverData(bar::wifi::WifiPopoverData),
|
||||||
SetProfile(String),
|
SetProfile(String),
|
||||||
|
BtPopoverData(bar::bluetooth::BtPopoverData),
|
||||||
MediaUpdate(bar::media::MediaState),
|
MediaUpdate(bar::media::MediaState),
|
||||||
ControlPanelData(bar::control::ControlPanelData),
|
ControlPanelData(bar::control::ControlPanelData),
|
||||||
}
|
}
|
||||||
|
|
@ -176,39 +199,15 @@ impl SimpleComponent for App {
|
||||||
let wifi_img =
|
let wifi_img =
|
||||||
gtk4::Image::from_paintable(Some(&svg_texture(asset!("WiFi Connecting.svg"))));
|
gtk4::Image::from_paintable(Some(&svg_texture(asset!("WiFi Connecting.svg"))));
|
||||||
|
|
||||||
let wifi_pair = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
|
||||||
wifi_pair.add_css_class("stat-pair");
|
|
||||||
wifi_pair.add_css_class("wifi-pair");
|
|
||||||
wifi_img.add_css_class("stat-icon");
|
wifi_img.add_css_class("stat-icon");
|
||||||
wifi_pair.append(&wifi_img);
|
|
||||||
wifi_pair.append(&wifi_lbl);
|
|
||||||
|
|
||||||
let wifi_popover_box = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
|
// Content pane only — this becomes a tab inside the merged
|
||||||
wifi_popover_box.add_css_class("wifi-popover-inner");
|
// connectivity popover built alongside the Bluetooth pane below,
|
||||||
wifi_popover_box.set_margin_top(4);
|
// once bt_img exists too. See "Connectivity popover" further down.
|
||||||
wifi_popover_box.set_margin_bottom(4);
|
let wifi_pane = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
|
||||||
wifi_popover_box.set_margin_start(4);
|
|
||||||
wifi_popover_box.set_margin_end(4);
|
|
||||||
let loading_lbl = gtk4::Label::new(Some("Scanning…"));
|
let loading_lbl = gtk4::Label::new(Some("Scanning…"));
|
||||||
loading_lbl.add_css_class("wifi-popover-loading");
|
loading_lbl.add_css_class("wifi-popover-loading");
|
||||||
wifi_popover_box.append(&loading_lbl);
|
wifi_pane.append(&loading_lbl);
|
||||||
|
|
||||||
let wifi_popover = gtk4::Popover::new();
|
|
||||||
wifi_popover.add_css_class("wifi-popover");
|
|
||||||
wifi_popover.set_child(Some(&wifi_popover_box));
|
|
||||||
wifi_popover.set_parent(&wifi_pair);
|
|
||||||
|
|
||||||
let wpop = wifi_popover.clone();
|
|
||||||
let gesture = gtk4::GestureClick::new();
|
|
||||||
gesture.connect_released(move |_, _, _, _| {
|
|
||||||
if wpop.is_visible() { wpop.popdown(); } else { wpop.popup(); }
|
|
||||||
});
|
|
||||||
wifi_pair.add_controller(gesture);
|
|
||||||
|
|
||||||
let sender_wp = sender.clone();
|
|
||||||
wifi_popover.connect_show(move |_| {
|
|
||||||
bar::wifi::spawn_popover_load(sender_wp.clone());
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Media widget (center) ────────────────────────────────────────
|
// ── Media widget (center) ────────────────────────────────────────
|
||||||
let media_widget = gtk4::Box::new(gtk4::Orientation::Horizontal, 4);
|
let media_widget = gtk4::Box::new(gtk4::Orientation::Horizontal, 4);
|
||||||
|
|
@ -235,18 +234,26 @@ impl SimpleComponent for App {
|
||||||
media_controls_box.set_margin_start(4);
|
media_controls_box.set_margin_start(4);
|
||||||
media_controls_box.set_margin_end(4);
|
media_controls_box.set_margin_end(4);
|
||||||
|
|
||||||
let prev_btn = gtk4::Button::with_label("⏮");
|
let prev_btn = gtk4::Button::new();
|
||||||
|
prev_btn.set_child(Some(>k4::Image::from_paintable(Some(&svg_texture(
|
||||||
|
asset!("Previous.svg"),
|
||||||
|
)))));
|
||||||
prev_btn.add_css_class("flat");
|
prev_btn.add_css_class("flat");
|
||||||
prev_btn.add_css_class("media-btn");
|
prev_btn.add_css_class("media-btn");
|
||||||
prev_btn.connect_clicked(|_| bar::media::spawn_cmd("previous"));
|
prev_btn.connect_clicked(|_| bar::media::spawn_cmd("previous"));
|
||||||
|
|
||||||
let media_play_btn = gtk4::Button::with_label("⏸");
|
let media_play_icon = gtk4::Image::from_paintable(Some(&svg_texture(asset!("Pause.svg"))));
|
||||||
|
let media_play_btn = gtk4::Button::new();
|
||||||
|
media_play_btn.set_child(Some(&media_play_icon));
|
||||||
media_play_btn.add_css_class("flat");
|
media_play_btn.add_css_class("flat");
|
||||||
media_play_btn.add_css_class("media-btn");
|
media_play_btn.add_css_class("media-btn");
|
||||||
media_play_btn.add_css_class("media-play-btn");
|
media_play_btn.add_css_class("media-play-btn");
|
||||||
media_play_btn.connect_clicked(|_| bar::media::spawn_cmd("play-pause"));
|
media_play_btn.connect_clicked(|_| bar::media::spawn_cmd("play-pause"));
|
||||||
|
|
||||||
let next_btn = gtk4::Button::with_label("⏭");
|
let next_btn = gtk4::Button::new();
|
||||||
|
next_btn.set_child(Some(>k4::Image::from_paintable(Some(&svg_texture(
|
||||||
|
asset!("Next.svg"),
|
||||||
|
)))));
|
||||||
next_btn.add_css_class("flat");
|
next_btn.add_css_class("flat");
|
||||||
next_btn.add_css_class("media-btn");
|
next_btn.add_css_class("media-btn");
|
||||||
next_btn.connect_clicked(|_| bar::media::spawn_cmd("next"));
|
next_btn.connect_clicked(|_| bar::media::spawn_cmd("next"));
|
||||||
|
|
@ -280,9 +287,22 @@ impl SimpleComponent for App {
|
||||||
// ── Stats box (right side) ───────────────────────────────────────
|
// ── Stats box (right side) ───────────────────────────────────────
|
||||||
let stats_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
let stats_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||||
stats_box.add_css_class("stats-box");
|
stats_box.add_css_class("stats-box");
|
||||||
stats_box.append(&stat_pair(asset!("CPU.svg"), &cpu_lbl));
|
|
||||||
stats_box.append(&stat_pair(asset!("RAM Usage.svg"), &mem_lbl));
|
// CPU/RAM/power draw: hidden by default (see StatsUpdate), so this
|
||||||
stats_box.append(&stat_pair(asset!("Power Draw.svg"), &pwr_lbl));
|
// whole sub-group — plus its separator — collapses away when quiet.
|
||||||
|
let cpu_pair = stat_pair(asset!("CPU.svg"), &cpu_lbl);
|
||||||
|
let mem_pair = stat_pair(asset!("RAM Usage.svg"), &mem_lbl);
|
||||||
|
let pwr_pair = stat_pair(asset!("Power Draw.svg"), &pwr_lbl);
|
||||||
|
let system_stats_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||||
|
system_stats_box.append(&cpu_pair);
|
||||||
|
system_stats_box.append(&mem_pair);
|
||||||
|
system_stats_box.append(&pwr_pair);
|
||||||
|
system_stats_box.set_visible(false);
|
||||||
|
stats_box.append(&system_stats_box);
|
||||||
|
let system_sep = gtk4::Separator::new(gtk4::Orientation::Vertical);
|
||||||
|
system_sep.add_css_class("bar-sep");
|
||||||
|
system_sep.set_visible(false);
|
||||||
|
stats_box.append(&system_sep);
|
||||||
|
|
||||||
let bat_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
let bat_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||||
bat_box.add_css_class("stat-pair");
|
bat_box.add_css_class("stat-pair");
|
||||||
|
|
@ -296,28 +316,121 @@ impl SimpleComponent for App {
|
||||||
stats_box.append(&bat_box);
|
stats_box.append(&bat_box);
|
||||||
|
|
||||||
bt_img.add_css_class("bt-icon");
|
bt_img.add_css_class("bt-icon");
|
||||||
bt_img.add_css_class("clickable");
|
|
||||||
let bt_gesture = gtk4::GestureClick::new();
|
// Content pane only — same deal as wifi_pane above.
|
||||||
bt_gesture.connect_released(|_, _, _, _| {
|
let bt_pane = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
|
||||||
relm4::spawn(async {
|
let bt_loading_lbl = gtk4::Label::new(Some("Loading…"));
|
||||||
let _ = tokio::process::Command::new("blueman-manager").spawn();
|
bt_loading_lbl.add_css_class("wifi-popover-loading");
|
||||||
|
bt_pane.append(&bt_loading_lbl);
|
||||||
|
|
||||||
|
// ── Connectivity popover ─────────────────────────────────────────
|
||||||
|
// WiFi and Bluetooth share one popover with one anatomy (same CSS
|
||||||
|
// classes throughout the two panes) instead of two near-identical
|
||||||
|
// popups behind two separate icons. A small tab row switches between
|
||||||
|
// them; both fetches kick off on open so switching tabs afterward is
|
||||||
|
// instant. The two panes live in a Stack rather than plain sibling
|
||||||
|
// Boxes with manual visibility toggling, which left stale width
|
||||||
|
// behind on reopen.
|
||||||
|
//
|
||||||
|
// hhomogeneous/vhomogeneous are ON (GTK's default), even though that
|
||||||
|
// means the popover is always sized to the *larger* of the two panes
|
||||||
|
// (visible empty space under the shorter one) — the alternative,
|
||||||
|
// sizing to just the active pane, means the popup has to resize
|
||||||
|
// itself in place when you switch tabs while it's open. Under
|
||||||
|
// gtk4-layer-shell that in-place resize doesn't reliably reach the
|
||||||
|
// compositor as a proper xdg_popup reposition; switching from the
|
||||||
|
// shorter tab to the taller one after a close/reopen cycle made the
|
||||||
|
// whole popover silently vanish instead of growing. A constant
|
||||||
|
// footprint sidesteps the resize entirely.
|
||||||
|
let content_stack = gtk4::Stack::new();
|
||||||
|
content_stack.set_hhomogeneous(true);
|
||||||
|
content_stack.set_vhomogeneous(true);
|
||||||
|
// Reserve enough room up front for the tallest realistic content
|
||||||
|
// (WiFi tab with a handful of nearby networks). Homogeneous sizing
|
||||||
|
// alone still lets the *first* real data load (Scanning… → populated
|
||||||
|
// list) trigger a live resize while the popup is mapped, which hits
|
||||||
|
// the same reposition fragility as the tab-switch case — claiming
|
||||||
|
// the space before anything is shown avoids that resize too.
|
||||||
|
content_stack.set_size_request(220, 420);
|
||||||
|
content_stack.add_named(&wifi_pane, Some("wifi"));
|
||||||
|
content_stack.add_named(&bt_pane, Some("bluetooth"));
|
||||||
|
content_stack.set_visible_child_name("wifi");
|
||||||
|
|
||||||
|
let wifi_tab_btn = gtk4::ToggleButton::with_label("Wi-Fi");
|
||||||
|
wifi_tab_btn.add_css_class("popover-tab");
|
||||||
|
wifi_tab_btn.set_active(true);
|
||||||
|
let bt_tab_btn = gtk4::ToggleButton::with_label("Bluetooth");
|
||||||
|
bt_tab_btn.add_css_class("popover-tab");
|
||||||
|
bt_tab_btn.set_group(Some(&wifi_tab_btn));
|
||||||
|
|
||||||
|
let tab_row = gtk4::Box::new(gtk4::Orientation::Horizontal, 4);
|
||||||
|
tab_row.add_css_class("popover-tab-row");
|
||||||
|
tab_row.append(&wifi_tab_btn);
|
||||||
|
tab_row.append(&bt_tab_btn);
|
||||||
|
|
||||||
|
let stack_for_wifi = content_stack.clone();
|
||||||
|
wifi_tab_btn.connect_toggled(move |btn| {
|
||||||
|
if btn.is_active() {
|
||||||
|
stack_for_wifi.set_visible_child_name("wifi");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
let stack_for_bt = content_stack.clone();
|
||||||
|
bt_tab_btn.connect_toggled(move |btn| {
|
||||||
|
if btn.is_active() {
|
||||||
|
stack_for_bt.set_visible_child_name("bluetooth");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let connectivity_inner = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
|
||||||
|
connectivity_inner.add_css_class("wifi-popover-inner");
|
||||||
|
connectivity_inner.append(&tab_row);
|
||||||
|
connectivity_inner.append(>k4::Separator::new(gtk4::Orientation::Horizontal));
|
||||||
|
connectivity_inner.append(&content_stack);
|
||||||
|
|
||||||
|
let connectivity_popover = gtk4::Popover::new();
|
||||||
|
connectivity_popover.add_css_class("wifi-popover");
|
||||||
|
connectivity_popover.set_child(Some(&connectivity_inner));
|
||||||
|
|
||||||
|
let connectivity_pair = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||||
|
connectivity_pair.add_css_class("stat-pair");
|
||||||
|
connectivity_pair.add_css_class("wifi-pair");
|
||||||
|
connectivity_pair.append(&bt_img);
|
||||||
|
connectivity_pair.append(&wifi_img);
|
||||||
|
connectivity_pair.append(&wifi_lbl);
|
||||||
|
// Anchored to wifi_lbl specifically, not the connectivity_pair row —
|
||||||
|
// a Popover parented to a multi-child Box via set_parent() balloons
|
||||||
|
// that Box's own allocation after a popup→popdown→popup cycle (its
|
||||||
|
// width roughly quadrupled in testing, shoving every bar item to its
|
||||||
|
// left further left). Anchoring to a single leaf widget instead
|
||||||
|
// sidesteps whatever GTK4/gtk4-layer-shell interaction causes that;
|
||||||
|
// the click target below still covers the whole row regardless.
|
||||||
|
connectivity_popover.set_parent(&wifi_lbl);
|
||||||
|
stats_box.append(&connectivity_pair);
|
||||||
|
|
||||||
|
let cpop = connectivity_popover.clone();
|
||||||
|
let gesture = gtk4::GestureClick::new();
|
||||||
|
gesture.connect_released(move |_, _, _, _| {
|
||||||
|
if cpop.is_visible() { cpop.popdown(); } else { cpop.popup(); }
|
||||||
|
});
|
||||||
|
connectivity_pair.add_controller(gesture);
|
||||||
|
|
||||||
|
let sender_conn = sender.clone();
|
||||||
|
connectivity_popover.connect_show(move |_| {
|
||||||
|
bar::wifi::spawn_popover_load(sender_conn.clone());
|
||||||
|
bar::bluetooth::spawn_popover_load(sender_conn.clone());
|
||||||
});
|
});
|
||||||
bt_img.add_controller(bt_gesture);
|
|
||||||
stats_box.append(&bt_img);
|
|
||||||
stats_box.append(&wifi_pair);
|
|
||||||
|
|
||||||
// ── Control panel popover ────────────────────────────────────────
|
// ── Control panel popover ────────────────────────────────────────
|
||||||
let panel_inner = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
|
let panel_inner = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
|
||||||
panel_inner.add_css_class("control-panel-inner");
|
panel_inner.add_css_class("control-panel-inner");
|
||||||
|
|
||||||
// Volume row
|
// Volume row
|
||||||
let vol_row = build_slider_row("🔊", 0.0, 1.5, 0.02);
|
let vol_row = build_slider_row(bar::stats::ICON_VOLUME, 0.0, 1.5, 0.02);
|
||||||
let panel_vol_slider = vol_row.1.clone();
|
let panel_vol_slider = vol_row.1.clone();
|
||||||
panel_inner.append(&vol_row.0);
|
panel_inner.append(&vol_row.0);
|
||||||
|
|
||||||
// Brightness row
|
// Brightness row
|
||||||
let bright_row = build_slider_row("☀", 0.0, 1.0, 0.02);
|
let bright_row = build_slider_row(bar::stats::ICON_BRIGHTNESS, 0.0, 1.0, 0.02);
|
||||||
let panel_bright_slider = bright_row.1.clone();
|
let panel_bright_slider = bright_row.1.clone();
|
||||||
panel_inner.append(&bright_row.0);
|
panel_inner.append(&bright_row.0);
|
||||||
|
|
||||||
|
|
@ -331,6 +444,14 @@ impl SimpleComponent for App {
|
||||||
panel_cpu_lbl.add_css_class("control-panel-stat");
|
panel_cpu_lbl.add_css_class("control-panel-stat");
|
||||||
panel_cpu_lbl.set_xalign(0.0);
|
panel_cpu_lbl.set_xalign(0.0);
|
||||||
|
|
||||||
|
let panel_mem_lbl = gtk4::Label::new(Some("RAM —"));
|
||||||
|
panel_mem_lbl.add_css_class("control-panel-stat");
|
||||||
|
panel_mem_lbl.set_xalign(0.0);
|
||||||
|
|
||||||
|
let panel_pwr_lbl = gtk4::Label::new(Some("PWR —"));
|
||||||
|
panel_pwr_lbl.add_css_class("control-panel-stat");
|
||||||
|
panel_pwr_lbl.set_xalign(0.0);
|
||||||
|
|
||||||
let panel_gpu_lbl = gtk4::Label::new(Some("GPU —"));
|
let panel_gpu_lbl = gtk4::Label::new(Some("GPU —"));
|
||||||
panel_gpu_lbl.add_css_class("control-panel-stat");
|
panel_gpu_lbl.add_css_class("control-panel-stat");
|
||||||
panel_gpu_lbl.set_xalign(0.0);
|
panel_gpu_lbl.set_xalign(0.0);
|
||||||
|
|
@ -340,6 +461,8 @@ impl SimpleComponent for App {
|
||||||
panel_net_lbl.set_xalign(0.0);
|
panel_net_lbl.set_xalign(0.0);
|
||||||
|
|
||||||
stats_section.append(&panel_cpu_lbl);
|
stats_section.append(&panel_cpu_lbl);
|
||||||
|
stats_section.append(&panel_mem_lbl);
|
||||||
|
stats_section.append(&panel_pwr_lbl);
|
||||||
stats_section.append(&panel_gpu_lbl);
|
stats_section.append(&panel_gpu_lbl);
|
||||||
stats_section.append(&panel_net_lbl);
|
stats_section.append(&panel_net_lbl);
|
||||||
panel_inner.append(&stats_section);
|
panel_inner.append(&stats_section);
|
||||||
|
|
@ -377,9 +500,14 @@ impl SimpleComponent for App {
|
||||||
tray_box.add_css_class("tray-box");
|
tray_box.add_css_class("tray-box");
|
||||||
tray_section.append(&tray_header);
|
tray_section.append(&tray_header);
|
||||||
tray_section.append(&tray_box);
|
tray_section.append(&tray_box);
|
||||||
|
// Collapsed (along with its separator) until an SNI app actually
|
||||||
|
// registers — an empty "Apps" section heading is dead weight.
|
||||||
|
tray_section.set_visible(false);
|
||||||
panel_inner.append(&tray_section);
|
panel_inner.append(&tray_section);
|
||||||
|
|
||||||
panel_inner.append(>k4::Separator::new(gtk4::Orientation::Horizontal));
|
let tray_sep = gtk4::Separator::new(gtk4::Orientation::Horizontal);
|
||||||
|
tray_sep.set_visible(false);
|
||||||
|
panel_inner.append(&tray_sep);
|
||||||
|
|
||||||
// Power section
|
// Power section
|
||||||
let power_section = gtk4::Box::new(gtk4::Orientation::Vertical, 4);
|
let power_section = gtk4::Box::new(gtk4::Orientation::Vertical, 4);
|
||||||
|
|
@ -391,13 +519,17 @@ impl SimpleComponent for App {
|
||||||
|
|
||||||
let power_row = gtk4::Box::new(gtk4::Orientation::Horizontal, 4);
|
let power_row = gtk4::Box::new(gtk4::Orientation::Horizontal, 4);
|
||||||
power_row.add_css_class("power-row");
|
power_row.add_css_class("power-row");
|
||||||
for (label, cmd) in [
|
for (icon_svg, cmd) in [
|
||||||
("🔒", vec!["hyprlock"]),
|
// breadlock is the ecosystem's own screen locker — hyprlock is
|
||||||
("💤", vec!["systemctl", "suspend"]),
|
// the thing it was built to replace; the bar shouldn't still
|
||||||
("🔄", vec!["systemctl", "reboot"]),
|
// be pointing at it.
|
||||||
("⏻", vec!["systemctl", "poweroff"]),
|
(bar::stats::ICON_LOCK, vec!["breadlock"]),
|
||||||
|
(bar::stats::ICON_SLEEP, vec!["systemctl", "suspend"]),
|
||||||
|
(bar::stats::ICON_RESTART, vec!["systemctl", "reboot"]),
|
||||||
|
(bar::stats::ICON_SHUTDOWN, vec!["systemctl", "poweroff"]),
|
||||||
] {
|
] {
|
||||||
let btn = gtk4::Button::with_label(label);
|
let btn = gtk4::Button::new();
|
||||||
|
btn.set_child(Some(>k4::Image::from_paintable(Some(&svg_texture(icon_svg)))));
|
||||||
btn.add_css_class("flat");
|
btn.add_css_class("flat");
|
||||||
btn.add_css_class("power-btn");
|
btn.add_css_class("power-btn");
|
||||||
btn.connect_clicked(move |_| {
|
btn.connect_clicked(move |_| {
|
||||||
|
|
@ -463,6 +595,11 @@ impl SimpleComponent for App {
|
||||||
button_map: std::collections::HashMap::new(),
|
button_map: std::collections::HashMap::new(),
|
||||||
time_str: bar::clock::current(),
|
time_str: bar::clock::current(),
|
||||||
clock_lbl,
|
clock_lbl,
|
||||||
|
system_stats_box,
|
||||||
|
system_sep,
|
||||||
|
cpu_pair,
|
||||||
|
mem_pair,
|
||||||
|
pwr_pair,
|
||||||
cpu_lbl,
|
cpu_lbl,
|
||||||
mem_lbl,
|
mem_lbl,
|
||||||
pwr_lbl,
|
pwr_lbl,
|
||||||
|
|
@ -475,14 +612,16 @@ impl SimpleComponent for App {
|
||||||
wifi_lbl,
|
wifi_lbl,
|
||||||
wifi_img,
|
wifi_img,
|
||||||
wifi_textures,
|
wifi_textures,
|
||||||
wifi_popover_box,
|
wifi_pane,
|
||||||
crumbs_status: None,
|
crumbs_status: None,
|
||||||
wifi_popover_data: None,
|
wifi_popover_data: None,
|
||||||
wifi_profile: None,
|
wifi_profile: None,
|
||||||
current_ssid: "—".to_string(),
|
current_ssid: "—".to_string(),
|
||||||
|
bt_pane,
|
||||||
|
bt_popover_data: None,
|
||||||
media_widget,
|
media_widget,
|
||||||
media_track_lbl,
|
media_track_lbl,
|
||||||
media_play_btn,
|
media_play_icon,
|
||||||
media_last: None,
|
media_last: None,
|
||||||
media_paused_at: None,
|
media_paused_at: None,
|
||||||
control_popover,
|
control_popover,
|
||||||
|
|
@ -494,8 +633,12 @@ impl SimpleComponent for App {
|
||||||
panel_sink_signal: None,
|
panel_sink_signal: None,
|
||||||
panel_sinks: vec![],
|
panel_sinks: vec![],
|
||||||
panel_cpu_lbl,
|
panel_cpu_lbl,
|
||||||
|
panel_mem_lbl,
|
||||||
|
panel_pwr_lbl,
|
||||||
panel_gpu_lbl,
|
panel_gpu_lbl,
|
||||||
panel_net_lbl,
|
panel_net_lbl,
|
||||||
|
tray_section,
|
||||||
|
tray_sep,
|
||||||
tray_box,
|
tray_box,
|
||||||
tray_items: std::collections::HashMap::new(),
|
tray_items: std::collections::HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
@ -539,6 +682,20 @@ impl SimpleComponent for App {
|
||||||
self.cpu_lbl.set_label(&stats.cpu);
|
self.cpu_lbl.set_label(&stats.cpu);
|
||||||
self.mem_lbl.set_label(&stats.mem);
|
self.mem_lbl.set_label(&stats.mem);
|
||||||
self.pwr_lbl.set_label(&stats.power);
|
self.pwr_lbl.set_label(&stats.power);
|
||||||
|
|
||||||
|
// Bar information diet: CPU/RAM/power draw only surface once
|
||||||
|
// they're actually worth knowing about, individually, so a
|
||||||
|
// hot CPU doesn't drag an idle RAM/power reading along with it.
|
||||||
|
let cpu_hot = stats.cpu_pct > CPU_ATTENTION_THRESHOLD;
|
||||||
|
let mem_hot = stats.mem_pct > MEM_ATTENTION_THRESHOLD;
|
||||||
|
let pwr_hot = stats.power_watts > POWER_ATTENTION_THRESHOLD;
|
||||||
|
self.cpu_pair.set_visible(cpu_hot);
|
||||||
|
self.mem_pair.set_visible(mem_hot);
|
||||||
|
self.pwr_pair.set_visible(pwr_hot);
|
||||||
|
let any_hot = cpu_hot || mem_hot || pwr_hot;
|
||||||
|
self.system_stats_box.set_visible(any_hot);
|
||||||
|
self.system_sep.set_visible(any_hot);
|
||||||
|
|
||||||
self.bat_lbl.set_label(&stats.bat);
|
self.bat_lbl.set_label(&stats.bat);
|
||||||
if let Some(tex) = self.bat_textures.get(&(stats.bat_icon.as_ptr() as usize)) {
|
if let Some(tex) = self.bat_textures.get(&(stats.bat_icon.as_ptr() as usize)) {
|
||||||
self.bat_img.set_paintable(Some(tex));
|
self.bat_img.set_paintable(Some(tex));
|
||||||
|
|
@ -574,6 +731,11 @@ impl SimpleComponent for App {
|
||||||
};
|
};
|
||||||
self.panel_cpu_lbl.set_label(&cpu_str);
|
self.panel_cpu_lbl.set_label(&cpu_str);
|
||||||
|
|
||||||
|
self.panel_mem_lbl
|
||||||
|
.set_label(&format!("RAM {:.0}% {}", stats.mem_pct, stats.mem));
|
||||||
|
self.panel_pwr_lbl
|
||||||
|
.set_label(&format!("PWR {}", stats.power));
|
||||||
|
|
||||||
let gpu_str = match (stats.gpu_usage, stats.gpu_temp) {
|
let gpu_str = match (stats.gpu_usage, stats.gpu_temp) {
|
||||||
(Some(u), Some(t)) => format!("GPU {u}% {t:.0}°C"),
|
(Some(u), Some(t)) => format!("GPU {u}% {t:.0}°C"),
|
||||||
(Some(u), None) => format!("GPU {u}%"),
|
(Some(u), None) => format!("GPU {u}%"),
|
||||||
|
|
@ -603,19 +765,34 @@ impl SimpleComponent for App {
|
||||||
btn.connect_clicked(move |_| bar::tray::spawn_activate(id_click.clone()));
|
btn.connect_clicked(move |_| bar::tray::spawn_activate(id_click.clone()));
|
||||||
self.tray_box.append(&btn);
|
self.tray_box.append(&btn);
|
||||||
self.tray_items.insert(id, btn);
|
self.tray_items.insert(id, btn);
|
||||||
|
self.tray_section.set_visible(true);
|
||||||
|
self.tray_sep.set_visible(true);
|
||||||
}
|
}
|
||||||
AppInput::TrayUpdate(bar::tray::TrayUpdate::Remove { id }) => {
|
AppInput::TrayUpdate(bar::tray::TrayUpdate::Remove { id }) => {
|
||||||
if let Some(btn) = self.tray_items.remove(&id) {
|
if let Some(btn) = self.tray_items.remove(&id) {
|
||||||
self.tray_box.remove(&btn);
|
self.tray_box.remove(&btn);
|
||||||
}
|
}
|
||||||
|
let has_items = !self.tray_items.is_empty();
|
||||||
|
self.tray_section.set_visible(has_items);
|
||||||
|
self.tray_sep.set_visible(has_items);
|
||||||
}
|
}
|
||||||
AppInput::CrumbsStatus(status) => {
|
AppInput::CrumbsStatus(status) => {
|
||||||
self.crumbs_status = Some(status);
|
self.crumbs_status = Some(status);
|
||||||
|
// CrumbsStatus and WifiPopoverData arrive independently (different
|
||||||
|
// pollers); whichever lands second must still repaint the header —
|
||||||
|
// otherwise it only shows up if CrumbsStatus happens to win the race.
|
||||||
|
if self.wifi_popover_data.is_some() {
|
||||||
|
self.rebuild_wifi_popover(&sender);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AppInput::WifiPopoverData(data) => {
|
AppInput::WifiPopoverData(data) => {
|
||||||
self.wifi_popover_data = Some(data);
|
self.wifi_popover_data = Some(data);
|
||||||
self.rebuild_wifi_popover(&sender);
|
self.rebuild_wifi_popover(&sender);
|
||||||
}
|
}
|
||||||
|
AppInput::BtPopoverData(data) => {
|
||||||
|
self.bt_popover_data = Some(data);
|
||||||
|
self.rebuild_bt_popover();
|
||||||
|
}
|
||||||
AppInput::SetProfile(name) => {
|
AppInput::SetProfile(name) => {
|
||||||
self.wifi_profile = Some(name);
|
self.wifi_profile = Some(name);
|
||||||
self.apply_wifi_label();
|
self.apply_wifi_label();
|
||||||
|
|
@ -628,8 +805,12 @@ impl SimpleComponent for App {
|
||||||
format!("{} · {}", state.artist, state.title)
|
format!("{} · {}", state.artist, state.title)
|
||||||
};
|
};
|
||||||
self.media_track_lbl.set_label(&label);
|
self.media_track_lbl.set_label(&label);
|
||||||
self.media_play_btn
|
let icon_svg = if state.playing {
|
||||||
.set_label(if state.playing { "⏸" } else { "▶" });
|
asset!("Pause.svg")
|
||||||
|
} else {
|
||||||
|
asset!("Play.svg")
|
||||||
|
};
|
||||||
|
self.media_play_icon.set_paintable(Some(&svg_texture(icon_svg)));
|
||||||
|
|
||||||
if state.playing {
|
if state.playing {
|
||||||
self.media_paused_at = None;
|
self.media_paused_at = None;
|
||||||
|
|
@ -717,8 +898,8 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rebuild_wifi_popover(&mut self, sender: &ComponentSender<Self>) {
|
fn rebuild_wifi_popover(&mut self, sender: &ComponentSender<Self>) {
|
||||||
while let Some(child) = self.wifi_popover_box.first_child() {
|
while let Some(child) = self.wifi_pane.first_child() {
|
||||||
self.wifi_popover_box.remove(&child);
|
self.wifi_pane.remove(&child);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(st) = &self.crumbs_status {
|
if let Some(st) = &self.crumbs_status {
|
||||||
|
|
@ -755,15 +936,15 @@ impl App {
|
||||||
status_lbl.set_xalign(0.0);
|
status_lbl.set_xalign(0.0);
|
||||||
header.append(&status_lbl);
|
header.append(&status_lbl);
|
||||||
|
|
||||||
self.wifi_popover_box.append(&header);
|
self.wifi_pane.append(&header);
|
||||||
self.wifi_popover_box
|
self.wifi_pane
|
||||||
.append(>k4::Separator::new(gtk4::Orientation::Horizontal));
|
.append(>k4::Separator::new(gtk4::Orientation::Horizontal));
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(data) = &self.wifi_popover_data else {
|
let Some(data) = &self.wifi_popover_data else {
|
||||||
let lbl = gtk4::Label::new(Some("Scanning…"));
|
let lbl = gtk4::Label::new(Some("Scanning…"));
|
||||||
lbl.add_css_class("wifi-popover-loading");
|
lbl.add_css_class("wifi-popover-loading");
|
||||||
self.wifi_popover_box.append(&lbl);
|
self.wifi_pane.append(&lbl);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -772,7 +953,7 @@ impl App {
|
||||||
ph.set_xalign(0.0);
|
ph.set_xalign(0.0);
|
||||||
ph.set_margin_top(6);
|
ph.set_margin_top(6);
|
||||||
ph.set_margin_bottom(2);
|
ph.set_margin_bottom(2);
|
||||||
self.wifi_popover_box.append(&ph);
|
self.wifi_pane.append(&ph);
|
||||||
|
|
||||||
for (name, active) in &data.profiles {
|
for (name, active) in &data.profiles {
|
||||||
let row = gtk4::Button::new();
|
let row = gtk4::Button::new();
|
||||||
|
|
@ -796,18 +977,18 @@ impl App {
|
||||||
bar::wifi::spawn_profile_set(name_clone.clone());
|
bar::wifi::spawn_profile_set(name_clone.clone());
|
||||||
close_parent_popover(btn);
|
close_parent_popover(btn);
|
||||||
});
|
});
|
||||||
self.wifi_popover_box.append(&row);
|
self.wifi_pane.append(&row);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.scan.is_empty() {
|
if !data.scan.is_empty() {
|
||||||
self.wifi_popover_box
|
self.wifi_pane
|
||||||
.append(>k4::Separator::new(gtk4::Orientation::Horizontal));
|
.append(>k4::Separator::new(gtk4::Orientation::Horizontal));
|
||||||
let nh = gtk4::Label::new(Some("Nearby"));
|
let nh = gtk4::Label::new(Some("Nearby"));
|
||||||
nh.add_css_class("wifi-popover-section");
|
nh.add_css_class("wifi-popover-section");
|
||||||
nh.set_xalign(0.0);
|
nh.set_xalign(0.0);
|
||||||
nh.set_margin_top(6);
|
nh.set_margin_top(6);
|
||||||
nh.set_margin_bottom(2);
|
nh.set_margin_bottom(2);
|
||||||
self.wifi_popover_box.append(&nh);
|
self.wifi_pane.append(&nh);
|
||||||
|
|
||||||
for entry in &data.scan {
|
for entry in &data.scan {
|
||||||
let row = gtk4::Button::new();
|
let row = gtk4::Button::new();
|
||||||
|
|
@ -847,25 +1028,120 @@ impl App {
|
||||||
}
|
}
|
||||||
close_parent_popover(btn);
|
close_parent_popover(btn);
|
||||||
});
|
});
|
||||||
self.wifi_popover_box.append(&row);
|
self.wifi_pane.append(&row);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.wifi_popover_box.set_visible(true);
|
fn rebuild_bt_popover(&mut self) {
|
||||||
|
while let Some(child) = self.bt_pane.first_child() {
|
||||||
|
self.bt_pane.remove(&child);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(data) = &self.bt_popover_data else {
|
||||||
|
let lbl = gtk4::Label::new(Some("Loading…"));
|
||||||
|
lbl.add_css_class("wifi-popover-loading");
|
||||||
|
self.bt_pane.append(&lbl);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Power toggle row
|
||||||
|
let toggle_row = gtk4::Box::new(gtk4::Orientation::Horizontal, 8);
|
||||||
|
toggle_row.set_margin_bottom(4);
|
||||||
|
let toggle_lbl = gtk4::Label::new(Some("Bluetooth"));
|
||||||
|
toggle_lbl.add_css_class("wifi-popover-ssid");
|
||||||
|
toggle_lbl.set_hexpand(true);
|
||||||
|
toggle_lbl.set_xalign(0.0);
|
||||||
|
let toggle_switch = gtk4::Switch::new();
|
||||||
|
toggle_switch.set_active(data.powered);
|
||||||
|
toggle_switch.set_valign(gtk4::Align::Center);
|
||||||
|
toggle_switch.connect_state_set(|_, on| {
|
||||||
|
bar::bluetooth::spawn_set_powered(on);
|
||||||
|
gtk4::glib::Propagation::Proceed
|
||||||
|
});
|
||||||
|
toggle_row.append(&toggle_lbl);
|
||||||
|
toggle_row.append(&toggle_switch);
|
||||||
|
self.bt_pane.append(&toggle_row);
|
||||||
|
self.bt_pane
|
||||||
|
.append(>k4::Separator::new(gtk4::Orientation::Horizontal));
|
||||||
|
|
||||||
|
if !data.powered {
|
||||||
|
let lbl = gtk4::Label::new(Some("Bluetooth is off"));
|
||||||
|
lbl.add_css_class("wifi-popover-loading");
|
||||||
|
self.bt_pane.append(&lbl);
|
||||||
|
} else if data.devices.is_empty() {
|
||||||
|
let lbl = gtk4::Label::new(Some("No paired devices"));
|
||||||
|
lbl.add_css_class("wifi-popover-loading");
|
||||||
|
self.bt_pane.append(&lbl);
|
||||||
|
} else {
|
||||||
|
let dh = gtk4::Label::new(Some("Paired"));
|
||||||
|
dh.add_css_class("wifi-popover-section");
|
||||||
|
dh.set_xalign(0.0);
|
||||||
|
dh.set_margin_top(2);
|
||||||
|
dh.set_margin_bottom(2);
|
||||||
|
self.bt_pane.append(&dh);
|
||||||
|
|
||||||
|
for dev in &data.devices {
|
||||||
|
let row = gtk4::Button::new();
|
||||||
|
row.add_css_class("flat");
|
||||||
|
row.add_css_class("wifi-popover-row");
|
||||||
|
if dev.connected {
|
||||||
|
row.add_css_class("wifi-popover-row-active");
|
||||||
|
}
|
||||||
|
let lbl = gtk4::Label::new(Some(&format!(
|
||||||
|
"{}{}",
|
||||||
|
if dev.connected { "● " } else { " " },
|
||||||
|
dev.name,
|
||||||
|
)));
|
||||||
|
lbl.set_xalign(0.0);
|
||||||
|
row.set_child(Some(&lbl));
|
||||||
|
|
||||||
|
let address = dev.address.clone();
|
||||||
|
let connected = dev.connected;
|
||||||
|
row.connect_clicked(move |_| {
|
||||||
|
if connected {
|
||||||
|
bar::bluetooth::spawn_disconnect(address.clone());
|
||||||
|
} else {
|
||||||
|
bar::bluetooth::spawn_connect(address.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.bt_pane.append(&row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bt_pane
|
||||||
|
.append(>k4::Separator::new(gtk4::Orientation::Horizontal));
|
||||||
|
let settings_row = gtk4::Button::new();
|
||||||
|
settings_row.add_css_class("flat");
|
||||||
|
settings_row.add_css_class("wifi-popover-row");
|
||||||
|
let settings_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 4);
|
||||||
|
let settings_icon = gtk4::Image::from_paintable(Some(&svg_texture(bar::stats::ICON_BT_SETTINGS)));
|
||||||
|
settings_icon.add_css_class("stat-icon");
|
||||||
|
settings_box.append(&settings_icon);
|
||||||
|
settings_box.append(>k4::Label::new(Some("Bluetooth settings")));
|
||||||
|
settings_row.set_child(Some(&settings_box));
|
||||||
|
settings_row.connect_clicked(|_| {
|
||||||
|
relm4::spawn(async {
|
||||||
|
let _ = tokio::process::Command::new("blueman-manager").spawn();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
self.bt_pane.append(&settings_row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn build_slider_row(icon: &str, min: f64, max: f64, step: f64) -> (gtk4::Box, gtk4::Scale) {
|
fn build_slider_row(icon_svg: &str, min: f64, max: f64, step: f64) -> (gtk4::Box, gtk4::Scale) {
|
||||||
let row = gtk4::Box::new(gtk4::Orientation::Horizontal, 8);
|
let row = gtk4::Box::new(gtk4::Orientation::Horizontal, 8);
|
||||||
row.add_css_class("control-panel-row");
|
row.add_css_class("control-panel-row");
|
||||||
row.set_margin_top(2);
|
row.set_margin_top(2);
|
||||||
row.set_margin_bottom(2);
|
row.set_margin_bottom(2);
|
||||||
|
|
||||||
let icon_lbl = gtk4::Label::new(Some(icon));
|
// Rendered larger than the standard 16px stat/section icons: these are
|
||||||
icon_lbl.add_css_class("control-panel-row-icon");
|
// the two things in the panel you actually drag, so they should read as
|
||||||
icon_lbl.set_width_chars(2);
|
// primary controls rather than blend in with passive readouts.
|
||||||
|
let icon = gtk4::Image::from_paintable(Some(&svg_texture_sized(icon_svg, 20)));
|
||||||
|
icon.add_css_class("control-panel-row-icon");
|
||||||
|
|
||||||
let slider = gtk4::Scale::with_range(gtk4::Orientation::Horizontal, min, max, step);
|
let slider = gtk4::Scale::with_range(gtk4::Orientation::Horizontal, min, max, step);
|
||||||
slider.set_draw_value(false);
|
slider.set_draw_value(false);
|
||||||
|
|
@ -873,7 +1149,7 @@ fn build_slider_row(icon: &str, min: f64, max: f64, step: f64) -> (gtk4::Box, gt
|
||||||
slider.set_width_request(180);
|
slider.set_width_request(180);
|
||||||
slider.add_css_class("control-panel-slider");
|
slider.add_css_class("control-panel-slider");
|
||||||
|
|
||||||
row.append(&icon_lbl);
|
row.append(&icon);
|
||||||
row.append(&slider);
|
row.append(&slider);
|
||||||
(row, slider)
|
(row, slider)
|
||||||
}
|
}
|
||||||
|
|
@ -984,12 +1260,21 @@ fn stat_pair(icon_svg: &str, label: >k4::Label) -> gtk4::Box {
|
||||||
pair
|
pair
|
||||||
}
|
}
|
||||||
|
|
||||||
fn svg_texture(svg_src: &str) -> gtk4::gdk::Texture {
|
pub(crate) fn svg_texture(svg_src: &str) -> gtk4::gdk::Texture {
|
||||||
|
svg_texture_sized(svg_src, 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `svg_texture` but rendered at an explicit pixel size — used to give
|
||||||
|
/// primary/interactive icons (e.g. sliders you actually drag) more visual
|
||||||
|
/// weight than passive informational ones, which otherwise all read as the
|
||||||
|
/// same flat 16px stroke glyph once emoji stopped providing accidental variety.
|
||||||
|
pub(crate) fn svg_texture_sized(svg_src: &str, px: u32) -> gtk4::gdk::Texture {
|
||||||
use resvg::{tiny_skia, usvg};
|
use resvg::{tiny_skia, usvg};
|
||||||
let fg = theme::fg_color();
|
let fg = theme::fg_color();
|
||||||
|
let dim = format!(r#"width="{px}" height="{px}""#);
|
||||||
let svg = svg_src
|
let svg = svg_src
|
||||||
.replace("currentColor", &fg)
|
.replace("currentColor", &fg)
|
||||||
.replace(r#"width="24" height="24""#, r#"width="16" height="16""#);
|
.replace(r#"width="24" height="24""#, &dim);
|
||||||
let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).expect("parse svg");
|
let tree = usvg::Tree::from_str(&svg, &usvg::Options::default()).expect("parse svg");
|
||||||
let size = tree.size().to_int_size();
|
let size = tree.size().to_int_size();
|
||||||
let (w, h) = (size.width(), size.height());
|
let (w, h) = (size.width(), size.height());
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,81 @@
|
||||||
pub mod popup;
|
pub mod popup;
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
use std::time::Duration;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use zbus::zvariant::OwnedValue;
|
use zbus::zvariant::OwnedValue;
|
||||||
|
|
||||||
|
/// How long a shown notification should stay up before auto-dismissing.
|
||||||
|
/// Distinct from `Option<Duration>` mainly for readability at call sites —
|
||||||
|
/// `Never` covers both the spec's `expire_timeout == 0` ("never expire")
|
||||||
|
/// and a critical-urgency notification with no explicit timeout, which
|
||||||
|
/// conventionally shouldn't auto-dismiss either.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Expire {
|
||||||
|
Never,
|
||||||
|
After(Duration),
|
||||||
|
}
|
||||||
|
|
||||||
pub enum NotifEvent {
|
pub enum NotifEvent {
|
||||||
Show {
|
Show {
|
||||||
id: u32,
|
id: u32,
|
||||||
app_name: String,
|
app_name: String,
|
||||||
summary: String,
|
summary: String,
|
||||||
body: String,
|
body: String,
|
||||||
timeout_ms: u32,
|
urgency: Urgency,
|
||||||
|
expire: Expire,
|
||||||
},
|
},
|
||||||
Close(u32),
|
Close(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Urgency {
|
||||||
|
Low,
|
||||||
|
Normal,
|
||||||
|
Critical,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Urgency {
|
||||||
|
/// Spec: hints["urgency"] is a byte, 0=low, 1=normal, 2=critical (default normal).
|
||||||
|
fn from_hint(hint: Option<&OwnedValue>) -> Self {
|
||||||
|
match hint.and_then(|v| u8::try_from(v.clone()).ok()) {
|
||||||
|
Some(0) => Urgency::Low,
|
||||||
|
Some(2) => Urgency::Critical,
|
||||||
|
_ => Urgency::Normal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CSS class suffix for `.notification-card.urgency-<kind>`.
|
||||||
|
pub fn css_class(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
Urgency::Low => None,
|
||||||
|
Urgency::Normal => Some("urgency-normal"),
|
||||||
|
Urgency::Critical => Some("urgency-critical"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps a `Notify` call's `expire_timeout` (plus whether the `urgency` hint
|
||||||
|
/// was critical) to our internal `Expire`, per the freedesktop notification
|
||||||
|
/// spec: `0` always means never expire; a negative value means "server
|
||||||
|
/// picks a default" (5s here, except critical notifications, which
|
||||||
|
/// conventionally persist); any non-negative value is taken literally.
|
||||||
|
/// Pulled out of `NotifServer::notify` so this mapping is unit-testable
|
||||||
|
/// without a live D-Bus connection.
|
||||||
|
fn compute_expire(expire_timeout: i32, urgency_critical: bool) -> Expire {
|
||||||
|
match expire_timeout {
|
||||||
|
0 => Expire::Never,
|
||||||
|
t if t < 0 => {
|
||||||
|
if urgency_critical {
|
||||||
|
Expire::Never
|
||||||
|
} else {
|
||||||
|
Expire::After(Duration::from_millis(5000))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t => Expire::After(Duration::from_millis(t as u64)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct NotifServer {
|
struct NotifServer {
|
||||||
tx: mpsc::Sender<NotifEvent>,
|
tx: mpsc::Sender<NotifEvent>,
|
||||||
next_id: AtomicU32,
|
next_id: AtomicU32,
|
||||||
|
|
@ -32,7 +93,7 @@ impl NotifServer {
|
||||||
summary: &str,
|
summary: &str,
|
||||||
body: &str,
|
body: &str,
|
||||||
_actions: Vec<String>,
|
_actions: Vec<String>,
|
||||||
_hints: std::collections::HashMap<String, OwnedValue>,
|
hints: std::collections::HashMap<String, OwnedValue>,
|
||||||
expire_timeout: i32,
|
expire_timeout: i32,
|
||||||
) -> u32 {
|
) -> u32 {
|
||||||
let id = if replaces_id != 0 {
|
let id = if replaces_id != 0 {
|
||||||
|
|
@ -40,11 +101,15 @@ impl NotifServer {
|
||||||
} else {
|
} else {
|
||||||
self.next_id.fetch_add(1, Ordering::Relaxed)
|
self.next_id.fetch_add(1, Ordering::Relaxed)
|
||||||
};
|
};
|
||||||
let timeout_ms = if expire_timeout <= 0 {
|
// Per spec: 0 means "never expire" — this used to be lumped in
|
||||||
5000
|
// with "-1: let the server pick a default" and coerced to a fixed
|
||||||
} else {
|
// 5s, so a sender explicitly asking for a persistent notification
|
||||||
expire_timeout as u32
|
// (e.g. a progress/error dialog) got auto-dismissed anyway.
|
||||||
};
|
// Critical-urgency notifications conventionally persist too, even
|
||||||
|
// when the sender left expire_timeout at the server-default (-1).
|
||||||
|
let urgency = Urgency::from_hint(hints.get("urgency"));
|
||||||
|
let expire = compute_expire(expire_timeout, urgency == Urgency::Critical);
|
||||||
|
|
||||||
let _ = self
|
let _ = self
|
||||||
.tx
|
.tx
|
||||||
.send(NotifEvent::Show {
|
.send(NotifEvent::Show {
|
||||||
|
|
@ -52,7 +117,8 @@ impl NotifServer {
|
||||||
app_name: app_name.to_string(),
|
app_name: app_name.to_string(),
|
||||||
summary: summary.to_string(),
|
summary: summary.to_string(),
|
||||||
body: body.to_string(),
|
body: body.to_string(),
|
||||||
timeout_ms,
|
urgency,
|
||||||
|
expire,
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
id
|
id
|
||||||
|
|
@ -78,6 +144,7 @@ impl NotifServer {
|
||||||
|
|
||||||
pub fn spawn() {
|
pub fn spawn() {
|
||||||
let (tx, rx) = mpsc::channel(32);
|
let (tx, rx) = mpsc::channel(32);
|
||||||
|
let (conn_tx, conn_rx) = tokio::sync::oneshot::channel();
|
||||||
|
|
||||||
relm4::spawn(async move {
|
relm4::spawn(async move {
|
||||||
let server = NotifServer {
|
let server = NotifServer {
|
||||||
|
|
@ -85,7 +152,7 @@ pub fn spawn() {
|
||||||
next_id: AtomicU32::new(1),
|
next_id: AtomicU32::new(1),
|
||||||
};
|
};
|
||||||
// Builder failures here would only occur with invalid static strings — safe to unwrap.
|
// Builder failures here would only occur with invalid static strings — safe to unwrap.
|
||||||
let _conn = zbus::connection::Builder::session()
|
let conn = zbus::connection::Builder::session()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.name("org.freedesktop.Notifications")
|
.name("org.freedesktop.Notifications")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -94,8 +161,55 @@ pub fn spawn() {
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
.expect("failed to claim org.freedesktop.Notifications on D-Bus session bus");
|
.expect("failed to claim org.freedesktop.Notifications on D-Bus session bus");
|
||||||
|
// Hand the connection to popup::run so it can emit `NotificationClosed`
|
||||||
|
// (spec-mandated whenever a notification actually goes away) — the
|
||||||
|
// dismiss decisions all happen over there, not in this interface impl.
|
||||||
|
let _ = conn_tx.send(conn);
|
||||||
std::future::pending::<()>().await
|
std::future::pending::<()>().await
|
||||||
});
|
});
|
||||||
|
|
||||||
relm4::spawn_local(popup::run(rx));
|
relm4::spawn_local(async move {
|
||||||
|
if let Ok(conn) = conn_rx.await {
|
||||||
|
popup::run(rx, conn).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zero_timeout_never_expires_regardless_of_urgency() {
|
||||||
|
assert!(matches!(compute_expire(0, false), Expire::Never));
|
||||||
|
assert!(matches!(compute_expire(0, true), Expire::Never));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn negative_timeout_defaults_to_five_seconds_for_normal_urgency() {
|
||||||
|
match compute_expire(-1, false) {
|
||||||
|
Expire::After(d) => assert_eq!(d, Duration::from_millis(5000)),
|
||||||
|
Expire::Never => panic!("expected a 5s default, got Never"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn negative_timeout_persists_for_critical_urgency() {
|
||||||
|
assert!(matches!(compute_expire(-1, true), Expire::Never));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn positive_timeout_is_taken_literally() {
|
||||||
|
match compute_expire(1500, false) {
|
||||||
|
Expire::After(d) => assert_eq!(d, Duration::from_millis(1500)),
|
||||||
|
Expire::Never => panic!("expected 1500ms, got Never"),
|
||||||
|
}
|
||||||
|
// Even for critical urgency, an explicit positive timeout is honored
|
||||||
|
// rather than overridden to Never — "critical persists" is only the
|
||||||
|
// *default* when the sender didn't specify one.
|
||||||
|
match compute_expire(1500, true) {
|
||||||
|
Expire::After(d) => assert_eq!(d, Duration::from_millis(1500)),
|
||||||
|
Expire::Never => panic!("expected 1500ms, got Never"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,29 @@
|
||||||
use std::{cell::RefCell, collections::HashMap, rc::Rc, time::Duration};
|
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
||||||
use tokio::sync::mpsc::Receiver;
|
use tokio::sync::mpsc::Receiver;
|
||||||
|
|
||||||
use super::NotifEvent;
|
use super::{Expire, NotifEvent, Urgency};
|
||||||
|
|
||||||
type Cards = Rc<RefCell<HashMap<u32, gtk4::Box>>>;
|
type Cards = Rc<RefCell<HashMap<u32, gtk4::Box>>>;
|
||||||
|
// Bumped every time an id gets a (re)placed card — an auto-dismiss timer
|
||||||
|
// scheduled for an earlier Show captures the generation it was scheduled
|
||||||
|
// under, and checks it's still current before dismissing. Without this, a
|
||||||
|
// notification that replaces an existing id (replaces_id) doesn't cancel
|
||||||
|
// the original's timer, so the *replacement* card gets dismissed on the
|
||||||
|
// *original*'s deadline instead of its own.
|
||||||
|
type Generations = Rc<RefCell<HashMap<u32, u64>>>;
|
||||||
|
|
||||||
pub async fn run(mut rx: Receiver<NotifEvent>) {
|
/// NotificationClosed reason codes per the freedesktop spec.
|
||||||
|
mod close_reason {
|
||||||
|
pub const EXPIRED: u32 = 1;
|
||||||
|
#[allow(dead_code)] // no in-app dismiss button exists yet (see make_card)
|
||||||
|
pub const DISMISSED_BY_USER: u32 = 2;
|
||||||
|
pub const CLOSE_NOTIFICATION_CALL: u32 = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(mut rx: Receiver<NotifEvent>, conn: zbus::Connection) {
|
||||||
let window = create_window();
|
let window = create_window();
|
||||||
let cards_box = gtk4::Box::new(gtk4::Orientation::Vertical, 4);
|
let cards_box = gtk4::Box::new(gtk4::Orientation::Vertical, 4);
|
||||||
cards_box.set_margin_top(8);
|
cards_box.set_margin_top(8);
|
||||||
|
|
@ -18,6 +33,7 @@ pub async fn run(mut rx: Receiver<NotifEvent>) {
|
||||||
window.set_child(Some(&cards_box));
|
window.set_child(Some(&cards_box));
|
||||||
|
|
||||||
let cards: Cards = Rc::new(RefCell::new(HashMap::new()));
|
let cards: Cards = Rc::new(RefCell::new(HashMap::new()));
|
||||||
|
let generations: Generations = Rc::new(RefCell::new(HashMap::new()));
|
||||||
|
|
||||||
while let Some(event) = rx.recv().await {
|
while let Some(event) = rx.recv().await {
|
||||||
match event {
|
match event {
|
||||||
|
|
@ -26,40 +42,87 @@ pub async fn run(mut rx: Receiver<NotifEvent>) {
|
||||||
app_name,
|
app_name,
|
||||||
summary,
|
summary,
|
||||||
body,
|
body,
|
||||||
timeout_ms,
|
urgency,
|
||||||
|
expire,
|
||||||
} => {
|
} => {
|
||||||
// Replace existing card with same id (replaces_id case)
|
// Replace existing card with same id (replaces_id case)
|
||||||
if let Some(old) = cards.borrow_mut().remove(&id) {
|
if let Some(old) = cards.borrow_mut().remove(&id) {
|
||||||
cards_box.remove(&old);
|
cards_box.remove(&old);
|
||||||
}
|
}
|
||||||
let card = make_card(&app_name, &summary, &body);
|
let card = make_card(&app_name, &summary, &body, urgency);
|
||||||
cards_box.prepend(&card);
|
cards_box.prepend(&card);
|
||||||
cards.borrow_mut().insert(id, card.clone());
|
cards.borrow_mut().insert(id, card.clone());
|
||||||
window.set_visible(true);
|
window.set_visible(true);
|
||||||
|
|
||||||
// Auto-dismiss via GLib-native timer (safe inside spawn_local)
|
let my_generation = {
|
||||||
|
let mut gens = generations.borrow_mut();
|
||||||
|
let g = gens.entry(id).or_insert(0);
|
||||||
|
*g += 1;
|
||||||
|
*g
|
||||||
|
};
|
||||||
|
|
||||||
|
// `Expire::Never` (expire_timeout=0, or a critical-urgency
|
||||||
|
// notification with no explicit timeout) schedules no timer
|
||||||
|
// at all — it persists until an explicit CloseNotification.
|
||||||
|
if let Expire::After(duration) = expire {
|
||||||
let cards_clone = cards.clone();
|
let cards_clone = cards.clone();
|
||||||
let cards_box_clone = cards_box.clone();
|
let cards_box_clone = cards_box.clone();
|
||||||
let win_clone = window.clone();
|
let win_clone = window.clone();
|
||||||
|
let generations_clone = generations.clone();
|
||||||
|
let conn_clone = conn.clone();
|
||||||
relm4::spawn_local(async move {
|
relm4::spawn_local(async move {
|
||||||
gtk4::glib::timeout_future(Duration::from_millis(timeout_ms as u64)).await;
|
gtk4::glib::timeout_future(duration).await;
|
||||||
dismiss(&cards_box_clone, &win_clone, &cards_clone, id);
|
let still_current =
|
||||||
|
generations_clone.borrow().get(&id) == Some(&my_generation);
|
||||||
|
if still_current
|
||||||
|
&& dismiss(&cards_box_clone, &win_clone, &cards_clone, id)
|
||||||
|
{
|
||||||
|
emit_closed(&conn_clone, id, close_reason::EXPIRED).await;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
NotifEvent::Close(id) => {
|
NotifEvent::Close(id) => {
|
||||||
dismiss(&cards_box, &window, &cards, id);
|
if dismiss(&cards_box, &window, &cards, id) {
|
||||||
|
emit_closed(&conn, id, close_reason::CLOSE_NOTIFICATION_CALL).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismiss(cards_box: >k4::Box, window: >k4::Window, cards: &Cards, id: u32) {
|
/// Removes `id`'s card if present. Returns whether a card was actually
|
||||||
if let Some(card) = cards.borrow_mut().remove(&id) {
|
/// removed, so callers only emit `NotificationClosed` for a real dismissal
|
||||||
|
/// (not a no-op on an id that's already gone or was never shown).
|
||||||
|
fn dismiss(cards_box: >k4::Box, window: >k4::Window, cards: &Cards, id: u32) -> bool {
|
||||||
|
let removed = cards.borrow_mut().remove(&id);
|
||||||
|
let Some(card) = removed else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
cards_box.remove(&card);
|
cards_box.remove(&card);
|
||||||
}
|
|
||||||
if cards.borrow().is_empty() {
|
if cards.borrow().is_empty() {
|
||||||
window.set_visible(false);
|
window.set_visible(false);
|
||||||
}
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits the spec-mandated `NotificationClosed(id, reason)` signal. Sent
|
||||||
|
/// directly over the connection rather than through the zbus interface
|
||||||
|
/// macro's generated helper, since the dismiss decision happens here in the
|
||||||
|
/// popup task, not inside `NotifServer`'s own method bodies.
|
||||||
|
async fn emit_closed(conn: &zbus::Connection, id: u32, reason: u32) {
|
||||||
|
let result = conn
|
||||||
|
.emit_signal(
|
||||||
|
None::<&str>,
|
||||||
|
"/org/freedesktop/Notifications",
|
||||||
|
"org.freedesktop.Notifications",
|
||||||
|
"NotificationClosed",
|
||||||
|
&(id, reason),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
if let Err(e) = result {
|
||||||
|
eprintln!("breadbar: failed to emit NotificationClosed for {id}: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_window() -> gtk4::Window {
|
fn create_window() -> gtk4::Window {
|
||||||
|
|
@ -75,11 +138,17 @@ fn create_window() -> gtk4::Window {
|
||||||
window
|
window
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_card(app_name: &str, summary: &str, body: &str) -> gtk4::Box {
|
fn make_card(app_name: &str, summary: &str, body: &str, urgency: Urgency) -> gtk4::Box {
|
||||||
let card = gtk4::Box::new(gtk4::Orientation::Vertical, 4);
|
let card = gtk4::Box::new(gtk4::Orientation::Vertical, 4);
|
||||||
card.add_css_class("notification-card");
|
card.add_css_class("notification-card");
|
||||||
|
if let Some(class) = urgency.css_class() {
|
||||||
|
card.add_css_class(class);
|
||||||
|
}
|
||||||
|
|
||||||
if !app_name.is_empty() {
|
// Senders often set the title/summary to their own app name (e.g. a bare
|
||||||
|
// "Spotify" notification) — showing app_name above an identical summary
|
||||||
|
// is pure repetition, so skip the app label in that case.
|
||||||
|
if !app_name.is_empty() && !app_name.eq_ignore_ascii_case(summary) {
|
||||||
let lbl = gtk4::Label::new(Some(app_name));
|
let lbl = gtk4::Label::new(Some(app_name));
|
||||||
lbl.add_css_class("notification-app");
|
lbl.add_css_class("notification-app");
|
||||||
lbl.set_xalign(0.0);
|
lbl.set_xalign(0.0);
|
||||||
|
|
|
||||||
45
src/osd.rs
|
|
@ -122,40 +122,39 @@ fn brightness_watcher(tx: mpsc::Sender<OsdEvent>) {
|
||||||
async fn run_osd(mut rx: mpsc::Receiver<OsdEvent>) {
|
async fn run_osd(mut rx: mpsc::Receiver<OsdEvent>) {
|
||||||
let window = create_window();
|
let window = create_window();
|
||||||
|
|
||||||
let container = gtk4::Box::new(gtk4::Orientation::Vertical, 6);
|
let container = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
||||||
container.set_margin_top(12);
|
container.set_margin_top(10);
|
||||||
container.set_margin_bottom(12);
|
container.set_margin_bottom(10);
|
||||||
container.set_margin_start(16);
|
container.set_margin_start(14);
|
||||||
container.set_margin_end(16);
|
container.set_margin_end(14);
|
||||||
window.set_child(Some(&container));
|
window.set_child(Some(&container));
|
||||||
|
|
||||||
let header = gtk4::Box::new(gtk4::Orientation::Horizontal, 0);
|
let icon = gtk4::Image::from_paintable(Some(&crate::svg_texture(
|
||||||
let kind_lbl = gtk4::Label::new(Some("Volume"));
|
crate::bar::stats::ICON_VOLUME,
|
||||||
kind_lbl.add_css_class("osd-kind");
|
)));
|
||||||
kind_lbl.set_hexpand(true);
|
icon.add_css_class("osd-icon");
|
||||||
kind_lbl.set_xalign(0.0);
|
container.append(&icon);
|
||||||
let pct_lbl = gtk4::Label::new(Some("0%"));
|
|
||||||
pct_lbl.add_css_class("osd-pct");
|
|
||||||
header.append(&kind_lbl);
|
|
||||||
header.append(&pct_lbl);
|
|
||||||
container.append(&header);
|
|
||||||
|
|
||||||
let pbar = gtk4::ProgressBar::new();
|
let pbar = gtk4::ProgressBar::new();
|
||||||
pbar.add_css_class("osd-bar");
|
pbar.add_css_class("osd-bar");
|
||||||
|
pbar.set_hexpand(true);
|
||||||
|
pbar.set_valign(gtk4::Align::Center);
|
||||||
container.append(&pbar);
|
container.append(&pbar);
|
||||||
|
|
||||||
let dismiss_token = Rc::new(Cell::new(0u32));
|
let dismiss_token = Rc::new(Cell::new(0u32));
|
||||||
|
|
||||||
while let Some(event) = rx.recv().await {
|
while let Some(event) = rx.recv().await {
|
||||||
let (kind, pct) = match event {
|
let (icon_svg, pct, muted) = match event {
|
||||||
OsdEvent::Volume { pct, muted } => {
|
OsdEvent::Volume { pct, muted } => (crate::bar::stats::ICON_VOLUME, pct, muted),
|
||||||
(if muted { "Volume (Muted)" } else { "Volume" }, pct)
|
OsdEvent::Brightness { pct } => (crate::bar::stats::ICON_BRIGHTNESS, pct, false),
|
||||||
}
|
|
||||||
OsdEvent::Brightness { pct } => ("Brightness", pct),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
kind_lbl.set_label(kind);
|
icon.set_paintable(Some(&crate::svg_texture(icon_svg)));
|
||||||
pct_lbl.set_label(&format!("{pct}%"));
|
if muted {
|
||||||
|
icon.add_css_class("osd-icon-muted");
|
||||||
|
} else {
|
||||||
|
icon.remove_css_class("osd-icon-muted");
|
||||||
|
}
|
||||||
pbar.set_fraction(pct as f64 / 100.0);
|
pbar.set_fraction(pct as f64 / 100.0);
|
||||||
window.set_visible(true);
|
window.set_visible(true);
|
||||||
|
|
||||||
|
|
@ -179,6 +178,6 @@ fn create_window() -> gtk4::Window {
|
||||||
window.set_layer(Layer::Overlay);
|
window.set_layer(Layer::Overlay);
|
||||||
window.set_anchor(Edge::Bottom, true);
|
window.set_anchor(Edge::Bottom, true);
|
||||||
window.set_margin(Edge::Bottom, 80);
|
window.set_margin(Edge::Bottom, 80);
|
||||||
window.set_default_width(280);
|
window.set_default_width(180);
|
||||||
window
|
window
|
||||||
}
|
}
|
||||||
|
|
|
||||||
66
src/theme.rs
|
|
@ -14,71 +14,89 @@ fn load_css() -> String {
|
||||||
// card) and child labels inherit it, so text stays legible whatever lightness
|
// card) and child labels inherit it, so text stays legible whatever lightness
|
||||||
// pywal hands a given slot. `on_*` are luminance-picked ink (black/white) for
|
// pywal hands a given slot. `on_*` are luminance-picked ink (black/white) for
|
||||||
// that background — the pywal hues themselves are untouched.
|
// that background — the pywal hues themselves are untouched.
|
||||||
|
//
|
||||||
|
// Shared tokens: one radius and one padding rhythm reused across every
|
||||||
|
// popover/card/OSD surface so they read as one design system rather than
|
||||||
|
// four different ones. `radius_pill` is only for the tiny transient OSD.
|
||||||
|
let radius = "10px";
|
||||||
|
let radius_sm = "6px";
|
||||||
|
let radius_pill = "20px";
|
||||||
|
let pad = "10px";
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"window.breadbar {{ background-color: {bg_rgba}; color: {on_bg}; border-radius: 0; }}\
|
"window.breadbar {{ background-color: {bg_rgba}; color: {on_bg}; border-radius: 0; }}\
|
||||||
.workspace-btn {{ background: transparent; opacity: 0.45;\
|
.workspace-btn {{ background: transparent; opacity: 0.45; color: {on_bg};\
|
||||||
border-radius: 0; border: none; outline: none; box-shadow: none;\
|
border-radius: {radius_sm}; border: none; outline: none; box-shadow: none;\
|
||||||
min-width: 24px; padding: 4px 8px; }}\
|
min-width: 20px; margin: 5px 2px; padding: 2px 9px; }}\
|
||||||
.workspace-btn:hover {{ opacity: 0.8; }}\
|
.workspace-btn:hover {{ opacity: 0.8; }}\
|
||||||
.workspace-btn.active {{ background: {accent}; color: {on_accent}; opacity: 1; }}\
|
.workspace-btn.active {{ background: alpha({accent}, 0.18); color: {accent}; opacity: 1; }}\
|
||||||
.stats-box {{ margin-right: 8px; }}\
|
.stats-box {{ margin-right: 8px; }}\
|
||||||
.stat-pair {{ margin-right: 14px; }}\
|
.stat-pair {{ margin-right: 14px; }}\
|
||||||
.stat-icon {{ margin-right: 2px; }}\
|
.stat-icon {{ margin-right: 2px; }}\
|
||||||
.bt-icon {{ margin-right: 14px; }}\
|
.bt-icon {{ margin-right: 14px; }}\
|
||||||
|
separator.bar-sep {{ min-height: 14px; margin: 0 8px 0 0; background: alpha({on_bg}, 0.14); }}\
|
||||||
window.breadbar-notification {{ background-color: alpha({bg_plain}, 0.95); color: {on_bg}; }}\
|
window.breadbar-notification {{ background-color: alpha({bg_plain}, 0.95); color: {on_bg}; }}\
|
||||||
.notification-card {{ background: {surface}; color: {on_surface}; border-radius: 8px;\
|
.notification-card {{ background: {surface}; color: {on_surface}; border-radius: {radius};\
|
||||||
padding: 12px; margin-bottom: 8px; }}\
|
padding: {pad}; margin-bottom: 8px; border-left: 3px solid transparent; }}\
|
||||||
|
.notification-card.urgency-critical {{ border-left-color: {critical}; }}\
|
||||||
|
.notification-card.urgency-normal {{ border-left-color: {accent}; }}\
|
||||||
.notification-summary {{ font-weight: bold; }}\
|
.notification-summary {{ font-weight: bold; }}\
|
||||||
.notification-app {{ opacity: 0.6; }}\
|
.notification-app {{ opacity: 0.6; }}\
|
||||||
window.breadbar-osd {{ background-color: alpha({bg_plain}, 0.95); color: {on_bg}; border-radius: 8px; }}\
|
window.breadbar-osd {{ background-color: alpha({bg_plain}, 0.95); color: {on_bg}; border-radius: {radius_pill}; }}\
|
||||||
.osd-kind {{ opacity: 0.75; font-size: 12px; }}\
|
.osd-icon {{ opacity: 0.85; margin-right: 8px; }}\
|
||||||
.osd-pct {{ font-weight: bold; font-size: 12px; }}\
|
.osd-icon-muted {{ opacity: 0.35; }}\
|
||||||
progressbar.osd-bar {{ min-height: 8px; }}\
|
progressbar.osd-bar {{ min-height: 6px; }}\
|
||||||
progressbar.osd-bar trough {{ background-image: none; background-color: {trough}; border-radius: 4px; min-height: 8px; }}\
|
progressbar.osd-bar trough {{ background-image: none; background-color: {trough}; border-radius: 3px; min-height: 6px; }}\
|
||||||
progressbar.osd-bar trough progress {{ background-image: none; background-color: {accent}; border-radius: 4px; min-height: 8px; }}\
|
progressbar.osd-bar trough progress {{ background-image: none; background-color: {accent}; border-radius: 3px; min-height: 6px; }}\
|
||||||
.clickable {{ cursor: pointer; }}\
|
.clickable {{ cursor: pointer; }}\
|
||||||
.wifi-pair {{ border-radius: 4px; padding: 0 2px; }}\
|
.wifi-pair {{ border-radius: {radius_sm}; padding: 0 2px; }}\
|
||||||
.wifi-pair:hover {{ background: alpha({on_bg}, 0.12); }}\
|
.wifi-pair:hover {{ background: alpha({on_bg}, 0.12); }}\
|
||||||
.wifi-popover-inner {{ min-width: 180px; padding: 2px; }}\
|
.wifi-popover-inner {{ min-width: 200px; padding: {pad}; }}\
|
||||||
|
.popover-tab-row {{ margin-bottom: {pad}; }}\
|
||||||
|
.popover-tab {{ background: transparent; color: {on_bg}; border: none; box-shadow: none;\
|
||||||
|
outline: none; border-radius: {radius_sm}; padding: 4px 10px; font-size: 11px;\
|
||||||
|
font-weight: bold; opacity: 0.55; }}\
|
||||||
|
.popover-tab:hover {{ opacity: 0.8; }}\
|
||||||
|
.popover-tab:checked {{ background: alpha({accent}, 0.18); color: {accent}; opacity: 1; }}\
|
||||||
.wifi-popover-ssid {{ font-weight: bold; font-size: 13px; }}\
|
.wifi-popover-ssid {{ font-weight: bold; font-size: 13px; }}\
|
||||||
.wifi-popover-ip {{ opacity: 0.6; font-size: 11px; }}\
|
.wifi-popover-ip {{ opacity: 0.6; font-size: 11px; }}\
|
||||||
.wifi-popover-status {{ font-size: 11px; margin-top: 2px; }}\
|
.wifi-popover-status {{ font-size: 11px; margin-top: 2px; }}\
|
||||||
.wifi-popover-section {{ font-size: 10px; font-weight: bold; opacity: 0.5; letter-spacing: 0.08em; }}\
|
.wifi-popover-section {{ font-size: 10px; font-weight: bold; opacity: 0.5; letter-spacing: 0.08em; }}\
|
||||||
.wifi-popover-row {{ background: transparent; border: none; box-shadow: none;\
|
.wifi-popover-row {{ background: transparent; border: none; box-shadow: none;\
|
||||||
border-radius: 4px; padding: 2px 6px; }}\
|
border-radius: {radius_sm}; padding: 4px 6px; }}\
|
||||||
.wifi-popover-row:hover {{ background: alpha({on_bg}, 0.08); }}\
|
.wifi-popover-row:hover {{ background: alpha({on_bg}, 0.08); }}\
|
||||||
.wifi-popover-row-active {{ color: {accent}; }}\
|
.wifi-popover-row-active {{ color: {accent}; }}\
|
||||||
.wifi-popover-row-unsaved {{ opacity: 0.4; }}\
|
.wifi-popover-row-unsaved {{ opacity: 0.4; }}\
|
||||||
.wifi-popover-loading {{ opacity: 0.5; padding: 8px; }}\
|
.wifi-popover-loading {{ opacity: 0.5; padding: 8px; }}\
|
||||||
window.wifi-add-dialog {{ background-color: {bg_rgba}; color: {on_bg}; min-width: 240px; }}\
|
window.wifi-add-dialog {{ background-color: {bg_rgba}; color: {on_bg}; min-width: 240px; }}\
|
||||||
.media-widget {{ border-radius: 4px; padding: 0 6px; cursor: pointer; }}\
|
.media-widget {{ border-radius: {radius_sm}; padding: 0 6px; cursor: pointer; }}\
|
||||||
.media-widget:hover {{ background: alpha({on_bg}, 0.10); }}\
|
.media-widget:hover {{ background: alpha({on_bg}, 0.10); }}\
|
||||||
.media-indicator {{ font-size: 11px; opacity: 0.7; margin-right: 2px; }}\
|
.media-indicator {{ font-size: 11px; opacity: 0.7; margin-right: 2px; }}\
|
||||||
.media-track-lbl {{ font-size: 12px; }}\
|
.media-track-lbl {{ font-size: 12px; }}\
|
||||||
.media-controls {{ padding: 2px; }}\
|
.media-controls {{ padding: 2px; }}\
|
||||||
.media-btn {{ font-size: 16px; min-width: 36px; padding: 2px 8px; }}\
|
.media-btn {{ min-width: 32px; padding: 4px 8px; }}\
|
||||||
.control-panel-btn {{ font-size: 14px; padding: 0 6px; margin-left: 6px; border-radius: 4px; }}\
|
.control-panel-btn {{ padding: 0 6px; margin-left: 6px; border-radius: {radius_sm}; }}\
|
||||||
.control-panel {{ }}\
|
.control-panel {{ }}\
|
||||||
.control-panel-inner {{ min-width: 240px; padding: 8px; }}\
|
.control-panel-inner {{ min-width: 220px; padding: {pad}; }}\
|
||||||
.control-panel-row {{ margin: 4px 0; }}\
|
.control-panel-row {{ margin: 4px 0; }}\
|
||||||
.control-panel-row-icon {{ opacity: 0.75; }}\
|
.control-panel-row-icon {{ opacity: 1; margin-right: 4px; }}\
|
||||||
.control-panel-slider {{ margin: 0; }}\
|
.control-panel-slider {{ margin: 0; }}\
|
||||||
.control-panel-stats {{ margin: 8px 0; }}\
|
.control-panel-stats {{ margin: {pad} 0; }}\
|
||||||
.control-panel-stat {{ font-size: 12px; opacity: 0.85; margin: 1px 0; }}\
|
.control-panel-stat {{ font-size: 12px; opacity: 0.85; margin: 1px 0; }}\
|
||||||
.control-panel-section {{ margin: 6px 0; }}\
|
.control-panel-section {{ margin: {pad} 0; }}\
|
||||||
.control-panel-section-header {{ font-size: 10px; font-weight: bold; opacity: 0.5;\
|
.control-panel-section-header {{ font-size: 10px; font-weight: bold; opacity: 0.5;\
|
||||||
letter-spacing: 0.08em; margin-bottom: 4px; }}\
|
letter-spacing: 0.08em; margin-bottom: 4px; }}\
|
||||||
.control-panel-sink-dropdown {{ }}\
|
.control-panel-sink-dropdown {{ }}\
|
||||||
.power-row {{ margin-top: 2px; }}\
|
.power-row {{ margin-top: 2px; }}\
|
||||||
.power-btn {{ font-size: 16px; min-width: 44px; padding: 4px; border-radius: 6px; }}\
|
.power-btn {{ min-width: 40px; padding: 8px; border-radius: {radius_sm}; }}\
|
||||||
separator {{ margin: 4px 0; }}",
|
separator {{ margin: 4px 0; }}",
|
||||||
bg_plain = p.background,
|
bg_plain = p.background,
|
||||||
bg_rgba = hex_to_rgba(&p.background, 0.92),
|
bg_rgba = hex_to_rgba(&p.background, 0.92),
|
||||||
surface = p.color0,
|
surface = p.color0,
|
||||||
accent = p.color4,
|
accent = p.color4,
|
||||||
|
critical = p.color1,
|
||||||
on_bg = ink_on(&p.background),
|
on_bg = ink_on(&p.background),
|
||||||
on_surface = ink_on(&p.color0),
|
on_surface = ink_on(&p.color0),
|
||||||
on_accent = ink_on(&p.color4),
|
|
||||||
trough = hex_to_rgba(&p.color4, 0.25),
|
trough = hex_to_rgba(&p.color4, 0.25),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||