592 lines
12 KiB
Markdown
592 lines
12 KiB
Markdown
# 开发者指南
|
||
|
||
本文档为 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**: 更多技术文档和示例
|
||
|
||
欢迎贡献代码和文档!🎉 |