#!/usr/bin/env python3 import subprocess, threading, urllib.request import dbus, dbus.service, dbus.mainloop.glib from gi.repository import GLib class MusicNotifier: def __init__(self): dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SessionBus() obj = bus.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") self.iface = dbus.Interface(obj, "org.freedesktop.Notifications") self.notif_id = dbus.UInt32(0) bus.add_signal_receiver( self.on_action, dbus_interface="org.freedesktop.Notifications", signal_name="ActionInvoked" ) def get_art(self, url): if not url: return "" try: if url.startswith("file://"): return url[7:] path = "/tmp/music_art.jpg" urllib.request.urlretrieve(url, path) return path except: return "" def notify(self, artist, title, art_url, status): art = self.get_art(art_url) play_label = "⏸ Pause" if status == "Playing" else "▶ Play" actions = ["prev", "⏮ Zurück", "toggle", play_label, "next", "⏭ Weiter"] hints = {"image-path": dbus.String(art)} if art else {} self.notif_id = self.iface.Notify( "music", self.notif_id, art, title, artist, actions, hints, 0 ) def on_action(self, notif_id, action): if notif_id != self.notif_id: return cmds = {"prev": "previous", "toggle": "play-pause", "next": "next"} if action in cmds: subprocess.run(["playerctl", cmds[action]]) def monitor(self): proc = subprocess.Popen( ["playerctl", "--follow", "metadata", "--format", "{{artist}}||{{title}}||{{mpris:artUrl}}||{{status}}"], stdout=subprocess.PIPE, text=True ) prev = "" for line in proc.stdout: parts = line.strip().split("||") if len(parts) != 4: continue artist, title, art_url, status = parts if title != prev: prev = title self.notify(artist, title, art_url, status) def run(self): threading.Thread(target=self.monitor, daemon=True).start() GLib.MainLoop().run() if __name__ == "__main__": MusicNotifier().run()