KeyPress/docs/DEVELOPER_GUIDE.md

592 lines
12 KiB
Markdown
Raw Permalink Normal View History

2025-10-22 17:26:03 +08:00
# 开发者指南
本文档为 KeyPressRemark 项目的开发者提供详细的技术指导和代码贡献指南。
## 📋 目录
- [开发环境设置](#开发环境设置)
- [项目架构](#项目架构)
- [核心技术](#核心技术)
- [代码规范](#代码规范)
- [开发流程](#开发流程)
- [测试指南](#测试指南)
- [调试技巧](#调试技巧)
- [性能优化](#性能优化)
- [常见问题](#常见问题)
## 🛠️ 开发环境设置
### 环境要求
- **操作系统**: Windows 10/11 (开发和测试)
- **Python**: 3.11+ (推荐 3.11.x)
- **IDE**: PyCharm / VSCode (推荐)
- **版本控制**: Git
### 开发环境配置
1. **克隆项目**
```bash
git clone <repository-url>
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/) 规范:
```
<type>[optional scope]: <description>
[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**: 更多技术文档和示例
欢迎贡献代码和文档!🎉