// SPDX-License-Identifier: LGPL-2.1-or-later #include "menu_bridge.h" #include "bootstrapper_hook.h" #include #include static DWORD foreground_thread32(void) { DWORD pid = 0; DWORD thread = GetWindowThreadProcessId(GetForegroundWindow(), &pid); HANDLE process = pid ? OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid) : NULL; BOOL wow64 = FALSE; BOOL known = process && IsWow64Process(process, &wow64); if (process) CloseHandle(process); return known && wow64 ? thread : 0; } // Hook DLLs must match the application's architecture. This small PE host // supplies 32-bit menu/editor hooks without requiring 32-bit Linux GLib/Winelib. int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show) { (void)instance; (void)previous; (void)show; char *end; DWORD parentId = strtoul(args, &end, 10); HWND bridge = (HWND)(UINT_PTR)strtoul(end, &end, 10); DWORD bridgeProcess = 0; GetWindowThreadProcessId(bridge, &bridgeProcess); if (!parentId || bridgeProcess != parentId) return 1; HANDLE parent = OpenProcess(SYNCHRONIZE, FALSE, parentId); if (!parent) return 1; WCHAR path[4096] = {0}; DWORD length = GetModuleFileNameW(NULL, path, 4096); WCHAR *name = NULL; for (DWORD i = 0; i < length && i < 4096; ++i) if (path[i] == L'\\' || path[i] == L'/') name = path + i + 1; if (!length || length >= 4096 || !name || name - path + lstrlenW(L"wine-access-menu32.dll") >= 4096) { CloseHandle(parent); return 1; } lstrcpyW(name, L"wine-access-menu32.dll"); HMODULE module = LoadLibraryW(path); HOOKPROC callback = module ? (HOOKPROC)GetProcAddress(module, "WineAccessMenuHook") : NULL; HOOKPROC inputCallback = module ? (HOOKPROC)GetProcAddress(module, "WineAccessInputHook") : NULL; HHOOK hook = NULL; HHOOK inputHook = NULL; DWORD hookedThread = 0; if (!callback || !inputCallback) fprintf(stderr, "wine-access: 32-bit menu/editor hooks unavailable (error %lu)\n", GetLastError()); if (callback && inputCallback) { while (IsWindow(bridge)) { // Wine's mixed-architecture global hook chain can suppress the 64-bit // hook. Attach only to the foreground 32-bit UI thread, and release it // when focus moves elsewhere. The 64-bit helper keeps its global hook. DWORD thread = foreground_thread32(); if (thread != hookedThread) { if (hook) UnhookWindowsHookEx(hook); if (inputHook) UnhookWindowsHookEx(inputHook); hook = thread ? SetWindowsHookExW(WH_CALLWNDPROC, callback, module, thread) : NULL; inputHook = thread ? SetWindowsHookExW(WH_GETMESSAGE, inputCallback, module, thread) : NULL; hookedThread = hook && inputHook ? thread : 0; if (!hookedThread) { if (hook) UnhookWindowsHookEx(hook); if (inputHook) UnhookWindowsHookEx(inputHook); hook = inputHook = NULL; } else { HWND foreground = GetForegroundWindow(); WCHAR className[128]; if (GetWindowThreadProcessId(foreground, NULL) == hookedThread && GetClassNameW(foreground, className, ARRAYSIZE(className)) && !lstrcmpW(className, L"Blizzard Bootstrapper")) { // A query can have passed through while this thread's hook was // detached. Reestablish readiness on every attachment, including // return from another workspace/application. RemovePropW(foreground, BOOTSTRAPPER_STATE_READY); RemovePropW(foreground, BOOTSTRAPPER_STATE_PENDING); PostMessageW(foreground, WM_NULL, 0, 0); } } } DWORD result = MsgWaitForMultipleObjects(1, &parent, FALSE, 100, QS_ALLINPUT); if (result == WAIT_OBJECT_0 || result == WAIT_FAILED) break; MSG message; while (PeekMessageW(&message, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&message); DispatchMessageW(&message); } } if (hook) UnhookWindowsHookEx(hook); if (inputHook) UnhookWindowsHookEx(inputHook); } if (module) FreeLibrary(module); CloseHandle(parent); return callback && inputCallback ? 0 : 1; }