"""Main orchestrator: tray icon, state, threads.""" import logging import sys import threading import tkinter as tk from tkinter import simpledialog import pystray from . import config from .fetcher import UsageFetcher from .menu import build_menu from .renderer import render_icon log = logging.getLogger(__name__) class App: def __init__(self): self.usage_data = {} self._lock = threading.Lock() self.fetcher = UsageFetcher(on_update=self._on_usage_update) self._icon = pystray.Icon( name="claude-usage", icon=render_icon(0), title="Claude Usage Widget", menu=build_menu(self), ) def run(self): """Start fetcher and run tray icon (blocks until quit).""" self.fetcher.start() self._icon.run() def _on_usage_update(self, data): """Callback from fetcher thread — update icon + menu.""" with self._lock: self.usage_data = data pct = data.get("five_hour_pct", 0) if not data.get("error") else 0 try: self._icon.icon = render_icon(pct) # Force menu rebuild for dynamic text self._icon.update_menu() except Exception: pass # icon not yet visible # Update tooltip if data.get("error"): self._icon.title = f"Claude Usage: {data['error']}" else: self._icon.title = f"Claude Usage: {data.get('five_hour_pct', 0)}%" def on_refresh(self): self.fetcher.refresh() def on_set_interval(self, seconds): self.fetcher.set_interval(seconds) def on_session_key(self): """Show a simple dialog to enter/update the session key.""" threading.Thread(target=self._session_key_dialog, daemon=True).start() def _session_key_dialog(self): root = tk.Tk() root.withdraw() current = config.get_session_key() key = simpledialog.askstring( "Claude Session Key", "Paste your claude.ai sessionKey cookie value:", initialvalue=current, parent=root, ) root.destroy() if key is not None and key.strip(): config.set_session_key(key.strip()) self.fetcher.refresh() def on_quit(self): self.fetcher.stop() self._icon.stop() def main(): logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) app = App() app.run()