269 lines
11 KiB
Python
269 lines
11 KiB
Python
"""
|
||
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 |