fix: add pywin32 dep, move Controller into class, wrap keyboard in to_thread
This commit is contained in:
@@ -8,6 +8,7 @@ dependencies = [
|
|||||||
"numpy>=2.0.0",
|
"numpy>=2.0.0",
|
||||||
"evdev>=1.7.0; sys_platform == 'linux'",
|
"evdev>=1.7.0; sys_platform == 'linux'",
|
||||||
"pynput>=1.7.0; sys_platform == 'win32'",
|
"pynput>=1.7.0; sys_platform == 'win32'",
|
||||||
|
"pywin32>=306; sys_platform == 'win32'",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ class TestWaylandInserter:
|
|||||||
|
|
||||||
class TestWin32Inserter:
|
class TestWin32Inserter:
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("whisper_local.inserter._win32.keyboard_controller")
|
@patch("whisper_local.inserter._win32.Win32Inserter._send_paste")
|
||||||
@patch("whisper_local.inserter._win32.Win32Inserter._get_clipboard", return_value="alter clipboard")
|
@patch("whisper_local.inserter._win32.Win32Inserter._get_clipboard", return_value="alter clipboard")
|
||||||
@patch("whisper_local.inserter._win32.Win32Inserter._set_clipboard")
|
@patch("whisper_local.inserter._win32.Win32Inserter._set_clipboard")
|
||||||
async def test_insert_text(self, mock_set, mock_get, mock_kb):
|
async def test_insert_text(self, mock_set, mock_get, mock_paste):
|
||||||
from whisper_local.inserter._win32 import Win32Inserter
|
from whisper_local.inserter._win32 import Win32Inserter
|
||||||
|
|
||||||
inserter = Win32Inserter()
|
inserter = Win32Inserter()
|
||||||
@@ -75,7 +75,9 @@ class TestWin32Inserter:
|
|||||||
|
|
||||||
# Clipboard wurde gesichert
|
# Clipboard wurde gesichert
|
||||||
mock_get.assert_called_once()
|
mock_get.assert_called_once()
|
||||||
# Text wurde in Clipboard gesetzt, dann Ctrl+V, dann Restore
|
# Ctrl+V wurde simuliert
|
||||||
|
mock_paste.assert_called_once()
|
||||||
|
# Text wurde in Clipboard gesetzt, dann Restore
|
||||||
assert mock_set.call_count == 2
|
assert mock_set.call_count == 2
|
||||||
mock_set.assert_any_call("Hallo Welt")
|
mock_set.assert_any_call("Hallo Welt")
|
||||||
mock_set.assert_any_call("alter clipboard")
|
mock_set.assert_any_call("alter clipboard")
|
||||||
@@ -88,14 +90,12 @@ class TestWin32Inserter:
|
|||||||
await inserter.insert("")
|
await inserter.insert("")
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("whisper_local.inserter._win32.keyboard_controller")
|
@patch("whisper_local.inserter._win32.Win32Inserter._send_paste", side_effect=OSError("keyboard error"))
|
||||||
@patch("whisper_local.inserter._win32.Win32Inserter._get_clipboard", return_value="original")
|
@patch("whisper_local.inserter._win32.Win32Inserter._get_clipboard", return_value="original")
|
||||||
@patch("whisper_local.inserter._win32.Win32Inserter._set_clipboard")
|
@patch("whisper_local.inserter._win32.Win32Inserter._set_clipboard")
|
||||||
async def test_clipboard_restored_on_error(self, mock_set, mock_get, mock_kb):
|
async def test_clipboard_restored_on_error(self, mock_set, mock_get, mock_paste):
|
||||||
from whisper_local.inserter._win32 import Win32Inserter
|
from whisper_local.inserter._win32 import Win32Inserter
|
||||||
|
|
||||||
mock_kb.press.side_effect = OSError("keyboard error")
|
|
||||||
|
|
||||||
inserter = Win32Inserter()
|
inserter = Win32Inserter()
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
await inserter.insert("Test")
|
await inserter.insert("Test")
|
||||||
|
|||||||
@@ -517,6 +517,19 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" },
|
{ url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pywin32"
|
||||||
|
version = "311"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.3"
|
version = "6.0.3"
|
||||||
@@ -692,6 +705,7 @@ dependencies = [
|
|||||||
{ name = "faster-whisper" },
|
{ name = "faster-whisper" },
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
{ name = "pynput", marker = "sys_platform == 'win32'" },
|
{ name = "pynput", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "pywin32", marker = "sys_platform == 'win32'" },
|
||||||
{ name = "sounddevice" },
|
{ name = "sounddevice" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -707,6 +721,7 @@ requires-dist = [
|
|||||||
{ name = "faster-whisper", specifier = ">=1.1.0" },
|
{ name = "faster-whisper", specifier = ">=1.1.0" },
|
||||||
{ name = "numpy", specifier = ">=2.0.0" },
|
{ name = "numpy", specifier = ">=2.0.0" },
|
||||||
{ name = "pynput", marker = "sys_platform == 'win32'", specifier = ">=1.7.0" },
|
{ name = "pynput", marker = "sys_platform == 'win32'", specifier = ">=1.7.0" },
|
||||||
|
{ name = "pywin32", marker = "sys_platform == 'win32'", specifier = ">=306" },
|
||||||
{ name = "sounddevice", specifier = ">=0.5.0" },
|
{ name = "sounddevice", specifier = ">=0.5.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ from pynput.keyboard import Controller, Key
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
keyboard_controller = Controller()
|
|
||||||
|
|
||||||
PASTE_DELAY = 0.2
|
PASTE_DELAY = 0.2
|
||||||
|
|
||||||
|
|
||||||
class Win32Inserter:
|
class Win32Inserter:
|
||||||
|
def __init__(self):
|
||||||
|
self._keyboard = Controller()
|
||||||
|
|
||||||
def _get_clipboard(self) -> str:
|
def _get_clipboard(self) -> str:
|
||||||
"""Liest den aktuellen Clipboard-Inhalt (Text)."""
|
"""Liest den aktuellen Clipboard-Inhalt (Text)."""
|
||||||
import win32clipboard
|
import win32clipboard
|
||||||
@@ -38,6 +39,13 @@ class Win32Inserter:
|
|||||||
finally:
|
finally:
|
||||||
win32clipboard.CloseClipboard()
|
win32clipboard.CloseClipboard()
|
||||||
|
|
||||||
|
def _send_paste(self) -> None:
|
||||||
|
"""Simuliert Ctrl+V."""
|
||||||
|
self._keyboard.press(Key.ctrl)
|
||||||
|
self._keyboard.press('v')
|
||||||
|
self._keyboard.release('v')
|
||||||
|
self._keyboard.release(Key.ctrl)
|
||||||
|
|
||||||
async def insert(self, text: str) -> None:
|
async def insert(self, text: str) -> None:
|
||||||
"""Fügt Text ins aktive Fenster ein via Clipboard + Ctrl+V."""
|
"""Fügt Text ins aktive Fenster ein via Clipboard + Ctrl+V."""
|
||||||
if not text:
|
if not text:
|
||||||
@@ -48,12 +56,7 @@ class Win32Inserter:
|
|||||||
try:
|
try:
|
||||||
await asyncio.to_thread(self._set_clipboard, text)
|
await asyncio.to_thread(self._set_clipboard, text)
|
||||||
await asyncio.sleep(0.05)
|
await asyncio.sleep(0.05)
|
||||||
|
await asyncio.to_thread(self._send_paste)
|
||||||
keyboard_controller.press(Key.ctrl)
|
|
||||||
keyboard_controller.press('v')
|
|
||||||
keyboard_controller.release('v')
|
|
||||||
keyboard_controller.release(Key.ctrl)
|
|
||||||
|
|
||||||
await asyncio.sleep(PASTE_DELAY)
|
await asyncio.sleep(PASTE_DELAY)
|
||||||
finally:
|
finally:
|
||||||
await asyncio.to_thread(self._set_clipboard, old_clipboard)
|
await asyncio.to_thread(self._set_clipboard, old_clipboard)
|
||||||
|
|||||||
Reference in New Issue
Block a user