Repository navigation
Conversation
现象
Win10 上右键任务栏的应用按钮,弹的是 ShellExperienceHost 的 XAML 跳转列表,
完全绕过 Shell;任务栏空白处却已被 Shell 接管。两者观感不一致。
根因
任务栏按钮的右键菜单不是 Win32 菜单:弹出时无 #32768 窗口、不经过 TrackPopupMenu(Ex)、
也不经过 IContextMenu,无法用常规 Hook 拦截(实测埋日志:桌面/托盘/任务栏空白处都有日志,
只有按钮菜单一条都没有)。
做法
- subclass 任务栏按钮区窗口链 Shell_TrayWnd → ReBarWindow32 → MSTaskSwWClass → MSTaskListWClass,
拦 WM_CONTEXTMENU;只有点在真正的应用按钮上才吞掉,空白处放行给 explorer。
- 用 UIA ElementFromPoint 识别按钮(ControlType 接受 Button / MenuItem / SplitButton,
多窗口合并按钮是 MenuItem);按钮身份经 shell:AppsFolder 显示名解析。
- 自建 HMENU 后交给 TrackPopupMenuExProc,Shell 会抓快照并按自己的主题重绘 ——
菜单因此自动变成 Shell 样式(方案的关键)。
- 菜单内容:应用名 / 固定或取消固定 / 关闭窗口(仅在有运行窗口时)。
- 自建菜单期间置 in_taskbar_button_menu,ContextMenu 的两处配置项入口跳过 .nss 规则。
改动范围
仅 Win10 分支生效(IsWindows11OrGreater() 为假时才挂 hook);Win11 路径不变。
验证
Win10 22H2 + Shell 1.9.19:按钮菜单已是 Shell 样式;固定/取消固定、关闭窗口(含 UWP)、
多窗口合并按钮均实测通过;任务栏空白处行为不变。
- 按钮菜单的上下文类型改成独立的 FSO_TASKBAR_BUTTON:用 type='taskbar' / window.is_taskbar
写的既有项(例如「转到」)不会再涌进按钮菜单;配置里用 where=window.is_taskbar_button
可以精确命中按钮菜单(任务栏空白处、桌面、文件菜单都是 false)。
- 新增内置命令 command.kill_process_tree:按 Toolhelp 快照展开按钮下**所有**窗口的进程树,
再从叶子往根终止(先杀父进程的话子进程会被系统改挂,杀树会漏)。
- 修好控制台窗口(cmd.exe)认不出来:AppsFolder 里 cmd.exe 的键是
{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\cmd.exe,拿 exe 全路径查显示名必然失败。
增加「按 exe 文件名查 AppsFolder」兜底(整表只建一次 + 缓存)。
cmd 按钮因此恢复:结束进程 / 关闭窗口 / 固定到任务栏。
- hook 重构成「主任务栏 + 每个副屏任务栏(Shell_SecondaryTrayWnd)」,并加长期巡检: 任务栏窗口被就地重建(改 DPI / 插拔显示器 / 切主题)时 subclass 会跟着丢, 原先只重试 15 次就结束,之后按钮菜单会静默退回原生 XAML 跳转列表 —— 现在每 3 秒巡检一次, 发现丢了就重新挂上(只有真的新挂上才写日志)。顺手去掉 4 行 [PROBE] 启动日志。 - 新增 window.taskbar_button_windows(按钮菜单当前匹配到的窗口数),用来做死项治理: 未运行 / 窗口认不出来的按钮没有可操作目标,配置项可用它把「结束进程」这类死项藏掉。
以前应用名项的 id 写 0,点了完全没有反应。现在点击 = - 有运行窗口:切到前台(最小化先 SW_RESTORE;SetForegroundWindow 被前台锁拒绝时 用 AttachThreadInput 接上当前前台线程再试一次); - 没在运行 / 没有可见窗口(例如只留在托盘的 Everything):启动程序, 优先走固定项 .lnk(能带上它原有的工作目录与参数),否则用窗口进程的 exe 路径。
任务管理器这类以管理员运行(高完整性)的窗口,从 explorer(中完整性)切前台时会失败: - SetForegroundWindow 被 Windows 的前台锁拒绝; - 常规兜底 AttachThreadInput 要访问目标线程的输入队列,**跨完整性被 UIPI 挡掉**; → 结果就是「点了完全没反应」(用户实测连点 4 次,日志里 4 行 activate 但窗口没动)。 现在按代价递增依次尝试,并把结果写进日志: 1) IsIconic → ShowWindow(SW_RESTORE)(窗口最小化时这条就够,不碰输入队列) 2) SetForegroundWindow 3) AttachThreadInput + SetForegroundWindow(同完整性时有效) 4) 模拟一次 ALT 键解开前台锁 + SetForegroundWindow(对提权窗口有效) 5) SwitchToThisWindow(hwnd, TRUE)(任务栏点击内部用的老 API) 日志改成 [BTN] activate window=0x.. ok=.. fg=0x..,以后「点了没反应」一眼能看出卡在哪一步。 实测:对提权的任务管理器连续两次 ok=1 且 fg 就是目标窗口。
为便于上游审核,把开发过程中留下的噪声还原为官方状态,行为零变化: - 删除全仓无调用点的 find_app_window() / is_pinned_to_taskbar(), 以及只写不读的 taskbar_window_finder::hwnd 字段与其赋值 - 新增注释统一改写为英文(Main.cpp 188 行、ContextMenu.cpp 11 行、 FuncExpression.cpp 10 行、FileSystemObjects.h 2 行) - 删除 [PROBE] / 无条件 IAT hook 试验等历史诊断注释 6 行 - Main.cpp / FuncExpression.cpp 恢复与官方一致的 UTF-8 BOM,去掉 1 处多余空行 验证:剥掉注释与空行后逐行比对,代码序列仅少上述死代码(added = 0); 编译 EXIT=0(v145 release x64,无 C4505);已部署并实机验证菜单功能正常。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On Windows 10, right-clicking a taskbar app button still pops the ShellExperienceHost XAML jump
list, even though the taskbar background menu is already owned by Shell.
Root cause
The button menu is not a Win32 menu: no
#32768window, noTrackPopupMenu(Ex), noIContextMenu- no ordinary hook can see it. The only entry point is to subclass the taskbar buttonarea window and intercept
WM_CONTEXTMENU.Approach
Shell_TrayWnd->ReBarWindow32->MSTaskSwWClass->MSTaskListWClassand interceptWM_CONTEXTMENU, swallowing it only when the click really lands on an app button; clicks in the gapsare passed through so explorer (and Shell) still shows the regular taskbar menu.
ElementFromPoint.ControlTypemust acceptButton,MenuItemandSplitButton- a merged multi-window button is exposed asMenuItem. Button identity comes from theshell:AppsFolder\<AUMID>display name.HMENUtoTrackPopupMenuExProc: Shell snapshots and re-themes it, so the menulooks native without any custom drawing.
pin / unpin / close window. Closing uses the UIA
WindowPattern::Close()and closes every window ofa merged button (one "Close all windows" entry); parallel fallbacks are used to bring elevated
windows to the front.
taskbarunpinverb on the pinned.lnk-> exe verb -> the officialIStartMenuPinnedListfallback. After a successful pin the shortcut's AppUserModel property set iscompleted so it merges with the running window.
FSO_TASKBAR_BUTTON:window.is_taskbar_buttonis trueonly there and
window.is_taskbarreturns false, so existingtype='taskbar'rules (e.g. "Go to")do not leak in while
.nssitems can target the button menu directly.command.kill_process_treeterminates the process tree of all windows behind the button(leaves-first from a Toolhelp snapshot).
window.taskbar_button_windowsexposes the matched windowcount so configs can hide dead items on buttons whose app is not running.
every 3 seconds, so a rebuilt taskbar window (DPI change, monitor hotplug, theme switch) does not
silently drop back to the native menu.
Scope
Windows 10 only - the hook is installed only when
IsWindows11OrGreater()is false, so theWindows 11 path is untouched. The changes are concentrated in
Main.cpp;ContextMenu.cpponly adds2 entry conditions.
Verification
Win10 22H2 + Shell 1.9.19:
.nsscustomisationwhere=window.is_taskbar_buttonitems show up only in the button menucommand.kill_process_tree[KILL] roots=2 processes=4 killed=4(child processes included)ok=1 fg=0x450166); launches the app when it is not running