Skip to main content

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    // Use /proc instead of spawning ps
78    if let Ok(proc_path) = std::fs::read_dir("/proc") {
79        for entry in proc_path.flatten() {
80            let name = entry.file_name();
81            let name_str = name.to_string_lossy();
82
83            // Only look at numeric PIDs
84            if !name_str.chars().all(|c| c.is_ascii_digit()) {
85                continue;
86            }
87
88            let comm_path = entry.path().join("comm");
89            if let Ok(comm) = std::fs::read_to_string(&comm_path) {
90                let process_name = comm.trim().to_lowercase();
91                for player in &players {
92                    if process_name.contains(*player) {
93                        return Some(player.to_string());
94                    }
95                }
96            }
97        }
98    }
99
100    None
101}
102
103#[cfg(test)]
104mod tests {
105
106    #[test]
107    fn test_parse_output_defaults() {
108        let mock_output = "\n\n\n";
109        let mut lines = mock_output.lines();
110
111        let artist = {
112            let val = lines.next().unwrap_or("").trim();
113            if val.is_empty() {
114                "Unknown Artist"
115            } else {
116                val
117            }
118        };
119        let album = {
120            let val = lines.next().unwrap_or("").trim();
121            if val.is_empty() {
122                "Unknown Album"
123            } else {
124                val
125            }
126        };
127        let title = {
128            let val = lines.next().unwrap_or("").trim();
129            if val.is_empty() {
130                "Unknown Song"
131            } else {
132                val
133            }
134        };
135
136        assert_eq!(artist, "Unknown Artist");
137        assert_eq!(album, "Unknown Album");
138        assert_eq!(title, "Unknown Song");
139    }
140}