KeyPress/src/core/key_sender.py

269 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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