// SPDX-License-Identifier: LGPL-2.1-or-later #include "menu_bridge.h" #include "edit_hook.h" #include "notification_hook.h" static void menu_text(WCHAR *output, UINT capacity, const WCHAR *input) { UINT used = 0; while (*input && *input != '\t' && used + 1 < capacity) { if (*input == '&') { ++input; if (*input != '&') continue; } output[used++] = *input++; } output[used] = 0; } // This runs in the application's process: Wine does not expose another // process's HMENU contents to the standalone reader. __declspec(dllexport) LRESULT CALLBACK WineAccessMenuHook(int code, WPARAM wp, LPARAM lp) { if (code >= 0) { wine_access_watch_notifications(); const CWPSTRUCT *message = (const CWPSTRUCT *)lp; if (message->message == WM_SETFOCUS) wine_access_watch_edit(message->hwnd); if (message->message == WM_MENUSELECT || message->message == WM_EXITMENULOOP) { HWND bridge = FindWindowW(WINE_ACCESS_MENU_CLASS, NULL); HWND owner = GetAncestor(message->hwnd, GA_ROOT); BOOL closed = message->message == WM_EXITMENULOOP || (!message->lParam && HIWORD(message->wParam) == 0xffff); if (bridge && (closed || owner == GetAncestor(GetForegroundWindow(), GA_ROOT))) { WineAccessMenuEvent event = {0}; UINT flags = HIWORD(message->wParam); HMENU menu = (HMENU)message->lParam; event.owner = (ULONGLONG)(UINT_PTR)owner; event.menu = (ULONGLONG)(UINT_PTR)menu; event.item = LOWORD(message->wParam); if (closed) { event.flags = WINE_ACCESS_MENU_CLOSED; } else { WCHAR text[640] = {0}; MENUITEMINFOW info = {0}; info.cbSize = sizeof(info); info.fMask = MIIM_STRING | MIIM_STATE | MIIM_FTYPE | MIIM_SUBMENU; info.dwTypeData = text; info.cch = sizeof(text) / sizeof(text[0]); if (!GetMenuItemInfoW(menu, event.item, (flags & MF_POPUP) != 0, &info) || (info.fType & MFT_SEPARATOR)) return CallNextHookEx(NULL, code, wp, lp); menu_text(event.name, 512, text); for (UINT i = 0; text[i]; ++i) { if (text[i] == '\t') { lstrcpynW(event.shortcut, text + i + 1, 128); break; } } if (!event.name[0]) return CallNextHookEx(NULL, code, wp, lp); if (info.hSubMenu) event.flags |= WINE_ACCESS_MENU_SUBMENU; if (info.fState & MFS_CHECKED) event.flags |= WINE_ACCESS_MENU_CHECKED; if (info.fState & MFS_DISABLED) event.flags |= WINE_ACCESS_MENU_DISABLED; GetWindowTextW(owner, event.title, 256); } COPYDATASTRUCT data = {WINE_ACCESS_MENU_TAG, sizeof(event), &event}; DWORD_PTR result; // The bridge only queues the payload; speech never runs in this hook. SendMessageTimeoutW(bridge, WM_COPYDATA, (WPARAM)message->hwnd, (LPARAM)&data, SMTO_ABORTIFHUNG | SMTO_BLOCK, 50, &result); } } } return CallNextHookEx(NULL, code, wp, lp); }