leenfetch_core/modules/linux/
song.rs

1use std::env;
2use std::process::Command;
3
4use crate::modules::enums::SongInfo;
5
6pub fn get_song() -> Option<SongInfo> {
7    let player = detect_player()?;
8
9    // Match player and run respective logic
10    let output = match player.as_str() {
11        "mocp" => run(&["mocp", "-Q", "%artist \n%album \n%song"]),
12        "deadbeef" => run(&[
13            "deadbeef",
14            "--nowplaying-tf",
15            "%artist% \\n%album% \\n%title%",
16        ]),
17        "qmmp" => run(&["qmmp", "--nowplaying", "%p \n%a \n%t"]),
18        "spotify" | "vlc" | "rhythmbox" | "clementine" | "lollypop" => run(&[
19            "playerctl",
20            "metadata",
21            "--player",
22            player.as_str(),
23            "--format",
24            "{{ artist }}\n{{ album }}\n{{ title }}",
25        ]),
26        "mpd" | "mopidy" => run(&["mpc", "-f", "%artist% \n%album% \n%title%", "current"]),
27        "playerctl" => run(&[
28            "playerctl",
29            "metadata",
30            "--format",
31            "{{ artist }}\n{{ album }}\n{{ title }}",
32        ]),
33        _ => return None,
34    }?;
35
36    // Parse output
37    let mut lines = output.lines();
38    let artist = lines.next().unwrap_or("Unknown Artist").trim().to_string();
39    let album = lines.next().unwrap_or("Unknown Album").trim().to_string();
40    let title = lines.next().unwrap_or("Unknown Song").trim().to_string();
41
42    Some(SongInfo {
43        artist,
44        album,
45        title,
46    })
47}
48
49fn run(cmd: &[&str]) -> Option<String> {
50    let output = Command::new(cmd[0]).args(&cmd[1..]).output().ok()?;
51    if !output.status.success() {
52        return None;
53    }
54    Some(String::from_utf8_lossy(&output.stdout).to_string())
55}
56
57fn detect_player() -> Option<String> {
58    let env_player = env::var("MUSIC_PLAYER").ok();
59    if let Some(p) = env_player {
60        return Some(p);
61    }
62
63    let players = [
64        "spotify",
65        "playerctl",
66        "mocp",
67        "qmmp",
68        "mpd",
69        "mopidy",
70        "vlc",
71        "rhythmbox",
72        "deadbeef",
73        "lollypop",
74        "clementine",
75    ];
76
77    let output = Command::new("ps").args(["-eo", "comm="]).output().ok()?;
78    let ps_output = String::from_utf8_lossy(&output.stdout);
79
80    for player in players {
81        if ps_output.lines().any(|line| line.trim().contains(player)) {
82            return Some(player.to_string());
83        }
84    }
85
86    None
87}
88
89#[cfg(test)]
90mod tests {
91
92    #[test]
93    fn test_parse_output_defaults() {
94        let mock_output = "\n\n\n";
95        let mut lines = mock_output.lines();
96
97        let artist = {
98            let val = lines.next().unwrap_or("").trim();
99            if val.is_empty() {
100                "Unknown Artist"
101            } else {
102                val
103            }
104        };
105        let album = {
106            let val = lines.next().unwrap_or("").trim();
107            if val.is_empty() { "Unknown Album" } else { val }
108        };
109        let title = {
110            let val = lines.next().unwrap_or("").trim();
111            if val.is_empty() { "Unknown Song" } else { val }
112        };
113
114        assert_eq!(artist, "Unknown Artist");
115        assert_eq!(album, "Unknown Album");
116        assert_eq!(title, "Unknown Song");
117    }
118}