KeyPress/docs/DEVELOPER_GUIDE.md

592 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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