Files
FancyInput-Qt/MIGRATION_PLAN.md
T
2026-08-19 23:18:08 +08:00

32 KiB
Raw Blame History

FancyInput WPF → Qt 6 (QML + C++) 迁移方案

版本:v2.0 · 日期:2026-08-19 · 状态:待评审 源项目:WPF\FancyInput(.NET 8.0 WPF,约 132 文件 / 24,000 行) 目标框架:Qt 6.8.3(MinGW 13.1.0 x64 + CMake + QML) 目标:UI 布局完全一致、逻辑相同,代码用 QML + C++ 重写 构建约束:CMake + MinGW(mingw32-make 生成器,无 Ninja),每阶段结束即可就地编译调试 许可约束:只使用可免费商用的组件,不使用 GPL/AGPL 强传染许可的开源模块


〇、许可合规策略(重要)

核心原则:全部组件要么是 LGPL 动态链接(可免费闭源商用),要么是宽松许可(MIT/zlib/BSD),一律避开 GPL/AGPL。

Qt 模块选型(Qt 6.8.3 开源版,LGPL v3)

Qt 模块 许可 商用方式 本项目用途 采用
Qt6Core / Gui / Qml / Quick LGPL v3 动态链接 DLL + 附 LGPL 声明 → 可闭源免费商用 基础/界面 ✅
Qt6QuickControls2 LGPL v3 同上 控件库 ✅
Qt6Network LGPL v3 同上 更新检查 ✅
Qt6Multimedia LGPL v3 同上 GIF/音效(可延后) ✅
Qt6Svg LGPL v3 同上 图标渲染(可选) ✅
Qt6QuickShapes LGPL v3 同上 矢量图标(替代 PathData) ✅
Qt6Charts GPL v3 需商业授权 不需要 ❌
Qt6DataVisualization GPL v3 需商业授权 不需要 ❌
QtGamepad 仅商业版 开源版没有该模块 手柄(改用自写 XInput/SDL3 封装) ❌

LGPL 合规要点(务必遵守):

  1. 动态链接 Qt 共享库(默认方式),禁止 -static 静态链接 Qt;
  2. 发布时附带 LGPLv3 许可文本 + 使用的 Qt 版本/模块清单;
  3. 随包分发 Qt DLL(windeployqt 自动拷贝),不修改 Qt 库源码;
  4. 应用自身代码可完全闭源。

第三方依赖选型

库 许可 商用 用途 采用
SDL3(C API) zlib ✅ 免费商用 手柄读取后端 ✅
XInput(xinput1_4.dll) 系统组件 ✅ 手柄读取后端(备选) ✅
ViGEmClient MIT ✅ 免费商用 虚拟 Xbox 手柄(延后阶段) ✅
HidHideCLI.exe GPL v3 ❌ 需授权/分发限制 隐藏真实手柄(WPF 中已注释) ❌ 不迁移
MouseKeyHook BSD-2 ✅ WPF 用;Qt 侧改用 Raw Input,不需要 ❌
WpfAnimatedGif MIT ✅ WPF 用;Qt 侧用 QML AnimatedImage/QMovie ❌

结论:所有用到的组件均可免费商用;HidHide 因 GPL 放弃迁移(WPF 中本就是注释代码)。


一、现状盘点

1.1 WPF 项目结构(迁移源)

层 文件数 行数 说明
Models/ 13 ~4,569 InputParser / RawInputParser / InputHandler / InputGenerator / XImage / OverlayParser / Macro 引擎
ViewModels/ 14 ~5,435 MainWindow / ElementTree / ElementViewModel×3 / Macro / ImageEditor 等
Views/Controls/ 22 ~2,050 IconButton / AnimatedImage / 五面板 / Terminal / FormRow 等
Views/Elements/ 22 ~1,610 10 种叠层元素编辑器 + ElementTest
Views/Macro/ 12 ~1,440 宏窗口 / 轨道 / 动作按钮 / 属性面板
Views/(根+窗口) 26 ~3,800 主窗 / 叠层窗 / 元素树窗 / 图像编辑器 / 辅助窗口
Common/ 5 ~340 枚举 / PathData / 转换器
根目录 7 ~1,900 App / MainWindow
XAML 样式 10 ~2,888 全局主题(紫色 #FF651F65 + Cascadia Mono)

1.2 功能模块清单

  1. 输入捕获:键盘(RawInput + 全局钩子双路径)、鼠标按键/移动/滚轮、XInput 手柄、SDL3 手柄(可切换)、Guide 键
  2. 输入合成(反向链路):SendInput 键鼠合成、ViGEm 虚拟 Xbox360 手柄、HidHide 手柄伪装
  3. 叠层系统:透明置顶穿透窗口、多窗口管理、10 种元素(键盘/鼠标/手柄按钮/滚轮/摇杆/扳机/玩家灯/DPad/鼠标移动/贴图)、帧切换驱动
  4. 元素树编辑器:列表 + 画布 + 属性面板、拖拽/框选/对齐/移动绑定、图集生成、PNG+JSON / .fip 项目文件导出
  5. 图像编辑器:裁剪/缩放/通道分离合并/圆形擦除/四方向合并/图集打包/拼接/GIF
  6. 宏系统:三轨道(Pre/On/Post)、6 种动作(命令/键盘/鼠标按键/鼠标移动/滚轮/延迟)、Once/Hold/Toggle 触发、录制、.fipm 文件
  7. 工坊:宏管理入口 + 默认路径
  8. 主窗口五面板:读取 / 创建 / 管理 / 工坊 / 设置
  9. 辅助窗口:关于 / 更新提示 / 版本时间线 / 二维码 / 控制台 / 开源声明 / 帮助 / 自定义消息框
  10. 系统级:单实例(Mutex + 命名管道)、托盘图标、管理员自提权、在线更新检查、config.json 持久化

1.3 本机 Qt 环境(已确认可用)

D:\Qt\6.8.3\mingw_64        ← Qt 6.8.3 MinGW 套件(qmake 已确认)
D:\Qt\Tools\mingw1310_64    ← MinGW 13.1.0(g++ 13.1.0 / mingw32-make)
cmake 4.3.2(C:\Program Files\CMake)
QML 模块:QtQuick / QuickControls2 / Network / Multimedia / Svg 全部可用(LGPL)
注意:
  - QtGamepad 模块开源版不存在 → 手柄后端用 SDL3(zlib)/ XInput(系统组件)C++ 封装
  - 无 Ninja → 用 "MinGW Makefiles" 生成器 + mingw32-make
  - QtCharts / DataVisualization 为 GPL → 不用

二、目标架构(Qt 6 + QML + C++)

2.1 目录结构(在 Qt\ 下新建)

Qt\
├── CMakeLists.txt                     # 顶层工程(Qt6 + QML 模块)
├── MIGRATION_PLAN.md                  # 本文档
├── resources\
│   ├── icons\                         # Icon.ico / Icon.png(拷贝自 WPF)
│   ├── qrcodes\                       # bilibili.jpg / wechat.jpg / xlworkspace.jpg
│   ├── styles\                        # 主题 qmldir + QML 样式组件
│   └── Update.txt                     # 本地更新说明
├── src\
│   ├── core\                          # 纯 C++ 逻辑层(不依赖 QML)
│   │   ├── input\                     # InputParser / RawInputFilter / InputHandler / InputGenerator
│   │   │   ├── enums.h/.cpp           # 全部枚举(FipKeys/FipRawKeys/InputDevice/…)
│   │   │   ├── inputevent.h           # InputEvent 统一事件结构(Q_GADGET)
│   │   │   ├── win32rawinputfilter.h/.cpp   # QAbstractNativeEventFilter + RawInput
│   │   │   ├── inputparser.h/.cpp     # 统一采集器(QTimer 心跳 + 双手柄后端)
│   │   │   ├── xinputbackend.h/.cpp   # XInput LoadLibrary 封装
│   │   │   ├── sdl3backend.h/.cpp     # SDL3 手柄封装(链接 SDL3 C API)
│   │   │   ├── inputhandler.h/.cpp    # 事件→元素状态分发
│   │   │   ├── inputgenerator.h/.cpp  # SendInput / ViGEm / HidHide
│   │   │   └── vigemmanager.h/.cpp    # ViGEmClient C++ SDK 封装
│   │   ├── image\                     # XImage 算法层(QImage 重写)
│   │   │   ├── ximage.h/.cpp          # Crop/ReSize/EraseCircle/Merge/PackImages/ConcatImages
│   │   │   └── gifplayer.h/.cpp       # QImageReader/QMovie 帧延迟封装
│   │   ├── overlay\                   # 叠层数据模型 + 序列化
│   │   │   ├── overlayparser.h/.cpp   # OverlayRoot/OverlayElement JSON 读写
│   │   │   └── overlaytypes.h         # OverlayElement struct
│   │   ├── macro\                     # 宏引擎
│   │   │   ├── macroaction.h/.cpp     # 6 种动作 + DTO 序列化
│   │   │   ├── macroplayer.h/.cpp     # 播放引擎(QTimer 状态机 + QProcess)
│   │   │   ├── macrotrack.h/.cpp      # 三轨道 + 录制
│   │   │   └── macro.h/.cpp           # MacroViewModel 等价(触发/循环/保存)
│   │   └── app\                       # 应用服务
│   │       ├── appconfig.h/.cpp       # config.json / .fips / 单实例 / 托盘
│   │       ├── updatechecker.h/.cpp   # QNetworkAccessManager 更新检查
│   │       └── singleinstance.h/.cpp  # QLocalServer/QLocalSocket 单实例 + 管道消息
│   ├── models\                        # QObject 模型层(QML 可绑定)
│   │   ├── elementviewmodel.h/.cpp    # ElementViewModel(Q_PROPERTY + Q_ENUM)
│   │   ├── elementtreeviewmodel.h/.cpp# 元素树模型(QAbstractListModel + DTO)
│   │   ├── overlaywindowviewmodel.h/.cpp
│   │   ├── mainwindowviewmodel.h/.cpp # 主窗 VM(配置/面板/叠层集合)
│   │   ├── macrowindowviewmodel.h/.cpp
│   │   └── imageeditorviewmodel.h/.cpp
│   └── qml\                           # QML 视图层
│       ├── Main.qml                   # 主窗口(五面板 + 标题 + 状态栏)
│       ├── pages\                     # ReadPage / CreatePage / ManagePage / WorkshopPanel / SettingsPage
│       ├── overlay\                   # OverlayWindow.qml + 元素渲染组件(Repeater + Image 换帧)
│       ├── editor\                    # ElementTreeWindow.qml + ElementEditor*.qml
│       ├── macro\                     # MacroWindow.qml / MacroTrack.qml / MacroActionPanel.qml
│       ├── imageeditor\               # ImageEditor.qml
│       ├── dialogs\                   # About / UpdatePrompt / RgbInput / AppMessageBox / …
│       └── controls\                  # IconButton / AnimatedImage / FormRow / ToggleSwitch / Slider / …

2.2 架构原则

  1. 分层:core/(纯 C++ 逻辑,可单测)→ models/(QObject,暴露给 QML)→ qml/(纯视图,不做业务逻辑)
  2. 与 WPF 的 MVVM 同构:WPF ViewModel → Qt QObject(Q_PROPERTY + notify 信号);WPF XAML → QML;WPF IValueConverter → QML 绑定或 C++ 转换属性;WPF Command → QML 信号处理器调用 C++ 槽
  3. UI 一致:主题色 #FF651F65 / #953895、Cascadia Mono 字体、圆角 8/10、阴影、滑动开关、现代滑杆、圆角 ComboBox——全部用 QML 自定义组件复刻
  4. 行为一致:所有阈值(200ms 滚轮停止、100ms 移动停止、XInput 20 / SDL 2000 / SDL 扳机 5、10 帧扳机缓存、12 样本滑动平均去极值、DPad 1~8 帧映射等)原样保留
  5. 数据兼容:.fip / .fips / .fipm / config.json / Overlay JSON 与 WPF 完全互通

三、逐子系统迁移方案

3.1 输入子系统(最高技术风险)

WPF 组件 Qt 6 对应 关键点
RawInputParser(WM_INPUT) Win32RawInputFilter : QAbstractNativeEventFilter qApp->installNativeEventFilter();MSG.message == WM_INPUT 分支;RegisterRawInputDevices(usage 0x06 键盘 / 0x02 鼠标 + RIDEV_INPUTSINK);绝对/相对坐标归一化;_pressedKeys 去重;修饰键/Lock 检测(GetKeyState);WM_ENTERSIZEMOVE/EXITSIZEMOVE 拖拽时注销/注册
Gma.System.MouseKeyHook 全局钩子 ① RAWMOUSE usButtonFlags 补齐按钮/滚轮(推荐,去掉钩子依赖)② 或 SetWindowsHookEx(WH_MOUSE_LL) 封装 二选一,推荐①
InputParser(DispatcherTimer 16.67ms) QTimer(Qt::PreciseTimer) + QElapsedTimer×2 滚轮停止 >200ms / 移动停止 >100ms;停止后虚拟坐标回屏幕中心;TimerInterval 钳制 [5,1000]
XInput 手柄 LoadLibrary("xinput1_4.dll") + GetProcAddress(无第三方依赖) 4 个 pad、IsConnected 过滤、按钮按位边沿、摇杆阈值 20、扳机无阈值
SDL3 手柄 SDL3 C API 静态链接(仅手柄子系统,不与 Qt 冲突) SDL_UpdateGamepads() 热插拔差分;阈值 2000/5;Y 轴反转;扳机 ×255/32767;按钮映射表(South→A…);Guide 独立设备
InputHandler 分发 C++ InputHandler(QObject) 响应者字典 / 键盘双模式字典 / DPadShift / 鼠标滑动平均 / Arrow/Dot / 摇杆换算 / 扳机帧索引 / 25ms 回弹定时器——全部照搬
InputGenerator(SendInput) user32 直调 + 结构体照搬(注意 64 位 sizeof) 扫描码合成(KEYEVENTF_SCANCODE);NumLock 小键盘表;绝对坐标 65535 归一化
VirtualControllerManager(ViGEm 反射) ViGEmClient C++ SDK 静态链接(MIT) vigem_alloc/connect/target_xbox360_alloc/add;SetButton/Trigger/Stick + submit_report;Select→Back 映射
HidHideManager QProcess 调 HidHideCLI.exe(改异步防卡 UI) 步骤照搬;JSON 解析 QJsonDocument;虚拟设备关键字 VIGEM/NEFARIUS/VIRTUAL/ROOT\
Screen / Cursor / DPI QScreen / QCursor / devicePixelRatio 高 DPI 自动处理,叠层物理像素对齐时用 devicePixelRatio

3.2 叠层与元素系统

WPF Qt 6 对应
OverlayWindow(透明置顶穿透) QML Window { flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool; color: "transparent" };穿透用 Qt.WindowTransparentForInput 运行时切换(替代 WS_EX_TRANSPARENT P/Invoke)
黄色边框/去边框模式 margin/x/y/width/height 联动 + 切换时 opacity 过渡(UpdateWindowSize 逻辑照搬)
缩放 2^Scale C++ 属性 currentWidth = initialWidth * pow(2, scale) + QML 绑定
元素渲染(Canvas+ItemsControl) Repeater + Item { x: model.posX; y: model.posY } + Image { source: model.currentFrame }——帧切换驱动换图,架构不变
多窗口 / 索引 / 置顶顺序 C++ MainWindowViewModel 持有 QList<OverlayWindowViewModel*>;raise()/lower();GamepadUserIndex 自动分配逻辑照搬
拖拽窗口 MouseArea + C++ 算坐标(或 QWindow::startSystemMove())
11 种元素 ElementViewModel(QObject)复用同一模型;每种元素渲染组件 + 编辑器组件
元素树编辑器 ElementTreeWindow.qml:左 ListView + 中 Flickable 画布(scale 缩放、锚点缩放公式照搬)+ 右属性面板 + SplitView 分栏
图集生成 QImage + QPainter 重写 PackImages/ConcatImages(含 SPACE_BETWEEN_IMAGES=3;PackImages 按高度降序逻辑照搬)
DTO 序列化 QJsonDocument 重写 .fip(含 base64 内嵌 PNG/GIF 字节);Overlay JSON 字段名不变

3.3 图像编辑器

WPF(XImage.cs 662 行) Qt 6 对应
BGRA32 像素数组 QImage::Format_ARGB32(小端同为 BGRA 字节序,可逐字节对照)
Crop(越界透明填充) QImage::copy 不透明填充 → 需 canvas.fill(Qt::transparent) + QPainter::drawImage 保持语义
ReSize / Scale QImage::scaled(..., Qt::SmoothTransformation)
EraseCircle QPainter + QPainterPath::addEllipse + CompositionMode_Clear(含包围盒优化)
ExtractChannels / MergeChannels QImage::bits() 逐像素循环,逻辑 1:1(注意参数顺序 r,g,b,a,h,w 保留)
四方向 Merge 像素级照搬(percentage 语义表:Up/Down/Left/Right 的 splitPos 公式一字不差)
PackImages QPainter drawImage 照搬(保持 WPF 的"按高度降序单行/行打包"输出一致)
ConcatImages QPainter drawImage + spacing
GIF 帧/延迟 QImageReader::imageCount()/jumpToNextImage()/nextImageDelay()(已处理 1/100s→ms 与 0→100ms)
GIF 播放 QML AnimatedImage(内部 QMovie)替代 WpfAnimatedGif
编辑器 UI ImageEditor.qml:Flickable + scale 锚点缩放(公式照搬)+ 滑杆面板;通道偏移 4 分支逻辑照搬(含透明保护、alpha 下限 1、蒙版)

3.4 宏系统

WPF Qt 6 对应
宏模型(三轨道 + 6 动作) C++ Macro/MacroTrack/MacroAction(QObject),动作枚举 Command/Keyboard/MouseButton/MouseMove/MouseWheel/Delay
触发(Once/Hold/Toggle + Press/Release) C++ 槽接 InputParser 信号;Channel 命令队列 → QQueue<TriggerCommand> + 互斥;校验逻辑(Hold 禁止 Release、宏包含触发动作禁止启用、总时长 <200ms 禁止)照搬
播放引擎 C++ 状态机:QTimer::singleShot 链式执行各动作 + 取消令牌;Hold/Toggle 循环 10ms 间隔
命令动作 QProcess(cmd /C 与 powershell -Command 参数照搬;stdout/stderr → 日志;waitUntilExit)
键盘/鼠标/滚轮/延迟动作 调 InputGenerator(SendInput / 动画插值函数照搬,含 MouseMoveAnimation 等)
录制 输入事件流 → 动作追加;5000ms 阈值拆分、滚轮合并、距离/10 步数——录制状态机照搬
取消安全语义 取消时补发 Release、防 Start/Stop 竞态(_isMacroRunning 由 Play 收尾复位)
.fipm 保存 QJsonDocument + 反射 DTO 改为手写字段序列化(字段名与 WPF 一致保证互通)
宏窗口 UI MacroWindow.qml:三栏(宏列表 / 轨道+终端 / 属性面板),WrapPanel → Flow,模板切换 → Loader,录制红点/停止按钮

3.5 主窗口与 UI 主题

WPF Qt 6 对应
标题(Fancy/Input 双色 80px Cascadia Mono) QML Text ×2(#FF651F65 + 默认黑)
五面板切换按钮(读取/创建/管理/工坊/设置) QML Button 自定义样式(选中 FancySelectedButton / 未选 FancyButton)
TabControl QML StackLayout + 按钮切换(与 WPF 面板切换行为一致)
状态栏(调试圆点 + 文字) QML Row + Rectangle(绿/红)+ Text
托盘 QSystemTrayIcon(C++)+ QML 信号桥;左键显示窗口
单实例 QLocalServer/QLocalSocket(对应 Mutex + NamedPipe);--restart-as-admin 参数处理照搬
管理员自提权 QProcess::startDetached 重启 + --restart-as-admin
更新检查 QNetworkAccessManager + QJsonDocument(API 地址、版本比较、忽略版本持久化照搬)
config.json QJsonDocument 读写 %APPDATA%/FancyInput/config.json(字段一致)

3.6 主题样式(QML 复刻)

WPF 样式 QML 复刻
FancyButton / RoundCornerButton 自定义 FancyButton.qml(radius 8、purple bg、hover #FF953895、按下 #AA953895、DropShadow)
FancyTextBox / RoundCornerTextBox FancyTextBox.qml(radius 8/4、purple border 2px、focus #FF805C8F)
SimpleToggleSwitch(滑动开关) ToggleSwitch.qml(80×36 容器、紫色 #9C27B0 滑入动画 0.2s、按下缩放 0.9)
ModernSlider FancySlider.qml(8px 轨道、紫色渐变进度、28px 圆形滑块 + 阴影)
RoundCornerComboBox FancyComboBox.qml(85×25、radius 5、箭头 Path、紫色高亮项)
SimpleScrollViewer QML ScrollView + 自定义 ScrollBar(8px 圆角 thumb #A8B3C2)
RoundedLabel / InfoTipBorder RoundedLabel.qml / InfoTip.qml
IconButton(PathData) IconButton.qml:Shape/Path 解析 SVG PathData 字符串(Qt 支持 path 子集),属性 PathFill/PathStroke/CornerRadius/IsFlipped 照搬
AnimatedImage QML AnimatedImage(GIF)+ 静态兜底逻辑

四、实施阶段划分(渐进式,每步可编译可调试)

核心原则:按依赖顺序分 8 个阶段推进,每个阶段结束都有可运行、可验证的产物(编译通过 + 可运行 + 可见结果)。宏系统、虚拟手柄、GIF 等锦上添花功能整体后移,不污染前期代码。

每阶段通用验收流程(适用于所有阶段):

  1. 编写/调整代码 → 2. cmake --build 就地编译 → 3. 运行程序验证 → 4. 有变化才进入下一步

每阶段通用的构建命令(Qt\ 目录下):

# 配置(仅首次或 CMakeLists 变更时)
cmake -S . -B build -G "MinGW Makefiles" -DCMAKE_PREFIX_PATH=D:/Qt/6.8.3/mingw_64 -DCMAKE_BUILD_TYPE=Debug
# 编译(每次改完代码)
cmake --build build -j
# 运行
.\build\FancyInputQt.exe

阶段 0:Hello World 工程骨架(可运行窗口)✅ 已完成

目标:CMake + MinGW 工程跑通,QML 窗口显示,主题色验证。之后每一步都在此骨架上增量开发。

  • Qt\CMakeLists.txt:find_package(Qt6 COMPONENTS Core Gui Qml Quick QuickControls2);qt_add_executable(FancyInputQt ...) + qt_add_qml_module
  • src\main.cpp:QGuiApplication + QQmlApplicationEngine 加载 QML(objectCreationFailed 兜底退出)
  • src\qml\Main.qml:ApplicationWindow 600×480,标题 "FancyInput · Qt 迁移版(阶段 0)"
  • src\qml\Theme.qml:全局主题单例(主色 #651F65、背景 #1E1E24、字体 Cascadia Mono)
  • 资源目录 resources\icons\(拷贝 Icon.png/ico,阶段 4 主界面时补)

产出:FancyInputQt.exe 可运行,窗口显示、主题色正确(已验证,2026-08-19)。

阶段 1:通用函数与数据结构(纯 C++,不依赖 UI)

目标:先把所有"不依赖其他代码"的通用模块落地并单测,为输入识别打基础。

  • src\core\common\Enums.h:全部枚举逐字迁移(FipKeys/FipRawKeys/KeyBoardCodeType/InputDevice/ElementType/FIPGamepadButtonflags/MoveState/TriggerMode 等,数值一致)
  • src\core\common\InputEvent.h:InputEvent 结构(对应 WPF InputArgs),Q_GADGET 便于 QML 消费
  • src\core\image\XImage.h/.cpp:QImage 重写(Crop/ReSize/Scale/EraseCircle/Merge/PackImages/ConcatImages/通道分离合并/GIF 帧),与 WPF XImage.cs API 一一对应
  • src\core\overlay\OverlayParser.h/.cpp:overlay JSON 读写(QJsonDocument),字段名与 WPF 完全一致(snake_case)
  • src\core\common\Utility.h/.cpp:剪贴板 Win32 封装(带重试)
  • 单元测试:枚举值对拍、XImage 像素操作对拍(用 WPF 生成的样本图)、Overlay JSON 往返一致

产出:核心算法库可用,单测全绿;不涉及任何 UI。

阶段 2:输入识别核心(技术风险最高,优先做)

目标:全局键盘/鼠标/手柄捕获 → 统一 InputEvent 事件流;先做识别(Read),合成(Write)下一阶段。

  • src\core\input\Win32RawInputFilter.h/.cpp:QAbstractNativeEventFilter + RegisterRawInputDevices(键盘 usage 0x06 / 鼠标 0x02 + RIDEV_INPUTSINK);WM_INPUT 解析;扫描码→FipRawKeys 映射表(60+ 分支照搬);_pressedKeys 去重;修饰键/Lock 检测
  • src\core\input\InputParser.h/.cpp:QTimer(PreciseTimer) 16.67ms 心跳;滚轮停止 >200ms / 移动停止 >100ms 判定;MoveState 语义;TimerInterval 钳制 [5,1000]
  • 鼠标按键/滚轮:RAWMOUSE usButtonFlags 补齐(不依赖 MouseKeyHook)
  • src\core\input\XInputBackend.h/.cpp:LoadLibrary("xinput1_4.dll") + GetProcAddress(系统组件,无第三方依赖);4 pad 轮询、按钮边沿、摇杆阈值 20
  • src\core\input\Sdl3Backend.h/.cpp:SDL3 C API(zlib 许可);热插拔差分、按钮映射表(South→A…)、摇杆 Y 反转、扳机 ×255/32767(已确认:双后端一开始都做,与 WPF 一致)
  • 调试界面:src\qml\pages\InputDebugPage.qml——实时显示键盘/鼠标/手柄事件(替换 WPF 的 DebubTest 状态栏)
  • 单元测试:键码映射对照、阈值行为

产出:程序运行时能实时看到全局输入事件流(键盘/鼠标/手柄),验证识别正确性。

阶段 3:输入合成(SendInput + 虚拟手柄,延后项前置部分)

目标:识别之外,把"模拟输出"打通(先键盘/鼠标,虚拟手柄后置)。

  • src\core\input\InputGenerator.h/.cpp:SendInput(KEYBDINPUT 扫描码模式 + MOUSEINPUT + 绝对坐标 65535 归一化),结构体照搬(注意 64 位 sizeof)
  • src\core\input\InputMacro.h/.cpp:渐变动画辅助(MouseMoveAnimation/MouseWheelAnimation/GamepadTriggerAnimation/KeyPress——先实现键鼠部分)
  • 虚拟手柄(ViGEm,MIT):已确认不做(与 HidHide 一起砍掉,保持代码纯净)

产出:能向系统发送模拟键鼠输入(用记事本验证);虚拟手柄留到后期。

阶段 4:主界面(五面板 + 主题控件库)

目标:WPF 主窗口布局完全一致的 QML 实现。

  • src\qml\controls\ 主题控件库:FancyButton / FancyTextBox / ToggleSwitch / FancySlider / FancyComboBox / ScrollViewer / IconButton(PathData→QML Path)——对应 WPF Styles/*.xaml
  • src\qml\Main.qml 完整化:顶部 "Fancy/Input" 标题(80px Cascadia Mono 双色)+ 5 面板按钮(读取/创建/管理/工坊/设置)+ StackLayout 切换 + 底部状态栏(调试圆点 + 文字)
  • src\models\MainWindowViewModel.h/.cpp:PanelType/SelectedPanelIndex/5 按钮状态/配置加载保存(%APPDATA%/FancyInput/config.json,QJsonDocument)
  • 五个占位面板:ReadPage / CreatePage / ManagePage / WorkshopPanel / SettingsPage(先静态布局,业务后续填)
  • 系统托盘 QSystemTrayIcon(退出菜单 + 双击恢复)

产出:主窗口与 WPF 布局逐像素一致,面板可切换,托盘可用。

阶段 5:各个分窗口(元素编辑窗 + 辅助对话框)

目标:10 种元素编辑窗 + 通用对话框,UI 布局与 WPF 一致。

  • src\models\ElementViewModel.h/.cpp:10 种元素模型(QObject + Q_PROPERTY;图片 9 段;触发枚举列表;OffsetAnchor 链)
  • src\qml\elements\:10 个元素编辑窗(KeyBoardButton / MouseButton / MouseWheel / MouseMovement / GamepadButton / GamepadDpad / GamepadTrigger / GamepadPlayerId / AnalogStick / Texture),统一布局:左侧 1-2 个贴图面板 + 右侧配置列(背景/名称/触发/检测按钮)
  • src\qml\controls\ElementViewPanel.qml:贴图面板(加载/编辑/复制/粘贴)
  • src\qml\dialogs\:AboutWindow / QRCodeWindow / RgbInputDialog / UpdatePromptWindow / FullScreenMask / AppMessageBox / ElementTreeHelp / OpenSourceStatement(布局照搬)
  • src\models\ImageEditorViewModel.h/.cpp + ImageEditor.qml:裁切/通道偏移/蒙版(可延后,先放按钮占位)

产出:元素编辑窗可打开、可配置,对话框可用。

阶段 6:Overlay 叠层窗口 + 元素树编辑器(核心业务,最后攻坚)

目标:透明置顶覆盖窗口 + 元素树编辑器,实现"输入驱动叠层动画"的核心闭环。

  • src\qml\overlay\OverlayWindow.qml:FramelessWindowHint + WindowStaysOnTopHint + 透明背景;元素渲染(Repeater + Image 换帧);拖拽/缩放/黄框模式
  • src\models\OverlayWindowViewModel.h/.cpp:位置/尺寸/置顶/隐藏/去黄边逻辑(照搬 WPF)
  • src\models\ElementTreeViewModel.h/.cpp:元素集合(QAbstractListModel)、选择矩形、锁定/隐藏、图集生成(XImage.PackImages)、导出 PNG+JSON/.fip
  • src\core\input\InputHandler.h/.cpp:输入事件 → 元素状态分发(响应者字典/扳机帧索引/DPadShift/鼠标滑动平均——全部照搬)
  • src\qml\editor\ElementTreeWindow.qml:三栏(列表/画布/属性)
  • src\qml\pages\ReadPage.qml 完成:加载 PNG+JSON / .fip;ManagePage.qml 完成:多窗口管理列表
  • FullScreenMask(坐标拾取)

产出:加载 WPF 生成的 PNG+JSON 工程,覆盖窗口显示一致,真实输入驱动元素动画。

阶段 7:宏系统 + 工坊(延后功能,单独成块)

目标:宏录制/编辑/播放,不影响前面任何代码。

  • src\core\macro\:MacroAction 体系(6 动作 + DTO 序列化)、MacroTrack 录制状态机、Macro 触发引擎(Once/Hold/Toggle)、MacroActionPlayer(QProcess 命令动作 + SendInput 键鼠动作)
  • src\qml\macro\MacroWindow.qml:三栏(宏列表 / 轨道+动作 / 属性面板)+ MacroTrack / MacroButton / FormRow / MacroActionPropertyPanel
  • 工坊面板(WorkshopPanel):宏管理入口 + 默认路径
  • .fipm 文件与 WPF 互通验证

阶段 8:锦上添花 + 收尾(GIF / 更新 / 发布)

  • GIF 支持(QML AnimatedImage / QMovie,替代 WpfAnimatedGif)
  • 在线更新检查(QNetworkAccessManager,API 地址照搬)
  • 虚拟手柄完善(ViGEm 若未在前置做)+ 终端页(QProcess cmd/PowerShell)
  • 管理员自提权、单实例、命令行参数(.fip/.fips 打开)
  • windeployqt 发布打包 + LGPL 合规文件(LGPLv3 文本 + Qt 版本声明)
  • UI 逐屏对照验收 + 行为对照验收

五、风险清单与对策

# 风险 等级 对策
1 RawInput + 全局钩子是 Win32 深度集成 高 QAbstractNativeEventFilter 是官方支持路径;键盘/鼠标全部收敛到 nativeEventFilter;保留 RAWMOUSE 按钮字段可去掉全局钩子
2 像素级图像算法(XImage)格式语义差异 高 QImage ARGB32 与 BGRA32 字节序同为 BGRA(小端);用 WPF 样本图做像素级对拍单测;Crop 透明填充必须手工补
3 ViGEm 从反射改 C++ SDK 中 官方 C++ 客户端 MIT 许可,API 一一对应;预留 QLibrary 动态加载兜底
4 SDL3-CS → SDL3 C API 中 同名枚举(SDL_GAMEPAD_BUTTON_SOUTH…),映射表照搬
5 GIF 动画(WpfAnimatedGif → QMovie) 中 QML AnimatedImage 内建 QMovie;静态兜底逻辑保留
6 QtGamepad 模块缺失 中 不用;手柄走 SDL3/XInput C++ 封装,QML 只消费结果
7 宏播放时序(Task 协程 → QTimer 状态机) 中 顺序执行语义用 singleShot 链实现;取消令牌 + 补发 Release 照搬;防竞态锁照搬
8 DPI/多屏(WPF 逻辑像素 vs Qt 高 DPI) 中 Qt 6 自动高 DPI;叠层/遮罩坐标用 devicePixelRatio 归一化
9 图标 PathData(SVG path 字符串) 低 Qt Path 支持 path 子集;少数不兼容的几何用 Canvas 兜底
10 反射式 DTO 序列化 低 改手写字段序列化,字段名/结构保持一致即可互通

六、工作量与里程碑估算

阶段 内容 估算
0 Hello World 工程骨架 0.5 天
1 通用函数与数据结构(枚举/XImage/OverlayParser) 2 天
2 输入识别核心(RawInput/InputParser/XInput) 3 天
3 输入合成(SendInput 键鼠) 1 天
4 主界面五面板 + 主题控件库 3 天
5 分窗口(元素编辑窗 + 对话框) 3 天
6 Overlay 叠层 + 元素树编辑器 4 天
7 宏系统 + 工坊 3 天
8 锦上添花 + 发布 2 天
合计 约 21.5 人日(单人全职约 5 周)

说明:阶段 1/2/3 为"核心功能前置",阶段 4/5/6 为"UI 主界面→分窗口"推进,阶段 7/8 为延后功能,互不污染,每阶段独立可编译调试。

里程碑:

  • M0(阶段 0 完成):Qt/MinGW 工程骨架跑通,QML 窗口显示 → 后续所有开发的基座
  • M1(阶段 2 完成):输入识别核心可用,实时看到全局输入事件流 → 技术风险解除
  • M2(阶段 4 完成):主窗口五面板 + 主题控件库,UI 与 WPF 一致
  • M3(阶段 6 完成):叠层窗口 + 元素树编辑器闭环,.fip/PNG+JSON 与 WPF 互通
  • M4(阶段 8 完成):宏/手柄/GIF/更新全功能对齐,验收通过

七、验收标准

  1. UI 一致:主窗口/叠层/编辑器/宏窗口/辅助窗口逐屏截图对比,布局、配色(#FF651F65 主题)、字体(Cascadia Mono)、控件样式(圆角/阴影/开关/滑杆)一致
  2. 逻辑一致:
    • 输入:同一套按键/手柄输入在两版下产生相同元素帧切换(对拍测试)
    • 图像:同一 PNG 经 Crop/Merge/PackImages 等处理后像素输出一致(对拍测试)
    • 宏:同一 .fipm 在两版下播放行为一致(含触发模式/时序/取消)
    • 配置:config.json / .fip / .fips 双向读写互通
  3. 性能:输入延迟、叠层渲染帧率、宏触发响应不低于 WPF 版

八、待用户确认事项

  1. 工程形式:纯 CMake 工程(已定,用 MinGW Makefiles 生成器)
  2. 构建工具:MinGW 13.1.0(已定)
  3. 手柄后端:SDL3(zlib)+ XInput(系统组件)双后端一开始都保留(已定,阶段 2 实现)
  4. GIF 支持:延后到阶段 8(已定,QML AnimatedImage 原生支持)
  5. 虚拟手柄(ViGEm):已确认不做;HidHide(GPL)同样不迁移
  6. 范围:按上表全功能迁移,宏系统/图像编辑器/在线更新延后(已定)
  7. 代码中已注释掉的逻辑(HidHide、MirrorGamepad、ConsoleWindow 等)是否一并迁移?建议不迁(保持与 WPF 行为一致)