PowerToys Mouse Utilities:四款鼠标工具的运行架构、启用链路与调试实战
【免费下载链接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows项目地址: https://gitcode.com/GitHub_Trending/po/PowerToys
Mouse Utilities 是 PowerToys 中集中增强鼠标与光标体验的模块,包含 Find My Mouse、Mouse Highlighter、Mouse Jump、Mouse Pointer Crosshairs 四个子工具。本文基于 Mouse Utilities 开发文档 及其四篇子工具文档,结合src/modules/MouseUtils下的真实源码,讲清该模块“三线程内嵌 + 一独立进程”的混合架构、各工具从设置 JSON 到窗口消息的完整启用链路、跨进程事件通信机制,以及针对每类工具的调试方法,帮助你在修改或排查鼠标工具行为时能快速定位到对应代码。
一、模块总览:四个子工具各解决什么问题
Mouse Utilities 是 Windows 上鼠标/光标功能增强工具集,四个子模块定位互不重叠:
| 子工具 | 功能定位 | 文档 | 核心实现目录 |
|---|---|---|---|
| Find My Mouse | 通过聚光灯(spotlight)特效帮助定位鼠标指针 | findmymouse.md | FindMyMouse |
| Mouse Highlighter | 在鼠标点击瞬间绘制可定制的高亮圈 | mousehighlighter.md | MouseHighlighter |
| Mouse Jump | 通过网格覆盖层快速将光标移动到屏幕任意位置 | mousejump.md | MouseJump |
| Mouse Pointer Crosshairs | 显示跟随光标的水平/垂直十字线 | mousepointer.md | MousePointerCrosshairs |
二、整体架构:两种进程模型并存
开发文档对架构的划分非常明确:Find My Mouse、Mouse Highlighter、Mouse Pointer Crosshairs 三个工具作为独立线程运行在 PowerToys Runner 进程内;Mouse Jump 则作为独立进程运行,通过共享事件与 Runner 通信。这两种模型在源码中都有直接对应。
2.1 内嵌线程模型
三个"轻量"工具都以 PowerToy 模块 DLL 形式被 Runner 加载,启用时各自detach()一个后台线程运行消息循环。例如 Mouse Pointer Crosshairs:
std::thread([=]() { InclusiveCrosshairsMain(hInstance, settings); }).detach();这种方式的优点是随 Runner 统一启停、无额外进程开销;代价是调试时需要直接 attach 到 Runner 进程(见第七节)。
2.2 Mouse Jump 的独立进程模型与共享事件
Mouse Jump 之所以单独成进程,是因为它需要全屏 WinUI3 覆盖层窗口来承载网格 UI。Runner 侧的模块实现位于 MouseJump/dllmain.cpp,构造时创建两个命名事件:
m_hInvokeEvent = CreateDefaultEvent(CommonSharedConstants::MOUSE_JUMP_SHOW_PREVIEW_EVENT); m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::TERMINATE_MOUSE_JUMP_SHARED_EVENT);这两个事件名是跨进程通信的“契约”,定义在 shared_constants.h 中,带有全局唯一的 GUID,避免与其他应用的事件重名:
const wchar_t MOUSE_JUMP_SHOW_PREVIEW_EVENT[] = L"Local\\MouseJumpEvent-aa0be051-3396-4976-b7ba-1a9cc7d236a5"; const wchar_t TERMINATE_MOUSE_JUMP_SHARED_EVENT[] = L"Local\\TerminateMouseJumpEvent-252fa337-317f-4c37-a61f-99464c3f9728";事件语义为:MOUSE_JUMP_SHOW_PREVIEW_EVENT由 Runner 置位以“通知 UI 进程显示覆盖层”,TERMINATE_MOUSE_JUMP_SHARED_EVENT由 Runner 置位以“通知 UI 进程清理并退出”。
从当前仓库源码结构看,Mouse Jump 的 UI 进程已从文档早期记载的PowerToys.MouseJumpUI.exe(WinForms,MainForm.cs)迁移为 WinUI3 应用:dllmain.cpp 中 launch_process() 实际启动的是WinUI3Apps\PowerToys.MouseJump.WinUI3.exe,对应 MouseJump.WinUI3 目录下的 PreviewWindow.xaml 覆盖层窗口。启动时仍以命令行参数传入 Runner 的 PID(GetCurrentProcessId()转宽字符串),UI 进程据此反查 Runner,这也是“直接调试 MouseJumpUI 很困难”的根因。
启动、热键与退出链路如下:
enable()时预热启动 UI 进程,保证首次热键按下时窗口已就绪(dllmain.cpp#L259-L269);- 按下激活快捷键时
on_hotkey()被 Runner 调用:若 UI 进程已死则重新拉起,再用EnumWindows+ PID 找到 UI 窗口并SetForegroundWindow(),最后SetEvent(m_hInvokeEvent)通知覆盖层显示(dllmain.cpp#L287-L330);源码注释特别说明,此时 Runner 的热键钩子线程拥有“最后输入”状态,因此SetForegroundWindow能可靠成功; - 用户在覆盖层上选定目标点,UI 进程将光标移动到该位置;
disable()或 PowerToys 退出时,Runner 置位TERMINATE_MOUSE_JUMP_SHARED_EVENT,等待进程自行退出(等待WaitForSingleObject),超时后TerminateProcess()强制结束(dllmain.cpp#L272-L285)。
Mouse Jump 的默认激活快捷键为Win+Shift+D:当设置 JSON 中没有有效的activation_shortcut时,parse_hotkey() 会回退到该默认组合。
三、代码结构地图:设置 UI 与模块实现
开发文档给出了 Mouse Utilities 的代码布局,按“设置 UI”与“Runner 侧模块实现”两层组织(以下路径均以仓库根目录为基准,并已对照当前仓库核实):
设置 UI(Settings.UI)
- MouseUtilsPage.xaml —— 鼠标工具设置页入口
- MouseJumpPanel.xaml / MouseJumpPanel.xaml.cs —— Mouse Jump 的快捷键录制面板
- MouseUtilsViewModel.cs / MouseUtilsViewModel_MouseJump.cs —— 设置页数据绑定
Runner 侧模块实现(src/modules/MouseUtils)
- FindMyMouse:
FindMyMouse.cpp+ 键盘钩子事件定义WinHookEventIDs.cpp - MouseHighlighter:
MouseHighlighter.cpp - MousePointerCrosshairs:
InclusiveCrosshairs.cpp - MouseJump:Runner 侧接口(上述
dllmain.cpp) - MouseJump.Common:Runner 侧与 UI 侧共享的 C# 代码,含 DPI/绘制/屏幕布局等 Helpers 与成像服务
- 从当前源码结构看,模块目录还包含 MouseJump.Models(视图模型)、MouseJump.HotKeys(按键模型)及各自的 UnitTests 工程;此外还有 CursorWrap 的 C++ 源码与测试目录,属于文档未列出的新增组件,本文不展开
四、Find My Mouse:聚光灯定位光标
Find My Mouse 基于 Raymond Chen 的 SuperSonar 工具思路:通过键盘快捷键(典型用法是双击 Ctrl)触发时,在光标位置显示一个聚光灯/涟漪动画。
4.1 启用链路
- 模块启用时创建后台线程异步运行主逻辑:
virtual void enable() { m_enabled = true; Trace::EnableFindMyMouse(true); std::thread([=]() { FindMyMouseMain(m_hModule, m_findMyMouseSettings); }).detach(); }CompositionSpotlight实例用用户设置初始化,失败则记录错误并返回:
CompositionSpotlight sonar; sonar.ApplySettings(settings, false); if (!sonar.Initialize(hinst)) { Logger::error("Couldn't initialize a sonar instance."); return 0; } m_sonar = &sonar;- 工具通过
WM_INPUT原始输入事件监听,比标准鼠标事件更精确、响应更快。
4.2 激活流程:从钩子到动画
激活过程是一条“键盘钩子 → 自定义窗口消息 → 消息处理器 → 动画”的调用链:
- 键盘钩子检测快捷键:初始化时注册全局低级键盘钩子,匹配双击 Ctrl 等模式后,向 sonar 窗口发送
WM_PRIV_SHORTCUT:
virtual void OnHotkeyEx() override { Logger::trace("OnHotkeyEx()"); HWND hwnd = GetSonarHwnd(); if (hwnd != nullptr) PostMessageW(hwnd, WM_PRIV_SHORTCUT, NULL, NULL); }- 消息处理器切换状态:
WM_PRIV_SHORTCUT经BaseWndProc()路由后在开/关动画间切换:
if (message == WM_PRIV_SHORTCUT) { if (m_sonarStart == NoSonar) StartSonar(); // 触发 sonar 动画 else StopSonar(); // 已在运行则取消 }- 聚光灯动画:
StartSonar()借助CompositionSpotlight在鼠标指针中心绘制涟漪,动画会自动淡出,也可被用户输入打断。
4.3 可配置项
从 FindMyMouse/dllmain.cpp 读取的设置键可以看出该工具的配置面:
const wchar_t JSON_KEY_ACTIVATION_METHOD[] = L"activation_method"; const wchar_t JSON_KEY_INCLUDE_WIN_KEY[] = L"include_win_key"; const wchar_t JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE[] = L"do_not_activate_on_game_mode"; const wchar_t JSON_KEY_BACKGROUND_COLOR[] = L"background_color"; const wchar_t JSON_KEY_SPOTLIGHT_COLOR[] = L"spotlight_color"; const wchar_t JSON_KEY_OVERLAY_OPACITY[] = L"overlay_opacity"; // legacy only (migrated into color alpha) const wchar_t JSON_KEY_SPOTLIGHT_RADIUS[] = L"spotlight_radius"; const wchar_t JSON_KEY_ANIMATION_DURATION_MS[] = L"animation_duration_ms"; const wchar_t JSON_KEY_SPOTLIGHT_INITIAL_ZOOM[] = L"spotlight_initial_zoom"; const wchar_t JSON_KEY_EXCLUDED_APPS[] = L"excluded_apps"; const wchar_t JSON_KEY_SHAKING_MINIMUM_DISTANCE[] = L"shaking_minimum_distance"; const wchar_t JSON_KEY_SHAKING_INTERVAL_MS[] = L"shaking_interval_ms"; const wchar_t JSON_KEY_SHAKING_FACTOR[] = L"shaking_factor"; const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";除了快捷键(activation_method/activation_shortcut/include_win_key)与游戏模式豁免,还有“甩动(shaking)”触发的三个参数(最小距离、间隔、方向变化因子)以及聚光灯颜色、半径、动画时长、初始缩放等视觉参数;overlay_opacity被标注为遗留项,其功能已并入颜色 alpha 通道。
事件处理方面:鼠标事件可触发 sonar 动画(如甩动或快捷键后),键盘事件可取消/切换效果,主窗口收到WM_DESTROY(关闭或禁用时)会清理 sonar 实例并优雅结束消息循环。
五、Mouse Highlighter:点击高亮
Mouse Highlighter 运行在 Runner 进程内,用 Windows Composition API 渲染,点击时在光标周围绘制圈形指示。
5.1 启用链路
- 后台线程异步启动:
std::thread([=]() { MouseHighlighterMain(m_hModule, m_highlightSettings); }).detach();- 单例
Highlighter实例化并应用设置、注册窗口类:
Highlighter highlighter; Highlighter::instance = &highlighter; highlighter.ApplySettings(settings); highlighter.MyRegisterClass(hInstance);- 创建透明高亮窗口:
instance->CreateHighlighter();窗口在WM_CREATE中初始化 Composition API(Compositor、visuals、target)。
5.2 激活流程:WM_SWITCH_ACTIVATION_MODE 双态切换
快捷键按下后,一条自定义消息(WM_SWITCH_ACTIVATION_MODE)被投递到高亮窗口,MouseHighlighter.cpp 的WndProc据此在开/关绘制间切换(源码中该消息出现 3 处):
case WM_SWITCH_ACTIVATION_MODE: if (instance->m_visible) instance->StopDrawing(); else instance->StartDrawing();- 开启(
StartDrawing()):将窗口置为最前、略微调整窗口尺寸以规避透明渲染 bug、显示透明绘制窗口、挂接全局鼠标钩子并开始绘制; - 关闭(
StopDrawing()):隐藏窗口、移除鼠标钩子、停止渲染。
高亮生效后的绘制过程:低级鼠标钩子捕获按钮事件 → 在光标位置绘制圆(或配置的其他视觉样式)→ 按用户设置随时间淡出 → 不同鼠标按键可配置不同颜色。
已知问题:开发文档记录了“透明度调为 0 后高亮颜色仍然滞留”的缺陷,且该问题存在已久、在较新发布版中仍可复现(见 mousehighlighter.md)。
六、Mouse Pointer Crosshairs:十字线跟随光标
十字线工具与高亮器同属 Runner 内嵌线程模型,核心实现是 InclusiveCrosshairs.cpp。
6.1 启用链路
std::thread([=]() { InclusiveCrosshairsMain(hInstance, settings); }).detach();InclusiveCrosshairs crosshairs; InclusiveCrosshairs::instance = &crosshairs; crosshairs.ApplySettings(settings, false); crosshairs.MyRegisterClass(hInstance);它通过CreateInclusiveCrosshairs()使用 Composition API 创建十字线视觉对象,在WM_CREATE中初始化 Compositor,并创建带WS_EX_LAYERED、WS_EX_TRANSPARENT等扩展样式的透明分层窗口用于绘制。
6.2 激活流程与 StartDrawing 全貌
同样由WndProc处理WM_SWITCH_ACTIVATION_MODE,在m_drawing状态下切换StartDrawing()/StopDrawing()。mousepointer.md 给出的StartDrawing()实现值得完整理解——它同时处理了自动隐藏游标的状态判断:
void InclusiveCrosshairs::StartDrawing() { Logger::info("Start drawing crosshairs."); UpdateCrosshairsPosition(); m_hiddenCursor = false; if (m_crosshairs_auto_hide) { CURSORINFO cursorInfo{}; cursorInfo.cbSize = sizeof(cursorInfo); if (GetCursorInfo(&cursorInfo)) { m_hiddenCursor = !(cursorInfo.flags & CURSOR_SHOWING); } SetAutoHideTimer(); } if (!m_hiddenCursor) { ShowWindow(m_hwnd, SW_SHOWNOACTIVATE); } m_drawing = true; m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, m_hinstance, 0); }要点:先更新十字线位置;若启用了自动隐藏,用GetCursorInfo判断当前系统光标是否已被其他程序隐藏(隐藏时不额外显示窗口),并设置自动隐藏定时器;光标可见则以SW_SHOWNOACTIVATE显示窗口(不抢焦点);最后挂接WH_MOUSE_LL低级鼠标钩子异步跟踪移动。StopDrawing()则移除钩子、销毁定时器、隐藏窗口并记录日志。
激活期间,钩子在每次WM_MOUSEMOVE上实时更新十字线位置;光标无操作达到设定时长后支持自动隐藏。
七、调试实战:按工具分头处理
开发文档的调试章节给出了针对性建议,核心原则是:内嵌三件套直接 attach Runner 进程,Mouse Jump 需要双进程 attach。
Find My Mouse / Mouse Highlighter / Mouse Pointer Crosshairs
- 直接 attach 到 PowerToys Runner 进程调试;
- 在对应源文件设断点:
FindMyMouse.cpp、MouseHighlighter.cpp、InclusiveCrosshairs.cpp; - 用激活快捷键(如 Find My Mouse 的双击 Ctrl)触发目标代码;
- 注意:调试器开销会导致视觉特效出现卡顿/异常表现,属预期现象。十字线工具尤其如此——
MouseHookProc在每次WM_MOUSEMOVE都更新位置,断点叠加高频更新会造成明显卡顿。
Mouse Jump
- 先调试 Runner 进程;
- UI 进程启动后,再把调试器 attach 到 MouseJumpUI/WinUI3 进程;
- 直接独立调试 UI 进程较困难:它启动时必须携带 Runner 的 PID 作为参数(见 dllmain.cpp 的 launch_process())。
八、UI 测试自动化迁移
Mouse Utilities 正在进行 UI 测试迁移以提升自动化测试覆盖。当前仓库中 MouseUtils.UITests 工程已包含四个子工具各自的测试类(FindMyMouseTests.cs、MouseHighlighterTests.cs、MouseJumpTests.cs、MousePointerCrosshairsTests.cs)与配置数据模型(util/下的各*Settings.cs),迁移进度清单见 Release-Test-Checklist-Migration-Progress.md。
九、社区渊源
- Michael Clayton(@mikeclayton):贡献了 Mouse Jump 的初始版本及基于其 FancyMouse 工具的多次更新;
- Raymond Chen(@oldnewthing):Find My Mouse 的思路源于他的 SuperSonar 工具。
十、小结:一张表看懂修改入口
| 想改什么 | 去哪里 |
|---|---|
| 双击 Ctrl 等复杂快捷键检测 | FindMyMouse(WinHookEventIDs.cpp定义事件) |
| 点击高亮的形状/颜色/淡出 | MouseHighlighter.cpp |
| 十字线位置更新与自动隐藏 | InclusiveCrosshairs.cpp 的StartDrawing/MouseHookProc |
| Mouse Jump 跨进程事件名 | shared_constants.h |
| Mouse Jump 进程拉起/前台切换 | MouseJump/dllmain.cpp 的launch_process()/on_hotkey() |
| 覆盖层网格 UI | MouseJump.WinUI3 的PreviewWindow.xaml |
| 设置页交互 | MouseUtilsPage.xaml 与两个 ViewModel |
理解这套架构的关键在于把握两条主线:一是“模块 DLL 被 Runner 加载 → 后台线程 + 透明窗口 + 钩子”的内嵌模型,二是“共享命名事件 + 独立 UI 进程”的跨进程模型。沿着WM_PRIV_SHORTCUT、WM_SWITCH_ACTIVATION_MODE这些自定义消息和Local\MouseJumpEvent-*事件名两个“契约”入手,就能快速还原任意一个子工具的完整调用链。
【免费下载链接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows项目地址: https://gitcode.com/GitHub_Trending/po/PowerToys
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考