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