v2: standalone Clawdmeter.exe — runs on any Win11 machine, no Python

Package the tray+daemon as a single PyInstaller onefile exe so it runs on a
fresh Windows 11 box with no Python and no pip install. Verified on hardware:
the frozen exe connects over BLE, reads the Windows media session, and pushes
now-playing (incl. Cyrillic) at the 3s / 60s cadence.

- clawdmeter.spec: onefile, windowed (no console; logs to daemon.log). collect_all
  for winrt + bleak is the key bit — bleak loads its WinRT backend dynamically and
  the winrt.windows.* projections are split distributions that PyInstaller's static
  analysis misses, so BLE and the media session would otherwise fail at runtime.
  upx off (lowers AV false-positive rate).
- build-exe.ps1: one-command build (venv + deps + pyinstaller + spec).
- tray_windows.py: resolve the brand-logo asset from sys._MEIPASS when frozen.
- autostart_windows.py: when frozen, the HKCU\Run "Start at login" value points at
  the exe itself (sys.executable), not pythonw + script.
- .gitignore: ignore /build, /dist, /.venv — the exe ships via a Gitea release.
- README-windows.md: document the standalone-exe path (download / run / build).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wenil
2026-06-21 09:58:26 +03:00
co-authored by Claude Opus 4.8
parent 650af4221b
commit ec8322fa21
6 changed files with 186 additions and 3 deletions
+7 -1
View File
@@ -27,7 +27,13 @@ import time
# imports below and the brand-logo asset load work no matter what the current
# working directory is — critical for logon autostart, where the HKCU\Run entry
# starts with cwd = System32, not the repo (APP-01 / SC#1).
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if getattr(sys, "frozen", False):
# PyInstaller bundle: the `daemon` package loads from the frozen archive and
# data files (the brand logo.h) live under sys._MEIPASS. Point _REPO_ROOT at
# the bundle root so the logo asset path below resolves inside the exe.
_REPO_ROOT = sys._MEIPASS # type: ignore[attr-defined]
else:
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _REPO_ROOT not in sys.path:
sys.path.insert(0, _REPO_ROOT)