You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于现有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 06:50:23