diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c index 540b49400cd..5c1a9d73653 100644 --- a/dlls/user32/tests/msg.c +++ b/dlls/user32/tests/msg.c @@ -11724,6 +11724,171 @@ static void test_defwinproc(void) DestroyWindow( hwnd); } +#define clear_clipboard(hwnd) clear_clipboard_(__LINE__, (hwnd)) +static void clear_clipboard_(int line, HWND hWnd) +{ + BOOL succ; + succ = OpenClipboard(hWnd); + ok_(__FILE__, line)(succ, "OpenClipboard failed, err=%u\n", GetLastError()); + succ = EmptyClipboard(); + ok_(__FILE__, line)(succ, "EmptyClipboard failed, err=%u\n", GetLastError()); + succ = CloseClipboard(); + ok_(__FILE__, line)(succ, "CloseClipboard failed, err=%u\n", GetLastError()); +} + +#define expect_HWND(expected, got) expect_HWND_(__LINE__, (expected), (got)) +static void expect_HWND_(int line, HWND expected, HWND got) +{ + ok_(__FILE__, line)(got==expected, "Expected %p, got %p\n", expected, got); +} + +static WNDPROC pOldViewerProc; + +static LRESULT CALLBACK recursive_viewer_proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + static BOOL recursion_guard; + + if (message == WM_DRAWCLIPBOARD && !recursion_guard) + { + recursion_guard = TRUE; + clear_clipboard(hWnd); + recursion_guard = FALSE; + } + return CallWindowProcA(pOldViewerProc, hWnd, message, wParam, lParam); +} + +static void test_clipboard_viewers(void) +{ + static struct message wm_change_cb_chain[] = + { + { WM_CHANGECBCHAIN, sent|wparam|lparam, 0, 0 }, + { 0 } + }; + static const struct message wm_clipboard_destroyed[] = + { + { WM_DESTROYCLIPBOARD, sent|wparam|lparam, 0, 0 }, + { 0 } + }; + static struct message wm_clipboard_changed[] = + { + { WM_DRAWCLIPBOARD, sent|wparam|lparam, 0, 0 }, + { 0 } + }; + static struct message wm_clipboard_changed_and_owned[] = + { + { WM_DESTROYCLIPBOARD, sent|wparam|lparam, 0, 0 }, + { WM_DRAWCLIPBOARD, sent|wparam|lparam, 0, 0 }, + { 0 } + }; + + HINSTANCE hInst = GetModuleHandleA(NULL); + HWND hWnd1, hWnd2, hWnd3; + HWND hOrigViewer; + HWND hRet; + + hWnd1 = CreateWindowExA(0, "TestWindowClass", "Clipboard viewer test wnd 1", + WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + GetDesktopWindow(), NULL, hInst, NULL); + hWnd2 = CreateWindowExA(0, "SimpleWindowClass", "Clipboard viewer test wnd 2", + WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + GetDesktopWindow(), NULL, hInst, NULL); + hWnd3 = CreateWindowExA(0, "SimpleWindowClass", "Clipboard viewer test wnd 3", + WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + GetDesktopWindow(), NULL, hInst, NULL); + trace("clipbd viewers: hWnd1=%p, hWnd2=%p, hWnd3=%p\n", hWnd1, hWnd2, hWnd3); + assert(hWnd1 && hWnd2 && hWnd3); + + flush_sequence(); + + /* Test getting the clipboard viewer and setting the viewer to NULL. */ + hOrigViewer = GetClipboardViewer(); + hRet = SetClipboardViewer(NULL); + ok_sequence(WmEmptySeq, "set viewer to NULL", FALSE); + expect_HWND(hOrigViewer, hRet); + expect_HWND(NULL, GetClipboardViewer()); + + /* Test registering hWnd1 as a viewer. */ + hRet = SetClipboardViewer(hWnd1); + wm_clipboard_changed[0].wParam = (WPARAM) GetClipboardOwner(); + ok_sequence(wm_clipboard_changed, "set viewer NULL->1", TRUE); + expect_HWND(NULL, hRet); + expect_HWND(hWnd1, GetClipboardViewer()); + + /* Test that changing the clipboard actually refreshes the registered viewer. */ + clear_clipboard(hWnd1); + wm_clipboard_changed[0].wParam = (WPARAM) GetClipboardOwner(); + ok_sequence(wm_clipboard_changed, "clear clipbd (viewer=owner=1)", TRUE); + + /* Again, but with different owner. */ + clear_clipboard(hWnd2); + wm_clipboard_changed_and_owned[1].wParam = (WPARAM) GetClipboardOwner(); + ok_sequence(wm_clipboard_changed_and_owned, "clear clipbd (viewer=1, owner=2)", TRUE); + + /* Test re-registering same window. */ + hRet = SetClipboardViewer(hWnd1); + wm_clipboard_changed[0].wParam = (WPARAM) GetClipboardOwner(); + ok_sequence(wm_clipboard_changed, "set viewer 1->1", TRUE); + expect_HWND(hWnd1, hRet); + expect_HWND(hWnd1, GetClipboardViewer()); + + /* Test ChangeClipboardChain. */ + ChangeClipboardChain(hWnd2, hWnd3); + wm_change_cb_chain[0].wParam = (WPARAM) hWnd2; + wm_change_cb_chain[0].lParam = (LPARAM) hWnd3; + ok_sequence(wm_change_cb_chain, "change chain (viewer=1, remove=2, next=3)", FALSE); + expect_HWND(hWnd1, GetClipboardViewer()); + + ChangeClipboardChain(hWnd2, NULL); + wm_change_cb_chain[0].wParam = (WPARAM) hWnd2; + wm_change_cb_chain[0].lParam = 0; + ok_sequence(wm_change_cb_chain, "change chain (viewer=1, remove=2, next=NULL)", FALSE); + expect_HWND(hWnd1, GetClipboardViewer()); + + ChangeClipboardChain(NULL, hWnd2); + ok_sequence(WmEmptySeq, "change chain (viewer=1, remove=NULL, next=2)", TRUE); + expect_HWND(hWnd1, GetClipboardViewer()); + + /* Actually change clipboard viewer with ChangeClipboardChain. */ + ChangeClipboardChain(hWnd1, hWnd2); + ok_sequence(WmEmptySeq, "change chain (viewer=remove=1, next=2)", FALSE); + expect_HWND(hWnd2, GetClipboardViewer()); + + /* Test that no refresh messages are sent when viewer has unregistered. */ + clear_clipboard(hWnd2); + ok_sequence(WmEmptySeq, "clear clipd (viewer=2, owner=1)", FALSE); + + /* Register hWnd1 again. */ + ChangeClipboardChain(hWnd2, hWnd1); + ok_sequence(WmEmptySeq, "change chain (viewer=remove=2, next=1)", FALSE); + expect_HWND(hWnd1, GetClipboardViewer()); + + /* Subclass hWnd1 so that when it receives a WM_DRAWCLIPBOARD message, it + * changes the clipboard. When this happens, the system shouldn't send + * another WM_DRAWCLIPBOARD (as this could cause an infinite loop). + */ + pOldViewerProc = (WNDPROC) SetWindowLongPtrA(hWnd1, GWLP_WNDPROC, (LONG_PTR) recursive_viewer_proc); + clear_clipboard(hWnd2); + /* The clipboard owner is changed in recursive_viewer_proc: */ + wm_clipboard_changed[0].wParam = (WPARAM) hWnd2; + ok_sequence(wm_clipboard_changed, "recursive clear clipbd (viewer=1, owner=2)", TRUE); + + /* Test unregistering. */ + ChangeClipboardChain(hWnd1, NULL); + ok_sequence(WmEmptySeq, "change chain (viewer=remove=1, next=NULL)", FALSE); + expect_HWND(NULL, GetClipboardViewer()); + + clear_clipboard(hWnd1); + ok_sequence(wm_clipboard_destroyed, "clear clipbd (no viewer, owner=1)", FALSE); + + DestroyWindow(hWnd1); + DestroyWindow(hWnd2); + DestroyWindow(hWnd3); + SetClipboardViewer(hOrigViewer); +} + static void test_PostMessage(void) { static const struct @@ -11867,6 +12032,7 @@ START_TEST(msg) test_menu_messages(); test_paintingloop(); test_defwinproc(); + test_clipboard_viewers(); /* keep it the last test, under Windows it tends to break the tests * which rely on active/foreground windows being correct. */