spolyrics 1.2.0__tar.gz → 1.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 paturrr
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 paturrr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spolyrics
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: A minimalist, borderless, zero-delay synced lyrics miniplayer for Spotify on Windows.
5
5
  Author: SpoLyrics Team
6
6
  Requires-Python: >=3.8
@@ -25,7 +25,8 @@ Dynamic: summary
25
25
  <h1 align="center">SpoLyrics</h1>
26
26
 
27
27
  <p align="center">
28
- <em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em>
28
+ <em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em><br>
29
+ <strong>🌐 <a href="https://paturrr.github.io/SpoLyrics/">Visit the Official Website</a></strong>
29
30
  </p>
30
31
 
31
32
  <p align="center">
@@ -75,6 +76,9 @@ I built this project to solve a specific complaint from my friends: they wanted
75
76
 
76
77
  - **Advanced Customization (Settings)**
77
78
  Change lyrics color using a modern real-time custom HSV color picker, adjust window transparency (opacity), and enable "Auto-Start with Windows". All configurations are permanently saved.
79
+
80
+ - **Auto Update Checker**
81
+ Automatically checks for the newest version on PyPI when you launch the app, offering a seamless one-click update and restart experience.
78
82
 
79
83
  ## Invisible Controls
80
84
 
@@ -1,126 +1,130 @@
1
- <p align="center">
2
- <img src="https://raw.githubusercontent.com/paturrr/SpoLyrics/main/icon.ico" width="150" alt="SpoLyrics Logo">
3
- </p>
4
-
5
- <h1 align="center">SpoLyrics</h1>
6
-
7
- <p align="center">
8
- <em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em>
9
- </p>
10
-
11
- <p align="center">
12
- <img src="https://img.shields.io/github/stars/paturrr/SpoLyrics.svg?style=flat-square&color=1DB954&label=stars&logo=github&logoColor=white" alt="Stars">
13
- <img src="https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-1DB954?style=flat-square&logo=python&logoColor=white" alt="Python Versions">
14
- <img src="https://img.shields.io/badge/OS-Windows%2010%2F11-1DB954?style=flat-square&logo=windows&logoColor=white" alt="Windows Only">
15
- <img src="https://img.shields.io/badge/%F0%9F%93%9C_License-MIT-1DB954?style=flat-square" alt="MIT license">
16
- </p>
17
-
18
- <p align="center">
19
- <strong>0-Delay Sync &middot; Native Windows API &middot; Glassmorphism UI &middot; Invisible Controls</strong><br>
20
- <sub>Built with Python & Windows System Media Transport Controls for 100% precision.</sub>
21
- </p>
22
-
23
- <p align="center">
24
- <img src="https://raw.githubusercontent.com/paturrr/SpoLyrics/main/assets/1.png" alt="SpoLyrics Transparent UI & Lyrics Sync">
25
- <br><br>
26
- <img src="https://raw.githubusercontent.com/paturrr/SpoLyrics/main/assets/2.png" alt="SpoLyrics Modern Settings & Shortcuts">
27
- </p>
28
-
29
- ---
30
-
31
- ## Background & Feedback
32
-
33
- I built this project to solve a specific complaint from my friends: they wanted to listen to songs on Spotify or YouTube Music while working, but the official miniplayers didn't show any lyrics.
34
-
35
- **Note:** This application is still in active development! If you encounter any bugs, have a feature request, or just want to suggest an improvement, please don't hesitate to open an issue or let me know.
36
-
37
- ---
38
-
39
- ## Key Features
40
-
41
- - **Zero-Delay Sync**
42
- 100% precise time synchronization. Pulls real-time duration data directly from the Windows OS core.
43
-
44
- - **Smart Duration Match**
45
- Automatically matches lyrics based on the duration of the currently playing song version (solving the Radio Edit vs. Album Version mismatch).
46
-
47
- - **Glassmorphism UI**
48
- Elegant, transparent, and borderless design.
49
-
50
- - **Freely Draggable**
51
- Left-click and hold anywhere to drag the lyrics window wherever you like.
52
-
53
- - **System Tray Integration**
54
- Runs quietly in the background. Right-click the system tray icon (SpoLyrics logo) to access settings, hide the lyrics, or view shortcuts.
55
-
56
- - **Advanced Customization (Settings)**
57
- Change lyrics color using a modern real-time custom HSV color picker, adjust window transparency (opacity), and enable "Auto-Start with Windows". All configurations are permanently saved.
58
-
59
- ## Invisible Controls
60
-
61
- This application uses zero conventional buttons to keep the UI perfectly clean. Use these mouse gestures:
62
-
63
- - `Right Click`: Play / Pause
64
- - `Mouse Scroll (Up/Down)`: Next / Previous track
65
- - `Middle Click (Scroll Wheel or 3-Finger Tap on Touchpad)`: Toggle Pin / Lock Window
66
- - *When locked, the resize icon changes to a padlock (🔒) and the app ignores all drags/clicks to prevent accidental touches. Middle click again to unlock.*
67
- - `Ctrl + Left Click`: Toggle song title visibility
68
- - `Ctrl + Mouse Scroll`: Increase / Decrease font size
69
- - `Drag Bottom Right Corner (⇲)`: Resize the window
70
- - `Double Left Click`: Close the application
71
-
72
- ## Installation
73
-
74
- Run this single command in your Windows Terminal or Command Prompt (Requires **Python 3.8 to 3.12**):
75
-
76
- > **Note on Python Version:** Do NOT use Python 3.13. The required `winsdk` package does not yet have pre-compiled binaries for Python 3.13 and will fail to install. Please stick to Python 3.12 or older.
77
-
78
- ```bash
79
- pip install spolyrics
80
- ```
81
- *(If you get a `pip is not recognized` error, use this alternative command instead:)*
82
- ```bash
83
- python -m pip install spolyrics
84
- ```
85
-
86
- > **IMPORTANT (OS Support):**
87
- > This app is **Exclusive to Windows**. Because it uses `winsdk` (Windows System Media Transport Controls) to achieve 0-delay performance without third-party servers, it **CANNOT** be installed on macOS or Linux.
88
-
89
- ### Alternative Installation (Manual)
90
-
91
- If you prefer to download the source code manually (e.g., as a `.zip` file) instead of using the direct `git` command above, you must install the required dependencies (the "shopping list") yourself.
92
- Open your terminal inside the `SpoLyrics` folder and run:
93
- ```bash
94
- pip install -r requirements.txt
95
- ```
96
-
97
- ## Usage
98
-
99
- Once installed, you can launch the app from any directory by typing:
100
- ```bash
101
- spolyrics
102
- ```
103
- Or, if you want it to run smoothly without spawning a black background terminal:
104
- ```bash
105
- pythonw -m main
106
- ```
107
-
108
- *(Make sure Spotify or your Windows media player is running and playing a song!)*
109
-
110
- ## Updating
111
-
112
- To update the application to the latest version, run:
113
- ```bash
114
- pip install --upgrade spolyrics
115
- ```
116
-
117
- ## Uninstallation
118
-
119
- If you wish to remove this application from your computer, simply run:
120
- ```bash
121
- pip uninstall spolyrics
122
- ```
123
-
124
- ## License
125
-
126
- This project is licensed under the [MIT License](LICENSE) - see the LICENSE file for details.
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/paturrr/SpoLyrics/main/icon.ico" width="150" alt="SpoLyrics Logo">
3
+ </p>
4
+
5
+ <h1 align="center">SpoLyrics</h1>
6
+
7
+ <p align="center">
8
+ <em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em><br>
9
+ <strong>🌐 <a href="https://paturrr.github.io/SpoLyrics/">Visit the Official Website</a></strong>
10
+ </p>
11
+
12
+ <p align="center">
13
+ <img src="https://img.shields.io/github/stars/paturrr/SpoLyrics.svg?style=flat-square&color=1DB954&label=stars&logo=github&logoColor=white" alt="Stars">
14
+ <img src="https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-1DB954?style=flat-square&logo=python&logoColor=white" alt="Python Versions">
15
+ <img src="https://img.shields.io/badge/OS-Windows%2010%2F11-1DB954?style=flat-square&logo=windows&logoColor=white" alt="Windows Only">
16
+ <img src="https://img.shields.io/badge/%F0%9F%93%9C_License-MIT-1DB954?style=flat-square" alt="MIT license">
17
+ </p>
18
+
19
+ <p align="center">
20
+ <strong>0-Delay Sync &middot; Native Windows API &middot; Glassmorphism UI &middot; Invisible Controls</strong><br>
21
+ <sub>Built with Python & Windows System Media Transport Controls for 100% precision.</sub>
22
+ </p>
23
+
24
+ <p align="center">
25
+ <img src="https://raw.githubusercontent.com/paturrr/SpoLyrics/main/assets/1.png" alt="SpoLyrics Transparent UI & Lyrics Sync">
26
+ <br><br>
27
+ <img src="https://raw.githubusercontent.com/paturrr/SpoLyrics/main/assets/2.png" alt="SpoLyrics Modern Settings & Shortcuts">
28
+ </p>
29
+
30
+ ---
31
+
32
+ ## Background & Feedback
33
+
34
+ I built this project to solve a specific complaint from my friends: they wanted to listen to songs on Spotify or YouTube Music while working, but the official miniplayers didn't show any lyrics.
35
+
36
+ **Note:** This application is still in active development! If you encounter any bugs, have a feature request, or just want to suggest an improvement, please don't hesitate to open an issue or let me know.
37
+
38
+ ---
39
+
40
+ ## Key Features
41
+
42
+ - **Zero-Delay Sync**
43
+ 100% precise time synchronization. Pulls real-time duration data directly from the Windows OS core.
44
+
45
+ - **Smart Duration Match**
46
+ Automatically matches lyrics based on the duration of the currently playing song version (solving the Radio Edit vs. Album Version mismatch).
47
+
48
+ - **Glassmorphism UI**
49
+ Elegant, transparent, and borderless design.
50
+
51
+ - **Freely Draggable**
52
+ Left-click and hold anywhere to drag the lyrics window wherever you like.
53
+
54
+ - **System Tray Integration**
55
+ Runs quietly in the background. Right-click the system tray icon (SpoLyrics logo) to access settings, hide the lyrics, or view shortcuts.
56
+
57
+ - **Advanced Customization (Settings)**
58
+ Change lyrics color using a modern real-time custom HSV color picker, adjust window transparency (opacity), and enable "Auto-Start with Windows". All configurations are permanently saved.
59
+
60
+ - **Auto Update Checker**
61
+ Automatically checks for the newest version on PyPI when you launch the app, offering a seamless one-click update and restart experience.
62
+
63
+ ## Invisible Controls
64
+
65
+ This application uses zero conventional buttons to keep the UI perfectly clean. Use these mouse gestures:
66
+
67
+ - `Right Click`: Play / Pause
68
+ - `Mouse Scroll (Up/Down)`: Next / Previous track
69
+ - `Middle Click (Scroll Wheel or 3-Finger Tap on Touchpad)`: Toggle Pin / Lock Window
70
+ - *When locked, the resize icon changes to a padlock (🔒) and the app ignores all drags/clicks to prevent accidental touches. Middle click again to unlock.*
71
+ - `Ctrl + Left Click`: Toggle song title visibility
72
+ - `Ctrl + Mouse Scroll`: Increase / Decrease font size
73
+ - `Drag Bottom Right Corner (⇲)`: Resize the window
74
+ - `Double Left Click`: Close the application
75
+
76
+ ## Installation
77
+
78
+ Run this single command in your Windows Terminal or Command Prompt (Requires **Python 3.8 to 3.12**):
79
+
80
+ > **Note on Python Version:** Do NOT use Python 3.13. The required `winsdk` package does not yet have pre-compiled binaries for Python 3.13 and will fail to install. Please stick to Python 3.12 or older.
81
+
82
+ ```bash
83
+ pip install spolyrics
84
+ ```
85
+ *(If you get a `pip is not recognized` error, use this alternative command instead:)*
86
+ ```bash
87
+ python -m pip install spolyrics
88
+ ```
89
+
90
+ > **IMPORTANT (OS Support):**
91
+ > This app is **Exclusive to Windows**. Because it uses `winsdk` (Windows System Media Transport Controls) to achieve 0-delay performance without third-party servers, it **CANNOT** be installed on macOS or Linux.
92
+
93
+ ### Alternative Installation (Manual)
94
+
95
+ If you prefer to download the source code manually (e.g., as a `.zip` file) instead of using the direct `git` command above, you must install the required dependencies (the "shopping list") yourself.
96
+ Open your terminal inside the `SpoLyrics` folder and run:
97
+ ```bash
98
+ pip install -r requirements.txt
99
+ ```
100
+
101
+ ## Usage
102
+
103
+ Once installed, you can launch the app from any directory by typing:
104
+ ```bash
105
+ spolyrics
106
+ ```
107
+ Or, if you want it to run smoothly without spawning a black background terminal:
108
+ ```bash
109
+ pythonw -m main
110
+ ```
111
+
112
+ *(Make sure Spotify or your Windows media player is running and playing a song!)*
113
+
114
+ ## Updating
115
+
116
+ To update the application to the latest version, run:
117
+ ```bash
118
+ pip install --upgrade spolyrics
119
+ ```
120
+
121
+ ## Uninstallation
122
+
123
+ If you wish to remove this application from your computer, simply run:
124
+ ```bash
125
+ pip uninstall spolyrics
126
+ ```
127
+
128
+ ## License
129
+
130
+ This project is licensed under the [MIT License](LICENSE) - see the LICENSE file for details.
@@ -12,8 +12,10 @@ import logging
12
12
  import sqlite3
13
13
  from datetime import datetime, timezone
14
14
  from assets import ICON_B64
15
+ from tkinter import messagebox
15
16
  from winsdk.windows.media.control import GlobalSystemMediaTransportControlsSessionManager as MediaManager
16
17
 
18
+ CURRENT_VERSION = "1.2.2"
17
19
  CONFIG_PATH = os.path.join(os.environ.get("APPDATA", ""), "SpoLyrics", "config.json")
18
20
  APP_DIR = os.path.join(os.environ.get("APPDATA", ""), "SpoLyrics")
19
21
  os.makedirs(APP_DIR, exist_ok=True)
@@ -85,19 +87,25 @@ def save_config(config):
85
87
  with open(CONFIG_PATH, 'w') as f:
86
88
  json.dump(config, f)
87
89
 
88
- def set_auto_start(enable):
90
+ def get_exe_path():
91
+ import sys
92
+ if getattr(sys, 'frozen', False):
93
+ return sys.executable
94
+ script_exe = os.path.join(sys.prefix, 'Scripts', 'spolyrics.exe')
95
+ if os.path.exists(script_exe):
96
+ return script_exe
97
+ return sys.executable
98
+
99
+ def set_auto_start(enable, force_update=False):
89
100
  try:
90
101
  if enable:
91
102
  app_dir = os.path.join(os.environ.get("APPDATA", ""), "SpoLyrics")
92
103
  icon_path = os.path.join(app_dir, 'icon.ico')
93
- import sys
94
- if getattr(sys, 'frozen', False):
95
- exe_path = sys.executable
96
- else:
97
- exe_path = shutil.which('spolyrics') or sys.executable
104
+ exe_path = get_exe_path()
98
105
  if exe_path and os.path.exists(icon_path):
99
- ps_script = f"$s=(New-Object -COM WScript.Shell).CreateShortcut('{STARTUP_SHORTCUT}');$s.TargetPath='{exe_path}';$s.IconLocation='{icon_path}';$s.Save()"
100
- subprocess.run(["powershell", "-Command", ps_script], creationflags=0x08000000)
106
+ if force_update or not os.path.exists(STARTUP_SHORTCUT):
107
+ ps_script = f"$s=(New-Object -COM WScript.Shell).CreateShortcut('{STARTUP_SHORTCUT}');$s.TargetPath='{exe_path}';$s.IconLocation='{icon_path}';$s.Save()"
108
+ subprocess.run(["powershell", "-Command", ps_script], creationflags=0x08000000)
101
109
  else:
102
110
  if os.path.exists(STARTUP_SHORTCUT):
103
111
  os.remove(STARTUP_SHORTCUT)
@@ -130,7 +138,7 @@ class MiniLyrics:
130
138
  self.is_pinned = False
131
139
  self.show_title = True
132
140
 
133
- self.lbl_title = tk.Label(self.root, text="", fg='#a0a0a0', bg='#191414', font=('Arial', 8, 'bold'), justify='center', wraplength=320)
141
+ self.lbl_title = tk.Label(self.root, text="", fg='#a0a0a0', bg='#191414', font=('Segoe UI', 8, 'bold'), justify='center', wraplength=320)
134
142
  self.lbl_title.place(relx=0.5, y=8, anchor='n')
135
143
 
136
144
  self.lbl_current = tk.Label(self.root, text="Waiting for song...", fg=self.config['color'], bg='#191414', font=('Arial', self.font_cur, 'bold'), wraplength=330, justify="center")
@@ -138,11 +146,16 @@ class MiniLyrics:
138
146
 
139
147
  self.lbl_next = tk.Label(self.root, text="", fg='#b3b3b3', bg='#191414', font=('Arial', self.font_nxt), wraplength=330, justify="center")
140
148
  self.lbl_next.pack(expand=True, fill='both', padx=10, pady=(0, 15))
149
+
150
+ self.update_available = False
151
+ self.root.after(2000, self.check_updates)
141
152
 
142
153
  self.grip = tk.Label(self.root, text="⇲", fg='#333333', bg='#191414', cursor="size_nw_se")
143
154
  self.grip.place(relx=1.0, rely=1.0, anchor="se")
144
155
 
145
156
  self.grip.bind("<B1-Motion>", self.on_resize)
157
+ self.grip.bind("<Enter>", lambda e: not self.is_pinned and self.grip.config(fg='#888888'))
158
+ self.grip.bind("<Leave>", lambda e: not self.is_pinned and self.grip.config(fg='#333333'))
146
159
  self.root.bind("<B1-Motion>", self.drag)
147
160
  self.root.bind("<Button-1>", self.click)
148
161
  self.root.bind("<Double-1>", lambda e: not self.is_pinned and self.root.destroy())
@@ -248,6 +261,16 @@ class MiniLyrics:
248
261
 
249
262
  def on_scroll(self, event):
250
263
  if self.is_pinned: return "break"
264
+
265
+ now = time.time()
266
+ if not hasattr(self, 'last_scroll_time'):
267
+ self.last_scroll_time = 0
268
+
269
+ if now - self.last_scroll_time < 0.8:
270
+ return "break"
271
+
272
+ self.last_scroll_time = now
273
+
251
274
  if event.delta > 0: self.media_control('prev')
252
275
  else: self.media_control('next')
253
276
  return "break"
@@ -270,7 +293,7 @@ class MiniLyrics:
270
293
 
271
294
  self.info_win = tk.Toplevel(self.root)
272
295
  self.info_win.title("SpoLyrics Info")
273
- self.info_win.geometry("340x400")
296
+ self.info_win.geometry("420x400")
274
297
  self.info_win.configure(bg='#191414')
275
298
  self.info_win.attributes('-topmost', True)
276
299
  self.info_win.overrideredirect(True)
@@ -337,7 +360,7 @@ class MiniLyrics:
337
360
 
338
361
  self.settings_win = tk.Toplevel(self.root)
339
362
  self.settings_win.title("SpoLyrics Settings")
340
- self.settings_win.geometry("380x410")
363
+ self.settings_win.geometry("380x520")
341
364
  self.settings_win.configure(bg='#191414')
342
365
  self.settings_win.attributes('-topmost', True)
343
366
  self.settings_win.resizable(False, False)
@@ -350,27 +373,29 @@ class MiniLyrics:
350
373
  self.settings_win.destroy()
351
374
  self.settings_win.protocol("WM_DELETE_WINDOW", on_settings_close)
352
375
 
353
- tk.Label(self.settings_win, text="SpoLyrics Settings", fg='white', bg='#191414', font=('Segoe UI', 16, 'bold')).pack(pady=(20, 15))
376
+ header_frame = tk.Frame(self.settings_win, bg='#191414')
377
+ header_frame.pack(fill='x', padx=25, pady=(20, 15))
378
+ tk.Label(header_frame, text="Settings", fg='white', bg='#191414', font=('Segoe UI', 18, 'bold')).pack(side='left')
354
379
 
355
- info_lbl = tk.Label(self.settings_win, text="❔", fg='#888888', bg='#191414', font=('Segoe UI', 12), cursor='hand2')
356
- info_lbl.place(x=340, y=20)
380
+ info_lbl = tk.Label(header_frame, text="❔", fg='#888888', bg='#191414', font=('Segoe UI', 12), cursor='hand2')
381
+ info_lbl.pack(side='right', pady=(5,0))
357
382
  info_lbl.bind("<Button-1>", lambda e: self.open_info())
358
383
  info_lbl.bind("<Enter>", lambda e: info_lbl.config(fg='white'))
359
384
  info_lbl.bind("<Leave>", lambda e: info_lbl.config(fg='#888888'))
360
385
 
361
- # --- Color Picker ---
362
- color_frame = tk.Frame(self.settings_win, bg='#191414')
363
- color_frame.pack(fill='x', padx=30, pady=5)
386
+ tk.Label(self.settings_win, text="APPEARANCE", fg='#888888', bg='#191414', font=('Segoe UI', 8, 'bold')).pack(anchor='w', padx=25, pady=(0, 5))
387
+ app_card = tk.Frame(self.settings_win, bg='#242424')
388
+ app_card.pack(fill='x', padx=25, pady=(0, 15))
364
389
 
365
- tk.Label(color_frame, text="Lyrics Color", fg='#b3b3b3', bg='#191414', font=('Segoe UI', 11, 'bold')).pack(side='left')
390
+ color_frame = tk.Frame(app_card, bg='#242424')
391
+ color_frame.pack(fill='x', padx=15, pady=12)
392
+ tk.Label(color_frame, text="Lyrics Color", fg='#e0e0e0', bg='#242424', font=('Segoe UI', 10)).pack(side='left')
366
393
 
367
394
  self.current_hex = self.config['color']
368
-
369
- color_btn = tk.Frame(color_frame, bg=self.current_hex, width=50, height=25, cursor='hand2', highlightbackground='#333333', highlightthickness=1)
395
+ color_btn = tk.Frame(color_frame, bg=self.current_hex, width=24, height=24, cursor='hand2', highlightbackground='#555', highlightthickness=1)
370
396
  color_btn.pack(side='right')
371
397
 
372
- # --- Ultra Modern Inline Sliders ---
373
- palette_frame = tk.Frame(self.settings_win, bg='#191414')
398
+ palette_frame = tk.Frame(app_card, bg='#1e1e1e')
374
399
  self.palette_visible = False
375
400
 
376
401
  import colorsys
@@ -378,8 +403,8 @@ class MiniLyrics:
378
403
  rgb = colorsys.hls_to_rgb(h, l, s)
379
404
  return "#{:02x}{:02x}{:02x}".format(int(rgb[0]*255), int(rgb[1]*255), int(rgb[2]*255))
380
405
 
381
- tk.Label(palette_frame, text="Hue (Warna):", fg='#888', bg='#191414', font=('Segoe UI', 9)).pack(anchor='w', padx=5)
382
- hue_canvas = tk.Canvas(palette_frame, width=240, height=12, bg='#191414', highlightthickness=0, cursor='hand2')
406
+ tk.Label(palette_frame, text="Hue:", fg='#888', bg='#1e1e1e', font=('Segoe UI', 8)).pack(anchor='w', padx=15, pady=(10,2))
407
+ hue_canvas = tk.Canvas(palette_frame, width=240, height=12, bg='#1e1e1e', highlightthickness=0, cursor='hand2')
383
408
  hue_canvas.pack(pady=(0, 5))
384
409
 
385
410
  for i in range(120):
@@ -387,9 +412,9 @@ class MiniLyrics:
387
412
  hue_canvas.create_rectangle(i*2, 0, (i+1)*2, 12, fill=c, outline=c)
388
413
  hue_thumb = hue_canvas.create_rectangle(0, 0, 4, 12, fill='white', outline='black')
389
414
 
390
- tk.Label(palette_frame, text="Lightness (Kecerahan):", fg='#888', bg='#191414', font=('Segoe UI', 9)).pack(anchor='w', padx=5)
391
- lit_canvas = tk.Canvas(palette_frame, width=240, height=12, bg='#191414', highlightthickness=0, cursor='hand2')
392
- lit_canvas.pack(pady=(0, 5))
415
+ tk.Label(palette_frame, text="Lightness:", fg='#888', bg='#1e1e1e', font=('Segoe UI', 8)).pack(anchor='w', padx=15, pady=(5,2))
416
+ lit_canvas = tk.Canvas(palette_frame, width=240, height=12, bg='#1e1e1e', highlightthickness=0, cursor='hand2')
417
+ lit_canvas.pack(pady=(0, 10))
393
418
 
394
419
  lit_rects = []
395
420
  for i in range(120):
@@ -397,10 +422,10 @@ class MiniLyrics:
397
422
  lit_rects.append(r)
398
423
  lit_thumb = lit_canvas.create_rectangle(120, 0, 124, 12, fill='black', outline='white')
399
424
 
400
- custom_frame = tk.Frame(palette_frame, bg='#191414')
401
- custom_frame.pack(pady=(5,0))
402
- tk.Label(custom_frame, text="Hex:", fg='#888', bg='#191414', font=('Segoe UI', 9)).pack(side='left')
403
- hex_entry = tk.Entry(custom_frame, width=9, bg='#282828', fg='white', insertbackground='white', relief='flat', justify='center')
425
+ custom_frame = tk.Frame(palette_frame, bg='#1e1e1e')
426
+ custom_frame.pack(pady=(0, 15))
427
+ tk.Label(custom_frame, text="Hex:", fg='#888', bg='#1e1e1e', font=('Segoe UI', 9)).pack(side='left')
428
+ hex_entry = tk.Entry(custom_frame, width=9, bg='#333', fg='white', insertbackground='white', relief='flat', justify='center')
404
429
  hex_entry.insert(0, self.current_hex)
405
430
  hex_entry.pack(side='left', padx=5)
406
431
 
@@ -446,41 +471,39 @@ class MiniLyrics:
446
471
 
447
472
  def toggle_palette(e=None):
448
473
  if not self.palette_visible:
449
- self.settings_win.geometry("380x540")
474
+ self.settings_win.geometry("380x680")
450
475
  update_lit_canvas()
451
- palette_frame.pack(fill='x', padx=30, after=color_frame)
476
+ palette_frame.pack(fill='x', after=color_frame)
452
477
  hex_entry.delete(0, 'end')
453
478
  hex_entry.insert(0, self.current_hex)
454
479
  else:
455
- self.settings_win.geometry("380x410")
480
+ self.settings_win.geometry("380x520")
456
481
  palette_frame.pack_forget()
457
482
  self.palette_visible = not self.palette_visible
458
483
 
459
484
  color_btn.bind("<Button-1>", toggle_palette)
460
485
 
461
- # --- Opacity Slider ---
462
- op_frame = tk.Frame(self.settings_win, bg='#191414')
463
- op_frame.pack(fill='x', padx=30, pady=(15, 0))
486
+ tk.Frame(app_card, height=1, bg='#333').pack(fill='x', padx=15)
464
487
 
465
- tk.Label(op_frame, text="Window Opacity", fg='#b3b3b3', bg='#191414', font=('Segoe UI', 11, 'bold')).pack(side='left')
466
- tk.Label(op_frame, text="(Normal: 85%)", fg='#666666', bg='#191414', font=('Segoe UI', 9)).pack(side='left', padx=5, pady=(2,0))
488
+ op_frame = tk.Frame(app_card, bg='#242424')
489
+ op_frame.pack(fill='x', padx=15, pady=12)
490
+ tk.Label(op_frame, text="Window Opacity", fg='#e0e0e0', bg='#242424', font=('Segoe UI', 10)).pack(side='left')
467
491
 
468
492
  self.cur_op = self.config.get('opacity', 0.85)
469
- op_val_lbl = tk.Label(op_frame, text=f"{int(self.cur_op*100)}%", fg='white', bg='#191414', font=('Segoe UI', 10, 'bold'))
493
+ op_val_lbl = tk.Label(op_frame, text=f"{int(self.cur_op*100)}%", fg='#888', bg='#242424', font=('Segoe UI', 9, 'bold'))
470
494
  op_val_lbl.pack(side='right')
471
495
 
472
- op_canvas = tk.Canvas(self.settings_win, width=320, height=14, bg='#191414', highlightthickness=0, cursor='hand2')
473
- op_canvas.pack(pady=(5, 15))
474
-
475
- op_canvas.create_rectangle(0, 5, 320, 9, fill='#333333', outline='#333333')
476
- op_fill = op_canvas.create_rectangle(0, 5, int(self.cur_op*320), 9, fill='#1DB954', outline='#1DB954')
477
- th_x = int(self.cur_op*320)
496
+ op_canvas = tk.Canvas(app_card, width=330, height=14, bg='#242424', highlightthickness=0, cursor='hand2')
497
+ op_canvas.pack(pady=(0, 15))
498
+ op_canvas.create_rectangle(20, 5, 310, 9, fill='#333', outline='#333', width=0)
499
+ op_fill = op_canvas.create_rectangle(20, 5, int(self.cur_op*290)+20, 9, fill='#1DB954', outline='#1DB954', width=0)
500
+ th_x = int(self.cur_op*290)+20
478
501
  op_thumb = op_canvas.create_oval(th_x-6, 1, th_x+6, 13, fill='white', outline='white')
479
502
 
480
503
  def on_op_drag(e):
481
- x = max(32, min(e.x, 320)) # min 10% opacity
482
- self.cur_op = x / 320.0
483
- op_canvas.coords(op_fill, 0, 5, x, 9)
504
+ x = max(20+29, min(e.x, 310))
505
+ self.cur_op = (x-20) / 290.0
506
+ op_canvas.coords(op_fill, 20, 5, x, 9)
484
507
  op_canvas.coords(op_thumb, x-6, 1, x+6, 13)
485
508
  op_val_lbl.config(text=f"{int(self.cur_op*100)}%")
486
509
  self.root.attributes('-alpha', self.cur_op)
@@ -488,20 +511,22 @@ class MiniLyrics:
488
511
  op_canvas.bind("<B1-Motion>", on_op_drag)
489
512
  op_canvas.bind("<Button-1>", on_op_drag)
490
513
 
491
- # --- Auto Start Toggle ---
492
- toggle_frame = tk.Frame(self.settings_win, bg='#191414')
493
- toggle_frame.pack(fill='x', padx=30, pady=5)
514
+ tk.Label(self.settings_win, text="SYSTEM", fg='#888888', bg='#191414', font=('Segoe UI', 8, 'bold')).pack(anchor='w', padx=25, pady=(5, 5))
515
+ sys_card = tk.Frame(self.settings_win, bg='#242424')
516
+ sys_card.pack(fill='x', padx=25, pady=(0, 15))
494
517
 
495
- tk.Label(toggle_frame, text="Auto-start Windows", fg='#b3b3b3', bg='#191414', font=('Segoe UI', 11, 'bold')).pack(side='left')
518
+ toggle_frame = tk.Frame(sys_card, bg='#242424')
519
+ toggle_frame.pack(fill='x', padx=15, pady=12)
520
+ tk.Label(toggle_frame, text="Auto-start Windows", fg='#e0e0e0', bg='#242424', font=('Segoe UI', 10)).pack(side='left')
496
521
 
497
522
  class ToggleSwitch(tk.Canvas):
498
523
  def __init__(self, parent, initial_state=False, *args, **kwargs):
499
- tk.Canvas.__init__(self, parent, width=46, height=24, bg='#191414', highlightthickness=0, cursor='hand2', *args, **kwargs)
524
+ tk.Canvas.__init__(self, parent, width=40, height=20, bg='#242424', highlightthickness=0, cursor='hand2', *args, **kwargs)
500
525
  self.is_on = initial_state
501
- self.track = self.create_oval(2, 2, 22, 22, fill="#555", outline="#555")
502
- self.track2 = self.create_oval(24, 2, 44, 22, fill="#555", outline="#555")
503
- self.rect = self.create_rectangle(12, 2, 34, 22, fill="#555", outline="#555")
504
- self.thumb = self.create_oval(4, 4, 20, 20, fill="white", outline="white")
526
+ self.track = self.create_oval(2, 2, 18, 18, fill="#555", outline="#555")
527
+ self.track2 = self.create_oval(22, 2, 38, 18, fill="#555", outline="#555")
528
+ self.rect = self.create_rectangle(10, 2, 30, 18, fill="#555", outline="#555")
529
+ self.thumb = self.create_oval(4, 4, 16, 16, fill="white", outline="white")
505
530
  self.bind("<Button-1>", self.toggle)
506
531
  self._update_ui()
507
532
 
@@ -514,35 +539,35 @@ class MiniLyrics:
514
539
  self.itemconfig(self.track, fill="#1DB954", outline="#1DB954")
515
540
  self.itemconfig(self.track2, fill="#1DB954", outline="#1DB954")
516
541
  self.itemconfig(self.rect, fill="#1DB954", outline="#1DB954")
517
- self.coords(self.thumb, 26, 4, 42, 20)
542
+ self.coords(self.thumb, 24, 4, 36, 16)
518
543
  else:
519
- self.itemconfig(self.track, fill="#555555", outline="#555555")
520
- self.itemconfig(self.track2, fill="#555555", outline="#555555")
521
- self.itemconfig(self.rect, fill="#555555", outline="#555555")
522
- self.coords(self.thumb, 4, 4, 20, 20)
544
+ self.itemconfig(self.track, fill="#555", outline="#555")
545
+ self.itemconfig(self.track2, fill="#555", outline="#555")
546
+ self.itemconfig(self.rect, fill="#555", outline="#555")
547
+ self.coords(self.thumb, 4, 4, 16, 16)
523
548
 
524
549
  toggle = ToggleSwitch(toggle_frame, initial_state=self.config['auto_start'])
525
550
  toggle.pack(side='right')
526
-
527
- # --- Cache Info ---
528
- cache_frame = tk.Frame(self.settings_win, bg='#191414')
529
- cache_frame.pack(fill='x', padx=30, pady=5)
530
551
 
531
- tk.Label(cache_frame, text="Lyrics Cache", fg='#b3b3b3', bg='#191414', font=('Segoe UI', 11, 'bold')).pack(side='left')
552
+ tk.Frame(sys_card, height=1, bg='#333').pack(fill='x', padx=15)
553
+
554
+ cache_frame = tk.Frame(sys_card, bg='#242424')
555
+ cache_frame.pack(fill='x', padx=15, pady=12)
556
+ tk.Label(cache_frame, text="Lyrics Cache", fg='#e0e0e0', bg='#242424', font=('Segoe UI', 10)).pack(side='left')
532
557
 
533
558
  def get_db_size():
534
559
  try:
535
560
  if os.path.exists(CACHE_DB_PATH):
536
561
  size_bytes = os.path.getsize(CACHE_DB_PATH)
537
- if size_bytes >= 1024 * 1024:
538
- return f"{size_bytes / (1024 * 1024):.2f} MB"
539
- else:
540
- return f"{size_bytes / 1024:.2f} KB"
541
- except Exception:
542
- pass
562
+ if size_bytes >= 1024 * 1024: return f"{size_bytes / (1024 * 1024):.2f} MB"
563
+ else: return f"{size_bytes / 1024:.2f} KB"
564
+ except Exception: pass
543
565
  return "0.00 KB"
544
566
 
545
- cache_size_lbl = tk.Label(cache_frame, text=get_db_size(), fg='white', bg='#191414', font=('Segoe UI', 10))
567
+ del_btn = tk.Label(cache_frame, text="Clear", bg='#333', fg='#ff4444', font=('Segoe UI', 8, 'bold'), padx=8, pady=2, cursor='hand2')
568
+ del_btn.pack(side='right')
569
+
570
+ cache_size_lbl = tk.Label(cache_frame, text=get_db_size(), fg='#888', bg='#242424', font=('Segoe UI', 9))
546
571
  cache_size_lbl.pack(side='right', padx=(0, 10))
547
572
 
548
573
  def delete_cache(e=None):
@@ -558,14 +583,10 @@ class MiniLyrics:
558
583
  except Exception as ex:
559
584
  logging.error("Failed to delete cache", exc_info=ex)
560
585
 
561
- del_btn = tk.Label(cache_frame, text="Clear", bg='#e91429', fg='white', font=('Segoe UI', 9, 'bold'), padx=10, pady=2, cursor='hand2')
562
- del_btn.pack(side='right')
563
586
  del_btn.bind("<Button-1>", delete_cache)
564
-
565
- # --- Separator ---
566
- tk.Frame(self.settings_win, height=1, bg='#282828').pack(fill='x', padx=30, pady=10)
587
+ del_btn.bind("<Enter>", lambda e: del_btn.config(bg='#ff4444', fg='white'))
588
+ del_btn.bind("<Leave>", lambda e: del_btn.config(bg='#333', fg='#ff4444'))
567
589
 
568
- # --- Save Button ---
569
590
  def save_and_apply(e=None):
570
591
  self.config['color'] = self.current_hex
571
592
  self.config['auto_start'] = toggle.is_on
@@ -580,15 +601,12 @@ class MiniLyrics:
580
601
  self.settings_win.protocol("WM_DELETE_WINDOW", lambda: None)
581
602
  self.root.after(100, lambda: self.settings_win.destroy() if self.settings_win.winfo_exists() else None)
582
603
 
583
- def on_enter(e): save_btn.config(bg='#1ed760')
584
- def on_leave(e): save_btn.config(bg='#1DB954')
585
-
586
- save_btn = tk.Label(self.settings_win, text="Save & Apply", bg='#1DB954', fg='white', font=('Segoe UI', 11, 'bold'), pady=8, cursor='hand2')
587
- save_btn.pack(fill='x', padx=40, pady=(0, 20))
604
+ save_btn = tk.Label(self.settings_win, text="Save & Apply", bg='#1DB954', fg='white', font=('Segoe UI', 10, 'bold'), pady=8, cursor='hand2')
605
+ save_btn.pack(fill='x', padx=25, pady=(5, 20))
588
606
 
589
607
  save_btn.bind("<Button-1>", save_and_apply)
590
- save_btn.bind("<Enter>", on_enter)
591
- save_btn.bind("<Leave>", on_leave)
608
+ save_btn.bind("<Enter>", lambda e: save_btn.config(bg='#1ed760'))
609
+ save_btn.bind("<Leave>", lambda e: save_btn.config(bg='#1DB954'))
592
610
 
593
611
  def media_control(self, action):
594
612
  if self.is_pinned: return "break"
@@ -706,7 +724,7 @@ class MiniLyrics:
706
724
  if song_id != self.current_song:
707
725
  self.current_song = song_id
708
726
  self.root.after(0, lambda sid=song_id: self.lbl_title.config(text=sid if self.show_title else ""))
709
- self.root.after(0, self.update_ui, f"Matching smart lyrics...\n{song_id}", "")
727
+ self.root.after(0, self.update_ui, "Loading lyrics... ", "")
710
728
 
711
729
  async def fetch_and_apply(sid, t, a, d):
712
730
  await asyncio.sleep(0.5)
@@ -724,7 +742,7 @@ class MiniLyrics:
724
742
  if self.current_song:
725
743
  self.current_song = ""
726
744
  self.synced_lyrics = []
727
- self.root.after(0, self.update_ui, "Tidak ada lagu diputar.", "")
745
+ self.root.after(0, self.update_ui, "No song playing.", "")
728
746
 
729
747
  except Exception as e:
730
748
  logging.error("Error in poll_song loop", exc_info=e)
@@ -733,7 +751,47 @@ class MiniLyrics:
733
751
 
734
752
  asyncio.run(main_loop())
735
753
 
754
+ def check_updates(self):
755
+ def _check():
756
+ try:
757
+ resp = requests.get('https://pypi.org/pypi/spolyrics/json', timeout=5).json()
758
+ latest = resp['info']['version']
759
+ if latest != CURRENT_VERSION:
760
+ self.root.after(0, self.show_update_prompt, latest)
761
+ except Exception as e:
762
+ logging.error("Failed to check for updates", exc_info=e)
763
+ threading.Thread(target=_check, daemon=True).start()
764
+
765
+ def prompt_update(self):
766
+ if self.update_available:
767
+ self.show_update_prompt(self.update_available)
768
+
769
+ def show_update_prompt(self, latest):
770
+ self.update_available = latest
771
+ if getattr(self, 'tray_icon', None):
772
+ self.tray_icon.update_menu()
773
+
774
+ res = messagebox.askyesno("Update Available", f"A new version of SpoLyrics (v{latest}) is available!\n\nYou are currently on v{CURRENT_VERSION}.\nWould you like to update now?")
775
+ if res:
776
+ import tempfile
777
+ bat_path = os.path.join(tempfile.gettempdir(), "update_spolyrics.bat")
778
+ exe_path = get_exe_path()
779
+ with open(bat_path, 'w') as f:
780
+ f.write('@echo off\n')
781
+ f.write('echo Updating SpoLyrics...\n')
782
+ f.write('ping 127.0.0.1 -n 3 > nul\n')
783
+ f.write('python -m pip install --upgrade spolyrics\n')
784
+ f.write(f'start "" "{exe_path}"\n')
785
+ f.write('del "%~f0"\n')
786
+ subprocess.Popen(['cmd.exe', '/c', bat_path], creationflags=16)
787
+ if getattr(self, 'tray_icon', None):
788
+ self.tray_icon.stop()
789
+ self.root.quit()
790
+ import sys
791
+ sys.exit(0)
792
+
736
793
  def create_shortcut():
794
+ path_changed = False
737
795
  try:
738
796
  shortcut_path = os.path.join(os.environ.get("APPDATA", ""), "Microsoft", "Windows", "Start Menu", "Programs", "SpoLyrics.lnk")
739
797
  app_dir = os.path.join(os.environ.get("APPDATA", ""), "SpoLyrics")
@@ -750,17 +808,26 @@ def create_shortcut():
750
808
  if not os.path.exists(shortcut_path):
751
809
  needs_update = True
752
810
 
811
+ exe_path = get_exe_path()
812
+ path_file = os.path.join(app_dir, 'current_exe_path.txt')
813
+ saved_path = ""
814
+ if os.path.exists(path_file):
815
+ with open(path_file, 'r') as f:
816
+ saved_path = f.read().strip()
817
+
818
+ if saved_path != exe_path:
819
+ needs_update = True
820
+ path_changed = True
821
+ with open(path_file, 'w') as f:
822
+ f.write(exe_path)
823
+
753
824
  if needs_update:
754
- import sys
755
- if getattr(sys, 'frozen', False):
756
- exe_path = sys.executable
757
- else:
758
- exe_path = shutil.which('spolyrics') or sys.executable
759
825
  if exe_path:
760
826
  ps_script = f"$s=(New-Object -COM WScript.Shell).CreateShortcut('{shortcut_path}');$s.TargetPath='{exe_path}';$s.IconLocation='{icon_path}';$s.Save()"
761
827
  subprocess.run(["powershell", "-Command", ps_script], creationflags=0x08000000)
762
828
  except Exception as e:
763
829
  logging.error("Failed to create shortcut", exc_info=e)
830
+ return path_changed
764
831
 
765
832
  def setup_tray(app):
766
833
  try:
@@ -789,27 +856,54 @@ def setup_tray(app):
789
856
  logging.error("Failed to open tray icon image, using fallback", exc_info=e)
790
857
  image = Image.new('RGB', (64, 64), color = (29, 185, 84))
791
858
 
792
- menu = pystray.Menu(
793
- pystray.MenuItem("Shortcuts & Info", on_info),
794
- pystray.MenuItem("Settings", on_settings),
795
- pystray.MenuItem("Toggle Visibility", on_toggle_lyrics),
796
- pystray.MenuItem("Quit", on_quit)
797
- )
798
-
799
- icon = pystray.Icon("SpoLyrics", image, "SpoLyrics", menu)
859
+ def get_menu():
860
+ items = [
861
+ pystray.MenuItem("Shortcuts & Info", on_info),
862
+ pystray.MenuItem("Settings", on_settings),
863
+ pystray.MenuItem("Toggle Visibility", on_toggle_lyrics),
864
+ pystray.MenuItem("Quit", on_quit),
865
+ pystray.MenuItem(f"v{CURRENT_VERSION}", lambda: None, enabled=False)
866
+ ]
867
+ if getattr(app, 'update_available', False):
868
+ items.insert(0, pystray.MenuItem("🔥 Update Available!", lambda: app.root.after(0, app.prompt_update)))
869
+ return pystray.Menu(*items)
870
+
871
+ icon = pystray.Icon("SpoLyrics", image, "SpoLyrics", menu=get_menu)
872
+ app.tray_icon = icon
800
873
  threading.Thread(target=icon.run, daemon=True).start()
801
874
  except Exception as e:
802
875
  logging.error("Tray icon failed to load", exc_info=e)
803
876
 
877
+ def check_single_instance():
878
+ mutex_name = "Global\\SpoLyrics_Mutex"
879
+ mutex = ctypes.windll.kernel32.CreateMutexW(None, False, mutex_name)
880
+ last_error = ctypes.windll.kernel32.GetLastError()
881
+
882
+ if last_error == 183:
883
+ from tkinter import messagebox
884
+ import sys
885
+ temp_root = tk.Tk()
886
+ temp_root.withdraw()
887
+ temp_root.attributes('-topmost', True)
888
+ res = messagebox.askyesno("Already Running", "SpoLyrics is already running in the background.\n\nAre you sure you want to open another instance?", parent=temp_root)
889
+ temp_root.destroy()
890
+ if not res:
891
+ sys.exit(0)
892
+ return mutex
893
+
804
894
  def start_app():
895
+ global _app_mutex
896
+ _app_mutex = check_single_instance()
897
+
805
898
  try:
806
899
  ctypes.windll.shcore.SetProcessDpiAwareness(1)
807
900
  except Exception as e:
808
901
  logging.error("Failed to set DPI awareness", exc_info=e)
902
+ logging.error("Failed to set DPI awareness", exc_info=e)
809
903
 
810
- create_shortcut()
904
+ path_changed = create_shortcut()
811
905
  config = load_config()
812
- set_auto_start(config['auto_start'])
906
+ set_auto_start(config['auto_start'], force_update=path_changed)
813
907
 
814
908
  app = MiniLyrics(config)
815
909
  setup_tray(app)
@@ -2,7 +2,7 @@ from setuptools import setup
2
2
 
3
3
  setup(
4
4
  name='spolyrics',
5
- version='1.2.0',
5
+ version='1.2.2',
6
6
  description='A minimalist, borderless, zero-delay synced lyrics miniplayer for Spotify on Windows.',
7
7
  long_description=open('README.md', 'r', encoding='utf-8').read(),
8
8
  long_description_content_type='text/markdown',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spolyrics
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: A minimalist, borderless, zero-delay synced lyrics miniplayer for Spotify on Windows.
5
5
  Author: SpoLyrics Team
6
6
  Requires-Python: >=3.8
@@ -25,7 +25,8 @@ Dynamic: summary
25
25
  <h1 align="center">SpoLyrics</h1>
26
26
 
27
27
  <p align="center">
28
- <em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em>
28
+ <em>A minimalist, transparent, and zero-delay Spotify lyrics miniplayer.</em><br>
29
+ <strong>🌐 <a href="https://paturrr.github.io/SpoLyrics/">Visit the Official Website</a></strong>
29
30
  </p>
30
31
 
31
32
  <p align="center">
@@ -75,6 +76,9 @@ I built this project to solve a specific complaint from my friends: they wanted
75
76
 
76
77
  - **Advanced Customization (Settings)**
77
78
  Change lyrics color using a modern real-time custom HSV color picker, adjust window transparency (opacity), and enable "Auto-Start with Windows". All configurations are permanently saved.
79
+
80
+ - **Auto Update Checker**
81
+ Automatically checks for the newest version on PyPI when you launch the app, offering a seamless one-click update and restart experience.
78
82
 
79
83
  ## Invisible Controls
80
84
 
File without changes
File without changes