From 947918f2902424694c00fce5d5e956fbbefcc345 Mon Sep 17 00:00:00 2001 From: v4806 Date: Sun, 11 Oct 2026 21:55:00 +0800 Subject: [PATCH] =?UTF-8?q?fix(context-menu):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E3=80=8C=E5=8F=91=E9=80=81=E5=88=B0=E3=80=8D=E7=AD=89=E6=83=B0?= =?UTF-8?q?=E6=80=A7=E5=A1=AB=E5=85=85=E7=9A=84=E7=B3=BB=E7=BB=9F=E5=AD=90?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E4=B8=A2=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现象 Win10 上 Shell 菜单里的「发送到」子菜单只显示 3~5 项,且每次内容都不一样; 原生菜单稳定有 9 项。丢的是「压缩(zipped)文件夹」「桌面快捷方式」「邮件收件人」 「文档」以及驱动器项 —— 即所有非 .lnk 的 shell 扩展项。 根因 Shell 在 Initialize()(IContextMenu::QueryContextMenu 阶段)就把原始菜单抓成快照 (build_system_menuitems),但 shell32 是惰性填充的:那一刻「发送到」子菜单里 只有几个 .lnk 项,其余 shell 扩展项和驱动器项还没加进去,快照因此残缺。 而该子菜单真正弹出(WM_INITMENUPOPUP 指向它)时,原始句柄已经补齐, 但 OnInitMenuPopup 只重放旧快照,于是永远丢项。 修法 - menuitem_t 记录抓快照时对应的原始子菜单句柄 source_handle; menu_t 记录它所对应的 menuitem_t(std_owner)。 - OnInitMenuPopup 中,若 GetMenuItemCount(source_handle) > 快照项数, 就清空并用 build_system_menuitems() 对原始句柄重抓 —— 仍然走 Shell 自己的 menuitem_t 路径。不直接拷贝原始 HMENU 里的项:那些是 explorer 的 owner-draw 项 (dwItemData 指向 explorer 的数据),直接插入会画不出来。 - 主菜单弹出时对整树刷新一次,作为兜底。 验证 Win10 22H2 + Shell 1.9.19,桌面 .lnk 右键 → 更多选项 → 发送到: 修复前 3 项,修复后稳定 9 项(连续 4 轮,含 explorer 重启)。 运行时日志: [FIX] refetch '发送到(&N)' snapshot=3 now=9 未触发重抓时行为与改动前完全一致(纯新增代码 + 一处条件调用)。 --- src/dll/src/ContextMenu.cpp | 46 +++++++++++++++++++++++++++++++ src/dll/src/Include/ContextMenu.h | 4 +++ 2 files changed, 50 insertions(+) diff --git a/src/dll/src/ContextMenu.cpp b/src/dll/src/ContextMenu.cpp index e7239da0..9e228ec8 100644 --- a/src/dll/src/ContextMenu.cpp +++ b/src/dll/src/ContextMenu.cpp @@ -995,6 +995,7 @@ namespace Nilesoft m_sub->owner = mii; m_sub->std_items = &item->items; + m_sub->std_owner = item; string path; if(!mii->path.empty()) @@ -1096,12 +1097,14 @@ namespace Nilesoft MenuItemInfo *owner = nullptr; if(menu->is_main) { + refresh_system_submenus(__system_menu_tree); menu->dynamics = _cache->dynamic.items; menu->std_items = &__system_menu_tree->items; } else { owner = menu->owner; + refresh_system_submenu_item(menu->std_owner); } prepare_system_items(_system_items, menu); @@ -4256,6 +4259,7 @@ namespace Nilesoft else { item->type = mii.hSubMenu != nullptr; + item->source_handle = mii.hSubMenu; item->disabled = mii.fState & MFS_DISABLED; item->checked = mii.fState & MFS_CHECKED; item->radio_check = mii.fType & MFT_RADIOCHECK; @@ -4336,6 +4340,48 @@ namespace Nilesoft } } + // shell32 fills some submenus (e.g. "SendTo") lazily, after QueryContextMenu. + // Our snapshot taken at Initialize() time can therefore be incomplete. + // Re-snapshot any submenu whose native source menu already has more items. + void ContextMenu::refresh_system_submenu_item(menuitem_t *item) + { + if(!item || !item->is_menu()) + return; + + if(!item->source_handle || !::IsMenu(item->source_handle)) + return; + + const auto source_count = ::GetMenuItemCount(item->source_handle); + const auto snapshot_count = static_cast(item->items.size()); + + if(source_count <= snapshot_count) + return; + + for(auto child : item->items) + delete child; + item->items.clear(); + + build_system_menuitems(item->source_handle, item, false); + + _log.info(L"[FIX] refetch '%s' snapshot=%d now=%d", + item->title.c_str(), snapshot_count, static_cast(item->items.size())); + } + + void ContextMenu::refresh_system_submenus(menuitem_t *menu) + { + if(!menu) + return; + + for(auto item : menu->items) + { + if(!item || !item->is_menu()) + continue; + + refresh_system_submenu_item(item); + refresh_system_submenus(item); + } + } + bool ContextMenu::Initialize() { try diff --git a/src/dll/src/Include/ContextMenu.h b/src/dll/src/Include/ContextMenu.h index 3a3743b0..32f19abf 100644 --- a/src/dll/src/Include/ContextMenu.h +++ b/src/dll/src/Include/ContextMenu.h @@ -92,6 +92,7 @@ namespace Nilesoft int checked = 0; ULONG_PTR dwItemData = 0; HBITMAP image = nullptr; + HMENU source_handle = nullptr; menuitem_t *parent = nullptr; string path; Position position = Position::Auto; @@ -359,6 +360,7 @@ plutovg_move_to(pluto, start.x, start.y); std::vector dynamics; std::vector *std_items = nullptr; + menuitem_t *std_owner = nullptr; NativeMenu *parent{}; //bool dynamic{}; @@ -734,6 +736,8 @@ plutovg_move_to(pluto, start.x, start.y); int Uninitialize(); int InvokeCommand(int id); void build_system_menuitems(HMENU hMenu, menuitem_t *menu, bool is_root = false); + void refresh_system_submenus(menuitem_t *menu); + void refresh_system_submenu_item(menuitem_t *item); void build_main_system_menuitems(menuitem_t *menu, bool is_root = false);