如何基于现有SendInput代码实现游戏鼠标点击发送?
用ctypes的SendInput实现游戏兼容的鼠标点击
问题描述
现有一段基于ctypes调用Windows API实现键盘按键发送的代码,但不清楚如何利用其中的MOUSEINPUT类实现鼠标点击操作。尝试过pyautogui、autoit、win32等库均无法向游戏发送有效鼠标事件,仅当前代码的键盘功能可用。
原代码如下:
import ctypes import wintypes import time user32 = ctypes.WinDLL('user32', use_last_error=True) INPUT_MOUSE = 0 INPUT_KEYBOARD = 1 INPUT_HARDWARE = 2 KEYEVENTF_EXTENDEDKEY = 0x0001 KEYEVENTF_KEYUP = 0x0002 KEYEVENTF_UNICODE = 0x0004 KEYEVENTF_SCANCODE = 0x0008 MAPVK_VK_TO_VSC = 0 wintypes.ULONG_PTR = wintypes.WPARAM class MOUSEINPUT(ctypes.Structure): _fields_ = (("dx", wintypes.LONG), ("dy", wintypes.LONG), ("mouseData", wintypes.DWORD), ("dwFlags", wintypes.DWORD), ("time", wintypes.DWORD), ("dwExtraInfo", wintypes.ULONG_PTR)) class KEYBDINPUT(ctypes.Structure): _fields_ = (("wVk", wintypes.WORD), ("wScan", wintypes.WORD), ("dwFlags", wintypes.DWORD), ("time", wintypes.DWORD), ("dwExtraInfo", wintypes.ULONG_PTR)) def __init__(self, *args, **kwds): super(KEYBDINPUT, self).__init__(*args, **kwds) # some programs use the scan code even if KEYEVENTF_SCANCODE # isn't set in dwFflags, so attempt to map the correct code. if not self.dwFlags & KEYEVENTF_UNICODE: self.wScan = user32.MapVirtualKeyExW(self.wVk, MAPVK_VK_TO_VSC, 0) class HARDWAREINPUT(ctypes.Structure): _fields_ = (("uMsg", wintypes.DWORD), ("wParamL", wintypes.WORD), ("wParamH", wintypes.WORD)) class INPUT(ctypes.Structure): class _INPUT(ctypes.Union): _fields_ = (("ki", KEYBDINPUT), ("mi", MOUSEINPUT), ("hi", HARDWAREINPUT)) _anonymous_ = ("_input",) _fields_ = (("type", wintypes.DWORD), ("_input", _INPUT)) LPINPUT = ctypes.POINTER(INPUT) def _check_count(result, func, args): if result == 0: raise ctypes.WinError(ctypes.get_last_error()) return args user32.SendInput.errcheck = _check_count user32.SendInput.argtypes = (wintypes.UINT, # nInputs LPINPUT, # pInputs ctypes.c_int) # cbSize # 假设VK_CODE是已定义的键码映射字典 VK_CODE = {"A": 0x41} # 示例映射,实际需补充完整 def Press(hexKeyCode): hexKeyCode = VK_CODE.get(hexKeyCode) x = INPUT(type=INPUT_KEYBOARD, ki=KEYBDINPUT(wVk=hexKeyCode)) user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x)) time.sleep(0.5) x = INPUT(type=INPUT_KEYBOARD, ki=KEYBDINPUT(wVk=hexKeyCode, dwFlags=KEYEVENTF_KEYUP)) user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))
解决方案
要实现鼠标点击,需利用MOUSEINPUT结构体配合SendInput函数,核心是设置正确的dwFlags参数指定鼠标事件类型。以下是具体实现步骤:
1. 补充鼠标事件常量
在原有常量定义区域添加以下内容:
# 鼠标事件常量 MOUSEEVENTF_LEFTDOWN = 0x0002 MOUSEEVENTF_LEFTUP = 0x0004 MOUSEEVENTF_RIGHTDOWN = 0x0008 MOUSEEVENTF_RIGHTUP = 0x0010 MOUSEEVENTF_MIDDLEDOWN = 0x0020 MOUSEEVENTF_MIDDLEUP = 0x0040 MOUSEEVENTF_ABSOLUTE = 0x8000 # 启用绝对坐标模式(范围0-65535)
2. 封装基础鼠标点击函数
添加以下函数实现左键、右键、中键的点击操作:
def mouse_click(button='left'): # 匹配对应的按下/抬起标志 button_flags = { 'left': (MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP), 'right': (MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP), 'middle': (MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP) } if button not in button_flags: raise ValueError("按钮类型只能是 'left'、'right' 或 'middle'") down_flag, up_flag = button_flags[button] # 发送鼠标按下事件 input_down = INPUT(type=INPUT_MOUSE, mi=MOUSEINPUT(dwFlags=down_flag)) user32.SendInput(1, ctypes.byref(input_down), ctypes.sizeof(input_down)) time.sleep(0.05) # 模拟真实点击的间隔,避免事件被忽略 # 发送鼠标抬起事件 input_up = INPUT(type=INPUT_MOUSE, mi=MOUSEINPUT(dwFlags=up_flag)) user32.SendInput(1, ctypes.byref(input_up), ctypes.sizeof(input_up))
3. (可选)绝对坐标点击函数
针对游戏这类需要精准屏幕定位的场景,实现指定坐标的点击:
def mouse_click_at(x, y, button='left'): # 获取屏幕分辨率 screen_width = user32.GetSystemMetrics(0) screen_height = user32.GetSystemMetrics(1) # 将屏幕坐标转换为0-65535范围的绝对坐标 abs_x = int((x / screen_width) * 65535) abs_y = int((y / screen_height) * 65535) # 匹配带绝对坐标标志的事件 button_flags = { 'left': (MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_LEFTUP | MOUSEEVENTF_ABSOLUTE), 'right': (MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE, MOUSEEVENTF_RIGHTUP | MOUSEEVENTF_ABSOLUTE) } if button not in button_flags: raise ValueError("按钮类型只能是 'left' 或 'right'") down_flag, up_flag = button_flags[button] # 移动到目标位置并按下鼠标 input_move_down = INPUT(type=INPUT_MOUSE, mi=MOUSEINPUT(dx=abs_x, dy=abs_y, dwFlags=down_flag)) user32.SendInput(1, ctypes.byref(input_move_down), ctypes.sizeof(input_move_down)) time.sleep(0.05) # 抬起鼠标 input_up = INPUT(type=INPUT_MOUSE, mi=MOUSEINPUT(dx=abs_x, dy=abs_y, dwFlags=up_flag)) user32.SendInput(1, ctypes.byref(input_up), ctypes.sizeof(input_up))
关键说明
dwFlags:必须分别发送按下和抬起事件,模拟真实点击的完整动作MOUSEEVENTF_ABSOLUTE:游戏通常需要绝对坐标输入,需将普通屏幕坐标转换为0-65535的范围- 事件间隔:短暂的
sleep能避免系统忽略连续的快速事件,更贴近真实操作逻辑
内容的提问来源于stack exchange,提问作者Akali DZ
相关产品推荐
相关产品推荐

