From 6d91c709a744a9815f2aa6533a1228dc700c3abd Mon Sep 17 00:00:00 2001 From: Cowork Supervisor Date: Wed, 8 Jul 2026 11:59:57 -0400 Subject: [PATCH] fix: bundle icons + set app window icon and Windows AppUserModelID (#20) --- bcc.py | 43 ++++++++++++++++++++++++++++++++++++++++--- bcc.spec | 2 +- tests/test_core.py | 15 +++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/bcc.py b/bcc.py index 6f33f93..5886a28 100644 --- a/bcc.py +++ b/bcc.py @@ -1253,6 +1253,31 @@ class LogViewerDialog(QDialog): super().closeEvent(event) +# --------------------------------------------------------------------------- # +# Bundled assets (icons) — resolves both a normal source run and a frozen +# PyInstaller build (onefile extracts assets under sys._MEIPASS). +# --------------------------------------------------------------------------- # +def _asset_dir() -> Path: + base = getattr(sys, "_MEIPASS", None) + return Path(base) if base else Path(__file__).resolve().parent + + +def _app_icon() -> QIcon: + """Multi-resolution app/window icon from the bundled PNGs (falls back to + the .ico). Returns a null QIcon if no asset is found.""" + icon = QIcon() + base = _asset_dir() / "icons" / "twin-gears" / "rounded" + for size in (16, 32, 48, 64, 128, 256, 512): + f = base / f"icon-{size}.png" + if f.is_file(): + icon.addFile(str(f)) + if icon.isNull(): + ico = _asset_dir() / "icons" / "app.ico" + if ico.is_file(): + icon.addFile(str(ico)) + return icon + + # --------------------------------------------------------------------------- # # About dialog # --------------------------------------------------------------------------- # @@ -1274,9 +1299,7 @@ class AboutDialog(QDialog): self._worker: UpdateCheckWorker | None = None self._release_url: str | None = None - icon_path = ( - Path(__file__).resolve().parent / "icons" / "twin-gears" / "rounded" / "icon-128.png" - ) + icon_path = _asset_dir() / "icons" / "twin-gears" / "rounded" / "icon-128.png" if icon_path.is_file(): self.setWindowIcon(QIcon(str(icon_path))) @@ -2366,9 +2389,23 @@ class MainWindow(QMainWindow): def main(): + if sys.platform == "win32": + # Without an explicit AppUserModelID, Windows taskbar groups the app + # under the default host/Python icon instead of our own window icon. + try: + import ctypes + + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( + "io.avezzano.better-claude-config" + ) + except Exception: + pass app = QApplication(sys.argv) app.setApplicationName("Better Claude Config") app.setApplicationDisplayName("Better Claude Config") + icon = _app_icon() + if not icon.isNull(): + app.setWindowIcon(icon) app.setStyleSheet(STYLESHEET) win = MainWindow() win.show() diff --git a/bcc.spec b/bcc.spec index d0cfa2a..1cc0bb7 100644 --- a/bcc.spec +++ b/bcc.spec @@ -31,7 +31,7 @@ a = Analysis( ["bcc.py"], pathex=[], binaries=[], - datas=[], + datas=[("icons", "icons")], hiddenimports=[], hookspath=[], hooksconfig={}, diff --git a/tests/test_core.py b/tests/test_core.py index 578387b..b07db51 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1326,3 +1326,18 @@ def test_health_from_spawn_result_failed_without_stderr_has_no_dash(): } _, summary = c.health_from_spawn_result(result) assert "—" not in summary + + +# --------------------------------------------------------------------------- # +# App icon assets present (issue #20 — icons must exist to be bundled/loaded) +# --------------------------------------------------------------------------- # +def test_app_icon_assets_present(): + from pathlib import Path + + root = Path(c.__file__).resolve().parent + rounded = root / "icons" / "twin-gears" / "rounded" + # The window-icon builder in bcc.py loads these sizes; keep them present. + for size in (16, 32, 128, 256): + assert (rounded / f"icon-{size}.png").is_file(), f"missing icon-{size}.png" + assert (root / "icons" / "app.ico").is_file() + assert (root / "icons" / "app.icns").is_file()