From 646d34675c048c82bdd3c0004417ee353971e119 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Mon, 20 Mar 2023 23:24:21 +1100 Subject: [PATCH 01/54] Allow tokio on gtk thread --- crates/eww/src/server.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/eww/src/server.rs b/crates/eww/src/server.rs index 5a7ecc873..4106346c0 100644 --- a/crates/eww/src/server.rs +++ b/crates/eww/src/server.rs @@ -100,7 +100,7 @@ pub fn initialize_server( } // initialize all the handlers and tasks running asyncronously - init_async_part(app.paths.clone(), ui_send); + let tokio_handle = init_async_part(app.paths.clone(), ui_send); glib::MainContext::default().spawn_local(async move { // if an action was given to the daemon initially, execute it first. @@ -121,22 +121,26 @@ pub fn initialize_server( } }); + // allow the GTK main thread to do tokio things + let _g = tokio_handle.enter(); + gtk::main(); log::info!("main application thread finished"); Ok(ForkResult::Child) } -fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender) { +fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender) -> tokio::runtime::Handle { + let rt = tokio::runtime::Builder::new_multi_thread() + .thread_name("main-async-runtime") + .enable_all() + .build() + .expect("Failed to initialize tokio runtime"); + let handle = rt.handle().clone(); + std::thread::Builder::new() .name("outer-main-async-runtime".to_string()) .spawn(move || { - let rt = tokio::runtime::Builder::new_multi_thread() - .thread_name("main-async-runtime") - .enable_all() - .build() - .expect("Failed to initialize tokio runtime"); - rt.block_on(async { let filewatch_join_handle = { let ui_send = ui_send.clone(); @@ -168,6 +172,8 @@ fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender }) }) .expect("Failed to start outer-main-async-runtime thread"); + + handle } /// Watch configuration files for changes, sending reload events to the eww app when the files change. From 380fe9d2e510d8b6f20193305a16bdf1507078dc Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:40:15 +1000 Subject: [PATCH 02/54] Basic notifier host implementation --- Cargo.lock | 542 +++++++++++++++++- crates/notifier_host/Cargo.toml | 16 + crates/notifier_host/src/dbus/dbus_menu.xml | 69 +++ .../src/dbus/dbus_status_notifier_item.rs | 111 ++++ .../src/dbus/dbus_status_notifier_item.xml | 49 ++ .../src/dbus/dbus_status_notifier_watcher.rs | 53 ++ .../src/dbus/dbus_status_notifier_watcher.xml | 52 ++ crates/notifier_host/src/dbus/mod.rs | 11 + crates/notifier_host/src/error.rs | 11 + crates/notifier_host/src/host.rs | 65 +++ crates/notifier_host/src/item.rs | 195 +++++++ crates/notifier_host/src/lib.rs | 10 + flake.lock | 42 +- 13 files changed, 1211 insertions(+), 15 deletions(-) create mode 100644 crates/notifier_host/Cargo.toml create mode 100644 crates/notifier_host/src/dbus/dbus_menu.xml create mode 100644 crates/notifier_host/src/dbus/dbus_status_notifier_item.rs create mode 100644 crates/notifier_host/src/dbus/dbus_status_notifier_item.xml create mode 100644 crates/notifier_host/src/dbus/dbus_status_notifier_watcher.rs create mode 100644 crates/notifier_host/src/dbus/dbus_status_notifier_watcher.xml create mode 100644 crates/notifier_host/src/dbus/mod.rs create mode 100644 crates/notifier_host/src/error.rs create mode 100644 crates/notifier_host/src/host.rs create mode 100644 crates/notifier_host/src/item.rs create mode 100644 crates/notifier_host/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index e3e9ca9c8..0cd0e6729 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,91 @@ dependencies = [ "term", ] +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.23", + "slab", + "socket2 0.4.9", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-process" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" +dependencies = [ + "async-io", + "async-lock", + "autocfg", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 0.37.23", + "signal-hook", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-recursion" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "async-task" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" + [[package]] name = "async-trait" version = "0.1.73" @@ -166,6 +251,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "atomic-waker" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" + [[package]] name = "autocfg" version = "1.1.0" @@ -223,12 +314,42 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand 1.9.0", + "futures-lite", + "log", +] + [[package]] name = "bumpalo" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.4.0" @@ -432,6 +553,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" version = "0.15.7" @@ -478,6 +608,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -527,6 +666,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "darling" version = "0.14.4" @@ -562,6 +711,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -581,6 +741,16 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -629,6 +799,27 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "enumflags2" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -669,6 +860,12 @@ dependencies = [ "libc", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "eww" version = "0.4.0" @@ -734,6 +931,21 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "field-offset" version = "0.3.6" @@ -825,6 +1037,21 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-macro" version = "0.3.28" @@ -953,6 +1180,16 @@ dependencies = [ "x11", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gethostname" version = "0.2.3" @@ -1216,6 +1453,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "humantime" version = "2.1.0" @@ -1313,6 +1556,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -1320,7 +1574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix", + "rustix 0.38.8", "windows-sys 0.48.0", ] @@ -1471,6 +1725,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.5" @@ -1586,6 +1846,17 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "notifier_host" +version = "0.1.0" +dependencies = [ + "gtk", + "log", + "thiserror", + "tokio", + "zbus", +] + [[package]] name = "notify" version = "6.0.1" @@ -1647,6 +1918,16 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "pango" version = "0.17.10" @@ -1673,6 +1954,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1815,6 +2102,22 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2056,6 +2359,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" version = "0.38.8" @@ -2065,7 +2382,7 @@ dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.5", "windows-sys 0.48.0", ] @@ -2133,6 +2450,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "serde_spanned" version = "0.6.3" @@ -2142,6 +2470,27 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2223,6 +2572,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.3" @@ -2336,6 +2695,19 @@ version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand 2.0.0", + "redox_syscall 0.3.5", + "rustix 0.38.8", + "windows-sys 0.48.0", +] + [[package]] name = "term" version = "0.7.0" @@ -2410,8 +2782,9 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.3", "tokio-macros", + "tracing", "windows-sys 0.48.0", ] @@ -2473,6 +2846,54 @@ dependencies = [ "winnow", ] +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uds_windows" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] + [[package]] name = "unescape" version = "0.1.0" @@ -2524,6 +2945,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.3" @@ -2822,6 +3249,16 @@ dependencies = [ "nix 0.25.1", ] +[[package]] +name = "xdg-home" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" +dependencies = [ + "nix 0.26.2", + "winapi", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -2860,3 +3297,102 @@ dependencies = [ "strum", "thiserror", ] + +[[package]] +name = "zbus" +version = "3.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" +dependencies = [ + "async-broadcast", + "async-process", + "async-recursion", + "async-trait", + "byteorder", + "derivative", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.26.2", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zvariant" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/crates/notifier_host/Cargo.toml b/crates/notifier_host/Cargo.toml new file mode 100644 index 000000000..7f5e922b5 --- /dev/null +++ b/crates/notifier_host/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "notifier_host" +version = "0.1.0" +authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"] +edition = "2021" +licence = "MIT" +description = "SystemNotifierHost implementation" +repository = "https://github.com/elkowar/eww" +homepage = "https://github.com/elkowar/eww" + +[dependencies] +gtk = { version = "0.17.1" } +log = "0.4" +thiserror = "1.0" +tokio = { version = "1.31.0", features = ["full"] } +zbus = { version = "3.7.0", default-features = false, features = ["tokio"] } diff --git a/crates/notifier_host/src/dbus/dbus_menu.xml b/crates/notifier_host/src/dbus/dbus_menu.xml new file mode 100644 index 000000000..ae5d79067 --- /dev/null +++ b/crates/notifier_host/src/dbus/dbus_menu.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs b/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs new file mode 100644 index 000000000..e20e32d68 --- /dev/null +++ b/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs @@ -0,0 +1,111 @@ +//! # DBus interface proxy for: `org.kde.StatusNotifierItem` +//! +//! This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data. +//! Source: `dbus-status-notifier-item.xml`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the +//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html) +//! section of the zbus documentation. + +use zbus::dbus_proxy; + +#[dbus_proxy(interface = "org.kde.StatusNotifierItem", assume_defaults = true)] +trait StatusNotifierItem { + /// Activate method + fn activate(&self, x: i32, y: i32) -> zbus::Result<()>; + + /// ContextMenu method + fn context_menu(&self, x: i32, y: i32) -> zbus::Result<()>; + + /// Scroll method + fn scroll(&self, delta: i32, orientation: &str) -> zbus::Result<()>; + + /// SecondaryActivate method + fn secondary_activate(&self, x: i32, y: i32) -> zbus::Result<()>; + + /// NewAttentionIcon signal + #[dbus_proxy(signal)] + fn new_attention_icon(&self) -> zbus::Result<()>; + + /// NewIcon signal + #[dbus_proxy(signal)] + fn new_icon(&self) -> zbus::Result<()>; + + /// NewOverlayIcon signal + #[dbus_proxy(signal)] + fn new_overlay_icon(&self) -> zbus::Result<()>; + + /// NewStatus signal + #[dbus_proxy(signal)] + fn new_status(&self, status: &str) -> zbus::Result<()>; + + /// NewTitle signal + #[dbus_proxy(signal)] + fn new_title(&self) -> zbus::Result<()>; + + /// NewToolTip signal + #[dbus_proxy(signal)] + fn new_tool_tip(&self) -> zbus::Result<()>; + + /// AttentionIconName property + #[dbus_proxy(property)] + fn attention_icon_name(&self) -> zbus::Result; + + /// AttentionIconPixmap property + #[dbus_proxy(property)] + fn attention_icon_pixmap(&self) -> zbus::Result)>>; + + /// AttentionMovieName property + #[dbus_proxy(property)] + fn attention_movie_name(&self) -> zbus::Result; + + /// Category property + #[dbus_proxy(property)] + fn category(&self) -> zbus::Result; + + /// IconName property + #[dbus_proxy(property)] + fn icon_name(&self) -> zbus::Result; + + /// IconPixmap property + #[dbus_proxy(property)] + fn icon_pixmap(&self) -> zbus::Result)>>; + + /// IconThemePath property + #[dbus_proxy(property)] + fn icon_theme_path(&self) -> zbus::Result; + + /// Id property + #[dbus_proxy(property)] + fn id(&self) -> zbus::Result; + + /// ItemIsMenu property + #[dbus_proxy(property)] + fn item_is_menu(&self) -> zbus::Result; + + /// Menu property + #[dbus_proxy(property)] + fn menu(&self) -> zbus::Result; + + /// OverlayIconName property + #[dbus_proxy(property)] + fn overlay_icon_name(&self) -> zbus::Result; + + /// OverlayIconPixmap property + #[dbus_proxy(property)] + fn overlay_icon_pixmap(&self) -> zbus::Result)>>; + + /// Status property + #[dbus_proxy(property)] + fn status(&self) -> zbus::Result; + + /// Title property + #[dbus_proxy(property)] + fn title(&self) -> zbus::Result; + + /// ToolTip property + #[dbus_proxy(property)] + fn tool_tip(&self) -> zbus::Result<(String, Vec<(i32, i32, Vec)>)>; +} diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_item.xml b/crates/notifier_host/src/dbus/dbus_status_notifier_item.xml new file mode 100644 index 000000000..c33cd8460 --- /dev/null +++ b/crates/notifier_host/src/dbus/dbus_status_notifier_item.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.rs b/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.rs new file mode 100644 index 000000000..8c352793f --- /dev/null +++ b/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.rs @@ -0,0 +1,53 @@ +//! # DBus interface proxy for: `org.kde.StatusNotifierWatcher` +//! +//! This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data. +//! Source: `dbus-status-notifier-watcher.xml`. +//! +//! You may prefer to adapt it, instead of using it verbatim. +//! +//! More information can be found in the +//! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html) +//! section of the zbus documentation. + +use zbus::dbus_proxy; + +#[dbus_proxy( + default_service="org.kde.StatusNotifierWatcher", + interface="org.kde.StatusNotifierWatcher", + default_path="/StatusNotifierWatcher", +)] +trait StatusNotifierWatcher { + /// RegisterStatusNotifierHost method + fn register_status_notifier_host(&self, service: &str) -> zbus::Result<()>; + + /// RegisterStatusNotifierItem method + fn register_status_notifier_item(&self, service: &str) -> zbus::Result<()>; + + /// StatusNotifierHostRegistered signal + #[dbus_proxy(signal)] + fn status_notifier_host_registered(&self) -> zbus::Result<()>; + + /// StatusNotifierHostUnregistered signal + #[dbus_proxy(signal)] + fn status_notifier_host_unregistered(&self) -> zbus::Result<()>; + + /// StatusNotifierItemRegistered signal + #[dbus_proxy(signal)] + fn status_notifier_item_registered(&self, service: &str) -> zbus::Result<()>; + + /// StatusNotifierItemUnregistered signal + #[dbus_proxy(signal)] + fn status_notifier_item_unregistered(&self, service: &str) -> zbus::Result<()>; + + /// IsStatusNotifierHostRegistered property + #[dbus_proxy(property)] + fn is_status_notifier_host_registered(&self) -> zbus::Result; + + /// ProtocolVersion property + #[dbus_proxy(property)] + fn protocol_version(&self) -> zbus::Result; + + /// RegisteredStatusNotifierItems property + #[dbus_proxy(property)] + fn registered_status_notifier_items(&self) -> zbus::Result>; +} diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.xml b/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.xml new file mode 100644 index 000000000..6370e4dd2 --- /dev/null +++ b/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/crates/notifier_host/src/dbus/mod.rs b/crates/notifier_host/src/dbus/mod.rs new file mode 100644 index 000000000..2852ce8fd --- /dev/null +++ b/crates/notifier_host/src/dbus/mod.rs @@ -0,0 +1,11 @@ +//! # DBus interface proxies +//! +//! The interface XML files are taken from +//! [Waybar](https://github.com/Alexays/Waybar/tree/master/protocol), and the proxies generated +//! with [zbus-gen](https://docs.rs/crate/zbus_xmlgen/latest). + +mod dbus_status_notifier_item; +pub use dbus_status_notifier_item::*; + +mod dbus_status_notifier_watcher; +pub use dbus_status_notifier_watcher::*; diff --git a/crates/notifier_host/src/error.rs b/crates/notifier_host/src/error.rs new file mode 100644 index 000000000..a8a2d6f80 --- /dev/null +++ b/crates/notifier_host/src/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Dbus connection error")] + DbusError(#[from] zbus::Error), + #[error("Service path {0} was not understood")] + DbusAddressError(String), +} + +pub type Result = std::result::Result; diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs new file mode 100644 index 000000000..79c1235a3 --- /dev/null +++ b/crates/notifier_host/src/host.rs @@ -0,0 +1,65 @@ +use crate::*; + +use zbus::export::ordered_stream::{self, OrderedStreamExt}; + +pub trait Host { + fn add_item(&mut self, id: &str, item: Item); + fn remove_item(&mut self, id: &str); +} + +// Attach to dbus and forward events to Host. +// +// This task is blocking and won't return unless an error occurs. +pub async fn serve(host: &mut dyn Host, id: &str) -> Result<()> { + // From : + // + // Instances of this service are registered on the Dbus session bus, under a name on the + // form org.freedesktop.StatusNotifierHost-id where id is an unique identifier, that keeps + // the names unique on the bus, such as the process-id of the application or another type + // of identifier if more that one StatusNotifierHost is registered by the same process. + + let wellknown_name = format!("org.freedesktop.StatusNotifierHost-{}-{}", std::process::id(), id); + let con = zbus::ConnectionBuilder::session()? + .name(wellknown_name.as_str())? + .build() + .await?; + + // register ourself to StatusNotifierWatcher + let snw = dbus::StatusNotifierWatcherProxy::new(&con).await?; + snw.register_status_notifier_host(&wellknown_name).await?; + + // initial items first + for svc in snw.registered_status_notifier_items().await? { + let item = Item::from_address(&con, &svc).await?; + host.add_item(&svc, item); + } + + // TODO this is a race condition? we might miss items that appear at this time + + enum ItemEvent { + NewItem(dbus::StatusNotifierItemRegistered), + GoneItem(dbus::StatusNotifierItemUnregistered), + } + + let new_items = snw.receive_status_notifier_item_registered().await?; + let gone_items = snw.receive_status_notifier_item_unregistered().await?; + let mut ev_stream = ordered_stream::join( + OrderedStreamExt::map(new_items, ItemEvent::NewItem), + OrderedStreamExt::map(gone_items, ItemEvent::GoneItem), + ); + while let Some(ev) = ev_stream.next().await { + match ev { + ItemEvent::NewItem(sig) => { + let args = sig.args()?; + let item = Item::from_address(&con, args.service).await?; + host.add_item(args.service, item); + }, + ItemEvent::GoneItem(sig) => { + let args = sig.args()?; + host.remove_item(args.service); + }, + } + } + + Ok(()) +} diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs new file mode 100644 index 000000000..d201a0b2a --- /dev/null +++ b/crates/notifier_host/src/item.rs @@ -0,0 +1,195 @@ +use crate::*; + +use log; +use gtk::{self, prelude::*}; +use zbus::export::ordered_stream::OrderedStreamExt; +use tokio::sync::watch; + +#[derive(Debug, Clone, Copy)] +pub enum Status { + /// The item doesn't convey important information to the user, it can be considered an "idle" + /// status and is likely that visualizations will chose to hide it. + Passive, + /// The item is active, is more important that the item will be shown in some way to the user. + Active, + /// The item carries really important information for the user, such as battery charge running + /// out and is wants to incentive the direct user intervention. Visualizations should emphasize + /// in some way the items with NeedsAttention status. + NeedsAttention, +} + +impl std::str::FromStr for Status { + type Err = (); + + fn from_str(s: &str) -> std::result::Result { + match s { + "Passive" => Ok(Status::Passive), + "Active" => Ok(Status::Active), + "NeedsAttention" => Ok(Status::NeedsAttention), + _ => Err(()), + } + } +} + +#[derive(Clone, Debug)] +pub struct Item { + pub sni: dbus::StatusNotifierItemProxy<'static>, + status_rx: watch::Receiver, + title_rx: watch::Receiver, +} + +/// Split a sevice name e.g. `:1.50:/org/ayatana/NotificationItem/nm_applet` into the address and +/// path. +/// +/// Original logic from +fn split_service_name(service: &str) -> Result<(String, String)> { + if let Some((addr, path)) = service.split_once('/') { + Ok((addr.to_owned(), format!("/{}", path))) + } else if service.contains(':') { + let addr = service.split(':').skip(1).next(); + // Some StatusNotifierItems will not return an object path, in that case we fallback + // to the default path. + if let Some(addr) = addr { + Ok((addr.to_owned(), "/StatusNotifierItem".to_owned())) + } else { + Err(Error::DbusAddressError(service.to_owned())) + } + } else { + Err(Error::DbusAddressError(service.to_owned())) + } +} + +impl Item { + pub async fn from_address(con: &zbus::Connection, addr: &str) -> Result { + let (addr, path) = split_service_name(addr)?; + let sni = dbus::StatusNotifierItemProxy::builder(con) + .destination(addr)? + .path(path)? + .build() + .await?; + + let (status_tx, status_rx) = watch::channel(sni.status().await?.parse().unwrap()); + tokio::spawn({ + let sni = sni.clone(); + async move { + let mut new_status_stream = sni.receive_new_status().await.unwrap(); + while let Some(sig) = new_status_stream.next().await { + let args = sig.args().unwrap(); + let status: Status = args.status.parse().unwrap(); + status_tx.send_replace(status); + } + } + }); + + let (title_tx, title_rx) = watch::channel(sni.title().await?); + tokio::spawn({ + let sni = sni.clone(); + async move { + let mut new_title_stream = sni.receive_new_title().await.unwrap(); + while let Some(_) = new_title_stream.next().await { + let title = sni.title().await.unwrap(); + title_tx.send_replace(title); + } + } + }); + + Ok(Item { + sni, + status_rx, + title_rx, + }) + } + + pub fn status(&self) -> watch::Receiver { + self.status_rx.clone() + } + + pub fn title(&self) -> watch::Receiver { + self.title_rx.clone() + } +} + +#[derive(thiserror::Error, Debug)] +pub enum IconError { + #[error("Dbus error")] + DbusError(#[from] zbus::Error), + #[error("Failed to load icon {icon_name:?} from theme {theme_path:?}")] + LoadIconFromTheme { + icon_name: String, + theme_path: String, + source: gtk::glib::Error, + }, + #[error("Failed to load icon {icon_name:?} from default theme")] + LoadIconFromDefaultTheme { + icon_name: String, + source: gtk::glib::Error, + }, +} + +impl Item { + pub fn load_pixmap(width: i32, height: i32, mut data: Vec) -> gtk::Image { + // We need to convert data from ARGB32 to RGBA32 + for chunk in data.chunks_mut(4) { + let a = chunk[0]; + let r = chunk[1]; + let g = chunk[2]; + let b = chunk[3]; + chunk[0] = r; + chunk[1] = g; + chunk[2] = b; + chunk[3] = a; + } + + let pixmap = gtk::gdk_pixbuf::Pixbuf::from_bytes( + >k::glib::Bytes::from_owned(data), + gtk::gdk_pixbuf::Colorspace::Rgb, + true, + 8, + width, + height, + width * 4, + ); + gtk::Image::from_pixbuf(Some(&pixmap)) + } + + pub async fn icon(&self, size: i32) -> std::result::Result { + let icon_name = self.sni.icon_name().await?; + let icon_theme_path = self.sni.icon_theme_path().await?; + + if icon_theme_path != "" { + // icon supplied a theme path, so only look there + let theme = gtk::IconTheme::new(); + theme.prepend_search_path(&icon_theme_path); + + return match theme.load_icon(&icon_name, size, gtk::IconLookupFlags::FORCE_SIZE) { + Err(e) => Err(IconError::LoadIconFromTheme { + icon_name, + theme_path: icon_theme_path, + source: e, + }), + Ok(pb) => return Ok(pb.unwrap()), + } + } + + // fallback to default theme + let theme = gtk::IconTheme::default().expect("Could not get default gtk theme"); + match theme.load_icon(&icon_name, size, gtk::IconLookupFlags::FORCE_SIZE) { + // TODO specifically match on icon missing here + Err(e) => log::warn!("Could not find icon {:?} in default theme: {}", &icon_name, e), + Ok(pb) => return Ok(pb.unwrap()), + } + + // "Visualizations are encouraged to prefer icon names over icon pixmaps if both are available." + // TODO icon_pixmap + + // fallback to default icon + let theme = gtk::IconTheme::default().expect("Could not get default gtk theme"); + return match theme.load_icon("image-missing", size, gtk::IconLookupFlags::FORCE_SIZE) { + Err(e) => Err(IconError::LoadIconFromDefaultTheme { + icon_name: "image-missing".to_owned(), + source: e, + }), + Ok(pb) => return Ok(pb.unwrap()), + } + } +} diff --git a/crates/notifier_host/src/lib.rs b/crates/notifier_host/src/lib.rs new file mode 100644 index 000000000..98adcfbd3 --- /dev/null +++ b/crates/notifier_host/src/lib.rs @@ -0,0 +1,10 @@ +pub mod dbus; + +mod error; +pub use error::*; + +mod host; +pub use host::*; + +mod item; +pub use item::*; diff --git a/flake.lock b/flake.lock index 3cafe2692..f7cd91cff 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1650374568, - "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", "owner": "edolstra", "repo": "flake-compat", - "rev": "b4a34015c698c7793d592d66adbab377907a2be8", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", "type": "github" }, "original": { @@ -17,12 +17,15 @@ } }, "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1656928814, - "narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=", + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "owner": "numtide", "repo": "flake-utils", - "rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", "type": "github" }, "original": { @@ -33,11 +36,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1661353537, - "narHash": "sha256-1E2IGPajOsrkR49mM5h55OtYnU0dGyre6gl60NXKITE=", + "lastModified": 1692463654, + "narHash": "sha256-F8hZmsQINI+S6UROM4jyxAMbQLtzE44pI8Nk6NtMdao=", "owner": "nixos", "repo": "nixpkgs", - "rev": "0e304ff0d9db453a4b230e9386418fd974d5804a", + "rev": "ca3c9ac9f4cdd4bea19f592b32bb59b74ab7d783", "type": "github" }, "original": { @@ -62,11 +65,11 @@ ] }, "locked": { - "lastModified": 1661655464, - "narHash": "sha256-by9Hb0mNVdiCR7TBvUHIgDb0QIv3znp8VMGh7Bl35VQ=", + "lastModified": 1692410823, + "narHash": "sha256-YM1QCenpghNqgleUmoCJUArTuMEBqScyQuhepA6JZaI=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "0c4c1432353e12b325d1472bea99e364871d2cb3", + "rev": "598b2f04ed252eb5808b108d7a10084c0c548753", "type": "github" }, "original": { @@ -74,6 +77,21 @@ "repo": "rust-overlay", "type": "github" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", From bde08aa94c9cd2dc00191f27c29cecb215b34342 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Mon, 20 Mar 2023 17:20:51 +1100 Subject: [PATCH 03/54] Implement systray widget --- Cargo.lock | 1 + crates/eww/Cargo.toml | 1 + crates/eww/src/widgets/mod.rs | 1 + crates/eww/src/widgets/systray.rs | 76 ++++++++++++++++++++ crates/eww/src/widgets/widget_definitions.rs | 29 +++++++- 5 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 crates/eww/src/widgets/systray.rs diff --git a/Cargo.lock b/Cargo.lock index 0cd0e6729..793c01435 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -894,6 +894,7 @@ dependencies = [ "log", "maplit", "nix 0.26.2", + "notifier_host", "notify", "once_cell", "pretty_env_logger", diff --git a/crates/eww/Cargo.toml b/crates/eww/Cargo.toml index fe40aec29..e11ad61a1 100644 --- a/crates/eww/Cargo.toml +++ b/crates/eww/Cargo.toml @@ -65,3 +65,4 @@ codespan-reporting = "0.11" simplexpr = { version = "0.1.0", path = "../simplexpr" } eww_shared_util = { version = "0.1.0", path = "../eww_shared_util" } yuck = { version = "0.1.0", path = "../yuck", default-features = false} +notifier_host = { version = "0.1.0", path = "../notifier_host" } diff --git a/crates/eww/src/widgets/mod.rs b/crates/eww/src/widgets/mod.rs index e5e25b7c6..33ad41811 100644 --- a/crates/eww/src/widgets/mod.rs +++ b/crates/eww/src/widgets/mod.rs @@ -4,6 +4,7 @@ pub mod build_widget; pub mod circular_progressbar; pub mod def_widget_macro; pub mod graph; +mod systray; pub mod transform; pub mod widget_definitions; diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs new file mode 100644 index 000000000..fc5cdf444 --- /dev/null +++ b/crates/eww/src/widgets/systray.rs @@ -0,0 +1,76 @@ +#![allow(unused)] + +use gtk::prelude::*; +use notifier_host; + +async fn gtk_run(f: F) -> R +where + F: FnOnce() -> R1 + 'static, + R1: std::future::Future, + R: 'static, +{ + let (tx, rx) = tokio::sync::oneshot::channel(); + glib::MainContext::default().spawn_local(async move { + let r = f().await; + tx.send(r).map_err(|_| ()).unwrap(); + }); + rx.await.unwrap() +} + +struct Host { + menubar: gtk::MenuBar, + items: std::collections::HashMap, +} + +async fn watch_foreach(mut rx: tokio::sync::watch::Receiver, mut f: impl FnMut(&T)) { + f(&rx.borrow()); + while rx.changed().await.is_ok() { + f(&rx.borrow()); + } +} + +impl notifier_host::Host for Host { + fn add_item(&mut self, id: &str, item: notifier_host::Item) { + let mi = gtk::MenuItem::new(); + self.menubar.add(&mi); + if let Some(old_mi) = self.items.insert(id.to_string(), mi.clone()) { + self.menubar.remove(&old_mi); + } + + // maintain title + glib::MainContext::default().spawn_local({ + let mi = mi.clone(); + watch_foreach(item.title(), move |title| { + mi.set_tooltip_text(Some(title)); + }) + }); + + let icon = gtk::Image::new(); + mi.add(&icon); + + glib::MainContext::default().spawn_local(async move { + let img = item.icon(24).await.unwrap(); + icon.set_from_pixbuf(Some(&img)); + }); + + mi.show_all(); + } + fn remove_item(&mut self, id: &str) { + if let Some(mi) = self.items.get(id) { + self.menubar.remove(mi); + } else { + log::warn!("Tried to remove nonexistent item {:?} from systray", id); + } + } +} + +pub fn maintain_menubar(menubar: gtk::MenuBar) { + menubar.show_all(); + glib::MainContext::default().spawn_local(async move { + let mut host = Host { + menubar, + items: std::collections::HashMap::new(), + }; + notifier_host::serve(&mut host, "eww").await.unwrap(); + }); +} diff --git a/crates/eww/src/widgets/widget_definitions.rs b/crates/eww/src/widgets/widget_definitions.rs index 07a081d35..d8cd233ce 100644 --- a/crates/eww/src/widgets/widget_definitions.rs +++ b/crates/eww/src/widgets/widget_definitions.rs @@ -3,7 +3,7 @@ use super::{build_widget::BuilderArgs, circular_progressbar::*, run_command, tra use crate::{ def_widget, enum_parse, error_handling_ctx, util::{list_difference, unindent}, - widgets::build_widget::build_gtk_widget, + widgets::{build_widget::build_gtk_widget, systray}, }; use anyhow::{anyhow, Context, Result}; use codespan_reporting::diagnostic::Severity; @@ -80,6 +80,7 @@ pub const BUILTIN_WIDGET_NAMES: &[&str] = &[ WIDGET_NAME_REVEALER, WIDGET_NAME_SCROLL, WIDGET_NAME_OVERLAY, + WIDGET_NAME_SYSTRAY, ]; //// widget definitions @@ -107,6 +108,7 @@ pub(super) fn widget_use_to_gtk_widget(bargs: &mut BuilderArgs) -> Result build_gtk_revealer(bargs)?.upcast(), WIDGET_NAME_SCROLL => build_gtk_scrolledwindow(bargs)?.upcast(), WIDGET_NAME_OVERLAY => build_gtk_overlay(bargs)?.upcast(), + WIDGET_NAME_SYSTRAY => build_systray(bargs)?.upcast(), _ => { return Err(DiagError(gen_diagnostic! { msg = format!("referenced unknown widget `{}`", bargs.widget_use.name), @@ -1042,6 +1044,21 @@ fn build_graph(bargs: &mut BuilderArgs) -> Result { Ok(w) } +const WIDGET_NAME_SYSTRAY: &str = "systray"; +/// @widget systray +/// @desc Tray for system notifier icons +fn build_systray(bargs: &mut BuilderArgs) -> Result { + let w = gtk::MenuBar::new(); + + def_widget!(bargs, _g, w, { + // @prop pack-direction - how to arrange tray items + prop(pack_direction: as_string) { w.set_pack_direction(parse_packdirection(&pack_direction)?); }, + }); + + systray::maintain_menubar(w.clone()); + Ok(w) +} + /// @var orientation - "vertical", "v", "horizontal", "h" fn parse_orientation(o: &str) -> Result { enum_parse! { "orientation", o, @@ -1096,6 +1113,16 @@ fn parse_justification(j: &str) -> Result { } } +/// @var packdirection - "right", "ltr", "left", "rtl", "down", "ttb", "up", "btt" +fn parse_packdirection(o: &str) -> Result { + enum_parse! { "packdirection", o, + "right" | "ltr" => gtk::PackDirection::Ltr, + "left" | "rtl" => gtk::PackDirection::Rtl, + "down" | "ttb" => gtk::PackDirection::Ttb, + "up" | "btt" => gtk::PackDirection::Btt, + } +} + /// Connect a function to the first map event of a widget. After that first map, the handler will get disconnected. fn connect_first_map, F: Fn(&W) + 'static>(widget: &W, func: F) { let signal_handler_id = std::rc::Rc::new(std::cell::RefCell::new(None)); From ba9d9321b732a1b292b69fa383abf9f2da2462a3 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Wed, 5 Apr 2023 17:29:05 +1000 Subject: [PATCH 04/54] Use dbusmenu-gtk3 --- Cargo.lock | 54 +++++++++++++++++++++++++++++++ crates/eww/src/widgets/systray.rs | 14 +++++--- crates/notifier_host/Cargo.toml | 1 + crates/notifier_host/src/item.rs | 6 ++++ 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 793c01435..8ac06bb9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -711,6 +711,59 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "dbusmenu-glib" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd1629dccc5775e0668eb9f07e78d1b88392a63efc34033a18c87ea44318b894" +dependencies = [ + "dbusmenu-glib-sys", + "glib", + "libc", +] + +[[package]] +name = "dbusmenu-glib-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff9ed40330718c94342b953c997ac19d840db07a7710fe35b45a5d3a3a1d6eb" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "dbusmenu-gtk3" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15e02bf53eed790cee4d6111643529bf2fdb82c9e61242407d254612f98436e" +dependencies = [ + "atk", + "dbusmenu-glib", + "dbusmenu-gtk3-sys", + "glib", + "gtk", + "libc", +] + +[[package]] +name = "dbusmenu-gtk3-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f30ba5f8aec0e38a84c579bc8ee3db6f6417b201e729fdd96a23d1f61cb6eca" +dependencies = [ + "dbusmenu-glib-sys", + "gdk-pixbuf-sys", + "gdk-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "libc", + "system-deps", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1851,6 +1904,7 @@ dependencies = [ name = "notifier_host" version = "0.1.0" dependencies = [ + "dbusmenu-gtk3", "gtk", "log", "thiserror", diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index fc5cdf444..2844fdffb 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -48,11 +48,17 @@ impl notifier_host::Host for Host { let icon = gtk::Image::new(); mi.add(&icon); - glib::MainContext::default().spawn_local(async move { - let img = item.icon(24).await.unwrap(); - icon.set_from_pixbuf(Some(&img)); - }); + // other initialisation + glib::MainContext::default().spawn_local({ + let mi = mi.clone(); + async move { + let img = item.icon(24).await.unwrap(); + icon.set_from_pixbuf(Some(&img)); + let menu = item.menu().await.unwrap(); + mi.set_submenu(Some(&menu)); + } + }); mi.show_all(); } fn remove_item(&mut self, id: &str) { diff --git a/crates/notifier_host/Cargo.toml b/crates/notifier_host/Cargo.toml index 7f5e922b5..5f0043578 100644 --- a/crates/notifier_host/Cargo.toml +++ b/crates/notifier_host/Cargo.toml @@ -14,3 +14,4 @@ log = "0.4" thiserror = "1.0" tokio = { version = "1.31.0", features = ["full"] } zbus = { version = "3.7.0", default-features = false, features = ["tokio"] } +dbusmenu-gtk3 = "0.1.0" diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index d201a0b2a..21df00546 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -192,4 +192,10 @@ impl Item { Ok(pb) => return Ok(pb.unwrap()), } } + + pub async fn menu(&self) -> Result { + // TODO better handling if menu() method doesn't exist + let menu = dbusmenu_gtk3::Menu::new(self.sni.destination(), &self.sni.menu().await?); + Ok(menu.upcast()) + } } From 7cb939830e626d89b8880da329d98628f29ec3ea Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Wed, 5 Apr 2023 17:29:36 +1000 Subject: [PATCH 05/54] Update flake.nix --- flake.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flake.nix b/flake.nix index d09738385..456564e97 100644 --- a/flake.nix +++ b/flake.nix @@ -61,6 +61,10 @@ rust rust-analyzer-unwrapped gcc + glib + gdk-pixbuf + librsvg + libdbusmenu-gtk3 gtk3 gtk-layer-shell pkg-config From 176c90054d07890a69c4f7fc04aea546f75d6a7d Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Wed, 5 Apr 2023 17:43:58 +1000 Subject: [PATCH 06/54] US spelling of license --- crates/notifier_host/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/notifier_host/Cargo.toml b/crates/notifier_host/Cargo.toml index 5f0043578..35c8c079a 100644 --- a/crates/notifier_host/Cargo.toml +++ b/crates/notifier_host/Cargo.toml @@ -3,7 +3,7 @@ name = "notifier_host" version = "0.1.0" authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"] edition = "2021" -licence = "MIT" +license = "MIT" description = "SystemNotifierHost implementation" repository = "https://github.com/elkowar/eww" homepage = "https://github.com/elkowar/eww" From 923d1b239b648d659e63d196eccad7f2afe176e8 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sat, 8 Apr 2023 23:42:07 +1000 Subject: [PATCH 07/54] Fix possible TOCTOU --- crates/notifier_host/src/host.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index 79c1235a3..aed2ea338 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -28,21 +28,21 @@ pub async fn serve(host: &mut dyn Host, id: &str) -> Result<()> { let snw = dbus::StatusNotifierWatcherProxy::new(&con).await?; snw.register_status_notifier_host(&wellknown_name).await?; - // initial items first - for svc in snw.registered_status_notifier_items().await? { - let item = Item::from_address(&con, &svc).await?; - host.add_item(&svc, item); - } - - // TODO this is a race condition? we might miss items that appear at this time - enum ItemEvent { NewItem(dbus::StatusNotifierItemRegistered), GoneItem(dbus::StatusNotifierItemUnregistered), } + // start listening to these streams let new_items = snw.receive_status_notifier_item_registered().await?; let gone_items = snw.receive_status_notifier_item_unregistered().await?; + + // initial items first + for svc in snw.registered_status_notifier_items().await? { + let item = Item::from_address(&con, &svc).await?; + host.add_item(&svc, item); + } + let mut ev_stream = ordered_stream::join( OrderedStreamExt::map(new_items, ItemEvent::NewItem), OrderedStreamExt::map(gone_items, ItemEvent::GoneItem), From bfaccba57067c32307f94bec47a9b30692a7be1a Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sat, 8 Apr 2023 23:43:29 +1000 Subject: [PATCH 08/54] Change how hosts are started --- Cargo.lock | 1 + crates/eww/Cargo.toml | 1 + crates/eww/src/widgets/systray.rs | 4 +++- crates/notifier_host/src/host.rs | 30 +++++++++++++++++++++--------- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ac06bb9b..99c3a7023 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -963,6 +963,7 @@ dependencies = [ "wait-timeout", "x11rb", "yuck", + "zbus", ] [[package]] diff --git a/crates/eww/Cargo.toml b/crates/eww/Cargo.toml index e11ad61a1..e8def8a1f 100644 --- a/crates/eww/Cargo.toml +++ b/crates/eww/Cargo.toml @@ -48,6 +48,7 @@ once_cell = "1.18" nix = "0.26.2" simple-signal = "1.1" unescape = "0.1" +zbus = { version = "3.7.0", default-features = false, features = ["tokio"] } tokio = { version = "1.31.0", features = ["full"] } futures = "0.3.28" diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 2844fdffb..417a0f771 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -73,10 +73,12 @@ impl notifier_host::Host for Host { pub fn maintain_menubar(menubar: gtk::MenuBar) { menubar.show_all(); glib::MainContext::default().spawn_local(async move { + let con = zbus::Connection::session().await.unwrap(); + let mut host = Host { menubar, items: std::collections::HashMap::new(), }; - notifier_host::serve(&mut host, "eww").await.unwrap(); + notifier_host::host_on(&mut host, &con).await.unwrap(); }); } diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index aed2ea338..962ac4de2 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -7,10 +7,10 @@ pub trait Host { fn remove_item(&mut self, id: &str); } -// Attach to dbus and forward events to Host. -// -// This task is blocking and won't return unless an error occurs. -pub async fn serve(host: &mut dyn Host, id: &str) -> Result<()> { +/// Attach to dbus and forward events to Host. +/// +/// This async function won't complete unless an error occurs. +pub async fn host_on(host: &mut dyn Host, con: &zbus::Connection) -> Result<()> { // From : // // Instances of this service are registered on the Dbus session bus, under a name on the @@ -18,11 +18,23 @@ pub async fn serve(host: &mut dyn Host, id: &str) -> Result<()> { // the names unique on the bus, such as the process-id of the application or another type // of identifier if more that one StatusNotifierHost is registered by the same process. - let wellknown_name = format!("org.freedesktop.StatusNotifierHost-{}-{}", std::process::id(), id); - let con = zbus::ConnectionBuilder::session()? - .name(wellknown_name.as_str())? - .build() - .await?; + // pick a new wellknown_name + let pid = std::process::id(); + let mut i = 0; + let wellknown_name = loop { + let wellknown_name = format!("org.freedesktop.StatusNotifierHost-{}-{}", pid, i); + let flags = [zbus::fdo::RequestNameFlags::DoNotQueue]; + + use zbus::fdo::RequestNameReply::*; + match con.request_name_with_flags(wellknown_name.as_str(), flags.into_iter().collect()).await? { + PrimaryOwner => break wellknown_name, + Exists => {}, + AlreadyOwner => {}, // we choose to not use an existing owner, is this correct? + InQueue => panic!("request_name_with_flags returned InQueue even though we specified DoNotQueue"), + }; + + i += 1; + }; // register ourself to StatusNotifierWatcher let snw = dbus::StatusNotifierWatcherProxy::new(&con).await?; From 2b05d2806cc2049251a753747f99b7f6d11545eb Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sat, 8 Apr 2023 23:43:45 +1000 Subject: [PATCH 09/54] Add watcher --- crates/eww/src/widgets/systray.rs | 2 + crates/notifier_host/src/lib.rs | 3 + crates/notifier_host/src/watcher.rs | 233 ++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 crates/notifier_host/src/watcher.rs diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 417a0f771..70a6d84b3 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -75,6 +75,8 @@ pub fn maintain_menubar(menubar: gtk::MenuBar) { glib::MainContext::default().spawn_local(async move { let con = zbus::Connection::session().await.unwrap(); + notifier_host::Watcher::new().run_on(&con).await.unwrap(); + let mut host = Host { menubar, items: std::collections::HashMap::new(), diff --git a/crates/notifier_host/src/lib.rs b/crates/notifier_host/src/lib.rs index 98adcfbd3..d88a6afdb 100644 --- a/crates/notifier_host/src/lib.rs +++ b/crates/notifier_host/src/lib.rs @@ -8,3 +8,6 @@ pub use host::*; mod item; pub use item::*; + +mod watcher; +pub use watcher::*; diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs new file mode 100644 index 000000000..db40099f6 --- /dev/null +++ b/crates/notifier_host/src/watcher.rs @@ -0,0 +1,233 @@ +use zbus::dbus_interface; +use zbus::Interface; +use zbus::export::ordered_stream::OrderedStreamExt; + +pub const WATCHER_BUS_NAME: &'static str = "org.kde.StatusNotifierWatcher"; +pub const WATCHER_OBJECT_NAME: &'static str = "/StatusNotifierWatcher"; + +fn parse_service(service: &str) -> (Option>, &str) { + if service.starts_with("/") { + // they sent us just the object path :( + (None, service) + } else { + // should be a bus name + (service.try_into().ok(), "/StatusNotifierItem") + } +} + +/// Wait for a DBus service to exit +async fn wait_for_service_exit( + connection: zbus::Connection, + service: zbus::names::BusName<'_>, +) -> zbus::fdo::Result<()> { + let dbus = zbus::fdo::DBusProxy::new(&connection).await?; + let mut owner_changes = dbus + .receive_name_owner_changed_with_args(&[(0, &service)]) + .await?; + + if !dbus.name_has_owner(service.as_ref()).await? { + return Ok(()) + } + + while let Some(sig) = owner_changes.next().await { + let args = sig.args()?; + if args.new_owner().is_none() { + break + } + } + + Ok(()) +} + +#[derive(Debug, Default)] +pub struct Watcher { + tasks: tokio::task::JoinSet<()>, + hosts: std::sync::Arc>>, + items: std::sync::Arc>>, +} + +#[dbus_interface(name="org.kde.StatusNotifierWatcher")] +impl Watcher { + /// RegisterStatusNotifierHost method + async fn register_status_notifier_host( + &mut self, + service: &str, + #[zbus(header)] hdr: zbus::MessageHeader<'_>, + #[zbus(connection)] con: &zbus::Connection, + #[zbus(signal_context)] ctxt: zbus::SignalContext<'_>, + ) -> zbus::fdo::Result<()> { + let (service, _) = parse_service(service); + let service = if let Some(x) = service { + x.to_owned() + } else if let Some(sender) = hdr.sender()? { + sender.to_owned().into() + } else { + log::warn!("register_status_notifier_host: unknown sender"); + return Err(zbus::fdo::Error::InvalidArgs("Unknown bus address".into())); + }; + log::info!("new host: {}", service); + + { + let mut hosts = self.hosts.lock().unwrap(); + if !hosts.insert(service.to_string()) { + // we're already tracking them + return Ok(()) + } + } + + self.is_status_notifier_host_registered_changed(&ctxt).await?; + Watcher::status_notifier_host_registered(&ctxt).await?; + + self.tasks.spawn({ + let hosts = self.hosts.clone(); + let ctxt = ctxt.to_owned(); + let con = con.to_owned(); + async move { + wait_for_service_exit(con.clone(), service.as_ref()).await.unwrap(); + log::info!("lost host: {}", service); + + { + let mut hosts = hosts.lock().unwrap(); + hosts.remove(service.as_str()); + } + + Watcher::is_status_notifier_host_registered_refresh(&ctxt).await.unwrap(); + Watcher::status_notifier_host_unregistered(&ctxt).await.unwrap(); + } + }); + + Ok(()) + } + + /// StatusNotifierHostRegistered signal + #[dbus_interface(signal)] + async fn status_notifier_host_registered(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()>; + + /// StatusNotifierHostUnregistered signal + #[dbus_interface(signal)] + async fn status_notifier_host_unregistered(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()>; + + /// IsStatusNotifierHostRegistered property + #[dbus_interface(property)] + async fn is_status_notifier_host_registered(&self) -> bool { + let hosts = self.hosts.lock().unwrap(); + !hosts.is_empty() + } + + // ------------------------------------------------------------------------ + + /// RegisterStatusNotifierItem method + async fn register_status_notifier_item( + &mut self, + service: &str, + #[zbus(header)] hdr: zbus::MessageHeader<'_>, + #[zbus(connection)] con: &zbus::Connection, + #[zbus(signal_context)] ctxt: zbus::SignalContext<'_>, + ) -> zbus::fdo::Result<()> { + let (service, objpath) = parse_service(service); + let service = if let Some(x) = service { + x.to_owned() + } else if let Some(sender) = hdr.sender()? { + sender.to_owned().into() + } else { + log::warn!("register_status_notifier_item: unknown sender"); + return Err(zbus::fdo::Error::InvalidArgs("Unknown bus address".into())); + }; + + let item = format!("{}{}", service, objpath); + log::info!("new item: {}", item); + + { + let mut items = self.items.lock().unwrap(); + if !items.insert(item.clone()) { + // we're already tracking them + return Ok(()) + } + } + + self.registered_status_notifier_items_changed(&ctxt).await?; + Watcher::status_notifier_item_registered(&ctxt, service.as_ref()).await?; + + self.tasks.spawn({ + let items = self.items.clone(); + let ctxt = ctxt.to_owned(); + let con = con.to_owned(); + async move { + wait_for_service_exit(con.clone(), service.as_ref()).await.unwrap(); + println!("gone item: {}", &item); + + { + let mut items = items.lock().unwrap(); + items.remove(&item); + } + + Watcher::registered_status_notifier_items_refresh(&ctxt).await.unwrap(); + Watcher::status_notifier_item_unregistered(&ctxt, service.as_ref()).await.unwrap(); + } + }); + + Ok(()) + } + + /// StatusNotifierItemRegistered signal + #[dbus_interface(signal)] + async fn status_notifier_item_registered(ctxt: &zbus::SignalContext<'_>, service: zbus::names::BusName<'_>) -> zbus::Result<()>; + + /// StatusNotifierItemUnregistered signal + #[dbus_interface(signal)] + async fn status_notifier_item_unregistered(ctxt: &zbus::SignalContext<'_>, service: zbus::names::BusName<'_>) -> zbus::Result<()>; + + /// RegisteredStatusNotifierItems property + #[dbus_interface(property)] + async fn registered_status_notifier_items(&self) -> Vec { + let items = self.items.lock().unwrap(); + items.iter().cloned().collect() + } + + // ------------------------------------------------------------------------ + + /// ProtocolVersion property + #[dbus_interface(property)] + fn protocol_version(&self) -> i32 { + 0 + } +} + +impl Watcher { + /// Create a new Watcher. + pub fn new() -> Watcher { + Default::default() + } + + /// Attach the Watcher to a connection. + pub async fn run_on(self, con: &zbus::Connection) -> zbus::Result<()> { + if !con.object_server().at(WATCHER_OBJECT_NAME, self).await? { + return Err(zbus::Error::Failure("Interface already exists at this path".into())) + } + + // no ReplaceExisting, no AllowReplacement, no DoNotQueue + con.request_name_with_flags(WATCHER_BUS_NAME, Default::default()).await?; + + Ok(()) + } + + // Based on is_status_notifier_host_registered_invalidate, but without requiring self + async fn is_status_notifier_host_registered_refresh(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()> { + zbus::fdo::Properties::properties_changed( + ctxt, + Self::name(), + &std::collections::HashMap::new(), + &["IsStatusNotifierHostRegistered"], + ).await + } + + // Based on registered_status_notifier_items_invalidate, but without requiring self + async fn registered_status_notifier_items_refresh(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()> { + zbus::fdo::Properties::properties_changed( + ctxt, + Self::name(), + &std::collections::HashMap::new(), + &["RegisteredStatusNotifierItems"], + ).await + } +} From bbc3f8e9605cb6f98d42f96f1e10681ea313f744 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 9 Apr 2023 19:37:10 +1000 Subject: [PATCH 10/54] Bunch of refactor --- crates/eww/src/widgets/systray.rs | 22 +++++--------------- crates/eww/src/widgets/widget_definitions.rs | 10 ++++----- crates/notifier_host/src/error.rs | 11 ---------- crates/notifier_host/src/host.rs | 14 +++++++------ crates/notifier_host/src/item.rs | 15 ++++++++----- crates/notifier_host/src/lib.rs | 3 --- crates/notifier_host/src/watcher.rs | 21 +++++++++++++++++++ 7 files changed, 49 insertions(+), 47 deletions(-) delete mode 100644 crates/notifier_host/src/error.rs diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 70a6d84b3..3c3521ebd 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -3,20 +3,6 @@ use gtk::prelude::*; use notifier_host; -async fn gtk_run(f: F) -> R -where - F: FnOnce() -> R1 + 'static, - R1: std::future::Future, - R: 'static, -{ - let (tx, rx) = tokio::sync::oneshot::channel(); - glib::MainContext::default().spawn_local(async move { - let r = f().await; - tx.send(r).map_err(|_| ()).unwrap(); - }); - rx.await.unwrap() -} - struct Host { menubar: gtk::MenuBar, items: std::collections::HashMap, @@ -71,16 +57,18 @@ impl notifier_host::Host for Host { } pub fn maintain_menubar(menubar: gtk::MenuBar) { + // TODO avoid having too many zbus::Connection instances + menubar.show_all(); glib::MainContext::default().spawn_local(async move { let con = zbus::Connection::session().await.unwrap(); + notifier_host::watcher_on(&con).await.unwrap(); - notifier_host::Watcher::new().run_on(&con).await.unwrap(); - + let snw = notifier_host::register_host(&con).await.unwrap(); let mut host = Host { menubar, items: std::collections::HashMap::new(), }; - notifier_host::host_on(&mut host, &con).await.unwrap(); + notifier_host::serve_host_forever_on(&mut host, snw).await.unwrap(); }); } diff --git a/crates/eww/src/widgets/widget_definitions.rs b/crates/eww/src/widgets/widget_definitions.rs index d8cd233ce..d78d7d727 100644 --- a/crates/eww/src/widgets/widget_definitions.rs +++ b/crates/eww/src/widgets/widget_definitions.rs @@ -1048,15 +1048,15 @@ const WIDGET_NAME_SYSTRAY: &str = "systray"; /// @widget systray /// @desc Tray for system notifier icons fn build_systray(bargs: &mut BuilderArgs) -> Result { - let w = gtk::MenuBar::new(); + let gtk_widget = gtk::MenuBar::new(); - def_widget!(bargs, _g, w, { + def_widget!(bargs, _g, gtk_widget, { // @prop pack-direction - how to arrange tray items - prop(pack_direction: as_string) { w.set_pack_direction(parse_packdirection(&pack_direction)?); }, + prop(pack_direction: as_string) { gtk_widget.set_pack_direction(parse_packdirection(&pack_direction)?); }, }); - systray::maintain_menubar(w.clone()); - Ok(w) + systray::maintain_menubar(gtk_widget.clone()); + Ok(gtk_widget) } /// @var orientation - "vertical", "v", "horizontal", "h" diff --git a/crates/notifier_host/src/error.rs b/crates/notifier_host/src/error.rs deleted file mode 100644 index a8a2d6f80..000000000 --- a/crates/notifier_host/src/error.rs +++ /dev/null @@ -1,11 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Dbus connection error")] - DbusError(#[from] zbus::Error), - #[error("Service path {0} was not understood")] - DbusAddressError(String), -} - -pub type Result = std::result::Result; diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index 962ac4de2..e6afc8745 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -7,10 +7,8 @@ pub trait Host { fn remove_item(&mut self, id: &str); } -/// Attach to dbus and forward events to Host. -/// -/// This async function won't complete unless an error occurs. -pub async fn host_on(host: &mut dyn Host, con: &zbus::Connection) -> Result<()> { +/// Register this connection as a StatusNotifierHost. +pub async fn register_host(con: &zbus::Connection) -> zbus::Result { // From : // // Instances of this service are registered on the Dbus session bus, under a name on the @@ -40,6 +38,10 @@ pub async fn host_on(host: &mut dyn Host, con: &zbus::Connection) -> Result<()> let snw = dbus::StatusNotifierWatcherProxy::new(&con).await?; snw.register_status_notifier_host(&wellknown_name).await?; + Ok(snw) +} + +pub async fn serve_host_forever_on(host: &mut dyn Host, snw: dbus::StatusNotifierWatcherProxy<'_>) -> zbus::Result<()> { enum ItemEvent { NewItem(dbus::StatusNotifierItemRegistered), GoneItem(dbus::StatusNotifierItemUnregistered), @@ -51,7 +53,7 @@ pub async fn host_on(host: &mut dyn Host, con: &zbus::Connection) -> Result<()> // initial items first for svc in snw.registered_status_notifier_items().await? { - let item = Item::from_address(&con, &svc).await?; + let item = Item::from_address(snw.connection(), &svc).await?; host.add_item(&svc, item); } @@ -63,7 +65,7 @@ pub async fn host_on(host: &mut dyn Host, con: &zbus::Connection) -> Result<()> match ev { ItemEvent::NewItem(sig) => { let args = sig.args()?; - let item = Item::from_address(&con, args.service).await?; + let item = Item::from_address(snw.connection(), args.service).await?; host.add_item(args.service, item); }, ItemEvent::GoneItem(sig) => { diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index 21df00546..75555e04b 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -5,6 +5,11 @@ use gtk::{self, prelude::*}; use zbus::export::ordered_stream::OrderedStreamExt; use tokio::sync::watch; +/// Recognised values of org.freedesktop.StatusNotifierItem.Status +/// +/// See +/// +/// for details. #[derive(Debug, Clone, Copy)] pub enum Status { /// The item doesn't convey important information to the user, it can be considered an "idle" @@ -42,7 +47,7 @@ pub struct Item { /// path. /// /// Original logic from -fn split_service_name(service: &str) -> Result<(String, String)> { +fn split_service_name(service: &str) -> zbus::Result<(String, String)> { if let Some((addr, path)) = service.split_once('/') { Ok((addr.to_owned(), format!("/{}", path))) } else if service.contains(':') { @@ -52,15 +57,15 @@ fn split_service_name(service: &str) -> Result<(String, String)> { if let Some(addr) = addr { Ok((addr.to_owned(), "/StatusNotifierItem".to_owned())) } else { - Err(Error::DbusAddressError(service.to_owned())) + Err(zbus::Error::Address(service.to_owned())) } } else { - Err(Error::DbusAddressError(service.to_owned())) + Err(zbus::Error::Address(service.to_owned())) } } impl Item { - pub async fn from_address(con: &zbus::Connection, addr: &str) -> Result { + pub async fn from_address(con: &zbus::Connection, addr: &str) -> zbus::Result { let (addr, path) = split_service_name(addr)?; let sni = dbus::StatusNotifierItemProxy::builder(con) .destination(addr)? @@ -193,7 +198,7 @@ impl Item { } } - pub async fn menu(&self) -> Result { + pub async fn menu(&self) -> zbus::Result { // TODO better handling if menu() method doesn't exist let menu = dbusmenu_gtk3::Menu::new(self.sni.destination(), &self.sni.menu().await?); Ok(menu.upcast()) diff --git a/crates/notifier_host/src/lib.rs b/crates/notifier_host/src/lib.rs index d88a6afdb..3ccc94b3b 100644 --- a/crates/notifier_host/src/lib.rs +++ b/crates/notifier_host/src/lib.rs @@ -1,8 +1,5 @@ pub mod dbus; -mod error; -pub use error::*; - mod host; pub use host::*; diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index db40099f6..adb60ae94 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -231,3 +231,24 @@ impl Watcher { ).await } } + +/// Start a StatusNotifierWatcher on this connection. +pub async fn watcher_on(con: &zbus::Connection) -> zbus::Result<()> { + if !con.object_server().at(WATCHER_OBJECT_NAME, Watcher::new()).await? { + // There's already something at this object + // TODO better handling? + return Err(zbus::Error::Failure(format!("Interface already exists at object {}", WATCHER_OBJECT_NAME))) + } + + use zbus::fdo::*; + match con.request_name_with_flags(WATCHER_BUS_NAME, [RequestNameFlags::DoNotQueue].into_iter().collect()).await? { + RequestNameReply::PrimaryOwner => return Ok(()), + RequestNameReply::Exists => {}, + RequestNameReply::AlreadyOwner => {}, // TODO should this return + RequestNameReply::InQueue => panic!("request_name_with_flags returned InQueue even though we specified DoNotQueue"), + } + + // TODO should we queue? + + Ok(()) +} From 76cdbf22db58c9be5dd7133575008306b0c9961c Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 9 Apr 2023 20:04:38 +1000 Subject: [PATCH 11/54] Handle errors better --- crates/eww/src/main.rs | 5 +++- crates/notifier_host/src/host.rs | 38 +++++++++++++++++++++++------ crates/notifier_host/src/watcher.rs | 8 +++--- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/crates/eww/src/main.rs b/crates/eww/src/main.rs index a2fc52829..05d137a3d 100644 --- a/crates/eww/src/main.rs +++ b/crates/eww/src/main.rs @@ -48,7 +48,10 @@ fn main() { if std::env::var("RUST_LOG").is_ok() { pretty_env_logger::init_timed(); } else { - pretty_env_logger::formatted_timed_builder().filter(Some("eww"), log_level_filter).init(); + pretty_env_logger::formatted_timed_builder() + .filter(Some("eww"), log_level_filter) + .filter(Some("notifier_host"), log_level_filter) + .init(); } #[allow(unused)] diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index e6afc8745..860ab1ec1 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -51,10 +51,19 @@ pub async fn serve_host_forever_on(host: &mut dyn Host, snw: dbus::StatusNotifie let new_items = snw.receive_status_notifier_item_registered().await?; let gone_items = snw.receive_status_notifier_item_unregistered().await?; + let mut item_names = std::collections::HashSet::new(); + // initial items first for svc in snw.registered_status_notifier_items().await? { - let item = Item::from_address(snw.connection(), &svc).await?; - host.add_item(&svc, item); + match Item::from_address(snw.connection(), &svc).await { + Ok(item) => { + item_names.insert(svc.to_owned()); + host.add_item(&svc, item); + }, + Err(e) => { + log::warn!("Could not create StatusNotifierItem from address {:?}: {:?}", svc, e); + }, + } } let mut ev_stream = ordered_stream::join( @@ -64,16 +73,31 @@ pub async fn serve_host_forever_on(host: &mut dyn Host, snw: dbus::StatusNotifie while let Some(ev) = ev_stream.next().await { match ev { ItemEvent::NewItem(sig) => { - let args = sig.args()?; - let item = Item::from_address(snw.connection(), args.service).await?; - host.add_item(args.service, item); + let svc = sig.args()?.service; + if item_names.contains(svc) { + log::warn!("Got duplicate new item: {:?}", svc); + } else { + match Item::from_address(snw.connection(), svc).await { + Ok(item) => { + item_names.insert(svc.to_owned()); + host.add_item(svc, item); + }, + Err(e) => { + log::warn!("Could not create StatusNotifierItem from address {:?}: {:?}", svc, e); + }, + } + } }, ItemEvent::GoneItem(sig) => { - let args = sig.args()?; - host.remove_item(args.service); + let svc = sig.args()?.service; + if item_names.remove(svc) { + host.remove_item(svc); + } }, } } + // TODO handle running out of events? why could this happen? + Ok(()) } diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index adb60ae94..197fa6526 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -125,14 +125,16 @@ impl Watcher { #[zbus(signal_context)] ctxt: zbus::SignalContext<'_>, ) -> zbus::fdo::Result<()> { let (service, objpath) = parse_service(service); - let service = if let Some(x) = service { - x.to_owned() + let service: zbus::names::UniqueName<'_> = if let Some(x) = service { + let dbus = zbus::fdo::DBusProxy::new(&con).await?; + dbus.get_name_owner(x).await?.into_inner() } else if let Some(sender) = hdr.sender()? { - sender.to_owned().into() + sender.to_owned() } else { log::warn!("register_status_notifier_item: unknown sender"); return Err(zbus::fdo::Error::InvalidArgs("Unknown bus address".into())); }; + let service = zbus::names::BusName::Unique(service); let item = format!("{}{}", service, objpath); log::info!("new item: {}", item); From edf2e93b2f63057adabe2f019e5dd43ef8c8f83e Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 9 Apr 2023 20:52:01 +1000 Subject: [PATCH 12/54] Refactor service parsing --- crates/notifier_host/src/watcher.rs | 60 +++++++++++++++++------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index 197fa6526..3e86423de 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -5,13 +5,42 @@ use zbus::export::ordered_stream::OrderedStreamExt; pub const WATCHER_BUS_NAME: &'static str = "org.kde.StatusNotifierWatcher"; pub const WATCHER_OBJECT_NAME: &'static str = "/StatusNotifierWatcher"; -fn parse_service(service: &str) -> (Option>, &str) { +async fn parse_service<'a>( + service: &'a str, + hdr: zbus::MessageHeader<'_>, + con: &zbus::Connection, +) -> zbus::fdo::Result<(zbus::names::UniqueName<'static>, &'a str)> +{ if service.starts_with("/") { // they sent us just the object path :( - (None, service) + if let Some(sender) = hdr.sender()? { + Ok((sender.to_owned(), service)) + } else { + log::warn!("unknown sender"); + Err(zbus::fdo::Error::InvalidArgs("Unknown bus address".into())) + } } else { - // should be a bus name - (service.try_into().ok(), "/StatusNotifierItem") + let busname: zbus::names::BusName = match service.try_into() { + Ok(x) => x, + Err(e) => { + log::warn!("received invalid bus name {:?}: {}", service, e); + return Err(zbus::fdo::Error::InvalidArgs(e.to_string())); + }, + }; + + if let zbus::names::BusName::Unique(unique) = busname { + Ok((unique.to_owned(), "/StatusNotifierItem")) + } else { + // unwrap: we should always be able to access the dbus interface + let dbus = zbus::fdo::DBusProxy::new(&con).await.unwrap(); + match dbus.get_name_owner(busname).await { + Ok(owner) => Ok((owner.into_inner(), "/StatusNotifierItem")), + Err(e) => { + log::warn!("failed to get owner of {:?}: {}", service, e); + Err(e) + } + } + } } } @@ -56,15 +85,7 @@ impl Watcher { #[zbus(connection)] con: &zbus::Connection, #[zbus(signal_context)] ctxt: zbus::SignalContext<'_>, ) -> zbus::fdo::Result<()> { - let (service, _) = parse_service(service); - let service = if let Some(x) = service { - x.to_owned() - } else if let Some(sender) = hdr.sender()? { - sender.to_owned().into() - } else { - log::warn!("register_status_notifier_host: unknown sender"); - return Err(zbus::fdo::Error::InvalidArgs("Unknown bus address".into())); - }; + let (service, _) = parse_service(service, hdr, con).await?; log::info!("new host: {}", service); { @@ -83,7 +104,7 @@ impl Watcher { let ctxt = ctxt.to_owned(); let con = con.to_owned(); async move { - wait_for_service_exit(con.clone(), service.as_ref()).await.unwrap(); + wait_for_service_exit(con.clone(), service.as_ref().into()).await.unwrap(); log::info!("lost host: {}", service); { @@ -124,16 +145,7 @@ impl Watcher { #[zbus(connection)] con: &zbus::Connection, #[zbus(signal_context)] ctxt: zbus::SignalContext<'_>, ) -> zbus::fdo::Result<()> { - let (service, objpath) = parse_service(service); - let service: zbus::names::UniqueName<'_> = if let Some(x) = service { - let dbus = zbus::fdo::DBusProxy::new(&con).await?; - dbus.get_name_owner(x).await?.into_inner() - } else if let Some(sender) = hdr.sender()? { - sender.to_owned() - } else { - log::warn!("register_status_notifier_item: unknown sender"); - return Err(zbus::fdo::Error::InvalidArgs("Unknown bus address".into())); - }; + let (service, objpath) = parse_service(service, hdr, con).await?; let service = zbus::names::BusName::Unique(service); let item = format!("{}{}", service, objpath); From 7500300b443d11c53ae2233c4548e5dfbcc53f92 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Mon, 10 Apr 2023 16:58:26 +1000 Subject: [PATCH 13/54] Avoid duplicate dbus connections --- crates/eww/src/widgets/systray.rs | 38 +++++++++++++++++++++---- crates/notifier_host/src/host.rs | 44 ++++++++++++----------------- crates/notifier_host/src/watcher.rs | 5 ++-- 3 files changed, 53 insertions(+), 34 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 3c3521ebd..76be2fa50 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -56,19 +56,45 @@ impl notifier_host::Host for Host { } } -pub fn maintain_menubar(menubar: gtk::MenuBar) { - // TODO avoid having too many zbus::Connection instances +struct DBusGlobalState { + con: zbus::Connection, + name: zbus::names::WellKnownName<'static>, +} - menubar.show_all(); - glib::MainContext::default().spawn_local(async move { +async fn dbus_state() -> std::sync::Arc { + use tokio::sync::Mutex; + use std::sync::{Weak, Arc}; + use once_cell::sync::Lazy; + static DBUS_STATE: Lazy>> = Lazy::new(Default::default); + + let mut dbus_state = DBUS_STATE.lock().await; + if let Some(state) = dbus_state.upgrade() { + state + } else { + // TODO error handling? let con = zbus::Connection::session().await.unwrap(); notifier_host::watcher_on(&con).await.unwrap(); - let snw = notifier_host::register_host(&con).await.unwrap(); + let name = notifier_host::attach_new_wellknown_name(&con).await.unwrap(); + + let arc = Arc::new(DBusGlobalState { + con, + name, + }); + *dbus_state = Arc::downgrade(&arc); + + arc + } +} + +pub fn maintain_menubar(menubar: gtk::MenuBar) { + menubar.show_all(); + glib::MainContext::default().spawn_local(async move { let mut host = Host { menubar, items: std::collections::HashMap::new(), }; - notifier_host::serve_host_forever_on(&mut host, snw).await.unwrap(); + let s = &dbus_state().await; + notifier_host::run_host_forever(&mut host, &s.con, &s.name).await.unwrap(); }); } diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index 860ab1ec1..e16eb2e20 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -7,41 +7,33 @@ pub trait Host { fn remove_item(&mut self, id: &str); } -/// Register this connection as a StatusNotifierHost. -pub async fn register_host(con: &zbus::Connection) -> zbus::Result { - // From : - // - // Instances of this service are registered on the Dbus session bus, under a name on the - // form org.freedesktop.StatusNotifierHost-id where id is an unique identifier, that keeps - // the names unique on the bus, such as the process-id of the application or another type - // of identifier if more that one StatusNotifierHost is registered by the same process. - - // pick a new wellknown_name +/// Add a new well-known name of format `org.freedesktop.StatusNotifierHost-{pid}-{nr}` for this connection. +pub async fn attach_new_wellknown_name(con: &zbus::Connection) -> zbus::Result> { let pid = std::process::id(); let mut i = 0; - let wellknown_name = loop { - let wellknown_name = format!("org.freedesktop.StatusNotifierHost-{}-{}", pid, i); - let flags = [zbus::fdo::RequestNameFlags::DoNotQueue]; - + let wellknown = loop { use zbus::fdo::RequestNameReply::*; - match con.request_name_with_flags(wellknown_name.as_str(), flags.into_iter().collect()).await? { - PrimaryOwner => break wellknown_name, - Exists => {}, - AlreadyOwner => {}, // we choose to not use an existing owner, is this correct? - InQueue => panic!("request_name_with_flags returned InQueue even though we specified DoNotQueue"), - }; i += 1; + let wellknown = format!("org.freedesktop.StatusNotifierHost-{}-{}", pid, i); + let wellknown: zbus::names::WellKnownName = wellknown.try_into().expect("generated well-known name is invalid"); + + let flags = [zbus::fdo::RequestNameFlags::DoNotQueue]; + match con.request_name_with_flags(&wellknown, flags.into_iter().collect()).await? { + PrimaryOwner => break wellknown, + Exists => {}, + AlreadyOwner => {}, + InQueue => unreachable!("request_name_with_flags returned InQueue even though we specified DoNotQueue"), + }; }; + Ok(wellknown) +} +pub async fn run_host_forever(host: &mut dyn Host, con: &zbus::Connection, name: &zbus::names::WellKnownName<'_>) -> zbus::Result<()> { // register ourself to StatusNotifierWatcher let snw = dbus::StatusNotifierWatcherProxy::new(&con).await?; - snw.register_status_notifier_host(&wellknown_name).await?; - - Ok(snw) -} + snw.register_status_notifier_host(&name).await?; -pub async fn serve_host_forever_on(host: &mut dyn Host, snw: dbus::StatusNotifierWatcherProxy<'_>) -> zbus::Result<()> { enum ItemEvent { NewItem(dbus::StatusNotifierItemRegistered), GoneItem(dbus::StatusNotifierItemUnregistered), @@ -75,7 +67,7 @@ pub async fn serve_host_forever_on(host: &mut dyn Host, snw: dbus::StatusNotifie ItemEvent::NewItem(sig) => { let svc = sig.args()?.service; if item_names.contains(svc) { - log::warn!("Got duplicate new item: {:?}", svc); + log::info!("Got duplicate new item: {:?}", svc); } else { match Item::from_address(snw.connection(), svc).await { Ok(item) => { diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index 3e86423de..0d2aac679 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -149,15 +149,16 @@ impl Watcher { let service = zbus::names::BusName::Unique(service); let item = format!("{}{}", service, objpath); - log::info!("new item: {}", item); { let mut items = self.items.lock().unwrap(); if !items.insert(item.clone()) { // we're already tracking them + log::info!("new item: {} (duplicate)", item); return Ok(()) } } + log::info!("new item: {}", item); self.registered_status_notifier_items_changed(&ctxt).await?; Watcher::status_notifier_item_registered(&ctxt, service.as_ref()).await?; @@ -259,7 +260,7 @@ pub async fn watcher_on(con: &zbus::Connection) -> zbus::Result<()> { RequestNameReply::PrimaryOwner => return Ok(()), RequestNameReply::Exists => {}, RequestNameReply::AlreadyOwner => {}, // TODO should this return - RequestNameReply::InQueue => panic!("request_name_with_flags returned InQueue even though we specified DoNotQueue"), + RequestNameReply::InQueue => unreachable!("request_name_with_flags returned InQueue even though we specified DoNotQueue"), } // TODO should we queue? From cc9e398aed7251f32136499e2dc853679c406daa Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Mon, 10 Apr 2023 17:19:42 +1000 Subject: [PATCH 14/54] Fix watcher producing bad items --- crates/notifier_host/src/watcher.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index 0d2aac679..31238efe8 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -88,15 +88,18 @@ impl Watcher { let (service, _) = parse_service(service, hdr, con).await?; log::info!("new host: {}", service); - { + let added_first = { let mut hosts = self.hosts.lock().unwrap(); if !hosts.insert(service.to_string()) { // we're already tracking them return Ok(()) } - } + hosts.len() == 1 + }; - self.is_status_notifier_host_registered_changed(&ctxt).await?; + if added_first { + self.is_status_notifier_host_registered_changed(&ctxt).await?; + } Watcher::status_notifier_host_registered(&ctxt).await?; self.tasks.spawn({ @@ -107,12 +110,15 @@ impl Watcher { wait_for_service_exit(con.clone(), service.as_ref().into()).await.unwrap(); log::info!("lost host: {}", service); - { + let removed_last = { let mut hosts = hosts.lock().unwrap(); - hosts.remove(service.as_str()); - } + let did_remove = hosts.remove(service.as_str()); + did_remove && hosts.is_empty() + }; - Watcher::is_status_notifier_host_registered_refresh(&ctxt).await.unwrap(); + if removed_last { + Watcher::is_status_notifier_host_registered_refresh(&ctxt).await.unwrap(); + } Watcher::status_notifier_host_unregistered(&ctxt).await.unwrap(); } }); @@ -161,7 +167,7 @@ impl Watcher { log::info!("new item: {}", item); self.registered_status_notifier_items_changed(&ctxt).await?; - Watcher::status_notifier_item_registered(&ctxt, service.as_ref()).await?; + Watcher::status_notifier_item_registered(&ctxt, item.as_ref()).await?; self.tasks.spawn({ let items = self.items.clone(); @@ -177,7 +183,7 @@ impl Watcher { } Watcher::registered_status_notifier_items_refresh(&ctxt).await.unwrap(); - Watcher::status_notifier_item_unregistered(&ctxt, service.as_ref()).await.unwrap(); + Watcher::status_notifier_item_unregistered(&ctxt, item.as_ref()).await.unwrap(); } }); @@ -186,11 +192,11 @@ impl Watcher { /// StatusNotifierItemRegistered signal #[dbus_interface(signal)] - async fn status_notifier_item_registered(ctxt: &zbus::SignalContext<'_>, service: zbus::names::BusName<'_>) -> zbus::Result<()>; + async fn status_notifier_item_registered(ctxt: &zbus::SignalContext<'_>, service: &str) -> zbus::Result<()>; /// StatusNotifierItemUnregistered signal #[dbus_interface(signal)] - async fn status_notifier_item_unregistered(ctxt: &zbus::SignalContext<'_>, service: zbus::names::BusName<'_>) -> zbus::Result<()>; + async fn status_notifier_item_unregistered(ctxt: &zbus::SignalContext<'_>, service: &str) -> zbus::Result<()>; /// RegisteredStatusNotifierItems property #[dbus_interface(property)] From 79d61a3a04a631e769d34902ca947935c64571b2 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Mon, 10 Apr 2023 22:06:42 +1000 Subject: [PATCH 15/54] Handle zbus::Error::NameTaken --- crates/notifier_host/src/watcher.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index 31238efe8..256c7e352 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -261,15 +261,12 @@ pub async fn watcher_on(con: &zbus::Connection) -> zbus::Result<()> { return Err(zbus::Error::Failure(format!("Interface already exists at object {}", WATCHER_OBJECT_NAME))) } - use zbus::fdo::*; - match con.request_name_with_flags(WATCHER_BUS_NAME, [RequestNameFlags::DoNotQueue].into_iter().collect()).await? { - RequestNameReply::PrimaryOwner => return Ok(()), - RequestNameReply::Exists => {}, - RequestNameReply::AlreadyOwner => {}, // TODO should this return - RequestNameReply::InQueue => unreachable!("request_name_with_flags returned InQueue even though we specified DoNotQueue"), - } - - // TODO should we queue? + // TODO should we queue if we couldn't take the name? - Ok(()) + use zbus::fdo::{RequestNameFlags, RequestNameReply}; + match con.request_name_with_flags(WATCHER_BUS_NAME, [RequestNameFlags::DoNotQueue].into_iter().collect()).await { + Ok(RequestNameReply::PrimaryOwner) => Ok(()), + Ok(_) | Err(zbus::Error::NameTaken) => Ok(()), // defer to existing + Err(e) => Err(e), + } } From 32a5f206748a6f08a4ad1872ba05fe26e55a83fc Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Mon, 10 Apr 2023 22:51:35 +1000 Subject: [PATCH 16/54] Refactor icon loading & don't panic on zoom --- crates/notifier_host/src/item.rs | 113 +++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 27 deletions(-) diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index 75555e04b..d0f4e7212 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -116,22 +116,43 @@ impl Item { #[derive(thiserror::Error, Debug)] pub enum IconError { - #[error("Dbus error")] - DbusError(#[from] zbus::Error), - #[error("Failed to load icon {icon_name:?} from theme {theme_path:?}")] - LoadIconFromTheme { - icon_name: String, - theme_path: String, - source: gtk::glib::Error, + #[error("unhandled dbus error while calling {bus_name}: {err}")] + DBusError { + #[source] err: zbus::Error, + bus_name: zbus::names::BusName<'static>, }, - #[error("Failed to load icon {icon_name:?} from default theme")] - LoadIconFromDefaultTheme { + #[error("failed to load icon {icon_name:?} from theme {theme_path:?}")] + LoadIconFromTheme { icon_name: String, + theme_path: Option, source: gtk::glib::Error, }, + #[error("no icon available")] + NotAvailable, +} + +/// Get the fallback GTK icon +async fn fallback_icon(size: i32) -> std::result::Result { + // TODO downgrade from panic to error return? + let theme = gtk::IconTheme::default().expect("Could not get default gtk theme"); + return match theme.load_icon("image-missing", size, gtk::IconLookupFlags::FORCE_SIZE) { + Err(e) => Err(IconError::LoadIconFromTheme { + icon_name: "image-missing".to_owned(), + theme_path: None, + source: e, + }), + Ok(pb) => Ok(pb.unwrap()), + } } impl Item { + fn to_dbus_err(&self, err: zbus::Error) -> IconError { + IconError::DBusError { + err, + bus_name: self.sni.destination().to_owned() + } + } + pub fn load_pixmap(width: i32, height: i32, mut data: Vec) -> gtk::Image { // We need to convert data from ARGB32 to RGBA32 for chunk in data.chunks_mut(4) { @@ -157,22 +178,35 @@ impl Item { gtk::Image::from_pixbuf(Some(&pixmap)) } - pub async fn icon(&self, size: i32) -> std::result::Result { - let icon_name = self.sni.icon_name().await?; - let icon_theme_path = self.sni.icon_theme_path().await?; + async fn icon_from_name(&self, size: i32) -> std::result::Result { + // TODO better handling of icon_name failure instead of punting it to the caller + let icon_name = match self.sni.icon_name().await { + Ok(s) if s == "" => return Err(IconError::NotAvailable), + Ok(s) => s, + Err(e) => return Err(self.to_dbus_err(e)), + }; + + let icon_theme_path = match self.sni.icon_theme_path().await { + Ok(p) => p, + Err(zbus::Error::FDO(e)) => match *e { + zbus::fdo::Error::UnknownProperty(_) => "".into(), + _ => return Err(self.to_dbus_err(zbus::Error::FDO(e))), + }, + Err(e) => return Err(self.to_dbus_err(e)), + }; if icon_theme_path != "" { - // icon supplied a theme path, so only look there + // icon supplied a theme path, so only look there (w/ fallback) let theme = gtk::IconTheme::new(); theme.prepend_search_path(&icon_theme_path); return match theme.load_icon(&icon_name, size, gtk::IconLookupFlags::FORCE_SIZE) { Err(e) => Err(IconError::LoadIconFromTheme { icon_name, - theme_path: icon_theme_path, + theme_path: Some(icon_theme_path), source: e, }), - Ok(pb) => return Ok(pb.unwrap()), + Ok(pb) => return Ok(pb.expect("no pixbuf from theme.load_icon despite no error")), } } @@ -180,22 +214,47 @@ impl Item { let theme = gtk::IconTheme::default().expect("Could not get default gtk theme"); match theme.load_icon(&icon_name, size, gtk::IconLookupFlags::FORCE_SIZE) { // TODO specifically match on icon missing here - Err(e) => log::warn!("Could not find icon {:?} in default theme: {}", &icon_name, e), - Ok(pb) => return Ok(pb.unwrap()), + Err(e) => { + log::warn!("Could not find icon {:?} in default theme: {}", &icon_name, e); + Err(IconError::LoadIconFromTheme { + icon_name, + theme_path: None, + source: e, + }) + }, + Ok(pb) => Ok(pb.unwrap()), } + } - // "Visualizations are encouraged to prefer icon names over icon pixmaps if both are available." - // TODO icon_pixmap + async fn icon_from_pixmap(&self, _size: i32) -> std::result::Result { + let _pixmap = match self.sni.icon_pixmap().await { + Ok(p) => p, + Err(e) => return Err(self.to_dbus_err(e)), + }; - // fallback to default icon - let theme = gtk::IconTheme::default().expect("Could not get default gtk theme"); - return match theme.load_icon("image-missing", size, gtk::IconLookupFlags::FORCE_SIZE) { - Err(e) => Err(IconError::LoadIconFromDefaultTheme { - icon_name: "image-missing".to_owned(), - source: e, - }), - Ok(pb) => return Ok(pb.unwrap()), + // TODO + Err(IconError::NotAvailable) + } + + pub async fn icon(&self, size: i32) -> std::result::Result { + // "Visualizations are encouraged to prefer icon names over icon pixmaps if both are + // available." + + match self.icon_from_name(size).await { + Ok(pb) => return Ok(pb), + // don't handle unknown dbus error + err @ Err(IconError::DBusError { .. }) => return err, + Err(_) => {}, + }; + + match self.icon_from_pixmap(size).await { + Ok(pb) => return Ok(pb), + // don't handle unknown dbus error + err @ Err(IconError::DBusError { .. }) => return err, + Err(_) => {}, } + + fallback_icon(size).await } pub async fn menu(&self) -> zbus::Result { From 1ea1b47461dfe2987f9e9ac67fe7efeaa877729c Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Tue, 13 Jun 2023 11:13:07 +1000 Subject: [PATCH 17/54] Implement pixbuf icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bojan Nemčić --- crates/notifier_host/src/item.rs | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index d0f4e7212..d70684ef2 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -50,7 +50,7 @@ pub struct Item { fn split_service_name(service: &str) -> zbus::Result<(String, String)> { if let Some((addr, path)) = service.split_once('/') { Ok((addr.to_owned(), format!("/{}", path))) - } else if service.contains(':') { + } else if service.contains(':') { // TODO why? let addr = service.split(':').skip(1).next(); // Some StatusNotifierItems will not return an object path, in that case we fallback // to the default path. @@ -153,7 +153,7 @@ impl Item { } } - pub fn load_pixmap(width: i32, height: i32, mut data: Vec) -> gtk::Image { + pub fn load_pixbuf(width: i32, height: i32, mut data: Vec) -> gtk::gdk_pixbuf::Pixbuf { // We need to convert data from ARGB32 to RGBA32 for chunk in data.chunks_mut(4) { let a = chunk[0]; @@ -166,7 +166,7 @@ impl Item { chunk[3] = a; } - let pixmap = gtk::gdk_pixbuf::Pixbuf::from_bytes( + gtk::gdk_pixbuf::Pixbuf::from_bytes( >k::glib::Bytes::from_owned(data), gtk::gdk_pixbuf::Colorspace::Rgb, true, @@ -174,8 +174,7 @@ impl Item { width, height, width * 4, - ); - gtk::Image::from_pixbuf(Some(&pixmap)) + ) } async fn icon_from_name(&self, size: i32) -> std::result::Result { @@ -226,14 +225,23 @@ impl Item { } } - async fn icon_from_pixmap(&self, _size: i32) -> std::result::Result { - let _pixmap = match self.sni.icon_pixmap().await { - Ok(p) => p, - Err(e) => return Err(self.to_dbus_err(e)), - }; + async fn icon_from_pixmap(&self, size: i32) -> std::result::Result { + match self.sni.icon_pixmap().await { + Ok(ps) => { + for (width, height, data) in ps { + if width == size && height == size { + return Ok(Self::load_pixbuf(width, height, data)) + } + } - // TODO - Err(IconError::NotAvailable) + Err(IconError::NotAvailable) + }, + Err(zbus::Error::FDO(e)) => match *e { + zbus::fdo::Error::UnknownMethod(_) => Err(IconError::NotAvailable), + _ => return Err(self.to_dbus_err(zbus::Error::FDO(e))), + }, + Err(e) => Err(self.to_dbus_err(e)), + } } pub async fn icon(&self, size: i32) -> std::result::Result { From a37adb06a6e45581aa5271014cbeeba5f9a25fd0 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Tue, 13 Jun 2023 11:31:25 +1000 Subject: [PATCH 18/54] Don't panic on icon/menu error --- crates/eww/src/widgets/systray.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 76be2fa50..eab4885d4 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -36,13 +36,18 @@ impl notifier_host::Host for Host { // other initialisation glib::MainContext::default().spawn_local({ + let id = id.to_owned(); let mi = mi.clone(); async move { - let img = item.icon(24).await.unwrap(); - icon.set_from_pixbuf(Some(&img)); + match item.icon(24).await { + Ok(img) => icon.set_from_pixbuf(Some(&img)), + Err(e) => log::warn!("Failed to load icon for {:?}: {}", id, e), + } - let menu = item.menu().await.unwrap(); - mi.set_submenu(Some(&menu)); + match item.menu().await { + Ok(menu) => mi.set_submenu(Some(&menu)), + Err(e) => log::warn!("Failed to load menu for {:?}: {}", id, e), + } } }); mi.show_all(); From e70717469dbcfbeedce1b7a9db8dd926463d12e8 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Tue, 13 Jun 2023 15:45:14 +1000 Subject: [PATCH 19/54] Improve icon error handling to make discord work --- crates/notifier_host/src/item.rs | 61 +++++++++++++++++--------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index d70684ef2..f179dfaf1 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -116,11 +116,12 @@ impl Item { #[derive(thiserror::Error, Debug)] pub enum IconError { - #[error("unhandled dbus error while calling {bus_name}: {err}")] - DBusError { - #[source] err: zbus::Error, - bus_name: zbus::names::BusName<'static>, - }, + #[error("failed to get icon name: {0}")] + DBusIconName(zbus::Error), + #[error("failed to get icon theme path: {0}")] + DBusTheme(zbus::Error), + #[error("failed to get pixmap: {0}")] + DBusPixmap(zbus::Error), #[error("failed to load icon {icon_name:?} from theme {theme_path:?}")] LoadIconFromTheme { icon_name: String, @@ -146,13 +147,6 @@ async fn fallback_icon(size: i32) -> std::result::Result IconError { - IconError::DBusError { - err, - bus_name: self.sni.destination().to_owned() - } - } - pub fn load_pixbuf(width: i32, height: i32, mut data: Vec) -> gtk::gdk_pixbuf::Pixbuf { // We need to convert data from ARGB32 to RGBA32 for chunk in data.chunks_mut(4) { @@ -182,27 +176,30 @@ impl Item { let icon_name = match self.sni.icon_name().await { Ok(s) if s == "" => return Err(IconError::NotAvailable), Ok(s) => s, - Err(e) => return Err(self.to_dbus_err(e)), + Err(e) => return Err(IconError::DBusIconName(e)), }; let icon_theme_path = match self.sni.icon_theme_path().await { - Ok(p) => p, + Ok(p) if p == "" => None, + Ok(p) => Some(p), Err(zbus::Error::FDO(e)) => match *e { - zbus::fdo::Error::UnknownProperty(_) => "".into(), - _ => return Err(self.to_dbus_err(zbus::Error::FDO(e))), + zbus::fdo::Error::UnknownProperty(_) + | zbus::fdo::Error::InvalidArgs(_) + => None, + _ => return Err(IconError::DBusTheme(zbus::Error::FDO(e))), }, - Err(e) => return Err(self.to_dbus_err(e)), + Err(e) => return Err(IconError::DBusTheme(e)), }; - if icon_theme_path != "" { + if let Some(theme_path) = icon_theme_path { // icon supplied a theme path, so only look there (w/ fallback) let theme = gtk::IconTheme::new(); - theme.prepend_search_path(&icon_theme_path); + theme.prepend_search_path(&theme_path); return match theme.load_icon(&icon_name, size, gtk::IconLookupFlags::FORCE_SIZE) { Err(e) => Err(IconError::LoadIconFromTheme { icon_name, - theme_path: Some(icon_theme_path), + theme_path: Some(theme_path), source: e, }), Ok(pb) => return Ok(pb.expect("no pixbuf from theme.load_icon despite no error")), @@ -237,29 +234,35 @@ impl Item { Err(IconError::NotAvailable) }, Err(zbus::Error::FDO(e)) => match *e { - zbus::fdo::Error::UnknownMethod(_) => Err(IconError::NotAvailable), - _ => return Err(self.to_dbus_err(zbus::Error::FDO(e))), + zbus::fdo::Error::UnknownProperty(_) + | zbus::fdo::Error::InvalidArgs(_) + => Err(IconError::NotAvailable), + _ => Err(IconError::DBusPixmap(zbus::Error::FDO(e))), }, - Err(e) => Err(self.to_dbus_err(e)), + Err(e) => Err(IconError::DBusPixmap(e)), } } pub async fn icon(&self, size: i32) -> std::result::Result { + // TODO make this function retun just Pixbuf instead of a result? + // "Visualizations are encouraged to prefer icon names over icon pixmaps if both are // available." match self.icon_from_name(size).await { Ok(pb) => return Ok(pb), - // don't handle unknown dbus error - err @ Err(IconError::DBusError { .. }) => return err, - Err(_) => {}, + Err(IconError::NotAvailable) + | Err(IconError::LoadIconFromTheme { .. }) + => {}, + // Don't fail icon loading here -- e.g. discord raises + // "org.freedesktop.DBus.Error.Failed: error occurred in Get" but has a valid pixmap + Err(e) => log::warn!("failed to get icon by name for {}: {}", self.sni.destination(), e), }; match self.icon_from_pixmap(size).await { Ok(pb) => return Ok(pb), - // don't handle unknown dbus error - err @ Err(IconError::DBusError { .. }) => return err, - Err(_) => {}, + Err(IconError::NotAvailable) => {}, + Err(e) => log::warn!("failed to get icon pixmap for {}: {}", self.sni.destination(), e), } fallback_icon(size).await From bc2082e79d1a261a0fca242da0206a3da6d04624 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Tue, 13 Jun 2023 16:00:15 +1000 Subject: [PATCH 20/54] Update comments --- crates/notifier_host/src/item.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index f179dfaf1..bc7d7ac4a 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -226,6 +226,8 @@ impl Item { match self.sni.icon_pixmap().await { Ok(ps) => { for (width, height, data) in ps { + // TODO use closest size instead of looking for exact match + // (can be tested with keepassxc, which only provides 48x48 and 22x22 pixmaps) if width == size && height == size { return Ok(Self::load_pixbuf(width, height, data)) } @@ -244,7 +246,8 @@ impl Item { } pub async fn icon(&self, size: i32) -> std::result::Result { - // TODO make this function retun just Pixbuf instead of a result? + // TODO make this function retun just Pixbuf instead of a result, now that we're handling + // all errors here? // "Visualizations are encouraged to prefer icon names over icon pixmaps if both are // available." @@ -257,7 +260,7 @@ impl Item { // Don't fail icon loading here -- e.g. discord raises // "org.freedesktop.DBus.Error.Failed: error occurred in Get" but has a valid pixmap Err(e) => log::warn!("failed to get icon by name for {}: {}", self.sni.destination(), e), - }; + } match self.icon_from_pixmap(size).await { Ok(pb) => return Ok(pb), From a6f2a485ac49df2217700feb4706d3085fc13bcd Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:50:27 +1000 Subject: [PATCH 21/54] Big refactor into actor model --- crates/eww/src/widgets/def_widget_macro.rs | 2 + crates/eww/src/widgets/systray.rs | 225 +++++++++++++------ crates/eww/src/widgets/widget_definitions.rs | 14 +- crates/notifier_host/src/item.rs | 103 +++++---- 4 files changed, 238 insertions(+), 106 deletions(-) diff --git a/crates/eww/src/widgets/def_widget_macro.rs b/crates/eww/src/widgets/def_widget_macro.rs index 282802970..da640ca8c 100644 --- a/crates/eww/src/widgets/def_widget_macro.rs +++ b/crates/eww/src/widgets/def_widget_macro.rs @@ -53,6 +53,8 @@ macro_rules! def_widget { // values is a map of all the variables that are required to evaluate the // attributes expression. + // allow $gtk_widget to never be used, by creating a reference that gets immediately discarded + {&$gtk_widget}; // We first initialize all the local variables for all the expected attributes in scope $( diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index eab4885d4..3bb094073 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -3,64 +3,7 @@ use gtk::prelude::*; use notifier_host; -struct Host { - menubar: gtk::MenuBar, - items: std::collections::HashMap, -} - -async fn watch_foreach(mut rx: tokio::sync::watch::Receiver, mut f: impl FnMut(&T)) { - f(&rx.borrow()); - while rx.changed().await.is_ok() { - f(&rx.borrow()); - } -} - -impl notifier_host::Host for Host { - fn add_item(&mut self, id: &str, item: notifier_host::Item) { - let mi = gtk::MenuItem::new(); - self.menubar.add(&mi); - if let Some(old_mi) = self.items.insert(id.to_string(), mi.clone()) { - self.menubar.remove(&old_mi); - } - - // maintain title - glib::MainContext::default().spawn_local({ - let mi = mi.clone(); - watch_foreach(item.title(), move |title| { - mi.set_tooltip_text(Some(title)); - }) - }); - - let icon = gtk::Image::new(); - mi.add(&icon); - - // other initialisation - glib::MainContext::default().spawn_local({ - let id = id.to_owned(); - let mi = mi.clone(); - async move { - match item.icon(24).await { - Ok(img) => icon.set_from_pixbuf(Some(&img)), - Err(e) => log::warn!("Failed to load icon for {:?}: {}", id, e), - } - - match item.menu().await { - Ok(menu) => mi.set_submenu(Some(&menu)), - Err(e) => log::warn!("Failed to load menu for {:?}: {}", id, e), - } - } - }); - mi.show_all(); - } - fn remove_item(&mut self, id: &str) { - if let Some(mi) = self.items.get(id) { - self.menubar.remove(mi); - } else { - log::warn!("Tried to remove nonexistent item {:?} from systray", id); - } - } -} - +// DBus state shared between systray instances, to avoid creating too many connections etc. struct DBusGlobalState { con: zbus::Connection, name: zbus::names::WellKnownName<'static>, @@ -92,14 +35,166 @@ async fn dbus_state() -> std::sync::Arc { } } -pub fn maintain_menubar(menubar: gtk::MenuBar) { - menubar.show_all(); +pub struct Props { + icon_size_tx: tokio::sync::watch::Sender, +} + +impl Props { + pub fn new() -> Self { + let (icon_size_tx, _) = tokio::sync::watch::channel(24); + Self { + icon_size_tx, + } + } + + pub fn icon_size(&self, value: i32) { + let _ = self.icon_size_tx.send_if_modified(|x| { + if *x == value { + false + } else { + *x = value; + true + } + }); + } +} + +struct Tray { + menubar: gtk::MenuBar, + items: std::collections::HashMap, + + icon_size: tokio::sync::watch::Receiver, +} + +pub fn spawn_systray( + menubar: >k::MenuBar, + props: &Props, +) { + let mut systray = Tray { + menubar: menubar.clone(), + items: Default::default(), + icon_size: props.icon_size_tx.subscribe(), + }; + glib::MainContext::default().spawn_local(async move { - let mut host = Host { - menubar, - items: std::collections::HashMap::new(), - }; let s = &dbus_state().await; - notifier_host::run_host_forever(&mut host, &s.con, &s.name).await.unwrap(); + systray.menubar.show(); + notifier_host::run_host_forever(&mut systray, &s.con, &s.name).await.unwrap(); }); } + +impl notifier_host::Host for Tray { + fn add_item(&mut self, id: &str, item: notifier_host::Item) { + let item = Item::new( + id.to_owned(), + item, + self.icon_size.clone() + ); + self.menubar.add(&item.mi); + if let Some(old_item) = self.items.insert(id.to_string(), item) { + self.menubar.remove(&old_item.mi); + } + } + fn remove_item(&mut self, id: &str) { + if let Some(item) = self.items.get(id) { + self.menubar.remove(&item.mi); + } else { + log::warn!("Tried to remove nonexistent item {:?} from systray", id); + } + } +} + +struct Item { + mi: gtk::MenuItem, + + tasks: Vec, +} + +impl Drop for Item { + fn drop(&mut self) { + for task in self.tasks.drain(..) { + // TODO does this abort the task + task.remove(); + } + } +} + +impl Item { + fn new( + id: String, + item: notifier_host::Item, + mut icon_size: tokio::sync::watch::Receiver, + ) -> Self { + let mi = gtk::MenuItem::new(); + let mut out = Self { + mi: mi.clone(), + tasks: Vec::new(), + }; + + out.spawn(async move { + // TODO don't unwrap so much + + // init icon + let icon = gtk::Image::new(); + mi.add(&icon); + icon.show(); + + // init menu + match item.menu().await { + Ok(m) => mi.set_submenu(Some(&m)), + Err(e) => log::warn!("failed to get menu of {}: {}", id, e), + } + + // TODO this is a lot of code duplication unfortunately, i'm not really sure how to + // refactor without making the borrow checker angry + + // set status + match item.status().await.unwrap() { + notifier_host::Status::Passive => mi.hide(), + notifier_host::Status::Active | notifier_host::Status::NeedsAttention => mi.show(), + } + + // set title + mi.set_tooltip_text(Some(&item.title().await.unwrap())); + + // set icon + match item.icon(*icon_size.borrow_and_update()).await { + Ok(p) => icon.set_from_pixbuf(Some(&p)), + Err(e) => log::warn!("failed to get icon of {}: {}", id, e), + } + + // updates + let mut status_updates = item.status_updates(); + let mut title_updates = item.title_updates(); + + loop { + tokio::select! { + Ok(_) = status_updates.changed() => { + // set status + match item.status().await.unwrap() { + notifier_host::Status::Passive => mi.hide(), + notifier_host::Status::Active | notifier_host::Status::NeedsAttention => mi.show(), + } + } + Ok(_) = icon_size.changed() => { + // set icon + match item.icon(*icon_size.borrow_and_update()).await { + Ok(p) => icon.set_from_pixbuf(Some(&p)), + Err(e) => log::warn!("failed to get icon of {}: {}", id, e), + } + } + Ok(_) = title_updates.changed() => { + // set title + mi.set_tooltip_text(Some(&item.title().await.unwrap())); + } + } + } + }); + + out + } + + fn spawn(&mut self, f: impl std::future::Future + 'static) { + self.tasks.push(glib::MainContext::default().spawn_local(f)); + } +} diff --git a/crates/eww/src/widgets/widget_definitions.rs b/crates/eww/src/widgets/widget_definitions.rs index d78d7d727..09953d5a2 100644 --- a/crates/eww/src/widgets/widget_definitions.rs +++ b/crates/eww/src/widgets/widget_definitions.rs @@ -1049,13 +1049,25 @@ const WIDGET_NAME_SYSTRAY: &str = "systray"; /// @desc Tray for system notifier icons fn build_systray(bargs: &mut BuilderArgs) -> Result { let gtk_widget = gtk::MenuBar::new(); + let props = Rc::new(systray::Props::new()); + let props_clone = props.clone(); + // copies for def_widget def_widget!(bargs, _g, gtk_widget, { + // @prop icon-size - size of icons in the tray + prop(icon_size: as_i32) { + if icon_size <= 0 { + log::warn!("Icon size is not a positive number"); + } else { + props.icon_size(icon_size); + } + }, // @prop pack-direction - how to arrange tray items prop(pack_direction: as_string) { gtk_widget.set_pack_direction(parse_packdirection(&pack_direction)?); }, }); - systray::maintain_menubar(gtk_widget.clone()); + systray::spawn_systray(>k_widget, &props_clone); + Ok(gtk_widget) } diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index bc7d7ac4a..e5f9a682a 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -3,7 +3,7 @@ use crate::*; use log; use gtk::{self, prelude::*}; use zbus::export::ordered_stream::OrderedStreamExt; -use tokio::sync::watch; +use tokio::{sync::watch, select}; /// Recognised values of org.freedesktop.StatusNotifierItem.Status /// @@ -23,26 +23,23 @@ pub enum Status { NeedsAttention, } +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct ParseStatusError; + impl std::str::FromStr for Status { - type Err = (); + type Err = ParseStatusError; - fn from_str(s: &str) -> std::result::Result { + fn from_str(s: &str) -> std::result::Result { match s { "Passive" => Ok(Status::Passive), "Active" => Ok(Status::Active), "NeedsAttention" => Ok(Status::NeedsAttention), - _ => Err(()), + _ => Err(ParseStatusError), } } } -#[derive(Clone, Debug)] -pub struct Item { - pub sni: dbus::StatusNotifierItemProxy<'static>, - status_rx: watch::Receiver, - title_rx: watch::Receiver, -} - /// Split a sevice name e.g. `:1.50:/org/ayatana/NotificationItem/nm_applet` into the address and /// path. /// @@ -64,6 +61,21 @@ fn split_service_name(service: &str) -> zbus::Result<(String, String)> { } } +pub struct Item { + pub sni: dbus::StatusNotifierItemProxy<'static>, + + status_rx: watch::Receiver<()>, + title_rx: watch::Receiver<()>, + + task: tokio::task::JoinHandle<()>, +} + +impl Drop for Item { + fn drop(&mut self) { + self.task.abort(); + } +} + impl Item { pub async fn from_address(con: &zbus::Connection, addr: &str) -> zbus::Result { let (addr, path) = split_service_name(addr)?; @@ -72,46 +84,61 @@ impl Item { .path(path)? .build() .await?; + let sni_out = sni.clone(); - let (status_tx, status_rx) = watch::channel(sni.status().await?.parse().unwrap()); - tokio::spawn({ - let sni = sni.clone(); - async move { - let mut new_status_stream = sni.receive_new_status().await.unwrap(); - while let Some(sig) = new_status_stream.next().await { - let args = sig.args().unwrap(); - let status: Status = args.status.parse().unwrap(); - status_tx.send_replace(status); - } - } - }); + let (status_tx, status_rx) = watch::channel(()); + let (title_tx, title_rx) = watch::channel(()); - let (title_tx, title_rx) = watch::channel(sni.title().await?); - tokio::spawn({ - let sni = sni.clone(); - async move { - let mut new_title_stream = sni.receive_new_title().await.unwrap(); - while let Some(_) = new_title_stream.next().await { - let title = sni.title().await.unwrap(); - title_tx.send_replace(title); + let task = tokio::spawn(async move { + let mut status_updates = sni.receive_new_status().await.unwrap(); + let mut title_updates = sni.receive_new_title().await.unwrap(); + + loop { + select! { + _ = status_updates.next() => { + status_tx.send_replace(()); + } + _ = title_updates.next() => { + title_tx.send_replace(()); + } } } }); Ok(Item { - sni, + sni: sni_out, status_rx, title_rx, + task, }) } - pub fn status(&self) -> watch::Receiver { + /// Get the current status of the item. + pub async fn status(&self) -> zbus::Result { + let status = self.sni.status().await?; + match status.parse() { + Ok(s) => Ok(s), + Err(_) => Err(zbus::Error::Failure(format!("Invalid status {:?}", status))), + } + } + + pub fn status_updates(&self) -> watch::Receiver<()> { self.status_rx.clone() } - pub fn title(&self) -> watch::Receiver { + pub async fn title(&self) -> zbus::Result { + self.sni.title().await + } + + pub fn title_updates(&self) -> watch::Receiver<()> { self.title_rx.clone() } + + pub async fn menu(&self) -> zbus::Result { + // TODO better handling if menu() method doesn't exist + let menu = dbusmenu_gtk3::Menu::new(self.sni.destination(), &self.sni.menu().await?); + Ok(menu.upcast()) + } } #[derive(thiserror::Error, Debug)] @@ -186,6 +213,8 @@ impl Item { zbus::fdo::Error::UnknownProperty(_) | zbus::fdo::Error::InvalidArgs(_) => None, + // this error is reported by discord, blueman-applet + zbus::fdo::Error::Failed(msg) if msg == "error occurred in Get" => None, _ => return Err(IconError::DBusTheme(zbus::Error::FDO(e))), }, Err(e) => return Err(IconError::DBusTheme(e)), @@ -270,10 +299,4 @@ impl Item { fallback_icon(size).await } - - pub async fn menu(&self) -> zbus::Result { - // TODO better handling if menu() method doesn't exist - let menu = dbusmenu_gtk3::Menu::new(self.sni.destination(), &self.sni.menu().await?); - Ok(menu.upcast()) - } } From 8734215b2ffea656bb43f5215eea503800add473 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:56:46 +1000 Subject: [PATCH 22/54] Reword error messages --- crates/notifier_host/src/item.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index e5f9a682a..955b9dc5c 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -143,11 +143,11 @@ impl Item { #[derive(thiserror::Error, Debug)] pub enum IconError { - #[error("failed to get icon name: {0}")] + #[error("failed to fetch icon name: {0}")] DBusIconName(zbus::Error), - #[error("failed to get icon theme path: {0}")] + #[error("failed to fetch icon theme path: {0}")] DBusTheme(zbus::Error), - #[error("failed to get pixmap: {0}")] + #[error("failed to fetch pixmap: {0}")] DBusPixmap(zbus::Error), #[error("failed to load icon {icon_name:?} from theme {theme_path:?}")] LoadIconFromTheme { From 4996f1063f0bdf1bfbea6afe08d24cce2c7b777f Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Fri, 16 Jun 2023 16:29:53 +1000 Subject: [PATCH 23/54] Remove redundant watcher_on function --- crates/eww/src/widgets/systray.rs | 5 +++- crates/notifier_host/src/watcher.rs | 46 ++++++++++++----------------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 3bb094073..f1cea8d6f 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -21,7 +21,10 @@ async fn dbus_state() -> std::sync::Arc { } else { // TODO error handling? let con = zbus::Connection::session().await.unwrap(); - notifier_host::watcher_on(&con).await.unwrap(); + notifier_host::Watcher::new() + .attach_to(&con) + .await + .unwrap(); let name = notifier_host::attach_new_wellknown_name(&con).await.unwrap(); diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index 256c7e352..0238faf7e 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -68,6 +68,9 @@ async fn wait_for_service_exit( Ok(()) } +/// An instance of [`org.kde.StatusNotifierWatcher`]. +/// +/// [`org.kde.StatusNotifierWatcher`]: https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/ #[derive(Debug, Default)] pub struct Watcher { tasks: tokio::task::JoinSet<()>, @@ -89,6 +92,7 @@ impl Watcher { log::info!("new host: {}", service); let added_first = { + // scoped around locking of hosts let mut hosts = self.hosts.lock().unwrap(); if !hosts.insert(service.to_string()) { // we're already tracking them @@ -220,19 +224,25 @@ impl Watcher { Default::default() } - /// Attach the Watcher to a connection. - pub async fn run_on(self, con: &zbus::Connection) -> zbus::Result<()> { + /// Attach and run the Watcher on a connection. + pub async fn attach_to(self, con: &zbus::Connection) -> zbus::Result<()> { if !con.object_server().at(WATCHER_OBJECT_NAME, self).await? { - return Err(zbus::Error::Failure("Interface already exists at this path".into())) + // There's already something at this object + // TODO is there a more specific error + return Err(zbus::Error::Failure(format!("Connection already has an object at {}", WATCHER_OBJECT_NAME))) } - // no ReplaceExisting, no AllowReplacement, no DoNotQueue - con.request_name_with_flags(WATCHER_BUS_NAME, Default::default()).await?; - - Ok(()) + // not AllowReplacement, not ReplaceExisting, not DoNotQueue + let flags: [zbus::fdo::RequestNameFlags; 0] = []; + match con.request_name_with_flags(WATCHER_BUS_NAME, flags.into_iter().collect()).await { + Ok(zbus::fdo::RequestNameReply::PrimaryOwner) => Ok(()), + Ok(_) | Err(zbus::Error::NameTaken) => Ok(()), // defer to existing + Err(e) => Err(e), + } } - // Based on is_status_notifier_host_registered_invalidate, but without requiring self + /// Equivalent to `is_status_notifier_host_registered_invalidate`, but without requiring + /// `self`. async fn is_status_notifier_host_registered_refresh(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()> { zbus::fdo::Properties::properties_changed( ctxt, @@ -242,7 +252,7 @@ impl Watcher { ).await } - // Based on registered_status_notifier_items_invalidate, but without requiring self + /// Equivalen to `registered_status_notifier_items_invalidate`, but without requiring `self`. async fn registered_status_notifier_items_refresh(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()> { zbus::fdo::Properties::properties_changed( ctxt, @@ -252,21 +262,3 @@ impl Watcher { ).await } } - -/// Start a StatusNotifierWatcher on this connection. -pub async fn watcher_on(con: &zbus::Connection) -> zbus::Result<()> { - if !con.object_server().at(WATCHER_OBJECT_NAME, Watcher::new()).await? { - // There's already something at this object - // TODO better handling? - return Err(zbus::Error::Failure(format!("Interface already exists at object {}", WATCHER_OBJECT_NAME))) - } - - // TODO should we queue if we couldn't take the name? - - use zbus::fdo::{RequestNameFlags, RequestNameReply}; - match con.request_name_with_flags(WATCHER_BUS_NAME, [RequestNameFlags::DoNotQueue].into_iter().collect()).await { - Ok(RequestNameReply::PrimaryOwner) => Ok(()), - Ok(_) | Err(zbus::Error::NameTaken) => Ok(()), // defer to existing - Err(e) => Err(e), - } -} From df4da7fc77cae48404227b48c761e8b12036fab8 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Fri, 16 Jun 2023 16:30:12 +1000 Subject: [PATCH 24/54] Big icon handling refactor --- crates/eww/src/widgets/systray.rs | 10 +- crates/notifier_host/src/icon.rs | 195 ++++++++++++++++++++++++++++++ crates/notifier_host/src/item.rs | 162 +------------------------ crates/notifier_host/src/lib.rs | 3 + 4 files changed, 203 insertions(+), 167 deletions(-) create mode 100644 crates/notifier_host/src/icon.rs diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index f1cea8d6f..e70dc165f 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -161,10 +161,7 @@ impl Item { mi.set_tooltip_text(Some(&item.title().await.unwrap())); // set icon - match item.icon(*icon_size.borrow_and_update()).await { - Ok(p) => icon.set_from_pixbuf(Some(&p)), - Err(e) => log::warn!("failed to get icon of {}: {}", id, e), - } + icon.set_from_pixbuf(Some(&item.icon(*icon_size.borrow_and_update()).await)); // updates let mut status_updates = item.status_updates(); @@ -181,10 +178,7 @@ impl Item { } Ok(_) = icon_size.changed() => { // set icon - match item.icon(*icon_size.borrow_and_update()).await { - Ok(p) => icon.set_from_pixbuf(Some(&p)), - Err(e) => log::warn!("failed to get icon of {}: {}", id, e), - } + icon.set_from_pixbuf(Some(&item.icon(*icon_size.borrow_and_update()).await)); } Ok(_) = title_updates.changed() => { // set title diff --git a/crates/notifier_host/src/icon.rs b/crates/notifier_host/src/icon.rs new file mode 100644 index 000000000..c9daa1799 --- /dev/null +++ b/crates/notifier_host/src/icon.rs @@ -0,0 +1,195 @@ +use crate::*; + +use gtk::{self, prelude::*}; + +#[derive(thiserror::Error, Debug)] +pub enum IconError { + #[error("while fetching icon name: {0}")] + DBusIconName(#[source] zbus::Error), + #[error("while fetching icon theme path: {0}")] + DBusTheme(#[source] zbus::Error), + #[error("while fetching pixmap: {0}")] + DBusPixmap(#[source] zbus::Error), + #[error("loading icon from file {path:?}")] + LoadIconFromFile { + path: String, + #[source] source: gtk::glib::Error, + }, + #[error("loading icon {icon_name:?} from theme {theme_path:?}")] + LoadIconFromTheme { + icon_name: String, + theme_path: Option, + #[source] source: gtk::glib::Error, + }, + #[error("no icon available")] + NotAvailable, +} + +/// Get the fallback GTK icon +pub async fn fallback_icon(size: i32) -> gtk::gdk_pixbuf::Pixbuf { + let theme = gtk::IconTheme::default().expect("Could not get default gtk theme"); + return match theme.load_icon("image-missing", size, gtk::IconLookupFlags::FORCE_SIZE) { + Err(e) => { + log::error!("failed to load \"image-missing\" from default theme: {}", e); + // create a blank pixbuf + gtk::gdk_pixbuf::Pixbuf::new( + gtk::gdk_pixbuf::Colorspace::Rgb, + false, + 0, + size, + size, + ).unwrap() + }, + Ok(pb) => pb.unwrap(), + } +} + +/// Load a pixbuf from StatusNotifierItem's [Icon format]. +/// +/// [Icon format]: https://freedesktop.org/wiki/Specifications/StatusNotifierItem/Icons/ +fn icon_from_pixmap(width: i32, height: i32, mut data: Vec) -> gtk::gdk_pixbuf::Pixbuf { + // We need to convert data from ARGB32 to RGBA32, since that's the only one that gdk-pixbuf + // understands. + for chunk in data.chunks_mut(4) { + let a = chunk[0]; + let r = chunk[1]; + let g = chunk[2]; + let b = chunk[3]; + chunk[0] = r; + chunk[1] = g; + chunk[2] = b; + chunk[3] = a; + } + + gtk::gdk_pixbuf::Pixbuf::from_bytes( + >k::glib::Bytes::from_owned(data), + gtk::gdk_pixbuf::Colorspace::Rgb, + true, + 8, + width, + height, + width * 4, + ) +} + +/// From a list of pixmaps, create an icon from the most appropriately sized one. +/// +/// This function returns None if and only if no pixmaps are provided. +fn icon_from_pixmaps(pixmaps: Vec<(i32, i32, Vec)>, size: i32) -> Option { + pixmaps.into_iter() + .max_by(|(w1, h1, _), (w2, h2, _)| { + // take smallest one bigger than requested size, otherwise take biggest + let a = size * size; + let a1 = w1 * h1; + let a2 = w2 * h2; + match (a1 >= a, a2 >= a) { + (true, true) => a2.cmp(&a1), + (true, false) => std::cmp::Ordering::Greater, + (false, true) => std::cmp::Ordering::Less, + (false, false) => a1.cmp(&a2), + } + }) + .map(|(w, h, d)| { + let pixbuf = icon_from_pixmap(w, h, d); + if w != size || h != size { + pixbuf.scale_simple(size, size, gtk::gdk_pixbuf::InterpType::Bilinear).unwrap() + } else { + pixbuf + } + }) +} + +fn icon_from_name(icon_name: &str, theme_path: Option<&str>, size: i32) -> std::result::Result { + let theme = if let Some(path) = theme_path { + let theme = gtk::IconTheme::new(); + theme.prepend_search_path(&path); + theme + } else { + gtk::IconTheme::default().expect("Could not get default gtk theme") + }; + + match theme.load_icon(icon_name, size, gtk::IconLookupFlags::FORCE_SIZE) { + Ok(pb) => Ok(pb.expect("no pixbuf from theme.load_icon despite no error")), + Err(e) => Err(IconError::LoadIconFromTheme { + icon_name: icon_name.to_owned(), + theme_path: theme_path.map(str::to_owned), + source: e, + }), + } +} + +pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i32) -> gtk::gdk_pixbuf::Pixbuf { + // "Visualizations are encouraged to prefer icon names over icon pixmaps if both are + // available." + + let icon_from_name: std::result::Result = (async { + // fetch icon name + let icon_name = match sni.icon_name().await { + Ok(s) if s == "" => return Err(IconError::NotAvailable), + Ok(s) => s, + Err(e) => return Err(IconError::DBusIconName(e)), + }; + + // interpret it as an absolute path if we can + let icon_path = std::path::Path::new(&icon_name); + if icon_path.is_absolute() && icon_path.is_file() { + return gtk::gdk_pixbuf::Pixbuf::from_file_at_size(icon_path, size, size) + .map_err(|e| IconError::LoadIconFromFile { + path: icon_name, + source: e, + }); + } + + // otherwise, fetch icon theme and lookup using icon_from_name + let icon_theme_path = match sni.icon_theme_path().await { + Ok(p) if p == "" => None, + Ok(p) => Some(p), + // treat property not existing as the same as it being empty i.e. to use the default + // system theme + Err(zbus::Error::FDO(e)) => match *e { + zbus::fdo::Error::UnknownProperty(_) + | zbus::fdo::Error::InvalidArgs(_) + => None, + // this error is reported by discord, blueman-applet + zbus::fdo::Error::Failed(msg) if msg == "error occurred in Get" => None, + _ => return Err(IconError::DBusTheme(zbus::Error::FDO(e))), + }, + Err(e) => return Err(IconError::DBusTheme(e)), + }; + let icon_theme_path: Option<&str> = match &icon_theme_path { + Some(s) => Some(&s), + None => None, + }; + + icon_from_name(&icon_name, icon_theme_path, size) + }).await; + match icon_from_name { + Ok(p) => return p, + Err(IconError::NotAvailable) => {}, // try pixbuf + // log and continue + Err(e) => log::warn!("failed to get icon by name for {}: {}", sni.destination(), e), + }; + + let icon_from_pixmaps = match sni.icon_pixmap().await { + Ok(ps) => match icon_from_pixmaps(ps, size) { + Some(p) => Ok(p), + None => Err(IconError::NotAvailable), + }, + Err(zbus::Error::FDO(e)) => match *e { + // property not existing is fine + zbus::fdo::Error::UnknownProperty(_) + | zbus::fdo::Error::InvalidArgs(_) + => Err(IconError::NotAvailable), + + _ => Err(IconError::DBusPixmap(zbus::Error::FDO(e))), + }, + Err(e) => Err(IconError::DBusPixmap(e)), + }; + match icon_from_pixmaps { + Ok(p) => return p, + Err(IconError::NotAvailable) => {}, + Err(e) => log::warn!("failed to get icon pixmap for {}: {}", sni.destination(), e), + }; + + fallback_icon(size).await +} diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index 955b9dc5c..57802fea0 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -1,6 +1,5 @@ use crate::*; -use log; use gtk::{self, prelude::*}; use zbus::export::ordered_stream::OrderedStreamExt; use tokio::{sync::watch, select}; @@ -139,164 +138,9 @@ impl Item { let menu = dbusmenu_gtk3::Menu::new(self.sni.destination(), &self.sni.menu().await?); Ok(menu.upcast()) } -} - -#[derive(thiserror::Error, Debug)] -pub enum IconError { - #[error("failed to fetch icon name: {0}")] - DBusIconName(zbus::Error), - #[error("failed to fetch icon theme path: {0}")] - DBusTheme(zbus::Error), - #[error("failed to fetch pixmap: {0}")] - DBusPixmap(zbus::Error), - #[error("failed to load icon {icon_name:?} from theme {theme_path:?}")] - LoadIconFromTheme { - icon_name: String, - theme_path: Option, - source: gtk::glib::Error, - }, - #[error("no icon available")] - NotAvailable, -} - -/// Get the fallback GTK icon -async fn fallback_icon(size: i32) -> std::result::Result { - // TODO downgrade from panic to error return? - let theme = gtk::IconTheme::default().expect("Could not get default gtk theme"); - return match theme.load_icon("image-missing", size, gtk::IconLookupFlags::FORCE_SIZE) { - Err(e) => Err(IconError::LoadIconFromTheme { - icon_name: "image-missing".to_owned(), - theme_path: None, - source: e, - }), - Ok(pb) => Ok(pb.unwrap()), - } -} - -impl Item { - pub fn load_pixbuf(width: i32, height: i32, mut data: Vec) -> gtk::gdk_pixbuf::Pixbuf { - // We need to convert data from ARGB32 to RGBA32 - for chunk in data.chunks_mut(4) { - let a = chunk[0]; - let r = chunk[1]; - let g = chunk[2]; - let b = chunk[3]; - chunk[0] = r; - chunk[1] = g; - chunk[2] = b; - chunk[3] = a; - } - - gtk::gdk_pixbuf::Pixbuf::from_bytes( - >k::glib::Bytes::from_owned(data), - gtk::gdk_pixbuf::Colorspace::Rgb, - true, - 8, - width, - height, - width * 4, - ) - } - - async fn icon_from_name(&self, size: i32) -> std::result::Result { - // TODO better handling of icon_name failure instead of punting it to the caller - let icon_name = match self.sni.icon_name().await { - Ok(s) if s == "" => return Err(IconError::NotAvailable), - Ok(s) => s, - Err(e) => return Err(IconError::DBusIconName(e)), - }; - - let icon_theme_path = match self.sni.icon_theme_path().await { - Ok(p) if p == "" => None, - Ok(p) => Some(p), - Err(zbus::Error::FDO(e)) => match *e { - zbus::fdo::Error::UnknownProperty(_) - | zbus::fdo::Error::InvalidArgs(_) - => None, - // this error is reported by discord, blueman-applet - zbus::fdo::Error::Failed(msg) if msg == "error occurred in Get" => None, - _ => return Err(IconError::DBusTheme(zbus::Error::FDO(e))), - }, - Err(e) => return Err(IconError::DBusTheme(e)), - }; - - if let Some(theme_path) = icon_theme_path { - // icon supplied a theme path, so only look there (w/ fallback) - let theme = gtk::IconTheme::new(); - theme.prepend_search_path(&theme_path); - - return match theme.load_icon(&icon_name, size, gtk::IconLookupFlags::FORCE_SIZE) { - Err(e) => Err(IconError::LoadIconFromTheme { - icon_name, - theme_path: Some(theme_path), - source: e, - }), - Ok(pb) => return Ok(pb.expect("no pixbuf from theme.load_icon despite no error")), - } - } - - // fallback to default theme - let theme = gtk::IconTheme::default().expect("Could not get default gtk theme"); - match theme.load_icon(&icon_name, size, gtk::IconLookupFlags::FORCE_SIZE) { - // TODO specifically match on icon missing here - Err(e) => { - log::warn!("Could not find icon {:?} in default theme: {}", &icon_name, e); - Err(IconError::LoadIconFromTheme { - icon_name, - theme_path: None, - source: e, - }) - }, - Ok(pb) => Ok(pb.unwrap()), - } - } - - async fn icon_from_pixmap(&self, size: i32) -> std::result::Result { - match self.sni.icon_pixmap().await { - Ok(ps) => { - for (width, height, data) in ps { - // TODO use closest size instead of looking for exact match - // (can be tested with keepassxc, which only provides 48x48 and 22x22 pixmaps) - if width == size && height == size { - return Ok(Self::load_pixbuf(width, height, data)) - } - } - - Err(IconError::NotAvailable) - }, - Err(zbus::Error::FDO(e)) => match *e { - zbus::fdo::Error::UnknownProperty(_) - | zbus::fdo::Error::InvalidArgs(_) - => Err(IconError::NotAvailable), - _ => Err(IconError::DBusPixmap(zbus::Error::FDO(e))), - }, - Err(e) => Err(IconError::DBusPixmap(e)), - } - } - - pub async fn icon(&self, size: i32) -> std::result::Result { - // TODO make this function retun just Pixbuf instead of a result, now that we're handling - // all errors here? - - // "Visualizations are encouraged to prefer icon names over icon pixmaps if both are - // available." - - match self.icon_from_name(size).await { - Ok(pb) => return Ok(pb), - Err(IconError::NotAvailable) - | Err(IconError::LoadIconFromTheme { .. }) - => {}, - // Don't fail icon loading here -- e.g. discord raises - // "org.freedesktop.DBus.Error.Failed: error occurred in Get" but has a valid pixmap - Err(e) => log::warn!("failed to get icon by name for {}: {}", self.sni.destination(), e), - } - - match self.icon_from_pixmap(size).await { - Ok(pb) => return Ok(pb), - Err(IconError::NotAvailable) => {}, - Err(e) => log::warn!("failed to get icon pixmap for {}: {}", self.sni.destination(), e), - } - fallback_icon(size).await + pub async fn icon(&self, size: i32) -> gtk::gdk_pixbuf::Pixbuf { + // see icon.rs + load_icon_from_sni(&self.sni, size).await } } diff --git a/crates/notifier_host/src/lib.rs b/crates/notifier_host/src/lib.rs index 3ccc94b3b..b4676e244 100644 --- a/crates/notifier_host/src/lib.rs +++ b/crates/notifier_host/src/lib.rs @@ -3,6 +3,9 @@ pub mod dbus; mod host; pub use host::*; +mod icon; +pub use icon::*; + mod item; pub use item::*; From 074206a5587d29844a468ecc6087b3fcc580fbfa Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Fri, 16 Jun 2023 17:12:17 +1000 Subject: [PATCH 25/54] Don't unnecessarily wrap StatusNotifierItem --- crates/eww/src/widgets/systray.rs | 13 ++++---- crates/notifier_host/src/item.rs | 50 +------------------------------ crates/notifier_host/src/lib.rs | 4 +++ 3 files changed, 12 insertions(+), 55 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index e70dc165f..596b2e983 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -2,6 +2,7 @@ use gtk::prelude::*; use notifier_host; +use notifier_host::export::ordered_stream::OrderedStreamExt; // DBus state shared between systray instances, to avoid creating too many connections etc. struct DBusGlobalState { @@ -158,18 +159,18 @@ impl Item { } // set title - mi.set_tooltip_text(Some(&item.title().await.unwrap())); + mi.set_tooltip_text(Some(&item.sni.title().await.unwrap())); // set icon icon.set_from_pixbuf(Some(&item.icon(*icon_size.borrow_and_update()).await)); // updates - let mut status_updates = item.status_updates(); - let mut title_updates = item.title_updates(); + let mut status_updates = item.sni.receive_new_status().await.unwrap(); + let mut title_updates = item.sni.receive_new_status().await.unwrap(); loop { tokio::select! { - Ok(_) = status_updates.changed() => { + Some(_) = status_updates.next() => { // set status match item.status().await.unwrap() { notifier_host::Status::Passive => mi.hide(), @@ -180,9 +181,9 @@ impl Item { // set icon icon.set_from_pixbuf(Some(&item.icon(*icon_size.borrow_and_update()).await)); } - Ok(_) = title_updates.changed() => { + Some(_) = title_updates.next() => { // set title - mi.set_tooltip_text(Some(&item.title().await.unwrap())); + mi.set_tooltip_text(Some(&item.sni.title().await.unwrap())); } } } diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index 57802fea0..2c2f8eec1 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -1,8 +1,6 @@ use crate::*; use gtk::{self, prelude::*}; -use zbus::export::ordered_stream::OrderedStreamExt; -use tokio::{sync::watch, select}; /// Recognised values of org.freedesktop.StatusNotifierItem.Status /// @@ -62,17 +60,6 @@ fn split_service_name(service: &str) -> zbus::Result<(String, String)> { pub struct Item { pub sni: dbus::StatusNotifierItemProxy<'static>, - - status_rx: watch::Receiver<()>, - title_rx: watch::Receiver<()>, - - task: tokio::task::JoinHandle<()>, -} - -impl Drop for Item { - fn drop(&mut self) { - self.task.abort(); - } } impl Item { @@ -83,32 +70,9 @@ impl Item { .path(path)? .build() .await?; - let sni_out = sni.clone(); - - let (status_tx, status_rx) = watch::channel(()); - let (title_tx, title_rx) = watch::channel(()); - - let task = tokio::spawn(async move { - let mut status_updates = sni.receive_new_status().await.unwrap(); - let mut title_updates = sni.receive_new_title().await.unwrap(); - - loop { - select! { - _ = status_updates.next() => { - status_tx.send_replace(()); - } - _ = title_updates.next() => { - title_tx.send_replace(()); - } - } - } - }); Ok(Item { - sni: sni_out, - status_rx, - title_rx, - task, + sni, }) } @@ -121,18 +85,6 @@ impl Item { } } - pub fn status_updates(&self) -> watch::Receiver<()> { - self.status_rx.clone() - } - - pub async fn title(&self) -> zbus::Result { - self.sni.title().await - } - - pub fn title_updates(&self) -> watch::Receiver<()> { - self.title_rx.clone() - } - pub async fn menu(&self) -> zbus::Result { // TODO better handling if menu() method doesn't exist let menu = dbusmenu_gtk3::Menu::new(self.sni.destination(), &self.sni.menu().await?); diff --git a/crates/notifier_host/src/lib.rs b/crates/notifier_host/src/lib.rs index b4676e244..a5fe2dd65 100644 --- a/crates/notifier_host/src/lib.rs +++ b/crates/notifier_host/src/lib.rs @@ -11,3 +11,7 @@ pub use item::*; mod watcher; pub use watcher::*; + +pub mod export { + pub use zbus::export::ordered_stream; +} From 0a1a6fae09b45e1d35f6f8c49ff6b6e3ddce5f05 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Fri, 16 Jun 2023 17:25:56 +1000 Subject: [PATCH 26/54] cargo fmt --- crates/eww/src/widgets/systray.rs | 50 +++++-------------- .../src/dbus/dbus_status_notifier_watcher.rs | 6 +-- crates/notifier_host/src/host.rs | 22 ++++---- crates/notifier_host/src/icon.rs | 47 ++++++++--------- crates/notifier_host/src/item.rs | 13 ++--- crates/notifier_host/src/watcher.rs | 36 ++++++------- 6 files changed, 68 insertions(+), 106 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 596b2e983..511127dcf 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -1,8 +1,7 @@ #![allow(unused)] use gtk::prelude::*; -use notifier_host; -use notifier_host::export::ordered_stream::OrderedStreamExt; +use notifier_host::{self, export::ordered_stream::OrderedStreamExt}; // DBus state shared between systray instances, to avoid creating too many connections etc. struct DBusGlobalState { @@ -11,9 +10,9 @@ struct DBusGlobalState { } async fn dbus_state() -> std::sync::Arc { - use tokio::sync::Mutex; - use std::sync::{Weak, Arc}; use once_cell::sync::Lazy; + use std::sync::{Arc, Weak}; + use tokio::sync::Mutex; static DBUS_STATE: Lazy>> = Lazy::new(Default::default); let mut dbus_state = DBUS_STATE.lock().await; @@ -22,17 +21,11 @@ async fn dbus_state() -> std::sync::Arc { } else { // TODO error handling? let con = zbus::Connection::session().await.unwrap(); - notifier_host::Watcher::new() - .attach_to(&con) - .await - .unwrap(); + notifier_host::Watcher::new().attach_to(&con).await.unwrap(); let name = notifier_host::attach_new_wellknown_name(&con).await.unwrap(); - let arc = Arc::new(DBusGlobalState { - con, - name, - }); + let arc = Arc::new(DBusGlobalState { con, name }); *dbus_state = Arc::downgrade(&arc); arc @@ -46,9 +39,7 @@ pub struct Props { impl Props { pub fn new() -> Self { let (icon_size_tx, _) = tokio::sync::watch::channel(24); - Self { - icon_size_tx, - } + Self { icon_size_tx } } pub fn icon_size(&self, value: i32) { @@ -70,15 +61,8 @@ struct Tray { icon_size: tokio::sync::watch::Receiver, } -pub fn spawn_systray( - menubar: >k::MenuBar, - props: &Props, -) { - let mut systray = Tray { - menubar: menubar.clone(), - items: Default::default(), - icon_size: props.icon_size_tx.subscribe(), - }; +pub fn spawn_systray(menubar: >k::MenuBar, props: &Props) { + let mut systray = Tray { menubar: menubar.clone(), items: Default::default(), icon_size: props.icon_size_tx.subscribe() }; glib::MainContext::default().spawn_local(async move { let s = &dbus_state().await; @@ -89,16 +73,13 @@ pub fn spawn_systray( impl notifier_host::Host for Tray { fn add_item(&mut self, id: &str, item: notifier_host::Item) { - let item = Item::new( - id.to_owned(), - item, - self.icon_size.clone() - ); + let item = Item::new(id.to_owned(), item, self.icon_size.clone()); self.menubar.add(&item.mi); if let Some(old_item) = self.items.insert(id.to_string(), item) { self.menubar.remove(&old_item.mi); } } + fn remove_item(&mut self, id: &str) { if let Some(item) = self.items.get(id) { self.menubar.remove(&item.mi); @@ -124,16 +105,9 @@ impl Drop for Item { } impl Item { - fn new( - id: String, - item: notifier_host::Item, - mut icon_size: tokio::sync::watch::Receiver, - ) -> Self { + fn new(id: String, item: notifier_host::Item, mut icon_size: tokio::sync::watch::Receiver) -> Self { let mi = gtk::MenuItem::new(); - let mut out = Self { - mi: mi.clone(), - tasks: Vec::new(), - }; + let mut out = Self { mi: mi.clone(), tasks: Vec::new() }; out.spawn(async move { // TODO don't unwrap so much diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.rs b/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.rs index 8c352793f..9ac2eaa9c 100644 --- a/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.rs +++ b/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.rs @@ -12,9 +12,9 @@ use zbus::dbus_proxy; #[dbus_proxy( - default_service="org.kde.StatusNotifierWatcher", - interface="org.kde.StatusNotifierWatcher", - default_path="/StatusNotifierWatcher", + default_service = "org.kde.StatusNotifierWatcher", + interface = "org.kde.StatusNotifierWatcher", + default_path = "/StatusNotifierWatcher" )] trait StatusNotifierWatcher { /// RegisterStatusNotifierHost method diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index e16eb2e20..b20b2d79e 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -21,15 +21,19 @@ pub async fn attach_new_wellknown_name(con: &zbus::Connection) -> zbus::Result break wellknown, - Exists => {}, - AlreadyOwner => {}, + Exists => {} + AlreadyOwner => {} InQueue => unreachable!("request_name_with_flags returned InQueue even though we specified DoNotQueue"), }; }; Ok(wellknown) } -pub async fn run_host_forever(host: &mut dyn Host, con: &zbus::Connection, name: &zbus::names::WellKnownName<'_>) -> zbus::Result<()> { +pub async fn run_host_forever( + host: &mut dyn Host, + con: &zbus::Connection, + name: &zbus::names::WellKnownName<'_>, +) -> zbus::Result<()> { // register ourself to StatusNotifierWatcher let snw = dbus::StatusNotifierWatcherProxy::new(&con).await?; snw.register_status_notifier_host(&name).await?; @@ -51,10 +55,10 @@ pub async fn run_host_forever(host: &mut dyn Host, con: &zbus::Connection, name: Ok(item) => { item_names.insert(svc.to_owned()); host.add_item(&svc, item); - }, + } Err(e) => { log::warn!("Could not create StatusNotifierItem from address {:?}: {:?}", svc, e); - }, + } } } @@ -73,19 +77,19 @@ pub async fn run_host_forever(host: &mut dyn Host, con: &zbus::Connection, name: Ok(item) => { item_names.insert(svc.to_owned()); host.add_item(svc, item); - }, + } Err(e) => { log::warn!("Could not create StatusNotifierItem from address {:?}: {:?}", svc, e); - }, + } } } - }, + } ItemEvent::GoneItem(sig) => { let svc = sig.args()?.service; if item_names.remove(svc) { host.remove_item(svc); } - }, + } } } diff --git a/crates/notifier_host/src/icon.rs b/crates/notifier_host/src/icon.rs index c9daa1799..137a165fe 100644 --- a/crates/notifier_host/src/icon.rs +++ b/crates/notifier_host/src/icon.rs @@ -13,13 +13,15 @@ pub enum IconError { #[error("loading icon from file {path:?}")] LoadIconFromFile { path: String, - #[source] source: gtk::glib::Error, + #[source] + source: gtk::glib::Error, }, #[error("loading icon {icon_name:?} from theme {theme_path:?}")] LoadIconFromTheme { icon_name: String, theme_path: Option, - #[source] source: gtk::glib::Error, + #[source] + source: gtk::glib::Error, }, #[error("no icon available")] NotAvailable, @@ -32,16 +34,10 @@ pub async fn fallback_icon(size: i32) -> gtk::gdk_pixbuf::Pixbuf { Err(e) => { log::error!("failed to load \"image-missing\" from default theme: {}", e); // create a blank pixbuf - gtk::gdk_pixbuf::Pixbuf::new( - gtk::gdk_pixbuf::Colorspace::Rgb, - false, - 0, - size, - size, - ).unwrap() - }, + gtk::gdk_pixbuf::Pixbuf::new(gtk::gdk_pixbuf::Colorspace::Rgb, false, 0, size, size).unwrap() + } Ok(pb) => pb.unwrap(), - } + }; } /// Load a pixbuf from StatusNotifierItem's [Icon format]. @@ -76,7 +72,8 @@ fn icon_from_pixmap(width: i32, height: i32, mut data: Vec) -> gtk::gdk_pixb /// /// This function returns None if and only if no pixmaps are provided. fn icon_from_pixmaps(pixmaps: Vec<(i32, i32, Vec)>, size: i32) -> Option { - pixmaps.into_iter() + pixmaps + .into_iter() .max_by(|(w1, h1, _), (w2, h2, _)| { // take smallest one bigger than requested size, otherwise take biggest let a = size * size; @@ -99,7 +96,11 @@ fn icon_from_pixmaps(pixmaps: Vec<(i32, i32, Vec)>, size: i32) -> Option, size: i32) -> std::result::Result { +fn icon_from_name( + icon_name: &str, + theme_path: Option<&str>, + size: i32, +) -> std::result::Result { let theme = if let Some(path) = theme_path { let theme = gtk::IconTheme::new(); theme.prepend_search_path(&path); @@ -134,10 +135,7 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i let icon_path = std::path::Path::new(&icon_name); if icon_path.is_absolute() && icon_path.is_file() { return gtk::gdk_pixbuf::Pixbuf::from_file_at_size(icon_path, size, size) - .map_err(|e| IconError::LoadIconFromFile { - path: icon_name, - source: e, - }); + .map_err(|e| IconError::LoadIconFromFile { path: icon_name, source: e }); } // otherwise, fetch icon theme and lookup using icon_from_name @@ -147,9 +145,7 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i // treat property not existing as the same as it being empty i.e. to use the default // system theme Err(zbus::Error::FDO(e)) => match *e { - zbus::fdo::Error::UnknownProperty(_) - | zbus::fdo::Error::InvalidArgs(_) - => None, + zbus::fdo::Error::UnknownProperty(_) | zbus::fdo::Error::InvalidArgs(_) => None, // this error is reported by discord, blueman-applet zbus::fdo::Error::Failed(msg) if msg == "error occurred in Get" => None, _ => return Err(IconError::DBusTheme(zbus::Error::FDO(e))), @@ -162,10 +158,11 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i }; icon_from_name(&icon_name, icon_theme_path, size) - }).await; + }) + .await; match icon_from_name { Ok(p) => return p, - Err(IconError::NotAvailable) => {}, // try pixbuf + Err(IconError::NotAvailable) => {} // try pixbuf // log and continue Err(e) => log::warn!("failed to get icon by name for {}: {}", sni.destination(), e), }; @@ -177,9 +174,7 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i }, Err(zbus::Error::FDO(e)) => match *e { // property not existing is fine - zbus::fdo::Error::UnknownProperty(_) - | zbus::fdo::Error::InvalidArgs(_) - => Err(IconError::NotAvailable), + zbus::fdo::Error::UnknownProperty(_) | zbus::fdo::Error::InvalidArgs(_) => Err(IconError::NotAvailable), _ => Err(IconError::DBusPixmap(zbus::Error::FDO(e))), }, @@ -187,7 +182,7 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i }; match icon_from_pixmaps { Ok(p) => return p, - Err(IconError::NotAvailable) => {}, + Err(IconError::NotAvailable) => {} Err(e) => log::warn!("failed to get icon pixmap for {}: {}", sni.destination(), e), }; diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index 2c2f8eec1..4ab2f090d 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -44,7 +44,8 @@ impl std::str::FromStr for Status { fn split_service_name(service: &str) -> zbus::Result<(String, String)> { if let Some((addr, path)) = service.split_once('/') { Ok((addr.to_owned(), format!("/{}", path))) - } else if service.contains(':') { // TODO why? + } else if service.contains(':') { + // TODO why? let addr = service.split(':').skip(1).next(); // Some StatusNotifierItems will not return an object path, in that case we fallback // to the default path. @@ -65,15 +66,9 @@ pub struct Item { impl Item { pub async fn from_address(con: &zbus::Connection, addr: &str) -> zbus::Result { let (addr, path) = split_service_name(addr)?; - let sni = dbus::StatusNotifierItemProxy::builder(con) - .destination(addr)? - .path(path)? - .build() - .await?; + let sni = dbus::StatusNotifierItemProxy::builder(con).destination(addr)?.path(path)?.build().await?; - Ok(Item { - sni, - }) + Ok(Item { sni }) } /// Get the current status of the item. diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index 0238faf7e..d04bf1949 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -1,6 +1,4 @@ -use zbus::dbus_interface; -use zbus::Interface; -use zbus::export::ordered_stream::OrderedStreamExt; +use zbus::{dbus_interface, export::ordered_stream::OrderedStreamExt, Interface}; pub const WATCHER_BUS_NAME: &'static str = "org.kde.StatusNotifierWatcher"; pub const WATCHER_OBJECT_NAME: &'static str = "/StatusNotifierWatcher"; @@ -9,8 +7,7 @@ async fn parse_service<'a>( service: &'a str, hdr: zbus::MessageHeader<'_>, con: &zbus::Connection, -) -> zbus::fdo::Result<(zbus::names::UniqueName<'static>, &'a str)> -{ +) -> zbus::fdo::Result<(zbus::names::UniqueName<'static>, &'a str)> { if service.starts_with("/") { // they sent us just the object path :( if let Some(sender) = hdr.sender()? { @@ -25,7 +22,7 @@ async fn parse_service<'a>( Err(e) => { log::warn!("received invalid bus name {:?}: {}", service, e); return Err(zbus::fdo::Error::InvalidArgs(e.to_string())); - }, + } }; if let zbus::names::BusName::Unique(unique) = busname { @@ -45,23 +42,18 @@ async fn parse_service<'a>( } /// Wait for a DBus service to exit -async fn wait_for_service_exit( - connection: zbus::Connection, - service: zbus::names::BusName<'_>, -) -> zbus::fdo::Result<()> { +async fn wait_for_service_exit(connection: zbus::Connection, service: zbus::names::BusName<'_>) -> zbus::fdo::Result<()> { let dbus = zbus::fdo::DBusProxy::new(&connection).await?; - let mut owner_changes = dbus - .receive_name_owner_changed_with_args(&[(0, &service)]) - .await?; + let mut owner_changes = dbus.receive_name_owner_changed_with_args(&[(0, &service)]).await?; if !dbus.name_has_owner(service.as_ref()).await? { - return Ok(()) + return Ok(()); } while let Some(sig) = owner_changes.next().await { let args = sig.args()?; if args.new_owner().is_none() { - break + break; } } @@ -78,7 +70,7 @@ pub struct Watcher { items: std::sync::Arc>>, } -#[dbus_interface(name="org.kde.StatusNotifierWatcher")] +#[dbus_interface(name = "org.kde.StatusNotifierWatcher")] impl Watcher { /// RegisterStatusNotifierHost method async fn register_status_notifier_host( @@ -96,7 +88,7 @@ impl Watcher { let mut hosts = self.hosts.lock().unwrap(); if !hosts.insert(service.to_string()) { // we're already tracking them - return Ok(()) + return Ok(()); } hosts.len() == 1 }; @@ -165,7 +157,7 @@ impl Watcher { if !items.insert(item.clone()) { // we're already tracking them log::info!("new item: {} (duplicate)", item); - return Ok(()) + return Ok(()); } } log::info!("new item: {}", item); @@ -229,7 +221,7 @@ impl Watcher { if !con.object_server().at(WATCHER_OBJECT_NAME, self).await? { // There's already something at this object // TODO is there a more specific error - return Err(zbus::Error::Failure(format!("Connection already has an object at {}", WATCHER_OBJECT_NAME))) + return Err(zbus::Error::Failure(format!("Connection already has an object at {}", WATCHER_OBJECT_NAME))); } // not AllowReplacement, not ReplaceExisting, not DoNotQueue @@ -249,7 +241,8 @@ impl Watcher { Self::name(), &std::collections::HashMap::new(), &["IsStatusNotifierHostRegistered"], - ).await + ) + .await } /// Equivalen to `registered_status_notifier_items_invalidate`, but without requiring `self`. @@ -259,6 +252,7 @@ impl Watcher { Self::name(), &std::collections::HashMap::new(), &["RegisteredStatusNotifierItems"], - ).await + ) + .await } } From 0ca558c74963f1cc9e825afc2cf1a9b87fbfd07f Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Fri, 16 Jun 2023 17:31:25 +1000 Subject: [PATCH 27/54] Documentation --- CHANGELOG.md | 1 + crates/eww/src/widgets/widget_definitions.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47fbdf835..119b785e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ All notable changes to eww will be listed here, starting at changes since versio - Add `--duration` flag to `eww open` - Add support for referring to monitor with `` - Add support for multiple matchers in `monitor` field +- Add `systray` widget (By: ralismark) ## [0.4.0] (04.09.2022) diff --git a/crates/eww/src/widgets/widget_definitions.rs b/crates/eww/src/widgets/widget_definitions.rs index 09953d5a2..e7731c31a 100644 --- a/crates/eww/src/widgets/widget_definitions.rs +++ b/crates/eww/src/widgets/widget_definitions.rs @@ -1125,7 +1125,7 @@ fn parse_justification(j: &str) -> Result { } } -/// @var packdirection - "right", "ltr", "left", "rtl", "down", "ttb", "up", "btt" +/// @var pack-direction - "right", "ltr", "left", "rtl", "down", "ttb", "up", "btt" fn parse_packdirection(o: &str) -> Result { enum_parse! { "packdirection", o, "right" | "ltr" => gtk::PackDirection::Ltr, From 05575d6cc6e96fb85ce57f2f33166d61d8c38a8a Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 18 Jun 2023 21:51:16 +1000 Subject: [PATCH 28/54] Avoid registering to StatusNotifierWatcher multiple times --- crates/eww/src/widgets/systray.rs | 7 +++++-- crates/notifier_host/src/host.rs | 11 ++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 511127dcf..3804d2c76 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -7,6 +7,7 @@ use notifier_host::{self, export::ordered_stream::OrderedStreamExt}; struct DBusGlobalState { con: zbus::Connection, name: zbus::names::WellKnownName<'static>, + snw: notifier_host::dbus::StatusNotifierWatcherProxy<'static>, } async fn dbus_state() -> std::sync::Arc { @@ -24,8 +25,9 @@ async fn dbus_state() -> std::sync::Arc { notifier_host::Watcher::new().attach_to(&con).await.unwrap(); let name = notifier_host::attach_new_wellknown_name(&con).await.unwrap(); + let snw = notifier_host::register_to_watcher(&con, &name).await.unwrap(); - let arc = Arc::new(DBusGlobalState { con, name }); + let arc = Arc::new(DBusGlobalState { con, name, snw }); *dbus_state = Arc::downgrade(&arc); arc @@ -64,10 +66,11 @@ struct Tray { pub fn spawn_systray(menubar: >k::MenuBar, props: &Props) { let mut systray = Tray { menubar: menubar.clone(), items: Default::default(), icon_size: props.icon_size_tx.subscribe() }; + // TODO when does this task die? glib::MainContext::default().spawn_local(async move { let s = &dbus_state().await; systray.menubar.show(); - notifier_host::run_host_forever(&mut systray, &s.con, &s.name).await.unwrap(); + notifier_host::run_host_forever(&mut systray, &s.snw).await.unwrap(); }); } diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index b20b2d79e..5278363c7 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -29,15 +29,20 @@ pub async fn attach_new_wellknown_name(con: &zbus::Connection) -> zbus::Result, -) -> zbus::Result<()> { +) -> zbus::Result> { // register ourself to StatusNotifierWatcher let snw = dbus::StatusNotifierWatcherProxy::new(&con).await?; snw.register_status_notifier_host(&name).await?; + Ok(snw) +} +pub async fn run_host_forever( + host: &mut dyn Host, + snw: &dbus::StatusNotifierWatcherProxy<'static>, +) -> zbus::Result<()> { enum ItemEvent { NewItem(dbus::StatusNotifierItemRegistered), GoneItem(dbus::StatusNotifierItemUnregistered), From b5d8aef5c88b6f4f834aa9e73ec3da77e337fe08 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:24:35 +1000 Subject: [PATCH 29/54] None theme means default theme --- crates/notifier_host/src/host.rs | 5 +---- crates/notifier_host/src/icon.rs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index 5278363c7..c8948e68d 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -39,10 +39,7 @@ pub async fn register_to_watcher( Ok(snw) } -pub async fn run_host_forever( - host: &mut dyn Host, - snw: &dbus::StatusNotifierWatcherProxy<'static>, -) -> zbus::Result<()> { +pub async fn run_host_forever(host: &mut dyn Host, snw: &dbus::StatusNotifierWatcherProxy<'static>) -> zbus::Result<()> { enum ItemEvent { NewItem(dbus::StatusNotifierItemRegistered), GoneItem(dbus::StatusNotifierItemUnregistered), diff --git a/crates/notifier_host/src/icon.rs b/crates/notifier_host/src/icon.rs index 137a165fe..c8bb8fafc 100644 --- a/crates/notifier_host/src/icon.rs +++ b/crates/notifier_host/src/icon.rs @@ -16,7 +16,7 @@ pub enum IconError { #[source] source: gtk::glib::Error, }, - #[error("loading icon {icon_name:?} from theme {theme_path:?}")] + #[error("loading icon {icon_name:?} from theme {}", .theme_path.as_ref().unwrap_or(&"(default)".to_owned()))] LoadIconFromTheme { icon_name: String, theme_path: Option, From e893eb41837123b0f7514f3de14f7ec973ee56ba Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Thu, 22 Jun 2023 22:47:26 +1000 Subject: [PATCH 30/54] Add dbus logging --- crates/notifier_host/src/icon.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/notifier_host/src/icon.rs b/crates/notifier_host/src/icon.rs index c8bb8fafc..2ca7408a6 100644 --- a/crates/notifier_host/src/icon.rs +++ b/crates/notifier_host/src/icon.rs @@ -125,7 +125,9 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i let icon_from_name: std::result::Result = (async { // fetch icon name - let icon_name = match sni.icon_name().await { + let icon_name = sni.icon_name().await; + log::debug!("dbus: {} icon_name -> {:?}", sni.destination(), icon_name); + let icon_name = match icon_name { Ok(s) if s == "" => return Err(IconError::NotAvailable), Ok(s) => s, Err(e) => return Err(IconError::DBusIconName(e)), @@ -139,7 +141,9 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i } // otherwise, fetch icon theme and lookup using icon_from_name - let icon_theme_path = match sni.icon_theme_path().await { + let icon_theme_path = sni.icon_theme_path().await; + log::debug!("dbus: {} icon_theme_path -> {:?}", sni.destination(), icon_theme_path); + let icon_theme_path = match icon_theme_path { Ok(p) if p == "" => None, Ok(p) => Some(p), // treat property not existing as the same as it being empty i.e. to use the default @@ -167,7 +171,9 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i Err(e) => log::warn!("failed to get icon by name for {}: {}", sni.destination(), e), }; - let icon_from_pixmaps = match sni.icon_pixmap().await { + let icon_pixmap = sni.icon_pixmap().await; + log::debug!("dbus: {} icon_pixmap -> {:?}", sni.destination(), icon_pixmap); + let icon_from_pixmaps = match icon_pixmap { Ok(ps) => match icon_from_pixmaps(ps, size) { Some(p) => Ok(p), None => Err(IconError::NotAvailable), From c1a2b8dcd3332c1b9393866a46bf0abb17698fab Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 9 Jul 2023 21:05:45 +1000 Subject: [PATCH 31/54] Add libdbusmenu-gtk3 dependency to docs --- docs/src/eww.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/eww.md b/docs/src/eww.md index 4a25bd4f8..1aa1aee1f 100644 --- a/docs/src/eww.md +++ b/docs/src/eww.md @@ -30,6 +30,7 @@ The following list of package names should work for arch linux: - gtk-layer-shell (only on Wayland) - pango (libpango) - gdk-pixbuf2 (libgdk_pixbuf-2) +- libdbusmenu-gtk3 - cairo (libcairo, libcairo-gobject) - glib2 (libgio, libglib-2, libgobject-2) - gcc-libs (libgcc) From 6f6edaf7e19c49bcf3de680f10a175c07ebbbd77 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Fri, 18 Aug 2023 12:43:11 +1000 Subject: [PATCH 32/54] Some code tidying --- crates/eww/src/widgets/systray.rs | 16 ++++++----- crates/notifier_host/src/icon.rs | 25 ++++++++-------- crates/notifier_host/src/item.rs | 2 +- crates/notifier_host/src/watcher.rs | 44 ++++++++++++++++++++--------- 4 files changed, 52 insertions(+), 35 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 3804d2c76..23ce0288d 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -1,12 +1,10 @@ -#![allow(unused)] - use gtk::prelude::*; use notifier_host::{self, export::ordered_stream::OrderedStreamExt}; // DBus state shared between systray instances, to avoid creating too many connections etc. struct DBusGlobalState { - con: zbus::Connection, - name: zbus::names::WellKnownName<'static>, + // con: zbus::Connection, + // name: zbus::names::WellKnownName<'static>, snw: notifier_host::dbus::StatusNotifierWatcherProxy<'static>, } @@ -27,7 +25,11 @@ async fn dbus_state() -> std::sync::Arc { let name = notifier_host::attach_new_wellknown_name(&con).await.unwrap(); let snw = notifier_host::register_to_watcher(&con, &name).await.unwrap(); - let arc = Arc::new(DBusGlobalState { con, name, snw }); + let arc = Arc::new(DBusGlobalState { + // con, + // name, + snw, + }); *dbus_state = Arc::downgrade(&arc); arc @@ -139,7 +141,7 @@ impl Item { mi.set_tooltip_text(Some(&item.sni.title().await.unwrap())); // set icon - icon.set_from_pixbuf(Some(&item.icon(*icon_size.borrow_and_update()).await)); + icon.set_from_pixbuf(item.icon(*icon_size.borrow_and_update()).await.as_ref()); // updates let mut status_updates = item.sni.receive_new_status().await.unwrap(); @@ -156,7 +158,7 @@ impl Item { } Ok(_) = icon_size.changed() => { // set icon - icon.set_from_pixbuf(Some(&item.icon(*icon_size.borrow_and_update()).await)); + icon.set_from_pixbuf(item.icon(*icon_size.borrow_and_update()).await.as_ref()); } Some(_) = title_updates.next() => { // set title diff --git a/crates/notifier_host/src/icon.rs b/crates/notifier_host/src/icon.rs index 2ca7408a6..a33a91d65 100644 --- a/crates/notifier_host/src/icon.rs +++ b/crates/notifier_host/src/icon.rs @@ -28,16 +28,15 @@ pub enum IconError { } /// Get the fallback GTK icon -pub async fn fallback_icon(size: i32) -> gtk::gdk_pixbuf::Pixbuf { +pub async fn fallback_icon(size: i32) -> Option { let theme = gtk::IconTheme::default().expect("Could not get default gtk theme"); - return match theme.load_icon("image-missing", size, gtk::IconLookupFlags::FORCE_SIZE) { + match theme.load_icon("image-missing", size, gtk::IconLookupFlags::FORCE_SIZE) { + Ok(pb) => pb, Err(e) => { log::error!("failed to load \"image-missing\" from default theme: {}", e); - // create a blank pixbuf - gtk::gdk_pixbuf::Pixbuf::new(gtk::gdk_pixbuf::Colorspace::Rgb, false, 0, size, size).unwrap() + None } - Ok(pb) => pb.unwrap(), - }; + } } /// Load a pixbuf from StatusNotifierItem's [Icon format]. @@ -46,7 +45,7 @@ pub async fn fallback_icon(size: i32) -> gtk::gdk_pixbuf::Pixbuf { fn icon_from_pixmap(width: i32, height: i32, mut data: Vec) -> gtk::gdk_pixbuf::Pixbuf { // We need to convert data from ARGB32 to RGBA32, since that's the only one that gdk-pixbuf // understands. - for chunk in data.chunks_mut(4) { + for chunk in data.chunks_exact_mut(4) { let a = chunk[0]; let r = chunk[1]; let g = chunk[2]; @@ -86,12 +85,12 @@ fn icon_from_pixmaps(pixmaps: Vec<(i32, i32, Vec)>, size: i32) -> Option a1.cmp(&a2), } }) - .map(|(w, h, d)| { + .and_then(|(w, h, d)| { let pixbuf = icon_from_pixmap(w, h, d); if w != size || h != size { - pixbuf.scale_simple(size, size, gtk::gdk_pixbuf::InterpType::Bilinear).unwrap() + pixbuf.scale_simple(size, size, gtk::gdk_pixbuf::InterpType::Bilinear) } else { - pixbuf + Some(pixbuf) } }) } @@ -119,7 +118,7 @@ fn icon_from_name( } } -pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i32) -> gtk::gdk_pixbuf::Pixbuf { +pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i32) -> Option { // "Visualizations are encouraged to prefer icon names over icon pixmaps if both are // available." @@ -165,7 +164,7 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i }) .await; match icon_from_name { - Ok(p) => return p, + Ok(p) => return Some(p), Err(IconError::NotAvailable) => {} // try pixbuf // log and continue Err(e) => log::warn!("failed to get icon by name for {}: {}", sni.destination(), e), @@ -187,7 +186,7 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i Err(e) => Err(IconError::DBusPixmap(e)), }; match icon_from_pixmaps { - Ok(p) => return p, + Ok(p) => return Some(p), Err(IconError::NotAvailable) => {} Err(e) => log::warn!("failed to get icon pixmap for {}: {}", sni.destination(), e), }; diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index 4ab2f090d..a6e18d63a 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -86,7 +86,7 @@ impl Item { Ok(menu.upcast()) } - pub async fn icon(&self, size: i32) -> gtk::gdk_pixbuf::Pixbuf { + pub async fn icon(&self, size: i32) -> Option { // see icon.rs load_icon_from_sni(&self.sni, size).await } diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index d04bf1949..64ff68e94 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -28,8 +28,7 @@ async fn parse_service<'a>( if let zbus::names::BusName::Unique(unique) = busname { Ok((unique.to_owned(), "/StatusNotifierItem")) } else { - // unwrap: we should always be able to access the dbus interface - let dbus = zbus::fdo::DBusProxy::new(&con).await.unwrap(); + let dbus = zbus::fdo::DBusProxy::new(&con).await?; match dbus.get_name_owner(busname).await { Ok(owner) => Ok((owner.into_inner(), "/StatusNotifierItem")), Err(e) => { @@ -66,6 +65,11 @@ async fn wait_for_service_exit(connection: zbus::Connection, service: zbus::name #[derive(Debug, Default)] pub struct Watcher { tasks: tokio::task::JoinSet<()>, + + // NOTE Intentionally using std::sync::Mutex instead of tokio's async mutex, since we don't + // need to hold the mutex across an await. + // + // See hosts: std::sync::Arc>>, items: std::sync::Arc>>, } @@ -85,7 +89,7 @@ impl Watcher { let added_first = { // scoped around locking of hosts - let mut hosts = self.hosts.lock().unwrap(); + let mut hosts = self.hosts.lock().unwrap(); // unwrap: mutex poisoning is okay if !hosts.insert(service.to_string()) { // we're already tracking them return Ok(()); @@ -103,19 +107,25 @@ impl Watcher { let ctxt = ctxt.to_owned(); let con = con.to_owned(); async move { - wait_for_service_exit(con.clone(), service.as_ref().into()).await.unwrap(); + if let Err(e) = wait_for_service_exit(con.clone(), service.as_ref().into()).await { + log::error!("failed to wait for service exit: {}", e); + } log::info!("lost host: {}", service); let removed_last = { - let mut hosts = hosts.lock().unwrap(); + let mut hosts = hosts.lock().unwrap(); // unwrap: mutex poisoning is okay let did_remove = hosts.remove(service.as_str()); did_remove && hosts.is_empty() }; if removed_last { - Watcher::is_status_notifier_host_registered_refresh(&ctxt).await.unwrap(); + if let Err(e) = Watcher::is_status_notifier_host_registered_refresh(&ctxt).await { + log::error!("failed to signal Watcher: {}", e); + } + } + if let Err(e) = Watcher::status_notifier_host_unregistered(&ctxt).await { + log::error!("failed to signal Watcher: {}", e); } - Watcher::status_notifier_host_unregistered(&ctxt).await.unwrap(); } }); @@ -133,7 +143,7 @@ impl Watcher { /// IsStatusNotifierHostRegistered property #[dbus_interface(property)] async fn is_status_notifier_host_registered(&self) -> bool { - let hosts = self.hosts.lock().unwrap(); + let hosts = self.hosts.lock().unwrap(); // unwrap: mutex poisoning is okay !hosts.is_empty() } @@ -153,7 +163,7 @@ impl Watcher { let item = format!("{}{}", service, objpath); { - let mut items = self.items.lock().unwrap(); + let mut items = self.items.lock().unwrap(); // unwrap: mutex poisoning is okay if !items.insert(item.clone()) { // we're already tracking them log::info!("new item: {} (duplicate)", item); @@ -170,16 +180,22 @@ impl Watcher { let ctxt = ctxt.to_owned(); let con = con.to_owned(); async move { - wait_for_service_exit(con.clone(), service.as_ref()).await.unwrap(); + if let Err(e) = wait_for_service_exit(con.clone(), service.as_ref()).await { + log::error!("failed to wait for service exit: {}", e); + } println!("gone item: {}", &item); { - let mut items = items.lock().unwrap(); + let mut items = items.lock().unwrap(); // unwrap: mutex poisoning is okay items.remove(&item); } - Watcher::registered_status_notifier_items_refresh(&ctxt).await.unwrap(); - Watcher::status_notifier_item_unregistered(&ctxt, item.as_ref()).await.unwrap(); + if let Err(e) = Watcher::registered_status_notifier_items_refresh(&ctxt).await { + log::error!("failed to signal Watcher: {}", e); + } + if let Err(e) = Watcher::status_notifier_item_unregistered(&ctxt, item.as_ref()).await { + log::error!("failed to signal Watcher: {}", e); + } } }); @@ -197,7 +213,7 @@ impl Watcher { /// RegisteredStatusNotifierItems property #[dbus_interface(property)] async fn registered_status_notifier_items(&self) -> Vec { - let items = self.items.lock().unwrap(); + let items = self.items.lock().unwrap(); // unwrap: mutex poisoning is okay items.iter().cloned().collect() } From 97bcd7eb6056863b893daffbd66fdbe058fa9d87 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Fri, 18 Aug 2023 12:50:04 +1000 Subject: [PATCH 33/54] Make Item more clearer --- crates/eww/src/widgets/systray.rs | 33 +++++++++++++++++-------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 23ce0288d..2c77608f0 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -79,31 +79,34 @@ pub fn spawn_systray(menubar: >k::MenuBar, props: &Props) { impl notifier_host::Host for Tray { fn add_item(&mut self, id: &str, item: notifier_host::Item) { let item = Item::new(id.to_owned(), item, self.icon_size.clone()); - self.menubar.add(&item.mi); + self.menubar.add(&item.widget); if let Some(old_item) = self.items.insert(id.to_string(), item) { - self.menubar.remove(&old_item.mi); + self.menubar.remove(&old_item.widget); } } fn remove_item(&mut self, id: &str) { if let Some(item) = self.items.get(id) { - self.menubar.remove(&item.mi); + self.menubar.remove(&item.widget); } else { log::warn!("Tried to remove nonexistent item {:?} from systray", id); } } } +/// Item represents a single icon being shown in the system tray. struct Item { - mi: gtk::MenuItem, + /// Main widget representing this tray item. + widget: gtk::MenuItem, + /// Async tasks to stop when this item gets removed. tasks: Vec, } impl Drop for Item { fn drop(&mut self) { for task in self.tasks.drain(..) { - // TODO does this abort the task + // TODO verify that this does indeed stop the task task.remove(); } } @@ -111,20 +114,20 @@ impl Drop for Item { impl Item { fn new(id: String, item: notifier_host::Item, mut icon_size: tokio::sync::watch::Receiver) -> Self { - let mi = gtk::MenuItem::new(); - let mut out = Self { mi: mi.clone(), tasks: Vec::new() }; + let widget = gtk::MenuItem::new(); + let mut out = Self { widget: widget.clone(), tasks: Vec::new() }; out.spawn(async move { // TODO don't unwrap so much // init icon let icon = gtk::Image::new(); - mi.add(&icon); + widget.add(&icon); icon.show(); // init menu match item.menu().await { - Ok(m) => mi.set_submenu(Some(&m)), + Ok(m) => widget.set_submenu(Some(&m)), Err(e) => log::warn!("failed to get menu of {}: {}", id, e), } @@ -133,12 +136,12 @@ impl Item { // set status match item.status().await.unwrap() { - notifier_host::Status::Passive => mi.hide(), - notifier_host::Status::Active | notifier_host::Status::NeedsAttention => mi.show(), + notifier_host::Status::Passive => widget.hide(), + notifier_host::Status::Active | notifier_host::Status::NeedsAttention => widget.show(), } // set title - mi.set_tooltip_text(Some(&item.sni.title().await.unwrap())); + widget.set_tooltip_text(Some(&item.sni.title().await.unwrap())); // set icon icon.set_from_pixbuf(item.icon(*icon_size.borrow_and_update()).await.as_ref()); @@ -152,8 +155,8 @@ impl Item { Some(_) = status_updates.next() => { // set status match item.status().await.unwrap() { - notifier_host::Status::Passive => mi.hide(), - notifier_host::Status::Active | notifier_host::Status::NeedsAttention => mi.show(), + notifier_host::Status::Passive => widget.hide(), + notifier_host::Status::Active | notifier_host::Status::NeedsAttention => widget.show(), } } Ok(_) = icon_size.changed() => { @@ -162,7 +165,7 @@ impl Item { } Some(_) = title_updates.next() => { // set title - mi.set_tooltip_text(Some(&item.sni.title().await.unwrap())); + widget.set_tooltip_text(Some(&item.sni.title().await.unwrap())); } } } From 437a6d7b25d5869f80ed7bbea16828bab08bad34 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Fri, 18 Aug 2023 12:58:54 +1000 Subject: [PATCH 34/54] Make clippy happy --- crates/eww/src/widgets/systray.rs | 2 +- .../notifier_host/src/dbus/dbus_status_notifier_item.rs | 3 +++ crates/notifier_host/src/host.rs | 4 ++-- crates/notifier_host/src/icon.rs | 9 +++++---- crates/notifier_host/src/item.rs | 2 +- crates/notifier_host/src/watcher.rs | 8 ++++---- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 2c77608f0..7095164b1 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -68,7 +68,7 @@ struct Tray { pub fn spawn_systray(menubar: >k::MenuBar, props: &Props) { let mut systray = Tray { menubar: menubar.clone(), items: Default::default(), icon_size: props.icon_size_tx.subscribe() }; - // TODO when does this task die? + // TODO stop this task when systray is disabled glib::MainContext::default().spawn_local(async move { let s = &dbus_state().await; systray.menubar.show(); diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs b/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs index e20e32d68..0f472e350 100644 --- a/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs +++ b/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs @@ -9,6 +9,9 @@ //! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html) //! section of the zbus documentation. +// suppress warning from generated code +#![allow(clippy::type_complexity)] + use zbus::dbus_proxy; #[dbus_proxy(interface = "org.kde.StatusNotifierItem", assume_defaults = true)] diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index c8948e68d..ff60a0d33 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -34,8 +34,8 @@ pub async fn register_to_watcher( name: &zbus::names::WellKnownName<'_>, ) -> zbus::Result> { // register ourself to StatusNotifierWatcher - let snw = dbus::StatusNotifierWatcherProxy::new(&con).await?; - snw.register_status_notifier_host(&name).await?; + let snw = dbus::StatusNotifierWatcherProxy::new(con).await?; + snw.register_status_notifier_host(name).await?; Ok(snw) } diff --git a/crates/notifier_host/src/icon.rs b/crates/notifier_host/src/icon.rs index a33a91d65..0a7520573 100644 --- a/crates/notifier_host/src/icon.rs +++ b/crates/notifier_host/src/icon.rs @@ -127,7 +127,7 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i let icon_name = sni.icon_name().await; log::debug!("dbus: {} icon_name -> {:?}", sni.destination(), icon_name); let icon_name = match icon_name { - Ok(s) if s == "" => return Err(IconError::NotAvailable), + Ok(s) if s.is_empty() => return Err(IconError::NotAvailable), Ok(s) => s, Err(e) => return Err(IconError::DBusIconName(e)), }; @@ -143,7 +143,7 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i let icon_theme_path = sni.icon_theme_path().await; log::debug!("dbus: {} icon_theme_path -> {:?}", sni.destination(), icon_theme_path); let icon_theme_path = match icon_theme_path { - Ok(p) if p == "" => None, + Ok(p) if p.is_empty() => None, Ok(p) => Some(p), // treat property not existing as the same as it being empty i.e. to use the default // system theme @@ -155,11 +155,12 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i }, Err(e) => return Err(IconError::DBusTheme(e)), }; + let icon_theme_path: Option<&str> = match &icon_theme_path { - Some(s) => Some(&s), + // this looks weird but this converts &String to &str + Some(s) => Some(s), None => None, }; - icon_from_name(&icon_name, icon_theme_path, size) }) .await; diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index a6e18d63a..5bd378a5a 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -46,7 +46,7 @@ fn split_service_name(service: &str) -> zbus::Result<(String, String)> { Ok((addr.to_owned(), format!("/{}", path))) } else if service.contains(':') { // TODO why? - let addr = service.split(':').skip(1).next(); + let addr = service.split(':').nth(1); // Some StatusNotifierItems will not return an object path, in that case we fallback // to the default path. if let Some(addr) = addr { diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index 64ff68e94..8e206ea8b 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -1,14 +1,14 @@ use zbus::{dbus_interface, export::ordered_stream::OrderedStreamExt, Interface}; -pub const WATCHER_BUS_NAME: &'static str = "org.kde.StatusNotifierWatcher"; -pub const WATCHER_OBJECT_NAME: &'static str = "/StatusNotifierWatcher"; +pub const WATCHER_BUS_NAME: &str = "org.kde.StatusNotifierWatcher"; +pub const WATCHER_OBJECT_NAME: &str = "/StatusNotifierWatcher"; async fn parse_service<'a>( service: &'a str, hdr: zbus::MessageHeader<'_>, con: &zbus::Connection, ) -> zbus::fdo::Result<(zbus::names::UniqueName<'static>, &'a str)> { - if service.starts_with("/") { + if service.starts_with('/') { // they sent us just the object path :( if let Some(sender) = hdr.sender()? { Ok((sender.to_owned(), service)) @@ -28,7 +28,7 @@ async fn parse_service<'a>( if let zbus::names::BusName::Unique(unique) = busname { Ok((unique.to_owned(), "/StatusNotifierItem")) } else { - let dbus = zbus::fdo::DBusProxy::new(&con).await?; + let dbus = zbus::fdo::DBusProxy::new(con).await?; match dbus.get_name_owner(busname).await { Ok(owner) => Ok((owner.into_inner(), "/StatusNotifierItem")), Err(e) => { From ca514cba98f16a92edf7064b328af03b5583092e Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 20 Aug 2023 11:28:00 +1000 Subject: [PATCH 35/54] Systray widget improvements --- crates/eww/src/widgets/systray.rs | 128 ++++++++++++++++-------------- 1 file changed, 70 insertions(+), 58 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 7095164b1..628ac335f 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -68,11 +68,17 @@ struct Tray { pub fn spawn_systray(menubar: >k::MenuBar, props: &Props) { let mut systray = Tray { menubar: menubar.clone(), items: Default::default(), icon_size: props.icon_size_tx.subscribe() }; - // TODO stop this task when systray is disabled - glib::MainContext::default().spawn_local(async move { + let task = glib::MainContext::default().spawn_local(async move { let s = &dbus_state().await; systray.menubar.show(); - notifier_host::run_host_forever(&mut systray, &s.snw).await.unwrap(); + if let Err(e) = notifier_host::run_host_forever(&mut systray, &s.snw).await { + log::error!("notifier host error: {}", e); + } + }); + + // stop the task when the widget is dropped + menubar.connect_destroy(move |_| { + task.abort(); }); } @@ -99,82 +105,88 @@ struct Item { /// Main widget representing this tray item. widget: gtk::MenuItem, - /// Async tasks to stop when this item gets removed. - tasks: Vec, + /// Async task to stop when this item gets removed. + task: Option>, } impl Drop for Item { fn drop(&mut self) { - for task in self.tasks.drain(..) { - // TODO verify that this does indeed stop the task - task.remove(); + if let Some(task) = &self.task { + task.abort(); } } } impl Item { - fn new(id: String, item: notifier_host::Item, mut icon_size: tokio::sync::watch::Receiver) -> Self { + fn new(id: String, item: notifier_host::Item, icon_size: tokio::sync::watch::Receiver) -> Self { let widget = gtk::MenuItem::new(); - let mut out = Self { widget: widget.clone(), tasks: Vec::new() }; + let out_widget = widget.clone(); // copy so we can return it - out.spawn(async move { - // TODO don't unwrap so much + let task = glib::MainContext::default().spawn_local(async move { + if let Err(e) = Item::maintain(widget.clone(), item, icon_size).await { + log::error!("error for systray item {}: {}", id, e); + } + }); - // init icon - let icon = gtk::Image::new(); - widget.add(&icon); - icon.show(); + Self { + widget: out_widget, + task: Some(task), + } + } - // init menu - match item.menu().await { - Ok(m) => widget.set_submenu(Some(&m)), - Err(e) => log::warn!("failed to get menu of {}: {}", id, e), - } + async fn maintain( + widget: gtk::MenuItem, + item: notifier_host::Item, + mut icon_size: tokio::sync::watch::Receiver, + ) -> zbus::Result<()> { + // init icon + let icon = gtk::Image::new(); + widget.add(&icon); + icon.show(); + + // init menu + match item.menu().await { + Ok(m) => widget.set_submenu(Some(&m)), + Err(e) => log::warn!("failed to get menu: {}", e), + } - // TODO this is a lot of code duplication unfortunately, i'm not really sure how to - // refactor without making the borrow checker angry + // TODO this is a lot of code duplication unfortunately, i'm not really sure how to + // refactor without making the borrow checker angry - // set status - match item.status().await.unwrap() { - notifier_host::Status::Passive => widget.hide(), - notifier_host::Status::Active | notifier_host::Status::NeedsAttention => widget.show(), - } + // set status + match item.status().await? { + notifier_host::Status::Passive => widget.hide(), + notifier_host::Status::Active | notifier_host::Status::NeedsAttention => widget.show(), + } - // set title - widget.set_tooltip_text(Some(&item.sni.title().await.unwrap())); + // set title + widget.set_tooltip_text(Some(&item.sni.title().await?)); - // set icon - icon.set_from_pixbuf(item.icon(*icon_size.borrow_and_update()).await.as_ref()); + // set icon + icon.set_from_pixbuf(item.icon(*icon_size.borrow_and_update()).await.as_ref()); - // updates - let mut status_updates = item.sni.receive_new_status().await.unwrap(); - let mut title_updates = item.sni.receive_new_status().await.unwrap(); + // updates + let mut status_updates = item.sni.receive_new_status().await?; + let mut title_updates = item.sni.receive_new_status().await?; - loop { - tokio::select! { - Some(_) = status_updates.next() => { - // set status - match item.status().await.unwrap() { - notifier_host::Status::Passive => widget.hide(), - notifier_host::Status::Active | notifier_host::Status::NeedsAttention => widget.show(), - } - } - Ok(_) = icon_size.changed() => { - // set icon - icon.set_from_pixbuf(item.icon(*icon_size.borrow_and_update()).await.as_ref()); - } - Some(_) = title_updates.next() => { - // set title - widget.set_tooltip_text(Some(&item.sni.title().await.unwrap())); + loop { + tokio::select! { + Some(_) = status_updates.next() => { + // set status + match item.status().await? { + notifier_host::Status::Passive => widget.hide(), + notifier_host::Status::Active | notifier_host::Status::NeedsAttention => widget.show(), } } + Ok(_) = icon_size.changed() => { + // set icon + icon.set_from_pixbuf(item.icon(*icon_size.borrow_and_update()).await.as_ref()); + } + Some(_) = title_updates.next() => { + // set title + widget.set_tooltip_text(Some(&item.sni.title().await?)); + } } - }); - - out - } - - fn spawn(&mut self, f: impl std::future::Future + 'static) { - self.tasks.push(glib::MainContext::default().spawn_local(f)); + } } } From 70a1cb19c13a17e2df8b9dad179b8f6516f6ff22 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 20 Aug 2023 11:49:32 +1000 Subject: [PATCH 36/54] Remove unwraps from dbus state --- crates/eww/src/widgets/systray.rs | 62 +++++++++++++++---------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 628ac335f..c6da58a87 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -2,38 +2,32 @@ use gtk::prelude::*; use notifier_host::{self, export::ordered_stream::OrderedStreamExt}; // DBus state shared between systray instances, to avoid creating too many connections etc. -struct DBusGlobalState { +struct DBusSession { // con: zbus::Connection, // name: zbus::names::WellKnownName<'static>, snw: notifier_host::dbus::StatusNotifierWatcherProxy<'static>, } -async fn dbus_state() -> std::sync::Arc { - use once_cell::sync::Lazy; - use std::sync::{Arc, Weak}; - use tokio::sync::Mutex; - static DBUS_STATE: Lazy>> = Lazy::new(Default::default); - - let mut dbus_state = DBUS_STATE.lock().await; - if let Some(state) = dbus_state.upgrade() { - state - } else { - // TODO error handling? - let con = zbus::Connection::session().await.unwrap(); - notifier_host::Watcher::new().attach_to(&con).await.unwrap(); - - let name = notifier_host::attach_new_wellknown_name(&con).await.unwrap(); - let snw = notifier_host::register_to_watcher(&con, &name).await.unwrap(); - - let arc = Arc::new(DBusGlobalState { - // con, - // name, - snw, - }); - *dbus_state = Arc::downgrade(&arc); - - arc - } +async fn dbus_session() -> zbus::Result<&'static DBusSession> { + // TODO make DBusSession reference counted so it's dropped when not in use? + + static DBUS_STATE: tokio::sync::OnceCell = tokio::sync::OnceCell::const_new(); + DBUS_STATE + .get_or_try_init(|| async { + // TODO error handling? + let con = zbus::Connection::session().await?; + notifier_host::Watcher::new().attach_to(&con).await?; + + let name = notifier_host::attach_new_wellknown_name(&con).await?; + let snw = notifier_host::register_to_watcher(&con, &name).await?; + + Ok(DBusSession { + // con, + // name, + snw, + }) + }) + .await } pub struct Props { @@ -69,7 +63,14 @@ pub fn spawn_systray(menubar: >k::MenuBar, props: &Props) { let mut systray = Tray { menubar: menubar.clone(), items: Default::default(), icon_size: props.icon_size_tx.subscribe() }; let task = glib::MainContext::default().spawn_local(async move { - let s = &dbus_state().await; + let s = match dbus_session().await { + Ok(x) => x, + Err(e) => { + log::error!("could not initialise dbus connection for tray: {}", e); + return; + } + }; + systray.menubar.show(); if let Err(e) = notifier_host::run_host_forever(&mut systray, &s.snw).await { log::error!("notifier host error: {}", e); @@ -128,10 +129,7 @@ impl Item { } }); - Self { - widget: out_widget, - task: Some(task), - } + Self { widget: out_widget, task: Some(task) } } async fn maintain( From 7e48bda6f343ad508a2f2962cf16167953357a6e Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Mon, 21 Aug 2023 22:49:39 +1000 Subject: [PATCH 37/54] Temporarily add libdbusmenu-gtk3 to flake buildInputs --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index 456564e97..ab09e913d 100644 --- a/flake.nix +++ b/flake.nix @@ -36,6 +36,8 @@ src = builtins.path { name = "eww"; path = prev.lib.cleanSource ./.; }; cargoDeps = rustPlatform.importCargoLock { lockFile = ./Cargo.lock; }; patches = [ ]; + # remove this when nixpkgs includes it + buildInputs = old.buildInputs ++ [ final.libdbusmenu-gtk3 ]; }); eww-wayland = final.eww.override { withWayland = true; }; From 9e6f6a1cad41fb13beba4f36b5f46f77091350c3 Mon Sep 17 00:00:00 2001 From: MoetaYuko Date: Sat, 5 Aug 2023 19:22:24 +0800 Subject: [PATCH 38/54] Fix blurry tray icon for HiDPI display --- crates/eww/src/widgets/systray.rs | 22 +++++++++++++++++++--- crates/notifier_host/src/icon.rs | 22 ++++++++++++++-------- crates/notifier_host/src/item.rs | 4 ++-- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index c6da58a87..7a312366f 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -1,4 +1,4 @@ -use gtk::prelude::*; +use gtk::{cairo::Surface, gdk::ffi::gdk_cairo_surface_create_from_pixbuf, prelude::*}; use notifier_host::{self, export::ordered_stream::OrderedStreamExt}; // DBus state shared between systray instances, to avoid creating too many connections etc. @@ -161,7 +161,8 @@ impl Item { widget.set_tooltip_text(Some(&item.sni.title().await?)); // set icon - icon.set_from_pixbuf(item.icon(*icon_size.borrow_and_update()).await.as_ref()); + let scale = icon.scale_factor(); + load_icon_for_item(&icon, &item, *icon_size.borrow_and_update(), scale).await; // updates let mut status_updates = item.sni.receive_new_status().await?; @@ -178,7 +179,7 @@ impl Item { } Ok(_) = icon_size.changed() => { // set icon - icon.set_from_pixbuf(item.icon(*icon_size.borrow_and_update()).await.as_ref()); + load_icon_for_item(&icon, &item, *icon_size.borrow_and_update(), scale).await; } Some(_) = title_updates.next() => { // set title @@ -188,3 +189,18 @@ impl Item { } } } + +async fn load_icon_for_item(icon: >k::Image, item: ¬ifier_host::Item, size: i32, scale: i32) { + if let Some(pixbuf) = item.icon(size, scale).await { + let surface = unsafe { + // gtk::cairo::Surface will destroy the underlying surface on drop + let ptr = gdk_cairo_surface_create_from_pixbuf( + pixbuf.as_ptr(), + scale, + icon.window().map_or(std::ptr::null_mut(), |v| v.as_ptr()), + ); + Surface::from_raw_full(ptr) + }; + icon.set_from_surface(surface.ok().as_ref()); + } +} diff --git a/crates/notifier_host/src/icon.rs b/crates/notifier_host/src/icon.rs index 0a7520573..45f7fc665 100644 --- a/crates/notifier_host/src/icon.rs +++ b/crates/notifier_host/src/icon.rs @@ -28,9 +28,9 @@ pub enum IconError { } /// Get the fallback GTK icon -pub async fn fallback_icon(size: i32) -> Option { +async fn fallback_icon(size: i32, scale: i32) -> Option { let theme = gtk::IconTheme::default().expect("Could not get default gtk theme"); - match theme.load_icon("image-missing", size, gtk::IconLookupFlags::FORCE_SIZE) { + match theme.load_icon_for_scale("image-missing", size, scale, gtk::IconLookupFlags::FORCE_SIZE) { Ok(pb) => pb, Err(e) => { log::error!("failed to load \"image-missing\" from default theme: {}", e); @@ -99,6 +99,7 @@ fn icon_from_name( icon_name: &str, theme_path: Option<&str>, size: i32, + scale: i32, ) -> std::result::Result { let theme = if let Some(path) = theme_path { let theme = gtk::IconTheme::new(); @@ -108,7 +109,7 @@ fn icon_from_name( gtk::IconTheme::default().expect("Could not get default gtk theme") }; - match theme.load_icon(icon_name, size, gtk::IconLookupFlags::FORCE_SIZE) { + match theme.load_icon_for_scale(icon_name, size, scale, gtk::IconLookupFlags::FORCE_SIZE) { Ok(pb) => Ok(pb.expect("no pixbuf from theme.load_icon despite no error")), Err(e) => Err(IconError::LoadIconFromTheme { icon_name: icon_name.to_owned(), @@ -118,10 +119,15 @@ fn icon_from_name( } } -pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i32) -> Option { +pub async fn load_icon_from_sni( + sni: &dbus::StatusNotifierItemProxy<'_>, + size: i32, + scale: i32, +) -> Option { // "Visualizations are encouraged to prefer icon names over icon pixmaps if both are // available." + let scaled_size = size * scale; let icon_from_name: std::result::Result = (async { // fetch icon name let icon_name = sni.icon_name().await; @@ -135,7 +141,7 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i // interpret it as an absolute path if we can let icon_path = std::path::Path::new(&icon_name); if icon_path.is_absolute() && icon_path.is_file() { - return gtk::gdk_pixbuf::Pixbuf::from_file_at_size(icon_path, size, size) + return gtk::gdk_pixbuf::Pixbuf::from_file_at_size(icon_path, scaled_size, scaled_size) .map_err(|e| IconError::LoadIconFromFile { path: icon_name, source: e }); } @@ -161,7 +167,7 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i Some(s) => Some(s), None => None, }; - icon_from_name(&icon_name, icon_theme_path, size) + icon_from_name(&icon_name, icon_theme_path, size, scale) }) .await; match icon_from_name { @@ -174,7 +180,7 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i let icon_pixmap = sni.icon_pixmap().await; log::debug!("dbus: {} icon_pixmap -> {:?}", sni.destination(), icon_pixmap); let icon_from_pixmaps = match icon_pixmap { - Ok(ps) => match icon_from_pixmaps(ps, size) { + Ok(ps) => match icon_from_pixmaps(ps, scaled_size) { Some(p) => Ok(p), None => Err(IconError::NotAvailable), }, @@ -192,5 +198,5 @@ pub async fn load_icon_from_sni(sni: &dbus::StatusNotifierItemProxy<'_>, size: i Err(e) => log::warn!("failed to get icon pixmap for {}: {}", sni.destination(), e), }; - fallback_icon(size).await + fallback_icon(size, scale).await } diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index 5bd378a5a..7fc9eb2bf 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -86,8 +86,8 @@ impl Item { Ok(menu.upcast()) } - pub async fn icon(&self, size: i32) -> Option { + pub async fn icon(&self, size: i32, scale: i32) -> Option { // see icon.rs - load_icon_from_sni(&self.sni, size).await + load_icon_from_sni(&self.sni, size, scale).await } } From 218a4056e4e5b4232580daf675a4ba334865ed93 Mon Sep 17 00:00:00 2001 From: hylo Date: Tue, 5 Sep 2023 17:39:31 +0200 Subject: [PATCH 39/54] feat: dynamic icons --- crates/eww/src/widgets/systray.rs | 5 +++++ crates/notifier_host/src/dbus/dbus_status_notifier_item.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 7a312366f..7a2c7dc00 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -167,6 +167,7 @@ impl Item { // updates let mut status_updates = item.sni.receive_new_status().await?; let mut title_updates = item.sni.receive_new_status().await?; + let mut icon_updates = item.sni.receive_new_icon().await?; loop { tokio::select! { @@ -185,6 +186,10 @@ impl Item { // set title widget.set_tooltip_text(Some(&item.sni.title().await?)); } + Some(_) = icon_updates.next() => { + // set icon + icon.set_from_pixbuf(item.icon(*icon_size.borrow_and_update()).await.as_ref()); + } } } } diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs b/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs index 0f472e350..7d5b0382b 100644 --- a/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs +++ b/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs @@ -69,7 +69,7 @@ trait StatusNotifierItem { fn category(&self) -> zbus::Result; /// IconName property - #[dbus_proxy(property)] + #[dbus_proxy(property(emits_changed_signal = "false"))] fn icon_name(&self) -> zbus::Result; /// IconPixmap property From 6a86d25abc97034ef7d6f38ba298533ce7265672 Mon Sep 17 00:00:00 2001 From: hylo Date: Fri, 29 Sep 2023 17:01:20 +0200 Subject: [PATCH 40/54] fix: don't cache IconPixmap property this fixes dynamic icons for some icons, e.g. syncthingtray --- crates/notifier_host/src/dbus/dbus_status_notifier_item.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs b/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs index 7d5b0382b..5c57b2a15 100644 --- a/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs +++ b/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs @@ -73,7 +73,7 @@ trait StatusNotifierItem { fn icon_name(&self) -> zbus::Result; /// IconPixmap property - #[dbus_proxy(property)] + #[dbus_proxy(property(emits_changed_signal = "false"))] fn icon_pixmap(&self) -> zbus::Result)>>; /// IconThemePath property From b32b165f02a9b72d93f2948ef92568ff481be283 Mon Sep 17 00:00:00 2001 From: MoetaYuko Date: Sun, 24 Dec 2023 21:13:08 +0800 Subject: [PATCH 41/54] fixup! feat: dynamic icons --- crates/eww/src/widgets/systray.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 7a2c7dc00..65018d45d 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -188,7 +188,7 @@ impl Item { } Some(_) = icon_updates.next() => { // set icon - icon.set_from_pixbuf(item.icon(*icon_size.borrow_and_update()).await.as_ref()); + load_icon_for_item(&icon, &item, *icon_size.borrow_and_update(), scale).await; } } } From 5b507c813c79be42b174f477b7acd2c95d58f09f Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Mon, 25 Dec 2023 11:46:20 +1100 Subject: [PATCH 42/54] Fix unused borrow warning --- crates/eww/src/widgets/def_widget_macro.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eww/src/widgets/def_widget_macro.rs b/crates/eww/src/widgets/def_widget_macro.rs index da640ca8c..41cd25a29 100644 --- a/crates/eww/src/widgets/def_widget_macro.rs +++ b/crates/eww/src/widgets/def_widget_macro.rs @@ -54,7 +54,7 @@ macro_rules! def_widget { // attributes expression. // allow $gtk_widget to never be used, by creating a reference that gets immediately discarded - {&$gtk_widget}; + {let _ = &$gtk_widget;}; // We first initialize all the local variables for all the expected attributes in scope $( From e7b6681166eaf540d45bff8d7317b8c67f015b6b Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 25 Feb 2024 15:36:43 +1100 Subject: [PATCH 43/54] Add some documentation to notifier_host --- crates/notifier_host/src/dbus/mod.rs | 13 ++- crates/notifier_host/src/host.rs | 5 +- crates/notifier_host/src/lib.rs | 32 +++++++ crates/notifier_host/src/watcher.rs | 138 +++++++++++++++------------ 4 files changed, 120 insertions(+), 68 deletions(-) diff --git a/crates/notifier_host/src/dbus/mod.rs b/crates/notifier_host/src/dbus/mod.rs index 2852ce8fd..8c8e9dfbd 100644 --- a/crates/notifier_host/src/dbus/mod.rs +++ b/crates/notifier_host/src/dbus/mod.rs @@ -1,8 +1,13 @@ -//! # DBus interface proxies +//! Proxies for DBus services, so we can call them. //! -//! The interface XML files are taken from -//! [Waybar](https://github.com/Alexays/Waybar/tree/master/protocol), and the proxies generated -//! with [zbus-gen](https://docs.rs/crate/zbus_xmlgen/latest). +//! The interface XML files were taken from +//! [Waybar](https://github.com/Alexays/Waybar/tree/master/protocol), and the proxies were +//! generated with [zbus-xmlgen](https://docs.rs/crate/zbus_xmlgen/latest) by running `zbus-xmlgen +//! dbus_status_notifier_item.xml` and `zbus-xmlgen dbus_status_notifier_watcher.xml`. At the +//! moment, `dbus_menu.xml` isn't used. +//! +//! For more information, see ["Writing a client proxy" in the zbus +//! tutorial](https://dbus2.github.io/zbus/). mod dbus_status_notifier_item; pub use dbus_status_notifier_item::*; diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index ff60a0d33..ca7bf163a 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -95,7 +95,6 @@ pub async fn run_host_forever(host: &mut dyn Host, snw: &dbus::StatusNotifierWat } } - // TODO handle running out of events? why could this happen? - - Ok(()) + // I do not know whether this is possible to reach or not. + unreachable!("StatusNotifierWatcher stopped producing events") } diff --git a/crates/notifier_host/src/lib.rs b/crates/notifier_host/src/lib.rs index a5fe2dd65..37253a63c 100644 --- a/crates/notifier_host/src/lib.rs +++ b/crates/notifier_host/src/lib.rs @@ -1,3 +1,35 @@ +//! The system tray side of the [notifier host DBus +//! protocols](https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierHost/), +//! implementing most of the relevant DBus protocol logic so system tray implementations (e.g. eww) +//! don't need to care about them. +//! +//! This crate does not implement the tray icon side of the protocol. For that, see, for example, +//! the [ksni](https://crates.io/crates/ksni) crate. +//! +//! # Overview / Notes for Contributors +//! +//! This crate makes extensive use of the `zbus` library to interact with DBus. You should read +//! through the [zbus tutorial](https://dbus2.github.io/zbus/) if you aren't familiar with DBus or +//! `zbus`. +//! +//! There are two separate services that are required for the tray side of the protocol: +//! +//! - `StatusNotifierWatcher`, a service which tracks what items and trays there are but doesn't do +//! any rendering. This is implemented by [`Watcher`] (see that for further details), and +//! should always be started alongside the `StatusNotifierHost`. +//! +//! - `StatusNotifierHost`, the actual tray, which registers itself to the StatusNotifierHost and +//! subscribes to its signals to know what items exist. This DBus service has a completely +//! empty interface, but is mainly by StatusNotifierWatcher to know when trays disappear. This +//! is represented by the [`Host`] trait. +//! +//! The actual tray implements the [`Host`] trait to be notified of when items (called +//! `StatusNotifierItem` in the spec and represented by [`Item`]) appear and disappear, then calls +//! [`run_host_forever`] to run the DBus side of the protocol. +//! +//! If there are multiple trays running on the system, there can be multiple `StatusNotifierHost`s, +//! but only one `StatusNotifierWatcher` (usually from whatever tray was started first). + pub mod dbus; mod host; diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index 8e206ea8b..dc75c8fbf 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -1,65 +1,14 @@ +use crate::*; + use zbus::{dbus_interface, export::ordered_stream::OrderedStreamExt, Interface}; pub const WATCHER_BUS_NAME: &str = "org.kde.StatusNotifierWatcher"; pub const WATCHER_OBJECT_NAME: &str = "/StatusNotifierWatcher"; -async fn parse_service<'a>( - service: &'a str, - hdr: zbus::MessageHeader<'_>, - con: &zbus::Connection, -) -> zbus::fdo::Result<(zbus::names::UniqueName<'static>, &'a str)> { - if service.starts_with('/') { - // they sent us just the object path :( - if let Some(sender) = hdr.sender()? { - Ok((sender.to_owned(), service)) - } else { - log::warn!("unknown sender"); - Err(zbus::fdo::Error::InvalidArgs("Unknown bus address".into())) - } - } else { - let busname: zbus::names::BusName = match service.try_into() { - Ok(x) => x, - Err(e) => { - log::warn!("received invalid bus name {:?}: {}", service, e); - return Err(zbus::fdo::Error::InvalidArgs(e.to_string())); - } - }; - - if let zbus::names::BusName::Unique(unique) = busname { - Ok((unique.to_owned(), "/StatusNotifierItem")) - } else { - let dbus = zbus::fdo::DBusProxy::new(con).await?; - match dbus.get_name_owner(busname).await { - Ok(owner) => Ok((owner.into_inner(), "/StatusNotifierItem")), - Err(e) => { - log::warn!("failed to get owner of {:?}: {}", service, e); - Err(e) - } - } - } - } -} - -/// Wait for a DBus service to exit -async fn wait_for_service_exit(connection: zbus::Connection, service: zbus::names::BusName<'_>) -> zbus::fdo::Result<()> { - let dbus = zbus::fdo::DBusProxy::new(&connection).await?; - let mut owner_changes = dbus.receive_name_owner_changed_with_args(&[(0, &service)]).await?; - - if !dbus.name_has_owner(service.as_ref()).await? { - return Ok(()); - } - - while let Some(sig) = owner_changes.next().await { - let args = sig.args()?; - if args.new_owner().is_none() { - break; - } - } - - Ok(()) -} - -/// An instance of [`org.kde.StatusNotifierWatcher`]. +/// An instance of [`org.kde.StatusNotifierWatcher`]. It only tracks what tray items and trays +/// exist, and doesn't have any logic for displaying items (for that, see [`Host`]). +/// +/// While this is usually run alongside the tray, it can also be used standalone. /// /// [`org.kde.StatusNotifierWatcher`]: https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/ #[derive(Debug, Default)] @@ -74,6 +23,10 @@ pub struct Watcher { items: std::sync::Arc>>, } +/// Implementation of the `StatusNotifierWatcher` service. +/// +/// Methods and properties correspond to methods and properties on the DBus service that can be +/// used by others, while signals are events that we generate that other services listen to. #[dbus_interface(name = "org.kde.StatusNotifierWatcher")] impl Watcher { /// RegisterStatusNotifierHost method @@ -132,7 +85,7 @@ impl Watcher { Ok(()) } - /// StatusNotifierHostRegistered signal + /// StatusNotifierHostRegistered signal. #[dbus_interface(signal)] async fn status_notifier_host_registered(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()>; @@ -235,9 +188,10 @@ impl Watcher { /// Attach and run the Watcher on a connection. pub async fn attach_to(self, con: &zbus::Connection) -> zbus::Result<()> { if !con.object_server().at(WATCHER_OBJECT_NAME, self).await? { - // There's already something at this object - // TODO is there a more specific error - return Err(zbus::Error::Failure(format!("Connection already has an object at {}", WATCHER_OBJECT_NAME))); + return Err(zbus::Error::Failure(format!( + "Object already exists at {} on this connection -- is StatusNotifierWatcher already running?", + WATCHER_OBJECT_NAME + ))); } // not AllowReplacement, not ReplaceExisting, not DoNotQueue @@ -272,3 +226,65 @@ impl Watcher { .await } } + +/// Decode the service name that others give to us, into the [bus +/// name](https://dbus2.github.io/zbus/concepts.html#bus-name--service-name) and the [object +/// path](https://dbus2.github.io/zbus/concepts.html#objects-and-object-paths) within the +/// connection. +async fn parse_service<'a>( + service: &'a str, + hdr: zbus::MessageHeader<'_>, + con: &zbus::Connection, +) -> zbus::fdo::Result<(zbus::names::UniqueName<'static>, &'a str)> { + if service.starts_with('/') { + // they sent us just the object path :( + if let Some(sender) = hdr.sender()? { + Ok((sender.to_owned(), service)) + } else { + log::warn!("unknown sender"); + Err(zbus::fdo::Error::InvalidArgs("Unknown bus address".into())) + } + } else { + let busname: zbus::names::BusName = match service.try_into() { + Ok(x) => x, + Err(e) => { + log::warn!("received invalid bus name {:?}: {}", service, e); + return Err(zbus::fdo::Error::InvalidArgs(e.to_string())); + } + }; + + if let zbus::names::BusName::Unique(unique) = busname { + Ok((unique.to_owned(), "/StatusNotifierItem")) + } else { + let dbus = zbus::fdo::DBusProxy::new(con).await?; + match dbus.get_name_owner(busname).await { + Ok(owner) => Ok((owner.into_inner(), "/StatusNotifierItem")), + Err(e) => { + log::warn!("failed to get owner of {:?}: {}", service, e); + Err(e) + } + } + } + } +} + +/// Wait for a DBus service to exit +async fn wait_for_service_exit(connection: zbus::Connection, service: zbus::names::BusName<'_>) -> zbus::fdo::Result<()> { + // TODO do we also want to catch when the object disappears? + + let dbus = zbus::fdo::DBusProxy::new(&connection).await?; + let mut owner_changes = dbus.receive_name_owner_changed_with_args(&[(0, &service)]).await?; + + if !dbus.name_has_owner(service.as_ref()).await? { + return Ok(()); + } + + while let Some(sig) = owner_changes.next().await { + let args = sig.args()?; + if args.new_owner().is_none() { + break; + } + } + + Ok(()) +} From 17c9705f2f9ac1edd147b014e4434efb87f9f99d Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 25 Feb 2024 15:39:55 +1100 Subject: [PATCH 44/54] Rename notifier_host::dbus to more descriptive notifier_host::proxy --- crates/notifier_host/src/host.rs | 10 +++++----- crates/notifier_host/src/icon.rs | 2 +- crates/notifier_host/src/item.rs | 4 ++-- crates/notifier_host/src/lib.rs | 2 +- crates/notifier_host/src/{dbus => proxy}/dbus_menu.xml | 0 .../src/{dbus => proxy}/dbus_status_notifier_item.rs | 0 .../src/{dbus => proxy}/dbus_status_notifier_item.xml | 0 .../{dbus => proxy}/dbus_status_notifier_watcher.rs | 0 .../{dbus => proxy}/dbus_status_notifier_watcher.xml | 0 crates/notifier_host/src/{dbus => proxy}/mod.rs | 0 10 files changed, 9 insertions(+), 9 deletions(-) rename crates/notifier_host/src/{dbus => proxy}/dbus_menu.xml (100%) rename crates/notifier_host/src/{dbus => proxy}/dbus_status_notifier_item.rs (100%) rename crates/notifier_host/src/{dbus => proxy}/dbus_status_notifier_item.xml (100%) rename crates/notifier_host/src/{dbus => proxy}/dbus_status_notifier_watcher.rs (100%) rename crates/notifier_host/src/{dbus => proxy}/dbus_status_notifier_watcher.xml (100%) rename crates/notifier_host/src/{dbus => proxy}/mod.rs (100%) diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index ca7bf163a..f78c4ec98 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -32,17 +32,17 @@ pub async fn attach_new_wellknown_name(con: &zbus::Connection) -> zbus::Result, -) -> zbus::Result> { +) -> zbus::Result> { // register ourself to StatusNotifierWatcher - let snw = dbus::StatusNotifierWatcherProxy::new(con).await?; + let snw = proxy::StatusNotifierWatcherProxy::new(con).await?; snw.register_status_notifier_host(name).await?; Ok(snw) } -pub async fn run_host_forever(host: &mut dyn Host, snw: &dbus::StatusNotifierWatcherProxy<'static>) -> zbus::Result<()> { +pub async fn run_host_forever(host: &mut dyn Host, snw: &proxy::StatusNotifierWatcherProxy<'static>) -> zbus::Result<()> { enum ItemEvent { - NewItem(dbus::StatusNotifierItemRegistered), - GoneItem(dbus::StatusNotifierItemUnregistered), + NewItem(proxy::StatusNotifierItemRegistered), + GoneItem(proxy::StatusNotifierItemUnregistered), } // start listening to these streams diff --git a/crates/notifier_host/src/icon.rs b/crates/notifier_host/src/icon.rs index 45f7fc665..1c29af8d7 100644 --- a/crates/notifier_host/src/icon.rs +++ b/crates/notifier_host/src/icon.rs @@ -120,7 +120,7 @@ fn icon_from_name( } pub async fn load_icon_from_sni( - sni: &dbus::StatusNotifierItemProxy<'_>, + sni: &proxy::StatusNotifierItemProxy<'_>, size: i32, scale: i32, ) -> Option { diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index 7fc9eb2bf..45acfba4c 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -60,13 +60,13 @@ fn split_service_name(service: &str) -> zbus::Result<(String, String)> { } pub struct Item { - pub sni: dbus::StatusNotifierItemProxy<'static>, + pub sni: proxy::StatusNotifierItemProxy<'static>, } impl Item { pub async fn from_address(con: &zbus::Connection, addr: &str) -> zbus::Result { let (addr, path) = split_service_name(addr)?; - let sni = dbus::StatusNotifierItemProxy::builder(con).destination(addr)?.path(path)?.build().await?; + let sni = proxy::StatusNotifierItemProxy::builder(con).destination(addr)?.path(path)?.build().await?; Ok(Item { sni }) } diff --git a/crates/notifier_host/src/lib.rs b/crates/notifier_host/src/lib.rs index 37253a63c..c636cf0a3 100644 --- a/crates/notifier_host/src/lib.rs +++ b/crates/notifier_host/src/lib.rs @@ -30,7 +30,7 @@ //! If there are multiple trays running on the system, there can be multiple `StatusNotifierHost`s, //! but only one `StatusNotifierWatcher` (usually from whatever tray was started first). -pub mod dbus; +pub mod proxy; mod host; pub use host::*; diff --git a/crates/notifier_host/src/dbus/dbus_menu.xml b/crates/notifier_host/src/proxy/dbus_menu.xml similarity index 100% rename from crates/notifier_host/src/dbus/dbus_menu.xml rename to crates/notifier_host/src/proxy/dbus_menu.xml diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_item.rs b/crates/notifier_host/src/proxy/dbus_status_notifier_item.rs similarity index 100% rename from crates/notifier_host/src/dbus/dbus_status_notifier_item.rs rename to crates/notifier_host/src/proxy/dbus_status_notifier_item.rs diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_item.xml b/crates/notifier_host/src/proxy/dbus_status_notifier_item.xml similarity index 100% rename from crates/notifier_host/src/dbus/dbus_status_notifier_item.xml rename to crates/notifier_host/src/proxy/dbus_status_notifier_item.xml diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.rs b/crates/notifier_host/src/proxy/dbus_status_notifier_watcher.rs similarity index 100% rename from crates/notifier_host/src/dbus/dbus_status_notifier_watcher.rs rename to crates/notifier_host/src/proxy/dbus_status_notifier_watcher.rs diff --git a/crates/notifier_host/src/dbus/dbus_status_notifier_watcher.xml b/crates/notifier_host/src/proxy/dbus_status_notifier_watcher.xml similarity index 100% rename from crates/notifier_host/src/dbus/dbus_status_notifier_watcher.xml rename to crates/notifier_host/src/proxy/dbus_status_notifier_watcher.xml diff --git a/crates/notifier_host/src/dbus/mod.rs b/crates/notifier_host/src/proxy/mod.rs similarity index 100% rename from crates/notifier_host/src/dbus/mod.rs rename to crates/notifier_host/src/proxy/mod.rs From e01e8936e1b2c29d22734114531ecfd655b7a604 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:36:57 +1100 Subject: [PATCH 45/54] fixup! Rename notifier_host::dbus to more descriptive notifier_host::proxy --- crates/eww/src/widgets/systray.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 65018d45d..be008a2f8 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -5,7 +5,7 @@ use notifier_host::{self, export::ordered_stream::OrderedStreamExt}; struct DBusSession { // con: zbus::Connection, // name: zbus::names::WellKnownName<'static>, - snw: notifier_host::dbus::StatusNotifierWatcherProxy<'static>, + snw: notifier_host::proxy::StatusNotifierWatcherProxy<'static>, } async fn dbus_session() -> zbus::Result<&'static DBusSession> { From ceafc5314cba744424ad26d04123acc0be4de9a4 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:37:16 +1100 Subject: [PATCH 46/54] fixup! Merge remote-tracking branch 'upstream/master' into tray-3 --- Cargo.lock | 576 ++++++++++++++++++++++++++++++++++++----------------- Cargo.toml | 2 +- 2 files changed, 396 insertions(+), 182 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99c3a7023..537cc70d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,22 +19,26 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.3.8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "const-random", + "getrandom", + "once_cell", + "version_check", ] [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" dependencies = [ + "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -46,6 +50,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -63,16 +73,15 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.2" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] @@ -102,19 +111,19 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.2" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.74" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6f84b74db2535ebae81eede2f39b947dcbf01d093ae5f791e5dd414a1bf289" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "ascii-canvas" @@ -201,7 +210,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] [[package]] @@ -218,15 +227,9 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] -[[package]] -name = "async_once" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce4f10ea3abcd6617873bae9f91d1c5332b4a778bd9ce34d0cd517474c1de82" - [[package]] name = "atk" version = "0.17.1" @@ -278,6 +281,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "bincode" version = "1.3.3" @@ -358,30 +367,25 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cached" -version = "0.42.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5877db5d1af7fae60d06b5db9430b68056a69b3582a0be8e3691e87654aeb6" +checksum = "355face540df58778b96814c48abb3c2ed67c4878a8087ab1819c1fedeec505f" dependencies = [ - "async-trait", - "async_once", + "ahash 0.8.9", "cached_proc_macro", "cached_proc_macro_types", - "futures", - "hashbrown 0.13.2", + "hashbrown 0.14.3", "instant", - "lazy_static", "once_cell", "thiserror", - "tokio", ] [[package]] name = "cached_proc_macro" -version = "0.16.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10ca87c81aaa3a949dbbe2b5e6c2c45dbc94ba4897e45ea31ff9ec5087be3dc" +checksum = "9d52f526f7cbc875b296856ca8c964a9f6290556922c303a8a3883e3c676e6a1" dependencies = [ - "cached_proc_macro_types", "darling", "proc-macro2", "quote", @@ -390,9 +394,9 @@ dependencies = [ [[package]] name = "cached_proc_macro_types" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] name = "cairo-rs" @@ -454,7 +458,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", - "time", + "time 0.1.45", "wasm-bindgen", "winapi", ] @@ -483,53 +487,61 @@ dependencies = [ [[package]] name = "chumsky" -version = "0.8.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ - "ahash 0.3.8", + "hashbrown 0.14.3", ] [[package]] name = "clap" -version = "4.3.21" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.21" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.0", +] + +[[package]] +name = "clap_complete" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +dependencies = [ + "clap", ] [[package]] name = "clap_derive" -version = "4.3.12" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "codemap" @@ -574,28 +586,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "const-random" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" -dependencies = [ - "const-random-macro", - "proc-macro-hack", -] - -[[package]] -name = "const-random-macro" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" -dependencies = [ - "getrandom", - "once_cell", - "proc-macro-hack", - "tiny-keccak", -] - [[package]] name = "convert_case" version = "0.4.0" @@ -696,7 +686,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn 1.0.109", ] @@ -764,6 +754,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "derivative" version = "2.2.0" @@ -870,7 +869,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] [[package]] @@ -921,7 +920,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "eww" -version = "0.4.0" +version = "0.5.0" dependencies = [ "anyhow", "bincode", @@ -929,6 +928,7 @@ dependencies = [ "cairo-sys-rs", "chrono", "clap", + "clap_complete", "codespan-reporting", "derive_more", "eww_shared_util", @@ -942,11 +942,11 @@ dependencies = [ "grass", "gtk", "gtk-layer-shell", - "itertools 0.11.0", + "itertools 0.12.1", "libc", "log", "maplit", - "nix 0.26.2", + "nix 0.27.1", "notifier_host", "notify", "once_cell", @@ -963,7 +963,6 @@ dependencies = [ "wait-timeout", "x11rb", "yuck", - "zbus", ] [[package]] @@ -983,7 +982,7 @@ checksum = "311a6d2f1f9d60bff73d2c78a0af97ed27f79672f15c238192a5bbb64db56d00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] [[package]] @@ -1115,7 +1114,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] [[package]] @@ -1486,15 +1485,13 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash 0.8.9", + "allocator-api2", +] [[package]] name = "heck" @@ -1514,6 +1511,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hifijson" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ef6b41c333e6dd2a4aaa59125a19b633cd17e7aaf372b2260809777bcdef4a" + [[package]] name = "humantime" version = "2.1.0" @@ -1531,7 +1534,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows 0.48.0", ] [[package]] @@ -1566,7 +1569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.3", ] [[package]] @@ -1644,9 +1647,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -1659,38 +1662,64 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "jaq-core" -version = "0.9.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1452b4acc3a7f49bd8dd516e90ed0c4f688bada805857275f85957aca2c0e7eb" +checksum = "03d6a5713b8f33675abfac79d1db0022a3f28764b2a6b96a185c199ad8dab86d" dependencies = [ - "ahash 0.3.8", - "dyn-clone", - "indexmap 1.9.3", - "itertools 0.10.5", - "jaq-parse", + "aho-corasick", + "base64", + "hifijson", + "jaq-interpret", + "libm", "log", + "regex", + "time 0.3.34", + "urlencoding", +] + +[[package]] +name = "jaq-interpret" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f569e38e5fc677db8dfda89ee0b4c25b3f53e811b16434fd14bdc5b43fc362ac" +dependencies = [ + "ahash 0.8.9", + "dyn-clone", + "hifijson", + "indexmap 2.0.0", + "jaq-syn", "once_cell", "serde_json", ] [[package]] name = "jaq-parse" -version = "0.9.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2130a59d64a5476f6feeb6b7e48cbe52ef05d8bc1b9174f50baa93e49052fd" +checksum = "ef6f8beb9f9922546419e774e24199e8a968f54c63a5a2323c8f3ef3321ace14" dependencies = [ "chumsky", - "serde", + "jaq-syn", ] [[package]] name = "jaq-std" -version = "0.9.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ab73d2079279e784a52dbbf5f3a5e0d792c89b41fd2c857de87cf698a4e24a" +checksum = "5d7871c59297cbfdd18f6f1bbbafaad24e97fd555ee1e2a1be7a40a5a20f551a" dependencies = [ "bincode", "jaq-parse", + "jaq-syn", +] + +[[package]] +name = "jaq-syn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4d60101fb791b20c982731d848ed6e7d25363656497647c2093b68bd88398d6" +dependencies = [ + "serde", ] [[package]] @@ -1724,9 +1753,9 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.19.12" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" +checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" dependencies = [ "ascii-canvas", "bit-set", @@ -1736,8 +1765,9 @@ dependencies = [ "itertools 0.10.5", "lalrpop-util", "petgraph", + "pico-args", "regex", - "regex-syntax 0.6.29", + "regex-syntax 0.7.4", "string_cache", "term", "tiny-keccak", @@ -1746,9 +1776,9 @@ dependencies = [ [[package]] name = "lalrpop-util" -version = "0.19.12" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" +checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" dependencies = [ "regex", ] @@ -1770,9 +1800,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libm" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "linked-hash-map" @@ -1816,9 +1852,9 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memoffset" @@ -1858,9 +1894,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", @@ -1897,10 +1933,20 @@ dependencies = [ "cfg-if", "libc", "memoffset 0.7.1", - "pin-utils", "static_assertions", ] +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "libc", +] + [[package]] name = "notifier_host" version = "0.1.0" @@ -1915,20 +1961,21 @@ dependencies = [ [[package]] name = "notify" -version = "6.0.1" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "crossbeam-channel", "filetime", "fsevent-sys", "inotify", "kqueue", "libc", + "log", "mio", "walkdir", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1940,6 +1987,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.16" @@ -1970,9 +2023,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "ordered-stream" @@ -2140,6 +2193,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project-lite" version = "0.2.12" @@ -2174,6 +2233,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2248,18 +2313,18 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.32" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2296,9 +2361,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" dependencies = [ "either", "rayon-core", @@ -2306,14 +2371,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] @@ -2347,58 +2410,58 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ef7e18e8841942ddb1cf845054f8008410030a3997875d9e49b7a363063df1" +checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfaf0c85b766276c797f3791f5bc6d5bd116b41d53049af2789666b0c0bc9fa" +checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] [[package]] name = "regex" -version = "1.9.3" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.7.4", + "regex-syntax 0.8.2", ] [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.8.2", ] [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc-demangle" @@ -2477,22 +2540,22 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.183" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.183" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] [[package]] @@ -2514,7 +2577,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] [[package]] @@ -2581,9 +2644,12 @@ dependencies = [ "chrono-tz", "eww_shared_util", "insta", - "itertools 0.10.5", + "itertools 0.12.1", "jaq-core", + "jaq-interpret", + "jaq-parse", "jaq-std", + "jaq-syn", "lalrpop", "lalrpop-util", "once_cell", @@ -2591,7 +2657,7 @@ dependencies = [ "serde", "serde_json", "static_assertions", - "strsim", + "strsim 0.11.0", "strum", "thiserror", ] @@ -2619,13 +2685,13 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "smart-default" -version = "0.6.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.50", ] [[package]] @@ -2640,12 +2706,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2673,26 +2739,32 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "strum" -version = "0.24.1" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 1.0.109", + "syn 2.0.50", ] [[package]] @@ -2708,9 +2780,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" dependencies = [ "proc-macro2", "quote", @@ -2719,9 +2791,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.29.8" +version = "0.30.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d10ed79c22663a35a255d289a7fdcb43559fc77ff15df5ce6c341809e7867528" +checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2" dependencies = [ "cfg-if", "core-foundation-sys", @@ -2729,7 +2801,7 @@ dependencies = [ "ntapi", "once_cell", "rayon", - "winapi", + "windows 0.52.0", ] [[package]] @@ -2801,7 +2873,7 @@ checksum = "f1728216d3244de4f14f14f8c15c79be1a7c67867d28d69b719690e2a19fb445" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] [[package]] @@ -2815,6 +2887,37 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -2826,9 +2929,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.31.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -2838,7 +2941,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.3", + "socket2 0.5.6", "tokio-macros", "tracing", "windows-sys 0.48.0", @@ -2846,13 +2949,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] [[package]] @@ -2922,7 +3025,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", ] [[package]] @@ -2974,6 +3077,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8parse" version = "0.2.1" @@ -3050,7 +3159,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", "wasm-bindgen-shared", ] @@ -3072,7 +3181,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.50", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3132,6 +3241,25 @@ dependencies = [ "windows-targets 0.48.2", ] +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.3", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.3", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3150,6 +3278,15 @@ dependencies = [ "windows-targets 0.48.2", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.3", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -3180,6 +3317,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.2", ] +[[package]] +name = "windows-targets" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +dependencies = [ + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -3192,6 +3344,12 @@ version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b10d0c968ba7f6166195e13d593af609ec2e3d24f916f081690695cf5eaffb2f" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3204,6 +3362,12 @@ version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "571d8d4e62f26d4932099a9efe89660e8bd5087775a2ab5cdd8b747b811f1058" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3216,6 +3380,12 @@ version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2229ad223e178db5fbbc8bd8d3835e51e566b8474bfca58d2e6150c48bb723cd" +[[package]] +name = "windows_i686_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3228,6 +3398,12 @@ version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "600956e2d840c194eedfc5d18f8242bc2e17c7775b6684488af3a9fff6fe3287" +[[package]] +name = "windows_i686_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3240,6 +3416,12 @@ version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea99ff3f8b49fb7a8e0d305e5aec485bd068c2ba691b6e277d29eaeac945868a" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -3252,6 +3434,12 @@ version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1a05a1ece9a7a0d5a7ccf30ba2c33e3a61a30e042ffd247567d1de1d94120d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3264,6 +3452,12 @@ version = "0.48.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" + [[package]] name = "winnow" version = "0.5.11" @@ -3339,7 +3533,7 @@ dependencies = [ "derive_more", "eww_shared_util", "insta", - "itertools 0.10.5", + "itertools 0.12.1", "lalrpop", "lalrpop-util", "maplit", @@ -3415,6 +3609,26 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] + [[package]] name = "zvariant" version = "3.15.0" diff --git a/Cargo.toml b/Cargo.toml index c9f542935..c886a8724 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ resolver = "2" simplexpr = { version = "0.1.0", path = "crates/simplexpr" } eww_shared_util = { version = "0.1.0", path = "crates/eww_shared_util" } yuck = { version = "0.1.0", path = "crates/yuck", default-features = false} -notifier_host = { version = "0.1.0", path = "crates/yuck" } +notifier_host = { version = "0.1.0", path = "crates/notifier_host" } anyhow = "1.0.79" bincode = "1.3.3" From 23c99a3d5dfdc5671eb89a8859adfe1d33a2475b Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:39:36 +1100 Subject: [PATCH 47/54] fixup! Merge remote-tracking branch 'upstream/master' into tray-3 --- Cargo.lock | 1 + crates/eww/Cargo.toml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 537cc70d8..a892132ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -963,6 +963,7 @@ dependencies = [ "wait-timeout", "x11rb", "yuck", + "zbus", ] [[package]] diff --git a/crates/eww/Cargo.toml b/crates/eww/Cargo.toml index cebbb0835..78abc8e94 100644 --- a/crates/eww/Cargo.toml +++ b/crates/eww/Cargo.toml @@ -35,6 +35,8 @@ gtk-layer-shell = { version = "0.6.1", optional = true } gdkx11 = { version = "0.17", optional = true } x11rb = { version = "0.11.1", features = ["randr"], optional = true } +zbus = { version = "3.7.0", default-features = false, features = ["tokio"] } + anyhow.workspace = true bincode.workspace = true chrono.workspace = true From 19a92417b4b5c57e01afaa35d66c137990f80561 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sun, 25 Feb 2024 16:45:19 +1100 Subject: [PATCH 48/54] Remove commented out fields of DBusSession --- crates/eww/src/widgets/systray.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index be008a2f8..80384cdc3 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -3,8 +3,6 @@ use notifier_host::{self, export::ordered_stream::OrderedStreamExt}; // DBus state shared between systray instances, to avoid creating too many connections etc. struct DBusSession { - // con: zbus::Connection, - // name: zbus::names::WellKnownName<'static>, snw: notifier_host::proxy::StatusNotifierWatcherProxy<'static>, } @@ -22,8 +20,6 @@ async fn dbus_session() -> zbus::Result<&'static DBusSession> { let snw = notifier_host::register_to_watcher(&con, &name).await?; Ok(DBusSession { - // con, - // name, snw, }) }) From 38ec238924888deccd37aeb9bf769f3ba73c7eab Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:39:58 +1100 Subject: [PATCH 49/54] Refactor host --- crates/eww/src/widgets/systray.rs | 8 ++-- crates/notifier_host/src/host.rs | 70 +++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 80384cdc3..1a40ba909 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -16,8 +16,7 @@ async fn dbus_session() -> zbus::Result<&'static DBusSession> { let con = zbus::Connection::session().await?; notifier_host::Watcher::new().attach_to(&con).await?; - let name = notifier_host::attach_new_wellknown_name(&con).await?; - let snw = notifier_host::register_to_watcher(&con, &name).await?; + let (_, snw) = notifier_host::register_as_host(&con).await?; Ok(DBusSession { snw, @@ -68,9 +67,8 @@ pub fn spawn_systray(menubar: >k::MenuBar, props: &Props) { }; systray.menubar.show(); - if let Err(e) = notifier_host::run_host_forever(&mut systray, &s.snw).await { - log::error!("notifier host error: {}", e); - } + let e = notifier_host::run_host(&mut systray, &s.snw).await; + log::error!("notifier host error: {}", e); }); // stop the task when the widget is dropped diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index f78c4ec98..d1bb38246 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -2,13 +2,34 @@ use crate::*; use zbus::export::ordered_stream::{self, OrderedStreamExt}; +/// Trait for system tray implementations, to be notified of changes to what items are in the tray. pub trait Host { + /// Called when an item is added to the tray. This is also called for all existing items when + /// starting [`run_host`]. fn add_item(&mut self, id: &str, item: Item); + + /// Called when an item is removed from the tray. fn remove_item(&mut self, id: &str); } -/// Add a new well-known name of format `org.freedesktop.StatusNotifierHost-{pid}-{nr}` for this connection. -pub async fn attach_new_wellknown_name(con: &zbus::Connection) -> zbus::Result> { +// TODO We aren't really thinking about what happens when we shut down a host. Currently, we don't +// provide a way to unregister as a host. +// +// It would also be good to combine `register_as_host` and `run_host`, so that we're only +// registered while we're running. + +/// Register this DBus connection as a StatusNotifierHost (i.e. system tray). +/// +/// This associates with the DBus connection new name of the format +/// `org.freedesktop.StatusNotifierHost-{pid}-{nr}`, and registers it to active +/// StatusNotifierWatcher. The name and the StatusNotifierWatcher proxy are returned. +/// +/// You still need to call [`run_host`] to have the instance of [`Host`] be notified of new and +/// removed items. +pub async fn register_as_host(con: &zbus::Connection) -> zbus::Result<(zbus::names::WellKnownName<'static>, proxy::StatusNotifierWatcherProxy<'static>)> { + let snw = proxy::StatusNotifierWatcherProxy::new(con).await?; + + // get a well-known name let pid = std::process::id(); let mut i = 0; let wellknown = loop { @@ -26,33 +47,46 @@ pub async fn attach_new_wellknown_name(con: &zbus::Connection) -> zbus::Result unreachable!("request_name_with_flags returned InQueue even though we specified DoNotQueue"), }; }; - Ok(wellknown) -} -pub async fn register_to_watcher( - con: &zbus::Connection, - name: &zbus::names::WellKnownName<'_>, -) -> zbus::Result> { - // register ourself to StatusNotifierWatcher - let snw = proxy::StatusNotifierWatcherProxy::new(con).await?; - snw.register_status_notifier_host(name).await?; - Ok(snw) + // register it to the StatusNotifierWatcher, so that they know there is a systray on the system + snw.register_status_notifier_host(&wellknown).await?; + + Ok((wellknown, snw)) } -pub async fn run_host_forever(host: &mut dyn Host, snw: &proxy::StatusNotifierWatcherProxy<'static>) -> zbus::Result<()> { +/// Run the Host forever, calling its methods as signals are received from the StatusNotifierWatcher. +/// +/// Before calling this, you should have called [`register_as_host`] (which returns an instance of +/// [`proxy::StatusNotifierWatcherProxy`]). +/// +/// This async function runs forever, and only returns if it gets an error! As such, it is +/// recommended to call this via something like `tokio::spawn` that runs this in the +/// background. +pub async fn run_host(host: &mut dyn Host, snw: &proxy::StatusNotifierWatcherProxy<'static>) -> zbus::Error +{ + // Replacement for ? operator since we're not returning a Result. + macro_rules! try_ { + ($e:expr) => { + match $e { + Ok(x) => x, + Err(e) => return e, + } + } + } + enum ItemEvent { NewItem(proxy::StatusNotifierItemRegistered), GoneItem(proxy::StatusNotifierItemUnregistered), } // start listening to these streams - let new_items = snw.receive_status_notifier_item_registered().await?; - let gone_items = snw.receive_status_notifier_item_unregistered().await?; + let new_items = try_!(snw.receive_status_notifier_item_registered().await); + let gone_items = try_!(snw.receive_status_notifier_item_unregistered().await); let mut item_names = std::collections::HashSet::new(); // initial items first - for svc in snw.registered_status_notifier_items().await? { + for svc in try_!(snw.registered_status_notifier_items().await) { match Item::from_address(snw.connection(), &svc).await { Ok(item) => { item_names.insert(svc.to_owned()); @@ -71,7 +105,7 @@ pub async fn run_host_forever(host: &mut dyn Host, snw: &proxy::StatusNotifierWa while let Some(ev) = ev_stream.next().await { match ev { ItemEvent::NewItem(sig) => { - let svc = sig.args()?.service; + let svc = try_!(sig.args()).service; if item_names.contains(svc) { log::info!("Got duplicate new item: {:?}", svc); } else { @@ -87,7 +121,7 @@ pub async fn run_host_forever(host: &mut dyn Host, snw: &proxy::StatusNotifierWa } } ItemEvent::GoneItem(sig) => { - let svc = sig.args()?.service; + let svc = try_!(sig.args()).service; if item_names.remove(svc) { host.remove_item(svc); } From 9c50e236c2b9aaef22c86fab19f5da29940974d0 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sat, 9 Mar 2024 15:40:42 +1100 Subject: [PATCH 50/54] Remove git conflict marker --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 518600916..443cc7f8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,6 @@ All notable changes to eww will be listed here, starting at changes since versio ### Fixes - Fix wrong values in `EWW_NET` - Fix logfiles growing indefinitely ->>>>>>> upstream/master ## [0.4.0] (04.09.2022) From 570c0d4a18f831c2fcb4c7dedd1081217c39f8d9 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sat, 9 Mar 2024 17:37:06 +1100 Subject: [PATCH 51/54] Various improvements --- Cargo.lock | 1 + crates/eww/Cargo.toml | 1 + crates/eww/src/widgets/systray.rs | 4 +- crates/notifier_host/src/host.rs | 9 +++-- crates/notifier_host/src/icon.rs | 2 +- crates/notifier_host/src/item.rs | 60 +++++++++++++++-------------- crates/notifier_host/src/lib.rs | 9 +++-- crates/notifier_host/src/watcher.rs | 55 +++++++++++++++----------- 8 files changed, 80 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a892132ec..0ab6a1877 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,6 +950,7 @@ dependencies = [ "notifier_host", "notify", "once_cell", + "ordered-stream", "pretty_env_logger", "regex", "serde", diff --git a/crates/eww/Cargo.toml b/crates/eww/Cargo.toml index 78abc8e94..2aa461a46 100644 --- a/crates/eww/Cargo.toml +++ b/crates/eww/Cargo.toml @@ -36,6 +36,7 @@ gdkx11 = { version = "0.17", optional = true } x11rb = { version = "0.11.1", features = ["randr"], optional = true } zbus = { version = "3.7.0", default-features = false, features = ["tokio"] } +ordered-stream = "0.2.0" anyhow.workspace = true bincode.workspace = true diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 1a40ba909..2360abedd 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -1,5 +1,6 @@ use gtk::{cairo::Surface, gdk::ffi::gdk_cairo_surface_create_from_pixbuf, prelude::*}; -use notifier_host::{self, export::ordered_stream::OrderedStreamExt}; +use notifier_host; +use futures::StreamExt; // DBus state shared between systray instances, to avoid creating too many connections etc. struct DBusSession { @@ -12,7 +13,6 @@ async fn dbus_session() -> zbus::Result<&'static DBusSession> { static DBUS_STATE: tokio::sync::OnceCell = tokio::sync::OnceCell::const_new(); DBUS_STATE .get_or_try_init(|| async { - // TODO error handling? let con = zbus::Connection::session().await?; notifier_host::Watcher::new().attach_to(&con).await?; diff --git a/crates/notifier_host/src/host.rs b/crates/notifier_host/src/host.rs index d1bb38246..b937395cb 100644 --- a/crates/notifier_host/src/host.rs +++ b/crates/notifier_host/src/host.rs @@ -26,7 +26,9 @@ pub trait Host { /// /// You still need to call [`run_host`] to have the instance of [`Host`] be notified of new and /// removed items. -pub async fn register_as_host(con: &zbus::Connection) -> zbus::Result<(zbus::names::WellKnownName<'static>, proxy::StatusNotifierWatcherProxy<'static>)> { +pub async fn register_as_host( + con: &zbus::Connection, +) -> zbus::Result<(zbus::names::WellKnownName<'static>, proxy::StatusNotifierWatcherProxy<'static>)> { let snw = proxy::StatusNotifierWatcherProxy::new(con).await?; // get a well-known name @@ -62,8 +64,7 @@ pub async fn register_as_host(con: &zbus::Connection) -> zbus::Result<(zbus::nam /// This async function runs forever, and only returns if it gets an error! As such, it is /// recommended to call this via something like `tokio::spawn` that runs this in the /// background. -pub async fn run_host(host: &mut dyn Host, snw: &proxy::StatusNotifierWatcherProxy<'static>) -> zbus::Error -{ +pub async fn run_host(host: &mut dyn Host, snw: &proxy::StatusNotifierWatcherProxy<'static>) -> zbus::Error { // Replacement for ? operator since we're not returning a Result. macro_rules! try_ { ($e:expr) => { @@ -71,7 +72,7 @@ pub async fn run_host(host: &mut dyn Host, snw: &proxy::StatusNotifierWatcherPro Ok(x) => x, Err(e) => return e, } - } + }; } enum ItemEvent { diff --git a/crates/notifier_host/src/icon.rs b/crates/notifier_host/src/icon.rs index 1c29af8d7..bcc994f2c 100644 --- a/crates/notifier_host/src/icon.rs +++ b/crates/notifier_host/src/icon.rs @@ -3,7 +3,7 @@ use crate::*; use gtk::{self, prelude::*}; #[derive(thiserror::Error, Debug)] -pub enum IconError { +enum IconError { #[error("while fetching icon name: {0}")] DBusIconName(#[source] zbus::Error), #[error("while fetching icon theme path: {0}")] diff --git a/crates/notifier_host/src/item.rs b/crates/notifier_host/src/item.rs index 45acfba4c..c258957e7 100644 --- a/crates/notifier_host/src/item.rs +++ b/crates/notifier_host/src/item.rs @@ -2,11 +2,9 @@ use crate::*; use gtk::{self, prelude::*}; -/// Recognised values of org.freedesktop.StatusNotifierItem.Status +/// Recognised values of [`org.freedesktop.StatusNotifierItem.Status`]. /// -/// See -/// -/// for details. +/// [`org.freedesktop.StatusNotifierItem.Status`]: https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierItem/#org.freedesktop.statusnotifieritem.status #[derive(Debug, Clone, Copy)] pub enum Status { /// The item doesn't convey important information to the user, it can be considered an "idle" @@ -37,35 +35,37 @@ impl std::str::FromStr for Status { } } -/// Split a sevice name e.g. `:1.50:/org/ayatana/NotificationItem/nm_applet` into the address and -/// path. +/// A StatusNotifierItem (SNI). /// -/// Original logic from -fn split_service_name(service: &str) -> zbus::Result<(String, String)> { - if let Some((addr, path)) = service.split_once('/') { - Ok((addr.to_owned(), format!("/{}", path))) - } else if service.contains(':') { - // TODO why? - let addr = service.split(':').nth(1); - // Some StatusNotifierItems will not return an object path, in that case we fallback - // to the default path. - if let Some(addr) = addr { - Ok((addr.to_owned(), "/StatusNotifierItem".to_owned())) - } else { - Err(zbus::Error::Address(service.to_owned())) - } - } else { - Err(zbus::Error::Address(service.to_owned())) - } -} - +/// At the moment, this does not wrap much of the SNI's properties and methods. As such, you should +/// directly access the `sni` member as needed for functionalty that is not provided. pub struct Item { + /// The StatusNotifierItem that is wrapped by this instance. pub sni: proxy::StatusNotifierItemProxy<'static>, } impl Item { - pub async fn from_address(con: &zbus::Connection, addr: &str) -> zbus::Result { - let (addr, path) = split_service_name(addr)?; + /// Create an instance from the service's address. + /// + /// The format of `addr` is `{bus}{object_path}` (e.g. + /// `:1.50/org/ayatana/NotificationItem/nm_applet`), which is the format that is used for + /// StatusNotifierWatcher's [RegisteredStatusNotifierItems property][rsni]). + /// + /// [rsni]: https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/#registeredstatusnotifieritems + pub async fn from_address(con: &zbus::Connection, service: &str) -> zbus::Result { + let (addr, path) = { + // Based on + // + // TODO is the service name format actually documented anywhere? + if let Some((addr, path)) = service.split_once('/') { + (addr.to_owned(), format!("/{}", path)) + } else if service.starts_with(':') { + (service[0..6].to_owned(), names::ITEM_OBJECT.to_owned()) + } else { + return Err(zbus::Error::Address(service.to_owned())); + } + }; + let sni = proxy::StatusNotifierItemProxy::builder(con).destination(addr)?.path(path)?.build().await?; Ok(Item { sni }) @@ -80,13 +80,17 @@ impl Item { } } + /// Get the menu of this item. pub async fn menu(&self) -> zbus::Result { - // TODO better handling if menu() method doesn't exist + // TODO document what this returns if there is no menu. let menu = dbusmenu_gtk3::Menu::new(self.sni.destination(), &self.sni.menu().await?); Ok(menu.upcast()) } + /// Get the current icon. pub async fn icon(&self, size: i32, scale: i32) -> Option { + // TODO explain what size and scale mean here + // see icon.rs load_icon_from_sni(&self.sni, size, scale).await } diff --git a/crates/notifier_host/src/lib.rs b/crates/notifier_host/src/lib.rs index c636cf0a3..8a8c46dd3 100644 --- a/crates/notifier_host/src/lib.rs +++ b/crates/notifier_host/src/lib.rs @@ -25,7 +25,7 @@ //! //! The actual tray implements the [`Host`] trait to be notified of when items (called //! `StatusNotifierItem` in the spec and represented by [`Item`]) appear and disappear, then calls -//! [`run_host_forever`] to run the DBus side of the protocol. +//! [`run_host`] to run the DBus side of the protocol. //! //! If there are multiple trays running on the system, there can be multiple `StatusNotifierHost`s, //! but only one `StatusNotifierWatcher` (usually from whatever tray was started first). @@ -44,6 +44,9 @@ pub use item::*; mod watcher; pub use watcher::*; -pub mod export { - pub use zbus::export::ordered_stream; +pub(crate) mod names { + pub const WATCHER_BUS: &str = "org.kde.StatusNotifierWatcher"; + pub const WATCHER_OBJECT: &str = "/StatusNotifierWatcher"; + + pub const ITEM_OBJECT: &str = "/StatusNotifierItem"; } diff --git a/crates/notifier_host/src/watcher.rs b/crates/notifier_host/src/watcher.rs index dc75c8fbf..a0e7c139a 100644 --- a/crates/notifier_host/src/watcher.rs +++ b/crates/notifier_host/src/watcher.rs @@ -1,12 +1,8 @@ -use crate::*; - +use crate::names; use zbus::{dbus_interface, export::ordered_stream::OrderedStreamExt, Interface}; -pub const WATCHER_BUS_NAME: &str = "org.kde.StatusNotifierWatcher"; -pub const WATCHER_OBJECT_NAME: &str = "/StatusNotifierWatcher"; - /// An instance of [`org.kde.StatusNotifierWatcher`]. It only tracks what tray items and trays -/// exist, and doesn't have any logic for displaying items (for that, see [`Host`]). +/// exist, and doesn't have any logic for displaying items (for that, see [`Host`][`crate::Host`]). /// /// While this is usually run alongside the tray, it can also be used standalone. /// @@ -15,8 +11,8 @@ pub const WATCHER_OBJECT_NAME: &str = "/StatusNotifierWatcher"; pub struct Watcher { tasks: tokio::task::JoinSet<()>, - // NOTE Intentionally using std::sync::Mutex instead of tokio's async mutex, since we don't - // need to hold the mutex across an await. + // Intentionally using std::sync::Mutex instead of tokio's async mutex, since we don't need to + // hold the mutex across an await. // // See hosts: std::sync::Arc>>, @@ -37,6 +33,13 @@ impl Watcher { #[zbus(connection)] con: &zbus::Connection, #[zbus(signal_context)] ctxt: zbus::SignalContext<'_>, ) -> zbus::fdo::Result<()> { + // TODO right now, we convert everything to the unique bus name (something like :1.234). + // However, it might make more sense to listen to the actual name they give us, so that if + // the connection dissociates itself from the org.kde.StatusNotifierHost-{pid}-{nr} name + // but still remains around, we drop them as a host. + // + // (This also applies to RegisterStatusNotifierItem) + let (service, _) = parse_service(service, hdr, con).await?; log::info!("new host: {}", service); @@ -60,7 +63,7 @@ impl Watcher { let ctxt = ctxt.to_owned(); let con = con.to_owned(); async move { - if let Err(e) = wait_for_service_exit(con.clone(), service.as_ref().into()).await { + if let Err(e) = wait_for_service_exit(&con, service.as_ref().into()).await { log::error!("failed to wait for service exit: {}", e); } log::info!("lost host: {}", service); @@ -133,7 +136,7 @@ impl Watcher { let ctxt = ctxt.to_owned(); let con = con.to_owned(); async move { - if let Err(e) = wait_for_service_exit(con.clone(), service.as_ref()).await { + if let Err(e) = wait_for_service_exit(&con, service.as_ref()).await { log::error!("failed to wait for service exit: {}", e); } println!("gone item: {}", &item); @@ -185,18 +188,18 @@ impl Watcher { Default::default() } - /// Attach and run the Watcher on a connection. + /// Attach and run the Watcher (in the background) on a connection. pub async fn attach_to(self, con: &zbus::Connection) -> zbus::Result<()> { - if !con.object_server().at(WATCHER_OBJECT_NAME, self).await? { + if !con.object_server().at(names::WATCHER_OBJECT, self).await? { return Err(zbus::Error::Failure(format!( "Object already exists at {} on this connection -- is StatusNotifierWatcher already running?", - WATCHER_OBJECT_NAME + names::WATCHER_OBJECT ))); } // not AllowReplacement, not ReplaceExisting, not DoNotQueue let flags: [zbus::fdo::RequestNameFlags; 0] = []; - match con.request_name_with_flags(WATCHER_BUS_NAME, flags.into_iter().collect()).await { + match con.request_name_with_flags(names::WATCHER_BUS, flags.into_iter().collect()).await { Ok(zbus::fdo::RequestNameReply::PrimaryOwner) => Ok(()), Ok(_) | Err(zbus::Error::NameTaken) => Ok(()), // defer to existing Err(e) => Err(e), @@ -215,7 +218,7 @@ impl Watcher { .await } - /// Equivalen to `registered_status_notifier_items_invalidate`, but without requiring `self`. + /// Equivalent to `registered_status_notifier_items_invalidate`, but without requiring `self`. async fn registered_status_notifier_items_refresh(ctxt: &zbus::SignalContext<'_>) -> zbus::Result<()> { zbus::fdo::Properties::properties_changed( ctxt, @@ -231,13 +234,16 @@ impl Watcher { /// name](https://dbus2.github.io/zbus/concepts.html#bus-name--service-name) and the [object /// path](https://dbus2.github.io/zbus/concepts.html#objects-and-object-paths) within the /// connection. +/// +/// The freedesktop.org specification has the format of this be just the bus name, however some +/// status items pass non-conforming values. One common one is just the object path. async fn parse_service<'a>( service: &'a str, hdr: zbus::MessageHeader<'_>, con: &zbus::Connection, ) -> zbus::fdo::Result<(zbus::names::UniqueName<'static>, &'a str)> { if service.starts_with('/') { - // they sent us just the object path :( + // they sent us just the object path if let Some(sender) = hdr.sender()? { Ok((sender.to_owned(), service)) } else { @@ -245,6 +251,7 @@ async fn parse_service<'a>( Err(zbus::fdo::Error::InvalidArgs("Unknown bus address".into())) } } else { + // parse the bus name they gave us let busname: zbus::names::BusName = match service.try_into() { Ok(x) => x, Err(e) => { @@ -254,11 +261,14 @@ async fn parse_service<'a>( }; if let zbus::names::BusName::Unique(unique) = busname { - Ok((unique.to_owned(), "/StatusNotifierItem")) + Ok((unique.to_owned(), names::ITEM_OBJECT)) } else { + // they gave us a "well-known name" like org.kde.StatusNotifierHost-81830-0, we need to + // convert this into the actual identifier for their bus (e.g. :1.234), so that even if + // they remove that well-known name it's fine. let dbus = zbus::fdo::DBusProxy::new(con).await?; match dbus.get_name_owner(busname).await { - Ok(owner) => Ok((owner.into_inner(), "/StatusNotifierItem")), + Ok(owner) => Ok((owner.into_inner(), names::ITEM_OBJECT)), Err(e) => { log::warn!("failed to get owner of {:?}: {}", service, e); Err(e) @@ -268,14 +278,13 @@ async fn parse_service<'a>( } } -/// Wait for a DBus service to exit -async fn wait_for_service_exit(connection: zbus::Connection, service: zbus::names::BusName<'_>) -> zbus::fdo::Result<()> { - // TODO do we also want to catch when the object disappears? - - let dbus = zbus::fdo::DBusProxy::new(&connection).await?; +/// Wait for a DBus service to disappear +async fn wait_for_service_exit(con: &zbus::Connection, service: zbus::names::BusName<'_>) -> zbus::fdo::Result<()> { + let dbus = zbus::fdo::DBusProxy::new(con).await?; let mut owner_changes = dbus.receive_name_owner_changed_with_args(&[(0, &service)]).await?; if !dbus.name_has_owner(service.as_ref()).await? { + // service has already disappeared return Ok(()); } From ac419bc2b35e0175ad9129123c0f787ded7e818b Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sat, 9 Mar 2024 18:08:41 +1100 Subject: [PATCH 52/54] Icon documentation --- crates/notifier_host/src/icon.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/notifier_host/src/icon.rs b/crates/notifier_host/src/icon.rs index bcc994f2c..45057e41a 100644 --- a/crates/notifier_host/src/icon.rs +++ b/crates/notifier_host/src/icon.rs @@ -27,7 +27,7 @@ enum IconError { NotAvailable, } -/// Get the fallback GTK icon +/// Get the fallback GTK icon, as a final fallback if the tray item has no icon. async fn fallback_icon(size: i32, scale: i32) -> Option { let theme = gtk::IconTheme::default().expect("Could not get default gtk theme"); match theme.load_icon_for_scale("image-missing", size, scale, gtk::IconLookupFlags::FORCE_SIZE) { @@ -95,6 +95,8 @@ fn icon_from_pixmaps(pixmaps: Vec<(i32, i32, Vec)>, size: i32) -> Option, @@ -128,6 +130,9 @@ pub async fn load_icon_from_sni( // available." let scaled_size = size * scale; + + // First, see if we can get an icon from the name they provide, using either the theme they + // specify or the default. let icon_from_name: std::result::Result = (async { // fetch icon name let icon_name = sni.icon_name().await; @@ -170,22 +175,21 @@ pub async fn load_icon_from_sni( icon_from_name(&icon_name, icon_theme_path, size, scale) }) .await; + match icon_from_name { - Ok(p) => return Some(p), - Err(IconError::NotAvailable) => {} // try pixbuf - // log and continue + Ok(p) => return Some(p), // got an icon! + Err(IconError::NotAvailable) => {} // this error is expected, don't log Err(e) => log::warn!("failed to get icon by name for {}: {}", sni.destination(), e), }; - let icon_pixmap = sni.icon_pixmap().await; - log::debug!("dbus: {} icon_pixmap -> {:?}", sni.destination(), icon_pixmap); - let icon_from_pixmaps = match icon_pixmap { + // Can't get it from name + theme, try the pixmap + let icon_from_pixmaps = match sni.icon_pixmap().await { Ok(ps) => match icon_from_pixmaps(ps, scaled_size) { Some(p) => Ok(p), None => Err(IconError::NotAvailable), }, Err(zbus::Error::FDO(e)) => match *e { - // property not existing is fine + // property not existing is an expected error zbus::fdo::Error::UnknownProperty(_) | zbus::fdo::Error::InvalidArgs(_) => Err(IconError::NotAvailable), _ => Err(IconError::DBusPixmap(zbus::Error::FDO(e))), @@ -198,5 +202,6 @@ pub async fn load_icon_from_sni( Err(e) => log::warn!("failed to get icon pixmap for {}: {}", sni.destination(), e), }; + // Tray didn't provide a valid icon so use the default fallback one. fallback_icon(size, scale).await } From 6f5cdd37885593839e6ffd9268ccd1e23e45115d Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sat, 9 Mar 2024 18:31:10 +1100 Subject: [PATCH 53/54] cargo fmt --- crates/eww/src/widgets/systray.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/eww/src/widgets/systray.rs b/crates/eww/src/widgets/systray.rs index 2360abedd..bd3f28e41 100644 --- a/crates/eww/src/widgets/systray.rs +++ b/crates/eww/src/widgets/systray.rs @@ -1,6 +1,6 @@ +use futures::StreamExt; use gtk::{cairo::Surface, gdk::ffi::gdk_cairo_surface_create_from_pixbuf, prelude::*}; use notifier_host; -use futures::StreamExt; // DBus state shared between systray instances, to avoid creating too many connections etc. struct DBusSession { @@ -18,9 +18,7 @@ async fn dbus_session() -> zbus::Result<&'static DBusSession> { let (_, snw) = notifier_host::register_as_host(&con).await?; - Ok(DBusSession { - snw, - }) + Ok(DBusSession { snw }) }) .await } From 708a11da2c8f998c03512fa0dda6d9f2005345a2 Mon Sep 17 00:00:00 2001 From: ralismark <13449732+ralismark@users.noreply.github.com> Date: Sat, 23 Mar 2024 14:06:46 +1100 Subject: [PATCH 54/54] Add dependency to CI --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 011ac1475..98a89a259 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install dependencies - run: sudo apt-get update && sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev + run: sudo apt-get update && sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev libdbusmenu-gtk3-dev - uses: actions/checkout@v4