autoxkit 2.4.9__tar.gz → 2.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autoxkit-2.5.2/MANIFEST.in +5 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/PKG-INFO +1 -1
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/__init__.py +3 -3
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/control.py +1 -1
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/streams.py +1 -1
- autoxkit-2.5.2/autoxkit/hook/_autoxhook.dll +0 -0
- autoxkit-2.4.9/autoxkit/hook/hook_listener.py → autoxkit-2.5.2/autoxkit/hook/_ctypes_fallback.py +23 -16
- autoxkit-2.5.2/autoxkit/hook/_native.py +94 -0
- autoxkit-2.5.2/autoxkit/hook/hook_listener.py +306 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/window/window.py +18 -11
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/window/window_action.py +41 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit.egg-info/PKG-INFO +1 -1
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit.egg-info/SOURCES.txt +9 -1
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit.egg-info/top_level.txt +0 -1
- autoxkit-2.5.2/csrc/autoxhook.c +421 -0
- autoxkit-2.5.2/csrc/autoxhook.h +100 -0
- autoxkit-2.5.2/csrc/build_dll.ps1 +36 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/pyproject.toml +12 -2
- autoxkit-2.5.2/scripts/bench_hook.py +200 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/LICENSE.txt +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/README.md +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/__init__.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/adb.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/binary.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/client.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/control_backup.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/models.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/constants.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/hook/__init__.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/hook/event.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/hook/hotkey_listener.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/match/__init__.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/match/match.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/mousekey/__init__.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/mousekey/input.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/mousekey/keyboard.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/mousekey/mouse.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/utils.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/window/__init__.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/window/window_match.py +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit.egg-info/dependency_links.txt +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit.egg-info/requires.txt +0 -0
- {autoxkit-2.4.9 → autoxkit-2.5.2}/setup.cfg +0 -0
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from .constants import Hex_Key_Code, Hex_Mouse_Code, Hex_Hook_Code
|
|
2
|
-
|
|
3
|
-
__all__ = ["Hex_Key_Code", "Hex_Mouse_Code", "Hex_Hook_Code"]
|
|
1
|
+
from .constants import Hex_Key_Code, Hex_Mouse_Code, Hex_Hook_Code
|
|
2
|
+
|
|
3
|
+
__all__ = ["Hex_Key_Code", "Hex_Mouse_Code", "Hex_Hook_Code"]
|
|
Binary file
|
autoxkit-2.4.9/autoxkit/hook/hook_listener.py → autoxkit-2.5.2/autoxkit/hook/_ctypes_fallback.py
RENAMED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# _ctypes_fallback.py
|
|
2
|
+
"""纯 ctypes 实现的 HookListener(回退路径)。
|
|
3
|
+
|
|
4
|
+
当 _autoxhook.dll 缺失、位数不匹配或加载失败时启用本实现,
|
|
5
|
+
功能与原生路径等价,性能为迁移前的基线水平。
|
|
6
|
+
核心事件链路迁移见项目根 优化方案.md 与 csrc/ 目录。
|
|
7
|
+
"""
|
|
2
8
|
import ctypes
|
|
3
|
-
|
|
9
|
+
import sys
|
|
4
10
|
import threading
|
|
5
|
-
import
|
|
11
|
+
from ctypes import CFUNCTYPE, POINTER, Structure, byref, wintypes
|
|
12
|
+
|
|
6
13
|
from .event import KeyEvent, MouseEvent
|
|
7
14
|
from ..constants import Hex_Hook_Code
|
|
8
15
|
|
|
@@ -66,7 +73,7 @@ user32.GetMessageW.restype = wintypes.BOOL
|
|
|
66
73
|
user32.PostThreadMessageW.argtypes = [wintypes.DWORD, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM]
|
|
67
74
|
user32.PostThreadMessageW.restype = wintypes.BOOL
|
|
68
75
|
|
|
69
|
-
|
|
76
|
+
|
|
70
77
|
class HookListener:
|
|
71
78
|
"""
|
|
72
79
|
每个实例可以独立运行、独立添加多个回调(keydown/keyup/mousedown/mouseup)
|
|
@@ -167,9 +174,9 @@ class HookListener:
|
|
|
167
174
|
if result is True:
|
|
168
175
|
return 1 # 截断事件传播
|
|
169
176
|
except ValueError as e:
|
|
170
|
-
print(f"[hook_listener] ValueError in keydown callback: {e}", file=
|
|
177
|
+
print(f"[hook_listener] ValueError in keydown callback: {e}", file=sys.stderr)
|
|
171
178
|
except Exception as e:
|
|
172
|
-
print(f"[hook_listener] Exception in keydown callback: {e}", file=
|
|
179
|
+
print(f"[hook_listener] Exception in keydown callback: {e}", file=sys.stderr)
|
|
173
180
|
elif wParam in (HHC["KeyUp"], HHC["SysKeyUp"]):
|
|
174
181
|
event = KeyEvent('KeyUp', kbd.vkCode)
|
|
175
182
|
for cb in self._on_keyup:
|
|
@@ -178,11 +185,11 @@ class HookListener:
|
|
|
178
185
|
if result is True:
|
|
179
186
|
return 1 # 截断事件传播
|
|
180
187
|
except ValueError as e:
|
|
181
|
-
print(f"[hook_listener] ValueError in keyup callback: {e}", file=
|
|
188
|
+
print(f"[hook_listener] ValueError in keyup callback: {e}", file=sys.stderr)
|
|
182
189
|
except Exception as e:
|
|
183
|
-
print(f"[hook_listener] Exception in keyup callback: {e}", file=
|
|
190
|
+
print(f"[hook_listener] Exception in keyup callback: {e}", file=sys.stderr)
|
|
184
191
|
except Exception as e:
|
|
185
|
-
print(f"[hook_listener] Exception in _keyboard_proc: {e}", file=
|
|
192
|
+
print(f"[hook_listener] Exception in _keyboard_proc: {e}", file=sys.stderr)
|
|
186
193
|
|
|
187
194
|
return user32.CallNextHookEx(self.keyboard_hook, nCode, wParam, lParam)
|
|
188
195
|
|
|
@@ -202,7 +209,7 @@ class HookListener:
|
|
|
202
209
|
if result is True:
|
|
203
210
|
return 1 # 截断事件传播
|
|
204
211
|
except Exception as e:
|
|
205
|
-
print(f"[hook_listener] Exception in mousedown callback: {e}", file=
|
|
212
|
+
print(f"[hook_listener] Exception in mousedown callback: {e}", file=sys.stderr)
|
|
206
213
|
|
|
207
214
|
elif wParam in (HHC["MLeftUp"], HHC["MRightUp"], HHC["MiddleUp"], HHC["XUp"]):
|
|
208
215
|
button = self._get_mouse_button(wParam, ms.mouseData)
|
|
@@ -213,7 +220,7 @@ class HookListener:
|
|
|
213
220
|
if result is True:
|
|
214
221
|
return 1 # 截断事件传播
|
|
215
222
|
except Exception as e:
|
|
216
|
-
print(f"[hook_listener] Exception in mouseup callback: {e}", file=
|
|
223
|
+
print(f"[hook_listener] Exception in mouseup callback: {e}", file=sys.stderr)
|
|
217
224
|
|
|
218
225
|
elif wParam == HHC["MWheel"]:
|
|
219
226
|
delta = ctypes.c_short(ms.mouseData >> 16).value / 120
|
|
@@ -226,7 +233,7 @@ class HookListener:
|
|
|
226
233
|
if result is True:
|
|
227
234
|
blocked = True
|
|
228
235
|
except Exception as e:
|
|
229
|
-
print(f"[hook_listener] Exception in mousedown callback: {e}", file=
|
|
236
|
+
print(f"[hook_listener] Exception in mousedown callback: {e}", file=sys.stderr)
|
|
230
237
|
|
|
231
238
|
event = MouseEvent("MouseUp", "MUWheel", x, y, distance=delta)
|
|
232
239
|
for cb in self._on_mouseup:
|
|
@@ -235,7 +242,7 @@ class HookListener:
|
|
|
235
242
|
if result is True:
|
|
236
243
|
blocked = True
|
|
237
244
|
except Exception as e:
|
|
238
|
-
print(f"[hook_listener] Exception in mouseup callback: {e}", file=
|
|
245
|
+
print(f"[hook_listener] Exception in mouseup callback: {e}", file=sys.stderr)
|
|
239
246
|
|
|
240
247
|
if blocked:
|
|
241
248
|
return 1 # 截断事件传播
|
|
@@ -249,7 +256,7 @@ class HookListener:
|
|
|
249
256
|
if result is True:
|
|
250
257
|
blocked = True
|
|
251
258
|
except Exception as e:
|
|
252
|
-
print(f"[hook_listener] Exception in mousedown callback: {e}", file=
|
|
259
|
+
print(f"[hook_listener] Exception in mousedown callback: {e}", file=sys.stderr)
|
|
253
260
|
|
|
254
261
|
event = MouseEvent("MouseUp", "MDWheel", x, y, distance=delta)
|
|
255
262
|
for cb in self._on_mouseup:
|
|
@@ -258,13 +265,13 @@ class HookListener:
|
|
|
258
265
|
if result is True:
|
|
259
266
|
blocked = True
|
|
260
267
|
except Exception as e:
|
|
261
|
-
print(f"[hook_listener] Exception in mouseup callback: {e}", file=
|
|
268
|
+
print(f"[hook_listener] Exception in mouseup callback: {e}", file=sys.stderr)
|
|
262
269
|
|
|
263
270
|
if blocked:
|
|
264
271
|
return 1 # 截断事件传播
|
|
265
272
|
|
|
266
273
|
except Exception as e:
|
|
267
|
-
print(f"[hook_listener] Exception in _mouse_proc: {e}", file=
|
|
274
|
+
print(f"[hook_listener] Exception in _mouse_proc: {e}", file=sys.stderr)
|
|
268
275
|
|
|
269
276
|
return user32.CallNextHookEx(self.mouse_hook, nCode, wParam, lParam)
|
|
270
277
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# _native.py
|
|
2
|
+
"""autoxhook.dll 的 ctypes 绑定层。
|
|
3
|
+
|
|
4
|
+
DLL 为纯 Win32 C 实现(源码见项目 csrc/ 目录),不链接任何 Python
|
|
5
|
+
运行时,因此一份 DLL 可跨 Python 3.10~3.13+ 所有版本使用。
|
|
6
|
+
|
|
7
|
+
Python 侧通过 WINFUNCTYPE 函数指针把回调交给 DLL;DLL 在 C 回调内
|
|
8
|
+
完成结构体解析与订阅掩码短路后,仅以纯整数参数调用该指针,
|
|
9
|
+
GIL 的获取/释放由 ctypes thunk 自动处理。
|
|
10
|
+
"""
|
|
11
|
+
import ctypes
|
|
12
|
+
import os
|
|
13
|
+
from ctypes import POINTER, WINFUNCTYPE, c_char_p, c_int, c_uint
|
|
14
|
+
|
|
15
|
+
# 回调原型:返回 1 = 拦截事件(吞掉),0 = 放行
|
|
16
|
+
# 键盘:(msg, vk, scan, flags, time)
|
|
17
|
+
KEY_CB = WINFUNCTYPE(c_int, c_int, c_int, c_int, c_int, c_uint)
|
|
18
|
+
# 鼠标:(msg, x, y, data, flags, time)
|
|
19
|
+
MOUSE_CB = WINFUNCTYPE(c_int, c_int, c_int, c_int, c_int, c_int, c_uint)
|
|
20
|
+
|
|
21
|
+
# 事件掩码位(必须与 csrc/autoxhook.h 保持一致)
|
|
22
|
+
AXH_KEY_DOWN = 1 << 0
|
|
23
|
+
AXH_KEY_UP = 1 << 1
|
|
24
|
+
AXH_M_LDOWN = 1 << 0
|
|
25
|
+
AXH_M_LUP = 1 << 1
|
|
26
|
+
AXH_M_RDOWN = 1 << 2
|
|
27
|
+
AXH_M_RUP = 1 << 3
|
|
28
|
+
AXH_M_MDOWN = 1 << 4
|
|
29
|
+
AXH_M_MUP = 1 << 5
|
|
30
|
+
AXH_M_WHEEL = 1 << 6
|
|
31
|
+
AXH_M_XDOWN = 1 << 7
|
|
32
|
+
AXH_M_XUP = 1 << 8
|
|
33
|
+
AXH_M_MOVE = 1 << 9
|
|
34
|
+
|
|
35
|
+
# axh_start 错误码 → 说明
|
|
36
|
+
START_ERRORS = {
|
|
37
|
+
-1: "无空闲监听器槽位",
|
|
38
|
+
-2: "DLL 内部创建线程失败",
|
|
39
|
+
-3: "DLL 钩子初始化超时",
|
|
40
|
+
-4: "键盘钩子安装失败",
|
|
41
|
+
-5: "鼠标钩子安装失败",
|
|
42
|
+
-6: "hook_keyboard 与 hook_mouse 不能同时为 0",
|
|
43
|
+
-7: "DLL 内部创建同步事件失败",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def load_dll():
|
|
48
|
+
"""加载 _autoxhook.dll 并声明导出函数原型。
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
OSError: DLL 缺失、位数不匹配或加载失败(调用方应回退到纯 ctypes 实现)
|
|
52
|
+
"""
|
|
53
|
+
dll_path = os.path.join(
|
|
54
|
+
os.path.dirname(os.path.abspath(__file__)), "_autoxhook.dll"
|
|
55
|
+
)
|
|
56
|
+
dll = ctypes.WinDLL(dll_path)
|
|
57
|
+
|
|
58
|
+
dll.axh_start.argtypes = [c_int, c_int, KEY_CB, MOUSE_CB]
|
|
59
|
+
dll.axh_start.restype = c_int
|
|
60
|
+
dll.axh_stop.argtypes = [c_int]
|
|
61
|
+
dll.axh_stop.restype = c_int
|
|
62
|
+
dll.axh_is_running.argtypes = [c_int]
|
|
63
|
+
dll.axh_is_running.restype = c_int
|
|
64
|
+
dll.axh_set_masks.argtypes = [c_int, c_uint, c_uint]
|
|
65
|
+
dll.axh_set_masks.restype = None
|
|
66
|
+
dll.axh_get_key_state.argtypes = [c_uint]
|
|
67
|
+
dll.axh_get_key_state.restype = c_uint
|
|
68
|
+
dll.axh_get_cursor_pos.argtypes = [POINTER(c_int), POINTER(c_int)]
|
|
69
|
+
dll.axh_get_cursor_pos.restype = c_int
|
|
70
|
+
dll.axh_version.argtypes = []
|
|
71
|
+
dll.axh_version.restype = c_char_p
|
|
72
|
+
return dll
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# WM 消息值 → 掩码位(模块加载期构建,热路径零开销)
|
|
76
|
+
# 与 constants.Hex_Hook_Code 对应:KeyDown=0x0100 SysKeyDown=0x0104 等
|
|
77
|
+
KEY_MSG_BIT = {
|
|
78
|
+
0x0100: AXH_KEY_DOWN, # WM_KEYDOWN
|
|
79
|
+
0x0104: AXH_KEY_DOWN, # WM_SYSKEYDOWN
|
|
80
|
+
0x0101: AXH_KEY_UP, # WM_KEYUP
|
|
81
|
+
0x0105: AXH_KEY_UP, # WM_SYSKEYUP
|
|
82
|
+
}
|
|
83
|
+
MOUSE_MSG_BIT = {
|
|
84
|
+
0x0201: AXH_M_LDOWN, # WM_LBUTTONDOWN
|
|
85
|
+
0x0202: AXH_M_LUP, # WM_LBUTTONUP
|
|
86
|
+
0x0204: AXH_M_RDOWN, # WM_RBUTTONDOWN
|
|
87
|
+
0x0205: AXH_M_RUP, # WM_RBUTTONUP
|
|
88
|
+
0x0207: AXH_M_MDOWN, # WM_MBUTTONDOWN
|
|
89
|
+
0x0208: AXH_M_MUP, # WM_MBUTTONUP
|
|
90
|
+
0x020A: AXH_M_WHEEL, # WM_MOUSEWHEEL
|
|
91
|
+
0x020B: AXH_M_XDOWN, # WM_XBUTTONDOWN
|
|
92
|
+
0x020C: AXH_M_XUP, # WM_XBUTTONUP
|
|
93
|
+
0x0200: AXH_M_MOVE, # WM_MOUSEMOVE(预留)
|
|
94
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# hook_listener.py
|
|
2
|
+
"""低级键盘/鼠标钩子监听器。
|
|
3
|
+
|
|
4
|
+
核心事件链路由 _autoxhook.dll(纯 Win32 C,见项目 csrc/ 目录)承担:
|
|
5
|
+
钩子安装、消息泵线程、结构体解析、订阅掩码短路、按键状态跟踪均在 C 层完成,
|
|
6
|
+
Python 只接收已解析的语义化整数参数。空订阅事件完全不进入 Python/GIL。
|
|
7
|
+
|
|
8
|
+
DLL 缺失、位数不匹配或加载失败时,自动回退到 _ctypes_fallback 的纯 ctypes
|
|
9
|
+
实现(功能等价,性能为迁移前基线)。
|
|
10
|
+
"""
|
|
11
|
+
import sys
|
|
12
|
+
import threading
|
|
13
|
+
from ctypes import POINTER, byref, c_int
|
|
14
|
+
|
|
15
|
+
from .event import KeyEvent, MouseEvent
|
|
16
|
+
from ..constants import Hex_Hook_Code
|
|
17
|
+
|
|
18
|
+
HHC = Hex_Hook_Code
|
|
19
|
+
|
|
20
|
+
# ---------- 原生 DLL 加载(失败自动回退) ----------
|
|
21
|
+
try:
|
|
22
|
+
from ._native import (
|
|
23
|
+
AXH_KEY_DOWN,
|
|
24
|
+
AXH_KEY_UP,
|
|
25
|
+
AXH_M_LDOWN,
|
|
26
|
+
AXH_M_LUP,
|
|
27
|
+
AXH_M_MDOWN,
|
|
28
|
+
AXH_M_MUP,
|
|
29
|
+
AXH_M_RDOWN,
|
|
30
|
+
AXH_M_RUP,
|
|
31
|
+
AXH_M_WHEEL,
|
|
32
|
+
AXH_M_XDOWN,
|
|
33
|
+
AXH_M_XUP,
|
|
34
|
+
KEY_CB,
|
|
35
|
+
MOUSE_CB,
|
|
36
|
+
START_ERRORS,
|
|
37
|
+
load_dll,
|
|
38
|
+
)
|
|
39
|
+
_dll = load_dll()
|
|
40
|
+
_NATIVE = True
|
|
41
|
+
except OSError:
|
|
42
|
+
_dll = None
|
|
43
|
+
_NATIVE = False
|
|
44
|
+
|
|
45
|
+
if not _NATIVE:
|
|
46
|
+
from ._ctypes_fallback import HookListener
|
|
47
|
+
else:
|
|
48
|
+
|
|
49
|
+
class HookListener:
|
|
50
|
+
"""
|
|
51
|
+
每个实例可以独立运行、独立添加多个回调(keydown/keyup/mousedown/mouseup)
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
# 回调列表(支持多个回调)
|
|
56
|
+
self._on_keydown = []
|
|
57
|
+
self._on_keyup = []
|
|
58
|
+
self._on_mousedown = []
|
|
59
|
+
self._on_mouseup = []
|
|
60
|
+
|
|
61
|
+
# 注册/启停路径的并发保护(事件热路径无锁)
|
|
62
|
+
self._handlers_lock = threading.Lock()
|
|
63
|
+
|
|
64
|
+
# DLL 监听器句柄(<0 表示未启动)
|
|
65
|
+
self._handle = -1
|
|
66
|
+
self._stop_event = threading.Event()
|
|
67
|
+
|
|
68
|
+
# 必须保存 WINFUNCTYPE 对象引用,避免被 GC 回收
|
|
69
|
+
self._key_cb = KEY_CB(self._on_key_event)
|
|
70
|
+
self._mouse_cb = MOUSE_CB(self._on_mouse_event)
|
|
71
|
+
|
|
72
|
+
# 注册回调
|
|
73
|
+
def add_handler(self, event_type: str, func):
|
|
74
|
+
"""
|
|
75
|
+
注册回调
|
|
76
|
+
Args:
|
|
77
|
+
event_type (str): 事件类型,可选值为 "keydown"、"keyup"、"mousedown"、"mouseup"
|
|
78
|
+
func (callable): 回调函数,参数为 KeyEvent 或 MouseEvent 对象
|
|
79
|
+
"""
|
|
80
|
+
if not callable(func):
|
|
81
|
+
raise ValueError("func must be a callable object")
|
|
82
|
+
|
|
83
|
+
with self._handlers_lock:
|
|
84
|
+
if event_type == "keydown":
|
|
85
|
+
self._on_keydown.append(func)
|
|
86
|
+
elif event_type == "keyup":
|
|
87
|
+
self._on_keyup.append(func)
|
|
88
|
+
elif event_type == "mousedown":
|
|
89
|
+
self._on_mousedown.append(func)
|
|
90
|
+
elif event_type == "mouseup":
|
|
91
|
+
self._on_mouseup.append(func)
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError("unknown event_type: " + str(event_type))
|
|
94
|
+
self._refresh_masks()
|
|
95
|
+
|
|
96
|
+
# 移除回调(可选)
|
|
97
|
+
def remove_handler(self, event_type: str, func):
|
|
98
|
+
"""
|
|
99
|
+
移除回调
|
|
100
|
+
Args:
|
|
101
|
+
event_type (str): 事件类型,可选值为 "keydown"、"keyup"、"mousedown"、"mouseup"
|
|
102
|
+
func (callable): 回调函数,参数为 KeyEvent 或 MouseEvent 对象
|
|
103
|
+
"""
|
|
104
|
+
if not callable(func):
|
|
105
|
+
raise ValueError("func must be a callable object")
|
|
106
|
+
|
|
107
|
+
with self._handlers_lock:
|
|
108
|
+
if event_type == "keydown":
|
|
109
|
+
target = self._on_keydown
|
|
110
|
+
elif event_type == "keyup":
|
|
111
|
+
target = self._on_keyup
|
|
112
|
+
elif event_type == "mousedown":
|
|
113
|
+
target = self._on_mousedown
|
|
114
|
+
elif event_type == "mouseup":
|
|
115
|
+
target = self._on_mouseup
|
|
116
|
+
else:
|
|
117
|
+
raise ValueError("unknown event_type: " + str(event_type))
|
|
118
|
+
try:
|
|
119
|
+
target.remove(func)
|
|
120
|
+
except ValueError:
|
|
121
|
+
raise ValueError("function not found: " + str(func))
|
|
122
|
+
self._refresh_masks()
|
|
123
|
+
|
|
124
|
+
# 根据回调列表刷新 DLL 订阅掩码(须持有 _handlers_lock)
|
|
125
|
+
def _refresh_masks(self):
|
|
126
|
+
if self._handle < 0:
|
|
127
|
+
return
|
|
128
|
+
key_mask = 0
|
|
129
|
+
if self._on_keydown:
|
|
130
|
+
key_mask |= AXH_KEY_DOWN
|
|
131
|
+
if self._on_keyup:
|
|
132
|
+
key_mask |= AXH_KEY_UP
|
|
133
|
+
mouse_mask = 0
|
|
134
|
+
if self._on_mousedown:
|
|
135
|
+
mouse_mask |= (
|
|
136
|
+
AXH_M_LDOWN | AXH_M_RDOWN | AXH_M_MDOWN | AXH_M_XDOWN
|
|
137
|
+
)
|
|
138
|
+
if self._on_mouseup:
|
|
139
|
+
mouse_mask |= AXH_M_LUP | AXH_M_RUP | AXH_M_MUP | AXH_M_XUP
|
|
140
|
+
# 滚轮同时驱动 down/up 两个回调列表,任一非空即订阅
|
|
141
|
+
if self._on_mousedown or self._on_mouseup:
|
|
142
|
+
mouse_mask |= AXH_M_WHEEL
|
|
143
|
+
_dll.axh_set_masks(self._handle, key_mask, mouse_mask)
|
|
144
|
+
|
|
145
|
+
# 获取当前鼠标位置
|
|
146
|
+
def get_mouse_position(self):
|
|
147
|
+
"""
|
|
148
|
+
获取当前鼠标位置
|
|
149
|
+
Returns:
|
|
150
|
+
tuple[int, int]: 鼠标位置 (x, y)
|
|
151
|
+
"""
|
|
152
|
+
x = c_int(0)
|
|
153
|
+
y = c_int(0)
|
|
154
|
+
if _dll.axh_get_cursor_pos(byref(x), byref(y)) == 0:
|
|
155
|
+
return (x.value, y.value)
|
|
156
|
+
import ctypes
|
|
157
|
+
|
|
158
|
+
raise ctypes.WinError(ctypes.get_last_error())
|
|
159
|
+
|
|
160
|
+
# 内部键盘回调(由 DLL 的 C 回调以纯整数参数调用,仅订阅事件会到达)
|
|
161
|
+
def _on_key_event(self, msg, vk_code, scan_code, flags, time):
|
|
162
|
+
if msg in (HHC["KeyDown"], HHC["SysKeyDown"]):
|
|
163
|
+
event = KeyEvent('KeyDown', vk_code)
|
|
164
|
+
for cb in self._on_keydown:
|
|
165
|
+
try:
|
|
166
|
+
result = cb(event)
|
|
167
|
+
if result is True:
|
|
168
|
+
return 1 # 截断事件传播
|
|
169
|
+
except ValueError as e:
|
|
170
|
+
print(f"[hook_listener] ValueError in keydown callback: {e}", file=sys.stderr)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
print(f"[hook_listener] Exception in keydown callback: {e}", file=sys.stderr)
|
|
173
|
+
else:
|
|
174
|
+
event = KeyEvent('KeyUp', vk_code)
|
|
175
|
+
for cb in self._on_keyup:
|
|
176
|
+
try:
|
|
177
|
+
result = cb(event)
|
|
178
|
+
if result is True:
|
|
179
|
+
return 1 # 截断事件传播
|
|
180
|
+
except ValueError as e:
|
|
181
|
+
print(f"[hook_listener] ValueError in keyup callback: {e}", file=sys.stderr)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print(f"[hook_listener] Exception in keyup callback: {e}", file=sys.stderr)
|
|
184
|
+
return 0 # 放行
|
|
185
|
+
|
|
186
|
+
# 内部鼠标回调(由 DLL 的 C 回调以纯整数参数调用)
|
|
187
|
+
# data 语义由 msg 决定:滚轮=有符号原始 delta;X键=1(MSide1)/2(MSide2);其余=0
|
|
188
|
+
def _on_mouse_event(self, msg, x, y, data, flags, time):
|
|
189
|
+
if msg == HHC["MWheel"]:
|
|
190
|
+
delta = data / 120
|
|
191
|
+
if delta > 0:
|
|
192
|
+
blocked = False
|
|
193
|
+
event = MouseEvent("MouseDown", "MUWheel", x, y, distance=delta)
|
|
194
|
+
for cb in self._on_mousedown:
|
|
195
|
+
try:
|
|
196
|
+
result = cb(event)
|
|
197
|
+
if result is True:
|
|
198
|
+
blocked = True
|
|
199
|
+
except Exception as e:
|
|
200
|
+
print(f"[hook_listener] Exception in mousedown callback: {e}", file=sys.stderr)
|
|
201
|
+
|
|
202
|
+
event = MouseEvent("MouseUp", "MUWheel", x, y, distance=delta)
|
|
203
|
+
for cb in self._on_mouseup:
|
|
204
|
+
try:
|
|
205
|
+
result = cb(event)
|
|
206
|
+
if result is True:
|
|
207
|
+
blocked = True
|
|
208
|
+
except Exception as e:
|
|
209
|
+
print(f"[hook_listener] Exception in mouseup callback: {e}", file=sys.stderr)
|
|
210
|
+
|
|
211
|
+
if blocked:
|
|
212
|
+
return 1 # 截断事件传播
|
|
213
|
+
|
|
214
|
+
elif delta < 0:
|
|
215
|
+
blocked = False
|
|
216
|
+
event = MouseEvent("MouseDown", "MDWheel", x, y, distance=delta)
|
|
217
|
+
for cb in self._on_mousedown:
|
|
218
|
+
try:
|
|
219
|
+
result = cb(event)
|
|
220
|
+
if result is True:
|
|
221
|
+
blocked = True
|
|
222
|
+
except Exception as e:
|
|
223
|
+
print(f"[hook_listener] Exception in mousedown callback: {e}", file=sys.stderr)
|
|
224
|
+
|
|
225
|
+
event = MouseEvent("MouseUp", "MDWheel", x, y, distance=delta)
|
|
226
|
+
for cb in self._on_mouseup:
|
|
227
|
+
try:
|
|
228
|
+
result = cb(event)
|
|
229
|
+
if result is True:
|
|
230
|
+
blocked = True
|
|
231
|
+
except Exception as e:
|
|
232
|
+
print(f"[hook_listener] Exception in mouseup callback: {e}", file=sys.stderr)
|
|
233
|
+
|
|
234
|
+
if blocked:
|
|
235
|
+
return 1 # 截断事件传播
|
|
236
|
+
|
|
237
|
+
elif msg in (HHC["MLeftDown"], HHC["MRightDown"], HHC["MiddleDown"], HHC["XDown"]):
|
|
238
|
+
button = self._get_mouse_button(msg, data)
|
|
239
|
+
event = MouseEvent("MouseDown", button, x, y)
|
|
240
|
+
for cb in self._on_mousedown:
|
|
241
|
+
try:
|
|
242
|
+
result = cb(event)
|
|
243
|
+
if result is True:
|
|
244
|
+
return 1 # 截断事件传播
|
|
245
|
+
except Exception as e:
|
|
246
|
+
print(f"[hook_listener] Exception in mousedown callback: {e}", file=sys.stderr)
|
|
247
|
+
|
|
248
|
+
elif msg in (HHC["MLeftUp"], HHC["MRightUp"], HHC["MiddleUp"], HHC["XUp"]):
|
|
249
|
+
button = self._get_mouse_button(msg, data)
|
|
250
|
+
event = MouseEvent("MouseUp", button, x, y)
|
|
251
|
+
for cb in self._on_mouseup:
|
|
252
|
+
try:
|
|
253
|
+
result = cb(event)
|
|
254
|
+
if result is True:
|
|
255
|
+
return 1 # 截断事件传播
|
|
256
|
+
except Exception as e:
|
|
257
|
+
print(f"[hook_listener] Exception in mouseup callback: {e}", file=sys.stderr)
|
|
258
|
+
|
|
259
|
+
return 0 # 放行
|
|
260
|
+
|
|
261
|
+
# 辅助函数:获取鼠标按键名称(C 侧已解析 X 键编号到 data)
|
|
262
|
+
@staticmethod
|
|
263
|
+
def _get_mouse_button(wParam, data):
|
|
264
|
+
if wParam in (HHC["MLeftDown"], HHC["MLeftUp"]):
|
|
265
|
+
return 'MLeft'
|
|
266
|
+
elif wParam in (HHC["MRightDown"], HHC["MRightUp"]):
|
|
267
|
+
return 'MRight'
|
|
268
|
+
elif wParam in (HHC["MiddleDown"], HHC["MiddleUp"]):
|
|
269
|
+
return 'Middle'
|
|
270
|
+
elif wParam in (HHC["XDown"], HHC["XUp"]):
|
|
271
|
+
return 'MSide1' if data == HHC["MSide1"] else 'MSide2'
|
|
272
|
+
|
|
273
|
+
# 启动监听(DLL 内部创建消息泵线程并安装钩子)
|
|
274
|
+
def start(self):
|
|
275
|
+
with self._handlers_lock:
|
|
276
|
+
if self._handle >= 0:
|
|
277
|
+
return
|
|
278
|
+
ret = _dll.axh_start(1, 1, self._key_cb, self._mouse_cb)
|
|
279
|
+
if ret < 0:
|
|
280
|
+
raise RuntimeError(
|
|
281
|
+
"autoxhook 启动失败: "
|
|
282
|
+
+ START_ERRORS.get(ret, f"错误码 {ret}")
|
|
283
|
+
)
|
|
284
|
+
self._handle = ret
|
|
285
|
+
self._stop_event.clear()
|
|
286
|
+
self._refresh_masks()
|
|
287
|
+
|
|
288
|
+
# 停止监听并取消钩子(DLL 内部时序:先断 Python 触达,再卸钩,再释放槽位)
|
|
289
|
+
def stop(self):
|
|
290
|
+
with self._handlers_lock:
|
|
291
|
+
if self._handle < 0:
|
|
292
|
+
self._stop_event.set()
|
|
293
|
+
return
|
|
294
|
+
_dll.axh_stop(self._handle)
|
|
295
|
+
self._handle = -1
|
|
296
|
+
self._stop_event.set()
|
|
297
|
+
|
|
298
|
+
# 阻塞等待直到停止
|
|
299
|
+
def wait(self):
|
|
300
|
+
self._stop_event.wait()
|
|
301
|
+
|
|
302
|
+
def __del__(self):
|
|
303
|
+
try:
|
|
304
|
+
self.stop()
|
|
305
|
+
except Exception:
|
|
306
|
+
pass
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import time
|
|
2
2
|
import ctypes
|
|
3
3
|
from ctypes import wintypes
|
|
4
4
|
from ctypes.wintypes import RECT
|
|
@@ -75,16 +75,23 @@ class Window(WindowAction, WindowMatch):
|
|
|
75
75
|
raise ValueError("窗口句柄未设置")
|
|
76
76
|
|
|
77
77
|
def activate_window(self):
|
|
78
|
-
"""
|
|
79
|
-
if self.hwnd:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
"""把窗口(含父窗口链)带到前台"""
|
|
79
|
+
if not self.hwnd:
|
|
80
|
+
raise ValueError("窗口句柄未设置")
|
|
81
|
+
|
|
82
|
+
current_hwnd = self.hwnd
|
|
83
|
+
for _ in range(8):
|
|
84
|
+
parent_hwnd = user32.GetParent(current_hwnd)
|
|
85
|
+
if parent_hwnd == 0:
|
|
86
|
+
break
|
|
87
|
+
current_hwnd = parent_hwnd
|
|
88
|
+
|
|
89
|
+
# 两种都开,确保窗口可见并带前台焦点
|
|
90
|
+
user32.ShowWindow(current_hwnd, 9)
|
|
91
|
+
|
|
92
|
+
if user32.GetForegroundWindow() != current_hwnd:
|
|
93
|
+
user32.SetForegroundWindow(current_hwnd)
|
|
94
|
+
time.sleep(0.02)
|
|
88
95
|
|
|
89
96
|
def bind_window(self):
|
|
90
97
|
"""
|
|
@@ -33,6 +33,11 @@ GetClientRect = user32.GetClientRect
|
|
|
33
33
|
GetClientRect.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.RECT)]
|
|
34
34
|
GetClientRect.restype = wintypes.BOOL
|
|
35
35
|
|
|
36
|
+
# 父窗口链(64 位 HWND 必须显式声明,否则返回值被截断、循环永不收敛)
|
|
37
|
+
GetParent = user32.GetParent
|
|
38
|
+
GetParent.argtypes = [wintypes.HWND]
|
|
39
|
+
GetParent.restype = wintypes.HWND
|
|
40
|
+
|
|
36
41
|
# 定义窗口消息常量
|
|
37
42
|
WM_KEYDOWN = 0x0100
|
|
38
43
|
WM_KEYUP = 0x0101
|
|
@@ -90,6 +95,38 @@ class WindowAction:
|
|
|
90
95
|
self.global_key = False
|
|
91
96
|
self.global_mouse = False
|
|
92
97
|
|
|
98
|
+
def _client_position(self):
|
|
99
|
+
"""
|
|
100
|
+
返回窗口客户区位置
|
|
101
|
+
Returns:
|
|
102
|
+
tuple: (x, y) 元组,x 为窗口客户区左上角 x 轴坐标,y 为窗口客户区左上角 y 轴坐标
|
|
103
|
+
"""
|
|
104
|
+
if self.hwnd:
|
|
105
|
+
client_point = wintypes.POINT(0, 0)
|
|
106
|
+
ctypes.windll.user32.ClientToScreen(self.hwnd, ctypes.byref(client_point))
|
|
107
|
+
self.client_point = client_point
|
|
108
|
+
return client_point.x, client_point.y
|
|
109
|
+
raise ValueError("窗口句柄未设置")
|
|
110
|
+
|
|
111
|
+
def _activate_window(self):
|
|
112
|
+
"""把窗口(含父窗口链)带到前台"""
|
|
113
|
+
if not self.hwnd:
|
|
114
|
+
raise ValueError("窗口句柄未设置")
|
|
115
|
+
|
|
116
|
+
current_hwnd = self.hwnd
|
|
117
|
+
for _ in range(8):
|
|
118
|
+
parent_hwnd = user32.GetParent(current_hwnd)
|
|
119
|
+
if parent_hwnd == 0:
|
|
120
|
+
break
|
|
121
|
+
current_hwnd = parent_hwnd
|
|
122
|
+
|
|
123
|
+
# 两种都开,确保窗口可见并带前台焦点
|
|
124
|
+
user32.ShowWindow(current_hwnd, 9)
|
|
125
|
+
|
|
126
|
+
if user32.GetForegroundWindow() != current_hwnd:
|
|
127
|
+
user32.SetForegroundWindow(current_hwnd)
|
|
128
|
+
time.sleep(0.02)
|
|
129
|
+
|
|
93
130
|
def send_activate_message(self, mode='send'):
|
|
94
131
|
"""
|
|
95
132
|
发送虚拟激活消息,让窗口认为自己被激活
|
|
@@ -219,6 +256,7 @@ class WindowAction:
|
|
|
219
256
|
raise ValueError("窗口句柄未设置")
|
|
220
257
|
|
|
221
258
|
if mode == 'global':
|
|
259
|
+
self._client_position()
|
|
222
260
|
if x is None or y is None:
|
|
223
261
|
self.mouse.mouse_down(button=button)
|
|
224
262
|
elif x == -1 and y == -1:
|
|
@@ -252,6 +290,7 @@ class WindowAction:
|
|
|
252
290
|
raise ValueError("窗口句柄未设置")
|
|
253
291
|
|
|
254
292
|
if mode == 'global':
|
|
293
|
+
self._client_position()
|
|
255
294
|
if x is None or y is None:
|
|
256
295
|
self.mouse.mouse_up(button=button)
|
|
257
296
|
elif x == -1 and y == -1:
|
|
@@ -300,6 +339,7 @@ class WindowAction:
|
|
|
300
339
|
raise ValueError("窗口句柄未设置")
|
|
301
340
|
|
|
302
341
|
if mode == 'global':
|
|
342
|
+
self._client_position()
|
|
303
343
|
self.mouse.mouse_move(self.client_point.x + x, self.client_point.y + y, duration, steps)
|
|
304
344
|
return
|
|
305
345
|
|
|
@@ -355,6 +395,7 @@ class WindowAction:
|
|
|
355
395
|
raise ValueError("窗口句柄未设置")
|
|
356
396
|
|
|
357
397
|
if mode == 'global':
|
|
398
|
+
self._client_position()
|
|
358
399
|
if x is None or y is None:
|
|
359
400
|
self.mouse.mouse_wheel(distance=distance)
|
|
360
401
|
else:
|