leenfetch_core/modules/linux/
song.rs1use 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 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 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}