--- type: Domain title: Plattform-Abstraktionen description: Die vier plattformabhängigen Module — Hotkey, Inserter, Media, Mikrofon — mit ihren jeweiligen Linux- und Windows-Backends. Protocol-Interfaces, Factory-Dispatch und implementierungsspezifische Details. tags: [domain, platform-abstraction, hotkey, inserter, media, microphone, protocol, factory] --- # Plattform-Abstraktionen whisper-local abstrahiert vier plattformabhängige Funktionsbereiche hinter `typing.Protocol`-Interfaces mit Factory-Dispatch. Die [Architektur-Übersicht](../architecture/overview.md) beschreibt das generelle Muster; hier werden die konkreten Backends dokumentiert. ## HotkeyListener **Protocol**: `HotkeyListener` in [`hotkey/__init__.py`](../../whisper_local/hotkey/__init__.py) **Attribute**: `on_press: AsyncCallback | None`, `on_release: AsyncCallback | None` **Methoden**: `async listen()`, `stop()` ### EvdevHotkeyListener (Linux) Datei: [`hotkey/_evdev.py`](../../whisper_local/hotkey/_evdev.py) - `find_keyboard_devices(key_name)` durchsucht alle `/dev/input/`-Devices und findet jene, die den konfigurierten Key unterstützen. Wirft `RuntimeError` wenn kein passendes Device gefunden wird. - `listen()` startet pro gefundenem Device einen `asyncio.Task` mit `async_read_loop()`. Dadurch werden alle Tastaturen parallel überwacht — wichtig bei Multi-Device-Setups. - `stop()` cancelt alle Tasks und schließt Devices. ### PynputHotkeyListener (Windows/Fallback) Datei: [`hotkey/_pynput.py`](../../whisper_local/hotkey/_pynput.py) - `_evdev_to_pynput_key()` übersetzt evdev-Namen (`KEY_F12`) ins pynput-Format (`Key.f12`). Unterstützt F-Tasten per Regex und direkte Attribut-Zuordnung. - **Key-Repeat-Unterdrückung**: Das `_pressed`-Flag verhindert, dass OS-Key-Repeat mehrfache `on_press`-Callbacks auslöst. Erst beim echten `on_release` wird das Flag zurückgesetzt. - pynput betreibt einen Thread; Callbacks werden via `call_soon_threadsafe` + `asyncio.ensure_future` in den Loop gebridged. - `stop()` signalisiert über ein `asyncio.Event`; der Listener wird im `finally`-Block gestoppt. ## Inserter **Protocol**: `Inserter` in [`inserter/__init__.py`](../../whisper_local/inserter/__init__.py) **Methode**: `async insert(text: str)` Beide Backends sichern den alten Clipboard-Inhalt, setzen den Text, senden `Ctrl+V`, und restaurieren das Clipboard im `finally`-Block. `PASTE_DELAY = 0.2s` nach dem Paste-Befehl. ### WaylandInserter (Linux) Datei: [`inserter/_wayland.py`](../../whisper_local/inserter/_wayland.py) - Clipboard-Backup via `wl-paste --no-newline`. - Text setzen via `wl-copy -- `. - `Ctrl+V` simulieren via `ydotool key 29:1 47:1 47:0 29:0` (evdev Keycodes für Ctrl down, V down, V up, Ctrl up). - Erfordert `ydotool`-Daemon und `input`-Gruppen-Mitgliedschaft. ### Win32Inserter (Windows) Datei: [`inserter/_win32.py`](../../whisper_local/inserter/_win32.py) - Clipboard-Operationen via `win32clipboard` (CF_UNICODETEXT). - `Ctrl+V` via `pynput.keyboard.Controller`. - Blocking-Aufrufe werden mit `asyncio.to_thread()` in Threads ausgelagert, um den Event-Loop nicht zu blockieren. ## MediaController **Protocol**: `MediaController` in [`media/__init__.py`](../../whisper_local/media/__init__.py) **Methoden**: `async pause()`, `async resume()` `create_media_controller(enabled)` gibt `NoopController` zurück wenn `enabled=False` oder die Plattform nicht unterstützt wird. ### MprisController (Linux) Datei: [`media/_mpris.py`](../../whisper_local/media/_mpris.py) - Verbindet sich via `dbus-next` (`MessageBus().connect()`) zur D-Bus-Session. - `pause()`: Listet alle `org.mpris.MediaPlayer2.*`-Services auf, pausiert nur Sessions im Status `Playing`, merkt sich pausierte Bus-Namen in `_paused`. - `resume()`: Setzt nur die Sessions fort, die selbst pausiert wurden (`_paused`-Liste). - **Circuit-Breaker**: `_bus_broken`-Flag deaktiviert den Controller dauerhaft bei D-Bus-Verbindungsfehler. ### SmtcController (Windows) Datei: [`media/_smtc.py`](../../whisper_local/media/_smtc.py) - Nutzt `winrt.windows.media.control.GlobalSystemMediaTransportControlsSessionManager`. - `pause()`: Iteriert über alle Sessions, pausiert nur `PLAYING`-Sessions via `try_pause_async()`, merkt sich AUMIDs in `_paused`. - `resume()`: Holt aktuelle Sessions, spielt nur die in `_paused` gelisteten via `try_play_async()` wieder ab. Überspringt Sessions, die nicht mehr existieren. - **Circuit-Breaker**: `_broken`-Flag bei SMTC-Initialisierungsfehler. ### NoopController Datei: [`media/_noop.py`](../../whisper_local/media/_noop.py) - `pause()` und `resume()` sind No-Ops. Wird verwendet wenn das Feature deaktiviert ist oder die Plattform nicht unterstützt wird. ## Mikrofon-Monitor **Protocol**: `MicrophoneMonitor` in [`microphone/__init__.py`](../../whisper_local/microphone/__init__.py) **Callbacks**: `on_device_added`, `on_device_removed`, `on_configured_missing` (alle `Awaitable`) **Methoden**: `async start()`, `stop()` ### PollMonitor (Cross-Platform) Datei: [`microphone/_poll.py`](../../whisper_local/microphone/_poll.py) - Pollt `sounddevice.query_devices()` alle 2,5 s, vergleicht mit `_known_devices`. - `start()` prüft sofort ob das konfigurierte Gerät vorhanden ist und ruft `on_configured_missing()` auf wenn nicht. - Bei Fehler beim Abfragen der Geräte wird der bisherige Stand beibehalten (kein Crash). ### Win32Monitor (Windows) Datei: [`microphone/_win32.py`](../../whisper_local/microphone/_win32.py) - Definiert COM-Interfaces (`IMMNotificationClient`, `IMMDeviceEnumerator`) via `comtypes` zur Laufzeit. - Registriert einen `IMMNotificationClient`-Callback bei der Core Audio API. COM-Events werden via `call_soon_threadsafe` in den asyncio-Loop delegiert. - **Polling-Fallback**: Wenn COM-Initialisierung fehlschlägt, wird automatisch ein `PollMonitor` als Fallback gestartet. - `stop()` deregistriert den COM-Callback und ruft `CoUninitialize()` auf. ### Geräte-Erkennungslogik Alle Monitors verwenden dieselbe Logik: `sd.query_devices()` filtern auf `max_input_channels > 0` (Eingabegeräte) und extrahieren Gerätenamen. Der Vergleich erfolgt über Set-Differenzen (`added = current - known`, `removed = known - current`).