# 开发者指南 本文档为 KeyPressRemark 项目的开发者提供详细的技术指导和代码贡献指南。 ## 📋 目录 - [开发环境设置](#开发环境设置) - [项目架构](#项目架构) - [核心技术](#核心技术) - [代码规范](#代码规范) - [开发流程](#开发流程) - [测试指南](#测试指南) - [调试技巧](#调试技巧) - [性能优化](#性能优化) - [常见问题](#常见问题) ## 🛠️ 开发环境设置 ### 环境要求 - **操作系统**: Windows 10/11 (开发和测试) - **Python**: 3.11+ (推荐 3.11.x) - **IDE**: PyCharm / VSCode (推荐) - **版本控制**: Git ### 开发环境配置 1. **克隆项目** ```bash git clone cd KeyPressRemark ``` 2. **创建虚拟环境** ```bash # 使用 uv (推荐) uv venv --python 3.11 .venv\Scripts\activate # 或使用 venv python -m venv .venv .venv\Scripts\activate ``` 3. **安装依赖** ```bash # 开发依赖(包含测试工具) uv pip install -r requirements.txt uv add --dev pytest pytest-qt black isort flake8 ``` 4. **IDE 配置** - 设置 Python 解释器为虚拟环境中的 Python - 配置代码格式化工具(Black) - 启用类型检查(mypy) ### 调试配置 **VSCode launch.json**: ```json { "version": "0.2.0", "configurations": [ { "name": "KeyPressRemark", "type": "python", "request": "launch", "program": "${workspaceFolder}/src/main.py", "console": "integratedTerminal", "justMyCode": true } ] } ``` ## 🏗️ 项目架构 ### 整体架构 ``` KeyPressRemark/ ├── src/ # 源代码 │ ├── core/ # 核心功能层 │ ├── gui/ # 用户界面层 │ ├── utils/ # 工具库层 │ └── main.py # 应用入口 ├── resources/ # 资源文件 ├── tests/ # 测试代码 └── docs/ # 文档 ``` ### 架构原则 1. **分层架构**: 核心功能、界面、工具明确分离 2. **依赖注入**: 通过构造函数传递依赖 3. **单一职责**: 每个模块只负责一个功能域 4. **开闭原则**: 对扩展开放,对修改封闭 ### 模块依赖关系 ``` main.py ├── gui.main_window (界面层) │ ├── core.window_selector (窗口选择) │ ├── core.key_sender (按键发送) │ └── utils.settings (设置管理) ├── core.admin_check (权限检查) └── resources (资源文件) ``` ## 🔧 核心技术 ### Windows API 技术 本项目大量使用 Windows API,主要包括: 1. **用户交互 API** - `win32gui`: 窗口操作、消息发送 - `win32api`: 系统信息获取 - `win32con`: 常量定义 2. **底层输入 API** - `SendMessage`: 同步消息发送 - `PostMessage`: 异步消息发送 - `SendInput`: 系统级输入模拟 3. **系统权限 API** - UAC 提升检查 - 进程权限管理 ### PyQt5 技术 1. **界面架构** - QWidget: 基础窗口组件 - QLayout: 布局管理 - QTimer: 定时器功能 2. **事件处理** - 信号槽机制 - 事件过滤器 - 自定义事件 ### 按键发送原理 #### 1. PostMessage 模式 ```python # 异步发送,消息进入目标窗口队列 win32gui.PostMessage(hwnd, WM_KEYDOWN, key_code, lparam) win32gui.PostMessage(hwnd, WM_KEYUP, key_code, lparam) ``` #### 2. SendMessage 模式 ```python # 同步发送,等待处理完成 win32gui.SendMessage(hwnd, WM_KEYDOWN, key_code, lparam) win32gui.SendMessage(hwnd, WM_KEYUP, key_code, lparam) ``` #### 3. SendInput 模式 ```python # 系统级模拟,最高兼容性 inputs = (INPUT * 2)() # 设置输入结构... ctypes.windll.user32.SendInput(2, ctypes.byref(inputs), ctypes.sizeof(INPUT)) ``` ## 📝 代码规范 ### Python 编码规范 1. **PEP 8 标准** - 使用 4 个空格缩进 - 行长度不超过 88 字符(Black 标准) - 函数和类之间用两个空行分隔 2. **命名规范** - 类名:`PascalCase` - 函数/变量:`snake_case` - 常量:`UPPER_CASE` - 私有成员:`_underscore_prefix` 3. **类型注解** ```python def send_key(self, key_code: int, mode: str = "post") -> bool: """发送按键""" pass ``` ### 文档规范 1. **模块文档** ```python """ 模块名 - 简短描述 详细说明模块的功能、用途和技术原理。 作者: xiao liu 版本: v0.1.0 """ ``` 2. **函数文档** ```python def function_name(param1: type, param2: type) -> return_type: """ 函数简短描述 详细说明函数的功能、算法或注意事项。 Args: param1: 参数1说明 param2: 参数2说明 Returns: 返回值说明 Raises: Exception: 异常情况说明 """ ``` 3. **行内注释** ```python # 检查目标窗口是否有效 if not self.target_hwnd or not win32gui.IsWindow(self.target_hwnd): return False ``` ### Git 提交规范 使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范: ``` [optional scope]: [optional body] [optional footer(s)] ``` **类型说明**: - `feat`: 新功能 - `fix`: 修复 bug - `docs`: 文档更新 - `style`: 代码格式化 - `refactor`: 代码重构 - `test`: 测试相关 - `chore`: 构建过程或辅助工具的变动 **示例**: ``` feat(core): 添加新的按键发送模式 - 实现了 SendInput 模式 - 提高了兼容性 - 添加了相关测试 Closes #123 ``` ## 🔄 开发流程 ### 功能开发流程 1. **需求分析** - 在 GitHub Issues 中创建需求 - 分析技术可行性 - 设计 API 接口 2. **创建分支** ```bash git checkout -b feature/new-feature-name ``` 3. **开发实现** - 编写核心逻辑 - 添加单元测试 - 更新文档 4. **代码检查** ```bash # 格式化代码 black src/ isort src/ # 静态检查 flake8 src/ mypy src/ # 运行测试 pytest tests/ ``` 5. **提交代码** ```bash git add . git commit -m "feat: 添加新功能" git push origin feature/new-feature-name ``` 6. **创建 Pull Request** - 填写详细的变更说明 - 添加测试截图(如适用) - 等待代码审查 ### Bug 修复流程 1. **复现问题** - 记录复现步骤 - 确认影响范围 - 添加回归测试 2. **定位问题** - 使用调试器定位 - 查看日志信息 - 分析堆栈跟踪 3. **修复实现** - 最小化修改范围 - 确保不引入新问题 - 验证修复效果 ## 🧪 测试指南 ### 测试框架 项目使用 `pytest` 和 `pytest-qt` 进行测试: ```python import pytest from PyQt5.QtWidgets import QApplication from src.gui.main_window import KeyPresser @pytest.fixture def app(): """创建 QApplication 实例""" app = QApplication([]) yield app app.quit() def test_window_creation(app): """测试窗口创建""" window = KeyPresser() assert window is not None assert window.windowTitle() == "KeyPresser(无钩子版)" ``` ### 测试分类 1. **单元测试** (`tests/unit/`) - 测试单个函数/方法 - 使用 mock 隔离依赖 - 覆盖边界条件 2. **集成测试** (`tests/integration/`) - 测试模块间交互 - 验证 API 契约 - 测试数据流 3. **界面测试** (`tests/gui/`) - 使用 pytest-qt - 模拟用户操作 - 验证界面响应 ### 运行测试 ```bash # 运行所有测试 pytest # 运行特定测试文件 pytest tests/test_key_sender.py # 运行特定测试函数 pytest tests/test_key_sender.py::test_post_message # 生成覆盖率报告 pytest --cov=src --cov-report=html ``` ## 🐛 调试技巧 ### 日志记录 使用 Python 标准库 `logging`: ```python import logging # 配置日志 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('keypress.log'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) def send_key(self, key_code: int) -> bool: logger.debug(f"发送按键: {key_code}") try: # 发送逻辑 result = self._do_send_key(key_code) logger.info(f"按键发送成功: {key_code}") return result except Exception as e: logger.error(f"按键发送失败: {key_code}, 错误: {e}") return False ``` ### 调试 Windows API 1. **检查窗口句柄** ```python import win32gui def debug_window_info(hwnd): if win32gui.IsWindow(hwnd): title = win32gui.GetWindowText(hwnd) class_name = win32gui.GetClassName(hwnd) print(f"窗口: {title}, 类名: {class_name}") else: print("无效的窗口句柄") ``` 2. **监控消息发送** ```python def send_key_with_debug(self, key_code: int): print(f"发送按键: {hex(key_code)} 到窗口: {hex(self.target_hwnd)}") result = win32gui.PostMessage(self.target_hwnd, WM_KEYDOWN, key_code, 0) print(f"PostMessage 返回值: {result}") ``` ### 性能分析 使用 `cProfile` 进行性能分析: ```bash python -m cProfile -o profile.stats src/main.py ``` 查看结果: ```python import pstats p = pstats.Stats('profile.stats') p.sort_stats('cumulative').print_stats(10) ``` ## ⚡ 性能优化 ### 按键发送优化 1. **批量发送** ```python def send_keys_batch(self, keys: List[int], mode: str = "post"): """批量发送按键,减少 API 调用次数""" for key_code in keys: self.send_key(key_code, mode) ``` 2. **缓存窗口信息** ```python class KeySender: def __init__(self): self._window_cache = {} def get_window_info(self, hwnd): if hwnd not in self._window_cache: self._window_cache[hwnd] = { 'title': win32gui.GetWindowText(hwnd), 'class': win32gui.GetClassName(hwnd) } return self._window_cache[hwnd] ``` ### 界面响应优化 1. **异步操作** ```python from PyQt5.QtCore import QThread, pyqtSignal class KeySendThread(QThread): finished = pyqtSignal() def __init__(self, key_sender, key_code): super().__init__() self.key_sender = key_sender self.key_code = key_code def run(self): self.key_sender.send_key(self.key_code) self.finished.emit() ``` 2. **减少重绘** ```python # 批量更新界面 self.setUpdatesEnabled(False) # 进行多个界面操作... self.setUpdatesEnabled(True) ``` ## ❓ 常见问题 ### Q1: 按键发送不生效 **可能原因**: 1. 目标应用需要管理员权限 2. 应用使用了低级键盘钩子 3. 窗口句柄已失效 **解决方案**: ```python # 检查窗口是否有效 if not win32gui.IsWindow(self.target_hwnd): print("窗口句柄已失效,需要重新选择") # 尝试不同的发送模式 for mode in ["post", "send_message", "send"]: if self.send_key(key_code, mode): print(f"模式 {mode} 发送成功") break ``` ### Q2: 界面冻结 **原因**: 在主线程执行耗时操作 **解决方案**: ```python # 使用 QTimer 分解操作 def process_large_task(self): self.timer = QTimer() self.timer.timeout.connect(self.process_chunk) self.timer.start(10) # 每10ms处理一小块 def process_chunk(self): # 处理一小部分数据 if self.is_finished(): self.timer.stop() ``` ### Q3: 内存泄漏 **检查点**: 1. Qt 对象是否正确释放 2. 定时器是否正确停止 3. 事件监听器是否移除 **解决方案**: ```python def cleanup(self): # 停止所有定时器 for timer in self.timers: timer.stop() # 断开信号连接 self.some_signal.disconnect() # 释放资源 if hasattr(self, 'key_sender'): del self.key_sender ``` ### Q4: 依赖安装问题 **pyqt5-qt5 版本问题**: ```bash # 强制安装特定版本 uv pip install PyQt5==5.15.11 pyqt5-qt5==5.15.2 --force-reinstall ``` **权限问题**: ```bash # 以管理员身份运行命令提示符 # 或者使用 --user 参数 pip install --user PyQt5 ``` --- ## 📞 获取帮助 - **GitHub Issues**: 报告 bug 和功能请求 - **讨论区**: 技术讨论和经验分享 - **Wiki**: 更多技术文档和示例 欢迎贡献代码和文档!🎉