Merge branch 'pre-release' into feature/external/macos-exclusive-mode
This commit is contained in:
20 files changed
+853
-333
No files matched your search
Generated
+43
-20
@@ -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",
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
Reference in new issue
Block a user