From 6c678fbcfb07b2d6cdf7bb540fd3732fbb558c37 Mon Sep 17 00:00:00 2001 From: Vitali Graf Date: Wed, 8 Apr 2026 10:53:09 +0200 Subject: [PATCH] fix: suppress key-repeat events in pynput hotkey listener Holding the hotkey caused dozens of on_press callbacks due to OS key-repeat. Added a _pressed guard flag so only the first press and actual release fire. Co-Authored-By: Claude Opus 4.6 --- tests/test_hotkey.py | 23 +++++++++++++++++++++++ whisper_local/hotkey/_pynput.py | 7 +++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/test_hotkey.py b/tests/test_hotkey.py index 0c48476..e938247 100644 --- a/tests/test_hotkey.py +++ b/tests/test_hotkey.py @@ -89,3 +89,26 @@ class TestPynputHotkeyListener: listener = PynputHotkeyListener("KEY_F12") await listener._handle_key_event(key_down=True) await listener._handle_key_event(key_down=False) + + def test_key_repeat_ignored(self): + from whisper_local.hotkey._pynput import PynputHotkeyListener + from pynput.keyboard import Key + listener = PynputHotkeyListener("KEY_F12") + listener._loop = MagicMock() + + # Erstes Press — wird durchgelassen + listener._on_press(Key.f12) + assert listener._loop.call_soon_threadsafe.call_count == 1 + + # Wiederholte Presses (Key-Repeat) — werden ignoriert + listener._on_press(Key.f12) + listener._on_press(Key.f12) + assert listener._loop.call_soon_threadsafe.call_count == 1 + + # Release — wird durchgelassen + listener._on_release(Key.f12) + assert listener._loop.call_soon_threadsafe.call_count == 2 + + # Erneutes Release ohne Press — wird ignoriert + listener._on_release(Key.f12) + assert listener._loop.call_soon_threadsafe.call_count == 2 diff --git a/whisper_local/hotkey/_pynput.py b/whisper_local/hotkey/_pynput.py index 411659a..cc9adfc 100644 --- a/whisper_local/hotkey/_pynput.py +++ b/whisper_local/hotkey/_pynput.py @@ -36,6 +36,7 @@ class PynputHotkeyListener: self.on_press: AsyncCallback | None = None self.on_release: AsyncCallback | None = None self._loop: asyncio.AbstractEventLoop | None = None + self._pressed = False async def _handle_key_event(self, key_down: bool) -> None: """Ruft den passenden Callback auf.""" @@ -54,12 +55,14 @@ class PynputHotkeyListener: ) def _on_press(self, key) -> None: - if key == self._target_key: + if key == self._target_key and not self._pressed: + self._pressed = True logger.debug("%s gedrückt", self.key_name) self._schedule_callback(key_down=True) def _on_release(self, key) -> None: - if key == self._target_key: + if key == self._target_key and self._pressed: + self._pressed = False logger.debug("%s losgelassen", self.key_name) self._schedule_callback(key_down=False)