/* * PROJECT: ReactOS api tests * LICENSE: GPL - See COPYING in the top level directory * PURPOSE: Test for AttachThreadInput * PROGRAMMERS: Giannis Adamopoulos */ #include #include #include #include "helper.h" #define DESKTOP_ALL_ACCESS 0x01ff typedef struct { DWORD tid; HANDLE hThread; HWND hWnd; WCHAR* Desktop; HANDLE StartEvent; HANDLE QueueStatusEvent; DWORD LastQueueStatus; MSG_CACHE cache; } THREAD_DATA; DWORD tidMouseMove; THREAD_DATA data[6]; HHOOK hMouseHookLL = NULL; HHOOK hKbdHookLL = NULL; #define EXPECT_FOREGROUND(expected) ok(GetForegroundWindow() == expected, \ "Expected hwnd%d at the foreground, got hwnd%d\n", \ get_iwnd(expected), get_iwnd(GetForegroundWindow())); #define EXPECT_ACTIVE(expected) ok(GetActiveWindow() == expected, \ "Expected hwnd%d to be active, got hwnd%d\n", \ get_iwnd(expected), get_iwnd(GetActiveWindow())); /* * Helper functions */ static int get_iwnd(HWND hWnd) { if(hWnd == data[0].hWnd) return 0; else if(hWnd == data[1].hWnd) return 1; else if(hWnd == data[2].hWnd) return 2; else if(hWnd == data[3].hWnd) return 3; else if(hWnd == data[4].hWnd) return 4; else return -1; } LRESULT CALLBACK TestProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int iwnd = get_iwnd(hWnd); if(iwnd >= 0 && message > 0 && message < WM_APP && message != WM_TIMER) record_message(&data[iwnd].cache, iwnd, message, SENT, wParam,0); return DefWindowProc(hWnd, message, wParam, lParam); } static void FlushMessages() { MSG msg; LRESULT res; while (PeekMessage( &msg, 0, 0, 0, PM_REMOVE )) { int iwnd = get_iwnd(msg.hwnd); if( iwnd >= 0 && msg.message > 0 && msg.message < WM_APP && msg.message != WM_TIMER) record_message(&data[0].cache, iwnd, msg.message, POST, msg.wParam,0); DispatchMessageA( &msg ); } /* Use SendMessage to sync with the other queues */ res = SendMessageTimeout(data[1].hWnd, WM_APP, 0,0, SMTO_NORMAL, 1000, NULL); ok (res != ERROR_TIMEOUT, "SendMessageTimeout timed out\n"); res = SendMessageTimeout(data[2].hWnd, WM_APP, 0,0, SMTO_NORMAL, 1000, NULL); ok (res != ERROR_TIMEOUT, "SendMessageTimeout timed out\n"); res = SendMessageTimeout(data[3].hWnd, WM_APP, 0,0, SMTO_NORMAL, 1000, NULL); ok (res != ERROR_TIMEOUT, "SendMessageTimeout timed out\n"); res = SendMessageTimeout(data[4].hWnd, WM_APP, 0,0, SMTO_NORMAL, 1000, NULL); ok (res != ERROR_TIMEOUT, "SendMessageTimeout timed out\n"); } static DWORD WINAPI thread_proc(void *param) { THREAD_DATA* current_data = (THREAD_DATA*)param; MSG msg; HDESK hdesk = NULL; int iwnd; if(current_data->Desktop) { hdesk = CreateDesktopW(current_data->Desktop, NULL, NULL, 0, DESKTOP_ALL_ACCESS, NULL ); SetThreadDesktop(hdesk); } /* create test window */ current_data->hWnd = CreateWindowW(L"TestClass", L"test", WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, 0, NULL); SetEvent( current_data->StartEvent ); iwnd = get_iwnd(current_data->hWnd); /* Use MsgWaitForMultipleObjects to let the thread process apcs */ while( GetMessage(&msg, 0,0,0) ) { if(msg.message > 0 && msg.message < WM_APP && msg.message != WM_TIMER ) record_message(&data[iwnd].cache, iwnd, msg.message, POST, msg.wParam,0); DispatchMessage(&msg); } if(hdesk) CloseDesktop(hdesk); return 0; } BOOL CreateTestThread(int i, WCHAR* Desktop) { DWORD ret; data[i].StartEvent = CreateEventW(NULL, 0, 0, NULL); data[i].Desktop = Desktop; data[i].hThread = CreateThread(NULL, 0, thread_proc, &data[i], 0, &data[i].tid); if(!data[i].hThread) goto fail; ret = WaitForSingleObject(data[i].StartEvent, 1000); CloseHandle(data[i].StartEvent); if(ret == WAIT_TIMEOUT) { fail: win_skip("child thread failed to initialize\n"); return FALSE; } return TRUE; } static LRESULT CALLBACK MouseLLHookProc(int nCode, WPARAM wParam, LPARAM lParam) { LRESULT ret; MSLLHOOKSTRUCT* params = (MSLLHOOKSTRUCT*) lParam; ret = CallNextHookEx(hMouseHookLL, nCode, wParam, lParam); if((params->flags & LLKHF_INJECTED) == 0) return TRUE; return ret; } LRESULT CALLBACK KbdLLHookProc(int nCode, WPARAM wParam, LPARAM lParam) { LRESULT ret; KBDLLHOOKSTRUCT* params = (KBDLLHOOKSTRUCT*) lParam; ret = CallNextHookEx(hMouseHookLL, nCode, wParam, lParam); if((params->flags & LLKHF_INJECTED) == 0) return TRUE; return ret; } BOOLEAN InitThreads() { /* Create a LL hook that drops any physical keyboard and mouse action and prevent the user from interfering with the test results */ if(!IsDebuggerPresent()) { hMouseHookLL = SetWindowsHookExW(WH_MOUSE_LL, MouseLLHookProc, GetModuleHandleW( NULL ), 0); ok(hMouseHookLL!=NULL,"failed to set hook\n"); hKbdHookLL = SetWindowsHookExW(WH_KEYBOARD_LL, KbdLLHookProc, GetModuleHandleW( NULL ), 0); ok(hKbdHookLL!=NULL,"failed to set hook\n"); } /* create test clases */ RegisterSimpleClass(TestProc, L"TestClass"); memset(&data[0], 0, sizeof(data[0])); data[0].tid = GetCurrentThreadId(); /* create test window */ data[0].hWnd = CreateWindowW(L"TestClass", L"test", WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, 0, NULL); if(!data[0].hWnd) { win_skip("CreateWindowW failed\n"); return FALSE; } /* create thread1(same desktop) */ if(!CreateTestThread(1, NULL)) return FALSE; /* create thread2(same desktop) */ if(!CreateTestThread(2, NULL)) return FALSE; /* ugly ros hack to bypass desktop crapiness */ if(!CreateTestThread(6, L"ThreadTestDesktop")) return FALSE; /* create thread3(different desktop) */ if(!CreateTestThread(3, L"ThreadTestDesktop")) return FALSE; /* create thread4(different desktop) */ if(!CreateTestThread(4, L"ThreadTestDesktop")) return FALSE; return TRUE; } static void cleanup_attachments() { int i,j; BOOL ret; for(i = 0; i< 4; i++); { for(j = 0; j< 4; j++); { ret = AttachThreadInput(data[i].tid,data[j].tid, FALSE); ok(ret==0, "expected AttachThreadInput to fail\n"); } } } /* * The actual tests */ void Test_SimpleParameters() { BOOL ret; /* FIXME: acording to msdn xp doesn't set last error but vista+ does*/ /* test wrong thread */ ret = AttachThreadInput( 0, 1, TRUE); ok(ret==0, "expected AttachThreadInput to fail\n"); /* test same thread */ ret = AttachThreadInput( data[1].tid, data[1].tid, TRUE); ok(ret==0, "expected AttachThreadInput to fail\n"); /* try to attach to a thread on another desktop*/ ret = AttachThreadInput( data[2].tid,data[3].tid, TRUE); ok(ret==0, "expected AttachThreadInput to fail\n"); if(ret == 1 ) AttachThreadInput( data[2].tid,data[3].tid, FALSE); /* test other desktop to this */ ret = AttachThreadInput( data[3].tid,data[2].tid, TRUE); ok(ret==0, "expected AttachThreadInput to fail\n"); if(ret == 1 ) AttachThreadInput( data[3].tid,data[2].tid, FALSE); /* attach two threads that are both in ThreadTestDesktop */ { /* Attach thread 3 and 4 */ ret = AttachThreadInput( data[3].tid,data[4].tid, TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); /* cleanup previous attachment */ ret = AttachThreadInput( data[3].tid,data[4].tid, FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); } { /* Attach thread 1 and 2 */ ret = AttachThreadInput( data[1].tid,data[2].tid, TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); /* attach already attached*/ ret = AttachThreadInput( data[1].tid,data[2].tid, TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); /* attach in the opposite order */ ret = AttachThreadInput( data[2].tid,data[1].tid, TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); /* Now try to detach 0 from 1 */ ret = AttachThreadInput( data[0].tid,data[1].tid, FALSE); ok(ret==0, "expected AttachThreadInput to fail\n"); /* also try to detach 3 from 2 */ ret = AttachThreadInput( data[3].tid,data[2].tid, FALSE); ok(ret==0, "expected AttachThreadInput to fail\n"); /* cleanup previous attachment */ ret = AttachThreadInput( data[1].tid,data[2].tid, FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); ret = AttachThreadInput( data[2].tid,data[1].tid, FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); ret = AttachThreadInput( data[1].tid,data[2].tid, FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); } /* test triple attach */ { ret = AttachThreadInput( data[0].tid, data[1].tid, TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); ret = AttachThreadInput( data[1].tid, data[2].tid, TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); /* try to detach 2 and 0 */ ret = AttachThreadInput( data[0].tid, data[2].tid, FALSE); ok(ret==0, "expected AttachThreadInput to fail\n"); ret = AttachThreadInput( data[2].tid, data[0].tid, FALSE); ok(ret==0, "expected AttachThreadInput to fail\n"); /* try to to attach 0 to 2. it works! */ ret = AttachThreadInput( data[0].tid, data[2].tid, TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); ret = AttachThreadInput( data[0].tid, data[2].tid, FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); /* detach in inverse order */ ret = AttachThreadInput( data[0].tid, data[1].tid, FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); ret = AttachThreadInput( data[1].tid, data[2].tid, FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); } /* test detaching in thread cleanup */ { ret = AttachThreadInput( data[0].tid, data[1].tid, TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); ret = AttachThreadInput( data[0].tid, data[1].tid, TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); ret = AttachThreadInput( data[1].tid, data[2].tid, TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); ret = AttachThreadInput( data[1].tid, data[2].tid, TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); TerminateThread(data[1].hThread, 0); ret = AttachThreadInput( data[0].tid, data[1].tid, FALSE); ok(ret==0, "expected AttachThreadInput to fail\n"); ret = AttachThreadInput( data[1].tid, data[2].tid, FALSE); ok(ret==0, "expected AttachThreadInput to fail\n"); /* Create Thread1 again */ CreateTestThread(1, NULL); } } void Test_Focus() //Focus Active Capture Foreground Capture { BOOL ret; trace("Thread hWnd0 0x%p hWnd1 0x%p\n",data[0].hWnd, data[1].hWnd); /* Window 1 is in the foreground */ SetForegroundWindow(data[1].hWnd); SetActiveWindow(data[0].hWnd); FlushMessages(); EXPECT_FOREGROUND(data[1].hWnd); EXPECT_ACTIVE(data[0].hWnd); /* attach thread 0 to 1 */ { ret = AttachThreadInput( data[0].tid, data[1].tid , TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); FlushMessages(); EXPECT_FOREGROUND(data[1].hWnd); EXPECT_ACTIVE(data[1].hWnd); ret = AttachThreadInput( data[0].tid, data[1].tid , FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); } EXPECT_FOREGROUND(data[1].hWnd); EXPECT_ACTIVE(0); SetForegroundWindow(data[1].hWnd); SetActiveWindow(data[0].hWnd); FlushMessages(); EXPECT_FOREGROUND(data[1].hWnd); EXPECT_ACTIVE(data[0].hWnd); /* attach thread 1 to 0 */ { ret = AttachThreadInput( data[1].tid, data[0].tid , TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); FlushMessages(); EXPECT_FOREGROUND(data[1].hWnd); EXPECT_ACTIVE(data[1].hWnd); ret = AttachThreadInput( data[1].tid, data[0].tid , FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); } /* Window 0 is in the foreground */ SetForegroundWindow(data[0].hWnd); SetActiveWindow(data[1].hWnd); FlushMessages(); EXPECT_FOREGROUND(data[0].hWnd); EXPECT_ACTIVE(data[0].hWnd); /* attach thread 0 to 1 */ { ret = AttachThreadInput( data[0].tid, data[1].tid , TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); FlushMessages(); EXPECT_FOREGROUND(data[0].hWnd); EXPECT_ACTIVE(data[0].hWnd); SetForegroundWindow(data[0].hWnd); SetActiveWindow(data[1].hWnd); FlushMessages(); EXPECT_FOREGROUND(data[1].hWnd); EXPECT_ACTIVE(data[1].hWnd); ret = AttachThreadInput( data[0].tid, data[1].tid , FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); } EXPECT_FOREGROUND(data[1].hWnd); EXPECT_ACTIVE(0); SetForegroundWindow(data[0].hWnd); SetActiveWindow(data[1].hWnd); FlushMessages(); EXPECT_FOREGROUND(data[0].hWnd); EXPECT_ACTIVE(data[0].hWnd); /* attach thread 1 to 0 */ { ret = AttachThreadInput( data[1].tid, data[0].tid , TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); FlushMessages(); EXPECT_FOREGROUND(data[0].hWnd); EXPECT_ACTIVE(data[0].hWnd); SetForegroundWindow(data[0].hWnd); SetActiveWindow(data[1].hWnd); FlushMessages(); EXPECT_FOREGROUND(data[1].hWnd); EXPECT_ACTIVE(data[1].hWnd); ret = AttachThreadInput( data[1].tid, data[0].tid , FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); } } /* test some functions like PostMessage and SendMessage that shouldn't be affected */ void Test_UnaffectedMessages() { BOOL ret; LRESULT res; EMPTY_CACHE_(&data[0].cache); EMPTY_CACHE_(&data[1].cache); /* test that messages posted before and after attachment are unaffected and that we don't receive a meassage from a window we shouldn't */ PostMessage(data[0].hWnd, WM_USER, 0,0); PostMessage(data[1].hWnd, WM_USER, 1,0); { MSG_ENTRY Thread0_chain[]={ {0,WM_USER, POST, 0, 0}, {0,WM_USER, POST, 2, 0}, {0,0}}; MSG_ENTRY Thread1_chain[]={ {1,WM_USER, POST, 1, 0}, {1,WM_USER, POST, 3, 0}, {0,0}}; ret = AttachThreadInput( data[1].tid, data[0].tid , TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); PostMessage(data[0].hWnd, WM_USER, 2,0); PostMessage(data[1].hWnd, WM_USER, 3,0); FlushMessages(); Sleep(100); COMPARE_CACHE_(&data[0].cache, Thread0_chain); COMPARE_CACHE_(&data[1].cache, Thread1_chain); ret = AttachThreadInput( data[1].tid, data[0].tid , FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); } /* test messages send to the wrong thread */ res = SendMessageTimeout(data[0].hWnd, WM_USER, 0,0, SMTO_NORMAL, 1000, NULL); ok (res != ERROR_TIMEOUT, "SendMessageTimeout timed out\n"); res = SendMessageTimeout(data[1].hWnd, WM_USER, 1,0, SMTO_NORMAL, 1000, NULL); ok (res != ERROR_TIMEOUT, "SendMessageTimeout timed out\n"); { MSG_ENTRY Thread0_chain[]={ {0,WM_USER, SENT, 0, 0}, {0,WM_USER, SENT, 2, 0}, {0,0}}; MSG_ENTRY Thread1_chain[]={ {1,WM_USER, SENT, 1, 0}, {1,WM_USER, SENT, 3, 0}, {1,WM_MOUSEMOVE, SENT, 0, 0}, {0,0}}; ret = AttachThreadInput( data[2].tid, data[1].tid , TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); res = SendMessageTimeout(data[0].hWnd, WM_USER, 2,0, SMTO_NORMAL, 1000, NULL); ok (res != ERROR_TIMEOUT, "SendMessageTimeout timed out\n"); res = SendMessageTimeout(data[1].hWnd, WM_USER, 3,0, SMTO_NORMAL, 1000, NULL); ok (res != ERROR_TIMEOUT, "SendMessageTimeout timed out\n"); /* Try to send a fake input message */ res = SendMessageTimeout(data[1].hWnd, WM_MOUSEMOVE, 0,0, SMTO_NORMAL, 1000, NULL); ok (res != ERROR_TIMEOUT, "SendMessageTimeout timed out\n"); COMPARE_CACHE_(&data[0].cache, Thread0_chain); COMPARE_CACHE_(&data[1].cache, Thread1_chain); ret = AttachThreadInput( data[2].tid, data[1].tid , FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); } /* todo: test keyboard layout that shouldn't be affected */ } void Test_SendInput() { MSG_ENTRY Thread1_chain[]={ {1,WM_KEYDOWN, POST, VK_SHIFT, 0}, {1,WM_KEYUP, POST, VK_SHIFT, 0}, {0,0}}; MSG_ENTRY Thread0_chain[]={ {0,WM_KEYDOWN, POST, VK_SHIFT, 0}, {0,WM_KEYUP, POST, VK_SHIFT, 0}, {0,0}}; BOOL ret; //trace("Thread hWnd0 0x%p hWnd1 0x%p\n",data[0].hWnd, data[1].hWnd); /* First try sending input without attaching. It will go to the foreground */ { SetForegroundWindow(data[1].hWnd); SetActiveWindow(data[0].hWnd); ok(GetForegroundWindow() == data[1].hWnd, "wrong foreground got 0x%p\n",GetForegroundWindow()); ok(GetActiveWindow() == data[0].hWnd, "wrong active got 0x%p\n",GetActiveWindow()); FlushMessages(); EMPTY_CACHE_(&data[0].cache); EMPTY_CACHE_(&data[1].cache); keybd_event(VK_SHIFT, 0,0,0); keybd_event(VK_SHIFT, 0,KEYEVENTF_KEYUP,0); Sleep(100); FlushMessages(); COMPARE_CACHE_(&data[0].cache, empty_chain); COMPARE_CACHE_(&data[1].cache, Thread1_chain); } /* Next attach and send input. It will go to the same thread as before */ { // from to ret = AttachThreadInput( data[1].tid, data[0].tid , TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); FlushMessages(); EMPTY_CACHE_(&data[0].cache); EMPTY_CACHE_(&data[1].cache); keybd_event(VK_SHIFT, 0,0,0); keybd_event(VK_SHIFT, 0,KEYEVENTF_KEYUP,0); Sleep(100); FlushMessages(); COMPARE_CACHE_(&data[0].cache, empty_chain); COMPARE_CACHE_(&data[1].cache, Thread1_chain); } /* Now set foreground and active again. Input will go to thread 0 */ { SetForegroundWindow(data[1].hWnd); SetActiveWindow(data[0].hWnd); FlushMessages(); ok(GetForegroundWindow() == data[0].hWnd, "wrong foreground got 0x%p\n",GetForegroundWindow()); ok(GetActiveWindow() == data[0].hWnd, "wrong active got 0x%p\n",GetActiveWindow()); EMPTY_CACHE_(&data[0].cache); EMPTY_CACHE_(&data[1].cache); keybd_event(VK_SHIFT, 0,0,0); keybd_event(VK_SHIFT, 0,KEYEVENTF_KEYUP,0); Sleep(100); FlushMessages(); COMPARE_CACHE_(&data[0].cache, Thread0_chain); COMPARE_CACHE_(&data[1].cache, empty_chain); ret = AttachThreadInput( data[1].tid, data[0].tid , FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); } /* Attach in the opposite order and send input */ { ret = AttachThreadInput( data[0].tid, data[1].tid , TRUE); ok(ret==1, "expected AttachThreadInput to succeed\n"); FlushMessages(); EMPTY_CACHE_(&data[0].cache); EMPTY_CACHE_(&data[1].cache); keybd_event(VK_SHIFT, 0,0,0); keybd_event(VK_SHIFT, 0,KEYEVENTF_KEYUP,0); Sleep(100); FlushMessages(); COMPARE_CACHE_(&data[0].cache, Thread0_chain); COMPARE_CACHE_(&data[1].cache, empty_chain); } /* Now set foreground and active again. Input will go to thread 0 */ { SetForegroundWindow(data[1].hWnd); SetActiveWindow(data[0].hWnd); FlushMessages(); ok(GetForegroundWindow() == data[0].hWnd, "wrong foreground got 0x%p\n",GetForegroundWindow()); ok(GetActiveWindow() == data[0].hWnd, "wrong active got 0x%p\n",GetActiveWindow()); EMPTY_CACHE_(&data[0].cache); EMPTY_CACHE_(&data[1].cache); keybd_event(VK_SHIFT, 0,0,0); keybd_event(VK_SHIFT, 0,KEYEVENTF_KEYUP,0); Sleep(100); FlushMessages(); COMPARE_CACHE_(&data[0].cache, Thread0_chain); COMPARE_CACHE_(&data[1].cache, empty_chain); ret = AttachThreadInput( data[0].tid, data[1].tid , FALSE); ok(ret==1, "expected AttachThreadInput to succeed\n"); } } START_TEST(AttachThreadInput) { if(!InitThreads()) return; Test_SimpleParameters(); cleanup_attachments(); Test_Focus(); cleanup_attachments(); Test_UnaffectedMessages(); cleanup_attachments(); Test_SendInput(); cleanup_attachments(); if(hMouseHookLL) UnhookWindowsHookEx(hMouseHookLL); if(hKbdHookLL) UnhookWindowsHookEx(hKbdHookLL); /* Stop all threads and exit gratefully */ PostThreadMessage(data[1].tid, WM_QUIT,0,0); PostThreadMessage(data[2].tid, WM_QUIT,0,0); PostThreadMessage(data[3].tid, WM_QUIT,0,0); PostThreadMessage(data[4].tid, WM_QUIT,0,0); }