From 2ab402e8ff206d104517cabc9b754ce2b248b67a Mon Sep 17 00:00:00 2001 From: MoeSnowyFox Date: Sat, 27 Sep 2025 04:47:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BD=BF=E7=94=A8keyboard=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/device_manager/keyboard_utils.py | 278 +++++++++++++++++++++ app/utils/device_manager/ldplayer.py | 58 +++-- app/utils/device_manager/postMessage.py | 156 ------------ 3 files changed, 317 insertions(+), 175 deletions(-) create mode 100644 app/utils/device_manager/keyboard_utils.py delete mode 100644 app/utils/device_manager/postMessage.py diff --git a/app/utils/device_manager/keyboard_utils.py b/app/utils/device_manager/keyboard_utils.py new file mode 100644 index 0000000..a451f8d --- /dev/null +++ b/app/utils/device_manager/keyboard_utils.py @@ -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) diff --git a/app/utils/device_manager/ldplayer.py b/app/utils/device_manager/ldplayer.py index 2e505d1..3f4d089 100644 --- a/app/utils/device_manager/ldplayer.py +++ b/app/utils/device_manager/ldplayer.py @@ -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: - return False + 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): - return True - else: - status = await post_keys_to_hwnd_sync(hwnd, boss_keys) - return status == (not is_show) + # 等待系统处理 + await asyncio.sleep(0.5) + + # 检查窗口可见性是否符合预期 + current_visible = win32gui.IsWindowVisible(hwnd) + expected_visible = is_show + + if current_visible == expected_visible: + return True + else: + # 如果第一次没有成功,再试一次 + 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端口""" diff --git a/app/utils/device_manager/postMessage.py b/app/utils/device_manager/postMessage.py deleted file mode 100644 index 0f9b0f3..0000000 --- a/app/utils/device_manager/postMessage.py +++ /dev/null @@ -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)