From 6971a1aa5e0ba41d41c418bb05cbdd8be7445fd2 Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Tue, 5 Mar 2024 21:14:47 +0100 Subject: [PATCH] desktop: Persist volume to disk --- desktop/src/cli.rs | 6 +- desktop/src/gui.rs | 22 ++++++- desktop/src/player.rs | 2 - desktop/src/preferences.rs | 34 ++++++++-- desktop/src/preferences/read.rs | 108 +++++++++++++++++++++++++++++++ desktop/src/preferences/write.rs | 25 +++++++ 6 files changed, 183 insertions(+), 14 deletions(-) diff --git a/desktop/src/cli.rs b/desktop/src/cli.rs index a5865e61e..d7c2366e8 100644 --- a/desktop/src/cli.rs +++ b/desktop/src/cli.rs @@ -87,9 +87,9 @@ pub struct Opt { #[clap(long, short, default_value = "show-all")] pub scale: StageScaleMode, - /// Audio volume as a number between 0 (muted) and 1 (full volume) - #[clap(long, short, default_value = "1.0")] - pub volume: f32, + /// Audio volume as a number between 0 (muted) and 1 (full volume). Default is 1. + #[clap(long, short)] + pub volume: Option, /// Prevent movies from changing the stage scale mode. #[clap(long, action)] diff --git a/desktop/src/gui.rs b/desktop/src/gui.rs index ff4b22df9..634a73ea3 100644 --- a/desktop/src/gui.rs +++ b/desktop/src/gui.rs @@ -106,7 +106,7 @@ impl RuffleGui { Self { is_about_visible: false, is_volume_visible: false, - volume_controls: VolumeControls::new(false, default_player_options.volume * 100.0), + volume_controls: VolumeControls::new(&preferences), is_open_dialog_visible: false, was_suspended_before_debug: false, @@ -481,6 +481,19 @@ impl RuffleGui { if let Some(player) = player { player.set_volume(self.volume_controls.get_volume()); } + // Don't update persisted volume if the CLI set it + if self.preferences.cli.volume.is_none() { + if let Err(e) = self.preferences.write_preferences(|writer| { + if changed_checkbox { + writer.set_mute(self.volume_controls.is_muted); + } + if changed_slider { + writer.set_volume(self.volume_controls.volume / 100.0); + } + }) { + tracing::warn!("Couldn't update volume preferences: {e}"); + } + } } }); } @@ -568,8 +581,11 @@ pub struct VolumeControls { } impl VolumeControls { - fn new(is_muted: bool, volume: f32) -> Self { - Self { is_muted, volume } + fn new(preferences: &GlobalPreferences) -> Self { + Self { + is_muted: preferences.mute(), + volume: preferences.preferred_volume() * 100.0, + } } /// Returns the volume between 0 and 1 (calculated out of the diff --git a/desktop/src/player.rs b/desktop/src/player.rs index 5dc469aa0..a22636246 100644 --- a/desktop/src/player.rs +++ b/desktop/src/player.rs @@ -39,7 +39,6 @@ pub struct PlayerOptions { pub align: StageAlign, pub force_align: bool, pub scale: StageScaleMode, - pub volume: f32, pub force_scale: bool, pub proxy: Option, pub socket_allowed: HashSet, @@ -68,7 +67,6 @@ impl From<&GlobalPreferences> for PlayerOptions { align: value.cli.align.unwrap_or_default(), force_align: value.cli.force_align, scale: value.cli.scale, - volume: value.cli.volume, force_scale: value.cli.force_scale, proxy: value.cli.proxy.clone(), upgrade_to_https: value.cli.upgrade_to_https, diff --git a/desktop/src/preferences.rs b/desktop/src/preferences.rs index 0baec1d7a..82400c786 100644 --- a/desktop/src/preferences.rs +++ b/desktop/src/preferences.rs @@ -62,23 +62,23 @@ impl GlobalPreferences { } pub fn graphics_backends(&self) -> GraphicsBackend { - self.cli.graphics.unwrap_or( + self.cli.graphics.unwrap_or_else(|| { self.preferences .lock() .expect("Preferences is not reentrant") .values - .graphics_backend, - ) + .graphics_backend + }) } pub fn graphics_power_preference(&self) -> PowerPreference { - self.cli.power.unwrap_or( + self.cli.power.unwrap_or_else(|| { self.preferences .lock() .expect("Preferences is not reentrant") .values - .graphics_power_preference, - ) + .graphics_power_preference + }) } pub fn language(&self) -> LanguageIdentifier { @@ -99,6 +99,24 @@ impl GlobalPreferences { .clone() } + pub fn mute(&self) -> bool { + self.preferences + .lock() + .expect("Preferences is not reentrant") + .values + .mute + } + + pub fn preferred_volume(&self) -> f32 { + self.cli.volume.unwrap_or_else(|| { + self.preferences + .lock() + .expect("Preferences is not reentrant") + .values + .volume + }) + } + pub fn write_preferences(&self, fun: impl FnOnce(&mut PreferencesWriter)) -> Result<(), Error> { let mut preferences = self .preferences @@ -138,6 +156,8 @@ pub struct SavedGlobalPreferences { pub graphics_power_preference: PowerPreference, pub language: LanguageIdentifier, pub output_device: Option, + pub mute: bool, + pub volume: f32, } impl Default for SavedGlobalPreferences { @@ -151,6 +171,8 @@ impl Default for SavedGlobalPreferences { graphics_power_preference: Default::default(), language: locale, output_device: None, + mute: false, + volume: 1.0, } } } diff --git a/desktop/src/preferences/read.rs b/desktop/src/preferences/read.rs index 8a4a792de..2a2d75b23 100644 --- a/desktop/src/preferences/read.rs +++ b/desktop/src/preferences/read.rs @@ -60,6 +60,18 @@ pub fn read_preferences(input: &str) -> (ParseResult, Document) { Err(e) => result.add_warning(format!("Invalid output_device: {e}")), }; + match parse_item_from_float(document.get("volume")) { + Ok(Some(value)) => result.result.volume = value.clamp(0.0, 1.0) as f32, + Ok(None) => {} + Err(e) => result.add_warning(format!("Invalid volume: {e}")), + }; + + match parse_item_from_bool(document.get("mute")) { + Ok(Some(value)) => result.result.mute = value, + Ok(None) => {} + Err(e) => result.add_warning(format!("Invalid mute: {e}")), + }; + (result, document) } @@ -79,6 +91,30 @@ fn parse_item_from_str(item: Option<&Item>) -> Result) -> Result, String> { + if let Some(item) = item { + if let Some(value) = item.as_float() { + Ok(Some(value)) + } else { + Err(format!("expected float but found {}", item.type_name())) + } + } else { + Ok(None) + } +} + +fn parse_item_from_bool(item: Option<&Item>) -> Result, String> { + if let Some(item) = item { + if let Some(value) = item.as_bool() { + Ok(Some(value)) + } else { + Err(format!("expected boolean but found {}", item.type_name())) + } + } else { + Ok(None) + } +} + #[cfg(test)] mod tests { use super::*; @@ -262,4 +298,76 @@ mod tests { result ); } + + #[test] + fn mute() { + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + mute: false, + ..Default::default() + }, + warnings: vec!["Invalid mute: expected boolean but found string".to_string()] + }, + read_preferences("mute = \"false\"").0 + ); + + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + mute: true, + ..Default::default() + }, + warnings: vec![] + }, + read_preferences("mute = true").0 + ); + + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + mute: false, + ..Default::default() + }, + warnings: vec![] + }, + read_preferences("").0 + ); + } + + #[test] + fn volume() { + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + volume: 1.0, + ..Default::default() + }, + warnings: vec!["Invalid volume: expected float but found string".to_string()] + }, + read_preferences("volume = \"0.5\"").0 + ); + + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + volume: 0.5, + ..Default::default() + }, + warnings: vec![] + }, + read_preferences("volume = 0.5").0 + ); + + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + volume: 0.0, + ..Default::default() + }, + warnings: vec![] + }, + read_preferences("volume = -1.0").0 + ); + } } diff --git a/desktop/src/preferences/write.rs b/desktop/src/preferences/write.rs index 5c8f14483..316f3242b 100644 --- a/desktop/src/preferences/write.rs +++ b/desktop/src/preferences/write.rs @@ -33,6 +33,16 @@ impl<'a> PreferencesWriter<'a> { } self.0.values.output_device = name; } + + pub fn set_mute(&mut self, mute: bool) { + self.0.toml_document["mute"] = value(mute); + self.0.values.mute = mute; + } + + pub fn set_volume(&mut self, volume: f32) { + self.0.toml_document["volume"] = value(volume as f64); + self.0.values.volume = volume; + } } #[cfg(test)] @@ -124,4 +134,19 @@ mod tests { "", ); } + + #[test] + fn set_volume() { + test("", |writer| writer.set_volume(0.5), "volume = 0.5\n"); + } + + #[test] + fn set_mute() { + test("", |writer| writer.set_mute(true), "mute = true\n"); + test( + "mute = true", + |writer| writer.set_mute(false), + "mute = false\n", + ); + } }