diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-08 01:36:51 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-08 01:36:51 +0300 |
| commit | fb56e64537de5f93786ed121f703da2a4105badc (patch) | |
| tree | 67f6e100141dbee05c75ec421927d62e54d23ec6 /src/sound.rs | |
| parent | 416827872a6e2c820bd58209d267685d4c07cb7d (diff) | |
fix: improve windows pane behavior
Diffstat (limited to 'src/sound.rs')
| -rw-r--r-- | src/sound.rs | 90 |
1 files changed, 81 insertions, 9 deletions
diff --git a/src/sound.rs b/src/sound.rs index ab6c11c5..ef45ccc7 100644 --- a/src/sound.rs +++ b/src/sound.rs @@ -1,7 +1,8 @@ //! Sound notifications for agent state changes. //! //! Embeds mp3 files in the binary and plays them via system audio tools. -//! Uses afplay (macOS) or decoder-capable Linux audio players — no Rust audio dependencies. +//! Uses afplay (macOS), Windows MediaPlayer, or decoder-capable Linux audio +//! players — no Rust audio dependencies. use std::io::Write; use std::path::{Path, PathBuf}; @@ -89,23 +90,81 @@ fn temp_sound_path() -> PathBuf { std::env::temp_dir().join(format!("herdr-sound-{}-{id}.mp3", std::process::id())) } +#[cfg(windows)] fn run_player(path: &Path) -> Result<Output, String> { - if cfg!(target_os = "macos") { - Command::new("afplay") - .arg(path) - .output() - .map_err(|e| format!("no audio player available: {e}")) - } else { - run_linux_player(path) - } + run_windows_player(path) +} + +#[cfg(target_os = "macos")] +fn run_player(path: &Path) -> Result<Output, String> { + Command::new("afplay") + .arg(path) + .output() + .map_err(|e| format!("no audio player available: {e}")) +} + +#[cfg(not(any(windows, target_os = "macos")))] +fn run_player(path: &Path) -> Result<Output, String> { + run_linux_player(path) } +#[cfg(any(windows, test))] +fn windows_media_player_script() -> &'static str { + r#" +param([string]$Path) +$ErrorActionPreference = 'Stop' +Add-Type -AssemblyName PresentationCore +$resolved = (Resolve-Path -LiteralPath $Path).ProviderPath +$player = [System.Windows.Media.MediaPlayer]::new() +$script:done = $false +$script:failed = $null +$player.add_MediaEnded({ $script:done = $true }) +$player.add_MediaFailed({ + param($sender, $eventArgs) + $script:failed = $eventArgs.ErrorException + $script:done = $true +}) +$player.Open([Uri]::new($resolved)) +$deadline = [DateTime]::UtcNow.AddSeconds(15) +while (-not $script:done -and -not $player.NaturalDuration.HasTimeSpan -and [DateTime]::UtcNow -lt $deadline) { + Start-Sleep -Milliseconds 25 +} +if ($script:failed) { throw $script:failed } +$player.Play() +while (-not $script:done -and [DateTime]::UtcNow -lt $deadline) { + Start-Sleep -Milliseconds 50 +} +$player.Close() +if ($script:failed) { throw $script:failed } +if (-not $script:done) { throw 'sound playback timed out' } +"# +} + +#[cfg(windows)] +fn run_windows_player(path: &Path) -> Result<Output, String> { + Command::new("powershell.exe") + .args([ + "-NoLogo", + "-NoProfile", + "-NonInteractive", + "-ExecutionPolicy", + "Bypass", + "-Command", + windows_media_player_script(), + ]) + .arg(path) + .output() + .map_err(|e| format!("Windows MediaPlayer playback failed: {e}")) +} + +#[cfg(not(any(windows, target_os = "macos")))] #[derive(Debug, Clone, Copy)] struct AudioPlayer { program: &'static str, args: &'static [&'static str], } +#[cfg(not(any(windows, target_os = "macos")))] impl AudioPlayer { fn output(self, path: &Path) -> std::io::Result<Output> { Command::new(self.program) @@ -115,6 +174,7 @@ impl AudioPlayer { } } +#[cfg(not(any(windows, target_os = "macos")))] fn linux_audio_players() -> &'static [AudioPlayer] { // Do not add bare aplay here. It does not decode MP3 and plays MP3 bytes as raw PCM. &[ @@ -141,6 +201,7 @@ fn linux_audio_players() -> &'static [AudioPlayer] { ] } +#[cfg(not(any(windows, target_os = "macos")))] fn run_linux_player(path: &Path) -> Result<Output, String> { let mut errors = Vec::new(); @@ -158,6 +219,7 @@ fn run_linux_player(path: &Path) -> Result<Output, String> { )) } +#[cfg(not(any(windows, target_os = "macos")))] fn player_error(player: AudioPlayer, output: &Output) -> String { let stderr = String::from_utf8_lossy(&output.stderr); let stderr = stderr.trim(); @@ -178,6 +240,7 @@ mod tests { assert_ne!(temp_sound_path(), temp_sound_path()); } + #[cfg(not(any(windows, target_os = "macos")))] #[test] fn linux_audio_players_are_mp3_capable() { let programs: Vec<&str> = linux_audio_players() @@ -188,4 +251,13 @@ mod tests { assert_eq!(programs, ["paplay", "pw-play", "ffplay", "mpg123", "mpv"]); assert!(!programs.contains(&"aplay")); } + + #[test] + fn windows_media_player_script_accepts_literal_path_argument() { + let script = windows_media_player_script(); + + assert!(script.contains("param([string]$Path)")); + assert!(script.contains("Resolve-Path -LiteralPath $Path")); + assert!(script.contains("System.Windows.Media.MediaPlayer")); + } } |
