""" KeySender - 按键和鼠标事件发送模块 本模块实现了三种不同的按键发送方式: 1. PostMessage - 异步消息发送,性能最佳,适用于大多数应用 2. SendMessage - 同步消息发送,响应可靠,确保按键顺序 3. SendInput - 系统级输入模拟,兼容性最好,适用于严格的应用 技术原理: - 使用Windows API进行底层按键模拟 - 支持向指定窗口发送按键和鼠标事件 - 自动处理按键扫描码和消息参数 作者: xiao liu 版本: v0.1.0 """ import ctypes import win32gui import win32api import win32con from .constants import * from utils.structures import INPUT class KeySender: """ 按键发送器类 负责向目标窗口发送按键和鼠标事件,支持三种不同的发送模式。 """ def __init__(self, target_hwnd=None): """ 初始化按键发送器 Args: target_hwnd (int, optional): 目标窗口句柄. Defaults to None. """ self.target_hwnd = target_hwnd def set_target_window(self, hwnd): """ 设置目标窗口句柄 Args: hwnd (int): 窗口句柄 """ self.target_hwnd = hwnd def send_key_post_message(self, key_code): """ 使用PostMessage方式发送按键 PostMessage是异步发送方式,消息会被放入目标窗口的消息队列中, 然后立即返回,不等待处理完成。这种方式性能最佳,适用于大多数应用。 Args: key_code (int): 按键的虚拟键码 Returns: bool: 发送成功返回True,失败返回False """ # 检查目标窗口是否有效 if not self.target_hwnd or not win32gui.IsWindow(self.target_hwnd): return False # 将虚拟键码转换为扫描码 # 扫描码是键盘硬件相关的代码,某些应用可能需要 scan_code = win32api.MapVirtualKey(key_code, 0) # 构造按键按下事件的lparam参数 # lparam包含重复次数、扫描码、扩展键标志等信息 # 格式:[31] 转换状态 [30] 前一个状态 [29] 上下文代码 [28:25] 保留 # [24] 扩展键 [23:16] 扫描码 [15:0] 重复次数 lparam = (scan_code << 16) | 1 # 发送按键按下消息 win32gui.PostMessage(self.target_hwnd, WM_KEYDOWN, key_code, lparam) # 对于可打印字符,还需要发送WM_CHAR消息 # WM_CHAR消息包含字符的Unicode值,用于文本输入 if key_code >= ord('A') and key_code <= ord('Z'): # 大写字母转换为小写 char_code = key_code + 32 win32gui.PostMessage(self.target_hwnd, WM_CHAR, char_code, lparam) elif (key_code >= ord('0') and key_code <= ord('9')) or key_code == VK_SPACE: # 数字和空格键直接发送 win32gui.PostMessage(self.target_hwnd, WM_CHAR, key_code, lparam) # 构造按键释放事件的lparam参数 # 设置第30位(前一个状态)和第31位(转换状态)为1,表示按键释放 lparam = (scan_code << 16) | (1 | (1 << 30) | (1 << 31)) # 发送按键释放消息 win32gui.PostMessage(self.target_hwnd, WM_KEYUP, key_code, lparam) return True def send_key_send_message(self, key_code): """ 使用SendMessage方式发送按键 SendMessage是同步发送方式,会等待目标窗口处理完消息后才返回。 这种方式响应可靠,能确保按键的处理顺序,适用于需要同步响应的应用。 Args: key_code (int): 按键的虚拟键码 Returns: bool: 发送成功返回True,失败返回False """ # 检查目标窗口是否有效 if not self.target_hwnd or not win32gui.IsWindow(self.target_hwnd): return False # 将虚拟键码转换为扫描码 scan_code = win32api.MapVirtualKey(key_code, 0) # 构造按键按下事件的lparam参数 lparam = (scan_code << 16) | 1 # 发送按键按下消息(同步) win32gui.SendMessage(self.target_hwnd, WM_KEYDOWN, key_code, lparam) # 对于可打印字符,发送WM_CHAR消息 if key_code >= ord('A') and key_code <= ord('Z'): char_code = key_code + 32 # 转换为小写 win32gui.SendMessage(self.target_hwnd, WM_CHAR, char_code, lparam) elif (key_code >= ord('0') and key_code <= ord('9')) or key_code == VK_SPACE: win32gui.SendMessage(self.target_hwnd, WM_CHAR, key_code, lparam) # 构造按键释放事件的lparam参数 lparam = (scan_code << 16) | (1 | (1 << 30) | (1 << 31)) # 发送按键释放消息(同步) win32gui.SendMessage(self.target_hwnd, WM_KEYUP, key_code, lparam) return True def send_key_send_input(self, key_code): """ 使用SendInput方式发送按键 SendInput是系统级的输入模拟方式,通过系统的输入队列发送按键事件。 这种方式兼容性最好,能被大多数应用正确识别,适用于对按键检测严格的应用。 Args: key_code (int): 按键的虚拟键码 Returns: bool: 发送成功返回True,失败返回False """ # 尝试将目标窗口设置为前台窗口 # SendInput发送的是全局输入事件,需要目标窗口处于前台才能接收 try: if self.target_hwnd: win32gui.SetForegroundWindow(self.target_hwnd) except: # 如果设置前台窗口失败,继续执行(某些情况下可能会被系统阻止) pass # 创建INPUT结构数组,包含按键按下和释放两个事件 inputs = (INPUT * 2)() # 设置按键按下事件 inputs[0].type = INPUT_KEYBOARD # 输入类型:键盘 inputs[0].u.ki.wVk = key_code # 虚拟键码 inputs[0].u.ki.wScan = win32api.MapVirtualKey(key_code, 0) # 扫描码 inputs[0].u.ki.dwFlags = 0 # 标志:0表示按键按下 inputs[0].u.ki.time = 0 # 时间戳:0表示系统自动生成 inputs[0].u.ki.dwExtraInfo = None # 额外信息 # 设置按键释放事件 inputs[1].type = INPUT_KEYBOARD # 输入类型:键盘 inputs[1].u.ki.wVk = key_code # 虚拟键码 inputs[1].u.ki.wScan = win32api.MapVirtualKey(key_code, 0) # 扫描码 inputs[1].u.ki.dwFlags = KEYEVENTF_KEYUP # 标志:KEYEVENTF_KEYUP表示按键释放 inputs[1].u.ki.time = 0 # 时间戳 inputs[1].u.ki.dwExtraInfo = None # 额外信息 # 调用SendInput API发送输入事件 # 参数:事件数量、事件数组指针、单个事件结构大小 ctypes.windll.user32.SendInput(2, ctypes.byref(inputs), ctypes.sizeof(INPUT)) return True def send_key(self, key_code, mode="post"): """ 根据指定模式发送按键 Args: key_code (int): 按键的虚拟键码 mode (str): 发送模式,可选值: "post" - 使用PostMessage "send_message" - 使用SendMessage "send" - 使用SendInput Returns: bool: 发送成功返回True,失败返回False """ if mode == "post": return self.send_key_post_message(key_code) elif mode == "send_message": return self.send_key_send_message(key_code) elif mode == "send": return self.send_key_send_input(key_code) return False def send_mouse_click(self, mode="post"): """ 向目标窗口发送鼠标左键点击事件 支持三种发送模式,与按键发送类似: - post/send_message: 向目标窗口发送鼠标消息 - send: 使用SendInput发送系统级鼠标事件 Args: mode (str): 发送模式,可选值:"post", "send_message", "send" Returns: bool: 发送成功返回True,失败返回False """ # 检查目标窗口是否有效 if not self.target_hwnd or not win32gui.IsWindow(self.target_hwnd): return False try: if mode == "post" or mode == "send_message": # 使用消息方式发送鼠标点击 # lparam通常包含鼠标坐标,这里设为0表示窗口左上角 lparam = 0 if mode == "post": # 异步发送鼠标按下和释放消息 win32gui.PostMessage(self.target_hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lparam) win32gui.PostMessage(self.target_hwnd, win32con.WM_LBUTTONUP, 0, lparam) else: # send_message # 同步发送鼠标按下和释放消息 win32gui.SendMessage(self.target_hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lparam) win32gui.SendMessage(self.target_hwnd, win32con.WM_LBUTTONUP, 0, lparam) else: # send mode # 使用SendInput方式发送系统级鼠标事件 inputs = (INPUT * 2)() # 设置鼠标按下事件 inputs[0].type = INPUT_MOUSE # 输入类型:鼠标 inputs[0].u.mi.dx = 0 # X坐标偏移 inputs[0].u.mi.dy = 0 # Y坐标偏移 inputs[0].u.mi.mouseData = 0 # 鼠标数据(滚轮等) inputs[0].u.mi.dwFlags = MOUSEEVENTF_LEFTDOWN # 标志:左键按下 inputs[0].u.mi.time = 0 # 时间戳 inputs[0].u.mi.dwExtraInfo = None # 额外信息 # 设置鼠标释放事件 inputs[1].type = INPUT_MOUSE # 输入类型:鼠标 inputs[1].u.mi.dx = 0 # X坐标偏移 inputs[1].u.mi.dy = 0 # Y坐标偏移 inputs[1].u.mi.mouseData = 0 # 鼠标数据 inputs[1].u.mi.dwFlags = MOUSEEVENTF_LEFTUP # 标志:左键释放 inputs[1].u.mi.time = 0 # 时间戳 inputs[1].u.mi.dwExtraInfo = None # 额外信息 # 发送鼠标输入事件 ctypes.windll.user32.SendInput(2, ctypes.byref(inputs), ctypes.sizeof(INPUT)) return True except Exception as e: print(f"发送鼠标点击时出错: {str(e)}") return False