Skip to content

feat(gesture): 鼠标呼出轮盘时把前台焦点切到呼出点所在窗口 - #178

Closed
Chino456 wants to merge 1 commit into
Star-Pie:mainfrom
Chino456:fix/wheel-focus-switch-to-trigger-point
Closed

Chino456 wants to merge 1 commit into
Star-Pie:mainfrom
Chino456:fix/wheel-focus-switch-to-trigger-point

Conversation

@Chino456

@Chino456 Chino456 commented Oct 1, 2026

Copy link
Copy Markdown

需求描述

我在两个显示器跨显示器使用,如果鼠标焦点在显示器 1,我想在显示器 2 使用轮盘,但是焦点并不会自动切换到显示器 2,会对显示器 1 误操作,请求添加功能,能让轮盘让焦点自动切换到呼出轮盘的界面。

问题根因

轮盘触发键(右键 / 中键 / 侧键)的按下与抬起都被 StarPie 自己的低级鼠标钩子 MouseHook 吞掉了,被点中的那个窗口收不到任何鼠标消息,Windows 自然不会把它切到前台 —— 前台仍留在按下之前那个窗口上。

单屏上表现为「右键点在背景窗口上,动作却进了原来那个前台窗口」;多显示器上最明显:光标在显示器 2、轮盘也画在显示器 2,但松手后执行的动作(SendInput 注入的快捷键、窗口操作等)全部落进显示器 1 上原来那个前台窗口,形成误操作。

修复方案

① 轮盘上屏之前把前台焦点对齐到呼出点所在的窗口

新增 WinPieGestures/WheelFocusSwitcher.cs,由 GestureController.ShowRadialUI 在轮盘真正 Present 之前调用一次 SwitchFocusToPoint(呼出点)。

必须放在 Present 之前:轮盘一旦呈现,用户随时可能松手执行动作,而动作作用的对象就是此刻的前台窗口。

激活复用既有的 WindowTaskbarHelper.ActivateWindow(前台锁的 AttachThreadInput / Alt 兜底都在那里)。本类只负责「解析出该切给谁」:不注入点击、不改窗口样式、不移动窗口;目标已经是前台时直接返回,不做任何多余的置顶 / 还原动作。

② 只服务「鼠标呼出轮盘」这一条路径

ShowRadialUI 新增 switchFocusToWheelPoint 参数,只有鼠标触发传 true:

  • 拖动阈值路径看 _kbTriggerWaiting;
  • 长按路径看 isMouseWaiting。

键盘触发(穿透模式)与插件经 IHostWheelService 呼出的粘滞轮盘一律不动前台 —— 前者用户正在某个窗口里打字,后者连触发都不在钩子上,抢前台都是错的。

③ 轮盘方案(扇区选项)必须一起跟过去

方案是按进程名取的(ConfigManager.GetProfileForProcess),而进程名过去一律取 ActiveWindowHelper.GetActiveWindowProcessName()(前台窗口)。跨显示器呼出时前台还在另一块屏幕上,于是第一次呼出拿到的是另一个程序的扇区配置 —— 前台虽然被切过去了,用户却得再呼出一次才看到该有的选项。

所以鼠标路径改用 WheelFocusSwitcher.TryGetProcessNameAtPoint(呼出点):

  • 解析必须在钩子线程上做、激活必须留到 UI 线程:ProcessMove(初始位置) 在 BeginInvoke 之前就按 _activeProfile 算好了初始扇区,松手执行的动作也读这一份。方案若等到 UI 线程才换,初判扇区会按另一个方案的扇区数算(12 扇区方案算出 10,套到 8 扇区方案上),用户松手可能落到错的扇区或空扇区。
  • TryGetProcessNameAtPoint 刻意只读(WindowFromPoint / GetAncestor / GetWindowRect / GetWindowLongPtr),前台切换仍留在 ShowRadialUI(UI 线程)里。
  • 键盘路径与解析失败时回落到前台窗口,行为不变。

④ 解析判据(保守,宁可不切也不切错)

先 WindowFromPoint + GetAncestor(GA_ROOT);命中的是自己人的窗口(轮盘 HWND 隐藏后仍占着原位置、轨迹浮层)或 no-activate / 点击穿透的悬浮层时,沿 z 序自上而下兜底找真正压在下面的窗口。

兜底只对上面这两类成立:命中的是 Shell 表面(桌面、任务栏)时绝不下钻 —— 桌面铺满整块屏幕、任务栏上方还压着最大化的程序,往下找到的第一个「矩形盖住该点」的窗口就是那个无关程序,在桌面 / 任务栏空白处呼出轮盘会把用户当前软件抢走;此时宁可不动前台。

被排除的目标:StarPie 自身进程的窗口、WS_EX_NOACTIVATE / WS_EX_TRANSPARENT 窗口、最小化 / 不可见窗口、桌面与任务栏等 Shell 表面。

另有一处兜底:进程名取不出来(顶层的 UWP 进程经常 OpenProcess 不到)时返回 null,同样回落到前台窗口的原行为,避免把用户已配好的方案换成默认方案。

变更文件

文件 说明
WinPieGestures/WheelFocusSwitcher.cs 新增,前台焦点解析与对齐(约 335 行)
WinPieGestures/ActiveWindowHelper.cs 新增 GetProcessNameForWindow(窗口句柄);抽出共用 ResolveProcessName
WinPieGestures/GestureController.cs ShowRadialUI 增加焦点切换参数;两条鼠标路径改用呼出点进程名
WinPieGestures/WinPieGestures.csproj 为探针开放 InternalsVisibleTo
scratch/test_wheel_focus.cs / .csproj / .ps1 新增端到端探针
scratch/Directory.Build.props 探针工程输出隔离(沿用既有约定)
AGENTS.md / CHANGELOG.md 架构规范 §3.7 与变更日志同步

验证

  • dotnet build WinPieGestures/WinPieGestures.csproj -c Release → 0 警告 0 错误;
  • 新增 scratch/test_wheel_focus.ps1 端到端探针(真摆几层窗口)→ 17 项断言全部通过,连续 3 次运行稳定,EXIT=0,覆盖:
    • 呼出点解析与前台切换、前台已是目标时的幂等性;
    • StarPie 自身进程窗口压在目标上时不切给自己、沿 z 序兜底到目标;
    • 其它进程的 WS_EX_NOACTIVATE 置顶悬浮层被跳过、前台不会被切给它;
    • 普通置顶窗口应正常切换;
    • 拉真记事本断言**「进程名取自呼出点而非前台窗口」**;
    • Shell 表面(任务栏 / 桌面)不会被当作焦点目标,且不做 z 序下钻。
  • 本机 --diag 模式实测复现了「自己那层压在最上面」的关键处境(解析结果为 0x0 而非误取自身)。

影响范围与兼容性

  • 只影响鼠标触发轮盘这一条路径;键盘触发轮盘、插件粘滞轮盘的前台语义与方案来源均保持原样。
  • 无 API 破坏性变更:WheelFocusSwitcher 为 internal,ShowRadialUI 为私有方法;ActiveWindowHelper 只新增方法,既有方法签名不变。
  • 无新增依赖、无 NuGet 包、无网络请求。

已知边界

  • 轮盘呼出后若用户中途放弃(鼠标甩出取消),焦点已切到呼出点窗口且不回滚 —— 这是本特性的直接后果。
  • ActivateWindow 的 topmost 兜底仅在 SetForegroundWindow 失败时触发,白名单放行的独占全屏应用理论上可能被这条路径打扰一次。

- 新增 WheelFocusSwitcher:轮盘上屏前把前台焦点对齐到呼出点所在的顶层窗口,
  复用 WindowTaskbarHelper.ActivateWindow 处理 Windows 前台锁,不注入点击、不改窗口样式、不移动窗口
- 只服务「鼠标呼出轮盘」这一条路径:键盘触发与插件经 IHostWheelService 呼出的粘滞轮盘一律不动前台
- 轮盘方案同步按呼出点所在窗口的进程名取,避免跨显示器时第一次呼出拿到另一块屏幕上那个程序的方案
- 进程名解析刻意只读(钩子线程不做激活),前台切换留在 UI 线程的 ShowRadialUI 里
- 判据保守:StarPie 自身进程窗口、WS_EX_NOACTIVATE / WS_EX_TRANSPARENT 悬浮层、最小化 / 不可见窗口、
  桌面与任务栏等 Shell 表面都不是切换目标;命中 Shell 表面时不做 z 序下钻,宁可不动前台
- ActiveWindowHelper 增加 GetProcessNameForWindow(按任意窗口句柄取进程名),
  缓存字段 _cachedForegroundHwnd 改名为 _cachedWindow 以反映其实际语义
- 新增 scratch/test_wheel_focus 端到端探针(17 项断言,含前置条件断言与 Shell 表面回归断言)
@SoftBlack42
SoftBlack42 self-requested a review October 1, 2026 10:51
@SoftBlack42 SoftBlack42 self-assigned this Oct 1, 2026
@SoftBlack42 SoftBlack42 added the enhancement New feature or request label Oct 1, 2026
@SoftBlack42 SoftBlack42 closed this Oct 4, 2026
@Chino456
Chino456 deleted the fix/wheel-focus-switch-to-trigger-point branch October 4, 2026 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants