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

View file

@ -84,7 +84,7 @@ music = ["dep:regex"]
"music+mpris" = ["music", "mpris"]
"music+mpd" = ["music", "mpd-utils"]
network_manager = ["futures-lite", "tokio-stream", "zbus"]
network_manager = ["futures-lite", "futures-signals", "zbus"]
notifications = ["zbus"]
@ -115,7 +115,7 @@ schema = ["dep:schemars"]
gtk = "0.18.2"
gtk-layer-shell = "0.8.2"
glib = "0.18.5"
tokio = { version = "1.46.1", features = [
tokio = { version = "1.47.1", features = [
"macros",
"rt-multi-thread",
"time",
@ -132,13 +132,13 @@ tracing-appender = "0.2.3"
strip-ansi-escapes = "0.2.0"
color-eyre = "0.6.5"
serde = { version = "1.0.219", features = ["derive"] }
indexmap = "2.10.0"
indexmap = { version = "2.10.0", features = ["serde"] }
dirs = "6.0.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-protocols-wlr = { version = "0.3.8", features = ["client"] }
smithay-client-toolkit = { version = "0.18.1", default-features = false, features = [
wayland-protocols-wlr = { version = "0.3.9", features = ["client"] }
smithay-client-toolkit = { version = "0.19.2", default-features = false, features = [
"calloop",
] }
universal-config = { version = "0.5.1", default-features = false }
@ -146,14 +146,14 @@ ctrlc = "3.4.7"
cfg-if = "1.0.1"
# cli
clap = { version = "4.5.40", optional = true, features = ["derive"] }
clap = { version = "4.5.42", optional = true, features = ["derive"] }
# http
reqwest = { version = "0.12.22", default-features = false, features = ["default-tls", "http2"], optional = true }
# cairo
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"] }
# clock
@ -161,7 +161,7 @@ chrono = { version = "0.4.41", optional = true, default-features = false, featur
# keyboard
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
mpd-utils = { version = "0.2.1", optional = true }
@ -171,30 +171,31 @@ regex = { version = "1.11.1", default-features = false, features = [
], optional = true }
# network_manager
tokio-stream = { version = "0.1.17", optional = true }
futures-signals = { version = "0.3.34", optional = true }
# sys_info
sysinfo = { version = "0.35.2", optional = true }
sysinfo = { version = "0.36.1", optional = true }
# tray
system-tray = { version = "0.7.0", features = ["dbusmenu-gtk3"], optional = true }
system-tray = { version = "0.8.1", features = ["dbusmenu-gtk3"], optional = true }
# volume
libpulse-binding = { version = "2.30.1", optional = true }
# shared
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
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
serde_json = { version = "1.0.140", optional = true } # ipc, niri
rustix = { version = "1.0.8", default-features = false, features = ["std", "fs", "pipe", "event"], optional = true } # clipboard, input
serde_json = { version = "1.0.142", optional = true } # ipc, niri
# schema
schemars = { version = "0.8.22", optional = true }
schemars = { version = "1.0.4", optional = true, features = ["indexmap2"] }
[build-dependencies]
clap = { version = "4.5.40", features = ["derive"] }
clap_complete = "4.5.54"
clap = { version = "4.5.42", features = ["derive"] }
clap_complete = "4.5.55"
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 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.
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.
## 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.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.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. |
<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.
| Token category | Valid name |
|----------------|-------------------------------------------------------------------------|
| CPU | A CPU thread, eg `cpu0`, `cpu1`, ... |
| Temperature | A sensor name, eg `CPUTIN`. These line up with the output of `sensors`. |
| Disk | A disk mountpoint, eg `/`, `/home`, ... |
| Network | An adapter name, eg `eth0` or `enp30s0`. |
| Token category | Valid name |
|----------------|------------------------------------------|
| CPU | A CPU thread, eg `cpu0`, `cpu1`, ... |
| Temperature | A sensor name, eg `CPUTIN`. |
| Disk | A disk mountpoint, eg `/`, `/home`, ... |
| Network | An adapter name, eg `eth0` or `enp30s0`. |
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}%"
```
> [!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
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": {
"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": {
"locked": {
"lastModified": 1747046372,
@ -17,14 +39,15 @@
},
"naersk": {
"inputs": {
"fenix": "fenix",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1745925850,
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=",
"lastModified": 1752689277,
"narHash": "sha256-uldUBFkZe/E7qbvxa3mH1ItrWZyT6w1dBKJQF/3ZSsc=",
"owner": "nix-community",
"repo": "naersk",
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f",
"rev": "0e72363d0938b0208d6c646d10649164c43f4d64",
"type": "github"
},
"original": {
@ -50,11 +73,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1751251929,
"narHash": "sha256-IJWIzZSkBsDzS7iS/iwSwur+xFkWqeLYC4kdf8ObtOM=",
"lastModified": 1752077645,
"narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b95255df2360a45ddbb03817a68869d5cb01bf96",
"rev": "be9e214982e20b8310878ac2baa063a961c1bdf6",
"type": "github"
},
"original": {
@ -66,11 +89,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1751011381,
"narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=",
"lastModified": 1753694789,
"narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7",
"rev": "dc9637876d0dcc8c9e5e22986b857632effeb727",
"type": "github"
},
"original": {
@ -87,6 +110,23 @@
"nix-systems": "nix-systems",
"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",

View file

@ -190,7 +190,7 @@ impl Clients {
if let Some(client) = &self.network_manager {
Ok(client.clone())
} else {
let client = networkmanager::create_client()?;
let client = await_sync(async move { networkmanager::create_client().await })?;
self.network_manager = Some(client.clone());
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 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::{Context, FlagSet, State};
use libpulse_binding::mainloop::standard::{IterateResult, Mainloop};
@ -24,11 +24,11 @@ type ArcMutVec<T> = Arc<Mutex<Vec<T>>>;
pub enum Event {
AddSink(Sink),
UpdateSink(Sink),
RemoveSink(String),
RemoveSink,
AddInput(SinkInput),
UpdateInput(SinkInput),
RemoveInput(u32),
AddInput,
UpdateInput,
RemoveInput,
}
#[derive(Debug)]
@ -51,10 +51,7 @@ struct Data {
pub enum ConnectionState {
Disconnected,
Connected {
context: Arc<Mutex<Context>>,
introspector: Introspector,
},
Connected,
}
impl Debug for ConnectionState {
@ -64,7 +61,7 @@ impl Debug for ConnectionState {
"{}",
match self {
Self::Disconnected => "Disconnected",
Self::Connected { .. } => "Connected",
Self::Connected => "Connected",
}
)
}
@ -120,14 +117,9 @@ impl Client {
error!("{err:?}");
}
let introspector = lock!(context).introspect();
{
let mut inner = lock!(self.connection);
*inner = ConnectionState::Connected {
context,
introspector,
};
*inner = ConnectionState::Connected;
}
loop {
@ -291,22 +283,4 @@ fn volume_to_percent(volume: ChannelVolumes) -> f64 {
((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);

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::lock;
use libpulse_binding::callbacks::ListResult;
@ -6,7 +6,7 @@ use libpulse_binding::context::Context;
use libpulse_binding::context::introspect::SinkInfo;
use libpulse_binding::context::subscribe::Operation;
use libpulse_binding::def::SinkState;
use std::sync::{Arc, Mutex, mpsc};
use std::sync::{Arc, Mutex};
use tokio::sync::broadcast;
use tracing::{debug, error, instrument, trace};
@ -14,7 +14,6 @@ use tracing::{debug, error, instrument, trace};
pub struct Sink {
index: u32,
pub name: String,
pub description: String,
pub volume: f64,
pub muted: bool,
pub active: bool,
@ -29,11 +28,6 @@ impl From<&SinkInfo<'_>> for Sink {
.as_ref()
.map(ToString::to_string)
.unwrap_or_default(),
description: value
.description
.as_ref()
.map(ToString::to_string)
.unwrap_or_default(),
muted: value.mute,
volume: volume_to_percent(value.volume),
active: value.state == SinkState::Running,
@ -46,43 +40,6 @@ impl Client {
pub fn sinks(&self) -> Arc<Mutex<Vec<Sink>>> {
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(
@ -177,10 +134,9 @@ fn update(
fn remove(index: u32, sinks: &ArcMutVec<Sink>, tx: &broadcast::Sender<Event>) {
trace!("removing {index}");
let mut sinks = lock!(sinks);
let sinks = lock!(sinks);
if let Some(pos) = sinks.iter().position(|s| s.index == index) {
let info = sinks.remove(pos);
tx.send_expect(Event::RemoveSink(info.name));
if let Some(_pos) = sinks.iter().position(|s| s.index == index) {
tx.send_expect(Event::RemoveSink);
}
}

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::lock;
use libpulse_binding::callbacks::ListResult;
use libpulse_binding::context::Context;
use libpulse_binding::context::introspect::SinkInputInfo;
use libpulse_binding::context::subscribe::Operation;
use std::sync::{Arc, Mutex, mpsc};
use std::sync::{Arc, Mutex};
use tokio::sync::broadcast;
use tracing::{debug, error, instrument, trace};
#[derive(Debug, Clone)]
pub struct SinkInput {
pub index: u32,
pub name: String,
pub volume: f64,
pub muted: bool,
pub can_set_volume: bool,
}
impl From<&SinkInputInfo<'_>> for SinkInput {
fn from(value: &SinkInputInfo) -> Self {
Self {
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,
}
Self { index: value.index }
}
}
@ -40,36 +25,6 @@ impl Client {
pub fn sink_inputs(&self) -> Arc<Mutex<Vec<SinkInput>>> {
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(
@ -119,7 +74,7 @@ pub fn add(
trace!("adding {info:?}");
lock!(inputs).push(info.into());
tx.send_expect(Event::AddInput(info.into()));
tx.send_expect(Event::AddInput);
}
fn update(
@ -143,16 +98,15 @@ fn update(
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>) {
let mut inputs = lock!(inputs);
let inputs = lock!(inputs);
trace!("removing {index}");
if let Some(pos) = inputs.iter().position(|s| s.index == index) {
let info = inputs.remove(pos);
tx.send_expect(Event::RemoveInput(info.index));
if let Some(_pos) = inputs.iter().position(|s| s.index == index) {
tx.send_expect(Event::RemoveInput);
}
}

View file

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

View file

@ -3,9 +3,10 @@ use color_eyre::{Help, Report, Result};
use std::collections::HashMap;
use std::env;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::process::Stdio;
use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
use tokio::sync::Mutex;
use tracing::{debug, error};
use walkdir::{DirEntry, WalkDir};
@ -238,6 +239,7 @@ impl DesktopFiles {
/// 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>> {
let mut files = self.files.lock().await;
let app_id_lower = app_id.to_lowercase();
// first pass - check name for exact match
for (_, file_ref) in files.iter_mut() {
@ -253,7 +255,7 @@ impl DesktopFiles {
for (_, file_ref) in files.iter_mut() {
let file = file_ref.get().await?;
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));
}
}
@ -264,19 +266,19 @@ impl DesktopFiles {
let file = file_ref.get().await?;
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));
}
}
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));
}
}
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));
}
}
@ -325,21 +327,37 @@ fn files(dir: &Path) -> Vec<PathBuf> {
}
/// Starts a `.desktop` file with the provided formatted command.
pub fn open_program(file_name: &str, str: &str) {
let expanded = str.replace("{app_name}", file_name);
pub async fn open_program(file_name: &str, launch_command: &str) {
let expanded = launch_command.replace("{app_name}", file_name);
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..])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.kill_on_drop(true)
.spawn()
{
error!(
"{:?}",
Report::new(err)
.wrap_err("Failed to run launch command.")
.suggestion("Perhaps the applications file is invalid?")
);
Ok(mut child) => Some(child.wait().await),
Err(err) => {
error!(
"{:?}",
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 color_eyre::Result;
use color_eyre::{Help, Report};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::UnixStream;
impl Ipc {
@ -16,18 +16,20 @@ impl Ipc {
.suggestion("Is Ironbar running?")),
}?;
let write_buffer = serde_json::to_vec(&command)?;
let mut write_buffer = serde_json::to_vec(&command)?;
if debug {
eprintln!("REQUEST JSON: {}", serde_json::to_string(&command)?);
}
write_buffer.push(b'\n');
stream.write_all(&write_buffer).await?;
let mut read_buffer = vec![0; 1024];
let bytes = stream.read(&mut read_buffer).await?;
let mut read_buffer = String::new();
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)
}
}

View file

@ -8,10 +8,10 @@ use std::rc::Rc;
use color_eyre::{Report, Result};
use gtk::Application;
use gtk::prelude::*;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::{UnixListener, UnixStream};
use tokio::sync::mpsc::{self, Receiver, Sender};
use tracing::{debug, error, info, warn};
use tracing::{debug, error, info, trace, warn};
use super::Ipc;
use crate::channels::{AsyncSenderExt, MpscReceiverExt};
@ -52,11 +52,13 @@ impl Ipc {
loop {
match listener.accept().await {
Ok((stream, _addr)) => {
debug!("handling incoming connection");
if let Err(err) =
Self::handle_connection(stream, &cmd_tx, &mut res_rx).await
{
error!("{err:?}");
}
debug!("done");
}
Err(err) => {
error!("{err:?}");
@ -80,10 +82,16 @@ impl Ipc {
cmd_tx: &Sender<Command>,
res_rx: &mut Receiver<Response>,
) -> 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 bytes = stream_read.read(&mut read_buffer).await?;
let mut read_buffer = Vec::with_capacity(1024);
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
let command = serde_json::from_slice::<Command>(&read_buffer[..bytes])?;
@ -95,10 +103,18 @@ impl Ipc {
.recv()
.await
.unwrap_or(Response::Err { message: None });
let res = serde_json::to_vec(&res)?;
stream_write.write_all(&res).await?;
stream_write.shutdown().await?;
let mut res = serde_json::to_vec(&res)?;
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(())
}

View file

@ -1,8 +1,7 @@
use std::collections::HashMap;
use color_eyre::Result;
use color_eyre::eyre::Report;
use gtk::prelude::*;
use indexmap::IndexMap;
use serde::Deserialize;
use tokio::sync::mpsc;
use tracing::{debug, trace};
@ -129,7 +128,7 @@ struct Icons {
/// }
/// ```
#[serde(default)]
layout_map: HashMap<String, String>,
layout_map: IndexMap<String, String>,
}
impl Default for Icons {
@ -141,7 +140,7 @@ impl Default for Icons {
num_off: String::new(),
scroll_on: default_icon_scroll(),
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)) => {
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);
}
});

View file

@ -244,21 +244,43 @@ impl Module<gtk::Box> for LauncherModule {
let tx2 = context.tx.clone();
let wl = context.client::<wayland::Client>();
let desktop_files = context.ironbar.desktop_files();
spawn(async move {
let items = items2;
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 handles = wl.toplevel_info_all();
for info in handles {
let mut items = lock!(items);
let item = items.get_mut(&info.app_id);
if let Some(item) = item {
let app_id = resolve_app_id(&info.app_id);
if let Some(item) = items.get_mut(&app_id) {
item.merge_toplevel(info.clone());
} else {
let item = Item::from(info.clone());
items.insert(info.app_id.clone(), item);
let mut item = Item::from(info.clone());
item.app_id = app_id.clone();
items.insert(app_id, item);
}
}
@ -284,14 +306,14 @@ impl Module<gtk::Box> for LauncherModule {
match event {
ToplevelEvent::New(info) => {
let app_id = info.app_id.clone();
let app_id = resolve_app_id(&info.app_id);
let new_item = {
let mut items = lock!(items);
let item = items.get_mut(&info.app_id);
match item {
match items.get_mut(&app_id) {
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());
ItemOrWindow::Item(item)
@ -313,9 +335,10 @@ impl Module<gtk::Box> for LauncherModule {
}?;
}
ToplevelEvent::Update(info) => {
let app_id = resolve_app_id(&info.app_id);
// check if open, as updates can be sent as program closes
// 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_name(info.id, info.title.clone());
@ -325,27 +348,27 @@ impl Module<gtk::Box> for LauncherModule {
};
send_update(LauncherUpdate::Focus(
info.app_id.clone(),
app_id.clone(),
is_open && info.focused,
))
.await?;
send_update(LauncherUpdate::Title(
info.app_id.clone(),
app_id.clone(),
info.id,
info.title.clone(),
))
.await?;
}
ToplevelEvent::Remove(info) => {
let app_id = resolve_app_id(&info.app_id);
let remove_item = {
let mut items = lock!(items);
let item = items.get_mut(&info.app_id);
match item {
match items.get_mut(&app_id) {
Some(item) => {
item.unmerge_toplevel(&info);
if item.windows.is_empty() {
items.shift_remove(&info.app_id);
items.shift_remove(&app_id);
Some(ItemOrWindowId::Item)
} else {
Some(ItemOrWindowId::Window)
@ -357,15 +380,11 @@ impl Module<gtk::Box> for LauncherModule {
match remove_item {
Some(ItemOrWindowId::Item) => {
send_update(LauncherUpdate::RemoveItem(info.app_id.clone()))
.await?;
send_update(LauncherUpdate::RemoveItem(app_id.clone())).await?;
}
Some(ItemOrWindowId::Window) => {
send_update(LauncherUpdate::RemoveWindow(
info.app_id.clone(),
info.id,
))
.await?;
send_update(LauncherUpdate::RemoveWindow(app_id.clone(), info.id))
.await?;
}
None => {}
}
@ -388,7 +407,7 @@ impl Module<gtk::Box> for LauncherModule {
if let ItemEvent::OpenItem(app_id) = event {
match desktop_files.find(&app_id).await {
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),
Err(err) => error!("Failed to find parse file for {}: {}", app_id, err),

View file

@ -100,7 +100,11 @@ where
let tx = tx.clone();
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();
tx.send_spawn(ModuleUpdateEvent::ClosePopup);

View file

@ -1,18 +1,16 @@
use crate::clients::networkmanager::Client;
use crate::clients::networkmanager::dbus::{DeviceState, DeviceType};
use crate::clients::networkmanager::event::Event;
use crate::channels::{AsyncSenderExt, BroadcastReceiverExt};
use crate::clients::networkmanager::{Client, ClientState};
use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt;
use crate::image::Provider;
use crate::modules::{Module, ModuleInfo, ModuleParts, WidgetContext};
use crate::{module_impl, spawn};
use color_eyre::{Result, eyre::Ok};
use glib::spawn_future_local;
use gtk::prelude::{ContainerExt, WidgetExt};
use gtk::{Image, Orientation};
use color_eyre::Result;
use futures_lite::StreamExt;
use futures_signals::signal::SignalExt;
use gtk::prelude::ContainerExt;
use gtk::{Box as GtkBox, Image};
use serde::Deserialize;
use std::collections::HashMap;
use tokio::sync::{broadcast, mpsc};
use tokio::sync::mpsc::Receiver;
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
@ -28,29 +26,26 @@ const fn default_icon_size() -> i32 {
24
}
impl Module<gtk::Box> for NetworkManagerModule {
type SendMessage = Event;
impl Module<GtkBox> for NetworkManagerModule {
type SendMessage = ClientState;
type ReceiveMessage = ();
module_impl!("network_manager");
fn spawn_controller(
&self,
_info: &ModuleInfo,
context: &WidgetContext<Event, ()>,
_rx: mpsc::Receiver<()>,
_: &ModuleInfo,
context: &WidgetContext<ClientState, ()>,
_: Receiver<()>,
) -> Result<()> {
let client = context.try_client::<Client>()?;
// Should we be using context.tx with ModuleUpdateEvent::Update instead?
let tx = context.update_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)?;
}
let mut client_signal = client.subscribe().to_stream();
let tx = context.tx.clone();
Ok(())
spawn(async move {
while let Some(state) = client_signal.next().await {
tx.send_update(state).await;
}
});
Ok(())
@ -58,105 +53,50 @@ impl Module<gtk::Box> for NetworkManagerModule {
fn into_widget(
self,
context: WidgetContext<Event, ()>,
_info: &ModuleInfo,
) -> Result<ModuleParts<gtk::Box>> {
let container = gtk::Box::new(Orientation::Horizontal, 0);
context: WidgetContext<ClientState, ()>,
info: &ModuleInfo,
) -> Result<ModuleParts<GtkBox>> {
const INITIAL_ICON_NAME: &str = "content-loading-symbolic";
// Must be done here synchronously to avoid race condition
let rx = context.subscribe();
// We cannot use recv_glib_async here because the lifetimes don't work out
spawn_future_local(handle_update_events(
rx,
container.clone(),
self.icon_size,
context.ironbar.image_provider(),
));
let container = GtkBox::new(info.bar_position.orientation(), 0);
let icon = Image::new();
icon.add_class("icon");
container.add(&icon);
let image_provider = 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))
}
}
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::os::raw::{c_char, c_int};
use std::ptr;
use system_tray::item::IconPixmap;
/// Gets the GTK icon theme search paths by calling the FFI function.
/// Conveniently returns the result as a `HashSet`.
@ -45,10 +46,10 @@ pub fn get_image(
icon_theme: &IconTheme,
) -> Result<Image> {
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 {
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`.
/// The Pixbuf expects RGBA32 format, so some channel shuffling
/// 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;
let pixmap = item
.icon_pixmap
.as_ref()
.and_then(|pixmap| pixmap.first())
.ok_or_else(|| Report::msg("Failed to get pixmap from tray icon"))?;

View file

@ -181,9 +181,14 @@ fn on_update(
UpdateEvent::AttentionIcon(_icon) => {
warn!("received unimplemented NewAttentionIcon event");
}
UpdateEvent::Icon(icon) => {
if icon.as_ref() != menu_item.icon_name() {
menu_item.set_icon_name(icon);
UpdateEvent::Icon {
icon_name,
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) {
Ok(image) => menu_item.set_image(&image),
Err(_) => menu_item.show_label(),

View file

@ -1,13 +1,13 @@
use crate::channels::{AsyncSenderExt, BroadcastReceiverExt};
use crate::clients::volume::{self, Event};
use crate::config::{CommonConfig, LayoutConfig, TruncateMode};
use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, PopupButton, WidgetContext,
};
use crate::{lock, module_impl, spawn};
use gtk::prelude::*;
use gtk::{Button, Image, Label, Scale, ToggleButton};
use gtk::{Button, Image};
use serde::Deserialize;
use tokio::sync::mpsc;
use tracing::trace;
@ -15,48 +15,21 @@ use tracing::trace;
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
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")]
icon_size: i32,
// -- 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).
#[serde(flatten)]
pub common: Option<CommonConfig>,
}
const fn default_max_volume() -> f64 {
100.0
}
const fn default_icon_size() -> i32 {
24
}
#[derive(Debug, Clone)]
pub enum Update {
SinkChange(String),
SinkVolume(String, f64),
SinkMute(String, bool),
InputVolume(u32, f64),
InputMute(u32, bool),
}
pub enum Update {}
impl Module<Button> for VolumeModule {
type SendMessage = Event;
@ -68,7 +41,7 @@ impl Module<Button> for VolumeModule {
&self,
_info: &ModuleInfo,
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
_rx: mpsc::Receiver<Self::ReceiveMessage>,
) -> color_eyre::Result<()>
where
<Self as Module<Button>>::SendMessage: Clone,
@ -102,8 +75,8 @@ impl Module<Button> for VolumeModule {
tx.send_update(Event::AddSink(sink)).await;
}
for input in inputs {
tx.send_update(Event::AddInput(input)).await;
for _input in inputs {
tx.send_update(Event::AddInput).await;
}
// 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(())
}
@ -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 {
let icon_variant = if muted {
"muted"