156 lines
5.1 KiB
Rust
156 lines
5.1 KiB
Rust
|
|
// 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();
|
||
|
|
}
|
||
|
|
}
|