reactos/modules/rostests/apitests/user32/AttachThreadInput.c
Timo Kreuzer e536256e0a [USER32_APITEST] Remove "ugly ros hack"
This hack corrupts memory and causes test failures on Windows.
2023-04-17 00:41:31 +03:00

680 lines
21 KiB
C

/*
* PROJECT: ReactOS api tests
* LICENSE: GPL - See COPYING in the top level directory
* PURPOSE: Test for AttachThreadInput
* PROGRAMMERS: Giannis Adamopoulos
*/
#include "precomp.h"
typedef struct {
DWORD tid;
HANDLE hThread;
HWND hWnd;
WCHAR* Desktop;
HANDLE StartEvent;
HANDLE QueueStatusEvent;
DWORD LastQueueStatus;
MSG_CACHE cache;
} THREAD_DATA;
static THREAD_DATA data[6];
static HHOOK hMouseHookLL = NULL;
static 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;
/* 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);
}