Files
whisper-local/docs/superpowers/specs/2026-05-14-microphone-monitor-design.md
2026-05-14 17:21:54 +02:00

4.9 KiB
Raw Permalink Blame History

Mikrofon-Monitor Design-Spec

Datum: 2026-05-14
Status: Genehmigt

Ziel

Die Anwendung soll erkennen, wenn Mikrofone im System hinzugefügt oder entfernt werden (USB, Bluetooth). Wenn das konfigurierte Mikrofon nicht mehr vorhanden ist, fällt die App automatisch auf das Standard-Mikrofon zurück und benachrichtigt den Nutzer.

Verhalten

  • Konfiguriertes Mikrofon verschwindet: Automatischer Fallback auf Standard-Mikrofon (device = None), Toast-Benachrichtigung + Tray-Tooltip-Warnung.
  • Konfiguriertes Mikrofon kehrt zurück: Stellt das konfigurierte Gerät wieder her, Toast „Mikrofon wieder verbunden", Tray-Tooltip-Warnung entfernen.
  • Konfiguriertes Mikrofon fehlt bereits beim Start: on_configured_missing sofort auslösen, nicht erst nach dem ersten Poll-Intervall.
  • Kein konfiguriertes Mikrofon (device = None): Monitor läuft trotzdem, meldet nur on_device_added / on_device_removed (für zukünftige Erweiterungen), keine Fehlerbehandlung nötig.

Neue Dateien

whisper_local/
  microphone/
    __init__.py          # Protocol + create_monitor() Factory
    _poll.py             # Polling-Implementierung (Linux + Fallback)
    _win32.py            # IMMNotificationClient-Implementierung (Windows)
  tray/
    _notification.py     # notify-py Toast-Wrapper

Protocol

class MicrophoneMonitor(Protocol):
    on_device_added:        Callable[[str], Awaitable[None]] | None
    on_device_removed:      Callable[[str], Awaitable[None]] | None
    on_configured_missing:  Callable[[], Awaitable[None]] | None

    async def start(self) -> None: ...
    def stop(self) -> None: ...

Factory

def create_monitor(configured_device: str | None) -> MicrophoneMonitor:
    ...
  • Windows: versucht Win32Monitor; fällt bei COM-Fehler auf PollMonitor zurück.
  • Alle anderen Plattformen: PollMonitor.

Erkennungsmechanismus

Windows: Win32Monitor (_win32.py)

  • Registriert IMMNotificationClient bei IMMDeviceEnumerator via comtypes.
  • Empfängt OnDeviceAdded, OnDeviceRemoved, OnDeviceStateChanged als COM-Callbacks.
  • Übergibt Events per loop.call_soon_threadsafe in den asyncio-Loop (identisches Muster wie pynput-Hotkey-Callbacks).
  • Wenn COM-Initialisierung fehlschlägt: transparenter Fallback auf PollMonitor.

Cross-Platform: PollMonitor (_poll.py)

  • Asyncio-Task mit asyncio.sleep(2.5) zwischen Prüfungen.
  • Vergleicht aktuelle sd.query_devices()-Liste mit letztem Snapshot (Set aus Gerätenamen).
  • Erkennt hinzugekommene und verschwundene Geräte, löst entsprechende Callbacks aus.
  • Exceptions in sd.query_devices() werden geloggt und übersprungen — Loop läuft weiter.

Benachrichtigung (_notification.py)

  • Nutzt notify-py (cross-platform, aktiv gepflegt).
  • Funktion notify(title: str, message: str) -> None — bei Fehler wird geloggt, nie blockierend.
  • Neue Abhängigkeit: notify-py in pyproject.toml eintragen.

Tray-Integration

PystrayApp erhält neue Methode:

def set_warning(self, msg: str | None) -> None: ...
  • msg = None: Icon-Titel zurück auf "whisper-local".
  • msg = "...": Icon-Titel auf "whisper-local ⚠ <msg>".

App-Integration (__main__.py)

  • App.__init__: erstellt self.monitor = create_monitor(config.microphone).
  • Callbacks registrieren:
    • on_configured_missingrecorder.device = None + notify(...) + tray.set_warning(...)
    • on_device_added → prüft ob konfiguriertes Gerät zurückgekehrt → recorder.device = config.microphone + notify(...) + tray.set_warning(None)
  • App.run(): startet monitor.start() als asyncio-Task parallel zum Hotkey-Listener.
  • _on_config_reload: stoppt alten Monitor, erstellt neuen mit aktualisierten Gerätedaten.

Fehlerbehandlung

Situation Verhalten
COM-Init schlägt fehl (Windows) Transparenter Fallback auf PollMonitor
sd.query_devices() wirft Exception Loggen + überspringen, Loop läuft weiter
notify-py wirft Exception Loggen + ignorieren, nie blockierend
Konfiguriertes Mikrofon fehlt beim Start Sofort on_configured_missing auslösen

Tests

  • tests/test_microphone_monitor.py:
    • PollMonitor mit gemocktem sd.query_devices().
    • Prüft: on_device_added wird ausgelöst wenn Gerät erscheint.
    • Prüft: on_device_removed wird ausgelöst wenn Gerät verschwindet.
    • Prüft: on_configured_missing wird sofort beim Start ausgelöst wenn Gerät fehlt.
  • Win32Monitor-Tests nur auf Windows (@pytest.mark.skipif(sys.platform != "win32", ...)), COM-Interface gemockt.
  • _notification.py: kein eigener Test (trivialer Wrapper).

Abhängigkeiten

Paket Zweck Plattform
notify-py Desktop-Toast-Benachrichtigungen alle
comtypes COM-Interface für IMMNotificationClient Windows (bereits transitiv vorhanden via pywinrt)