win-mouse-listen
基于 Windows Raw Input API 的非阻塞鼠标监听模块。协议与 mouse 库兼容,但不会插入鼠标事件链,因此:
- 回调无论多慢,都不会让鼠标卡顿;
- 需要管理员权限;
- 支持按键、滚轮、移动,移动事件额外附带设备原始增量
dx / dy,可用于「触顶/触底持续移动」等场景。
为什么不用 WH_MOUSE_LL 钩子
mouse 库底层使用 SetWindowsHookEx(WH_MOUSE_LL)。虽然它也能全局监听,但它位于鼠标事件链上——系统必须等你的钩子返回,才会继续把事件传给其他程序。只要你的回调里出现了锁等待、GIL 争抢、I/O 阻塞,鼠标就会卡。
Raw Input 完全不同:鼠标事件由系统正常处理,同时复制一份 WM_INPUT 消息投递给你的隐藏窗口。你的窗口处理再慢,也不会影响鼠标。
安装
pip install win-mouse-listen
或从本地 whl 安装:
pip install dist/win_mouse_listen-0.1.0-py3-none-any.whl
快速开始
import time
import win_mouse_listen as wm
# 注册回调,第一次 hook 会自动启动监听
wm.hook(lambda ev: print('event:', ev))
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
wm.stop()
或者使用类接口:
from win_mouse_listen import Listener
with Listener() as l:
l.hook(print)
time.sleep(10)
事件协议
与 mouse 库兼容的 namedtuple:
ButtonEvent(event_type, button, time)
WheelEvent(delta, time)
MoveEvent(x, y, time, dx, dy)
event_type:'up'或'down'button:LEFT/RIGHT/MIDDLE/X/X2time:Unix 时间戳(float,秒)x, y:屏幕坐标,由GetCursorPos获取,钳制在屏幕内dx, dy:本次事件的设备原始增量,即使光标已经贴到屏幕边缘,仍会持续给出同方向的非零值
注意:
MoveEvent是 5 字段版本,与mouse.MoveEvent的 3 字段版本不兼容isinstance。如果你的代码依赖mouse库的类型,请改用 duck typing 判断字段。
API
回调
wm.hook(callback) # 注册回调,返回 callback
wm.unhook(callback) # 注销单个回调
wm.unhook_all() # 注销所有回调
回调在独立的分发线程中执行,阻塞回调不会影响鼠标。
生命周期
wm.start() # 显式启动(hook 会自动启动)
wm.stop() # 停止并重置默认监听器
非阻塞查询
wm.poll() # 取一个事件,没有则返回 None
wm.get_events(max_count=None) # 批量取事件
wm.clear_buffer() # 清空缓冲区
wm.pending_count() # 当前缓冲的事件数量
wm.is_pressed(button=LEFT) # 指定按键是否按下
wm.position() # 当前鼠标屏幕坐标 (x, y)
典型用法:检测「触顶 / 触底持续移动」
某些场景下需要判断鼠标已经顶到屏幕边缘,但用户仍在继续往同方向移动。传统做法是用「越界负坐标」反推增量,麻烦又容易出错。使用 dx / dy 直接判断即可:
import win_mouse_listen as wm
SCREEN_TOP_Y = 0
SCREEN_BOTTOM_Y = 1079
def on_move(ev):
if ev.y <= SCREEN_TOP_Y and ev.dy < 0:
print('向上越界移动', ev.dy)
elif ev.y >= SCREEN_BOTTOM_Y and ev.dy > 0:
print('向下越界移动', ev.dy)
wm.hook(on_move)
提示:高回报率鼠标每秒会产生数百到上千个
MoveEvent,每个事件的dx/dy通常只有个位数。如果需要按距离阈值触发逻辑,建议在时间窗口内累加。
限制与注意事项
-
绝对模式:触摸板、数位板、RDP 远程桌面等设备上报的是绝对坐标(
usFlags & MOUSE_MOVE_ABSOLUTE)。此时lLastX/lLastY不是增量,模块会将dx/dy置 0,避免出现巨大数值。物理鼠标不受影响。 -
多显示器:
x, y来自GetCursorPos,在虚拟屏幕坐标系下,副屏位于主屏左侧或上方时x/y本来就可能为负。这和「触顶越界」是两回事。 -
仅支持 Windows:模块依赖
user32.dll,其他平台会直接ImportError。 -
需要管理员:没有管理员权限启动软件,无法做到全局监听鼠标信息。
打包
项目根目录执行:
pip install build
python -m build
产物在 dist/:
dist/
├── win_mouse_listen-0.1.0-py3-none-any.whl
└── win_mouse_listen-0.1.0.tar.gz
本地安装测试:
pip install dist/win_mouse_listen-0.1.0-py3-none-any.whl
项目结构
win_mouse_listen/
├── __init__.py # 公共 API 与模块级单例
├── _types.py # 事件协议(ButtonEvent / WheelEvent / MoveEvent)
├── _win32.py # ctypes 绑定与常量
└── _listener.py # 核心监听器实现
pyproject.toml
README.md
设计要点
- Raw Input 而非全局钩子:鼠标事件不经过本模块,回调再慢也不会卡鼠标。
- 隐藏消息窗口:
HWND_MESSAGE类型窗口,不出现在任务栏。 - 双重缓冲:内部维护
deque(maxlen=N),满了自动丢弃最旧事件,永不阻塞钩子线程。 - 无锁读取:利用 CPython 中
deque.append/popleft、list引用替换的原子性,避免在热路径上加锁。 - 分发线程独立:用户回调在专用线程执行,隔离用户代码。
License
MIT
Metadata
Release files for win-mouse-listener 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| win_mouse_listener-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Release files / win_mouse_listener-0.2.2-py3-none-any.whl
| Download URL | win_mouse_listener-0.2.2-py3-none-any.whl |
|---|---|
| Size | 17.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
915c59be11e76292a73a1f059ea3245199ca8604a07d4adbc7008c78d761a913
|
|
BLAKE2b-256 checksum How to use checksums |
7f231d28223e13b2a28cdb71e94ba5ac76672adb7fcf490c63dfd055c1470fb7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.9
|
Security for win-mouse-listener 0.2.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.