win-mouse-listen
非阻塞的 Windows 鼠标监听模块,对外协议与 mouse 库兼容。
两种后端实现,定义期选一种,公共 API 完全一致。
- 回调无论多慢,都不会让鼠标卡顿;
- 支持按键(含 X1/X2)、滚轮、移动,移动事件附带
dx / dy,可用于「触顶/触底持续移动」等场景。
两种后端
rawinput(默认) |
process(全局钩子多进程版) |
|
|---|---|---|
| 原理 | RegisterRawInputDevices + 隐藏消息窗口收 WM_INPUT |
子进程跑 mouse 库的 SetWindowsHookEx(WH_MOUSE_LL),主进程收 IPC |
| 进程模型 | 单进程 + 2 线程 | 1 个子进程 + 主进程 2 线程 |
| 第三方依赖 | 无 | mouse |
| 起停开销 | 极小 | 每个监听器一个进程(约几十 MB、几十 ms) |
| 管理员权限 | 需要(RIDEV_INPUTSINK 才能收全局事件) |
按 mouse 库要求;一般普通权限即可 |
| 是否在鼠标事件链上 | 否,系统只是抄一份给我们 | 是(WH_MOUSE_LL),但钩子在子进程里,主进程 GIL 再忙也不影响它 |
MoveEvent.dx/dy |
设备原始增量;触摸板/RDP 等绝对模式设备下为 0 | 光标像素位移(含系统指针加速);绝对模式设备上也有值 |
DOUBLE 事件 |
不产生(常量仍存在) | 会产生(mouse 库合成) |
多显示器 dx/dy |
直接来自设备,不依赖显示器布局 | 由子进程的 MonitorTracker 按显示器边界钳制推断 |
| 副作用 | 无 | 子进程内设置 DPI 感知,宿主进程完全不受影响 |
两个后端都满足「回调不在鼠标事件链上,也绝不阻塞鼠标事件的分发」。
选哪个主要看:要不要 extra 依赖和子进程(rawinput 更轻),
以及要不要 DOUBLE 事件 / 绝对模式设备上的 dx、dy(process 才有)。
选择后端
只在定义期决定一次,之后不再变化 —— 第一次 hook() / start() 之后
再改会抛 RuntimeError。
import win_mouse_listener as wm
wm.use('process') # 必须在第一次 hook()/start() 之前
wm.current() # -> 'process'
三种方式,优先级从高到低:
wm.Listener(backend='process') # 1. 只让某一个实例用别的后端
# 2. 环境变量(整进程生效,模块级 wm.hook() 也吃这一套)
# WIN_MOUSE_LISTENER_BACKEND=process
# 3. 默认值 'rawinput'
后端名支持别名(raw / raw_input / multiprocess / global-hook …),
大小写、空格、下划线、连字符都不敏感。
安装
pip install win-mouse-listener # 只有 rawinput 后端
pip install "win-mouse-listener[process]" # 额外装 mouse,启用 process 后端
或从本地 whl 安装:
pip install dist/win_mouse_listener-0.3.0-py3-none-any.whl
快速开始
import time
import win_mouse_listener as wm
# 注册回调,第一次 hook 会自动启动监听
wm.hook(lambda ev: print('event:', ev))
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
wm.stop()
或者使用类接口:
from win_mouse_listener import Listener
with Listener() as l: # Listener(backend='process') 也行
l.hook(print)
time.sleep(10)
用
process后端时,程序入口必须有if __name__ == '__main__':保护: 它用multiprocessing的 spawn 启动子进程,子进程会重新导入主模块, 没有保护就会递归地起监听进程。rawinput后端没有这个限制。
事件协议
与 mouse 库兼容的 namedtuple:
ButtonEvent(event_type, button, time)
WheelEvent(delta, time)
MoveEvent(x, y, time, dx, dy)
event_type:'up'/'down'/(仅 process 后端)'double'button:LEFT/RIGHT/MIDDLE/X/X2time:Unix 时间戳(float,秒)x, y:屏幕坐标,由GetCursorPos获取,物理像素dx, dy:见上面两种后端的差异
注意:
MoveEvent是 5 字段版本,与mouse.MoveEvent的 3 字段版本 不兼容isinstance。如果你的代码依赖mouse库的类型,请改用 duck typing 判断字段。
API
后端
wm.use(name) # 定义期选择后端,返回归一化后的名字
wm.current() # 当前后端名
wm.Listener(backend=...) # 单实例指定后端
回调
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_listener as wm
def on_move(ev):
if ev.y <= 0 and ev.dy < 0:
print('向上越界移动', ev.dy)
elif ev.y >= 1079 and ev.dy > 0:
print('向下越界移动', ev.dy)
wm.hook(on_move)
提示:高回报率鼠标每秒会产生数百到上千个
MoveEvent,每个事件的dx/dy通常只有个位数。如果需要按距离阈值触发逻辑,建议在时间窗口内累加。
限制与注意事项
- 绝对模式:触摸板、数位板、RDP 远程桌面等设备上报的是绝对坐标。
rawinput后端此时把dx/dy置 0(lLastX/lLastY不是增量,直接给会 误导);process后端不受影响。 - 多显示器:
x, y来自GetCursorPos,在虚拟屏幕坐标系下,副屏位于 主屏左侧或上方时x/y本来就可能为负。这和「触顶越界」是两回事。 - 仅支持 Windows:依赖
user32.dll,其他平台会直接ImportError。 - DPI 缩放:
process后端需要枚举显示器来做边缘钳制,而EnumDisplayMonitors在非 DPI 感知的进程里返回逻辑像素、和鼠标钩子的 物理像素对不上。这个 DPI 感知只在子进程里设置,宿主进程(Qt 应用等) 完全不受影响。 - 管理员:没有管理员权限启动软件,无法做到全局监听鼠标信息。
process后端的入口保护:见上面「快速开始」里的提示。
打包
项目根目录执行:
pip install build
python -m build
产物在 dist/:
dist/
├── win_mouse_listener-0.3.0-py3-none-any.whl
└── win_mouse_listener-0.3.0.tar.gz
项目结构
win_mouse_listener/
├── __init__.py 公共 API(唯一对外入口)+ 模块级默认实例
├── _types.py 事件协议与常量(两个后端共用)
├── _base.py _ListenerBase:缓冲 / 分发 / 按下状态 / 公共 API
├── _listener.py 门面 Listener(实例化时按配置分发到后端子类)
├── _select.py 后端选择:环境变量 + use(),只解析一次
├── _win32.py 共用 ctypes:POINT、GetCursorPos
├── rawinput/ Raw Input 版
│ ├── listener.py RawInputListener(消息窗口 + WM_INPUT 解析)
│ └── win32.py Raw Input 常量、结构体、函数签名
└── process/ 全局钩子多进程版
├── listener.py ProcessListener(主进程侧)
├── child.py 子进程:mouse 钩子 + 发送线程(含 MonitorTracker)
├── ipc.py 二进制 IPC 协议
└── monitors.py 显示器枚举 + dx/dy 推断 + 子进程 DPI 感知
pyproject.toml
README.md
设计要点
- 回调不在输入链上:rawinput 后端靠 Raw Input(系统只是复制一份事件给我们);
process 后端靠把
WH_MOUSE_LL钩子关进子进程,主进程 GIL 再忙也影响不到它。 - 隐藏消息窗口:
WS_POPUP+WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, 不显示、不进任务栏、不抢焦点。 - 双重缓冲:内部维护
deque(maxlen=N),满了自动丢弃最旧事件,永不阻塞生产侧。 - 无锁读取:利用 CPython 中
deque.append/popleft、list引用替换的原子性, 避免在热路径上加锁。 - 分发线程独立:用户回调在专用线程执行,用
Condition唤醒而非轮询。 - 懒加载后端:
import win_mouse_listener不会导入另一个后端的模块 (默认 rawinput 时不会碰mouse,也不会碰multiprocessing)。
License
MIT
Metadata
Release files for win-mouse-listener 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| win_mouse_listener-0.3.0.tar.gz | 29.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| win_mouse_listener-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 60.7 kB
Release files / win_mouse_listener-0.3.0.tar.gz
| Download URL | win_mouse_listener-0.3.0.tar.gz |
|---|---|
| Size | 29.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4894a2a3c77dd52862a91edd520773fe395afa91bdf243e08eab6d307370753c
|
|
BLAKE2b-256 checksum How to use checksums |
723af2b0371d4349cca9eceb8def62b3868db8e2fc164034cbd86454385a8771
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.9
|
Release files / win_mouse_listener-0.3.0-py3-none-any.whl
| Download URL | win_mouse_listener-0.3.0-py3-none-any.whl |
|---|---|
| Size | 31.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e43b3117e0be4d6b177ffc431e55a231a6bab56c46993226194308799c8c3103
|
|
BLAKE2b-256 checksum How to use checksums |
9a36fadd765da2cc18842e54263e0cd7318d73cdf26f82faa450dd9b2bd28b7a
|
| 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.3.0
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.