Merge branch 'pre-release' into feature/external/macos-exclusive-mode

This commit is contained in:
Vudgekek authored and GitHub committed 2026-05-07 15:34:44 -05:00
commit 5cd76f53d4
20 files changed
+853 -333

No files matched your search

+43 -20
View File
@@ -812,7 +812,7 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
dependencies = [
"byteorder",
"fnv",
"uuid",
"uuid 1.23.1",
]
[[package]]
@@ -1479,6 +1479,19 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "discord-rich-presence"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75db747ecd252c01bfecaf709b07fcb4c634adf0edb5fed47bc9c3052e7076b"
dependencies = [
"serde",
"serde_derive",
"serde_json",
"serde_repr",
"uuid 0.8.2",
]
[[package]]
name = "dispatch"
version = "0.2.0"
@@ -3709,9 +3722,9 @@ dependencies = [
[[package]]
name = "notify-rust"
version = "4.16.1"
version = "4.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bdaf6120b9df005d37e58f6b75329be6255450453fbeba9ce4192324f921fb9"
checksum = "50ff2e74231b72c832d82982193b417f230945be6bdb5575b251d941d31adb00"
dependencies = [
"futures-lite",
"log",
@@ -4965,7 +4978,7 @@ dependencies = [
"serde",
"serde_json",
"thiserror 2.0.18",
"uuid",
"uuid 1.23.1",
"walkdir",
]
@@ -4994,6 +5007,7 @@ dependencies = [
"base64 0.22.1",
"chrono",
"dirs",
"discord-rich-presence",
"dotenvy",
"ebur128",
"env_logger",
@@ -5056,7 +5070,7 @@ dependencies = [
"tokio-stream",
"tower-http 0.5.2",
"urlencoding",
"uuid",
"uuid 1.23.1",
"walkdir",
]
@@ -5113,7 +5127,7 @@ dependencies = [
"serde",
"sha2 0.10.9",
"thiserror 2.0.18",
"uuid",
"uuid 1.23.1",
]
[[package]]
@@ -5129,7 +5143,7 @@ dependencies = [
"serde_json",
"thiserror 2.0.18",
"tokio",
"uuid",
"uuid 1.23.1",
]
[[package]]
@@ -5150,7 +5164,7 @@ dependencies = [
"serde",
"serde_json",
"thiserror 2.0.18",
"uuid",
"uuid 1.23.1",
]
[[package]]
@@ -5800,9 +5814,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.37"
version = "0.23.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
dependencies = [
"aws-lc-rs",
"log",
@@ -5917,7 +5931,7 @@ dependencies = [
"serde",
"serde_json",
"url",
"uuid",
"uuid 1.23.1",
]
[[package]]
@@ -6912,7 +6926,7 @@ dependencies = [
"thiserror 2.0.18",
"time",
"url",
"uuid",
"uuid 1.23.1",
"walkdir",
]
@@ -6964,9 +6978,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-deep-link"
version = "2.4.8"
version = "2.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db49816aee496a9b200d55b55ab6ae73fd50847c79f2fabc7ee20871fa75c95"
checksum = "70ee75bc5627f77bfdf40c913255ebc258117b10ebe2b2239a1a1cf40b0b58aa"
dependencies = [
"dunce",
"plist",
@@ -7027,9 +7041,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-opener"
version = "2.5.3"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc624469b06f59f5a29f874bbc61a2ed737c0f9c23ef09855a292c389c42e83f"
checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29"
dependencies = [
"dunce",
"glob",
@@ -7209,7 +7223,7 @@ dependencies = [
"toml 0.9.12+spec-1.1.0",
"url",
"urlpattern",
"uuid",
"uuid 1.23.1",
"walkdir",
]
@@ -7393,9 +7407,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.52.1"
version = "1.52.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386"
dependencies = [
"bytes",
"libc",
@@ -7927,6 +7941,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "uuid"
version = "1.23.1"
@@ -9310,7 +9333,7 @@ dependencies = [
"tokio",
"tracing",
"uds_windows",
"uuid",
"uuid 1.23.1",
"windows-sys 0.61.2",
"winnow 0.7.14",
"zbus_macros 5.14.0",
+3
View File
@@ -97,6 +97,9 @@ rand = "0.10"
# Fuzzy string similarity (for track-mix dedup)
strsim = "0.11"
# Discord Rich Presence (#336)
discord-rich-presence = "0.2.4"
# Audio playback
rodio = { version = "0.22", features = ["symphonia-all"] }
symphonia = { version = "0.5", features = ["all"] }
+122
View File
@@ -0,0 +1,122 @@
//! Discord Rich Presence integration.
//!
//! Wraps `discord-rich-presence` behind three Tauri V2 commands. Connection
//! is lazy: the IPC client is created on the first `update` call when the
//! feature is enabled, and dropped if any operation fails. Failure is silent
//! — when Discord is not running, when the sandbox blocks the IPC socket,
//! or when the user has not opted in, the activity simply does not appear.
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use discord_rich_presence::{activity, DiscordIpc, DiscordIpcClient};
use tauri::State;
/// Public Discord Application ID for QBZ. Public identifier, not a secret.
const DISCORD_APP_ID: &str = "1501835855587708988";
const QBZ_HOMEPAGE: &str = "https://qbz.lol";
#[derive(Default)]
pub struct DiscordRpcState {
client: Mutex<Option<DiscordIpcClient>>,
enabled: AtomicBool,
}
#[tauri::command]
pub fn v2_discord_rpc_set_enabled(
state: State<'_, DiscordRpcState>,
enabled: bool,
) -> Result<(), String> {
state.enabled.store(enabled, Ordering::SeqCst);
if !enabled {
let mut guard = state.client.lock().map_err(|e| e.to_string())?;
if let Some(client) = guard.as_mut() {
let _ = client.clear_activity();
let _ = client.close();
}
*guard = None;
}
Ok(())
}
#[tauri::command]
pub fn v2_discord_rpc_clear(state: State<'_, DiscordRpcState>) -> Result<(), String> {
let mut guard = state.client.lock().map_err(|e| e.to_string())?;
if let Some(client) = guard.as_mut() {
let _ = client.clear_activity();
let _ = client.close();
}
*guard = None;
Ok(())
}
#[tauri::command]
pub fn v2_discord_rpc_update(
state: State<'_, DiscordRpcState>,
title: String,
artist: String,
album: String,
is_playing: bool,
current_time: f64,
duration: f64,
cover_url: Option<String>,
) -> Result<(), String> {
if !state.enabled.load(Ordering::SeqCst) {
return Ok(());
}
let mut guard = state.client.lock().map_err(|e| e.to_string())?;
if guard.is_none() {
if let Ok(mut client) = DiscordIpcClient::new(DISCORD_APP_ID) {
if client.connect().is_ok() {
*guard = Some(client);
}
}
}
let Some(client) = guard.as_mut() else {
return Ok(());
};
let state_str = format!("by {}", artist);
let small_text = if is_playing {
"Playing".to_string()
} else {
let mins = (current_time / 60.0).floor() as u32;
let secs = (current_time % 60.0).floor() as u32;
format!("Paused at {:02}:{:02}", mins, secs)
};
let large_image = cover_url.unwrap_or_else(|| "cover".to_string());
let mut act = activity::Activity::new()
.details(&title)
.state(&state_str)
.activity_type(activity::ActivityType::Listening)
.assets(
activity::Assets::new()
.large_image(&large_image)
.small_image("icon")
.large_text(&album)
.small_text(&small_text),
)
.buttons(vec![activity::Button::new("Get QBZ", QBZ_HOMEPAGE)]);
if is_playing {
if let Ok(now) = SystemTime::now().duration_since(UNIX_EPOCH) {
let start_time = now.as_secs() as i64 - current_time as i64;
let mut ts = activity::Timestamps::new().start(start_time);
if duration > 0.0 {
ts = ts.end(start_time + duration as i64);
}
act = act.timestamps(ts);
}
}
if client.set_activity(act).is_err() {
*guard = None;
}
Ok(())
}
+5
View File
@@ -38,6 +38,7 @@ pub mod cast;
pub mod config;
pub mod credentials;
pub mod discogs;
pub mod discord_rpc;
pub mod flatpak;
#[cfg(target_os = "linux")]
pub mod idle_inhibit;
@@ -1376,6 +1377,7 @@ pub fn run(qconnect_cli_override: Option<bool>) {
.manage(listenbrainz_v2_state)
.manage(musicbrainz_v2_state)
.manage(lastfm_v2_state)
.manage(discord_rpc::DiscordRpcState::default())
.invoke_handler(tauri::generate_handler![
commands_v2::runtime_get_status,
commands_v2::runtime_bootstrap,
@@ -1949,6 +1951,9 @@ pub fn run(qconnect_cli_override: Option<bool>) {
commands_v2::v2_collection_shuffle_tracks,
commands_v2::v2_skip_to_next_item,
commands_v2::v2_skip_to_previous_item,
discord_rpc::v2_discord_rpc_set_enabled,
discord_rpc::v2_discord_rpc_update,
discord_rpc::v2_discord_rpc_clear,
])
.build(tauri::generate_context!())
.expect("error while building tauri application")