|
| 1 | +/* |
| 2 | + * PROJECT: ReactOS API tests |
| 3 | + * LICENSE: LGPL-2.1+ (https://spdx.org/licenses/LGPL-2.1+) |
| 4 | + * PURPOSE: Tests for TrackPopupMenuEx |
| 5 | + * COPYRIGHT: Copyright 2025 Katayama Hirofumi MZ <[email protected]> |
| 6 | + */ |
| 7 | + |
| 8 | +#include "precomp.h" |
| 9 | +#include <versionhelpers.h> |
| 10 | + |
| 11 | +#define CLASSNAME L"TrackPopupMenuEx tests" |
| 12 | +#define MENUCLASS L"#32768" |
| 13 | + |
| 14 | +#define VALID_TPM_FLAGS ( \ |
| 15 | + TPM_LAYOUTRTL | TPM_NOANIMATION | TPM_VERNEGANIMATION | TPM_VERPOSANIMATION | \ |
| 16 | + TPM_HORNEGANIMATION | TPM_HORPOSANIMATION | TPM_RETURNCMD | \ |
| 17 | + TPM_NONOTIFY | TPM_VERTICAL | TPM_BOTTOMALIGN | TPM_VCENTERALIGN | \ |
| 18 | + TPM_RIGHTALIGN | TPM_CENTERALIGN | TPM_RIGHTBUTTON | TPM_RECURSE \ |
| 19 | +) |
| 20 | + |
| 21 | +#ifndef TPM_WORKAREA |
| 22 | +#define TPM_WORKAREA 0x10000 |
| 23 | +#endif |
| 24 | + |
| 25 | +static VOID |
| 26 | +TEST_InvalidFlags(VOID) |
| 27 | +{ |
| 28 | + HWND hwnd = GetDesktopWindow(); |
| 29 | + HMENU hMenu = CreatePopupMenu(); |
| 30 | + BOOL ret; |
| 31 | + |
| 32 | + ret = AppendMenuW(hMenu, MF_STRING, 100, L"(Dummy)"); |
| 33 | + ok_int(ret, TRUE); |
| 34 | + |
| 35 | + INT iBit; |
| 36 | + UINT uFlags; |
| 37 | + for (iBit = 0; iBit < sizeof(DWORD) * CHAR_BIT; ++iBit) |
| 38 | + { |
| 39 | + uFlags = (1 << iBit); |
| 40 | + if (uFlags & ~VALID_TPM_FLAGS) |
| 41 | + { |
| 42 | + SetLastError(0xBEEFCAFE); |
| 43 | + ret = TrackPopupMenuEx(hMenu, uFlags, 0, 0, hwnd, NULL); |
| 44 | + ok_int(ret, FALSE); |
| 45 | + if (uFlags == TPM_WORKAREA && IsWindows7OrGreater()) |
| 46 | + ok_err(ERROR_INVALID_PARAMETER); |
| 47 | + else |
| 48 | + ok_err(ERROR_INVALID_FLAGS); |
| 49 | + } |
| 50 | + } |
| 51 | + |
| 52 | + DestroyMenu(hMenu); |
| 53 | +} |
| 54 | + |
| 55 | +static VOID |
| 56 | +TEST_InvalidSize(VOID) |
| 57 | +{ |
| 58 | + HWND hwnd = GetDesktopWindow(); |
| 59 | + HMENU hMenu = CreatePopupMenu(); |
| 60 | + TPMPARAMS params; |
| 61 | + UINT uFlags = TPM_RIGHTBUTTON; |
| 62 | + BOOL ret; |
| 63 | + |
| 64 | + ZeroMemory(¶ms, sizeof(params)); |
| 65 | + |
| 66 | + ret = AppendMenuW(hMenu, MF_STRING, 100, L"(Dummy)"); |
| 67 | + ok_int(ret, TRUE); |
| 68 | + |
| 69 | + SetLastError(0xBEEFCAFE); |
| 70 | + params.cbSize = 0; |
| 71 | + ret = TrackPopupMenuEx(hMenu, uFlags, 0, 0, hwnd, ¶ms); |
| 72 | + ok_int(ret, FALSE); |
| 73 | + ok_err(ERROR_INVALID_PARAMETER); |
| 74 | + |
| 75 | + SetLastError(0xBEEFCAFE); |
| 76 | + params.cbSize = sizeof(params) - 1; |
| 77 | + ret = TrackPopupMenuEx(hMenu, uFlags, 0, 0, hwnd, ¶ms); |
| 78 | + ok_int(ret, FALSE); |
| 79 | + ok_err(ERROR_INVALID_PARAMETER); |
| 80 | + |
| 81 | + SetLastError(0xBEEFCAFE); |
| 82 | + params.cbSize = sizeof(params) + 1; |
| 83 | + ret = TrackPopupMenuEx(hMenu, uFlags, 0, 0, hwnd, ¶ms); |
| 84 | + ok_int(ret, FALSE); |
| 85 | + ok_err(ERROR_INVALID_PARAMETER); |
| 86 | + |
| 87 | + DestroyMenu(hMenu); |
| 88 | +} |
| 89 | + |
| 90 | +#define DELAY 100 |
| 91 | +#define INTERVAL 300 |
| 92 | + |
| 93 | +typedef enum tagAUTO_CLICK |
| 94 | +{ |
| 95 | + AUTO_LEFT_CLICK, |
| 96 | + AUTO_RIGHT_CLICK, |
| 97 | + AUTO_LEFT_DOUBLE_CLICK, |
| 98 | + AUTO_RIGHT_DOUBLE_CLICK, |
| 99 | +} AUTO_CLICK; |
| 100 | + |
| 101 | +typedef enum tagAUTO_KEY |
| 102 | +{ |
| 103 | + AUTO_KEY_DOWN, |
| 104 | + AUTO_KEY_UP, |
| 105 | + AUTO_KEY_DOWN_UP, |
| 106 | +} AUTO_KEY; |
| 107 | + |
| 108 | +static VOID |
| 109 | +AutoKey(AUTO_KEY type, UINT vKey) |
| 110 | +{ |
| 111 | + if (type == AUTO_KEY_DOWN_UP) |
| 112 | + { |
| 113 | + AutoKey(AUTO_KEY_DOWN, vKey); |
| 114 | + AutoKey(AUTO_KEY_UP, vKey); |
| 115 | + return; |
| 116 | + } |
| 117 | + |
| 118 | + INPUT input; |
| 119 | + ZeroMemory(&input, sizeof(input)); |
| 120 | + |
| 121 | + input.type = INPUT_KEYBOARD; |
| 122 | + input.ki.wVk = vKey; |
| 123 | + input.ki.dwFlags = ((type == AUTO_KEY_UP) ? KEYEVENTF_KEYUP : 0); |
| 124 | + SendInput(1, &input, sizeof(INPUT)); |
| 125 | + Sleep(DELAY); |
| 126 | +} |
| 127 | + |
| 128 | +static VOID |
| 129 | +AutoClick(AUTO_CLICK type, INT x, INT y) |
| 130 | +{ |
| 131 | + INPUT input; |
| 132 | + ZeroMemory(&input, sizeof(input)); |
| 133 | + |
| 134 | + INT nScreenWidth = GetSystemMetrics(SM_CXSCREEN) - 1; |
| 135 | + INT nScreenHeight = GetSystemMetrics(SM_CYSCREEN) - 1; |
| 136 | + |
| 137 | + input.type = INPUT_MOUSE; |
| 138 | + input.mi.dx = (LONG)(x * (65535.0f / nScreenWidth)); |
| 139 | + input.mi.dy = (LONG)(y * (65535.0f / nScreenHeight)); |
| 140 | + input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; |
| 141 | + SendInput(1, &input, sizeof(INPUT)); |
| 142 | + Sleep(DELAY); |
| 143 | + |
| 144 | + input.mi.dx = input.mi.dy = 0; |
| 145 | + |
| 146 | + INT i, count = 1; |
| 147 | + switch (type) |
| 148 | + { |
| 149 | + case AUTO_LEFT_DOUBLE_CLICK: |
| 150 | + count = 2; |
| 151 | + // FALL THROUGH |
| 152 | + case AUTO_LEFT_CLICK: |
| 153 | + { |
| 154 | + for (i = 0; i < count; ++i) |
| 155 | + { |
| 156 | + input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; |
| 157 | + SendInput(1, &input, sizeof(INPUT)); |
| 158 | + Sleep(DELAY); |
| 159 | + |
| 160 | + input.mi.dwFlags = MOUSEEVENTF_LEFTUP; |
| 161 | + SendInput(1, &input, sizeof(INPUT)); |
| 162 | + Sleep(DELAY); |
| 163 | + } |
| 164 | + break; |
| 165 | + } |
| 166 | + case AUTO_RIGHT_DOUBLE_CLICK: |
| 167 | + count = 2; |
| 168 | + // FALL THROUGH |
| 169 | + case AUTO_RIGHT_CLICK: |
| 170 | + { |
| 171 | + for (i = 0; i < count; ++i) |
| 172 | + { |
| 173 | + input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; |
| 174 | + SendInput(1, &input, sizeof(INPUT)); |
| 175 | + Sleep(DELAY); |
| 176 | + |
| 177 | + input.mi.dwFlags = MOUSEEVENTF_RIGHTUP; |
| 178 | + SendInput(1, &input, sizeof(INPUT)); |
| 179 | + Sleep(DELAY); |
| 180 | + } |
| 181 | + break; |
| 182 | + } |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +static POINT |
| 187 | +CenterPoint(const RECT *prc) |
| 188 | +{ |
| 189 | + POINT pt = { (prc->left + prc->right) / 2, (prc->top + prc->bottom) / 2 }; |
| 190 | + return pt; |
| 191 | +} |
| 192 | + |
| 193 | +typedef struct tagCOUNTMENUWND |
| 194 | +{ |
| 195 | + INT nMenuCount; |
| 196 | +} COUNTMENUWND, *PCOUNTMENUWND; |
| 197 | + |
| 198 | +static BOOL CALLBACK |
| 199 | +CountMenuWndProc(HWND hwnd, LPARAM lParam) |
| 200 | +{ |
| 201 | + if (!IsWindowVisible(hwnd)) |
| 202 | + return TRUE; |
| 203 | + |
| 204 | + WCHAR szClass[64]; |
| 205 | + GetClassNameW(hwnd, szClass, _countof(szClass)); |
| 206 | + if (lstrcmpiW(szClass, MENUCLASS) != 0) |
| 207 | + return TRUE; |
| 208 | + |
| 209 | + PCOUNTMENUWND pData = (PCOUNTMENUWND)lParam; |
| 210 | + pData->nMenuCount += 1; |
| 211 | + return TRUE; |
| 212 | +} |
| 213 | + |
| 214 | +static INT |
| 215 | +CountMenuWnds(VOID) |
| 216 | +{ |
| 217 | + COUNTMENUWND data = { 0 }; |
| 218 | + EnumWindows(CountMenuWndProc, (LPARAM)&data); |
| 219 | + return data.nMenuCount; |
| 220 | +} |
| 221 | + |
| 222 | +static DWORD WINAPI |
| 223 | +TEST_Tracking_ThreadFunc(LPVOID arg) |
| 224 | +{ |
| 225 | + HWND hwnd = (HWND)arg; |
| 226 | + |
| 227 | + ok_int(CountMenuWnds(), 0); |
| 228 | + |
| 229 | + RECT rc; |
| 230 | + GetWindowRect(hwnd, &rc); |
| 231 | + POINT pt = CenterPoint(&rc); |
| 232 | + |
| 233 | + AutoClick(AUTO_RIGHT_CLICK, pt.x, pt.y); |
| 234 | + Sleep(INTERVAL); |
| 235 | + |
| 236 | + ok_int(CountMenuWnds(), 1); |
| 237 | + |
| 238 | + AutoKey(AUTO_KEY_DOWN_UP, VK_DOWN); |
| 239 | + Sleep(INTERVAL); |
| 240 | + |
| 241 | + ok_int(CountMenuWnds(), 1); |
| 242 | + |
| 243 | + AutoKey(AUTO_KEY_DOWN_UP, VK_RETURN); |
| 244 | + Sleep(INTERVAL); |
| 245 | + |
| 246 | + ok_int(CountMenuWnds(), 0); |
| 247 | + |
| 248 | + PostMessageW(hwnd, WM_CLOSE, 0, 0); |
| 249 | + return 0; |
| 250 | +} |
| 251 | + |
| 252 | +static VOID |
| 253 | +OnRButtonDown(HWND hwnd) |
| 254 | +{ |
| 255 | + SetForegroundWindow(hwnd); |
| 256 | + |
| 257 | + POINT pt; |
| 258 | + GetCursorPos(&pt); |
| 259 | + |
| 260 | + HMENU hMenu = CreatePopupMenu(); |
| 261 | + BOOL ret = AppendMenuW(hMenu, MF_STRING, 100, L"(Dummy)"); |
| 262 | + ok_int(ret, TRUE); |
| 263 | + |
| 264 | + UINT uFlags = TPM_RIGHTBUTTON | TPM_RETURNCMD; |
| 265 | + INT nCmdID = (INT)TrackPopupMenuEx(hMenu, uFlags, pt.x, pt.y, hwnd, NULL); |
| 266 | + |
| 267 | + ok_int(nCmdID, 100); |
| 268 | + |
| 269 | + DestroyMenu(hMenu); |
| 270 | +} |
| 271 | + |
| 272 | +static LRESULT CALLBACK |
| 273 | +WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) |
| 274 | +{ |
| 275 | + switch (uMsg) |
| 276 | + { |
| 277 | + case WM_CREATE: |
| 278 | + return 0; |
| 279 | + case WM_RBUTTONDOWN: |
| 280 | + OnRButtonDown(hwnd); |
| 281 | + break; |
| 282 | + case WM_DESTROY: |
| 283 | + PostQuitMessage(0); |
| 284 | + break; |
| 285 | + default: |
| 286 | + return DefWindowProcW(hwnd, uMsg, wParam, lParam); |
| 287 | + } |
| 288 | + return 0; |
| 289 | +} |
| 290 | + |
| 291 | +static VOID |
| 292 | +TEST_Tracking(VOID) |
| 293 | +{ |
| 294 | + HINSTANCE hInstance = GetModuleHandleW(NULL);; |
| 295 | + |
| 296 | + WNDCLASSW wc = { 0, WindowProc }; |
| 297 | + wc.hInstance = hInstance; |
| 298 | + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); |
| 299 | + wc.hCursor = LoadCursor(NULL, IDC_ARROW); |
| 300 | + wc.hbrBackground = (HBRUSH)UlongToHandle(COLOR_3DFACE + 1); |
| 301 | + wc.lpszClassName = CLASSNAME; |
| 302 | + if (!RegisterClassW(&wc)) |
| 303 | + { |
| 304 | + skip("RegisterClassW failed\n"); |
| 305 | + return; |
| 306 | + } |
| 307 | + |
| 308 | + DWORD style = WS_OVERLAPPEDWINDOW; |
| 309 | + HWND hwnd = CreateWindowW(CLASSNAME, CLASSNAME, style, |
| 310 | + 0, 0, 320, 200, NULL, NULL, hInstance, NULL); |
| 311 | + if (!hwnd) |
| 312 | + { |
| 313 | + skip("CreateWindowW failed\n"); |
| 314 | + return; |
| 315 | + } |
| 316 | + |
| 317 | + ShowWindow(hwnd, SW_SHOWNORMAL); |
| 318 | + UpdateWindow(hwnd); |
| 319 | + |
| 320 | + HANDLE hThread = CreateThread(NULL, 0, TEST_Tracking_ThreadFunc, hwnd, 0, NULL); |
| 321 | + if (!hThread) |
| 322 | + { |
| 323 | + skip("CreateThread failed\n"); |
| 324 | + DestroyWindow(hwnd); |
| 325 | + return; |
| 326 | + } |
| 327 | + CloseHandle(hThread); |
| 328 | + |
| 329 | + MSG msg; |
| 330 | + while (GetMessageW(&msg, NULL, 0, 0)) |
| 331 | + { |
| 332 | + TranslateMessage(&msg); |
| 333 | + DispatchMessageW(&msg); |
| 334 | + } |
| 335 | +} |
| 336 | + |
| 337 | +START_TEST(TrackPopupMenuEx) |
| 338 | +{ |
| 339 | + TEST_InvalidFlags(); |
| 340 | + TEST_InvalidSize(); |
| 341 | + TEST_Tracking(); |
| 342 | +} |
0 commit comments