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.
Files changed (43) hide show
  1. autoxkit-2.5.2/MANIFEST.in +5 -0
  2. {autoxkit-2.4.9 → autoxkit-2.5.2}/PKG-INFO +1 -1
  3. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/__init__.py +3 -3
  4. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/control.py +1 -1
  5. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/streams.py +1 -1
  6. autoxkit-2.5.2/autoxkit/hook/_autoxhook.dll +0 -0
  7. autoxkit-2.4.9/autoxkit/hook/hook_listener.py → autoxkit-2.5.2/autoxkit/hook/_ctypes_fallback.py +23 -16
  8. autoxkit-2.5.2/autoxkit/hook/_native.py +94 -0
  9. autoxkit-2.5.2/autoxkit/hook/hook_listener.py +306 -0
  10. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/window/window.py +18 -11
  11. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/window/window_action.py +41 -0
  12. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit.egg-info/PKG-INFO +1 -1
  13. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit.egg-info/SOURCES.txt +9 -1
  14. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit.egg-info/top_level.txt +0 -1
  15. autoxkit-2.5.2/csrc/autoxhook.c +421 -0
  16. autoxkit-2.5.2/csrc/autoxhook.h +100 -0
  17. autoxkit-2.5.2/csrc/build_dll.ps1 +36 -0
  18. {autoxkit-2.4.9 → autoxkit-2.5.2}/pyproject.toml +12 -2
  19. autoxkit-2.5.2/scripts/bench_hook.py +200 -0
  20. {autoxkit-2.4.9 → autoxkit-2.5.2}/LICENSE.txt +0 -0
  21. {autoxkit-2.4.9 → autoxkit-2.5.2}/README.md +0 -0
  22. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/__init__.py +0 -0
  23. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/adb.py +0 -0
  24. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/binary.py +0 -0
  25. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/client.py +0 -0
  26. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/control_backup.py +0 -0
  27. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/android/models.py +0 -0
  28. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/constants.py +0 -0
  29. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/hook/__init__.py +0 -0
  30. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/hook/event.py +0 -0
  31. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/hook/hotkey_listener.py +0 -0
  32. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/match/__init__.py +0 -0
  33. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/match/match.py +0 -0
  34. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/mousekey/__init__.py +0 -0
  35. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/mousekey/input.py +0 -0
  36. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/mousekey/keyboard.py +0 -0
  37. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/mousekey/mouse.py +0 -0
  38. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/utils.py +0 -0
  39. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/window/__init__.py +0 -0
  40. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit/window/window_match.py +0 -0
  41. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit.egg-info/dependency_links.txt +0 -0
  42. {autoxkit-2.4.9 → autoxkit-2.5.2}/autoxkit.egg-info/requires.txt +0 -0
  43. {autoxkit-2.4.9 → autoxkit-2.5.2}/setup.cfg +0 -0
@@ -0,0 +1,5 @@
1
+ include csrc/*.c
2
+ include csrc/*.h
3
+ include csrc/build_dll.ps1
4
+ include autoxkit/hook/_autoxhook.dll
5
+ recursive-include scripts *.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autoxkit
3
- Version: 2.4.9
3
+ Version: 2.5.2
4
4
  Summary: Python library for Windows automation and Android device screen casting and control
5
5
  Author-email: YorickFin <1582456060@qq.com>
6
6
  License: GPL-3.0-or-later
@@ -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"]
@@ -1,4 +1,4 @@
1
- """scrcpy-server v4.0 的控制消息和设备消息序列化器。"""
1
+ """scrcpy-server v4.0 的控制消息和设备消息序列化器。"""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -1,4 +1,4 @@
1
- """scrcpy-server v4.0 的异步流读取器和写入器。"""
1
+ """scrcpy-server v4.0 的异步流读取器和写入器。"""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -1,8 +1,15 @@
1
- # hook_listener.py
1
+ # _ctypes_fallback.py
2
+ """纯 ctypes 实现的 HookListener(回退路径)。
3
+
4
+ 当 _autoxhook.dll 缺失、位数不匹配或加载失败时启用本实现,
5
+ 功能与原生路径等价,性能为迁移前的基线水平。
6
+ 核心事件链路迁移见项目根 优化方案.md 与 csrc/ 目录。
7
+ """
2
8
  import ctypes
3
- from ctypes import wintypes, Structure, POINTER, CFUNCTYPE, byref
9
+ import sys
4
10
  import threading
5
- import time
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
- # ---------- HookListener 类 ----------
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=__import__('sys').stderr)
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=__import__('sys').stderr)
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=__import__('sys').stderr)
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=__import__('sys').stderr)
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=__import__('sys').stderr)
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=__import__('sys').stderr)
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=__import__("sys").stderr)
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=__import__("sys").stderr)
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=__import__("sys").stderr)
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=__import__("sys").stderr)
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=__import__("sys").stderr)
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=__import__("sys").stderr)
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
- current_hwnd = self.hwnd
82
- while True:
83
- parent_hwnd = user32.GetParent(current_hwnd)
84
- if parent_hwnd == 0:
85
- break
86
- current_hwnd = parent_hwnd
87
- user32.ShowWindow(current_hwnd, 9)
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: