From 3dc7882a99018e1ac5be78ecf57d981face39c5b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Jul 2026 18:57:22 +0000 Subject: [PATCH 1/5] fix(ledmatrix-music): clip scrolling text to viewport (fix 64px overflow) + GPL-3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The now-playing title/artist/album marquee drew the full (rotated) string at the text-area x with no right-edge clip, so long text ran past the panel's right edge. The safety harness flagged this as an overflow on the 64px-wide panels (64x32 / 64x64), where the text area is only ~29px wide. Add _clip_text_to_width() and apply it to all four text draws so the marquee stays inside its viewport: long text still scrolls (the char-based rotation is unchanged) — it's just clipped on the right instead of overflowing the panel. Also relicense to GPL-3.0 (LICENSE + manifest + README). music was the plugin deferred from the repo-wide MIT -> GPL-3.0 pass because of this overflow. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01F9Saiat9CQmNi3DeDdvacB --- plugins.json | 6 +++--- plugins/ledmatrix-music/LICENSE | 30 ++++++++++++--------------- plugins/ledmatrix-music/README.md | 2 +- plugins/ledmatrix-music/manager.py | 26 +++++++++++++++++++---- plugins/ledmatrix-music/manifest.json | 12 ++++++++--- 5 files changed, 48 insertions(+), 28 deletions(-) diff --git a/plugins.json b/plugins.json index c54ca1a4..b2a460d6 100644 --- a/plugins.json +++ b/plugins.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "last_updated": "2026-07-08", + "last_updated": "2026-07-09", "plugins": [ { "id": "7-segment-clock", @@ -475,10 +475,10 @@ "plugin_path": "plugins/ledmatrix-music", "stars": 0, "downloads": 0, - "last_updated": "2026-05-15", + "last_updated": "2026-07-08", "verified": true, "screenshot": "", - "latest_version": "1.0.6" + "latest_version": "1.0.7" }, { "id": "news", diff --git a/plugins/ledmatrix-music/LICENSE b/plugins/ledmatrix-music/LICENSE index b5a96de3..e653a0c1 100644 --- a/plugins/ledmatrix-music/LICENSE +++ b/plugins/ledmatrix-music/LICENSE @@ -1,21 +1,17 @@ -MIT License +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 -Copyright (c) 2025 ChuckBuilds +Copyright (C) 2025 LEDMatrix Team -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: +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. -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. +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/plugins/ledmatrix-music/README.md b/plugins/ledmatrix-music/README.md index dc6073da..d0b08e7c 100644 --- a/plugins/ledmatrix-music/README.md +++ b/plugins/ledmatrix-music/README.md @@ -307,7 +307,7 @@ For issues, feature requests, or questions: ## License -This plugin is licensed under the MIT License. See the LICENSE file for details. +This plugin is licensed under the GNU General Public License v3.0. See the [LICENSE](LICENSE) file for details. ## Contributing diff --git a/plugins/ledmatrix-music/manager.py b/plugins/ledmatrix-music/manager.py index 2a7a2508..ba8fd697 100644 --- a/plugins/ledmatrix-music/manager.py +++ b/plugins/ledmatrix-music/manager.py @@ -733,6 +733,24 @@ def update(self) -> None: if not self.poll_thread or not self.poll_thread.is_alive(): self.start_polling() + def _clip_text_to_width(self, text, font, max_width): + """Trim trailing characters so the rendered text fits within max_width px. + + The scrolling marquee rotates which characters lead, but the drawn + string is still full-length; without this clip it overflows the right + edge of the panel. This keeps the text inside its viewport, which + matters most on narrow 64px-wide displays where the text area is only + ~29px wide. + """ + if not text or max_width <= 0: + return text + if self.display_manager.get_text_width(text, font) <= max_width: + return text + clipped = text + while clipped and self.display_manager.get_text_width(clipped, font) > max_width: + clipped = clipped[:-1] + return clipped + def display(self, force_clear: bool = False) -> None: """Display music information - called by plugin system.""" perform_full_refresh_this_cycle = force_clear @@ -1056,7 +1074,7 @@ def _safe_y_percent(value, fallback): self.title_end_pause_counter = 0 self.title_at_end = False - self.display_manager.draw_text(current_title_display_text, + self.display_manager.draw_text(self._clip_text_to_width(current_title_display_text, font_title, text_area_width), x=text_area_x_start, y=y_pos_title_top, color=(255, 255, 255), font=font_title) # Artist scrolling with configurable settings @@ -1111,7 +1129,7 @@ def _safe_y_percent(value, fallback): self.artist_end_pause_counter = 0 self.artist_at_end = False - self.display_manager.draw_text(current_artist_display_text, + self.display_manager.draw_text(self._clip_text_to_width(current_artist_display_text, font_artist, text_area_width), x=text_area_x_start, y=y_pos_artist_top, color=(180, 180, 180), font=font_artist) # Album @@ -1127,7 +1145,7 @@ def _safe_y_percent(value, fallback): if album_width <= text_area_width: # Album fits without scrolling - display normally self.logger.debug(f"MusicPlugin.display: Drawing album '{album}' at ({text_area_x_start}, {y_pos_album_top}) - fits without scrolling") - self.display_manager.draw_text(album, + self.display_manager.draw_text(self._clip_text_to_width(album, font_album, text_area_width), x=text_area_x_start, y=y_pos_album_top, color=(150, 150, 150), font=font_album) self.scroll_position_album = 0 self.album_scroll_tick = 0 @@ -1179,7 +1197,7 @@ def _safe_y_percent(value, fallback): self.album_scroll_tick = 0 self.logger.debug(f"MusicPlugin.display: Drawing scrolling album '{current_album_display_text}' at ({text_area_x_start}, {y_pos_album_top}) - position: {self.scroll_position_album}") - self.display_manager.draw_text(current_album_display_text, + self.display_manager.draw_text(self._clip_text_to_width(current_album_display_text, font_album, text_area_width), x=text_area_x_start, y=y_pos_album_top, color=(150, 150, 150), font=font_album) else: self.logger.debug(f"MusicPlugin.display: Album '{album}' not displayed - insufficient height (available: {available_height_for_album}, needed: {album_height})") diff --git a/plugins/ledmatrix-music/manifest.json b/plugins/ledmatrix-music/manifest.json index 19a753cb..40e1ea13 100644 --- a/plugins/ledmatrix-music/manifest.json +++ b/plugins/ledmatrix-music/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-music", "name": "Music Player - Now Playing", - "version": "1.0.6", + "version": "1.0.7", "description": "Real-time now playing display for Spotify and YouTube Music with album art, scrolling text, and progress bars", "author": "ChuckBuilds", "entry_point": "manager.py", @@ -28,7 +28,7 @@ ], "homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/ledmatrix-music", "website": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/ledmatrix-music", - "license": "MIT", + "license": "GPL-3.0", "compatible_versions": [ ">=2.0.0" ], @@ -66,6 +66,12 @@ } ], "versions": [ + { + "released": "2026-07-08", + "version": "1.0.7", + "notes": "Fix now-playing text overflowing the right edge on narrow (64px-wide) panels: clip the scrolling title/artist/album text to the text-area viewport so long text stays within bounds while it scrolls, instead of drawing past the panel edge. Also set the license to GPL-3.0 (project standard).", + "ledmatrix_min": "2.0.0" + }, { "released": "2026-05-15", "version": "1.0.6", @@ -92,7 +98,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-05-15", + "last_updated": "2026-07-08", "stars": 0, "downloads": 0, "verified": true, From cb35c6035c72b58f592b3e65675e457e245b0a6e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Jul 2026 19:00:32 +0000 Subject: [PATCH 2/5] fix(ledmatrix-music): draw no text when the panel has no text room (square panels) Follow-up to the viewport clip: on square panels (e.g. 64x64) the album art is sized to the panel height, so it fills the full width and text_area_width goes negative. The clip helper's `max_width <= 0` guard returned the text UNCLIPPED, so it was still drawn off-panel (overflow bbox=(64,28,91,36) at 64x64). Now return an empty string when there's no room, and skip the progress bar too. Album art fills the panel; no text is drawn rather than overflowing. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01F9Saiat9CQmNi3DeDdvacB --- plugins/ledmatrix-music/manager.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/ledmatrix-music/manager.py b/plugins/ledmatrix-music/manager.py index ba8fd697..b2602d19 100644 --- a/plugins/ledmatrix-music/manager.py +++ b/plugins/ledmatrix-music/manager.py @@ -742,7 +742,11 @@ def _clip_text_to_width(self, text, font, max_width): matters most on narrow 64px-wide displays where the text area is only ~29px wide. """ - if not text or max_width <= 0: + if max_width <= 0: + # No horizontal room for text (e.g. a square panel where the album + # art fills the full width) — draw nothing rather than overflow. + return "" + if not text: return text if self.display_manager.get_text_width(text, font) <= max_width: return text @@ -1206,7 +1210,7 @@ def _safe_y_percent(value, fallback): duration_ms = current_track_info_snapshot.get('duration_ms', 0) progress_ms = current_track_info_snapshot.get('progress_ms', 0) - if duration_ms > 0: + if duration_ms > 0 and text_area_width > 0: bar_total_width = text_area_width filled_ratio = progress_ms / duration_ms filled_width = int(filled_ratio * bar_total_width) From 6de55c4d4042dc864b97ce930166cecbe4823f90 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Jul 2026 19:04:36 +0000 Subject: [PATCH 3/5] fix(ledmatrix-music): clip the "Nothing Playing" screen to the panel width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That was the real overflow the harness caught (CI has no auth -> no track -> the "Nothing Playing" idle screen renders). The text is centered via x = (width - text_width)//2, which goes negative when the text is wider than the panel (64px), spilling off both edges — matching the failing bbox exactly (y = height//2 - 4 = 12 at 64x32, 28 at 64x64). Clip the text to the panel width and clamp x to >= 0. (The earlier marquee viewport clip is still correct for the now-playing view with a real track.) Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01F9Saiat9CQmNi3DeDdvacB --- plugins/ledmatrix-music/manager.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/ledmatrix-music/manager.py b/plugins/ledmatrix-music/manager.py index b2602d19..5e461414 100644 --- a/plugins/ledmatrix-music/manager.py +++ b/plugins/ledmatrix-music/manager.py @@ -847,10 +847,11 @@ def display(self, force_clear: bool = False) -> None: if perform_full_refresh_this_cycle or not self.is_currently_showing_nothing_playing: self.display_manager.clear() - text_width = self.display_manager.get_text_width("Nothing Playing", self.display_manager.regular_font) - x_pos = (self.display_manager.matrix.width - text_width) // 2 + np_text = self._clip_text_to_width("Nothing Playing", self.display_manager.regular_font, self.display_manager.matrix.width) + text_width = self.display_manager.get_text_width(np_text, self.display_manager.regular_font) + x_pos = max(0, (self.display_manager.matrix.width - text_width) // 2) y_pos = (self.display_manager.matrix.height // 2) - 4 - self.display_manager.draw_text("Nothing Playing", x=x_pos, y=y_pos, font=self.display_manager.regular_font) + self.display_manager.draw_text(np_text, x=x_pos, y=y_pos, font=self.display_manager.regular_font) self.display_manager.update_display() self.is_currently_showing_nothing_playing = True From d9caf1be7556bcadb1edaf304ac6b9d6645f3ddf Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 9 Jul 2026 19:10:16 +0000 Subject: [PATCH 4/5] fix(ledmatrix-music): keep the ellipsis when truncating non-scrolling text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When scrolling is disabled, the title/artist/album were truncated with a rough char-count estimate (`text[:width//6] + "..."`) and then passed through the draw-time viewport clip, which could trim the trailing "..." back off — leaving no truncation indicator. Add `_truncate_text_with_ellipsis`, which reserves room for the ellipsis before clipping so the "..." always survives and the result fits the text-area viewport. Bumps ledmatrix-music to 1.0.8. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01F9Saiat9CQmNi3DeDdvacB --- plugins.json | 4 +-- plugins/ledmatrix-music/manager.py | 35 ++++++++++++++++++++++----- plugins/ledmatrix-music/manifest.json | 10 ++++++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/plugins.json b/plugins.json index b2a460d6..5fd13316 100644 --- a/plugins.json +++ b/plugins.json @@ -475,10 +475,10 @@ "plugin_path": "plugins/ledmatrix-music", "stars": 0, "downloads": 0, - "last_updated": "2026-07-08", + "last_updated": "2026-07-09", "verified": true, "screenshot": "", - "latest_version": "1.0.7" + "latest_version": "1.0.8" }, { "id": "news", diff --git a/plugins/ledmatrix-music/manager.py b/plugins/ledmatrix-music/manager.py index 5e461414..d0fa9a42 100644 --- a/plugins/ledmatrix-music/manager.py +++ b/plugins/ledmatrix-music/manager.py @@ -755,6 +755,29 @@ def _clip_text_to_width(self, text, font, max_width): clipped = clipped[:-1] return clipped + def _truncate_text_with_ellipsis(self, text, font, max_width): + """Truncate text to fit max_width px, appending "..." when trimmed. + + Used by the scrolling-disabled branches. Reserves room for the + ellipsis before clipping, so the result already fits the viewport and + survives the draw-time _clip_text_to_width pass unchanged — otherwise + that pass would trim the trailing "..." back off and drop the + truncation indicator. + """ + if max_width <= 0: + return "" + if not text: + return text + if self.display_manager.get_text_width(text, font) <= max_width: + return text + ellipsis = "..." + ellipsis_width = self.display_manager.get_text_width(ellipsis, font) + if ellipsis_width >= max_width: + # Not even room for the ellipsis — clip the raw text instead. + return self._clip_text_to_width(text, font, max_width) + clipped = self._clip_text_to_width(text, font, max_width - ellipsis_width) + return clipped + ellipsis + def display(self, force_clear: bool = False) -> None: """Display music information - called by plugin system.""" perform_full_refresh_this_cycle = force_clear @@ -1067,8 +1090,8 @@ def _safe_y_percent(value, fallback): self.scroll_position_title = (self.scroll_position_title + 1) % len(title) self.title_scroll_tick = 0 elif title_width > text_area_width and not title_config['enabled']: - # Scrolling disabled - truncate text - current_title_display_text = title[:text_area_width // 6] + "..." # Rough truncation + # Scrolling disabled - truncate to the viewport with an ellipsis + current_title_display_text = self._truncate_text_with_ellipsis(title, font_title, text_area_width) self.scroll_position_title = 0 self.title_scroll_tick = 0 else: @@ -1122,8 +1145,8 @@ def _safe_y_percent(value, fallback): self.scroll_position_artist = (self.scroll_position_artist + 1) % len(artist) self.artist_scroll_tick = 0 elif artist_width > text_area_width and not artist_config['enabled']: - # Scrolling disabled - truncate text - current_artist_display_text = artist[:text_area_width // 5] + "..." # Rough truncation + # Scrolling disabled - truncate to the viewport with an ellipsis + current_artist_display_text = self._truncate_text_with_ellipsis(artist, font_artist, text_area_width) self.scroll_position_artist = 0 self.artist_scroll_tick = 0 else: @@ -1196,8 +1219,8 @@ def _safe_y_percent(value, fallback): self.scroll_position_album = (self.scroll_position_album + 1) % len(album) self.album_scroll_tick = 0 else: - # Scrolling disabled - truncate text - current_album_display_text = album[:text_area_width // 5] + "..." # Rough truncation + # Scrolling disabled - truncate to the viewport with an ellipsis + current_album_display_text = self._truncate_text_with_ellipsis(album, font_album, text_area_width) self.scroll_position_album = 0 self.album_scroll_tick = 0 diff --git a/plugins/ledmatrix-music/manifest.json b/plugins/ledmatrix-music/manifest.json index 40e1ea13..21102489 100644 --- a/plugins/ledmatrix-music/manifest.json +++ b/plugins/ledmatrix-music/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-music", "name": "Music Player - Now Playing", - "version": "1.0.7", + "version": "1.0.8", "description": "Real-time now playing display for Spotify and YouTube Music with album art, scrolling text, and progress bars", "author": "ChuckBuilds", "entry_point": "manager.py", @@ -66,6 +66,12 @@ } ], "versions": [ + { + "released": "2026-07-09", + "version": "1.0.8", + "notes": "When text scrolling is disabled, truncate the title/artist/album to the text-area width and append an ellipsis, reserving room for it so the '...' indicator isn't clipped away at draw time.", + "ledmatrix_min": "2.0.0" + }, { "released": "2026-07-08", "version": "1.0.7", @@ -98,7 +104,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-07-08", + "last_updated": "2026-07-09", "stars": 0, "downloads": 0, "verified": true, From 579f9f16da0f7610198ffddb70c066a8082bd3d3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 17:00:36 +0000 Subject: [PATCH 5/5] Merge main + use non-deprecated ledmatrix_min_version in music manifest Follows the maintainer's correction on #175: the core's manifest validator flags `ledmatrix_min` as deprecated and expects `ledmatrix_min_version`. Switch the music versions[] entries (1.0.8, 1.0.7, 1.0.6) to `ledmatrix_min_version` so device installs don't log a deprecated-field warning. Also merges the latest main (#174, #175) and regenerates plugins.json. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01F9Saiat9CQmNi3DeDdvacB --- plugins/ledmatrix-music/manifest.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/ledmatrix-music/manifest.json b/plugins/ledmatrix-music/manifest.json index 21102489..f494ec78 100644 --- a/plugins/ledmatrix-music/manifest.json +++ b/plugins/ledmatrix-music/manifest.json @@ -70,18 +70,18 @@ "released": "2026-07-09", "version": "1.0.8", "notes": "When text scrolling is disabled, truncate the title/artist/album to the text-area width and append an ellipsis, reserving room for it so the '...' indicator isn't clipped away at draw time.", - "ledmatrix_min": "2.0.0" + "ledmatrix_min_version": "2.0.0" }, { "released": "2026-07-08", "version": "1.0.7", "notes": "Fix now-playing text overflowing the right edge on narrow (64px-wide) panels: clip the scrolling title/artist/album text to the text-area viewport so long text stays within bounds while it scrolls, instead of drawing past the panel edge. Also set the license to GPL-3.0 (project standard).", - "ledmatrix_min": "2.0.0" + "ledmatrix_min_version": "2.0.0" }, { "released": "2026-05-15", "version": "1.0.6", - "ledmatrix_min": "2.0.0" + "ledmatrix_min_version": "2.0.0" }, { "version": "1.0.5",