feat: integrate tray icon, settings dialog, and config reload into App

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 21:21:29 +02:00
parent ad60da4a38
commit 71806cd0b8
2 changed files with 105 additions and 5 deletions
+48 -1
View File
@@ -9,6 +9,7 @@ from whisper_local.hotkey import create_listener
from whisper_local.inserter import create_inserter
from whisper_local.recorder import Recorder
from whisper_local.transcriber import Transcriber
from whisper_local.tray import AppState, create_tray
logger = logging.getLogger(__name__)
@@ -18,10 +19,15 @@ class App:
if config is None:
config = load_config()
self._config = config
self._loop: asyncio.AbstractEventLoop | None = None
self._hotkey_task: asyncio.Task | None = None
self.recorder = Recorder(
sample_rate=config.sample_rate,
channels=config.channels,
min_duration=config.min_duration,
device=config.microphone or None,
)
self.transcriber = Transcriber(
model_name=config.whisper_model,
@@ -32,10 +38,12 @@ class App:
self.hotkey = create_listener(key_name=config.hotkey)
self.hotkey.on_press = self.on_press
self.hotkey.on_release = self.on_release
self.tray = create_tray(on_settings=self._open_settings, on_quit=self._quit)
async def on_press(self) -> None:
"""Callback: Hotkey gedrückt — Aufnahme starten."""
logger.info("Aufnahme startet...")
self.tray.set_state(AppState.RECORDING)
self.recorder.start()
async def on_release(self) -> None:
@@ -43,17 +51,56 @@ class App:
audio = self.recorder.stop()
if audio is None:
logger.info("Keine Audio-Daten, übersprungen")
self.tray.set_state(AppState.WAITING)
return
logger.info("Transkribiere...")
self.tray.set_state(AppState.TRANSCRIBING)
text = self.transcriber.transcribe(audio)
if text:
await self.inserter.insert(text)
self.tray.set_state(AppState.WAITING)
def _quit(self) -> None:
"""Beendet die Anwendung sauber."""
if self._loop is not None:
self._loop.call_soon_threadsafe(self._loop.stop)
def _open_settings(self) -> None:
"""Öffnet den Einstellungs-Dialog."""
from whisper_local.tray._settings import SettingsDialog
SettingsDialog(config=self._config, on_save=self._on_config_reload).open()
def _on_config_reload(self, new_config: Config) -> None:
"""Übernimmt neue Konfiguration ohne App-Neustart."""
self._config = new_config
self.recorder = Recorder(
sample_rate=new_config.sample_rate,
channels=new_config.channels,
min_duration=new_config.min_duration,
device=new_config.microphone or None,
)
if self._loop is not None:
asyncio.run_coroutine_threadsafe(
self._restart_hotkey(new_config.hotkey), self._loop
)
async def _restart_hotkey(self, key_name: str) -> None:
"""Stoppt den alten Hotkey-Listener und startet einen neuen."""
self.hotkey.stop()
await asyncio.sleep(0.1)
self.hotkey = create_listener(key_name=key_name)
self.hotkey.on_press = self.on_press
self.hotkey.on_release = self.on_release
self._hotkey_task = asyncio.create_task(self.hotkey.listen())
async def run(self) -> None:
"""Startet den Hauptloop."""
self._loop = asyncio.get_running_loop()
logger.info("whisper-local gestartet, warte auf Hotkey...")
await self.hotkey.listen()
self.tray.start()
self._hotkey_task = asyncio.create_task(self.hotkey.listen())
await self._hotkey_task
def main():