use super::NowPlaying;
const MUSIC_SCRIPT: &str = r#"set np to ""
try
if application "Music" is running then
tell application "Music"
if player state is playing then
set np to "Music" & tab & (name of current track) & tab & (artist of current track)
end if
end tell
end if
end try
return np"#;
const SPOTIFY_SCRIPT: &str = r#"set np to ""
try
if application "Spotify" is running then
tell application "Spotify"
if player state is playing then
set np to "Spotify" & tab & (name of current track) & tab & (artist of current track)
end if
end tell
end if
end try
return np"#;
fn app_installed(name: &str) -> bool {
let mut paths: Vec<std::path::PathBuf> = vec![
std::path::PathBuf::from(format!("/Applications/{name}.app")),
std::path::PathBuf::from(format!("/System/Applications/{name}.app")),
];
if let Ok(home) = std::env::var("HOME") {
paths.push(std::path::PathBuf::from(format!(
"{home}/Applications/{name}.app"
)));
}
paths.iter().any(|p| p.exists())
}
fn run_script(script: &str) -> String {
std::process::Command::new("osascript")
.arg("-e")
.arg(script)
.output()
.ok()
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_default()
}
pub fn poll() -> Option<NowPlaying> {
if !cfg!(target_os = "macos") {
return None;
}
if app_installed("Music")
&& let Some(np) = parse(&run_script(MUSIC_SCRIPT))
{
return Some(np);
}
if app_installed("Spotify")
&& let Some(np) = parse(&run_script(SPOTIFY_SCRIPT))
{
return Some(np);
}
None
}
pub fn parse(line: &str) -> Option<NowPlaying> {
let line = line.trim();
if line.is_empty() {
return None;
}
let mut parts = line.split('\t');
let app = parts.next().unwrap_or("").trim();
let track = parts.next().unwrap_or("").trim();
let artist = parts.next().unwrap_or("").trim();
if track.is_empty() {
return None;
}
Some(NowPlaying {
source: if app.is_empty() {
"macOS".to_string()
} else {
app.to_string()
},
playing: true,
track: track.to_string(),
detail: artist.to_string(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_a_music_line() {
let n = parse("Music\tAerodynamic\tDaft Punk\n").unwrap();
assert_eq!(n.source, "Music");
assert!(n.playing);
assert_eq!(n.track, "Aerodynamic");
assert_eq!(n.detail, "Daft Punk");
}
#[test]
fn parses_a_spotify_line_without_an_artist() {
let n = parse("Spotify\tSome Track\t").unwrap();
assert_eq!(n.source, "Spotify");
assert_eq!(n.track, "Some Track");
assert_eq!(n.detail, "");
}
#[test]
fn empty_or_trackless_input_is_none() {
assert!(parse("").is_none());
assert!(parse(" \n").is_none());
assert!(parse("Music\t\t").is_none()); }
}