feat:使用keyboard库
This commit is contained in:
278
app/utils/device_manager/keyboard_utils.py
Normal file
278
app/utils/device_manager/keyboard_utils.py
Normal file
@@ -0,0 +1,278 @@
|
||||
"""
|
||||
键盘工具模块
|
||||
|
||||
提供虚拟键码到keyboard库按键名称的转换功能,以及相关的键盘操作辅助函数。
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import keyboard
|
||||
from typing import List
|
||||
from app.utils.logger import get_logger
|
||||
|
||||
logger = get_logger("键盘工具")
|
||||
|
||||
|
||||
def vk_code_to_key_name(vk_code: int) -> str:
|
||||
"""
|
||||
将Windows虚拟键码转换为keyboard库识别的按键名称
|
||||
|
||||
Args:
|
||||
vk_code (int): Windows虚拟键码
|
||||
|
||||
Returns:
|
||||
str: keyboard库识别的按键名称
|
||||
|
||||
Examples:
|
||||
>>> vk_code_to_key_name(0x1B)
|
||||
'esc'
|
||||
>>> vk_code_to_key_name(0x70)
|
||||
'f1'
|
||||
>>> vk_code_to_key_name(0x41)
|
||||
'a'
|
||||
"""
|
||||
# Windows虚拟键码到keyboard库按键名称的映射
|
||||
vk_mapping = {
|
||||
# 常用功能键
|
||||
0x1B: "esc", # VK_ESCAPE
|
||||
0x0D: "enter", # VK_RETURN
|
||||
0x20: "space", # VK_SPACE
|
||||
0x08: "backspace", # VK_BACK
|
||||
0x09: "tab", # VK_TAB
|
||||
0x2E: "delete", # VK_DELETE
|
||||
0x24: "home", # VK_HOME
|
||||
0x23: "end", # VK_END
|
||||
0x21: "page up", # VK_PRIOR
|
||||
0x22: "page down", # VK_NEXT
|
||||
0x2D: "insert", # VK_INSERT
|
||||
# 修饰键
|
||||
0x10: "shift", # VK_SHIFT
|
||||
0x11: "ctrl", # VK_CONTROL
|
||||
0x12: "alt", # VK_MENU
|
||||
0x5B: "left windows", # VK_LWIN
|
||||
0x5C: "right windows", # VK_RWIN
|
||||
0x5D: "apps", # VK_APPS (右键菜单键)
|
||||
# 方向键
|
||||
0x25: "left", # VK_LEFT
|
||||
0x26: "up", # VK_UP
|
||||
0x27: "right", # VK_RIGHT
|
||||
0x28: "down", # VK_DOWN
|
||||
# 功能键 F1-F12
|
||||
0x70: "f1",
|
||||
0x71: "f2",
|
||||
0x72: "f3",
|
||||
0x73: "f4",
|
||||
0x74: "f5",
|
||||
0x75: "f6",
|
||||
0x76: "f7",
|
||||
0x77: "f8",
|
||||
0x78: "f9",
|
||||
0x79: "f10",
|
||||
0x7A: "f11",
|
||||
0x7B: "f12",
|
||||
# 数字键 0-9
|
||||
0x30: "0",
|
||||
0x31: "1",
|
||||
0x32: "2",
|
||||
0x33: "3",
|
||||
0x34: "4",
|
||||
0x35: "5",
|
||||
0x36: "6",
|
||||
0x37: "7",
|
||||
0x38: "8",
|
||||
0x39: "9",
|
||||
# 字母键 A-Z
|
||||
0x41: "a",
|
||||
0x42: "b",
|
||||
0x43: "c",
|
||||
0x44: "d",
|
||||
0x45: "e",
|
||||
0x46: "f",
|
||||
0x47: "g",
|
||||
0x48: "h",
|
||||
0x49: "i",
|
||||
0x4A: "j",
|
||||
0x4B: "k",
|
||||
0x4C: "l",
|
||||
0x4D: "m",
|
||||
0x4E: "n",
|
||||
0x4F: "o",
|
||||
0x50: "p",
|
||||
0x51: "q",
|
||||
0x52: "r",
|
||||
0x53: "s",
|
||||
0x54: "t",
|
||||
0x55: "u",
|
||||
0x56: "v",
|
||||
0x57: "w",
|
||||
0x58: "x",
|
||||
0x59: "y",
|
||||
0x5A: "z",
|
||||
# 数字小键盘
|
||||
0x60: "num 0",
|
||||
0x61: "num 1",
|
||||
0x62: "num 2",
|
||||
0x63: "num 3",
|
||||
0x64: "num 4",
|
||||
0x65: "num 5",
|
||||
0x66: "num 6",
|
||||
0x67: "num 7",
|
||||
0x68: "num 8",
|
||||
0x69: "num 9",
|
||||
0x6A: "num *",
|
||||
0x6B: "num +",
|
||||
0x6D: "num -",
|
||||
0x6E: "num .",
|
||||
0x6F: "num /",
|
||||
0x90: "num lock",
|
||||
# 标点符号和特殊键
|
||||
0xBA: ";", # VK_OEM_1 (;:)
|
||||
0xBB: "=", # VK_OEM_PLUS (=+)
|
||||
0xBC: ",", # VK_OEM_COMMA (,<)
|
||||
0xBD: "-", # VK_OEM_MINUS (-_)
|
||||
0xBE: ".", # VK_OEM_PERIOD (.>)
|
||||
0xBF: "/", # VK_OEM_2 (/?)
|
||||
0xC0: "`", # VK_OEM_3 (`~)
|
||||
0xDB: "[", # VK_OEM_4 ([{)
|
||||
0xDC: "\\", # VK_OEM_5 (\|)
|
||||
0xDD: "]", # VK_OEM_6 (]})
|
||||
0xDE: "'", # VK_OEM_7 ('")
|
||||
# 系统键
|
||||
0x14: "caps lock", # VK_CAPITAL
|
||||
0x91: "scroll lock", # VK_SCROLL
|
||||
0x13: "pause", # VK_PAUSE
|
||||
0x2C: "print screen", # VK_SNAPSHOT
|
||||
# 媒体键
|
||||
0xA0: "left shift", # VK_LSHIFT
|
||||
0xA1: "right shift", # VK_RSHIFT
|
||||
0xA2: "left ctrl", # VK_LCONTROL
|
||||
0xA3: "right ctrl", # VK_RCONTROL
|
||||
0xA4: "left alt", # VK_LMENU
|
||||
0xA5: "right alt", # VK_RMENU
|
||||
}
|
||||
|
||||
return vk_mapping.get(vk_code, f"vk_{vk_code}")
|
||||
|
||||
|
||||
def vk_codes_to_key_names(vk_codes: List[int]) -> List[str]:
|
||||
"""
|
||||
将多个虚拟键码转换为keyboard库识别的按键名称列表
|
||||
|
||||
Args:
|
||||
vk_codes (List[int]): Windows虚拟键码列表
|
||||
|
||||
Returns:
|
||||
List[str]: keyboard库识别的按键名称列表
|
||||
|
||||
Examples:
|
||||
>>> vk_codes_to_key_names([0x11, 0x12, 0x44])
|
||||
['ctrl', 'alt', 'd']
|
||||
"""
|
||||
return [vk_code_to_key_name(vk) for vk in vk_codes]
|
||||
|
||||
|
||||
async def send_key_combination(key_names: List[str], hold_time: float = 0.05) -> bool:
|
||||
"""
|
||||
发送按键组合
|
||||
|
||||
Args:
|
||||
key_names (List[str]): 按键名称列表
|
||||
hold_time (float): 按键保持时间(秒),默认 0.05 秒
|
||||
|
||||
Returns:
|
||||
bool: 操作是否成功
|
||||
|
||||
Examples:
|
||||
>>> await send_key_combination(['ctrl', 'alt', 'd'])
|
||||
True
|
||||
>>> await send_key_combination(['f1'])
|
||||
True
|
||||
"""
|
||||
try:
|
||||
if not key_names:
|
||||
logger.warning("按键名称列表为空")
|
||||
return False
|
||||
|
||||
if len(key_names) == 1:
|
||||
# 单个按键
|
||||
keyboard.press_and_release(key_names[0])
|
||||
logger.debug(f"发送单个按键: {key_names[0]}")
|
||||
else:
|
||||
# 组合键:按下所有键,然后释放
|
||||
logger.debug(f"发送组合键: {'+'.join(key_names)}")
|
||||
for key in key_names:
|
||||
keyboard.press(key)
|
||||
await asyncio.sleep(hold_time) # 保持按键状态
|
||||
for key in reversed(key_names):
|
||||
keyboard.release(key)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送按键组合失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def send_vk_combination(vk_codes: List[int], hold_time: float = 0.05) -> bool:
|
||||
"""
|
||||
发送虚拟键码组合
|
||||
|
||||
Args:
|
||||
vk_codes (List[int]): Windows虚拟键码列表
|
||||
hold_time (float): 按键保持时间(秒),默认 0.05 秒
|
||||
|
||||
Returns:
|
||||
bool: 操作是否成功
|
||||
|
||||
Examples:
|
||||
>>> await send_vk_combination([0x11, 0x12, 0x44]) # Ctrl+Alt+D
|
||||
True
|
||||
>>> await send_vk_combination([0x70]) # F1
|
||||
True
|
||||
"""
|
||||
try:
|
||||
key_names = vk_codes_to_key_names(vk_codes)
|
||||
return await send_key_combination(key_names, hold_time)
|
||||
except Exception as e:
|
||||
logger.error(f"发送虚拟键码组合失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_common_boss_keys() -> dict[str, List[int]]:
|
||||
"""
|
||||
获取常见的BOSS键组合
|
||||
|
||||
Returns:
|
||||
dict[str, List[int]]: 常见BOSS键组合的名称和对应的虚拟键码
|
||||
"""
|
||||
return {
|
||||
"alt_tab": [0x12, 0x09], # Alt+Tab
|
||||
"ctrl_alt_d": [0x11, 0x12, 0x44], # Ctrl+Alt+D
|
||||
"win_d": [0x5B, 0x44], # Win+D (显示桌面)
|
||||
"win_m": [0x5B, 0x4D], # Win+M (最小化所有窗口)
|
||||
"f1": [0x70], # F1
|
||||
"f2": [0x71], # F2
|
||||
"f3": [0x72], # F3
|
||||
"f4": [0x73], # F4
|
||||
"alt_f4": [0x12, 0x73], # Alt+F4 (关闭窗口)
|
||||
"ctrl_shift_esc": [0x11, 0x10, 0x1B], # Ctrl+Shift+Esc (任务管理器)
|
||||
}
|
||||
|
||||
|
||||
def describe_vk_combination(vk_codes: List[int]) -> str:
|
||||
"""
|
||||
描述虚拟键码组合
|
||||
|
||||
Args:
|
||||
vk_codes (List[int]): Windows虚拟键码列表
|
||||
|
||||
Returns:
|
||||
str: 组合键的描述字符串
|
||||
|
||||
Examples:
|
||||
>>> describe_vk_combination([0x11, 0x12, 0x44])
|
||||
'Ctrl+Alt+D'
|
||||
>>> describe_vk_combination([0x70])
|
||||
'F1'
|
||||
"""
|
||||
key_names = vk_codes_to_key_names(vk_codes)
|
||||
return "+".join(name.title() for name in key_names)
|
||||
@@ -2,9 +2,9 @@ import asyncio
|
||||
from typing import Literal
|
||||
from app.utils.device_manager.utils import BaseDevice, ExeRunner, DeviceStatus
|
||||
from app.utils.logger import get_logger
|
||||
from app.utils.device_manager.postMessage import (
|
||||
post_keys_to_hwnd,
|
||||
post_keys_to_hwnd_sync,
|
||||
from app.utils.device_manager.keyboard_utils import (
|
||||
vk_codes_to_key_names,
|
||||
send_key_combination,
|
||||
)
|
||||
import psutil
|
||||
from pydantic import BaseModel
|
||||
@@ -156,6 +156,7 @@ class LDManager(BaseDevice):
|
||||
|
||||
try:
|
||||
emulator_info = result.get(int(idx))
|
||||
print(emulator_info)
|
||||
if not emulator_info:
|
||||
self.logger.error(f"未找到模拟器{idx}的信息")
|
||||
return False, {}, DeviceStatus.UNKNOWN
|
||||
@@ -164,10 +165,11 @@ class LDManager(BaseDevice):
|
||||
|
||||
# 计算状态码
|
||||
if emulator_info.in_android == 1:
|
||||
status = DeviceStatus.STARTING
|
||||
status = DeviceStatus.ONLINE
|
||||
elif emulator_info.in_android == 2:
|
||||
if emulator_info.vbox_pid > 0:
|
||||
status = DeviceStatus.ONLINE
|
||||
status = DeviceStatus.STARTING
|
||||
# 雷电启动后, vbox_pid为-1, 目前不知道有什么区别
|
||||
else:
|
||||
status = DeviceStatus.STARTING
|
||||
elif emulator_info.in_android == 0:
|
||||
@@ -230,10 +232,10 @@ class LDManager(BaseDevice):
|
||||
|
||||
async def send_boss_key(
|
||||
self,
|
||||
idx: str,
|
||||
boss_keys: list[int],
|
||||
result: EmulatorInfo,
|
||||
is_show: bool = False, # True: 显示, False: 隐藏
|
||||
is_show: bool = False,
|
||||
# True: 显示, False: 隐藏
|
||||
) -> bool:
|
||||
"""
|
||||
发送BOSS键
|
||||
@@ -242,19 +244,37 @@ class LDManager(BaseDevice):
|
||||
idx (str): 模拟器索引
|
||||
boss_keys (list[int]): BOSS键的虚拟键码列表
|
||||
result (EmulatorInfo): 模拟器信息
|
||||
is_show (bool, optional): 隐藏或显示窗口,默认为 False(隐藏)。
|
||||
is_show (bool, optional): 将要隐藏或显示窗口,默认为 False(隐藏)。
|
||||
"""
|
||||
hwnd = result.top_hwnd
|
||||
if not hwnd:
|
||||
try:
|
||||
# 使用键盘工具发送按键组合
|
||||
success = await send_key_combination(vk_codes_to_key_names(boss_keys))
|
||||
if not success:
|
||||
return False
|
||||
|
||||
await post_keys_to_hwnd(hwnd, boss_keys)
|
||||
# 等待系统处理
|
||||
await asyncio.sleep(0.5)
|
||||
if win32gui.IsWindowVisible(hwnd) == (not is_show):
|
||||
|
||||
# 检查窗口可见性是否符合预期
|
||||
current_visible = win32gui.IsWindowVisible(hwnd)
|
||||
expected_visible = is_show
|
||||
|
||||
if current_visible == expected_visible:
|
||||
return True
|
||||
else:
|
||||
status = await post_keys_to_hwnd_sync(hwnd, boss_keys)
|
||||
return status == (not is_show)
|
||||
# 如果第一次没有成功,再试一次
|
||||
success = await send_key_combination(vk_codes_to_key_names(boss_keys))
|
||||
if not success:
|
||||
return False
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
current_visible = win32gui.IsWindowVisible(hwnd)
|
||||
return current_visible == expected_visible
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"发送BOSS键失败: {e}")
|
||||
return False
|
||||
|
||||
async def hide_device(
|
||||
self,
|
||||
@@ -268,7 +288,7 @@ class LDManager(BaseDevice):
|
||||
if status != DeviceStatus.ONLINE:
|
||||
return False, status
|
||||
|
||||
return await self.send_boss_key(idx, boss_keys, result, False), status
|
||||
return await self.send_boss_key(boss_keys, result, False), status
|
||||
|
||||
async def show_device(
|
||||
self,
|
||||
@@ -282,7 +302,7 @@ class LDManager(BaseDevice):
|
||||
if status != DeviceStatus.ONLINE:
|
||||
return False, status
|
||||
|
||||
return await self.send_boss_key(idx, boss_keys, result, True), status
|
||||
return await self.send_boss_key(boss_keys, result, True), status
|
||||
|
||||
async def get_adb_ports(self, pid: int) -> int:
|
||||
"""使用psutil获取adb端口"""
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
import ctypes
|
||||
import asyncio
|
||||
|
||||
# 加载 user32.dll
|
||||
user32 = ctypes.windll.user32
|
||||
|
||||
# Windows 消息常量
|
||||
WM_KEYDOWN = 0x0100
|
||||
WM_KEYUP = 0x0101
|
||||
|
||||
|
||||
async def post_key_to_hwnd(hwnd: int, vk_code: int) -> bool:
|
||||
"""
|
||||
使用 PostMessage 向指定窗口句柄发送一个完整的按键(按下 + 释放)。
|
||||
!由于 PostMessage 是异步的,并不能保证按键一定被处理。
|
||||
|
||||
参数:
|
||||
hwnd (int): 目标窗口句柄
|
||||
vk_code (int): 虚拟键码
|
||||
|
||||
注意:
|
||||
- 此函数不检查 hwnd 是否有效,也不等待消息处理(异步)。
|
||||
- 如果 hwnd 无效,PostMessage 会静默失败。
|
||||
"""
|
||||
if hwnd <= 0:
|
||||
raise ValueError("hwnd 必须是正整数")
|
||||
if not (0 <= vk_code <= 0xFFFF):
|
||||
raise ValueError("vk_code 必须是有效的虚拟键码(0~65535)")
|
||||
|
||||
user32.PostMessageW(hwnd, WM_KEYDOWN, vk_code, 0)
|
||||
user32.PostMessageW(hwnd, WM_KEYUP, vk_code, 0)
|
||||
return True
|
||||
|
||||
|
||||
async def send_key_to_hwnd_sync(hwnd: int, vk_code: int) -> bool:
|
||||
"""
|
||||
使用 SendMessage 向指定窗口句柄同步发送一个完整的按键(按下 + 释放)。
|
||||
|
||||
参数:
|
||||
hwnd (int): 目标窗口句柄
|
||||
vk_code (int): 虚拟键码
|
||||
|
||||
返回:
|
||||
bool:
|
||||
- 对于 SendMessage,返回值是目标窗口过程(WindowProc)对消息的返回值。
|
||||
- 通常非零表示成功,但具体含义由目标窗口定义。
|
||||
- 如果 hwnd 无效,会返回 0。
|
||||
|
||||
注意:
|
||||
- 此调用是**同步阻塞**的:当前线程会等待目标窗口处理完消息才返回。
|
||||
- 如果目标窗口无响应(hung),当前程序也会卡住!
|
||||
"""
|
||||
if hwnd <= 0:
|
||||
raise ValueError("hwnd 必须是正整数")
|
||||
if not (0 <= vk_code <= 0xFFFF):
|
||||
raise ValueError("vk_code 必须是有效的虚拟键码(0~65535)")
|
||||
|
||||
# 发送 WM_KEYDOWN
|
||||
result_down = user32.SendMessageW(hwnd, WM_KEYDOWN, vk_code, 0)
|
||||
# 发送 WM_KEYUP
|
||||
result_up = user32.SendMessageW(hwnd, WM_KEYUP, vk_code, 0)
|
||||
|
||||
return bool(result_down and result_up)
|
||||
|
||||
|
||||
async def post_keys_to_hwnd(
|
||||
hwnd: int, vk_codes: list[int], hold_time: float = 0.05
|
||||
) -> bool:
|
||||
"""
|
||||
使用 PostMessage 向指定窗口句柄同时发送多个按键
|
||||
!由于 PostMessage 是异步的,并不能保证按键一定被处理。
|
||||
|
||||
参数:
|
||||
hwnd (int): 目标窗口句柄
|
||||
vk_codes (List[int]): 虚拟键码列表
|
||||
hold_time (float): 按键保持时间(秒),默认 0.05 秒
|
||||
|
||||
返回:
|
||||
bool: 总是返回 True(PostMessage 不提供错误反馈)
|
||||
|
||||
注意:
|
||||
- 此函数不检查 hwnd 是否有效,也不等待消息处理(异步)。
|
||||
- 如果 hwnd 无效,PostMessage 会静默失败。
|
||||
- 按键顺序:先按下所有键,等待,然后按相反顺序释放所有键。
|
||||
"""
|
||||
if hwnd <= 0:
|
||||
raise ValueError("hwnd 必须是正整数")
|
||||
if not vk_codes:
|
||||
raise ValueError("vk_codes 不能为空")
|
||||
|
||||
# 验证所有虚拟键码
|
||||
for vk_code in vk_codes:
|
||||
if not (0 <= vk_code <= 0xFFFF):
|
||||
raise ValueError(f"vk_code {vk_code} 必须是有效的虚拟键码(0~65535)")
|
||||
|
||||
# 按下所有按键
|
||||
for vk_code in vk_codes:
|
||||
user32.PostMessageW(hwnd, WM_KEYDOWN, vk_code, 0)
|
||||
|
||||
# 保持按键状态
|
||||
await asyncio.sleep(hold_time)
|
||||
|
||||
# 按相反顺序释放所有按键(模拟真实按键行为)
|
||||
for vk_code in reversed(vk_codes):
|
||||
user32.PostMessageW(hwnd, WM_KEYUP, vk_code, 0)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def post_keys_to_hwnd_sync(
|
||||
hwnd: int, vk_codes: list[int], hold_time: float = 0.05
|
||||
) -> bool:
|
||||
"""
|
||||
使用 SendMessage 向指定窗口句柄同步发送多个按键
|
||||
先按下所有按键,等待指定时间,然后释放所有按键。
|
||||
|
||||
参数:
|
||||
hwnd (int): 目标窗口句柄
|
||||
vk_codes (List[int]): 虚拟键码列表
|
||||
hold_time (float): 按键保持时间(秒),默认 0.05 秒
|
||||
|
||||
返回:
|
||||
bool: 如果所有消息都成功发送则返回 True
|
||||
|
||||
注意:
|
||||
- 此调用是**同步阻塞**的:当前线程会等待目标窗口处理完每个消息才继续。
|
||||
- 如果目标窗口无响应(hung),当前程序也会卡住!
|
||||
- 按键顺序:先按下所有键,等待,然后按相反顺序释放所有键。
|
||||
"""
|
||||
if hwnd <= 0:
|
||||
raise ValueError("hwnd 必须是正整数")
|
||||
if not vk_codes:
|
||||
raise ValueError("vk_codes 不能为空")
|
||||
|
||||
# 验证所有虚拟键码
|
||||
for vk_code in vk_codes:
|
||||
if not (0 <= vk_code <= 0xFFFF):
|
||||
raise ValueError(f"vk_code {vk_code} 必须是有效的虚拟键码(0~65535)")
|
||||
|
||||
# 按下所有按键
|
||||
down_results = []
|
||||
for vk_code in vk_codes:
|
||||
result = user32.SendMessageW(hwnd, WM_KEYDOWN, vk_code, 0)
|
||||
down_results.append(result)
|
||||
|
||||
# 保持按键状态
|
||||
await asyncio.sleep(hold_time)
|
||||
|
||||
# 按相反顺序释放所有按键(模拟真实按键行为)
|
||||
up_results = []
|
||||
for vk_code in reversed(vk_codes):
|
||||
result = user32.SendMessageW(hwnd, WM_KEYUP, vk_code, 0)
|
||||
up_results.append(result)
|
||||
|
||||
# 如果所有按键操作都成功,则返回 True
|
||||
return all(down_results) and all(up_results)
|
||||
Reference in New Issue
Block a user