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.
This commit is contained in:
parent
fe0e4bf409
commit
aa438821b9
9 changed files with 310 additions and 4 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -24,3 +24,6 @@ debian/*
|
||||||
!debian/links
|
!debian/links
|
||||||
!debian/rules
|
!debian/rules
|
||||||
!debian/source
|
!debian/source
|
||||||
|
|
||||||
|
cosmic-applet-status-area/data/com.system76.CosmicStatusNotifierWatcher.service
|
||||||
|
cosmic-applet-status-area/data/dbus-1/com.system76.CosmicStatusNotifierWatcher.service
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -1,9 +1,23 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
use std::{env, process};
|
||||||
|
|
||||||
mod components;
|
mod components;
|
||||||
mod subscriptions;
|
mod subscriptions;
|
||||||
|
|
||||||
|
pub mod status_notifier_watcher;
|
||||||
|
mod unique_names;
|
||||||
|
|
||||||
pub fn run() -> cosmic::iced::Result {
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
155
cosmic-applet-status-area/src/status_notifier_watcher.rs
Normal file
155
cosmic-applet-status-area/src/status_notifier_watcher.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
|
// 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<zbus::names::UniqueName<'static>>,
|
||||||
|
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<CosmicAppletStatusNotifierWatcher>,
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ use futures::{StreamExt, stream};
|
||||||
use crate::subscriptions::status_notifier_item::StatusNotifierItem;
|
use crate::subscriptions::status_notifier_item::StatusNotifierItem;
|
||||||
|
|
||||||
mod client;
|
mod client;
|
||||||
mod server;
|
pub(crate) mod server;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
|
@ -51,7 +51,9 @@ async fn connect() -> zbus::Result<(zbus::Connection, client::EventStream)> {
|
||||||
let connection = zbus::Connection::session().await?;
|
let connection = zbus::Connection::session().await?;
|
||||||
|
|
||||||
// Start `StatusNotifierWatcher` service, if there isn't one running already
|
// 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
|
// Connect client and listen for registered/unregistered
|
||||||
let stream = client::watch(&connection).await?;
|
let stream = client::watch(&connection).await?;
|
||||||
|
|
|
||||||
107
cosmic-applet-status-area/src/unique_names.rs
Normal file
107
cosmic-applet-status-area/src/unique_names.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright 2026 System76 <info@system76.com>
|
||||||
|
// 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<UniqueName<'static>>,
|
||||||
|
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<Mutex<Inner>>) -> impl Future<Output = ()> {
|
||||||
|
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<Mutex<Inner>>);
|
||||||
|
|
||||||
|
impl UniqueNames {
|
||||||
|
pub async fn new(connection: &zbus::Connection) -> zbus::Result<Self> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
9
justfile
9
justfile
|
|
@ -13,6 +13,7 @@ sharedir := rootdir + prefix + '/share'
|
||||||
iconsdir := sharedir + '/icons/hicolor'
|
iconsdir := sharedir + '/icons/hicolor'
|
||||||
prefixdir := prefix + '/bin'
|
prefixdir := prefix + '/bin'
|
||||||
bindir := rootdir + prefixdir
|
bindir := rootdir + prefixdir
|
||||||
|
libdir := rootdir + prefix + '/lib'
|
||||||
default-schema-target := sharedir / 'cosmic'
|
default-schema-target := sharedir / 'cosmic'
|
||||||
|
|
||||||
cosmic-applets-bin := prefixdir / 'cosmic-applets'
|
cosmic-applets-bin := prefixdir / 'cosmic-applets'
|
||||||
|
|
@ -57,8 +58,14 @@ _install_button id name: (_install_icons name) (_install_desktop name + '/data/'
|
||||||
_install_metainfo:
|
_install_metainfo:
|
||||||
install -Dm0644 {{metainfo-src}} {{metainfo-dst}}
|
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
|
# 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 Cargo dependencies locally
|
||||||
vendor:
|
vendor:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue