diff --git a/modules/rostests/win32/user32/CMakeLists.txt b/modules/rostests/win32/user32/CMakeLists.txt index be1aa50f51b..f86e696165d 100644 --- a/modules/rostests/win32/user32/CMakeLists.txt +++ b/modules/rostests/win32/user32/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(biditext) add_subdirectory(editalign) +add_subdirectory(menuaccels) add_subdirectory(messagebox) add_subdirectory(paintdesktop) add_subdirectory(psmtest) diff --git a/modules/rostests/win32/user32/menuaccels/CMakeLists.txt b/modules/rostests/win32/user32/menuaccels/CMakeLists.txt new file mode 100644 index 00000000000..f87afc95342 --- /dev/null +++ b/modules/rostests/win32/user32/menuaccels/CMakeLists.txt @@ -0,0 +1,6 @@ + +add_rc_deps(menuaccels.rc ${CMAKE_CURRENT_SOURCE_DIR}/menuaccels.ico) +add_executable(menuaccels menuaccels.c menuaccels.rc) +set_module_type(menuaccels win32gui UNICODE) +add_importlibs(menuaccels user32 msvcrt kernel32) +add_rostests_file(TARGET menuaccels SUBDIR suppl) diff --git a/modules/rostests/win32/user32/menuaccels/data.c b/modules/rostests/win32/user32/menuaccels/data.c new file mode 100644 index 00000000000..3267ddfd488 --- /dev/null +++ b/modules/rostests/win32/user32/menuaccels/data.c @@ -0,0 +1,317 @@ +/* + * PROJECT: ReactOS Tests + * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) + * PURPOSE: Common data + * COPYRIGHT: Copyright 2026 Hermès Bélusca-Maïto + */ + +/* + * References: + * https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes + * https://bsakatu.net/doc/virtual-key-of-windows/ + */ +static const PCSTR VkNames[] = +{ +/* 0x00 */ + "VK_NONE", + "VK_LBUTTON", + "VK_RBUTTON", + "VK_CANCEL", + "VK_MBUTTON", +// #if (_WIN32_WINNT >= 0x0500) + "VK_XBUTTON1", + "VK_XBUTTON2", +// #endif /* _WIN32_WINNT >= 0x0500 */ + NULL, // Reserved + "VK_BACK", + "VK_TAB", + NULL, // Reserved + NULL, // Reserved + "VK_CLEAR", + "VK_RETURN", + NULL, // Unassigned + NULL, // Unassigned +/* 0x10 */ + "VK_SHIFT", + "VK_CONTROL", + "VK_MENU", + "VK_PAUSE", + "VK_CAPITAL", + "VK_KANA-VK_HANGUL", + "VK_IME_ON", + "VK_JUNJA", + "VK_FINAL", + "VK_HANJA-VK_KANJI", + "VK_IME_OFF", + "VK_ESCAPE", + "VK_CONVERT", + "VK_NONCONVERT", + "VK_ACCEPT", + "VK_MODECHANGE", +/* 0x20 */ + "VK_SPACE", + "VK_PRIOR", + "VK_NEXT", + "VK_END", + "VK_HOME", + "VK_LEFT", + "VK_UP", + "VK_RIGHT", + "VK_DOWN", + "VK_SELECT", + "VK_PRINT", + "VK_EXECUTE", + "VK_SNAPSHOT", + "VK_INSERT", + "VK_DELETE", + "VK_HELP", +/* 0x30 */ + /* VK_0 - VK_9 are the same as ASCII '0' - '9' (0x30 - 0x39) */ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + NULL, // Undefined + NULL, // Undefined + NULL, // Undefined + NULL, // Undefined + NULL, // Undefined + NULL, // Undefined +/* 0x40 */ + NULL, // Undefined + /* VK_A - VK_Z are the same as ASCII 'A' - 'Z' (0x41 - 0x5A) */ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", +/* 0x50 */ + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "VK_LWIN", + "VK_RWIN", + "VK_APPS", + NULL, // Reserved + "VK_SLEEP", +/* 0x60 */ + "VK_NUMPAD0", + "VK_NUMPAD1", + "VK_NUMPAD2", + "VK_NUMPAD3", + "VK_NUMPAD4", + "VK_NUMPAD5", + "VK_NUMPAD6", + "VK_NUMPAD7", + "VK_NUMPAD8", + "VK_NUMPAD9", + "VK_MULTIPLY", + "VK_ADD", + "VK_SEPARATOR", + "VK_SUBTRACT", + "VK_DECIMAL", + "VK_DIVIDE", +/* 0x70 */ + "VK_F1", + "VK_F2", + "VK_F3", + "VK_F4", + "VK_F5", + "VK_F6", + "VK_F7", + "VK_F8", + "VK_F9", + "VK_F10", + "VK_F11", + "VK_F12", + "VK_F13", + "VK_F14", + "VK_F15", + "VK_F16", +/* 0x80 */ + "VK_F17", + "VK_F18", + "VK_F19", + "VK_F20", + "VK_F21", + "VK_F22", + "VK_F23", + "VK_F24", +// #if (_WIN32_WINNT >= 0x0604) + "VK_NAVIGATION_VIEW", // Reserved + "VK_NAVIGATION_MENU", // Reserved + "VK_NAVIGATION_UP", // Reserved + "VK_NAVIGATION_DOWN", // Reserved + "VK_NAVIGATION_LEFT", // Reserved + "VK_NAVIGATION_RIGHT", // Reserved + "VK_NAVIGATION_ACCEPT", // Reserved + "VK_NAVIGATION_CANCEL", // Reserved +// #endif /* _WIN32_WINNT >= 0x0604 */ +/* 0x90 */ + "VK_NUMLOCK", + "VK_SCROLL", + /* + * OEM specific + */ + /* NEC PC-9800 kbd definitions */ + // "VK_OEM_NEC_EQUAL", // '=' key on numpad // Same value as VK_OEM_FJ_JISHO + /* + * Fujitsu/OASYS kbd definitions + */ + "VK_OEM_FJ_JISHO", // 'Dictionary' key + "VK_OEM_FJ_MASSHOU", // 'Unregister word' key + "VK_OEM_FJ_TOUROKU", // 'Register word' key + "VK_OEM_FJ_LOYA", // 'Left OYAYUBI' key + "VK_OEM_FJ_ROYA", // 'Right OYAYUBI' key + NULL, // Unassigned + NULL, // Unassigned + NULL, // Unassigned + NULL, // Unassigned + NULL, // Unassigned + NULL, // Unassigned + NULL, // Unassigned + NULL, // Unassigned + NULL, // Unassigned +/* 0xA0 */ + "VK_LSHIFT", + "VK_RSHIFT", + "VK_LCONTROL", + "VK_RCONTROL", + "VK_LMENU", + "VK_RMENU", +// #if (_WIN32_WINNT >= 0x0500) + "VK_BROWSER_BACK", + "VK_BROWSER_FORWARD", + "VK_BROWSER_REFRESH", + "VK_BROWSER_STOP", + "VK_BROWSER_SEARCH", + "VK_BROWSER_FAVORITES", + "VK_BROWSER_HOME", + "VK_VOLUME_MUTE", + "VK_VOLUME_DOWN", + "VK_VOLUME_UP", +/* 0xB0 */ + "VK_MEDIA_NEXT_TRACK", + "VK_MEDIA_PREV_TRACK", + "VK_MEDIA_STOP", + "VK_MEDIA_PLAY_PAUSE", + "VK_LAUNCH_MAIL", + "VK_LAUNCH_MEDIA_SELECT", + "VK_LAUNCH_APP1", + "VK_LAUNCH_APP2", +// #endif /* _WIN32_WINNT >= 0x0500 */ + NULL, // Reserved + NULL, // Reserved + "VK_OEM_1", // ';:' for US + "VK_OEM_PLUS", // '+' any country + "VK_OEM_COMMA", // ',' any country + "VK_OEM_MINUS", // '-' any country + "VK_OEM_PERIOD", // '.' any country + "VK_OEM_2", // '/?' for US +/* 0xC0 */ + "VK_OEM_3", // '`~' for US + NULL, // Reserved + NULL, // Reserved +// #if (_WIN32_WINNT >= 0x0604) + "VK_GAMEPAD_A", + "VK_GAMEPAD_B", + "VK_GAMEPAD_X", + "VK_GAMEPAD_Y", + "VK_GAMEPAD_RIGHT_SHOULDER", + "VK_GAMEPAD_LEFT_SHOULDER", + "VK_GAMEPAD_LEFT_TRIGGER", + "VK_GAMEPAD_RIGHT_TRIGGER", + "VK_GAMEPAD_DPAD_UP", + "VK_GAMEPAD_DPAD_DOWN", + "VK_GAMEPAD_DPAD_LEFT", + "VK_GAMEPAD_DPAD_RIGHT", + "VK_GAMEPAD_MENU", +/* 0xD0 */ + "VK_GAMEPAD_VIEW", + "VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON", + "VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON", + "VK_GAMEPAD_LEFT_THUMBSTICK_UP", + "VK_GAMEPAD_LEFT_THUMBSTICK_DOWN", + "VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT", + "VK_GAMEPAD_LEFT_THUMBSTICK_LEFT", + "VK_GAMEPAD_RIGHT_THUMBSTICK_UP", + "VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN", + "VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT", + "VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT", +// #endif /* _WIN32_WINNT >= 0x0604 */ + "VK_OEM_4", // '[{' for US + "VK_OEM_5", // '\|' for US + "VK_OEM_6", // ']}' for US + "VK_OEM_7", // ''"' for US + "VK_OEM_8", +/* 0xE0 */ + NULL, // Reserved + "VK_OEM_AX", // 'AX' key on Japanese AX kbd // OEM specific + "VK_OEM_102", // "<>" or "\|" on RT 102-key kbd // OEM specific + "VK_ICO_HELP", // Help key on ICO // OEM specific + "VK_ICO_00", // 00 key on ICO // OEM specific +// #if (WINVER >= 0x0400) + "VK_PROCESSKEY", +// #endif /* WINVER >= 0x0400 */ + "VK_ICO_CLEAR", // OEM specific +// #if (_WIN32_WINNT >= 0x0500) + "VK_PACKET", +// #endif /* _WIN32_WINNT >= 0x0500 */ + NULL, // Unassigned + /* + * OEM specific + * Nokia/Ericsson definitions + */ + "VK_OEM_RESET", + "VK_OEM_JUMP", + "VK_OEM_PA1", + "VK_OEM_PA2", + "VK_OEM_PA3", + "VK_OEM_WSCTRL", + "VK_OEM_CUSEL", +/* 0xF0 */ + "VK_OEM_ATTN", + "VK_OEM_FINISH", + "VK_OEM_COPY", + "VK_OEM_AUTO", + "VK_OEM_ENLW", + "VK_OEM_BACKTAB", + /* END of OEM definitions */ + "VK_ATTN", + "VK_CRSEL", + "VK_EXSEL", + "VK_EREOF", + "VK_PLAY", + "VK_ZOOM", + "VK_NONAME", // Reserved + "VK_PA1", + "VK_OEM_CLEAR", + "VK_UNKNOWN" +}; +C_ASSERT(_countof(VkNames) == 0x100); + +/* EOF */ diff --git a/modules/rostests/win32/user32/menuaccels/menuaccels.c b/modules/rostests/win32/user32/menuaccels/menuaccels.c new file mode 100644 index 00000000000..c37d5f343f1 --- /dev/null +++ b/modules/rostests/win32/user32/menuaccels/menuaccels.c @@ -0,0 +1,612 @@ +/* + * PROJECT: ReactOS Tests + * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) + * PURPOSE: Tests the interaction between menus, accelerators, and Alt+Numpad keys + * COPYRIGHT: Copyright 2026 Hermès Bélusca-Maïto + */ + +/* HEADERS *******************************************************************/ + +/* C Headers */ +#include +#include + +/* PSDK Headers */ +#include +#include +#include + +#include "resource.h" + +/* GLOBALS *******************************************************************/ + +static HINSTANCE g_hInst; +static const PCWSTR szWindowClass = L"TESTAPP"; + +static HWND g_hEdit, g_hMsgDump; +static ATOM g_aHotKey1, g_aHotKey2; +static HHOOK g_hHook1, g_hHook2, g_hHook3; +static BOOL g_bShowEdit = TRUE; +static BOOL g_bXlatAccels = TRUE; + +#define EDIT_STYLE_WRAP (WS_CHILD | WS_VSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_NOHIDESEL) +#define EDIT_STYLE (EDIT_STYLE_WRAP | WS_HSCROLL | ES_AUTOHSCROLL) + +/* FUNCTIONS *****************************************************************/ + +LRESULT CALLBACK +HookProcCallWnd( + _In_ int nCode, + _In_ WPARAM wParam, + _In_ LPARAM lParam); + +LRESULT CALLBACK +HookProcGetMsg( + _In_ int nCode, + _In_ WPARAM wParam, + _In_ LPARAM lParam); + +LRESULT CALLBACK +HookProcMessage( + _In_ int nCode, + _In_ WPARAM wParam, + _In_ LPARAM lParam); + +LRESULT CALLBACK +WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +static ATOM +MyRegisterClass(_In_ HINSTANCE hInstance) +{ + WNDCLASSEXW wcex; + HICON hSmIcon, hBgIcon; + + hSmIcon = LoadImageW(hInstance, + MAKEINTRESOURCEW(IDI_TESTAPP), + IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + LR_SHARED); + + hBgIcon = LoadImageW(hInstance, + MAKEINTRESOURCEW(IDI_TESTAPP), + IMAGE_ICON, + GetSystemMetrics(SM_CXICON), + GetSystemMetrics(SM_CYICON), + LR_SHARED); + + wcex.cbSize = sizeof(wcex); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = hBgIcon; + wcex.hCursor = LoadCursorW(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_TESTAPP); + wcex.lpszClassName = szWindowClass; + wcex.hIconSm = hSmIcon; + + return RegisterClassExW(&wcex); +} + +int APIENTRY +wWinMain( + _In_ HINSTANCE hInstance, + _In_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ int nCmdShow) +{ + HWND hWnd; + MSG msg; + HACCEL hAccelTable; + + UNREFERENCED_PARAMETER(hPrevInstance); + UNREFERENCED_PARAMETER(lpCmdLine); + + /* Register ourselves */ + if (!MyRegisterClass(hInstance)) + return FALSE; + g_hInst = hInstance; + + /* Create the main window and load the accelerators */ + hWnd = CreateWindowW(szWindowClass, L"MyTestApp", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, hInstance, NULL); + if (!hWnd) + return FALSE; + + /* Show ourselves */ + ShowWindow(hWnd, nCmdShow); + UpdateWindow(hWnd); + + /* Set up the accelerators, hotkey, and window hooks */ + hAccelTable = LoadAcceleratorsW(hInstance, MAKEINTRESOURCEW(IDC_TESTAPP)); + if (!hAccelTable) + OutputDebugStringA("LoadAcceleratorsW() failed\n"); + + g_aHotKey1 = GlobalAddAtomW(L"TESTAPP Alt 4 Hotkey"); + if (!RegisterHotKey(NULL, g_aHotKey1, MOD_ALT, VK_NUMPAD4)) + OutputDebugStringA("RegisterHotKey() failed\n"); //DPRINT1("RegisterHotKey failed with %lu\n", GetLastError()); + + g_aHotKey2 = GlobalAddAtomW(L"TESTAPP Alt 5 Hotkey"); + if (!RegisterHotKey(hWnd, g_aHotKey2, MOD_ALT, VK_NUMPAD5)) + OutputDebugStringA("RegisterHotKey() failed\n"); //DPRINT1("RegisterHotKey failed with %lu\n", GetLastError()); + + g_hHook1 = SetWindowsHookExW(WH_CALLWNDPROC, HookProcCallWnd, NULL, GetCurrentThreadId()); + g_hHook2 = SetWindowsHookExW(WH_GETMESSAGE, HookProcGetMsg, NULL, GetCurrentThreadId()); + g_hHook3 = SetWindowsHookExW(WH_MSGFILTER, HookProcMessage, NULL, GetCurrentThreadId()); + + /* Pump the queue */ + while (GetMessageW(&msg, NULL, 0, 0)) + { + if ((msg.message == WM_HOTKEY) && (msg.wParam == g_aHotKey1)) + { + // if (LOWORD(msg.lParam) == MOD_ALT && HIWORD(msg.lParam) == VK_NUMPAD4) + PostMessageW(hWnd, WM_COMMAND, MAKEWPARAM(IDM_FOUR, 1), 0); + continue; + } + + if (!g_bXlatAccels || !TranslateAcceleratorW(hWnd, hAccelTable, &msg)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + + /* Cleanup and return */ + UnhookWindowsHookEx(g_hHook3); + UnhookWindowsHookEx(g_hHook2); + UnhookWindowsHookEx(g_hHook1); + + UnregisterHotKey(NULL, g_aHotKey2); + GlobalDeleteAtom(g_aHotKey2); + UnregisterHotKey(NULL, g_aHotKey1); + GlobalDeleteAtom(g_aHotKey1); + + return (int)msg.wParam; +} + +static void +SetMenuString( + _In_ HMENU hMenu, + _In_ UINT uCommand, + _In_ LPCWSTR pszString) +{ + WCHAR szMenu[128]; + + if (IS_INTRESOURCE(pszString)) + { + LoadStringW(g_hInst, PtrToUlong(pszString), szMenu, _countof(szMenu)); + pszString = szMenu; + } + ModifyMenuW(hMenu, uCommand, MF_BYCOMMAND | MF_STRING, uCommand, pszString); +} + +static void +UpdateEditWnd(_In_ HWND hWndParent) +{ + /* Change the menu command string */ + SetMenuString(GetMenu(hWndParent), IDM_SHOWHIDEEDIT, + g_bShowEdit ? MAKEINTRESOURCEW(IDS_EDITHIDE) + : MAKEINTRESOURCEW(IDS_EDITSHOW)); + + /* Show or hide the editable edit control and set focus to it if needed */ + ShowWindow(g_hEdit, g_bShowEdit ? SW_SHOW : SW_HIDE); + if (g_bShowEdit) + SetFocus(g_hEdit); + + /* Re-arrange controls */ + PostMessageW(hWndParent, WM_SIZE, 0, 0); +} + +static void +UpdateAccelXlatMenu(_In_ HWND hWnd) +{ + /* Change the menu command string */ + SetMenuString(GetMenu(hWnd), IDM_ACCELXLAT, + g_bXlatAccels ? MAKEINTRESOURCEW(IDS_ACCELNOXLAT) + : MAKEINTRESOURCEW(IDS_ACCELXLAT)); +} + +static void +DoCreateEditChildren(_In_ HWND hWndParent) +{ + g_hEdit = CreateWindowW(/*WC_EDITW*/ L"Edit", L"", EDIT_STYLE_WRAP, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + hWndParent, NULL, g_hInst, NULL); + SendMessageW(g_hEdit, EM_LIMITTEXT, 0, 0); + + g_hMsgDump = CreateWindowW(/*WC_EDITW*/ L"Edit", L"", EDIT_STYLE_WRAP | ES_READONLY, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + hWndParent, NULL, g_hInst, NULL); + SendMessageW(g_hMsgDump, EM_LIMITTEXT, 0, 0); + + /* Finally shows new edit controls */ + ShowWindow(g_hMsgDump, SW_SHOW); + UpdateEditWnd(hWndParent); + + /* Re-arrange controls */ + PostMessageW(hWndParent, WM_SIZE, 0, 0); +} + + +static void +AppendTextLineA( + _In_ HWND hEdit, + _In_ PCSTR pszLine) +{ + DWORD l, r; + SendMessageW(hEdit, EM_GETSEL, (WPARAM)&l, (LPARAM)&r); + //SendMessageW(hEdit, EM_SETSEL, -1, -1); + SendMessageW(hEdit, EM_SETSEL, GetWindowTextLengthW(hEdit), -1); + SendMessageA(hEdit, EM_REPLACESEL, FALSE, (LPARAM)pszLine); + SendMessageW(hEdit, EM_SETSEL, l, r); +} + +static void dump_printf(const CHAR *fmt, ...) +{ + CHAR szText[512]; + int len; + + va_list va; + va_start(va, fmt); + len = _vsnprintf(szText, _countof(szText) - 2, fmt, va); + if (len < 0) + *szText = ANSI_NULL; + va_end(va); + + /* Ensure the line ends with CR-LF */ + len = min(len, _countof(szText) - 3); + szText[len + 0] = '\r'; + szText[len + 1] = '\n'; + szText[len + 2] = ANSI_NULL; + + /* Append the text line */ + AppendTextLineA(g_hMsgDump, szText); + //DbgPrint("TEST: %s\n", szText); + OutputDebugStringA(szText); +} +#define MSGDUMP_PRINTF dump_printf +//#include "msgdump.h" +#define MSGDUMP_PREFIX "" +#define MSGDUMP_API WINAPI + +/* For 'PCSTR VkNames[]' */ +#include "data.c" + +static int +GetVkName( + _In_ UINT vk, + _Out_writes_(cchBuff) PSTR pszBuffer, + _In_ SIZE_T cchBuff) +{ + PCSTR vkName; + + if (vk > 0xFF) + vk = 0xFF; // VK_UNKNOWN + vkName = VkNames[vk]; + + if (!vkName) + { + return _snprintf(pszBuffer, cchBuff, "%lX", vk); + } + else + { + if ((0x30 <= vk && vk <= 0x39) || (0x41 <= vk && vk <= 0x5A)) + return _snprintf(pszBuffer, cchBuff, "'%s'", vkName); + else + return _snprintf(pszBuffer, cchBuff, "%s", vkName); + } +} + +static __inline void MSGDUMP_API +MD_OnKey(_In_ CHAR Pfx, _In_ HWND hwnd, _In_ BOOL fDown, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + UINT vk = (UINT)wParam; + UINT cRepeat = (UINT)LOWORD(lParam); + USHORT flags = HIWORD(lParam); + UCHAR ScanCode = (flags & 0xFF); + + CHAR szVkName[100]; + GetVkName(vk, szVkName, _countof(szVkName)); + + if (fDown) + { + MSGDUMP_PRINTF("%p %c %sWM_KEYDOWN\tnVirtKey:%s cRepeat:%u ScanCode:%02X fExtended:%u fAltDown:%u fRepeat:%u fUp:%u [wParam:%08lX lParam:%08lX]", + hwnd, Pfx, MSGDUMP_PREFIX, szVkName, cRepeat, ScanCode, + !!(flags & KF_EXTENDED), !!(flags & KF_ALTDOWN), !!(flags & KF_REPEAT), !!(flags & KF_UP), + wParam, lParam); + } + else + { + MSGDUMP_PRINTF("%p %c %sWM_KEYUP\tnVirtKey:%s cRepeat:%u ScanCode:%02X fExtended:%u fAltDown:%u fRepeat:%u fUp:%u [wParam:%08lX lParam:%08lX]", + hwnd, Pfx, MSGDUMP_PREFIX, szVkName, cRepeat, ScanCode, + !!(flags & KF_EXTENDED), !!(flags & KF_ALTDOWN), !!(flags & KF_REPEAT), !!(flags & KF_UP), + wParam, lParam); + } +} + +static __inline void MSGDUMP_API +MD_OnChar(_In_ CHAR Pfx, _In_ HWND hwnd, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + WCHAR ch = (WCHAR)wParam; + UINT cRepeat = (UINT)LOWORD(lParam); + USHORT flags = HIWORD(lParam); + UCHAR ScanCode = (flags & 0xFF); + MSGDUMP_PRINTF("%p %c %sWM_CHAR\tchCharCode:'%04X' (%u) cRepeat:%u ScanCode:%02X fExtended:%u fAltDown:%u fRepeat:%u fUp:%u [wParam:%08lX lParam:%08lX]", + hwnd, Pfx, MSGDUMP_PREFIX, ch, ch, cRepeat, ScanCode, + !!(flags & KF_EXTENDED), !!(flags & KF_ALTDOWN), !!(flags & KF_REPEAT), !!(flags & KF_UP), + wParam, lParam); +} + +static __inline void MSGDUMP_API +MD_OnDeadChar(_In_ CHAR Pfx, _In_ HWND hwnd, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + WCHAR ch = (WCHAR)wParam; + UINT cRepeat = (UINT)LOWORD(lParam); + USHORT flags = HIWORD(lParam); + UCHAR ScanCode = (flags & 0xFF); + MSGDUMP_PRINTF("%p %c %sWM_DEADCHAR\tchCharCode:'%04X' (%u) cRepeat:%u ScanCode:%02X fExtended:%u fAltDown:%u fRepeat:%u fUp:%u [wParam:%08lX lParam:%08lX]", + hwnd, Pfx, MSGDUMP_PREFIX, ch, ch, cRepeat, ScanCode, + !!(flags & KF_EXTENDED), !!(flags & KF_ALTDOWN), !!(flags & KF_REPEAT), !!(flags & KF_UP), + wParam, lParam); +} + +static __inline void MSGDUMP_API +MD_OnSysKey(_In_ CHAR Pfx, _In_ HWND hwnd, _In_ BOOL fDown, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + UINT vk = (UINT)wParam; + UINT cRepeat = (UINT)LOWORD(lParam); + USHORT flags = HIWORD(lParam); + UCHAR ScanCode = (flags & 0xFF); + + CHAR szVkName[100]; + GetVkName(vk, szVkName, _countof(szVkName)); + + if (fDown) + { + MSGDUMP_PRINTF("%p %c %sWM_SYSKEYDOWN\tnVirtKey:%s cRepeat:%u ScanCode:%02X fExtended:%u fAltDown:%u fRepeat:%u fUp:%u [wParam:%08lX lParam:%08lX]", + hwnd, Pfx, MSGDUMP_PREFIX, szVkName, cRepeat, ScanCode, + !!(flags & KF_EXTENDED), !!(flags & KF_ALTDOWN), !!(flags & KF_REPEAT), !!(flags & KF_UP), + wParam, lParam); + } + else + { + MSGDUMP_PRINTF("%p %c %sWM_SYSKEYUP\tnVirtKey:%s cRepeat:%u ScanCode:%02X fExtended:%u fAltDown:%u fRepeat:%u fUp:%u [wParam:%08lX lParam:%08lX]", + hwnd, Pfx, MSGDUMP_PREFIX, szVkName, cRepeat, ScanCode, + !!(flags & KF_EXTENDED), !!(flags & KF_ALTDOWN), !!(flags & KF_REPEAT), !!(flags & KF_UP), + wParam, lParam); + } +} + +static __inline void MSGDUMP_API +MD_OnSysChar(_In_ CHAR Pfx, _In_ HWND hwnd, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + WCHAR ch = (WCHAR)wParam; + UINT cRepeat = (UINT)LOWORD(lParam); + USHORT flags = HIWORD(lParam); + UCHAR ScanCode = (flags & 0xFF); + MSGDUMP_PRINTF("%p %c %sWM_SYSCHAR\tchCharCode:'%04X' (%u) cRepeat:%u ScanCode:%02X fExtended:%u fAltDown:%u fRepeat:%u fUp:%u [wParam:%08lX lParam:%08lX]", + hwnd, Pfx, MSGDUMP_PREFIX, ch, ch, cRepeat, ScanCode, + !!(flags & KF_EXTENDED), !!(flags & KF_ALTDOWN), !!(flags & KF_REPEAT), !!(flags & KF_UP), + wParam, lParam); +} + +static __inline void MSGDUMP_API +MD_OnSysDeadChar(_In_ CHAR Pfx, _In_ HWND hwnd, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + WCHAR ch = (WCHAR)wParam; + UINT cRepeat = (UINT)LOWORD(lParam); + USHORT flags = HIWORD(lParam); + UCHAR ScanCode = (flags & 0xFF); + MSGDUMP_PRINTF("%p %c %sWM_SYSDEADCHAR\tchCharCode:'%04X' (%u) cRepeat:%u ScanCode:%02X fExtended:%u fAltDown:%u fRepeat:%u fUp:%u [wParam:%08lX lParam:%08lX]", + hwnd, Pfx, MSGDUMP_PREFIX, ch, ch, cRepeat, ScanCode, + !!(flags & KF_EXTENDED), !!(flags & KF_ALTDOWN), !!(flags & KF_REPEAT), !!(flags & KF_UP), + wParam, lParam); +} + +static __inline void MSGDUMP_API +MD_OnUniChar(_In_ CHAR Pfx, _In_ HWND hwnd, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + WCHAR ch = (WCHAR)wParam; + UINT cRepeat = (UINT)LOWORD(lParam); + USHORT flags = HIWORD(lParam); + UCHAR ScanCode = (flags & 0xFF); + MSGDUMP_PRINTF("%p %c %sWM_UNICHAR\tchCharCode:'%04X' (%u) cRepeat:%u ScanCode:%02X fExtended:%u fAltDown:%u fRepeat:%u fUp:%u [wParam:%08lX lParam:%08lX]", + hwnd, Pfx, MSGDUMP_PREFIX, ch, ch, cRepeat, ScanCode, + !!(flags & KF_EXTENDED), !!(flags & KF_ALTDOWN), !!(flags & KF_REPEAT), !!(flags & KF_UP), + wParam, lParam); +} + +static __inline void MSGDUMP_API +MD_OnHotKey(_In_ CHAR Pfx, _In_ HWND hwnd, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + int idHotKey = (int)wParam; + UINT fuModifiers = (UINT)LOWORD(lParam); + UINT vk = (UINT)HIWORD(lParam); + + CHAR szVkName[100]; + GetVkName(vk, szVkName, _countof(szVkName)); + // TODO: Convert fuModifiers to "human-readable" names. + + MSGDUMP_PRINTF("%p %c %sWM_HOTKEY\tidHotKey:%04X fuModifiers:%u nVirtKey:%s [wParam:%08lX lParam:%08lX]", + hwnd, Pfx, MSGDUMP_PREFIX, idHotKey, fuModifiers, szVkName, + wParam, lParam); +} + +static __inline LRESULT MSGDUMP_API +MD_msgdump(_In_ CHAR Pfx, _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam) +{ + switch (uMsg) + { + case WM_KEYDOWN: MD_OnKey(Pfx, hwnd, TRUE, wParam, lParam); break; + case WM_KEYUP: MD_OnKey(Pfx, hwnd, FALSE, wParam, lParam); break; + case WM_CHAR: MD_OnChar(Pfx, hwnd, wParam, lParam); break; + case WM_DEADCHAR: MD_OnDeadChar(Pfx, hwnd, wParam, lParam); break; + case WM_SYSKEYDOWN: MD_OnSysKey(Pfx, hwnd, TRUE, wParam, lParam); break; + case WM_SYSKEYUP: MD_OnSysKey(Pfx, hwnd, FALSE, wParam, lParam); break; + case WM_SYSCHAR: MD_OnSysChar(Pfx, hwnd, wParam, lParam); break; + case WM_SYSDEADCHAR: MD_OnSysDeadChar(Pfx, hwnd, wParam, lParam); break; + case WM_UNICHAR: MD_OnUniChar(Pfx, hwnd, wParam, lParam); break; + case WM_HOTKEY: MD_OnHotKey(Pfx, hwnd, wParam, lParam); break; + case WM_IME_CHAR: break; // TODO + case WM_IME_KEYDOWN: break; // TODO + case WM_IME_KEYUP: break; // TODO + } + return 0; +} + +LRESULT CALLBACK +HookProcCallWnd( + _In_ int nCode, + _In_ WPARAM wParam, + _In_ LPARAM lParam) +{ + PCWPSTRUCT pcwp; + + //if (nCode < 0) /* Do not process the message */ + // return CallNextHookEx(g_hHook1, nCode, wParam, lParam); + + pcwp = (PCWPSTRUCT)lParam; + MD_msgdump('C', pcwp->hwnd, pcwp->message, pcwp->wParam, pcwp->lParam); + + return CallNextHookEx(g_hHook1, nCode, wParam, lParam); +} + +LRESULT CALLBACK +HookProcGetMsg( + _In_ int nCode, + _In_ WPARAM wParam, + _In_ LPARAM lParam) +{ + PMSG pmsg; + + //if (nCode < 0) /* Do not process the message */ + // return CallNextHookEx(g_hHook2, nCode, wParam, lParam); + + pmsg = (PMSG)lParam; + MD_msgdump('G', pmsg->hwnd, pmsg->message, pmsg->wParam, pmsg->lParam); + + return CallNextHookEx(g_hHook2, nCode, wParam, lParam); +} + +LRESULT CALLBACK +HookProcMessage( + _In_ int nCode, + _In_ WPARAM wParam, + _In_ LPARAM lParam) +{ + PMSG pmsg; + + //if (nCode < 0) /* Do not process the message */ + // return CallNextHookEx(g_hHook3, nCode, wParam, lParam); + + pmsg = (PMSG)lParam; + MD_msgdump('M', pmsg->hwnd, pmsg->message, pmsg->wParam, pmsg->lParam); + + return CallNextHookEx(g_hHook3, nCode, wParam, lParam); +} + + +LRESULT CALLBACK +WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + // MD_msgdump(hWnd, uMsg, wParam, lParam); + switch (uMsg) + { + case WM_CREATE: + DoCreateEditChildren(hWnd); + UpdateAccelXlatMenu(hWnd); + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + case WM_HOTKEY: + if (wParam == g_aHotKey1) // && LOWORD(lParam) == MOD_ALT && HIWORD(lParam) == VK_NUMPAD4 + PostMessageW(hWnd, WM_COMMAND, MAKEWPARAM(IDM_FOUR, 1), 0); + else if (wParam == g_aHotKey2) // && LOWORD(lParam) == MOD_ALT && HIWORD(lParam) == VK_NUMPAD5 + PostMessageW(hWnd, WM_COMMAND, MAKEWPARAM(IDM_FIVE, 1), 0); + break; + + case WM_COMMAND: + { + WORD wmId = LOWORD(wParam); + //WORD wmEvent = HIWORD(wParam); + switch (wmId) + { + case IDM_ONE: case IDM_TWO: case IDM_THREE: + case IDM_FOUR: case IDM_FIVE: + case IDM_THIRTEEN: case IDM_FOURTEEN: case IDM_FIFTEEN: + { + /* See `IDC_TESTAPP ACCELERATORS` */ + static const PCWSTR Messages[] = + {L"One (Alt '1' ASCII)", L"Two (Alt 2 VIRTKEY)", L"Three (Alt VK_NUMPAD3 VIRTKEY)", + L"Four (Alt VK_NUMPAD4 -- WM_HOTKEY thread)", L"Five (Alt VK_NUMPAD5 -- WM_HOTKEY hWnd)", + L"Thirteen (Alt 'D' ASCII)", L"Fourteen (Alt E VIRTKEY)", L"Fifteen (Alt F VIRTKEY)"}; + + if (IDM_ONE <= wmId && wmId <= IDM_FIVE) + wmId -= IDM_ONE; + else if (IDM_THIRTEEN <= wmId && wmId <= IDM_FIFTEEN) + wmId -= IDM_THIRTEEN - (IDM_FIVE - IDM_ONE + 1); + MessageBoxW(hWnd, Messages[wmId], L"Info", MB_OK); + break; + } + + case IDM_SHOWHIDEEDIT: + g_bShowEdit = !g_bShowEdit; + UpdateEditWnd(hWnd); + break; + + case IDM_ACCELXLAT: + g_bXlatAccels = !g_bXlatAccels; + UpdateAccelXlatMenu(hWnd); + break; + + case IDM_CLEARLOG: + SendMessageW(g_hMsgDump, WM_SETTEXT, 0, (LPARAM)L""); + break; + + case IDM_EXIT: + DestroyWindow(hWnd); + break; + default: + return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } + break; + } + + case WM_SIZE: + { + RECT rc; + GetClientRect(hWnd, &rc); + if (g_bShowEdit) + { + MoveWindow(g_hEdit, 0, 0, rc.right, rc.bottom / 2, TRUE); + MoveWindow(g_hMsgDump, 0, rc.bottom / 2, rc.right, rc.bottom / 2, TRUE); + } + else + { + MoveWindow(g_hMsgDump, 0, 0, rc.right, rc.bottom, TRUE); + } + break; + } + + case WM_PAINT: + { + PAINTSTRUCT ps; + BeginPaint(hWnd, &ps); + EndPaint(hWnd, &ps); + break; + } + + default: + return DefWindowProcW(hWnd, uMsg, wParam, lParam); + } + return 0; +} + +/* EOF */ diff --git a/modules/rostests/win32/user32/menuaccels/menuaccels.ico b/modules/rostests/win32/user32/menuaccels/menuaccels.ico new file mode 100644 index 00000000000..c4005b83f4d Binary files /dev/null and b/modules/rostests/win32/user32/menuaccels/menuaccels.ico differ diff --git a/modules/rostests/win32/user32/menuaccels/menuaccels.rc b/modules/rostests/win32/user32/menuaccels/menuaccels.rc new file mode 100644 index 00000000000..28fcc50bb5c --- /dev/null +++ b/modules/rostests/win32/user32/menuaccels/menuaccels.rc @@ -0,0 +1,116 @@ +/* + * PROJECT: ReactOS Tests + * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) + * PURPOSE: Resources + * COPYRIGHT: Copyright 2026 Hermès Bélusca-Maïto + */ + +#include +#include +#include "resource.h" + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL + +#define REACTOS_STR_FILE_DESCRIPTION "ReactOS Menu & Accelerators Test" +#define REACTOS_STR_INTERNAL_NAME "menuaccels" +#define REACTOS_STR_ORIGINAL_FILENAME "menuaccels.exe" +#include + +IDI_TESTAPP ICON "menuaccels.ico" + +IDC_TESTAPP MENU +BEGIN + POPUP "Zero (&0)" + BEGIN + MENUITEM "", IDM_SHOWHIDEEDIT + MENUITEM "", IDM_ACCELXLAT + MENUITEM "&Clear Log", IDM_CLEARLOG + MENUITEM "&Quit", IDM_EXIT + END + POPUP "One (&1)" + BEGIN + MENUITEM "Alpha", ID_ONE_ALPHA + END + POPUP "Two (&2)" + BEGIN + MENUITEM "Bravo", ID_TWO_BRAVO + END + POPUP "Three (&3)" + BEGIN + MENUITEM "Charlie", ID_THREE_CHARLIE + END + POPUP "Four (&4)" + BEGIN + MENUITEM "Delta", ID_FOUR_DELTA + END + POPUP "Five (&5)" + BEGIN + MENUITEM "Echo", ID_FIVE_ECHO + END + POPUP "Six (&6)" + BEGIN + MENUITEM "Foxtrot", ID_SIX_FOXTROT + END + POPUP "Seven (&7)" + BEGIN + MENUITEM "Golf", ID_SEVEN_GOLF + END + POPUP "Eight (&8)" + BEGIN + MENUITEM "Hotel", ID_EIGHT_HOTEL + END + POPUP "Nine (&9)" + BEGIN + MENUITEM "India", ID_NINE_INDIA + END + POPUP "Ten (&A)" + BEGIN + MENUITEM "Juliet", ID_TEN_JULIET + END + POPUP "Eleven (&B)" + BEGIN + MENUITEM "Kilo", ID_ELEVEN_KILO + END + POPUP "Twelve (&C)" + BEGIN + MENUITEM "Lima", ID_TWELVE_LIMA + END + POPUP "Thirteen (&D)" + BEGIN + MENUITEM "Mike", ID_THIRTEEN_MIKE + END + POPUP "Fourteen (&E)" + BEGIN + MENUITEM "November", ID_FOURTEEN_NOVEMBER + END + POPUP "Fifteen (&F)" + BEGIN + MENUITEM "Oscar", ID_FIFTEEN_OSCAR + END + POPUP "Plus (&+)" + BEGIN + MENUITEM "Papa", ID_PLUS_PAPA + END + POPUP "Dot (&.)" + BEGIN + MENUITEM "Quebec", ID_DOT_QUEBEC + END +END + +IDC_TESTAPP ACCELERATORS +BEGIN + "1", IDM_ONE, ASCII, ALT, NOINVERT + "2", IDM_TWO, VIRTKEY, ALT, NOINVERT + VK_NUMPAD3, IDM_THREE, VIRTKEY, ALT, NOINVERT + "D", IDM_THIRTEEN, ASCII, ALT, NOINVERT + "E", IDM_FOURTEEN, VIRTKEY, ALT, NOINVERT + "F", IDM_FIFTEEN, VIRTKEY, ALT, NOINVERT +END + +STRINGTABLE +BEGIN + IDS_EDITSHOW "&Show Edit" + IDS_EDITHIDE "&Hide Edit" + IDS_ACCELXLAT "&Translate accelerators" + IDS_ACCELNOXLAT "&Don't translate accelerators" +END diff --git a/modules/rostests/win32/user32/menuaccels/resource.h b/modules/rostests/win32/user32/menuaccels/resource.h new file mode 100644 index 00000000000..990b0c23a7d --- /dev/null +++ b/modules/rostests/win32/user32/menuaccels/resource.h @@ -0,0 +1,47 @@ +/* + * PROJECT: ReactOS Tests + * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) + * PURPOSE: Resources header + * COPYRIGHT: Copyright 2026 Hermès Bélusca-Maïto + */ + +#define IDC_STATIC (-1) + +#define IDI_TESTAPP 100 +#define IDC_TESTAPP 101 + +#define IDS_EDITSHOW 110 +#define IDS_EDITHIDE 111 +#define IDS_ACCELXLAT 112 +#define IDS_ACCELNOXLAT 113 + +#define IDM_SHOWHIDEEDIT 200 +#define IDM_ACCELXLAT 201 +#define IDM_CLEARLOG 202 +#define IDM_EXIT 203 +#define IDM_ONE 204 +#define IDM_TWO 205 +#define IDM_THREE 206 +#define IDM_FOUR 207 +#define IDM_FIVE 208 +#define IDM_THIRTEEN 209 +#define IDM_FOURTEEN 210 +#define IDM_FIFTEEN 211 + +#define ID_ONE_ALPHA 220 +#define ID_TWO_BRAVO 221 +#define ID_THREE_CHARLIE 222 +#define ID_FOUR_DELTA 223 +#define ID_FIVE_ECHO 224 +#define ID_SIX_FOXTROT 225 +#define ID_SEVEN_GOLF 226 +#define ID_EIGHT_HOTEL 227 +#define ID_NINE_INDIA 228 +#define ID_TEN_JULIET 229 +#define ID_ELEVEN_KILO 230 +#define ID_TWELVE_LIMA 231 +#define ID_THIRTEEN_MIKE 232 +#define ID_FOURTEEN_NOVEMBER 233 +#define ID_FIFTEEN_OSCAR 234 +#define ID_PLUS_PAPA 235 +#define ID_DOT_QUEBEC 236