Files
whisper-local/openwiki/domain/platform-abstractions.md
info 66ecf55ea9 docs: OpenWiki-Dokumentation und Auto-Update-Workflow hinzufügen
Fügt generierte OpenWiki-Docs (Architektur, Domain, Betrieb, Tests, Workflows)
sowie den GitHub-Actions-Workflow zur automatischen Aktualisierung hinzu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 14:53:30 +02:00

116 lines
6.1 KiB
Markdown

---
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 -- <text>`.
- `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`).