feat: add Win32TrayApp and NoOpTray with state management
This commit is contained in:
@@ -22,3 +22,38 @@ class TestCreateIcon:
|
|||||||
waiting = create_icon(AppState.WAITING)
|
waiting = create_icon(AppState.WAITING)
|
||||||
recording = create_icon(AppState.RECORDING)
|
recording = create_icon(AppState.RECORDING)
|
||||||
assert waiting.tobytes() != recording.tobytes()
|
assert waiting.tobytes() != recording.tobytes()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.platform != "win32", reason="Tray nur auf Windows")
|
||||||
|
class TestWin32TrayApp:
|
||||||
|
def test_set_state_updates_icon(self):
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
from whisper_local.tray._tray import AppState, Win32TrayApp
|
||||||
|
|
||||||
|
app = Win32TrayApp(on_settings=MagicMock(), on_quit=MagicMock())
|
||||||
|
|
||||||
|
mock_icon = MagicMock()
|
||||||
|
app._icon = mock_icon
|
||||||
|
|
||||||
|
app.set_state(AppState.RECORDING)
|
||||||
|
|
||||||
|
assert mock_icon.icon is not None
|
||||||
|
|
||||||
|
def test_set_state_before_start_is_safe(self):
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
from whisper_local.tray._tray import AppState, Win32TrayApp
|
||||||
|
|
||||||
|
app = Win32TrayApp(on_settings=MagicMock(), on_quit=MagicMock())
|
||||||
|
app.set_state(AppState.WAITING) # kein Fehler, _icon ist None
|
||||||
|
|
||||||
|
|
||||||
|
class TestNoOpTray:
|
||||||
|
def test_start_does_nothing(self):
|
||||||
|
from whisper_local.tray._tray import AppState, NoOpTray
|
||||||
|
tray = NoOpTray()
|
||||||
|
tray.start() # kein Fehler
|
||||||
|
|
||||||
|
def test_set_state_does_nothing(self):
|
||||||
|
from whisper_local.tray._tray import AppState, NoOpTray
|
||||||
|
tray = NoOpTray()
|
||||||
|
tray.set_state(AppState.RECORDING) # kein Fehler
|
||||||
|
|||||||
@@ -1,9 +1,62 @@
|
|||||||
"""Tray-App und App-Zustände für whisper-local (Windows)."""
|
"""Tray-App und App-Zustände für whisper-local (Windows)."""
|
||||||
|
|
||||||
import enum
|
import enum
|
||||||
|
import threading
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
class AppState(enum.Enum):
|
class AppState(enum.Enum):
|
||||||
WAITING = "waiting"
|
WAITING = "waiting"
|
||||||
RECORDING = "recording"
|
RECORDING = "recording"
|
||||||
TRANSCRIBING = "transcribing"
|
TRANSCRIBING = "transcribing"
|
||||||
|
|
||||||
|
|
||||||
|
class Win32TrayApp:
|
||||||
|
"""Tray-Icon via pystray für Windows."""
|
||||||
|
|
||||||
|
def __init__(self, on_settings: Callable[[], None], on_quit: Callable[[], None]):
|
||||||
|
self._on_settings = on_settings
|
||||||
|
self._on_quit = on_quit
|
||||||
|
self._icon = None
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
"""Startet pystray in einem Daemon-Thread."""
|
||||||
|
import pystray
|
||||||
|
from whisper_local.tray._icon import create_icon
|
||||||
|
|
||||||
|
menu = pystray.Menu(
|
||||||
|
pystray.MenuItem("Einstellungen", self._menu_settings),
|
||||||
|
pystray.MenuItem("Beenden", self._menu_quit),
|
||||||
|
)
|
||||||
|
self._icon = pystray.Icon(
|
||||||
|
"whisper-local",
|
||||||
|
create_icon(AppState.WAITING),
|
||||||
|
"whisper-local",
|
||||||
|
menu,
|
||||||
|
)
|
||||||
|
thread = threading.Thread(target=self._icon.run, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def set_state(self, state: AppState) -> None:
|
||||||
|
"""Tauscht das Icon aus (thread-sicher)."""
|
||||||
|
if self._icon is not None:
|
||||||
|
from whisper_local.tray._icon import create_icon
|
||||||
|
self._icon.icon = create_icon(state)
|
||||||
|
|
||||||
|
def _menu_settings(self, icon, item) -> None:
|
||||||
|
self._on_settings()
|
||||||
|
|
||||||
|
def _menu_quit(self, icon, item) -> None:
|
||||||
|
if self._icon is not None:
|
||||||
|
self._icon.stop()
|
||||||
|
self._on_quit()
|
||||||
|
|
||||||
|
|
||||||
|
class NoOpTray:
|
||||||
|
"""Platzhalter-Implementierung für nicht-Windows-Plattformen."""
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_state(self, state: AppState) -> None:
|
||||||
|
pass
|
||||||
|
|||||||
Reference in New Issue
Block a user