fix(linux): withdraw now-playing portal notifications

cx
This commit is contained in:
vicrodh committed 2026-08-27 19:47:08 -06:00
1 parent 5bae442864
commit 08ccdc54c3
5 files changed
+76 -5

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ mod types;
pub use types::{xesam_url_for, MediaEvent, MediaIntegration, PlaybackStatus, TrackMeta};
pub mod notify;
pub use notify::{show_track_notification, NotificationMeta};
pub use notify::{show_track_notification, withdraw_track_notification, NotificationMeta};
#[cfg(target_os = "linux")]
mod inhibit;
+60 -4
View File
@@ -20,6 +20,19 @@
use std::path::PathBuf;
#[cfg(target_os = "linux")]
const PORTAL_NOTIFICATION_ID: &str = "track-now-playing";
/// Serializes portal mutations and invalidates slow, stale artwork jobs. A
/// track-A notification can spend seconds downloading its cover while track B
/// starts (or playback stops); without this generation check A may overwrite B
/// or resurrect the notification after it was withdrawn.
#[cfg(target_os = "linux")]
static PORTAL_NOTIFICATION_GENERATION: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
#[cfg(target_os = "linux")]
static PORTAL_NOTIFICATION_GATE: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
/// Everything needed to render a track-change notification. The crate formats
/// the body + quality line itself so the output matches the Tauri notification
/// exactly, regardless of frontend.
@@ -249,11 +262,19 @@ pub async fn show_track_notification(meta: NotificationMeta, offline: bool) {
#[cfg(target_os = "linux")]
{
use ashpd::desktop::notification::{Notification as PortalNotification, NotificationProxy};
use ashpd::desktop::notification::{
DisplayHint, Notification as PortalNotification, NotificationProxy,
};
use ashpd::desktop::Icon;
use std::sync::atomic::Ordering;
let mut notification =
PortalNotification::new(&meta.title).body(Some(body.as_str()));
let generation = PORTAL_NOTIFICATION_GENERATION
.fetch_add(1, Ordering::AcqRel)
.wrapping_add(1);
let mut notification = PortalNotification::new(&meta.title)
.body(Some(body.as_str()))
.display_hint([DisplayHint::Transient]);
if let Some(url) = meta.art_url.clone() {
let prepared = tokio::task::spawn_blocking(move || -> Result<Vec<u8>, String> {
@@ -273,8 +294,13 @@ pub async fn show_track_notification(meta: NotificationMeta, offline: bool) {
match NotificationProxy::new().await {
Ok(proxy) => {
let _guard = PORTAL_NOTIFICATION_GATE.lock().await;
if PORTAL_NOTIFICATION_GENERATION.load(Ordering::Acquire) != generation {
log::debug!("[notify] stale track notification discarded");
return;
}
if let Err(e) = proxy
.add_notification("track-now-playing", notification)
.add_notification(PORTAL_NOTIFICATION_ID, notification)
.await
{
log::warn!("[notify] XDG portal add_notification failed: {e}");
@@ -314,3 +340,33 @@ pub async fn show_track_notification(meta: NotificationMeta, offline: bool) {
log::info!("[notify] desktop notifications not implemented on this platform");
}
}
/// Withdraw the active Linux portal notification, if any. This also
/// invalidates an in-flight artwork preparation so it cannot publish after a
/// stop or process shutdown. Other platforms currently have no replaceable
/// notification handle, so this is a deliberate no-op there.
pub async fn withdraw_track_notification() {
#[cfg(target_os = "linux")]
{
use ashpd::desktop::notification::NotificationProxy;
use std::sync::atomic::Ordering;
let generation = PORTAL_NOTIFICATION_GENERATION
.fetch_add(1, Ordering::AcqRel)
.wrapping_add(1);
let proxy = match NotificationProxy::new().await {
Ok(proxy) => proxy,
Err(e) => {
log::debug!("[notify] XDG notification portal unavailable during withdrawal: {e}");
return;
}
};
let _guard = PORTAL_NOTIFICATION_GATE.lock().await;
if PORTAL_NOTIFICATION_GENERATION.load(Ordering::Acquire) != generation {
return;
}
if let Err(e) = proxy.remove_notification(PORTAL_NOTIFICATION_ID).await {
log::debug!("[notify] XDG portal remove_notification failed: {e}");
}
}
}