1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2026-01-08 12:16:44 +01:00

Compare commits

...

7 commits

26 changed files with 689 additions and 937 deletions

326
Cargo.lock generated
View file

@ -265,9 +265,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.0" version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -309,7 +309,7 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"cairo-sys-rs", "cairo-sys-rs",
"glib", "glib",
"libc", "libc",
@ -330,11 +330,11 @@ dependencies = [
[[package]] [[package]]
name = "calloop" name = "calloop"
version = "0.12.4" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"log", "log",
"polling", "polling",
"rustix 0.38.44", "rustix 0.38.44",
@ -344,9 +344,9 @@ dependencies = [
[[package]] [[package]]
name = "calloop-wayland-source" name = "calloop-wayland-source"
version = "0.2.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
dependencies = [ dependencies = [
"calloop", "calloop",
"rustix 0.38.44", "rustix 0.38.44",
@ -410,9 +410,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.40" version = "4.5.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -420,9 +420,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.40" version = "4.5.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -432,18 +432,18 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "4.5.54" version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aad5b1b4de04fead402672b48897030eec1f3bfe1550776322f59f6d6e6a5677" checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
dependencies = [ dependencies = [
"clap", "clap",
] ]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.40" version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@ -736,6 +736,12 @@ dependencies = [
"windows-sys 0.60.2", "windows-sys 0.60.2",
] ]
[[package]]
name = "discard"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.5" version = "0.2.5"
@ -821,11 +827,11 @@ dependencies = [
[[package]] [[package]]
name = "evdev-rs" name = "evdev-rs"
version = "0.6.1" version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9812d5790fb6fcce449333eb6713dad335e8c979225ed98755c84a3987e06dba" checksum = "53b9cb6084eed4e72c0306e1cbcd3fd4c8acb613044e66810f9f5d3c7896bfb7"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 2.9.1",
"evdev-sys", "evdev-sys",
"libc", "libc",
"log", "log",
@ -833,9 +839,9 @@ dependencies = [
[[package]] [[package]]
name = "evdev-sys" name = "evdev-sys"
version = "0.2.5" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14ead42b547b15d47089c1243d907bcf0eb94e457046d3b315a26ac9c9e9ea6d" checksum = "cdcf0d489f4d9a80ac2b3b35b92fdd8fcf68d33bb67f947afe5cd36e482de576"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -1012,6 +1018,22 @@ dependencies = [
"syn 2.0.99", "syn 2.0.99",
] ]
[[package]]
name = "futures-signals"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70abe9c40a0dccd69bf7c59ba58714ebeb6c15a88143a10c6be7130e895f1696"
dependencies = [
"discard",
"futures-channel",
"futures-core",
"futures-util",
"gensym",
"log",
"pin-project",
"serde",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.31" version = "0.3.31"
@ -1123,6 +1145,18 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "gensym"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913dce4c5f06c2ea40fc178c06f777ac89fc6b1383e90c254fafb1abe4ba3c82"
dependencies = [
"proc-macro2",
"quote 1.0.39",
"syn 2.0.99",
"uuid",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.15" version = "0.2.15"
@ -1203,7 +1237,7 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-executor", "futures-executor",
@ -1303,7 +1337,7 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc759b3184830a547b31549ab40c4b54450ab702bba79ba23f049bc1d1e3ca98" checksum = "bc759b3184830a547b31549ab40c4b54450ab702bba79ba23f049bc1d1e3ca98"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"gdk", "gdk",
"glib", "glib",
"glib-sys 0.18.1", "glib-sys 0.18.1",
@ -1525,7 +1559,7 @@ dependencies = [
"libc", "libc",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2 0.5.10",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
@ -1755,7 +1789,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"inotify-sys", "inotify-sys",
"libc", "libc",
] ]
@ -1775,7 +1809,7 @@ 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 = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"cfg-if", "cfg-if",
"libc", "libc",
] ]
@ -1811,6 +1845,7 @@ dependencies = [
"dirs", "dirs",
"evdev-rs", "evdev-rs",
"futures-lite", "futures-lite",
"futures-signals",
"glib", "glib",
"gtk", "gtk",
"gtk-layer-shell", "gtk-layer-shell",
@ -1824,7 +1859,7 @@ dependencies = [
"notify", "notify",
"regex", "regex",
"reqwest", "reqwest",
"rustix 1.0.7", "rustix 1.0.8",
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
@ -1834,7 +1869,6 @@ dependencies = [
"sysinfo", "sysinfo",
"system-tray", "system-tray",
"tokio", "tokio",
"tokio-stream",
"tracing", "tracing",
"tracing-appender", "tracing-appender",
"tracing-error", "tracing-error",
@ -1842,7 +1876,7 @@ dependencies = [
"universal-config", "universal-config",
"walkdir", "walkdir",
"wayland-client", "wayland-client",
"wayland-protocols-wlr 0.3.8", "wayland-protocols-wlr",
"zbus", "zbus",
] ]
@ -1896,9 +1930,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.173" version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]] [[package]]
name = "libcorn" name = "libcorn"
@ -1929,7 +1963,7 @@ version = "2.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909eb3049e16e373680fe65afe6e2a722ace06b671250cc4849557bc57d6a397" checksum = "909eb3049e16e373680fe65afe6e2a722ace06b671250cc4849557bc57d6a397"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"libc", "libc",
"libpulse-sys", "libpulse-sys",
"num-derive", "num-derive",
@ -1956,7 +1990,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"libc", "libc",
] ]
@ -1990,9 +2024,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.26" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "lua-src" name = "lua-src"
@ -2065,9 +2099,9 @@ dependencies = [
[[package]] [[package]]
name = "mlua" name = "mlua"
version = "0.10.5" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1f5f8fbebc7db5f671671134b9321c4b9aa9adeafccfd9a8c020ae45c6a35d0" checksum = "de25fc513588ac1273aa8c6dc0fffee6d32c12f38dc75f5cdc74547121a107ef"
dependencies = [ dependencies = [
"bstr", "bstr",
"either", "either",
@ -2080,9 +2114,9 @@ dependencies = [
[[package]] [[package]]
name = "mlua-sys" name = "mlua-sys"
version = "0.6.8" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93" checksum = "bcdf7c9e260ca82aaa32ac11148941952b856bb8c69aa5a9e65962f21fcb8637"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
@ -2163,7 +2197,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@ -2182,11 +2216,11 @@ dependencies = [
[[package]] [[package]]
name = "notify" name = "notify"
version = "8.1.0" version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3163f59cd3fa0e9ef8c32f242966a7b9994fd7378366099593e0e73077cd8c97" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"inotify", "inotify",
"kqueue", "kqueue",
"libc", "libc",
@ -2254,7 +2288,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
] ]
[[package]] [[package]]
@ -2288,7 +2322,7 @@ version = "0.10.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
@ -2507,6 +2541,26 @@ dependencies = [
"siphasher", "siphasher",
] ]
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote 1.0.39",
"syn 2.0.99",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.16" version = "0.2.16"
@ -2658,7 +2712,7 @@ version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
] ]
[[package]] [[package]]
@ -2672,6 +2726,26 @@ dependencies = [
"thiserror 2.0.12", "thiserror 2.0.12",
] ]
[[package]]
name = "ref-cast"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
dependencies = [
"proc-macro2",
"quote 1.0.39",
"syn 2.0.99",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.11.1" version = "1.11.1"
@ -2775,7 +2849,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f"
dependencies = [ dependencies = [
"base64", "base64",
"bitflags 2.9.0", "bitflags 2.9.1",
"serde", "serde",
"serde_derive", "serde_derive",
"unicode-ident", "unicode-ident",
@ -2808,7 +2882,7 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.4.15", "linux-raw-sys 0.4.15",
@ -2817,15 +2891,15 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.0.7" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.9.3", "linux-raw-sys 0.9.3",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -2890,11 +2964,13 @@ dependencies = [
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.22" 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 = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
dependencies = [ dependencies = [
"dyn-clone", "dyn-clone",
"indexmap",
"ref-cast",
"schemars_derive", "schemars_derive",
"serde", "serde",
"serde_json", "serde_json",
@ -2902,9 +2978,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars_derive" name = "schemars_derive"
version = "0.8.22" 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 = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.39", "quote 1.0.39",
@ -2924,7 +3000,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"core-foundation", "core-foundation",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -2980,9 +3056,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.140" version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -3093,11 +3169,11 @@ checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]] [[package]]
name = "smithay-client-toolkit" name = "smithay-client-toolkit"
version = "0.18.1" version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"calloop", "calloop",
"calloop-wayland-source", "calloop-wayland-source",
"cursor-icon", "cursor-icon",
@ -3110,8 +3186,8 @@ dependencies = [
"wayland-client", "wayland-client",
"wayland-csd-frame", "wayland-csd-frame",
"wayland-cursor", "wayland-cursor",
"wayland-protocols 0.31.2", "wayland-protocols",
"wayland-protocols-wlr 0.2.0", "wayland-protocols-wlr",
"wayland-scanner", "wayland-scanner",
"xkeysym", "xkeysym",
] ]
@ -3126,6 +3202,16 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.0" version = "1.2.0"
@ -3254,9 +3340,9 @@ dependencies = [
[[package]] [[package]]
name = "sysinfo" name = "sysinfo"
version = "0.35.2" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d"
dependencies = [ dependencies = [
"libc", "libc",
"memchr", "memchr",
@ -3294,10 +3380,11 @@ dependencies = [
[[package]] [[package]]
name = "system-tray" name = "system-tray"
version = "0.7.0" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3397841ed755bf361606a845779e0f7333d35fb4e39627ef6f656d7cdad4c73" checksum = "90d5d024b1573d22079347055d817863c21ea0903df404668095499c08800e4a"
dependencies = [ dependencies = [
"cfg-if",
"dbusmenu-gtk3-sys", "dbusmenu-gtk3-sys",
"futures-lite", "futures-lite",
"gtk", "gtk",
@ -3421,9 +3508,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.46.1" version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -3433,10 +3520,10 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"slab", "slab",
"socket2", "socket2 0.6.0",
"tokio-macros", "tokio-macros",
"tracing", "tracing",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -3470,17 +3557,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-stream"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.13" version = "0.7.13"
@ -3571,7 +3647,7 @@ 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 = "5cc2d9e086a412a451384326f521c8123a99a466b329941a9403696bff9b0da2" checksum = "5cc2d9e086a412a451384326f521c8123a99a466b329941a9403696bff9b0da2"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
@ -3789,6 +3865,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
dependencies = [
"getrandom 0.3.1",
]
[[package]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.1" version = "0.1.1"
@ -3929,25 +4014,25 @@ dependencies = [
[[package]] [[package]]
name = "wayland-backend" name = "wayland-backend"
version = "0.3.10" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
dependencies = [ dependencies = [
"cc", "cc",
"downcast-rs", "downcast-rs",
"rustix 0.38.44", "rustix 1.0.8",
"smallvec", "smallvec",
"wayland-sys", "wayland-sys",
] ]
[[package]] [[package]]
name = "wayland-client" name = "wayland-client"
version = "0.31.10" version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"rustix 0.38.44", "rustix 1.0.8",
"wayland-backend", "wayland-backend",
"wayland-scanner", "wayland-scanner",
] ]
@ -3958,7 +4043,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"cursor-icon", "cursor-icon",
"wayland-backend", "wayland-backend",
] ]
@ -3976,23 +4061,11 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols" name = "wayland-protocols"
version = "0.31.2" version = "0.32.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"wayland-backend",
"wayland-client",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols"
version = "0.32.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a"
dependencies = [
"bitflags 2.9.0",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-scanner", "wayland-scanner",
@ -4000,35 +4073,22 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols-wlr" name = "wayland-protocols-wlr"
version = "0.2.0" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-protocols 0.31.2", "wayland-protocols",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols-wlr"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf"
dependencies = [
"bitflags 2.9.0",
"wayland-backend",
"wayland-client",
"wayland-protocols 0.32.8",
"wayland-scanner", "wayland-scanner",
] ]
[[package]] [[package]]
name = "wayland-scanner" name = "wayland-scanner"
version = "0.31.6" version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quick-xml", "quick-xml",
@ -4037,9 +4097,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-sys" name = "wayland-sys"
version = "0.31.6" version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142"
dependencies = [ dependencies = [
"pkg-config", "pkg-config",
] ]
@ -4374,7 +4434,7 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
] ]
[[package]] [[package]]
@ -4427,9 +4487,9 @@ dependencies = [
[[package]] [[package]]
name = "zbus" name = "zbus"
version = "5.7.1" version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3a7c7cee313d044fca3f48fa782cb750c79e4ca76ba7bc7718cd4024cdf6f68" checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-recursion", "async-recursion",
@ -4455,9 +4515,9 @@ dependencies = [
[[package]] [[package]]
name = "zbus_macros" name = "zbus_macros"
version = "5.7.1" version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17e7e5eec1550f747e71a058df81a9a83813ba0f6a95f39c4e218bdc7ba366a" checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659"
dependencies = [ dependencies = [
"proc-macro-crate 3.2.0", "proc-macro-crate 3.2.0",
"proc-macro2", "proc-macro2",

View file

@ -84,7 +84,7 @@ music = ["dep:regex"]
"music+mpris" = ["music", "mpris"] "music+mpris" = ["music", "mpris"]
"music+mpd" = ["music", "mpd-utils"] "music+mpd" = ["music", "mpd-utils"]
network_manager = ["futures-lite", "tokio-stream", "zbus"] network_manager = ["futures-lite", "futures-signals", "zbus"]
notifications = ["zbus"] notifications = ["zbus"]
@ -115,7 +115,7 @@ schema = ["dep:schemars"]
gtk = "0.18.2" gtk = "0.18.2"
gtk-layer-shell = "0.8.2" gtk-layer-shell = "0.8.2"
glib = "0.18.5" glib = "0.18.5"
tokio = { version = "1.46.1", features = [ tokio = { version = "1.47.1", features = [
"macros", "macros",
"rt-multi-thread", "rt-multi-thread",
"time", "time",
@ -132,13 +132,13 @@ tracing-appender = "0.2.3"
strip-ansi-escapes = "0.2.0" strip-ansi-escapes = "0.2.0"
color-eyre = "0.6.5" color-eyre = "0.6.5"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
indexmap = "2.10.0" indexmap = { version = "2.10.0", features = ["serde"] }
dirs = "6.0.0" dirs = "6.0.0"
walkdir = "2.5.0" walkdir = "2.5.0"
notify = { version = "8.1.0", default-features = false } notify = { version = "8.2.0", default-features = false }
wayland-client = "0.31.1" wayland-client = "0.31.1"
wayland-protocols-wlr = { version = "0.3.8", features = ["client"] } wayland-protocols-wlr = { version = "0.3.9", features = ["client"] }
smithay-client-toolkit = { version = "0.18.1", default-features = false, features = [ smithay-client-toolkit = { version = "0.19.2", default-features = false, features = [
"calloop", "calloop",
] } ] }
universal-config = { version = "0.5.1", default-features = false } universal-config = { version = "0.5.1", default-features = false }
@ -146,14 +146,14 @@ ctrlc = "3.4.7"
cfg-if = "1.0.1" cfg-if = "1.0.1"
# cli # cli
clap = { version = "4.5.40", optional = true, features = ["derive"] } clap = { version = "4.5.42", optional = true, features = ["derive"] }
# http # http
reqwest = { version = "0.12.22", default-features = false, features = ["default-tls", "http2"], optional = true } reqwest = { version = "0.12.22", default-features = false, features = ["default-tls", "http2"], optional = true }
# cairo # cairo
lua-src = { version = "548.1.1", optional = true } lua-src = { version = "548.1.1", optional = true }
mlua = { version = "0.10.5", optional = true, features = ["luajit", "send"] } mlua = { version = "0.11.1", optional = true, features = ["luajit", "send"] }
cairo-rs = { version = "0.18.5", optional = true, features = ["png"] } cairo-rs = { version = "0.18.5", optional = true, features = ["png"] }
# clock # clock
@ -161,7 +161,7 @@ chrono = { version = "0.4.41", optional = true, default-features = false, featur
# keyboard # keyboard
colpetto = { version = "0.6.0", features = ["tokio", "tracing"], optional = true } colpetto = { version = "0.6.0", features = ["tokio", "tracing"], optional = true }
evdev-rs = { version = "0.6.1", optional = true } evdev-rs = { version = "0.6.2", optional = true }
# music # music
mpd-utils = { version = "0.2.1", optional = true } mpd-utils = { version = "0.2.1", optional = true }
@ -171,30 +171,31 @@ regex = { version = "1.11.1", default-features = false, features = [
], optional = true } ], optional = true }
# network_manager # network_manager
tokio-stream = { version = "0.1.17", optional = true } futures-signals = { version = "0.3.34", optional = true }
# sys_info # sys_info
sysinfo = { version = "0.35.2", optional = true } sysinfo = { version = "0.36.1", optional = true }
# tray # tray
system-tray = { version = "0.7.0", features = ["dbusmenu-gtk3"], optional = true } system-tray = { version = "0.8.1", features = ["dbusmenu-gtk3"], optional = true }
# volume # volume
libpulse-binding = { version = "2.30.1", optional = true } libpulse-binding = { version = "2.30.1", optional = true }
# shared # shared
futures-lite = { version = "2.6.0", optional = true } # network_manager, upower, workspaces, keyboard futures-lite = { version = "2.6.0", optional = true } # network_manager, upower, workspaces, keyboard
zbus = { version = "5.7.1", default-features = false, features = ["blocking-api", "tokio"], optional = true } # network_manager, notifications, upower zbus = { version = "5.9.0", default-features = false, features = ["tokio"], optional = true } # network_manager, notifications, upower
swayipc-async = { version = "2.1.0", optional = true } # workspaces, keyboard swayipc-async = { version = "2.1.0", optional = true } # workspaces, keyboard
hyprland = { version = "0.4.0-beta.2", optional = true } # workspaces, keyboard hyprland = { version = "0.4.0-beta.2", optional = true } # workspaces, keyboard
rustix = { version = "1.0.7", default-features = false, features = ["std", "fs", "pipe", "event"], optional = true } # clipboard, input rustix = { version = "1.0.8", default-features = false, features = ["std", "fs", "pipe", "event"], optional = true } # clipboard, input
serde_json = { version = "1.0.140", optional = true } # ipc, niri serde_json = { version = "1.0.142", optional = true } # ipc, niri
# schema # schema
schemars = { version = "0.8.22", optional = true }
schemars = { version = "1.0.4", optional = true, features = ["indexmap2"] }
[build-dependencies] [build-dependencies]
clap = { version = "4.5.40", features = ["derive"] } clap = { version = "4.5.42", features = ["derive"] }
clap_complete = "4.5.54" clap_complete = "4.5.55"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140" serde_json = "1.0.142"

View file

@ -35,13 +35,11 @@ All error responses will cause the CLI to exit code 3.
The server listens on a Unix socket. The server listens on a Unix socket.
The path is printed on startup, and can usually be found at `/run/user/$UID/ironbar-ipc.sock`. The path is printed on startup, and can usually be found at `/run/user/$UID/ironbar-ipc.sock`.
Commands and responses are sent as JSON objects. Commands and responses are sent as JSON objects.
The JSON should be minified and must NOT contain any `\n` characters.
Commands will have a `command` key, and a `subcommand` key when part of a sub-command. Commands will have a `command` key, and a `subcommand` key when part of a sub-command.
The message buffer is currently limited to `1024` bytes.
Particularly large messages will be truncated or cause an error.
The full spec can be found below. The full spec can be found below.
## Libraries ## Libraries

View file

@ -25,7 +25,7 @@ Displays the toggle state of the capslock, num lock and scroll lock keys, and th
| `icons.num_off` | `string` or [image](images) | `''` | Icon to show for disabled num lock indicator. | | `icons.num_off` | `string` or [image](images) | `''` | Icon to show for disabled num lock indicator. |
| `icons.scroll_on` | `string` or [image](images) | `` | Icon to show for enabled scroll lock indicator. | | `icons.scroll_on` | `string` or [image](images) | `` | Icon to show for enabled scroll lock indicator. |
| `icons.scroll_off` | `string` or [image](images) | `''` | Icon to show for disabled scroll lock indicator. | | `icons.scroll_off` | `string` or [image](images) | `''` | Icon to show for disabled scroll lock indicator. |
| `icons.layout_map` | `Map<string, string or image>` | `{}` | Map of icons or labels to show for a particular keyboard layout. Layouts use their actual name if not present in the map. | | `icons.layout_map` | `Map<string, string or image>` | `{}` | Map of icons or labels to show for a particular keyboard layout. Layouts use their actual name if not present in the map. Layouts are matched in the order they appear in the map. If a pattern to match ends with a `*`, it acts as a wildcard, matching any layout name that begins with the part before the `*`. |
| `seat` | `string` | `seat0` | ID of the Wayland seat to attach to. | | `seat` | `string` | `seat0` | ID of the Wayland seat to attach to. |
<details> <details>

View file

@ -194,12 +194,12 @@ The list of available functions is shown below:
It is also possible to get only a single value from the set by specifying a name instead of a function. It is also possible to get only a single value from the set by specifying a name instead of a function.
| Token category | Valid name | | Token category | Valid name |
|----------------|-------------------------------------------------------------------------| |----------------|------------------------------------------|
| CPU | A CPU thread, eg `cpu0`, `cpu1`, ... | | CPU | A CPU thread, eg `cpu0`, `cpu1`, ... |
| Temperature | A sensor name, eg `CPUTIN`. These line up with the output of `sensors`. | | Temperature | A sensor name, eg `CPUTIN`. |
| Disk | A disk mountpoint, eg `/`, `/home`, ... | | Disk | A disk mountpoint, eg `/`, `/home`, ... |
| Network | An adapter name, eg `eth0` or `enp30s0`. | | Network | An adapter name, eg `eth0` or `enp30s0`. |
To specify a name or function, use a `@`. For example, to show disk percent for `/home`: To specify a name or function, use a `@`. For example, to show disk percent for `/home`:
@ -214,6 +214,22 @@ To show total CPU utilization where each core represents 100% (like `htop` etc):
"{cpu_percent@sum}%" "{cpu_percent@sum}%"
``` ```
> [!TIP]
> Available values can be queried over IPC using the CLI.
> This can be particularly useful for sensors, which tend not to have obvious names.
>
> ```shell
> ironbar var list sysinfo.temp_c
> ```
>
> Some usual cases to look out for:
>
> - `k10temp` is an AMD CPU internal sensor
> - Motherboard chipsets tend to prefix their sensors accordingly. For example, `CPUTIN`, `nct6687 CPU`, `asusec AMD`.
> - `amdgpu` is as it suggests.
>
> Sensor names are pulled from `hwmon` and should vaguely line up with the output of `sensors`
#### Prefixes and units #### Prefixes and units
For tokens which return an appropriate unit, you can specify the SI prefix (or unit in some special cases). For tokens which return an appropriate unit, you can specify the SI prefix (or unit in some special cases).

58
flake.lock generated
View file

@ -1,5 +1,27 @@
{ {
"nodes": { "nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"naersk",
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1752475459,
"narHash": "sha256-z6QEu4ZFuHiqdOPbYss4/Q8B0BFhacR8ts6jO/F/aOU=",
"owner": "nix-community",
"repo": "fenix",
"rev": "bf0d6f70f4c9a9cf8845f992105652173f4b617f",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": { "flake-compat": {
"locked": { "locked": {
"lastModified": 1747046372, "lastModified": 1747046372,
@ -17,14 +39,15 @@
}, },
"naersk": { "naersk": {
"inputs": { "inputs": {
"fenix": "fenix",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1745925850, "lastModified": 1752689277,
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=", "narHash": "sha256-uldUBFkZe/E7qbvxa3mH1ItrWZyT6w1dBKJQF/3ZSsc=",
"owner": "nix-community", "owner": "nix-community",
"repo": "naersk", "repo": "naersk",
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f", "rev": "0e72363d0938b0208d6c646d10649164c43f4d64",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -50,11 +73,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1751251929, "lastModified": 1752077645,
"narHash": "sha256-IJWIzZSkBsDzS7iS/iwSwur+xFkWqeLYC4kdf8ObtOM=", "narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b95255df2360a45ddbb03817a68869d5cb01bf96", "rev": "be9e214982e20b8310878ac2baa063a961c1bdf6",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -66,11 +89,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1751011381, "lastModified": 1753694789,
"narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=", "narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7", "rev": "dc9637876d0dcc8c9e5e22986b857632effeb727",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -87,6 +110,23 @@
"nix-systems": "nix-systems", "nix-systems": "nix-systems",
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
} }
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1752428706,
"narHash": "sha256-EJcdxw3aXfP8Ex1Nm3s0awyH9egQvB2Gu+QEnJn2Sfg=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "591e3b7624be97e4443ea7b5542c191311aa141d",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -190,7 +190,7 @@ impl Clients {
if let Some(client) = &self.network_manager { if let Some(client) = &self.network_manager {
Ok(client.clone()) Ok(client.clone())
} else { } else {
let client = networkmanager::create_client()?; let client = await_sync(async move { networkmanager::create_client().await })?;
self.network_manager = Some(client.clone()); self.network_manager = Some(client.clone());
Ok(client) Ok(client)
} }

View file

@ -0,0 +1,171 @@
use std::sync::Arc;
use crate::{register_fallible_client, spawn};
use color_eyre::Result;
use futures_signals::signal::{Mutable, MutableSignalCloned};
use tracing::error;
use zbus::export::ordered_stream::OrderedStreamExt;
use zbus::fdo::PropertiesProxy;
use zbus::{
Connection,
names::InterfaceName,
proxy,
zvariant::{ObjectPath, Str},
};
const DBUS_BUS: &str = "org.freedesktop.NetworkManager";
const DBUS_PATH: &str = "/org/freedesktop/NetworkManager";
const DBUS_INTERFACE: &str = "org.freedesktop.NetworkManager";
#[derive(Debug)]
pub struct Client {
client_state: Mutable<ClientState>,
interface_name: InterfaceName<'static>,
dbus_connection: Connection,
props_proxy: PropertiesProxy<'static>,
}
#[derive(Clone, Debug)]
pub enum ClientState {
WiredConnected,
WifiConnected,
CellularConnected,
VpnConnected,
WifiDisconnected,
Offline,
Unknown,
}
#[proxy(
default_service = "org.freedesktop.NetworkManager",
interface = "org.freedesktop.NetworkManager",
default_path = "/org/freedesktop/NetworkManager"
)]
trait NetworkManagerDbus {
#[zbus(property)]
fn active_connections(&self) -> Result<Vec<ObjectPath>>;
#[zbus(property)]
fn devices(&self) -> Result<Vec<ObjectPath>>;
#[zbus(property)]
fn networking_enabled(&self) -> Result<bool>;
#[zbus(property)]
fn primary_connection(&self) -> Result<ObjectPath>;
#[zbus(property)]
fn primary_connection_type(&self) -> Result<Str>;
#[zbus(property)]
fn wireless_enabled(&self) -> Result<bool>;
}
impl Client {
async fn new() -> Result<Self> {
let client_state = Mutable::new(ClientState::Unknown);
let dbus_connection = Connection::system().await?;
let interface_name = InterfaceName::from_static_str(DBUS_INTERFACE)?;
let props_proxy = PropertiesProxy::builder(&dbus_connection)
.destination(DBUS_BUS)?
.path(DBUS_PATH)?
.build()
.await?;
Ok(Self {
client_state,
interface_name,
dbus_connection,
props_proxy,
})
}
async fn run(&self) -> Result<()> {
let proxy = NetworkManagerDbusProxy::new(&self.dbus_connection).await?;
let mut primary_connection = proxy.primary_connection().await?;
let mut primary_connection_type = proxy.primary_connection_type().await?;
let mut wireless_enabled = proxy.wireless_enabled().await?;
self.client_state.set(determine_state(
&primary_connection,
&primary_connection_type,
wireless_enabled,
));
let mut stream = self.props_proxy.receive_properties_changed().await?;
while let Some(change) = stream.next().await {
let args = change.args()?;
if args.interface_name != self.interface_name {
continue;
}
let changed_props = args.changed_properties;
let mut relevant_prop_changed = false;
if changed_props.contains_key("PrimaryConnection") {
primary_connection = proxy.primary_connection().await?;
relevant_prop_changed = true;
}
if changed_props.contains_key("PrimaryConnectionType") {
primary_connection_type = proxy.primary_connection_type().await?;
relevant_prop_changed = true;
}
if changed_props.contains_key("WirelessEnabled") {
wireless_enabled = proxy.wireless_enabled().await?;
relevant_prop_changed = true;
}
if relevant_prop_changed {
self.client_state.set(determine_state(
&primary_connection,
&primary_connection_type,
wireless_enabled,
));
}
}
Ok(())
}
pub fn subscribe(&self) -> MutableSignalCloned<ClientState> {
self.client_state.signal_cloned()
}
}
pub async fn create_client() -> Result<Arc<Client>> {
let client = Arc::new(Client::new().await?);
{
let client = client.clone();
spawn(async move {
if let Err(error) = client.run().await {
error!("{}", error);
}
});
}
Ok(client)
}
fn determine_state(
primary_connection: &str,
primary_connection_type: &str,
wireless_enabled: bool,
) -> ClientState {
if primary_connection == "/" {
if wireless_enabled {
ClientState::WifiDisconnected
} else {
ClientState::Offline
}
} else {
match primary_connection_type {
"802-3-ethernet" | "adsl" | "pppoe" => ClientState::WiredConnected,
"802-11-olpc-mesh" | "802-11-wireless" | "wifi-p2p" => ClientState::WifiConnected,
"cdma" | "gsm" | "wimax" => ClientState::CellularConnected,
"vpn" | "wireguard" => ClientState::VpnConnected,
_ => ClientState::Unknown,
}
}
}
register_fallible_client!(Client, network_manager);

View file

@ -1,84 +0,0 @@
use color_eyre::Result;
use zbus::proxy;
use zbus::zvariant::{ObjectPath, OwnedValue, Str};
#[proxy(
default_service = "org.freedesktop.NetworkManager",
interface = "org.freedesktop.NetworkManager",
default_path = "/org/freedesktop/NetworkManager"
)]
pub(super) trait Dbus {
#[zbus(property)]
fn all_devices(&self) -> Result<Vec<ObjectPath<'_>>>;
}
#[proxy(
default_service = "org.freedesktop.NetworkManager",
interface = "org.freedesktop.NetworkManager.Device"
)]
pub(super) trait DeviceDbus {
#[zbus(property)]
fn device_type(&self) -> Result<DeviceType>;
#[zbus(property)]
fn interface(&self) -> Result<Str<'_>>;
#[zbus(property)]
fn state(&self) -> Result<DeviceState>;
}
// For reference: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/e1a7d5ac062f4f23ce3a6b33c62e856056161ad8/src/libnm-core-public/nm-dbus-interface.h#L212-L253
#[derive(Clone, Debug, Eq, Hash, OwnedValue, PartialEq)]
#[repr(u32)]
pub enum DeviceType {
Unknown = 0,
Ethernet = 1,
Wifi = 2,
Bluetooth = 5,
OlpcMesh = 6,
Wimax = 7,
Modem = 8,
Infiniband = 9,
Bond = 10,
Vlan = 11,
Adsl = 12,
Bridge = 13,
Team = 15,
Tun = 16,
IpTunnel = 17,
Macvlan = 18,
Vxlan = 19,
Veth = 20,
Macsec = 21,
Dummy = 22,
Ppp = 23,
OvsInterface = 24,
OvsPort = 25,
OvsBridge = 26,
Wpan = 27,
Lowpan = 28,
Wireguard = 29,
WifiP2p = 30,
Vrf = 31,
Loopback = 32,
Hsr = 33,
}
// For reference: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/e1a7d5ac062f4f23ce3a6b33c62e856056161ad8/src/libnm-core-public/nm-dbus-interface.h#L501-L538
#[derive(Clone, Debug, OwnedValue, PartialEq)]
#[repr(u32)]
pub enum DeviceState {
Unknown = 0,
Unmanaged = 10,
Unavailable = 20,
Disconnected = 30,
Prepare = 40,
Config = 50,
NeedAuth = 60,
IpConfig = 70,
IpCheck = 80,
Secondaries = 90,
Activated = 100,
Deactivating = 110,
Failed = 120,
}

View file

@ -1,13 +0,0 @@
use crate::clients::networkmanager::dbus::{DeviceState, DeviceType};
#[derive(Debug, Clone)]
pub enum Event {
DeviceAdded {
interface: String,
},
DeviceStateChanged {
interface: String,
r#type: DeviceType,
state: DeviceState,
},
}

View file

@ -1,118 +0,0 @@
use color_eyre::Result;
use color_eyre::eyre::Ok;
use futures_lite::StreamExt;
use std::collections::HashSet;
use std::sync::Arc;
use tokio::sync::broadcast;
use zbus::Connection;
use zbus::zvariant::{ObjectPath, Str};
use crate::clients::ClientResult;
use crate::clients::networkmanager::dbus::{DbusProxy, DeviceDbusProxy};
use crate::clients::networkmanager::event::Event;
use crate::{register_fallible_client, spawn};
pub mod dbus;
pub mod event;
#[derive(Debug)]
pub struct Client {
tx: broadcast::Sender<Event>,
}
impl Client {
fn new() -> Result<Client> {
let (tx, _) = broadcast::channel(64);
Ok(Client { tx })
}
fn run(&self) -> Result<()> {
let tx = self.tx.clone();
spawn(async move {
let dbus_connection = Connection::system().await?;
let root = DbusProxy::new(&dbus_connection).await?;
let mut devices = HashSet::new();
let mut devices_changes = root.receive_all_devices_changed().await;
while let Some(devices_change) = devices_changes.next().await {
// The new list of devices from dbus, not to be confused with the added devices below
let new_devices = HashSet::from_iter(devices_change.get().await?);
let added_devices = new_devices.difference(&devices);
for added_device in added_devices {
spawn(watch_device(added_device.to_owned(), tx.clone()));
}
let removed_devices = devices.difference(&new_devices);
// TODO: Cook up some way to notify closures for removed devices to exit
devices = new_devices;
}
Ok(())
});
Ok(())
}
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
self.tx.subscribe()
}
}
pub fn create_client() -> ClientResult<Client> {
let client = Arc::new(Client::new()?);
client.run()?;
Ok(client)
}
async fn watch_device(device_path: ObjectPath<'_>, tx: broadcast::Sender<Event>) -> Result<()> {
let dbus_connection = Connection::system().await?;
let device = DeviceDbusProxy::new(&dbus_connection, device_path.to_owned()).await?;
let interface = device.interface().await?;
tx.send(Event::DeviceAdded {
interface: interface.to_string(),
})?;
spawn(watch_device_state(
device_path.to_owned(),
interface.to_owned(),
tx.clone(),
));
Ok(())
}
async fn watch_device_state(
device_path: ObjectPath<'_>,
interface: Str<'_>,
tx: broadcast::Sender<Event>,
) -> Result<()> {
let dbus_connection = Connection::system().await?;
let device = DeviceDbusProxy::new(&dbus_connection, &device_path).await?;
let r#type = device.device_type().await?;
// Send an event communicating the initial state
let state = device.state().await?;
tx.send(Event::DeviceStateChanged {
interface: interface.to_string(),
r#type: r#type.clone(),
state,
})?;
let mut state_changes = device.receive_state_changed().await;
while let Some(state_change) = state_changes.next().await {
let state = state_change.get().await?;
tx.send(Event::DeviceStateChanged {
interface: interface.to_string(),
r#type: r#type.clone(),
state,
})?;
}
Ok(())
}
register_fallible_client!(Client, network_manager);

View file

@ -1,165 +0,0 @@
use crate::clients::networkmanager::dbus::{
ActiveConnectionDbusProxy, DeviceDbusProxy, DeviceState, DeviceType,
};
use color_eyre::Result;
use std::collections::HashMap;
use zbus::zvariant::ObjectPath;
type PathMap<'l, ValueType> = HashMap<ObjectPath<'l>, ValueType>;
#[derive(Clone, Debug)]
pub struct State {
pub wired: WiredState,
pub wifi: WifiState,
pub cellular: CellularState,
pub vpn: VpnState,
}
#[derive(Clone, Debug)]
pub enum WiredState {
Connected,
Disconnected,
NotPresent,
Unknown,
}
#[derive(Clone, Debug)]
pub enum WifiState {
Connected(WifiConnectedState),
Disconnected,
Disabled,
NotPresent,
Unknown,
}
#[derive(Clone, Debug)]
pub struct WifiConnectedState {
pub ssid: String,
}
#[derive(Clone, Debug)]
pub enum CellularState {
Connected,
Disconnected,
Disabled,
NotPresent,
Unknown,
}
#[derive(Clone, Debug)]
pub enum VpnState {
Connected(VpnConnectedState),
Disconnected,
Unknown,
}
#[derive(Clone, Debug)]
pub struct VpnConnectedState {
pub name: String,
}
pub(super) async fn determine_wired_state(
devices: &PathMap<'_, DeviceDbusProxy<'_>>,
) -> Result<WiredState> {
let mut present = false;
let mut connected = false;
for device in devices.values() {
if device.device_type().await? == DeviceType::Ethernet {
present = true;
if device.state().await?.is_enabled() {
connected = true;
break;
}
}
}
if connected {
Ok(WiredState::Connected)
} else if present {
Ok(WiredState::Disconnected)
} else {
Ok(WiredState::NotPresent)
}
}
pub(super) async fn determine_wifi_state(
devices: &PathMap<'_, DeviceDbusProxy<'_>>,
) -> Result<WifiState> {
let mut present = false;
let mut enabled = false;
let mut connected = false;
for device in devices.values() {
if device.device_type().await? == DeviceType::Wifi {
present = true;
if device.state().await?.is_enabled() {
enabled = true;
if device.state().await? == DeviceState::Activated {
connected = true;
break;
}
}
}
}
if connected {
Ok(WifiState::Connected(WifiConnectedState {
// TODO: Implement obtaining SSID
ssid: "unknown".into(),
}))
} else if enabled {
Ok(WifiState::Disconnected)
} else if present {
Ok(WifiState::Disabled)
} else {
Ok(WifiState::NotPresent)
}
}
pub(super) async fn determine_cellular_state(
devices: &PathMap<'_, DeviceDbusProxy<'_>>,
) -> Result<CellularState> {
let mut present = false;
let mut enabled = false;
let mut connected = false;
for device in devices.values() {
if device.device_type().await? == DeviceType::Modem {
present = true;
if device.state().await?.is_enabled() {
enabled = true;
if device.state().await? == DeviceState::Activated {
connected = true;
break;
}
}
}
}
if connected {
Ok(CellularState::Connected)
} else if enabled {
Ok(CellularState::Disconnected)
} else if present {
Ok(CellularState::Disabled)
} else {
Ok(CellularState::NotPresent)
}
}
pub(super) async fn determine_vpn_state(
active_connections: &PathMap<'_, ActiveConnectionDbusProxy<'_>>,
) -> Result<VpnState> {
for connection in active_connections.values() {
match connection.type_().await?.as_str() {
"vpn" | "wireguard" => {
return Ok(VpnState::Connected(VpnConnectedState {
name: "unknown".into(),
}));
}
_ => {}
}
}
Ok(VpnState::Disconnected)
}

View file

@ -3,7 +3,7 @@ mod sink_input;
use crate::{APP_ID, arc_mut, lock, register_client, spawn_blocking}; use crate::{APP_ID, arc_mut, lock, register_client, spawn_blocking};
use libpulse_binding::callbacks::ListResult; use libpulse_binding::callbacks::ListResult;
use libpulse_binding::context::introspect::{Introspector, ServerInfo}; use libpulse_binding::context::introspect::ServerInfo;
use libpulse_binding::context::subscribe::{Facility, InterestMaskSet, Operation}; use libpulse_binding::context::subscribe::{Facility, InterestMaskSet, Operation};
use libpulse_binding::context::{Context, FlagSet, State}; use libpulse_binding::context::{Context, FlagSet, State};
use libpulse_binding::mainloop::standard::{IterateResult, Mainloop}; use libpulse_binding::mainloop::standard::{IterateResult, Mainloop};
@ -24,11 +24,11 @@ type ArcMutVec<T> = Arc<Mutex<Vec<T>>>;
pub enum Event { pub enum Event {
AddSink(Sink), AddSink(Sink),
UpdateSink(Sink), UpdateSink(Sink),
RemoveSink(String), RemoveSink,
AddInput(SinkInput), AddInput,
UpdateInput(SinkInput), UpdateInput,
RemoveInput(u32), RemoveInput,
} }
#[derive(Debug)] #[derive(Debug)]
@ -51,10 +51,7 @@ struct Data {
pub enum ConnectionState { pub enum ConnectionState {
Disconnected, Disconnected,
Connected { Connected,
context: Arc<Mutex<Context>>,
introspector: Introspector,
},
} }
impl Debug for ConnectionState { impl Debug for ConnectionState {
@ -64,7 +61,7 @@ impl Debug for ConnectionState {
"{}", "{}",
match self { match self {
Self::Disconnected => "Disconnected", Self::Disconnected => "Disconnected",
Self::Connected { .. } => "Connected", Self::Connected => "Connected",
} }
) )
} }
@ -120,14 +117,9 @@ impl Client {
error!("{err:?}"); error!("{err:?}");
} }
let introspector = lock!(context).introspect();
{ {
let mut inner = lock!(self.connection); let mut inner = lock!(self.connection);
*inner = ConnectionState::Connected { *inner = ConnectionState::Connected;
context,
introspector,
};
} }
loop { loop {
@ -291,22 +283,4 @@ fn volume_to_percent(volume: ChannelVolumes) -> f64 {
((avg - Volume::MUTED.0) as f64 / base_delta).round() ((avg - Volume::MUTED.0) as f64 / base_delta).round()
} }
/// Converts a percentage volume into a Pulse volume value,
/// which can be used for setting channel volumes.
pub fn percent_to_volume(target_percent: f64) -> u32 {
let base_delta = (Volume::NORMAL.0 as f32 - Volume::MUTED.0 as f32) / 100.0;
if target_percent < 0.0 {
Volume::MUTED.0
} else if target_percent == 100.0 {
Volume::NORMAL.0
} else if target_percent >= 150.0 {
(Volume::NORMAL.0 as f32 * 1.5) as u32
} else if target_percent < 100.0 {
Volume::MUTED.0 + target_percent as u32 * base_delta as u32
} else {
Volume::NORMAL.0 + (target_percent - 100.0) as u32 * base_delta as u32
}
}
register_client!(Client, volume); register_client!(Client, volume);

View file

@ -1,4 +1,4 @@
use super::{ArcMutVec, Client, ConnectionState, Event, percent_to_volume, volume_to_percent}; use super::{ArcMutVec, Client, Event, volume_to_percent};
use crate::channels::SyncSenderExt; use crate::channels::SyncSenderExt;
use crate::lock; use crate::lock;
use libpulse_binding::callbacks::ListResult; use libpulse_binding::callbacks::ListResult;
@ -6,7 +6,7 @@ use libpulse_binding::context::Context;
use libpulse_binding::context::introspect::SinkInfo; use libpulse_binding::context::introspect::SinkInfo;
use libpulse_binding::context::subscribe::Operation; use libpulse_binding::context::subscribe::Operation;
use libpulse_binding::def::SinkState; use libpulse_binding::def::SinkState;
use std::sync::{Arc, Mutex, mpsc}; use std::sync::{Arc, Mutex};
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tracing::{debug, error, instrument, trace}; use tracing::{debug, error, instrument, trace};
@ -14,7 +14,6 @@ use tracing::{debug, error, instrument, trace};
pub struct Sink { pub struct Sink {
index: u32, index: u32,
pub name: String, pub name: String,
pub description: String,
pub volume: f64, pub volume: f64,
pub muted: bool, pub muted: bool,
pub active: bool, pub active: bool,
@ -29,11 +28,6 @@ impl From<&SinkInfo<'_>> for Sink {
.as_ref() .as_ref()
.map(ToString::to_string) .map(ToString::to_string)
.unwrap_or_default(), .unwrap_or_default(),
description: value
.description
.as_ref()
.map(ToString::to_string)
.unwrap_or_default(),
muted: value.mute, muted: value.mute,
volume: volume_to_percent(value.volume), volume: volume_to_percent(value.volume),
active: value.state == SinkState::Running, active: value.state == SinkState::Running,
@ -46,43 +40,6 @@ impl Client {
pub fn sinks(&self) -> Arc<Mutex<Vec<Sink>>> { pub fn sinks(&self) -> Arc<Mutex<Vec<Sink>>> {
self.data.sinks.clone() self.data.sinks.clone()
} }
#[instrument(level = "trace")]
pub fn set_default_sink(&self, name: &str) {
if let ConnectionState::Connected { context, .. } = &*lock!(self.connection) {
lock!(context).set_default_sink(name, |_| {});
}
}
#[instrument(level = "trace")]
pub fn set_sink_volume(&self, name: &str, volume_percent: f64) {
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
let (tx, rx) = mpsc::channel();
introspector.get_sink_info_by_name(name, move |info| {
let ListResult::Item(info) = info else {
return;
};
tx.send_expect(info.volume);
});
let new_volume = percent_to_volume(volume_percent);
let mut volume = rx.recv().expect("to receive info");
for v in volume.get_mut() {
v.0 = new_volume;
}
introspector.set_sink_volume_by_name(name, &volume, None);
}
}
#[instrument(level = "trace")]
pub fn set_sink_muted(&self, name: &str, muted: bool) {
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
introspector.set_sink_mute_by_name(name, muted, None);
}
}
} }
pub fn on_event( pub fn on_event(
@ -177,10 +134,9 @@ fn update(
fn remove(index: u32, sinks: &ArcMutVec<Sink>, tx: &broadcast::Sender<Event>) { fn remove(index: u32, sinks: &ArcMutVec<Sink>, tx: &broadcast::Sender<Event>) {
trace!("removing {index}"); trace!("removing {index}");
let mut sinks = lock!(sinks); let sinks = lock!(sinks);
if let Some(pos) = sinks.iter().position(|s| s.index == index) { if let Some(_pos) = sinks.iter().position(|s| s.index == index) {
let info = sinks.remove(pos); tx.send_expect(Event::RemoveSink);
tx.send_expect(Event::RemoveSink(info.name));
} }
} }

View file

@ -1,37 +1,22 @@
use super::{ArcMutVec, Client, ConnectionState, Event, percent_to_volume, volume_to_percent}; use super::{ArcMutVec, Client, Event};
use crate::channels::SyncSenderExt; use crate::channels::SyncSenderExt;
use crate::lock; use crate::lock;
use libpulse_binding::callbacks::ListResult; use libpulse_binding::callbacks::ListResult;
use libpulse_binding::context::Context; use libpulse_binding::context::Context;
use libpulse_binding::context::introspect::SinkInputInfo; use libpulse_binding::context::introspect::SinkInputInfo;
use libpulse_binding::context::subscribe::Operation; use libpulse_binding::context::subscribe::Operation;
use std::sync::{Arc, Mutex, mpsc}; use std::sync::{Arc, Mutex};
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tracing::{debug, error, instrument, trace}; use tracing::{debug, error, instrument, trace};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SinkInput { pub struct SinkInput {
pub index: u32, pub index: u32,
pub name: String,
pub volume: f64,
pub muted: bool,
pub can_set_volume: bool,
} }
impl From<&SinkInputInfo<'_>> for SinkInput { impl From<&SinkInputInfo<'_>> for SinkInput {
fn from(value: &SinkInputInfo) -> Self { fn from(value: &SinkInputInfo) -> Self {
Self { Self { index: value.index }
index: value.index,
name: value
.name
.as_ref()
.map(ToString::to_string)
.unwrap_or_default(),
muted: value.mute,
volume: volume_to_percent(value.volume),
can_set_volume: value.has_volume && value.volume_writable,
}
} }
} }
@ -40,36 +25,6 @@ impl Client {
pub fn sink_inputs(&self) -> Arc<Mutex<Vec<SinkInput>>> { pub fn sink_inputs(&self) -> Arc<Mutex<Vec<SinkInput>>> {
self.data.sink_inputs.clone() self.data.sink_inputs.clone()
} }
#[instrument(level = "trace")]
pub fn set_input_volume(&self, index: u32, volume_percent: f64) {
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
let (tx, rx) = mpsc::channel();
introspector.get_sink_input_info(index, move |info| {
let ListResult::Item(info) = info else {
return;
};
tx.send_expect(info.volume);
});
let new_volume = percent_to_volume(volume_percent);
let mut volume = rx.recv().expect("to receive info");
for v in volume.get_mut() {
v.0 = new_volume;
}
introspector.set_sink_input_volume(index, &volume, None);
}
}
#[instrument(level = "trace")]
pub fn set_input_muted(&self, index: u32, muted: bool) {
if let ConnectionState::Connected { introspector, .. } = &mut *lock!(self.connection) {
introspector.set_sink_input_mute(index, muted, None);
}
}
} }
pub fn on_event( pub fn on_event(
@ -119,7 +74,7 @@ pub fn add(
trace!("adding {info:?}"); trace!("adding {info:?}");
lock!(inputs).push(info.into()); lock!(inputs).push(info.into());
tx.send_expect(Event::AddInput(info.into())); tx.send_expect(Event::AddInput);
} }
fn update( fn update(
@ -143,16 +98,15 @@ fn update(
inputs[pos] = info.into(); inputs[pos] = info.into();
} }
tx.send_expect(Event::UpdateInput(info.into())); tx.send_expect(Event::UpdateInput);
} }
fn remove(index: u32, inputs: &ArcMutVec<SinkInput>, tx: &broadcast::Sender<Event>) { fn remove(index: u32, inputs: &ArcMutVec<SinkInput>, tx: &broadcast::Sender<Event>) {
let mut inputs = lock!(inputs); let inputs = lock!(inputs);
trace!("removing {index}"); trace!("removing {index}");
if let Some(pos) = inputs.iter().position(|s| s.index == index) { if let Some(_pos) = inputs.iter().position(|s| s.index == index) {
let info = inputs.remove(pos); tx.send_expect(Event::RemoveInput);
tx.send_expect(Event::RemoveInput(info.index));
} }
} }

View file

@ -52,16 +52,11 @@ where
} }
#[cfg(feature = "schema")] #[cfg(feature = "schema")]
pub fn schema_layer(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { pub fn schema_layer(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
use schemars::JsonSchema; schemars::json_schema!({
let mut schema: schemars::schema::SchemaObject = <String>::json_schema(generator).into(); "type": "string",
schema.enum_values = Some(vec![ "enum": ["background", "bottom", "top", "overlay"],
"background".into(), })
"bottom".into(),
"top".into(),
"overlay".into(),
]);
schema.into()
} }
impl BarPosition { impl BarPosition {

View file

@ -3,9 +3,10 @@ use color_eyre::{Help, Report, Result};
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command, Stdio}; use std::process::Stdio;
use std::sync::Arc; use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing::{debug, error}; use tracing::{debug, error};
use walkdir::{DirEntry, WalkDir}; use walkdir::{DirEntry, WalkDir};
@ -238,6 +239,7 @@ impl DesktopFiles {
/// Checks file contents for an exact or partial match of the provided input. /// Checks file contents for an exact or partial match of the provided input.
async fn find_by_file_contents(&self, app_id: &str) -> Result<Option<DesktopFile>> { async fn find_by_file_contents(&self, app_id: &str) -> Result<Option<DesktopFile>> {
let mut files = self.files.lock().await; let mut files = self.files.lock().await;
let app_id_lower = app_id.to_lowercase();
// first pass - check name for exact match // first pass - check name for exact match
for (_, file_ref) in files.iter_mut() { for (_, file_ref) in files.iter_mut() {
@ -253,7 +255,7 @@ impl DesktopFiles {
for (_, file_ref) in files.iter_mut() { for (_, file_ref) in files.iter_mut() {
let file = file_ref.get().await?; let file = file_ref.get().await?;
if let Some(name) = &file.name { if let Some(name) = &file.name {
if name.to_lowercase().contains(app_id) { if name.to_lowercase().contains(&app_id_lower) {
return Ok(Some(file)); return Ok(Some(file));
} }
} }
@ -264,19 +266,19 @@ impl DesktopFiles {
let file = file_ref.get().await?; let file = file_ref.get().await?;
if let Some(name) = &file.exec { if let Some(name) = &file.exec {
if name.to_lowercase().contains(app_id) { if name.to_lowercase().contains(&app_id_lower) {
return Ok(Some(file)); return Ok(Some(file));
} }
} }
if let Some(name) = &file.startup_wm_class { if let Some(name) = &file.startup_wm_class {
if name.to_lowercase().contains(app_id) { if name.to_lowercase().contains(&app_id_lower) {
return Ok(Some(file)); return Ok(Some(file));
} }
} }
if let Some(name) = &file.icon { if let Some(name) = &file.icon {
if name.to_lowercase().contains(app_id) { if name.to_lowercase().contains(&app_id_lower) {
return Ok(Some(file)); return Ok(Some(file));
} }
} }
@ -325,21 +327,37 @@ fn files(dir: &Path) -> Vec<PathBuf> {
} }
/// Starts a `.desktop` file with the provided formatted command. /// Starts a `.desktop` file with the provided formatted command.
pub fn open_program(file_name: &str, str: &str) { pub async fn open_program(file_name: &str, launch_command: &str) {
let expanded = str.replace("{app_name}", file_name); let expanded = launch_command.replace("{app_name}", file_name);
let launch_command_parts: Vec<&str> = expanded.split_whitespace().collect(); let launch_command_parts: Vec<&str> = expanded.split_whitespace().collect();
if let Err(err) = Command::new(&launch_command_parts[0])
debug!("running {launch_command_parts:?}");
let exit_status = match Command::new(launch_command_parts[0])
.args(&launch_command_parts[1..]) .args(&launch_command_parts[1..])
.stdin(Stdio::null())
.stdout(Stdio::null()) .stdout(Stdio::null())
.stderr(Stdio::null()) .stderr(Stdio::null())
.kill_on_drop(true)
.spawn() .spawn()
{ {
error!( Ok(mut child) => Some(child.wait().await),
"{:?}", Err(err) => {
Report::new(err) error!(
.wrap_err("Failed to run launch command.") "{:?}",
.suggestion("Perhaps the applications file is invalid?") Report::new(err)
); .wrap_err("Failed to run launch command.")
.suggestion("Perhaps the desktop file is invalid or orphaned?")
);
None
}
};
match exit_status {
Some(Ok(exit_status)) if !exit_status.success() => {
error!("received non-success exit status running {launch_command_parts:?}")
}
Some(Err(err)) => error!("{err:?}"),
_ => {}
} }
} }

View file

@ -2,7 +2,7 @@ use super::Ipc;
use crate::ipc::{Command, Response}; use crate::ipc::{Command, Response};
use color_eyre::Result; use color_eyre::Result;
use color_eyre::{Help, Report}; use color_eyre::{Help, Report};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::UnixStream; use tokio::net::UnixStream;
impl Ipc { impl Ipc {
@ -16,18 +16,20 @@ impl Ipc {
.suggestion("Is Ironbar running?")), .suggestion("Is Ironbar running?")),
}?; }?;
let write_buffer = serde_json::to_vec(&command)?; let mut write_buffer = serde_json::to_vec(&command)?;
if debug { if debug {
eprintln!("REQUEST JSON: {}", serde_json::to_string(&command)?); eprintln!("REQUEST JSON: {}", serde_json::to_string(&command)?);
} }
write_buffer.push(b'\n');
stream.write_all(&write_buffer).await?; stream.write_all(&write_buffer).await?;
let mut read_buffer = vec![0; 1024]; let mut read_buffer = String::new();
let bytes = stream.read(&mut read_buffer).await?; let mut reader = BufReader::new(stream);
let bytes = reader.read_line(&mut read_buffer).await?;
let response = serde_json::from_slice(&read_buffer[..bytes])?; let response = serde_json::from_str(&read_buffer[..bytes])?;
Ok(response) Ok(response)
} }
} }

View file

@ -8,10 +8,10 @@ use std::rc::Rc;
use color_eyre::{Report, Result}; use color_eyre::{Report, Result};
use gtk::Application; use gtk::Application;
use gtk::prelude::*; use gtk::prelude::*;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::{UnixListener, UnixStream}; use tokio::net::{UnixListener, UnixStream};
use tokio::sync::mpsc::{self, Receiver, Sender}; use tokio::sync::mpsc::{self, Receiver, Sender};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, trace, warn};
use super::Ipc; use super::Ipc;
use crate::channels::{AsyncSenderExt, MpscReceiverExt}; use crate::channels::{AsyncSenderExt, MpscReceiverExt};
@ -52,11 +52,13 @@ impl Ipc {
loop { loop {
match listener.accept().await { match listener.accept().await {
Ok((stream, _addr)) => { Ok((stream, _addr)) => {
debug!("handling incoming connection");
if let Err(err) = if let Err(err) =
Self::handle_connection(stream, &cmd_tx, &mut res_rx).await Self::handle_connection(stream, &cmd_tx, &mut res_rx).await
{ {
error!("{err:?}"); error!("{err:?}");
} }
debug!("done");
} }
Err(err) => { Err(err) => {
error!("{err:?}"); error!("{err:?}");
@ -80,10 +82,16 @@ impl Ipc {
cmd_tx: &Sender<Command>, cmd_tx: &Sender<Command>,
res_rx: &mut Receiver<Response>, res_rx: &mut Receiver<Response>,
) -> Result<()> { ) -> Result<()> {
let (mut stream_read, mut stream_write) = stream.split(); trace!("awaiting readable state");
stream.readable().await?;
let mut read_buffer = vec![0; 1024]; let mut read_buffer = Vec::with_capacity(1024);
let bytes = stream_read.read(&mut read_buffer).await?;
let mut reader = BufReader::new(&mut stream);
trace!("reading bytes");
let bytes = reader.read_until(b'\n', &mut read_buffer).await?;
debug!("read {} bytes", bytes);
// FIXME: Error on invalid command // FIXME: Error on invalid command
let command = serde_json::from_slice::<Command>(&read_buffer[..bytes])?; let command = serde_json::from_slice::<Command>(&read_buffer[..bytes])?;
@ -95,10 +103,18 @@ impl Ipc {
.recv() .recv()
.await .await
.unwrap_or(Response::Err { message: None }); .unwrap_or(Response::Err { message: None });
let res = serde_json::to_vec(&res)?;
stream_write.write_all(&res).await?; let mut res = serde_json::to_vec(&res)?;
stream_write.shutdown().await?; res.push(b'\n');
trace!("awaiting writable state");
stream.writable().await?;
debug!("writing {} bytes", res.len());
stream.write_all(&res).await?;
trace!("bytes written, shutting down stream");
stream.shutdown().await?;
Ok(()) Ok(())
} }

View file

@ -1,8 +1,7 @@
use std::collections::HashMap;
use color_eyre::Result; use color_eyre::Result;
use color_eyre::eyre::Report; use color_eyre::eyre::Report;
use gtk::prelude::*; use gtk::prelude::*;
use indexmap::IndexMap;
use serde::Deserialize; use serde::Deserialize;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::{debug, trace}; use tracing::{debug, trace};
@ -129,7 +128,7 @@ struct Icons {
/// } /// }
/// ``` /// ```
#[serde(default)] #[serde(default)]
layout_map: HashMap<String, String>, layout_map: IndexMap<String, String>,
} }
impl Default for Icons { impl Default for Icons {
@ -141,7 +140,7 @@ impl Default for Icons {
num_off: String::new(), num_off: String::new(),
scroll_on: default_icon_scroll(), scroll_on: default_icon_scroll(),
scroll_off: String::new(), scroll_off: String::new(),
layout_map: HashMap::new(), layout_map: IndexMap::new(),
} }
} }
} }
@ -338,7 +337,19 @@ impl Module<gtk::Box> for KeyboardModule {
} }
} }
KeyboardUpdate::Layout(KeyboardLayoutUpdate(language)) => { KeyboardUpdate::Layout(KeyboardLayoutUpdate(language)) => {
let text = icons.layout_map.get(&language).unwrap_or(&language); let text = icons
.layout_map
.iter()
.find_map(|(pattern, display_text)| {
let is_match = if pattern.ends_with("*") {
language.starts_with(&pattern[..pattern.len() - 1])
} else {
pattern == &language
};
is_match.then(|| display_text)
})
.unwrap_or(&language);
layout_button.set_label(text); layout_button.set_label(text);
} }
}); });

View file

@ -244,21 +244,43 @@ impl Module<gtk::Box> for LauncherModule {
let tx2 = context.tx.clone(); let tx2 = context.tx.clone();
let wl = context.client::<wayland::Client>(); let wl = context.client::<wayland::Client>();
let desktop_files = context.ironbar.desktop_files();
spawn(async move { spawn(async move {
let items = items2; let items = items2;
let tx = tx2; let tx = tx2;
// Build app_id mapping once at startup
let mut app_id_map = IndexMap::<String, String>::new();
{
let favorites: Vec<_> = lock!(items).keys().cloned().collect();
for fav in favorites {
if let Ok(Some(file)) = desktop_files.find(&fav).await {
if let Some(wm_class) = file.startup_wm_class {
app_id_map.insert(wm_class, fav);
}
}
}
}
let resolve_app_id = |app_id: &str| {
app_id_map
.get(app_id)
.cloned()
.unwrap_or_else(|| app_id.to_string())
};
let mut wlrx = wl.subscribe_toplevels(); let mut wlrx = wl.subscribe_toplevels();
let handles = wl.toplevel_info_all(); let handles = wl.toplevel_info_all();
for info in handles { for info in handles {
let mut items = lock!(items); let mut items = lock!(items);
let item = items.get_mut(&info.app_id); let app_id = resolve_app_id(&info.app_id);
if let Some(item) = item { if let Some(item) = items.get_mut(&app_id) {
item.merge_toplevel(info.clone()); item.merge_toplevel(info.clone());
} else { } else {
let item = Item::from(info.clone()); let mut item = Item::from(info.clone());
items.insert(info.app_id.clone(), item); item.app_id = app_id.clone();
items.insert(app_id, item);
} }
} }
@ -284,14 +306,14 @@ impl Module<gtk::Box> for LauncherModule {
match event { match event {
ToplevelEvent::New(info) => { ToplevelEvent::New(info) => {
let app_id = info.app_id.clone(); let app_id = resolve_app_id(&info.app_id);
let new_item = { let new_item = {
let mut items = lock!(items); let mut items = lock!(items);
let item = items.get_mut(&info.app_id); match items.get_mut(&app_id) {
match item {
None => { None => {
let item: Item = info.into(); let mut item: Item = info.into();
item.app_id = app_id.clone();
items.insert(app_id.clone(), item.clone()); items.insert(app_id.clone(), item.clone());
ItemOrWindow::Item(item) ItemOrWindow::Item(item)
@ -313,9 +335,10 @@ impl Module<gtk::Box> for LauncherModule {
}?; }?;
} }
ToplevelEvent::Update(info) => { ToplevelEvent::Update(info) => {
let app_id = resolve_app_id(&info.app_id);
// check if open, as updates can be sent as program closes // check if open, as updates can be sent as program closes
// if it's a focused favourite closing, it otherwise incorrectly re-focuses. // if it's a focused favourite closing, it otherwise incorrectly re-focuses.
let is_open = if let Some(item) = lock!(items).get_mut(&info.app_id) { let is_open = if let Some(item) = lock!(items).get_mut(&app_id) {
item.set_window_focused(info.id, info.focused); item.set_window_focused(info.id, info.focused);
item.set_window_name(info.id, info.title.clone()); item.set_window_name(info.id, info.title.clone());
@ -325,27 +348,27 @@ impl Module<gtk::Box> for LauncherModule {
}; };
send_update(LauncherUpdate::Focus( send_update(LauncherUpdate::Focus(
info.app_id.clone(), app_id.clone(),
is_open && info.focused, is_open && info.focused,
)) ))
.await?; .await?;
send_update(LauncherUpdate::Title( send_update(LauncherUpdate::Title(
info.app_id.clone(), app_id.clone(),
info.id, info.id,
info.title.clone(), info.title.clone(),
)) ))
.await?; .await?;
} }
ToplevelEvent::Remove(info) => { ToplevelEvent::Remove(info) => {
let app_id = resolve_app_id(&info.app_id);
let remove_item = { let remove_item = {
let mut items = lock!(items); let mut items = lock!(items);
let item = items.get_mut(&info.app_id); match items.get_mut(&app_id) {
match item {
Some(item) => { Some(item) => {
item.unmerge_toplevel(&info); item.unmerge_toplevel(&info);
if item.windows.is_empty() { if item.windows.is_empty() {
items.shift_remove(&info.app_id); items.shift_remove(&app_id);
Some(ItemOrWindowId::Item) Some(ItemOrWindowId::Item)
} else { } else {
Some(ItemOrWindowId::Window) Some(ItemOrWindowId::Window)
@ -357,15 +380,11 @@ impl Module<gtk::Box> for LauncherModule {
match remove_item { match remove_item {
Some(ItemOrWindowId::Item) => { Some(ItemOrWindowId::Item) => {
send_update(LauncherUpdate::RemoveItem(info.app_id.clone())) send_update(LauncherUpdate::RemoveItem(app_id.clone())).await?;
.await?;
} }
Some(ItemOrWindowId::Window) => { Some(ItemOrWindowId::Window) => {
send_update(LauncherUpdate::RemoveWindow( send_update(LauncherUpdate::RemoveWindow(app_id.clone(), info.id))
info.app_id.clone(), .await?;
info.id,
))
.await?;
} }
None => {} None => {}
} }
@ -388,7 +407,7 @@ impl Module<gtk::Box> for LauncherModule {
if let ItemEvent::OpenItem(app_id) = event { if let ItemEvent::OpenItem(app_id) = event {
match desktop_files.find(&app_id).await { match desktop_files.find(&app_id).await {
Ok(Some(file)) => { Ok(Some(file)) => {
open_program(&file.file_name, &launch_command_str); open_program(&file.file_name, &launch_command_str).await;
} }
Ok(None) => warn!("Could not find applications file for {}", app_id), Ok(None) => warn!("Could not find applications file for {}", app_id),
Err(err) => error!("Failed to find parse file for {}: {}", app_id, err), Err(err) => error!("Failed to find parse file for {}: {}", app_id, err),

View file

@ -100,7 +100,11 @@ where
let tx = tx.clone(); let tx = tx.clone();
button.connect_clicked(move |_button| { button.connect_clicked(move |_button| {
open_program(&file_name, &command); // TODO: this needs refactoring to call open from the controller
let file_name = file_name.clone();
let command = command.clone();
spawn(async move { open_program(&file_name, &command).await });
sub_menu.hide(); sub_menu.hide();
tx.send_spawn(ModuleUpdateEvent::ClosePopup); tx.send_spawn(ModuleUpdateEvent::ClosePopup);

View file

@ -1,18 +1,16 @@
use crate::clients::networkmanager::Client; use crate::channels::{AsyncSenderExt, BroadcastReceiverExt};
use crate::clients::networkmanager::dbus::{DeviceState, DeviceType}; use crate::clients::networkmanager::{Client, ClientState};
use crate::clients::networkmanager::event::Event;
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::IronbarGtkExt;
use crate::image::Provider;
use crate::modules::{Module, ModuleInfo, ModuleParts, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleParts, WidgetContext};
use crate::{module_impl, spawn}; use crate::{module_impl, spawn};
use color_eyre::{Result, eyre::Ok}; use color_eyre::Result;
use glib::spawn_future_local; use futures_lite::StreamExt;
use gtk::prelude::{ContainerExt, WidgetExt}; use futures_signals::signal::SignalExt;
use gtk::{Image, Orientation}; use gtk::prelude::ContainerExt;
use gtk::{Box as GtkBox, Image};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use tokio::sync::mpsc::Receiver;
use tokio::sync::{broadcast, mpsc};
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
@ -28,29 +26,26 @@ const fn default_icon_size() -> i32 {
24 24
} }
impl Module<gtk::Box> for NetworkManagerModule { impl Module<GtkBox> for NetworkManagerModule {
type SendMessage = Event; type SendMessage = ClientState;
type ReceiveMessage = (); type ReceiveMessage = ();
module_impl!("network_manager"); module_impl!("network_manager");
fn spawn_controller( fn spawn_controller(
&self, &self,
_info: &ModuleInfo, _: &ModuleInfo,
context: &WidgetContext<Event, ()>, context: &WidgetContext<ClientState, ()>,
_rx: mpsc::Receiver<()>, _: Receiver<()>,
) -> Result<()> { ) -> Result<()> {
let client = context.try_client::<Client>()?; let client = context.try_client::<Client>()?;
// Should we be using context.tx with ModuleUpdateEvent::Update instead? let mut client_signal = client.subscribe().to_stream();
let tx = context.update_tx.clone(); let tx = context.tx.clone();
// Must be done here synchronously to avoid race condition
let mut client_rx = client.subscribe();
spawn(async move {
while let Result::Ok(event) = client_rx.recv().await {
tx.send(event)?;
}
Ok(()) spawn(async move {
while let Some(state) = client_signal.next().await {
tx.send_update(state).await;
}
}); });
Ok(()) Ok(())
@ -58,105 +53,50 @@ impl Module<gtk::Box> for NetworkManagerModule {
fn into_widget( fn into_widget(
self, self,
context: WidgetContext<Event, ()>, context: WidgetContext<ClientState, ()>,
_info: &ModuleInfo, info: &ModuleInfo,
) -> Result<ModuleParts<gtk::Box>> { ) -> Result<ModuleParts<GtkBox>> {
let container = gtk::Box::new(Orientation::Horizontal, 0); const INITIAL_ICON_NAME: &str = "content-loading-symbolic";
// Must be done here synchronously to avoid race condition let container = GtkBox::new(info.bar_position.orientation(), 0);
let rx = context.subscribe(); let icon = Image::new();
// We cannot use recv_glib_async here because the lifetimes don't work out icon.add_class("icon");
spawn_future_local(handle_update_events( container.add(&icon);
rx,
container.clone(), let image_provider = context.ironbar.image_provider();
self.icon_size,
context.ironbar.image_provider(), glib::spawn_future_local({
)); let image_provider = image_provider.clone();
let icon = icon.clone();
async move {
image_provider
.load_into_image_silent(INITIAL_ICON_NAME, self.icon_size, false, &icon)
.await;
}
});
context.subscribe().recv_glib_async((), move |(), state| {
let image_provider = image_provider.clone();
let icon = icon.clone();
let icon_name = match state {
ClientState::WiredConnected => "network-wired-symbolic",
ClientState::WifiConnected => "network-wireless-symbolic",
ClientState::CellularConnected => "network-cellular-symbolic",
ClientState::VpnConnected => "network-vpn-symbolic",
ClientState::WifiDisconnected => "network-wireless-acquiring-symbolic",
ClientState::Offline => "network-wireless-disabled-symbolic",
ClientState::Unknown => "dialog-question-symbolic",
};
async move {
image_provider
.load_into_image_silent(icon_name, self.icon_size, false, &icon)
.await;
}
});
Ok(ModuleParts::new(container, None)) Ok(ModuleParts::new(container, None))
} }
} }
async fn handle_update_events(
mut rx: broadcast::Receiver<Event>,
container: gtk::Box,
icon_size: i32,
image_provider: Provider,
) {
let mut icons = HashMap::<String, Image>::new();
while let Result::Ok(event) = rx.recv().await {
match event {
Event::DeviceAdded { interface, .. } => {
let icon = Image::new();
icon.add_class("icon");
container.add(&icon);
icons.insert(interface, icon);
}
Event::DeviceStateChanged {
interface,
r#type,
state,
} => {
let icon = icons
.get(&interface)
.expect("the icon for the interface to be present");
// TODO: Make this configurable at runtime
let icon_name = get_icon_for_device_state(&r#type, &state);
match icon_name {
Some(icon_name) => {
image_provider
.load_into_image_silent(icon_name, icon_size, false, icon)
.await;
icon.show();
}
None => {
icon.hide();
}
}
}
};
}
}
fn get_icon_for_device_state(r#type: &DeviceType, state: &DeviceState) -> Option<&'static str> {
match r#type {
DeviceType::Ethernet => match state {
DeviceState::Unavailable => Some("icon:network-wired-disconnected-symbolic"),
DeviceState::Disconnected => Some("icon:network-wired-disconnected-symbolic"),
DeviceState::Prepare => Some("icon:network-wired-disconnected-symbolic"),
DeviceState::Config => Some("icon:network-wired-disconnected-symbolic"),
DeviceState::NeedAuth => Some("icon:network-wired-disconnected-symbolic"),
DeviceState::IpConfig => Some("icon:network-wired-disconnected-symbolic"),
DeviceState::IpCheck => Some("icon:network-wired-disconnected-symbolic"),
DeviceState::Secondaries => Some("icon:network-wired-disconnected-symbolic"),
DeviceState::Activated => Some("icon:network-wired-symbolic"),
DeviceState::Deactivating => Some("icon:network-wired-disconnected-symbolic"),
DeviceState::Failed => Some("icon:network-wired-disconnected-symbolic"),
_ => None,
},
DeviceType::Wifi => match state {
DeviceState::Unavailable => Some("icon:network-wireless-hardware-disabled-symbolic"),
DeviceState::Disconnected => Some("icon:network-wireless-offline-symbolic"),
DeviceState::Prepare => Some("icon:network-wireless-offline-symbolic"),
DeviceState::Config => Some("icon:network-wireless-offline-symbolic"),
DeviceState::NeedAuth => Some("icon:network-wireless-offline-symbolic"),
DeviceState::IpConfig => Some("icon:network-wireless-offline-symbolic"),
DeviceState::IpCheck => Some("icon:network-wireless-offline-symbolic"),
DeviceState::Secondaries => Some("icon:network-wireless-offline-symbolic"),
DeviceState::Activated => Some("icon:network-wireless-connected-symbolic"),
DeviceState::Deactivating => Some("icon:network-wireless-offline-symbolic"),
DeviceState::Failed => Some("icon:network-wireless-offline-symbolic"),
_ => None,
},
DeviceType::Tun => match state {
DeviceState::Activated => Some("icon:network-vpn-symbolic"),
_ => None,
},
DeviceType::Wireguard => match state {
DeviceState::Activated => Some("icon:network-vpn-symbolic"),
_ => None,
},
_ => None,
}
}

View file

@ -11,6 +11,7 @@ use std::collections::HashSet;
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
use std::ptr; use std::ptr;
use system_tray::item::IconPixmap;
/// Gets the GTK icon theme search paths by calling the FFI function. /// Gets the GTK icon theme search paths by calling the FFI function.
/// Conveniently returns the result as a `HashSet`. /// Conveniently returns the result as a `HashSet`.
@ -45,10 +46,10 @@ pub fn get_image(
icon_theme: &IconTheme, icon_theme: &IconTheme,
) -> Result<Image> { ) -> Result<Image> {
if !prefer_icons && item.icon_pixmap.is_some() { if !prefer_icons && item.icon_pixmap.is_some() {
get_image_from_pixmap(item, size) get_image_from_pixmap(item.icon_pixmap.as_deref(), size)
} else { } else {
get_image_from_icon_name(item, size, icon_theme) get_image_from_icon_name(item, size, icon_theme)
.or_else(|_| get_image_from_pixmap(item, size)) .or_else(|_| get_image_from_pixmap(item.icon_pixmap.as_deref(), size))
} }
} }
@ -81,12 +82,10 @@ fn get_image_from_icon_name(item: &TrayMenu, size: u32, icon_theme: &IconTheme)
/// which has 8 bits per sample and a bit stride of `4*width`. /// which has 8 bits per sample and a bit stride of `4*width`.
/// The Pixbuf expects RGBA32 format, so some channel shuffling /// The Pixbuf expects RGBA32 format, so some channel shuffling
/// is required. /// is required.
fn get_image_from_pixmap(item: &TrayMenu, size: u32) -> Result<Image> { fn get_image_from_pixmap(item: Option<&[IconPixmap]>, size: u32) -> Result<Image> {
const BITS_PER_SAMPLE: i32 = 8; const BITS_PER_SAMPLE: i32 = 8;
let pixmap = item let pixmap = item
.icon_pixmap
.as_ref()
.and_then(|pixmap| pixmap.first()) .and_then(|pixmap| pixmap.first())
.ok_or_else(|| Report::msg("Failed to get pixmap from tray icon"))?; .ok_or_else(|| Report::msg("Failed to get pixmap from tray icon"))?;

View file

@ -181,9 +181,14 @@ fn on_update(
UpdateEvent::AttentionIcon(_icon) => { UpdateEvent::AttentionIcon(_icon) => {
warn!("received unimplemented NewAttentionIcon event"); warn!("received unimplemented NewAttentionIcon event");
} }
UpdateEvent::Icon(icon) => { UpdateEvent::Icon {
if icon.as_ref() != menu_item.icon_name() { icon_name,
menu_item.set_icon_name(icon); icon_pixmap,
} => {
menu_item.icon_pixmap = icon_pixmap;
if icon_name.as_ref() != menu_item.icon_name() {
menu_item.set_icon_name(icon_name);
match icon::get_image(menu_item, icon_size, prefer_icons, icon_theme) { match icon::get_image(menu_item, icon_size, prefer_icons, icon_theme) {
Ok(image) => menu_item.set_image(&image), Ok(image) => menu_item.set_image(&image),
Err(_) => menu_item.show_label(), Err(_) => menu_item.show_label(),

View file

@ -1,13 +1,13 @@
use crate::channels::{AsyncSenderExt, BroadcastReceiverExt}; use crate::channels::{AsyncSenderExt, BroadcastReceiverExt};
use crate::clients::volume::{self, Event}; use crate::clients::volume::{self, Event};
use crate::config::{CommonConfig, LayoutConfig, TruncateMode}; use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{ use crate::modules::{
Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, PopupButton, WidgetContext, Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, PopupButton, WidgetContext,
}; };
use crate::{lock, module_impl, spawn}; use crate::{lock, module_impl, spawn};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, Image, Label, Scale, ToggleButton}; use gtk::{Button, Image};
use serde::Deserialize; use serde::Deserialize;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::trace; use tracing::trace;
@ -15,48 +15,21 @@ use tracing::trace;
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct VolumeModule { pub struct VolumeModule {
/// Maximum value to allow volume sliders to reach.
/// Pulse supports values > 100 but this may result in distortion.
///
/// **Default**: `100`
#[serde(default = "default_max_volume")]
max_volume: f64,
#[serde(default = "default_icon_size")] #[serde(default = "default_icon_size")]
icon_size: i32, icon_size: i32,
// -- Common -- // -- Common --
/// See [truncate options](module-level-options#truncate-mode).
///
/// **Default**: `null`
pub(crate) truncate: Option<TruncateMode>,
/// See [layout options](module-level-options#layout)
#[serde(default, flatten)]
layout: LayoutConfig,
/// See [common options](module-level-options#common-options). /// See [common options](module-level-options#common-options).
#[serde(flatten)] #[serde(flatten)]
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
} }
const fn default_max_volume() -> f64 {
100.0
}
const fn default_icon_size() -> i32 { const fn default_icon_size() -> i32 {
24 24
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Update { pub enum Update {}
SinkChange(String),
SinkVolume(String, f64),
SinkMute(String, bool),
InputVolume(u32, f64),
InputMute(u32, bool),
}
impl Module<Button> for VolumeModule { impl Module<Button> for VolumeModule {
type SendMessage = Event; type SendMessage = Event;
@ -68,7 +41,7 @@ impl Module<Button> for VolumeModule {
&self, &self,
_info: &ModuleInfo, _info: &ModuleInfo,
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>, context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
mut rx: mpsc::Receiver<Self::ReceiveMessage>, _rx: mpsc::Receiver<Self::ReceiveMessage>,
) -> color_eyre::Result<()> ) -> color_eyre::Result<()>
where where
<Self as Module<Button>>::SendMessage: Clone, <Self as Module<Button>>::SendMessage: Clone,
@ -102,8 +75,8 @@ impl Module<Button> for VolumeModule {
tx.send_update(Event::AddSink(sink)).await; tx.send_update(Event::AddSink(sink)).await;
} }
for input in inputs { for _input in inputs {
tx.send_update(Event::AddInput(input)).await; tx.send_update(Event::AddInput).await;
} }
// recv loop // recv loop
@ -114,19 +87,6 @@ impl Module<Button> for VolumeModule {
}); });
} }
// ui events
spawn(async move {
while let Some(update) = rx.recv().await {
match update {
Update::SinkChange(name) => client.set_default_sink(&name),
Update::SinkVolume(name, volume) => client.set_sink_volume(&name, volume),
Update::SinkMute(name, muted) => client.set_sink_muted(&name, muted),
Update::InputVolume(index, volume) => client.set_input_volume(index, volume),
Update::InputMute(index, muted) => client.set_input_muted(index, muted),
}
}
});
Ok(()) Ok(())
} }
@ -179,13 +139,6 @@ impl Module<Button> for VolumeModule {
} }
} }
struct InputUi {
container: gtk::Box,
label: Label,
slider: Scale,
btn_mute: ToggleButton,
}
fn determine_volume_icon(muted: bool, volume: f64) -> String { fn determine_volume_icon(muted: bool, volume: f64) -> String {
let icon_variant = if muted { let icon_variant = if muted {
"muted" "muted"