Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/app/facade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::config::{
use crate::discovery::{DiscoveryService, MdnsDiscoveryService};
use crate::error::RairstreamError;
use crate::pairing::ReceiverCredentials;
use crate::platform;
use crate::receiver::{Receiver, selector};
use crate::session::{
PlaybackSession, pair_receiver_with_pin, play_capture, play_file,
Expand Down Expand Up @@ -200,6 +201,29 @@ where
Ok(())
}

pub fn set_auto_reconnect(&mut self, enabled: bool) -> Result<(), RairstreamError> {
self.set_auto_reconnect_with_start_at_login(enabled, platform::set_start_at_login_enabled)
}

fn set_auto_reconnect_with_start_at_login<F>(
&mut self,
enabled: bool,
set_start_at_login_enabled: F,
) -> Result<(), RairstreamError>
where
F: FnOnce(bool) -> Result<(), String>,
{
if enabled {
set_start_at_login_enabled(true).map_err(|message| RairstreamError::Playback {
message: format!("failed to enable start at login for auto reconnect: {message}"),
})?;
}

self.config.set_auto_reconnect(enabled);
self.persist_config()?;
Ok(())
}

pub fn paired_forget(
&mut self,
selector_text: &str,
Expand Down Expand Up @@ -694,6 +718,55 @@ mod tests {
let _ = std::fs::remove_file(path);
}

#[test]
fn set_auto_reconnect_enables_start_at_login_before_persisting() {
let path = temp_config_path();
let mut facade = AppFacade::with_config_path(
FixedDiscoveryService {
receivers: Vec::new(),
},
path.clone(),
)
.unwrap();

facade
.set_auto_reconnect_with_start_at_login(true, |enabled| {
assert!(enabled);
Ok(())
})
.unwrap();

assert!(facade.config().auto_reconnect);
let reloaded = crate::config::load_config(&path).unwrap();
assert!(reloaded.auto_reconnect);
let _ = std::fs::remove_file(path);
}

#[test]
fn set_auto_reconnect_failure_does_not_persist_enabled_state() {
let path = temp_config_path();
let mut facade = AppFacade::with_config_path(
FixedDiscoveryService {
receivers: Vec::new(),
},
path.clone(),
)
.unwrap();

let error = facade
.set_auto_reconnect_with_start_at_login(true, |_| Err(String::from("registry denied")))
.unwrap_err();

assert_eq!(
error.to_string(),
"playback failed: failed to enable start at login for auto reconnect: registry denied"
);
assert!(!facade.config().auto_reconnect);
let reloaded = crate::config::load_config(&path).unwrap();
assert!(!reloaded.auto_reconnect);
let _ = std::fs::remove_file(path);
}

#[test]
fn play_file_resets_state_after_failure() {
let path = temp_config_path();
Expand Down
7 changes: 7 additions & 0 deletions src/config/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub struct AppConfig {
pub tray_selected_receiver_ids: Vec<String>,
#[serde(default)]
pub tray_language: TrayLanguagePreference,
#[serde(default)]
pub auto_reconnect: bool,
}

impl Default for AppConfig {
Expand All @@ -43,6 +45,7 @@ impl Default for AppConfig {
receiver_cache: HashMap::new(),
tray_selected_receiver_ids: Vec::new(),
tray_language: TrayLanguagePreference::default(),
auto_reconnect: false,
}
}
}
Expand Down Expand Up @@ -76,6 +79,10 @@ impl AppConfig {
pub fn set_tray_language(&mut self, language: TrayLanguagePreference) {
self.tray_language = language;
}

pub fn set_auto_reconnect(&mut self, enabled: bool) {
self.auto_reconnect = enabled;
}
}

const fn default_sender_volume_percent() -> u16 {
Expand Down
9 changes: 9 additions & 0 deletions src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ mod windows;
#[cfg(target_os = "windows")]
pub use windows::{is_start_at_login_enabled, set_start_at_login_enabled};

#[cfg(not(target_os = "windows"))]
pub fn set_start_at_login_enabled(enabled: bool) -> Result<(), String> {
if enabled {
return Err(String::from("start at login is only supported on Windows"));
}

Ok(())
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlatformInfo {
pub os: &'static str,
Expand Down
14 changes: 14 additions & 0 deletions src/ui/tray/i18n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ pub enum TrayText {
StatusIdle,
StatusWaitingForPin,
StatusStreamingToDevices,
StatusReconnecting,
RefreshDevices,
PlaybackTargets,
PairDevice,
ForgetPairing,
StartStreaming,
StopStreaming,
AutoReconnect,
StartAtLogin,
Language,
LanguageSystem,
Expand Down Expand Up @@ -74,6 +76,14 @@ impl TrayI18n {
)
}

#[must_use]
pub fn status_reconnecting(&self, count: usize, attempt: u32) -> String {
format!(
"{} {count} device(s) - attempt {attempt}",
self.text(TrayText::StatusReconnecting)
)
}

#[must_use]
pub fn saved_pairing_for(&self, label: &str) -> String {
format!("{} {label}", self.text(TrayText::SavedPairingFor))
Expand Down Expand Up @@ -180,12 +190,14 @@ fn text_for(locale: TrayLocale, key: TrayText) -> &'static str {
(TrayLocale::ZhCn, TrayText::StatusIdle) => "状态:空闲",
(TrayLocale::ZhCn, TrayText::StatusWaitingForPin) => "状态:等待 PIN",
(TrayLocale::ZhCn, TrayText::StatusStreamingToDevices) => "状态:正在串流到",
(TrayLocale::ZhCn, TrayText::StatusReconnecting) => "状态:正在重连到",
(TrayLocale::ZhCn, TrayText::RefreshDevices) => "刷新设备",
(TrayLocale::ZhCn, TrayText::PlaybackTargets) => "播放目标",
(TrayLocale::ZhCn, TrayText::PairDevice) => "配对设备",
(TrayLocale::ZhCn, TrayText::ForgetPairing) => "忘记配对",
(TrayLocale::ZhCn, TrayText::StartStreaming) => "开始串流",
(TrayLocale::ZhCn, TrayText::StopStreaming) => "停止串流",
(TrayLocale::ZhCn, TrayText::AutoReconnect) => "自动重连",
(TrayLocale::ZhCn, TrayText::StartAtLogin) => "登录时启动",
(TrayLocale::ZhCn, TrayText::Language) => "语言",
(TrayLocale::ZhCn, TrayText::LanguageSystem) => "跟随系统",
Expand All @@ -203,12 +215,14 @@ fn text_for(locale: TrayLocale, key: TrayText) -> &'static str {
(_, TrayText::StatusIdle) => "Status: Idle",
(_, TrayText::StatusWaitingForPin) => "Status: Waiting for PIN",
(_, TrayText::StatusStreamingToDevices) => "Status: Streaming to",
(_, TrayText::StatusReconnecting) => "Status: Reconnecting to",
(_, TrayText::RefreshDevices) => "Refresh Devices",
(_, TrayText::PlaybackTargets) => "Playback Targets",
(_, TrayText::PairDevice) => "Pair Device",
(_, TrayText::ForgetPairing) => "Forget Pairing",
(_, TrayText::StartStreaming) => "Start Streaming",
(_, TrayText::StopStreaming) => "Stop Streaming",
(_, TrayText::AutoReconnect) => "Auto Reconnect",
(_, TrayText::StartAtLogin) => "Start at login",
(_, TrayText::Language) => "Language",
(_, TrayText::LanguageSystem) => "Follow System",
Expand Down
Loading
Loading