It looks like that GetKeyState is supposed to catch key presses when called from a background thread even after its thread message queue and thread input structures have been created.
This happens in Bioshock 2 Remaster, and causes the game to stay forever on a "Press space to continue" screen, as the thread checking for space key press calls GetKeyState repeatedly (and PeekMessage), although it's in background.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=26269 Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/user32/tests/input.c | 132 ++++++++++++++++++++++++++++---------- 1 file changed, 98 insertions(+), 34 deletions(-)
diff --git a/dlls/user32/tests/input.c b/dlls/user32/tests/input.c index 9523401988d..396a39a06c9 100644 --- a/dlls/user32/tests/input.c +++ b/dlls/user32/tests/input.c @@ -3715,74 +3715,138 @@ static void test_attach_input(void) DestroyWindow(Wnd2); }
+struct get_key_state_test_desc +{ + BOOL peek_message_init; + BOOL peek_message_main; + BOOL peek_message_thread; + BOOL create_window; + BOOL expect, todo; +}; + +struct get_key_state_test_desc get_key_state_tests[] = +{ + /* 0: without a message queue: GetKeyState miss key press */ + {FALSE, FALSE, FALSE, FALSE, /* expect: */ FALSE, TRUE}, + /* 1: peeked at least once: GetKeyState catches key press */ + { TRUE, FALSE, FALSE, FALSE, /* expect: */ TRUE, TRUE}, + /* 2: peeking in fgnd thread: GetKeyState catches key press */ + { TRUE, TRUE, FALSE, FALSE, /* expect: */ TRUE, TRUE}, + /* 3: peeking in bgnd thread: GetKeyState catches key press */ + { TRUE, FALSE, TRUE, FALSE, /* expect: */ TRUE, TRUE}, + /* 4: creating window in bgnd but not peeking messages: GetKeyState miss key press */ + {FALSE, FALSE, FALSE, TRUE, /* expect: */ FALSE, FALSE}, + /* 5: creating window in bgnd and peeking bgnd messages: GetKeyState catches key press */ + {FALSE, FALSE, TRUE, TRUE, /* expect: */ TRUE, TRUE}, + /* 6: creating window in bgnd and peeking fgnd messages: GetKeyState miss key press */ + {FALSE, TRUE, FALSE, TRUE, /* expect: */ FALSE, FALSE}, +}; + +struct get_key_state_thread_params +{ + HANDLE semaphores[2]; + int index; +}; + static DWORD WINAPI get_key_state_thread(void *arg) { - HANDLE *semaphores = arg; + struct get_key_state_thread_params *params = arg; + struct get_key_state_test_desc* test; + HANDLE *semaphores = params->semaphores; DWORD result; + HWND hwnd; + MSG msg; + int i = params->index; + + test = get_key_state_tests + i; + if (!test->create_window) hwnd = NULL; + else + { + hwnd = CreateWindowA("static", "Title", WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 10, 10, 200, 200, NULL, NULL, NULL, NULL); + ok(hwnd != NULL, "CreateWindowA failed %u\n", GetLastError()); + empty_message_queue(); + } + if (test->peek_message_init) while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
ReleaseSemaphore(semaphores[0], 1, NULL); result = WaitForSingleObject(semaphores[1], 1000); - ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result); + ok(result == WAIT_OBJECT_0, "%d: WaitForSingleObject returned %u\n", i, result); + if (test->peek_message_thread) while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
result = GetKeyState('X'); - ok((result & 0x8000) || broken(!(result & 0x8000)), /* > Win 2003 */ - "expected that highest bit is set, got %x\n", result); - - ok((SHORT)(result & 0x007e) == 0, - "expected that undefined bits are unset, got %x\n", result); + if (test->expect) + todo_wine ok((result & 0x8000), "%d: expected that highest bit is set, got %#x\n", i, result); + else + todo_wine_if(test->todo) ok(!(result & 0x8000), "%d: expected that highest bit is unset, got %#x\n", i, result); + ok(!(result & 0x007e), "%d: expected that undefined bits are unset, got %#x\n", i, result);
ReleaseSemaphore(semaphores[0], 1, NULL); result = WaitForSingleObject(semaphores[1], 1000); - ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result); + ok(result == WAIT_OBJECT_0, "%d: WaitForSingleObject returned %u\n", i, result);
+ if (test->peek_message_thread) while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessageA(&msg); result = GetKeyState('X'); - ok(!(result & 0x8000), "expected that highest bit is unset, got %x\n", result); - - ok((SHORT)(result & 0x007e) == 0, - "expected that undefined bits are unset, got %x\n", result); + ok(!(result & 0x8000), "%d: expected that highest bit is unset, got %#x\n", i, result); + ok(!(result & 0x007e), "%d: expected that undefined bits are unset, got %#x\n", i, result);
+ if (hwnd) DestroyWindow(hwnd); return 0; }
static void test_GetKeyState(void) { - HANDLE semaphores[2]; + struct get_key_state_thread_params params; HANDLE thread; DWORD result; HWND hwnd; + MSG msg; + int i;
- semaphores[0] = CreateSemaphoreA(NULL, 0, 1, NULL); - ok(semaphores[0] != NULL, "CreateSemaphoreA failed %u\n", GetLastError()); - semaphores[1] = CreateSemaphoreA(NULL, 0, 1, NULL); - ok(semaphores[1] != NULL, "CreateSemaphoreA failed %u\n", GetLastError()); + params.semaphores[0] = CreateSemaphoreA(NULL, 0, 1, NULL); + ok(params.semaphores[0] != NULL, "CreateSemaphoreA failed %u\n", GetLastError()); + params.semaphores[1] = CreateSemaphoreA(NULL, 0, 1, NULL); + ok(params.semaphores[1] != NULL, "CreateSemaphoreA failed %u\n", GetLastError());
hwnd = CreateWindowA("static", "Title", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 10, 10, 200, 200, NULL, NULL, NULL, NULL); ok(hwnd != NULL, "CreateWindowA failed %u\n", GetLastError()); + empty_message_queue();
- thread = CreateThread(NULL, 0, get_key_state_thread, semaphores, 0, NULL); - ok(thread != NULL, "CreateThread failed %u\n", GetLastError()); - result = WaitForSingleObject(semaphores[0], 1000); - ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result); + for (i = 0; i < ARRAY_SIZE(get_key_state_tests); ++i) + { + struct get_key_state_test_desc* test = get_key_state_tests + i; + + params.index = i; + thread = CreateThread(NULL, 0, get_key_state_thread, ¶ms, 0, NULL); + ok(thread != NULL, "CreateThread failed %u\n", GetLastError());
- SetForegroundWindow(hwnd); - SetFocus(hwnd); - keybd_event('X', 0, 0, 0); + result = WaitForSingleObject(params.semaphores[0], 1000); + ok(result == WAIT_OBJECT_0, "%d: WaitForSingleObject returned %u\n", i, result);
- ReleaseSemaphore(semaphores[1], 1, NULL); - result = WaitForSingleObject(semaphores[0], 1000); - ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result); + SetForegroundWindow(hwnd); + SetFocus(hwnd);
- keybd_event('X', 0, KEYEVENTF_KEYUP, 0); + keybd_event('X', 0, 0, 0); + if (test->peek_message_main) while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessageA(&msg);
- ReleaseSemaphore(semaphores[1], 1, NULL); - result = WaitForSingleObject(thread, 1000); - ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result); - CloseHandle(thread); + ReleaseSemaphore(params.semaphores[1], 1, NULL); + result = WaitForSingleObject(params.semaphores[0], 1000); + ok(result == WAIT_OBJECT_0, "%d: WaitForSingleObject returned %u\n", i, result); + + keybd_event('X', 0, KEYEVENTF_KEYUP, 0); + if (test->peek_message_main) while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessageA(&msg); + + ReleaseSemaphore(params.semaphores[1], 1, NULL); + + result = WaitForSingleObject(thread, 1000); + ok(result == WAIT_OBJECT_0, "WaitForSingleObject returned %u\n", result); + CloseHandle(thread); + }
DestroyWindow(hwnd); - CloseHandle(semaphores[0]); - CloseHandle(semaphores[1]); + CloseHandle(params.semaphores[0]); + CloseHandle(params.semaphores[1]); }
static void test_OemKeyScan(void)
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=85133
Your paranoid android.
=== w10pro64 (32 bit report) ===
user32: input.c:1292: Test failed: GetCursorPos: (99,100)
=== w10pro64_ja (64 bit report) ===
user32: input.c:3268: Test failed: expected WM_NCHITTEST message input.c:3269: Test failed: expected WM_RBUTTONDOWN message input.c:3270: Test failed: expected WM_RBUTTONUP message input.c:3299: Test failed: expected WM_LBUTTONDOWN message input.c:3300: Test failed: expected WM_LBUTTONUP message input.c:3353: Test failed: expected loop with WM_NCHITTEST messages input.c:3406: Test failed: expected WM_LBUTTONDOWN message input.c:3407: Test failed: expected WM_LBUTTONUP message input.c:2747: Test failed: 0: expected WM_MOUSEMOVE message input.c:2747: Test failed: 1: expected WM_MOUSEMOVE message input.c:2747: Test failed: 2: expected WM_MOUSEMOVE message
=== w10pro64_zh_CN (64 bit report) ===
user32: input.c:2747: Test failed: 0: expected WM_MOUSEMOVE message input.c:2747: Test failed: 1: expected WM_MOUSEMOVE message input.c:2747: Test failed: 2: expected WM_MOUSEMOVE message
Please, just ignore this, I'll send an updated version.
I think the two cases where GetKeyState is reportedly failing, are actually because the created window steals foreground from the main thread even if the main thread calls SetForegroundWindow/SetFocus right after.
Adding SetForegroundWindow/SetFocus call in the "background" thread to restore focus to the main thread as it's supposed to be, makes GetKeyState calls from this background thread catch the key presses even if that thread doesn't peek its messages.
My understanding is then that GetKeyState returns some global (process-wide?) state, that gets updated by whichever thread is foreground as long as this thread peeks its messages, and regardless of what the other threads are doing.
I'm also not entirely sure that keybd_event behaves exactly as if a user is pressing the keys, but there's some obvious issues with Wine implementation nonetheless.