返回

获取鼠标原始输入:进阶方法与实现

python

获取鼠标原始输入:一种进阶方法

常见场景中,开发者常常需要读取鼠标输入,用于交互、自动化测试或游戏开发。诸如PyAutoGUI和Pynput等库,它们能读取鼠标在屏幕上的位置坐标。但是,某些应用场景,例如游戏宏录制,则需要直接捕获鼠标的原始输入信号——并非屏幕上的位置变化,而是设备本身发送的数据流。本文将讨论几种直接获取鼠标原始输入的方法,及其优缺点。

系统级事件捕获

操作系统层提供了底层的事件监听机制,可以捕获包括鼠标在内的多种输入设备的数据。Windows系统使用win32apipywin32模块可以实现此功能。在Linux或macOS系统中,则可以利用Xlib或相应的系统调用实现。

Windows:pywin32 方法

pywin32允许Python直接调用Windows API,访问更底层的硬件信息。可以注册一个Windows钩子,截取鼠标事件。这种方法通常能提供非常精确且低延迟的数据,但是它较为复杂,并且对操作系统高度依赖。

import win32api
import win32con
import time
import struct
import ctypes


class RawInput:
    def __init__(self):
        self.mouse_x = 0
        self.mouse_y = 0
        self.buffer = bytearray(ctypes.sizeof(win32con.RAWINPUT))
        self.handle = win32api.GetRawInputDeviceList()[1].hDevice  # 获取第一个鼠标设备
        
        # 注册鼠标设备
        win32api.RegisterRawInputDevices([
            (win32con.RIM_TYPEHID,win32con.RIDEV_INPUTSINK | win32con.RIDEV_NOLEGACY,0) # 如果没有这个可能会造成鼠标无法使用
            ]) 

    def update_mouse(self):
        while True:
            try:
                headerSize = win32api.GetRawInputData(self.handle, win32con.RID_INPUT, None, win32api.GetRawInputDeviceInfo(self.handle, win32con.RIDI_RAWDATA_SIZE), ctypes.sizeof(win32con.RAWINPUTHEADER))
                if headerSize >= 0: # headerSize > 0 可能存在兼容性问题,
                   size = win32api.GetRawInputData(self.handle,win32con.RID_INPUT, self.buffer,headerSize, ctypes.sizeof(win32con.RAWINPUTHEADER))
                   # unpack的格式与 RAWINPUT结构体需要对应 https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawinput 
                   _, mouse_header_type,_,_,mouse_buttons,mouse_x_raw,mouse_y_raw,mouse_wheel  =  struct.unpack('<HHIiIIi', self.buffer[:44]) 

                   self.mouse_x = mouse_x_raw
                   self.mouse_y = mouse_y_raw
            except Exception:
               pass  #某些情况下,无法获取鼠标输入可以忽略
    def get_data(self):
        return self.mouse_x,self.mouse_y
 

if __name__ == "__main__":
  
   input_handle = RawInput()
   
   import threading
   t = threading.Thread(target=input_handle.update_mouse)
   t.start()

   while True:
     print(input_handle.get_data())
     time.sleep(0.01)
  • 操作步骤:
    1. 安装 pywin32pip install pywin32
    2. 复制并运行提供的代码,输出为鼠标的相对位置变化。
    3. 根据具体需求处理 mouse_x, mouse_y
  • 原理: 通过Windows API注册鼠标输入设备,实时获取设备的原始输入数据。

Linux:evdev 方法

在Linux环境中, /dev/input/ 下的事件设备文件提供了访问鼠标原始输入的入口。可以使用evdev 模块,它可以直接读取这些事件文件,捕获鼠标的移动、按钮点击等动作。这种方式直接、高效。

from evdev import InputDevice, categorize, ecodes

# 列出所有输入设备,选择鼠标设备 (如果鼠标是多个,注意识别)
devices = [InputDevice(path) for path in  '/dev/input/' in os.listdir()  if  path.startswith('event')  ]
for d in devices:
    print(d.path, d.name, d.phys)
# 例如 mouse0 = InputDevice('/dev/input/event2') 请替换成你自己的鼠标的event序号
mouse = InputDevice('/dev/input/event2')  

for event in mouse.read_loop():
    if event.type == ecodes.EV_REL:
      if event.code == ecodes.REL_X:
        print("相对X坐标: ", event.value)
      elif event.code == ecodes.REL_Y:
        print("相对Y坐标:", event.value)
    if event.type == ecodes.EV_KEY:
      if event.code == ecodes.BTN_LEFT:
        print("左键:", event.value) # 1是按下,0是弹起
      if event.code == ecodes.BTN_RIGHT:
          print("右键:", event.value) # 1是按下,0是弹起

  • 操作步骤:
    1. 安装evdev 模块: pip install evdev
    2. 找出你的鼠标对应的 /dev/input/eventX 文件, 将其替换代码中 mouse = InputDevice('/dev/input/event2') ,运行代码。
    3. 根据需要处理鼠标移动和按钮事件。
  • 原理: evdev 直接与 Linux 内核的输入子系统交互,捕获设备的原始数据流。

优缺点比较

系统级事件捕获的优势:

  • 精度高: 可以获取设备原始数据,避免了窗口系统等上层抽象造成的偏差。
  • 延迟低: 与系统底层交互,降低延迟。

系统级事件捕获的缺点:

  • 平台依赖性强: 代码不能在不同操作系统之间通用。需要为不同的操作系统编写特定的代码。
  • 复杂度较高: 需要更深入的系统知识。

注意事项和安全建议

  1. 权限管理: 某些情况下,捕获原始输入需要管理员权限或使用特殊的用户组。
  2. 设备冲突: 读取原始输入可能与其他使用同一设备的应用发生冲突,需要谨慎使用。
  3. 安全问题: 读取鼠标输入可能暴露隐私信息, 特别在执行鼠标移动和点击模拟时。需要充分理解相关API的权限,避免滥用,谨慎操作。

综上所述,在需要获取精确和低延迟的鼠标原始输入时,系统级的事件捕获提供了一种有效的手段。开发者可以根据具体应用场景选择合适的方案,理解其原理和限制。务必谨慎操作,尤其是在模拟用户行为时。