Files
clawdmeter/clawdmeter.spec
T
wenilandClaude Opus 4.8 ec8322fa21 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>
2026-06-21 09:58:26 +03:00

77 lines
2.5 KiB
RPMSpec

# -*- mode: python ; coding: utf-8 -*-
#
# clawdmeter.spec — build the standalone Windows daemon + tray executable.
#
# Produces a single, self-contained dist\Clawdmeter.exe that bundles its own
# Python, bleak (WinRT BLE) and the winrt media projection — so it runs on ANY
# Windows 11 machine with no Python and no pip install. The exe is the tray app
# (daemon thread + notification-area icon + login-autostart toggle); it is built
# windowed, so there is no console window and it logs to
# %LOCALAPPDATA%\Clawdmeter\daemon.log.
#
# Build (from the repo root, inside the venv) — or just run build-exe.ps1:
# .venv\Scripts\python.exe -m PyInstaller --noconfirm clawdmeter.spec
#
# winrt + bleak ship C-extension projections (.pyd) that PyInstaller's static
# analysis misses: bleak imports its WinRT backend dynamically, and the
# winrt.windows.* namespaces are split across separate distributions. collect_all
# pulls in their submodules, binaries and metadata so both BLE (bluetooth) and the
# now-playing media session work inside the frozen exe. Verified on hardware:
# connects, reads the media session, and pushes Cyrillic now-playing payloads.
from PyInstaller.utils.hooks import collect_all
datas = [('firmware/src/logo.h', 'firmware/src')] # tray icon parsed at runtime
binaries = []
hiddenimports = [
# Imported lazily inside tray_windows.main(), so name them explicitly.
'daemon.claude_usage_daemon_windows',
'daemon.autostart_windows',
'daemon.icon_assets',
# The exact winrt media modules read_now_playing() pulls in.
'winrt.windows.media',
'winrt.windows.media.control',
]
for _pkg in ('winrt', 'bleak', 'pystray', 'PIL'):
_d, _b, _h = collect_all(_pkg)
datas += _d
binaries += _b
hiddenimports += _h
a = Analysis(
['daemon\\tray_windows.py'],
pathex=['.'],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=['tkinter', 'pytest'],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='Clawdmeter',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False, # UPX compression raises AV false-positive rates — leave it off
upx_exclude=[],
runtime_tmpdir=None,
console=False, # windowed tray app: no console window; logs to daemon.log
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)