feat(notifications): migrate from notify-rust to ashpd XDG Portal

Replace direct D-Bus notifications (notify-rust) with XDG Desktop
Portal notifications (ashpd) for Flathub compliance. Portal
notifications use the app's .desktop file identity and don't require
--talk-name=org.freedesktop.Notifications permission.

- Replace notify-rust dep with ashpd (linux-only, tokio+notification)
- Use NotificationProxy for portal-based notifications
- Use stable ID "track-now-playing" to replace instead of stacking
- Convert artwork path to file:// URI via ashpd::Uri
- Graceful fallback when portal is unavailable
This commit is contained in:
vicrodh committed 2026-03-03 22:25:59 -06:00
1 parent b15a375e23
commit 69aed4e1ad
2 files changed
+24 -20

No files matched your search

+2 -3
View File
@@ -90,8 +90,7 @@ ebur128 = "0.1"
# Media controls (MPRIS on Linux)
souvlaki = "0.7"
# Desktop notifications
notify-rust = "4"
# Desktop notifications → ashpd in [target.linux] deps below
# Image processing (tray icon + thumbnails)
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp"] }
@@ -141,8 +140,8 @@ futures-util = "0.3"
# Platform-specific dependencies
[target.'cfg(target_os = "linux")'.dependencies]
# Note: xdg-portal feature disabled due to ashpd/zbus version incompatibility
tauri-plugin-dialog = { version = "2.6.0" }
ashpd = { version = "0.13", default-features = false, features = ["tokio", "notification"] }
# Direct ALSA access for bit-perfect playback (bypasses CPAL limitations)
alsa = "0.9"
+22 -17
View File
@@ -61,7 +61,9 @@ use crate::runtime::{
};
use crate::AppState;
use md5::{Digest, Md5};
use notify_rust::Notification;
use ashpd::desktop::notification::{Notification as PortalNotification, NotificationProxy};
use ashpd::desktop::Icon;
use ashpd::Uri;
use std::collections::HashSet;
use std::fs;
use std::io::Write;
@@ -9397,7 +9399,7 @@ pub async fn v2_uncache_favorite_artist(
// ==================== Remaining Legacy-Equivalent V2 Commands ====================
#[tauri::command]
pub fn v2_show_track_notification(
pub async fn v2_show_track_notification(
title: String,
artist: String,
album: String,
@@ -9428,27 +9430,30 @@ pub fn v2_show_track_notification(
lines.push(quality);
}
let mut notification = Notification::new();
notification
.summary(&title)
.body(&lines.join("\n"))
.appname("QBZ")
.timeout(4000);
let body_text = lines.join("\n");
let mut notification = PortalNotification::new(&title)
.body(Some(body_text.as_str()));
if let Some(url) = artwork_url {
if let Ok(path) = v2_cache_notification_artwork(&url) {
if let Some(path_str) = path.to_str() {
notification.image_path(path_str);
if let Some(ref url_str) = artwork_url {
if let Ok(path) = v2_cache_notification_artwork(url_str) {
let file_uri_str = format!("file://{}", path.display());
if let Ok(uri) = Uri::parse(&file_uri_str) {
notification = notification.icon(Icon::Uri(uri));
}
}
}
if let Err(e) = notification.show() {
log::warn!(
"Could not show notification (notification system may be unavailable): {}",
e
);
match NotificationProxy::new().await {
Ok(proxy) => {
if let Err(e) = proxy.add_notification("track-now-playing", notification).await {
log::warn!("Could not show notification via XDG portal: {}", e);
}
}
Err(e) => {
log::warn!("XDG notification portal unavailable: {}", e);
}
}
Ok(())
}