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 <noreply@anthropic.com>
This commit is contained in:
@@ -89,3 +89,26 @@ class TestPynputHotkeyListener:
|
|||||||
listener = PynputHotkeyListener("KEY_F12")
|
listener = PynputHotkeyListener("KEY_F12")
|
||||||
await listener._handle_key_event(key_down=True)
|
await listener._handle_key_event(key_down=True)
|
||||||
await listener._handle_key_event(key_down=False)
|
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
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class PynputHotkeyListener:
|
|||||||
self.on_press: AsyncCallback | None = None
|
self.on_press: AsyncCallback | None = None
|
||||||
self.on_release: AsyncCallback | None = None
|
self.on_release: AsyncCallback | None = None
|
||||||
self._loop: asyncio.AbstractEventLoop | None = None
|
self._loop: asyncio.AbstractEventLoop | None = None
|
||||||
|
self._pressed = False
|
||||||
|
|
||||||
async def _handle_key_event(self, key_down: bool) -> None:
|
async def _handle_key_event(self, key_down: bool) -> None:
|
||||||
"""Ruft den passenden Callback auf."""
|
"""Ruft den passenden Callback auf."""
|
||||||
@@ -54,12 +55,14 @@ class PynputHotkeyListener:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _on_press(self, key) -> None:
|
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)
|
logger.debug("%s gedrückt", self.key_name)
|
||||||
self._schedule_callback(key_down=True)
|
self._schedule_callback(key_down=True)
|
||||||
|
|
||||||
def _on_release(self, key) -> None:
|
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)
|
logger.debug("%s losgelassen", self.key_name)
|
||||||
self._schedule_callback(key_down=False)
|
self._schedule_callback(key_down=False)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user