Skip to content

Commit b204e9e

Browse files
Paulluxclaude
andcommitted
Add PyQt6 system tray UI, VLC HTTP backend, and app icon
- System tray (Qt/GTK) with status icon: violet=idle, green=playing, red=error - Settings window to configure .env without editing files manually - VLC HTTP API backend with priority over SMTC, including cover art via /art endpoint - Multi-platform backend dispatch: smtc.py (Windows) / mpris.py (Linux) - Smart Discord update: resync every 15s and on seek (drift > 3s) - App icon (SVG/PNG/ICO) with music notes, Windows badge and Tux badge - PyInstaller spec updated with assets/, icon.ico and PyQt6 hidden imports - WACUP support added to ICON_NAMES Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7e57294 commit b204e9e

14 files changed

Lines changed: 430 additions & 20 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ __pycache__/
66
.venv/
77
venv/
88
MPRIS-Discord-Presence/
9+
build/
10+
dist/

MusicPresence.spec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ a = Analysis(
3535
binaries=[],
3636
datas=[
3737
('playersIcons', 'playersIcons'),
38+
('assets', 'assets'),
3839
],
3940
hiddenimports=hiddenimports,
4041
hookspath=[],
@@ -63,6 +64,7 @@ exe = EXE(
6364
upx=True,
6465
upx_exclude=[],
6566
runtime_tmpdir=None,
67+
icon='assets/icon.ico',
6668
console=False,
6769
disable_windowed_traceback=False,
6870
target_arch=None,

assets/icon.ico

853 Bytes
Binary file not shown.

assets/icon.png

39.7 KB
Loading

assets/icon.svg

Lines changed: 43 additions & 0 deletions
Loading

assets/icon_fg.png

23.8 KB
Loading

assets/icon_fg.svg

Lines changed: 44 additions & 0 deletions
Loading

main.py

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
CACHE_PATH = os.path.join(script_dir, 'image_cache.json')
2626

2727
ICON_NAMES = {
28-
# Windows (SMTC source_app_user_model_id)
28+
# Windows
2929
'Spotify': 'spotify',
3030
'Chrome': 'chrome',
3131
'Microsoft Edge': 'edge',
@@ -34,7 +34,7 @@
3434
'foobar2000': 'foobar2000',
3535
'MusicBee': 'musicbee',
3636
'wacup.exe': 'wacup',
37-
# Linux (MPRIS Identity)
37+
# Linux
3838
'Clementine': 'clementine',
3939
'Media Player Classic Qute Theater': 'mpc-qt',
4040
'mpv': 'mpv',
@@ -48,7 +48,6 @@
4848
'default': 'default_icon',
4949
}
5050

51-
# Import du backend selon la plateforme
5251
if sys.platform == 'win32':
5352
from backends.smtc import get_track_info
5453
else:
@@ -158,15 +157,34 @@ async def clear_discord():
158157

159158
# ---------- Boucle principale ----------
160159

161-
async def main_loop():
162-
await RPC.connect()
160+
async def main_loop(bridge=None):
161+
def emit_status(s):
162+
if bridge:
163+
try:
164+
bridge.status_changed.emit(s)
165+
except Exception:
166+
pass
167+
168+
def emit_track(artist, title):
169+
if bridge:
170+
try:
171+
bridge.track_changed.emit(artist, title)
172+
except Exception:
173+
pass
174+
175+
try:
176+
await RPC.connect()
177+
except Exception as e:
178+
print(f"Erreur connexion Discord: {e}")
179+
emit_status('error')
180+
163181
print(f"MusicLocal Discord Presence démarré ({sys.platform}).")
164182
last_log = None
165-
last_track = None # (title, artist, source_app)
166-
last_update_time = 0 # timestamp de la dernière mise à jour Discord
167-
last_position_s = 0 # position envoyée à Discord
168-
SYNC_INTERVAL = 15 # secondes entre mises à jour forcées (limite Discord)
169-
SEEK_TOLERANCE = 3 # secondes de dérive avant resync
183+
last_track = None
184+
last_update_time = 0
185+
last_position_s = 0
186+
SYNC_INTERVAL = 15
187+
SEEK_TOLERANCE = 3
170188

171189
while True:
172190
info = await get_track_info()
@@ -177,14 +195,15 @@ async def main_loop():
177195
last_log = None
178196
last_track = None
179197
last_update_time = 0
198+
emit_status('idle')
199+
emit_track('', '')
180200
else:
181201
print("Aucune session multimédia active.")
182202
else:
183203
title, artist, image_bytes, source_app, position_s, duration_s = info
184204
current_track = (title, artist, source_app)
185205
now = time.time()
186206

187-
# Dérive attendue = temps écoulé depuis la dernière mise à jour
188207
expected_position = last_position_s + (now - last_update_time)
189208
position_drift = abs(position_s - expected_position)
190209

@@ -193,18 +212,13 @@ async def main_loop():
193212
seeked = last_track is not None and position_drift > SEEK_TOLERANCE
194213

195214
if track_changed or needs_sync or seeked:
196-
if track_changed:
197-
image_url = None
198-
if image_bytes:
199-
image_url = upload_cover(image_bytes)
200-
else:
201-
# Réutilise l'image déjà uploadée depuis le cache
202-
image_url = upload_cover(image_bytes) if image_bytes else None
203-
215+
image_url = upload_cover(image_bytes) if image_bytes else None
204216
await update_discord(title, artist, position_s, duration_s, image_url, source_app)
205217
last_update_time = now
206218
last_position_s = position_s
207219
last_track = current_track
220+
emit_status('playing')
221+
emit_track(artist, title)
208222

209223
log = f"[{source_app}] {artist}{title}"
210224
if log != last_log:
@@ -214,5 +228,25 @@ async def main_loop():
214228
await asyncio.sleep(5)
215229

216230

231+
# ---------- Entrée ----------
232+
233+
def _get_tray():
234+
if sys.platform == 'win32':
235+
from ui.tray_qt import TrayApp
236+
return TrayApp(main_loop)
237+
238+
desktop = os.getenv('XDG_CURRENT_DESKTOP', '').lower()
239+
if 'gnome' in desktop or 'unity' in desktop:
240+
try:
241+
from ui.tray_gtk import TrayApp
242+
return TrayApp(main_loop)
243+
except Exception:
244+
pass
245+
246+
from ui.tray_qt import TrayApp
247+
return TrayApp(main_loop)
248+
249+
217250
if __name__ == '__main__':
218-
asyncio.run(main_loop())
251+
tray = _get_tray()
252+
tray.run()

requirements-linux.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ minio
55
Pillow
66
dbus-python
77
pympris
8+
PyQt6
9+
pygobject
810
pyinstaller

requirements-windows.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ requests
44
minio
55
Pillow
66
winsdk
7+
PyQt6
78
pyinstaller

0 commit comments

Comments
 (0)