From 67acd2cce4d652610ae711a80e6e4439dc422a92 Mon Sep 17 00:00:00 2001 From: Remi Akirazar Date: Thu, 13 Nov 2025 16:34:37 -0500 Subject: [PATCH] add Spotify in main bar --- __pycache__/config.cpython-313.pyc | Bin 0 -> 17585 bytes __pycache__/spotify.cpython-313.pyc | Bin 0 -> 7906 bytes config.py | 15 +- spotify.py | 204 ++++++++++++++++++++++++++++ spotify.py~ | 204 ++++++++++++++++++++++++++++ 5 files changed, 421 insertions(+), 2 deletions(-) create mode 100644 __pycache__/config.cpython-313.pyc create mode 100644 __pycache__/spotify.cpython-313.pyc create mode 100644 spotify.py create mode 100644 spotify.py~ diff --git a/__pycache__/config.cpython-313.pyc b/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b78504696975051d805c4e0f607332f962792dc5 GIT binary patch literal 17585 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�+r%ezRx`M*u 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 literal 0 HcmV?d00001 diff --git a/__pycache__/spotify.cpython-313.pyc b/__pycache__/spotify.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e1c71d5dce38ab0e2167ebada33c7f4550f8f22 GIT binary patch literal 7906 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 literal 0 HcmV?d00001 diff --git a/config.py b/config.py index 4c6a8e1..3ee138e 100644 --- a/config.py +++ b/config.py @@ -29,10 +29,11 @@ from libqtile.config import Click, Drag, Group, Key, Match, hook, Screen, KeyCho from libqtile.lazy import lazy from libqtile.utils import guess_terminal from libqtile.dgroups import simple_key_binder +from spotify import Spotify mod = "mod4" #aka Windows key -terminal = "alacritty" #This is an example on how flexible Qtile is, you create variables then use them in a keybind for example (see below) +terminal = "kitty" #This is an example on how flexible Qtile is, you create variables then use them in a keybind for example (see below) mod1 = "mod1" #alt key filemanager = "thunar" @@ -230,7 +231,6 @@ def open_btop(): def open_pavucontrol(): qtile.cmd_spawn("pavucontrol") - # █▄▄ ▄▀█ █▀█ # █▄█ █▀█ █▀▄ @@ -308,6 +308,17 @@ screens = [ filename = '~/.config/qtile/Assets/5.png', ), + widget.Image( + filename = '~/.config/qtile/Assets/1.png', + background = '#52548D', + ), + + Spotify(), + + widget.Image( + filename = '~/.config/qtile/Assets/5.png', + ), + widget.Image( filename = '~/.config/qtile/Assets/1.png', background = '#52548D', diff --git a/spotify.py b/spotify.py new file mode 100644 index 0000000..2fc90e4 --- /dev/null +++ b/spotify.py @@ -0,0 +1,204 @@ +# 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 +from libqtile.log_utils import logger + +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} - {track}", "Spotify display format"), + ] + + def __init__(self, **config) -> None: + # init base class + super().__init__(text="", **config) + self.add_defaults(Spotify.defaults) + self.add_callbacks( + { + "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 = f"{proc_name}$" + # pgrep will return a string of pids for matching processes + cmd = ["pgrep", "-fli", pattern] + proc_out = run(cmd, capture_output=True).stdout.decode( + "utf-8" + ) + + return proc_out != "" + + 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() + logger.warning(f"current group info: {current_group_info}") + windows = current_group_info["windows"] + if SPOTIFY in windows: + # go to previous group + logger.warning("going to previous group") + current_screen.group.get_previous_group().toscreen() + logger.warning("went to previous group") + 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.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"] + if SPOTIFY in windows: + name = group.name + # switch to 'name' group + spotify_group = self.qtile.groups_map[name] + spotify_group.toscreen() + break + + def poll(self) -> str: # type: ignore + """Poll content for the text box""" + vars = { + "icon": self.pause_icon if self.playing else self.play_icon, + "artist": self.artist, + "track": self.song_title, + "album": self.album, + } + + return self.format.format(**vars) # type: ignore + + def toggle_music(self) -> None: + cmd = """ + dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify \ + /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause + """ + run(cmd, shell=True) + + + def get_proc_output(self, proc: CompletedProcess) -> str: + stdout = proc.stdout.decode("utf-8") + no_spotify = "Error" in stdout + return ( + "" + if no_spotify + else stdout.rstrip() + ) + + + @property + def _meta(self) -> str: + cmd = """dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify \ + /org/mpris/MediaPlayer2 \ + org.freedesktop.DBus.Properties.Get \ + string:'org.mpris.MediaPlayer2.Player' \ + string:'Metadata' + """ + proc = run( cmd, 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: + cmd =""" + 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' + """ + proc: CompletedProcess = run(cmd, + shell=True, + capture_output=True, + ) + + return self.get_proc_output(proc) + + @property + def song_title(self) -> str: + cmd = f""" + echo '{self._meta}' | grep -m1 'xesam:title' -b1 | tail -n1 | grep -o '\".*\"' | \ + sed 's/\"//g' | sed -e 's/&/and/g' + """ + proc: CompletedProcess = run(cmd, + shell=True, + capture_output=True + ) + + return self.get_proc_output(proc) + + @property + def album(self) -> str: + cmd = f""" + echo '{self._meta}' | grep -m1 'xesam:album' -b1 | tail -n1 | grep -o '\".*\"' | \ + sed 's/\"//g' | sed -e 's/&/and/g' + """ + proc = run(cmd, + shell=True, + capture_output=True, + ) + + return self.get_proc_output(proc) + + @property + def playing(self) -> bool: + cmd = """ + 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 + """ + play = run(cmd, + shell=True, + capture_output=True, + ).stdout.decode("utf-8") + + return play != "" diff --git a/spotify.py~ b/spotify.py~ new file mode 100644 index 0000000..c5de735 --- /dev/null +++ b/spotify.py~ @@ -0,0 +1,204 @@ +# 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 +from libqtile.log_utils import logger + +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: + # init base class + super().__init__(text="", **config) + self.add_defaults(Spotify.defaults) + self.add_callbacks( + { + "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 = f"{proc_name}$" + # pgrep will return a string of pids for matching processes + cmd = ["pgrep", "-fli", pattern] + proc_out = run(cmd, capture_output=True).stdout.decode( + "utf-8" + ) + + return proc_out != "" + + 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() + logger.warning(f"current group info: {current_group_info}") + windows = current_group_info["windows"] + if SPOTIFY in windows: + # go to previous group + logger.warning("going to previous group") + current_screen.group.get_previous_group().toscreen() + logger.warning("went to previous group") + 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.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"] + if SPOTIFY in windows: + name = group.name + # switch to 'name' group + spotify_group = self.qtile.groups_map[name] + spotify_group.toscreen() + break + + def poll(self) -> str: # type: ignore + """Poll content for the text box""" + vars = { + "icon": self.pause_icon if self.playing else self.play_icon, + "artist": self.artist, + "track": self.song_title, + "album": self.album, + } + + return self.format.format(**vars) # type: ignore + + def toggle_music(self) -> None: + cmd = """ + dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify \ + /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause + """ + run(cmd, shell=True) + + + def get_proc_output(self, proc: CompletedProcess) -> str: + stdout = proc.stdout.decode("utf-8") + no_spotify = "Error" in stdout + return ( + "" + if no_spotify + else stdout.rstrip() + ) + + + @property + def _meta(self) -> str: + cmd = """dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify \ + /org/mpris/MediaPlayer2 \ + org.freedesktop.DBus.Properties.Get \ + string:'org.mpris.MediaPlayer2.Player' \ + string:'Metadata' + """ + proc = run( cmd, 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: + cmd =""" + 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' + """ + proc: CompletedProcess = run(cmd, + shell=True, + capture_output=True, + ) + + return self.get_proc_output(proc) + + @property + def song_title(self) -> str: + cmd = f""" + echo '{self._meta}' | grep -m1 'xesam:title' -b1 | tail -n1 | grep -o '\".*\"' | \ + sed 's/\"//g' | sed -e 's/&/and/g' + """ + proc: CompletedProcess = run(cmd, + shell=True, + capture_output=True + ) + + return self.get_proc_output(proc) + + @property + def album(self) -> str: + cmd = f""" + echo '{self._meta}' | grep -m1 'xesam:album' -b1 | tail -n1 | grep -o '\".*\"' | \ + sed 's/\"//g' | sed -e 's/&/and/g' + """ + proc = run(cmd, + shell=True, + capture_output=True, + ) + + return self.get_proc_output(proc) + + @property + def playing(self) -> bool: + cmd = """ + 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 + """ + play = run(cmd, + shell=True, + capture_output=True, + ).stdout.decode("utf-8") + + return play != ""