feat: add Win32 inserter backend with clipboard + Ctrl+V
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,3 +60,45 @@ class TestWaylandInserter:
|
|||||||
await inserter.insert("Test")
|
await inserter.insert("Test")
|
||||||
|
|
||||||
assert call_count == 4
|
assert call_count == 4
|
||||||
|
|
||||||
|
|
||||||
|
class TestWin32Inserter:
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@patch("whisper_local.inserter._win32.keyboard_controller")
|
||||||
|
@patch("whisper_local.inserter._win32.Win32Inserter._get_clipboard", return_value="alter clipboard")
|
||||||
|
@patch("whisper_local.inserter._win32.Win32Inserter._set_clipboard")
|
||||||
|
async def test_insert_text(self, mock_set, mock_get, mock_kb):
|
||||||
|
from whisper_local.inserter._win32 import Win32Inserter
|
||||||
|
|
||||||
|
inserter = Win32Inserter()
|
||||||
|
await inserter.insert("Hallo Welt")
|
||||||
|
|
||||||
|
# Clipboard wurde gesichert
|
||||||
|
mock_get.assert_called_once()
|
||||||
|
# Text wurde in Clipboard gesetzt, dann Ctrl+V, dann Restore
|
||||||
|
assert mock_set.call_count == 2
|
||||||
|
mock_set.assert_any_call("Hallo Welt")
|
||||||
|
mock_set.assert_any_call("alter clipboard")
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_insert_empty_does_nothing(self):
|
||||||
|
from whisper_local.inserter._win32 import Win32Inserter
|
||||||
|
|
||||||
|
inserter = Win32Inserter()
|
||||||
|
await inserter.insert("")
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@patch("whisper_local.inserter._win32.keyboard_controller")
|
||||||
|
@patch("whisper_local.inserter._win32.Win32Inserter._get_clipboard", return_value="original")
|
||||||
|
@patch("whisper_local.inserter._win32.Win32Inserter._set_clipboard")
|
||||||
|
async def test_clipboard_restored_on_error(self, mock_set, mock_get, mock_kb):
|
||||||
|
from whisper_local.inserter._win32 import Win32Inserter
|
||||||
|
|
||||||
|
mock_kb.press.side_effect = OSError("keyboard error")
|
||||||
|
|
||||||
|
inserter = Win32Inserter()
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
await inserter.insert("Test")
|
||||||
|
|
||||||
|
# Clipboard muss trotzdem wiederhergestellt werden
|
||||||
|
mock_set.assert_called_with("original")
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
"""Text-Einfügung via pynput Clipboard + Ctrl+V (Windows)."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pynput.keyboard import Controller, Key
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
keyboard_controller = Controller()
|
||||||
|
|
||||||
|
PASTE_DELAY = 0.2
|
||||||
|
|
||||||
|
|
||||||
|
class Win32Inserter:
|
||||||
|
def _get_clipboard(self) -> str:
|
||||||
|
"""Liest den aktuellen Clipboard-Inhalt (Text)."""
|
||||||
|
import win32clipboard
|
||||||
|
try:
|
||||||
|
win32clipboard.OpenClipboard()
|
||||||
|
try:
|
||||||
|
data = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
|
||||||
|
return data
|
||||||
|
except TypeError:
|
||||||
|
return ""
|
||||||
|
finally:
|
||||||
|
win32clipboard.CloseClipboard()
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _set_clipboard(self, text: str) -> None:
|
||||||
|
"""Setzt den Clipboard-Inhalt."""
|
||||||
|
import win32clipboard
|
||||||
|
win32clipboard.OpenClipboard()
|
||||||
|
try:
|
||||||
|
win32clipboard.EmptyClipboard()
|
||||||
|
win32clipboard.SetClipboardText(text, win32clipboard.CF_UNICODETEXT)
|
||||||
|
finally:
|
||||||
|
win32clipboard.CloseClipboard()
|
||||||
|
|
||||||
|
async def insert(self, text: str) -> None:
|
||||||
|
"""Fügt Text ins aktive Fenster ein via Clipboard + Ctrl+V."""
|
||||||
|
if not text:
|
||||||
|
return
|
||||||
|
|
||||||
|
old_clipboard = await asyncio.to_thread(self._get_clipboard)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await asyncio.to_thread(self._set_clipboard, text)
|
||||||
|
await asyncio.sleep(0.05)
|
||||||
|
|
||||||
|
keyboard_controller.press(Key.ctrl)
|
||||||
|
keyboard_controller.press('v')
|
||||||
|
keyboard_controller.release('v')
|
||||||
|
keyboard_controller.release(Key.ctrl)
|
||||||
|
|
||||||
|
await asyncio.sleep(PASTE_DELAY)
|
||||||
|
finally:
|
||||||
|
await asyncio.to_thread(self._set_clipboard, old_clipboard)
|
||||||
|
|
||||||
|
logger.info("Text eingefügt (%d Zeichen)", len(text))
|
||||||
Reference in New Issue
Block a user