From ebe18a17c58364f163ad0730abc0a38dc537833c Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 20 Jan 2026 14:23:01 -0800 Subject: [PATCH 1/6] status-area: Disable buttons when not `enabled` in DBusMenu --- cosmic-applet-status-area/src/components/status_menu.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cosmic-applet-status-area/src/components/status_menu.rs b/cosmic-applet-status-area/src/components/status_menu.rs index f76ea784..e5137863 100644 --- a/cosmic-applet-status-area/src/components/status_menu.rs +++ b/cosmic-applet-status-area/src/components/status_menu.rs @@ -242,7 +242,11 @@ fn layout_view(layout: &Layout, expanded: Option) -> cosmic::Element<'_, Ms .symbolic(true); children.push(icon.into()); } - let button = row_button(children).on_press(Msg::Click(i.id(), is_submenu)); + + let mut button = row_button(children); + if i.enabled() { + button = button.on_press(Msg::Click(i.id(), is_submenu)); + } if is_submenu && is_expanded { Some( From 6e99ad42b573291efd55f98ec4e6f76e48b2b3b7 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 27 Jan 2026 14:32:44 -0800 Subject: [PATCH 2/6] status-area: Don't add duplicate status item Some clients seem to have issues with this. --- .../subscriptions/status_notifier_watcher/server.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cosmic-applet-status-area/src/subscriptions/status_notifier_watcher/server.rs b/cosmic-applet-status-area/src/subscriptions/status_notifier_watcher/server.rs index 5cf2455f..816a0be7 100644 --- a/cosmic-applet-status-area/src/subscriptions/status_notifier_watcher/server.rs +++ b/cosmic-applet-status-area/src/subscriptions/status_notifier_watcher/server.rs @@ -37,11 +37,15 @@ impl StatusNotifierWatcher { } else { service.to_string() }; - Self::status_notifier_item_registered(&ctxt, &service) - .await - .unwrap(); - self.items.push((sender.to_owned(), service)); + // Ignore duplicate + if !self.items.iter().any(|(a, b)| (a, b) == (sender, &service)) { + Self::status_notifier_item_registered(&ctxt, &service) + .await + .unwrap(); + + self.items.push((sender.to_owned(), service)); + } } fn register_status_notifier_host(&self, _service: &str) { From 468017654c062886e084448ada14a27d2b7ac70f Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 28 Jan 2026 15:32:03 -0800 Subject: [PATCH 3/6] status-area: Don't try to call `activate()` in `status_menu` This was added in https://github.com/pop-os/cosmic-applets/pull/1143, but I don't think this code will ever be reached? This code is called when activating an item in a menu, so it should only apply when there is a menu. --- cosmic-applet-status-area/src/components/status_menu.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/cosmic-applet-status-area/src/components/status_menu.rs b/cosmic-applet-status-area/src/components/status_menu.rs index e5137863..8ca6bd85 100644 --- a/cosmic-applet-status-area/src/components/status_menu.rs +++ b/cosmic-applet-status-area/src/components/status_menu.rs @@ -121,14 +121,6 @@ impl State { let item_proxy = self.item.item_proxy().clone(); let Some(menu_proxy) = self.item.menu_proxy().cloned() else { - tokio::spawn(async move { - let _ = item_proxy.provide_xdg_activation_token(token).await; - if let Err(err) = item_proxy.activate(0, 0).await { - tracing::error!( - "Error activating status notifier item without menu proxy: {err:?}" - ); - } - }); return iced::Task::none(); }; tokio::spawn(async move { From c24b769acd8edaa27c93531c104fe270d55a3482 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 28 Jan 2026 18:30:17 -0800 Subject: [PATCH 4/6] status-area: Move duplicated code to an `activate` function --- .../src/components/app.rs | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/cosmic-applet-status-area/src/components/app.rs b/cosmic-applet-status-area/src/components/app.rs index e8d9f0b1..eeeba7ec 100644 --- a/cosmic-applet-status-area/src/components/app.rs +++ b/cosmic-applet-status-area/src/components/app.rs @@ -16,7 +16,10 @@ use cosmic::{ }; use std::collections::BTreeMap; -use crate::{components::status_menu, subscriptions::status_notifier_watcher}; +use crate::{ + components::status_menu, + subscriptions::{status_notifier_item::StatusNotifierItem, status_notifier_watcher}, +}; #[derive(Clone, Debug)] pub enum Msg { @@ -158,13 +161,7 @@ impl cosmic::Application for App { }); } else { if let Some(menu) = self.menus.get(&id) { - let item_proxy = menu.item.item_proxy().clone(); - return Task::future(async move { - match item_proxy.activate(0, 0).await { - Ok(_) => cosmic::action::app(Msg::None), - Err(_) => cosmic::action::app(Msg::TogglePopup(id)), - } - }); + return activate(id, &menu.item, None); } } Task::none() @@ -290,29 +287,7 @@ impl cosmic::Application for App { if let Some(id_str) = id.strip_prefix("activate:") { if let Ok(real_id) = id_str.parse::() { if let Some(menu) = self.menus.get(&real_id) { - let item_proxy = menu.item.item_proxy().clone(); - let token = token.clone(); - let id = real_id; - return Task::future(async move { - if let Some(t) = token { - match item_proxy.provide_xdg_activation_token(t).await { - Ok(_) => { - println!("Token provided successfully to {}", id) - } - Err(e) => eprintln!( - "Failed to provide token to {}: {}", - id, e - ), - } - } - match item_proxy.activate(0, 0).await { - Ok(_) => cosmic::action::app(Msg::None), - Err(err) => { - eprintln!("Activate failed: {}", err); - cosmic::action::app(Msg::TogglePopup(id)) - } - } - }); + return activate(real_id, &menu.item, token.clone()); } } return Task::none(); @@ -563,6 +538,31 @@ impl cosmic::Application for App { } } +fn activate( + id: usize, + item: &StatusNotifierItem, + activation_token: Option, +) -> Task> { + let item_proxy = item.item_proxy().clone(); + Task::future(async move { + if let Some(t) = activation_token { + match item_proxy.provide_xdg_activation_token(t).await { + Ok(_) => { + tracing::debug!("Token provided successfully to {}", id) + } + Err(e) => tracing::error!("Failed to provide token to {}: {}", id, e), + } + } + match item_proxy.activate(0, 0).await { + Ok(_) => cosmic::action::app(Msg::None), + Err(err) => { + tracing::error!("Activate failed: {}", err); + cosmic::action::app(Msg::TogglePopup(id)) + } + } + }) +} + fn menu_icon_button<'a>( applet: &'a cosmic::applet::Context, menu: &'a status_menu::State, From fe0e4bf409234fc1b632efc22403b46282f892c0 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 28 Jan 2026 18:44:05 -0800 Subject: [PATCH 5/6] status-area: Use `ItemIsMenu` correctly The comment that was previously here assumed that `ItemIsMenu` should be defined for any status item that has a menu, but the FreeDesktop spec rather defines it as an item that "only supports the context menu", so we should open the menu instead of trying to call `Activate`. This change won't affect behavior if `ItemIsMenu` is true, but `Activate` errors, except by opening the menu without having to wait for that errror first. It will fix the left click to open menu behavior if any client defines that method to not error, but does set `ItemIsMenu`. --- .../src/components/app.rs | 3 ++ .../src/subscriptions/status_notifier_item.rs | 41 +++++++++---------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/cosmic-applet-status-area/src/components/app.rs b/cosmic-applet-status-area/src/components/app.rs index eeeba7ec..03e56e9d 100644 --- a/cosmic-applet-status-area/src/components/app.rs +++ b/cosmic-applet-status-area/src/components/app.rs @@ -543,6 +543,9 @@ fn activate( item: &StatusNotifierItem, activation_token: Option, ) -> Task> { + if item.is_menu() { + return Task::done(cosmic::action::app(Msg::TogglePopup(id))); + } let item_proxy = item.item_proxy().clone(); Task::future(async move { if let Some(t) = activation_token { diff --git a/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs b/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs index ee12cbe0..5a67f07b 100644 --- a/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs +++ b/cosmic-applet-status-area/src/subscriptions/status_notifier_item.rs @@ -10,6 +10,7 @@ use zbus::zvariant::{self, OwnedValue}; #[derive(Clone, Debug)] pub struct StatusNotifierItem { name: String, + is_menu: bool, item_proxy: StatusNotifierItemProxy<'static>, menu_proxy: Option>, } @@ -44,32 +45,25 @@ impl StatusNotifierItem { .build() .await?; - // XX: some items will not implement this but have a menu anyway - let is_menu = item_proxy.item_is_menu().await; + let is_menu = item_proxy.item_is_menu().await.unwrap_or(false); - let menu_path = item_proxy.menu().await; - - // Why would an item say it has no menu but provide a menu path? Slack does this. - let is_menu = menu_path.is_ok() || is_menu.unwrap_or(false); - - if !is_menu { - return Ok(Self { - name, - item_proxy, - menu_proxy: None, - }); - } - let menu_path = menu_path?; - let menu_proxy = DBusMenuProxy::builder(connection) - .destination(dest.to_string())? - .path(menu_path)? - .build() - .await?; + let menu_proxy = if let Ok(menu_path) = item_proxy.menu().await { + Some( + DBusMenuProxy::builder(connection) + .destination(dest.to_string())? + .path(menu_path)? + .build() + .await?, + ) + } else { + None + }; Ok(Self { name, + is_menu, item_proxy, - menu_proxy: Some(menu_proxy), + menu_proxy, }) } @@ -126,6 +120,11 @@ impl StatusNotifierItem { ) } + /// Item is only a menu, with no `Activate` action + pub fn is_menu(&self) -> bool { + self.is_menu + } + pub fn menu_proxy(&self) -> Option<&DBusMenuProxy<'static>> { self.menu_proxy.as_ref() } From aa438821b96199e01647320a048d0e67837fabc3 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 9 Dec 2025 18:41:37 -0800 Subject: [PATCH 6/6] status-area: Seperate daemon for status notifier daemon This allows the applet to be restarted on panel configuration changes without replacing the daemon, or having races between different applet instances trying to run the watcher. Otherwise, this should behave similarly to the existing version. Should fix https://github.com/pop-os/cosmic-panel/issues/284. --- .gitignore | 3 + ...m76.CosmicStatusNotifierWatcher.service.in | 7 + ...m76.CosmicStatusNotifierWatcher.service.in | 4 + ...ktop.impl.portal.desktop.cosmic.service.in | 7 + cosmic-applet-status-area/src/lib.rs | 16 +- .../src/status_notifier_watcher.rs | 155 ++++++++++++++++++ .../status_notifier_watcher/mod.rs | 6 +- cosmic-applet-status-area/src/unique_names.rs | 107 ++++++++++++ justfile | 9 +- 9 files changed, 310 insertions(+), 4 deletions(-) create mode 100644 cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service.in create mode 100644 cosmic-applet-status-area/data/dbus-1/com.system76.CosmicStatusNotifierWatcher.service.in create mode 100644 cosmic-applet-status-area/data/org.freedesktop.impl.portal.desktop.cosmic.service.in create mode 100644 cosmic-applet-status-area/src/status_notifier_watcher.rs create mode 100644 cosmic-applet-status-area/src/unique_names.rs diff --git a/.gitignore b/.gitignore index 5c0031bd..e178ac1a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ debian/* !debian/links !debian/rules !debian/source + +cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service +cosmic-applet-status-area/data/dbus-1/com.system76.CosmicStatusNotifierWatcher.service diff --git a/cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service.in b/cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service.in new file mode 100644 index 00000000..4f2067ee --- /dev/null +++ b/cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service.in @@ -0,0 +1,7 @@ +[Unit] +Description=COSMIC Status Notifier Watcher backend + +[Service] +Type=dbus +BusName=com.system76.CosmicStatusNotifierWatcher +ExecStart=@bindir@/cosmic-applet-status-area --status-notifier-watcher diff --git a/cosmic-applet-status-area/data/dbus-1/com.system76.CosmicStatusNotifierWatcher.service.in b/cosmic-applet-status-area/data/dbus-1/com.system76.CosmicStatusNotifierWatcher.service.in new file mode 100644 index 00000000..0c3345d5 --- /dev/null +++ b/cosmic-applet-status-area/data/dbus-1/com.system76.CosmicStatusNotifierWatcher.service.in @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=com.system76.CosmicStatusNotifierWatcher +Exec=@bindir@/cosmic-applet-status-area --status-notifier-watcher +SystemdService=com.system76.CosmicStatusNotifierWatcher.service diff --git a/cosmic-applet-status-area/data/org.freedesktop.impl.portal.desktop.cosmic.service.in b/cosmic-applet-status-area/data/org.freedesktop.impl.portal.desktop.cosmic.service.in new file mode 100644 index 00000000..4f2067ee --- /dev/null +++ b/cosmic-applet-status-area/data/org.freedesktop.impl.portal.desktop.cosmic.service.in @@ -0,0 +1,7 @@ +[Unit] +Description=COSMIC Status Notifier Watcher backend + +[Service] +Type=dbus +BusName=com.system76.CosmicStatusNotifierWatcher +ExecStart=@bindir@/cosmic-applet-status-area --status-notifier-watcher diff --git a/cosmic-applet-status-area/src/lib.rs b/cosmic-applet-status-area/src/lib.rs index 371b8aff..17166cbc 100644 --- a/cosmic-applet-status-area/src/lib.rs +++ b/cosmic-applet-status-area/src/lib.rs @@ -1,9 +1,23 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only +use std::{env, process}; + mod components; mod subscriptions; +pub mod status_notifier_watcher; +mod unique_names; + pub fn run() -> cosmic::iced::Result { - components::app::main() + if let Some(arg) = env::args().nth(1) { + if arg == "--status-notifier-watcher" { + status_notifier_watcher::run() + } else { + tracing::error!("Invalid argument `{arg}` for status-area applet`"); + process::exit(1); + } + } else { + components::app::main() + } } diff --git a/cosmic-applet-status-area/src/status_notifier_watcher.rs b/cosmic-applet-status-area/src/status_notifier_watcher.rs new file mode 100644 index 00000000..1bb75d79 --- /dev/null +++ b/cosmic-applet-status-area/src/status_notifier_watcher.rs @@ -0,0 +1,155 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: GPL-3.0-only + +//! A seperate DBus socket-activated daemon to serve as the status notifier watcher +//! +//! By socket-activating this daemon, panel configuration changes do not end up terminating +//! the daemon and having different applet instances race to start it. +//! +//! This provides a seperate interface from the standard one, with a single register method, so it +//! can be socket-activated and not conflict with anything else running as a status notifier +//! watcher. +//! +//! The daemon runs as long as as there is at least one client still connected. Which it checks +//! for every `REFRESH_INTERVAL`. + +use crate::subscriptions::status_notifier_watcher::server::create_service; +use crate::unique_names::UniqueNames; + +use futures::StreamExt; +use std::{collections::HashSet, time::Duration}; +use zbus::fdo; +use zbus::message::Header; + +const DBUS_NAME: &str = "com.system76.CosmicStatusNotifierWatcher"; +const OBJECT_PATH: &str = "/CosmicStatusNotifierWatcher"; +const REFRESH_INTERVAL: Duration = Duration::from_secs(60); + +/// Run daemon +pub fn run() -> cosmic::iced::Result { + if let Err(err) = run_inner() { + eprintln!("Zbus error running status notifier watcher: {}", err); + std::process::exit(1); + } + Ok(()) +} + +/// Register client with daemon +pub async fn cosmic_register(conn: &zbus::Connection) -> zbus::Result<()> { + let cosmic_watcher = CosmicAppletStatusNotifierWatcherProxy::new(conn).await?; + cosmic_watcher.register_applet().await?; + let mut stream = cosmic_watcher.0.receive_owner_changed().await?; + tokio::spawn(async move { + while let Some(value) = stream.next().await { + if let Some(_unique_name) = value { + /// Register with new owner + let _ = cosmic_watcher.register_applet().await; + } + } + }); + Ok(()) +} + +#[zbus::proxy( + interface = "com.system76.CosmicStatusNotifierWatcher", + default_service = "com.system76.CosmicStatusNotifierWatcher", + default_path = "/CosmicStatusNotifierWatcher" +)] +trait CosmicAppletStatusNotifierWatcher { + async fn register_applet(&self) -> zbus::Result<()>; +} + +struct CosmicAppletStatusNotifierWatcher { + applets: HashSet>, + unique_names: UniqueNames, +} + +#[zbus::interface(name = "com.system76.CosmicStatusNotifierWatcher")] +impl CosmicAppletStatusNotifierWatcher { + fn register_applet(&mut self, #[zbus(header)] hdr: Header<'_>) { + if let Some(sender) = hdr.sender() { + if self.unique_names.has_unique_name(sender) { + self.applets.insert(sender.to_owned()); + } + } + } +} + +impl CosmicAppletStatusNotifierWatcher { + fn has_client(&self) -> bool { + !self.applets.is_empty() + } + + /// Purge registered clients that no longer exist on bus + fn refresh(&mut self) { + self.applets + .retain(|n| self.unique_names.has_unique_name(n)); + } +} + +#[tokio::main] +pub async fn run_inner() -> zbus::Result<()> { + let (running, abort_handle) = futures::future::abortable(std::future::pending::<()>()); + + let conn = zbus::Connection::session().await?; + create_service(&conn).await?; + let dbus = zbus::fdo::DBusProxy::new(&conn).await?; + conn.object_server() + .at( + OBJECT_PATH, + CosmicAppletStatusNotifierWatcher { + applets: HashSet::new(), + unique_names: UniqueNames::new(&conn).await?, + }, + ) + .await?; + let interface = conn + .object_server() + .interface::<_, CosmicAppletStatusNotifierWatcher>(OBJECT_PATH) + .await?; + tokio::spawn(refresh_task(interface.clone(), abort_handle.clone())); + let name_lost_stream = dbus.receive_name_lost().await?; + tokio::spawn(name_lost_task(name_lost_stream, abort_handle)); + conn.request_name(DBUS_NAME).await?; + + let _ = running.await; + Ok(()) +} + +// Task to terminate daemon with the owned name is lost. +// (If a different instance of this daemon is manually started.) +async fn name_lost_task( + mut name_lost_stream: fdo::NameLostStream, + abort_handle: futures::future::AbortHandle, +) { + while let Some(name_lost) = name_lost_stream.next().await { + let Ok(args) = name_lost.args() else { + return; + }; + if args.name == DBUS_NAME { + eprintln!("'{}' name on bus lost. Exiting.", DBUS_NAME); + abort_handle.abort(); + return; + } + } +} + +async fn refresh_task( + interface: zbus::object_server::InterfaceRef, + abort_handle: futures::future::AbortHandle, +) { + let mut interval = tokio::time::interval(std::time::Duration::from_secs(60)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + // Initial tick, waiting for first client to connect + interval.tick().await; + loop { + interval.tick().await; + let mut watcher = interface.get_mut().await; + if !watcher.has_client() { + // No clients since last refresh; exit + abort_handle.abort(); + return; + } + watcher.refresh(); + } +} diff --git a/cosmic-applet-status-area/src/subscriptions/status_notifier_watcher/mod.rs b/cosmic-applet-status-area/src/subscriptions/status_notifier_watcher/mod.rs index af0f74ae..9f9c7d14 100644 --- a/cosmic-applet-status-area/src/subscriptions/status_notifier_watcher/mod.rs +++ b/cosmic-applet-status-area/src/subscriptions/status_notifier_watcher/mod.rs @@ -9,7 +9,7 @@ use futures::{StreamExt, stream}; use crate::subscriptions::status_notifier_item::StatusNotifierItem; mod client; -mod server; +pub(crate) mod server; #[derive(Clone, Debug)] pub enum Event { @@ -51,7 +51,9 @@ async fn connect() -> zbus::Result<(zbus::Connection, client::EventStream)> { let connection = zbus::Connection::session().await?; // Start `StatusNotifierWatcher` service, if there isn't one running already - server::create_service(&connection).await?; + if let Err(err) = crate::status_notifier_watcher::cosmic_register(&connection).await { + eprintln!("Failed to start status notifier watcher: {}", err); + } // Connect client and listen for registered/unregistered let stream = client::watch(&connection).await?; diff --git a/cosmic-applet-status-area/src/unique_names.rs b/cosmic-applet-status-area/src/unique_names.rs new file mode 100644 index 00000000..cab6483e --- /dev/null +++ b/cosmic-applet-status-area/src/unique_names.rs @@ -0,0 +1,107 @@ +// Copyright 2026 System76 +// SPDX-License-Identifier: GPL-3.0-only + +// Based on https://github.com/pop-os/cosmic-comp/blob/master/src/dbus/name_owners.rs, +// but only tracking unique names, and using tokio executor. + +use futures::StreamExt; +use std::{ + collections::HashSet, + future::{Future, poll_fn}, + sync::{Arc, Mutex, Weak}, + task::{Context, Poll, Waker}, +}; +use zbus::{ + fdo, + names::{BusName, UniqueName}, +}; + +#[derive(Debug)] +struct Inner { + unique_names: HashSet>, + stream: fdo::NameOwnerChangedStream, + // Waker from `update_task` is stored, so that task will still be woken after + // polling elsewhere. + waker: Waker, +} + +impl Drop for Inner { + fn drop(&mut self) { + // Wake `update_task` so it can terminate + self.waker.wake_by_ref(); + } +} + +impl Inner { + /// Process all events so far on `stream`, and update `unique_names`. + fn update_if_needed(&mut self) { + let mut context = Context::from_waker(&self.waker); + while let Poll::Ready(val) = self.stream.poll_next_unpin(&mut context) { + let val = val.unwrap(); + let args = val.args().unwrap(); + match args.name { + BusName::Unique(name) => { + if args.new_owner.is_some() { + self.unique_names.insert(name.to_owned()); + } else { + self.unique_names.remove(&name.to_owned()); + } + } + BusName::WellKnown(_) => {} + } + } + } +} + +/// This task polls the steam regularly, to make sure events on the stream aren't just +/// buffered indefinitely. +fn update_task(inner: Weak>) -> impl Future { + poll_fn(move |context| { + if let Some(inner) = inner.upgrade() { + let mut inner = inner.lock().unwrap(); + inner.waker = context.waker().clone(); + inner.update_if_needed(); + // Nothing to do now until waker is invoked + Poll::Pending + } else { + // All strong references have been dropped, so task has nothing left to do. + Poll::Ready(()) + } + }) +} + +#[derive(Clone, Debug)] +pub struct UniqueNames(Arc>); + +impl UniqueNames { + pub async fn new(connection: &zbus::Connection) -> zbus::Result { + let dbus = fdo::DBusProxy::new(connection).await?; + let stream = dbus.receive_name_owner_changed().await?; + + let names = dbus.list_names().await?; + let unique_names = names + .iter() + .filter_map(|n| match n.inner() { + BusName::Unique(name) => Some(name.to_owned()), + BusName::WellKnown(_) => None, + }) + .collect(); + + let inner = Arc::new(Mutex::new(Inner { + unique_names, + stream, + waker: Waker::noop().clone(), + })); + + tokio::spawn(update_task(Arc::downgrade(&inner))); + + Ok(UniqueNames(inner)) + } + + #[allow(dead_code)] + pub fn has_unique_name(&self, name: &UniqueName<'_>) -> bool { + let mut inner = self.0.lock().unwrap(); + inner.update_if_needed(); + inner.unique_names.contains(name) + } +} diff --git a/justfile b/justfile index a1ea651c..6c5033d3 100644 --- a/justfile +++ b/justfile @@ -13,6 +13,7 @@ sharedir := rootdir + prefix + '/share' iconsdir := sharedir + '/icons/hicolor' prefixdir := prefix + '/bin' bindir := rootdir + prefixdir +libdir := rootdir + prefix + '/lib' default-schema-target := sharedir / 'cosmic' cosmic-applets-bin := prefixdir / 'cosmic-applets' @@ -57,8 +58,14 @@ _install_button id name: (_install_icons name) (_install_desktop name + '/data/' _install_metainfo: install -Dm0644 {{metainfo-src}} {{metainfo-dst}} +_install_status_notifier_watcher: + sed "s|@bindir@|{{prefixdir}}|" cosmic-applet-status-area/data/dbus-1/com.system76.CosmicStatusNotifierWatcher.service.in > cosmic-applet-status-area/data/dbus-1/com.system76.CosmicStatusNotifierWatcher.service + install -Dm0644 cosmic-applet-status-area/data/dbus-1/com.system76.CosmicStatusNotifierWatcher.service {{sharedir}}/dbus-1/services/com.system76.CosmicStatusNotifierWatcher.service + sed "s|@bindir@|{{prefixdir}}|" cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service.in > cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service + install -Dm0644 cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service {{libdir}}/systemd/user/com.system76.CosmicStatusNotifierWatcher.service + # Installs files into the system -install: (_install_bin 'cosmic-applets') (_link_applet 'cosmic-panel-button') (_install_applet 'com.system76.CosmicAppList' 'cosmic-app-list') (_install_default_schema 'cosmic-app-list') (_install_applet 'com.system76.CosmicAppletA11y' 'cosmic-applet-a11y') (_install_applet 'com.system76.CosmicAppletAudio' 'cosmic-applet-audio') (_install_applet 'com.system76.CosmicAppletInputSources' 'cosmic-applet-input-sources') (_install_applet 'com.system76.CosmicAppletBattery' 'cosmic-applet-battery') (_install_applet 'com.system76.CosmicAppletBluetooth' 'cosmic-applet-bluetooth') (_install_applet 'com.system76.CosmicAppletMinimize' 'cosmic-applet-minimize') (_install_applet 'com.system76.CosmicAppletNetwork' 'cosmic-applet-network') (_install_applet 'com.system76.CosmicAppletNotifications' 'cosmic-applet-notifications') (_install_applet 'com.system76.CosmicAppletPower' 'cosmic-applet-power') (_install_applet 'com.system76.CosmicAppletStatusArea' 'cosmic-applet-status-area') (_install_applet 'com.system76.CosmicAppletTiling' 'cosmic-applet-tiling') (_install_applet 'com.system76.CosmicAppletTime' 'cosmic-applet-time') (_install_applet 'com.system76.CosmicAppletWorkspaces' 'cosmic-applet-workspaces') (_install_button 'com.system76.CosmicPanelAppButton' 'cosmic-panel-app-button') (_install_button 'com.system76.CosmicPanelLauncherButton' 'cosmic-panel-launcher-button') (_install_button 'com.system76.CosmicPanelWorkspacesButton' 'cosmic-panel-workspaces-button') (_install_metainfo) +install: (_install_bin 'cosmic-applets') (_link_applet 'cosmic-panel-button') (_install_applet 'com.system76.CosmicAppList' 'cosmic-app-list') (_install_default_schema 'cosmic-app-list') (_install_applet 'com.system76.CosmicAppletA11y' 'cosmic-applet-a11y') (_install_applet 'com.system76.CosmicAppletAudio' 'cosmic-applet-audio') (_install_applet 'com.system76.CosmicAppletInputSources' 'cosmic-applet-input-sources') (_install_applet 'com.system76.CosmicAppletBattery' 'cosmic-applet-battery') (_install_applet 'com.system76.CosmicAppletBluetooth' 'cosmic-applet-bluetooth') (_install_applet 'com.system76.CosmicAppletMinimize' 'cosmic-applet-minimize') (_install_applet 'com.system76.CosmicAppletNetwork' 'cosmic-applet-network') (_install_applet 'com.system76.CosmicAppletNotifications' 'cosmic-applet-notifications') (_install_applet 'com.system76.CosmicAppletPower' 'cosmic-applet-power') (_install_applet 'com.system76.CosmicAppletStatusArea' 'cosmic-applet-status-area') (_install_applet 'com.system76.CosmicAppletTiling' 'cosmic-applet-tiling') (_install_applet 'com.system76.CosmicAppletTime' 'cosmic-applet-time') (_install_applet 'com.system76.CosmicAppletWorkspaces' 'cosmic-applet-workspaces') (_install_button 'com.system76.CosmicPanelAppButton' 'cosmic-panel-app-button') (_install_button 'com.system76.CosmicPanelLauncherButton' 'cosmic-panel-launcher-button') (_install_button 'com.system76.CosmicPanelWorkspacesButton' 'cosmic-panel-workspaces-button') (_install_metainfo) (_install_status_notifier_watcher) # Vendor Cargo dependencies locally vendor: