-
Notifications
You must be signed in to change notification settings - Fork 0
/
spotify.py
191 lines (156 loc) · 6.83 KB
/
spotify.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# The MIT License (MIT)
# Copyright(c) 2022 Ben Hunt
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from subprocess import CompletedProcess, run
from typing import List
from libqtile.group import _Group
from libqtile.config import Screen
from libqtile.widget import base
SPOTIFY = "Spotify"
class Spotify(base.ThreadPoolText):
"""
A widget to interact with spotify via dbus.
"""
defaults = [
("play_icon", "", "icon to display when playing music"),
("pause_icon", "", "icon to display when music paused"),
("update_interval", 0.5, "polling rate in seconds"),
("format", "{icon} {artist}:{album} - {track}", "Spotify display format"),
]
def __init__(self, **config) -> None:
base.ThreadPoolText.__init__(self, text="", **config)
self.add_defaults(Spotify.defaults)
self.add_callbacks(
{
"Button3": self.toggle_between_groups,
"Button1": self.toggle_music,
}
)
def _is_proc_running(self, proc_name: str) -> bool:
# create regex pattern to search for to avoid similar named processes
pattern = proc_name + "$"
# pgrep will return a string of pids for matching processes
proc_out = run(["pgrep", "-fli", pattern], capture_output=True).stdout.decode(
"utf-8"
)
# empty string means nothing started
is_running = proc_out != ""
return is_running
def toggle_between_groups(self) -> None:
"""
remember which group you were on before you switched to spotify
so you can toggle between the 2 groups
"""
current_screen: Screen = self.qtile.current_screen
current_group_info = self.qtile.current_group.info()
windows = current_group_info["windows"]
if SPOTIFY in windows:
# go to previous group
current_screen.previous_group.cmd_toscreen()
else:
self.go_to_spotify()
def go_to_spotify(self) -> None:
"""
Switch to whichever group has the current spotify instance
if none exists then we will spawn an instance on the current group
"""
# spawn spotify if not already running
if not self._is_proc_running("spotify"):
self.qtile.cmd_spawn("spotify", shell=True)
return
all_groups: List[_Group] = self.qtile.groups
# we need to find the group that has spotify in it
for group in all_groups:
info = group.info()
# get a list of windows for the group. We look for 'Spotify here'
windows = info["windows"]
name = group.name
if SPOTIFY in windows:
# switch to 'name' group
spotify_group = self.qtile.groups_map[name]
spotify_group.cmd_toscreen()
break
def poll(self) -> str:
"""Poll content for the text box"""
vars = {}
if self.playing:
vars["icon"] = self.play_icon
else:
vars["icon"] = self.pause_icon
vars["artist"] = self.artist
vars["track"] = self.song_title
vars["album"] = self.album
return self.format.format(**vars)
def toggle_music(self) -> None:
run(
"dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause",
shell=True,
)
def get_proc_output(self, proc: CompletedProcess) -> str:
if proc.stderr.decode("utf-8") != "":
return (
""
if "org.mpris.MediaPlayer2.spotify" in proc.stderr.decode("utf-8")
else proc.stderr.decode("utf-8")
)
output = proc.stdout.decode("utf-8").rstrip()
return output
@property
def _meta(self) -> str:
proc = run(
"dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:'org.mpris.MediaPlayer2.Player' string:'Metadata'",
shell=True,
capture_output=True,
)
output: str = proc.stdout.decode("utf-8").replace("'", "ʼ").rstrip()
return "" if ("org.mpris.MediaPlayer2.spotify" in output) else output
@property
def artist(self) -> str:
proc: CompletedProcess = run(
"dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:'org.mpris.MediaPlayer2.Player' string:'Metadata' | grep -m1 'xesam:artist' -b2 | tail -n 1 | grep -o '\".*\"' | sed 's/\"//g' | sed -e 's/&/and/g'",
shell=True,
capture_output=True,
)
output = self.get_proc_output(proc)
return output
@property
def song_title(self) -> str:
proc: CompletedProcess = run(
f"echo '{self._meta}' | grep -m1 'xesam:title' -b1 | tail -n1 | grep -o '\".*\"' | sed 's/\"//g' | sed -e 's/&/and/g'",
shell=True,
capture_output=True,
)
output = self.get_proc_output(proc)
return output
@property
def album(self) -> str:
proc = run(
f"echo '{self._meta}' | grep -m1 'xesam:album' -b1 | tail -n1 | grep -o '\".*\"' | sed 's/\"//g' | sed -e 's/&/and/g'",
shell=True,
capture_output=True,
)
output: str = self.get_proc_output(proc)
return output
@property
def playing(self) -> bool:
play = run(
"dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:'org.mpris.MediaPlayer2.Player' string:'PlaybackStatus' | grep -o Playing",
shell=True,
capture_output=True,
).stdout.decode("utf-8")
is_running = play != ""
return is_running