-- v4: user32: Pass real argument to NtUserGetClassName in RealGetWindowClass{A/W}. win32u: Add support for retrieving real window class ID across processes. user32: Set real window class ID for user32 standard controls. win32u/tests: Add a test for real window class name retrieval. comctl32/tests: Add tests for RealGetWindowClass. user32/tests: Add tests for RealGetWindowClass.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/user32/tests/class.c | 239 +++++++++++++++++++++++++++++++++++++- 1 file changed, 238 insertions(+), 1 deletion(-)
diff --git a/dlls/user32/tests/class.c b/dlls/user32/tests/class.c index 686fced55ee..47a0ae60c7f 100644 --- a/dlls/user32/tests/class.c +++ b/dlls/user32/tests/class.c @@ -1592,6 +1592,239 @@ static void test_class_name(void) UnregisterClassW(class_name, hinst); }
+#define SUPER_CLASS_NAME_W L"SuperClass Test" +#define SUPER_CLASS_NAME_A "SuperClass Test" +struct real_class_test { + const char *class_name_a; + const char *real_class_name_a; + BOOL test_cross_process; + BOOL wine_todo; +}; + +static const struct real_class_test class_tests[] = { + { "Button", "Button", TRUE, TRUE }, + { "ComboBox", "ComboBox", TRUE, TRUE }, + { "Edit", "Edit", TRUE, TRUE }, + { "ListBox", "ListBox", TRUE, TRUE }, + { "ScrollBar", "ScrollBar", TRUE, TRUE }, + { "Static", "Static", TRUE, TRUE }, + { "ComboLBox", "ListBox", TRUE, TRUE }, + { "MDIClient", "MDIClient", TRUE, TRUE }, + { "#32768", "#32768", TRUE, TRUE }, + { "#32770", "#32770", TRUE, TRUE }, + /* Not all built-in classes set real window class. */ + { "Message", SUPER_CLASS_NAME_A, FALSE, FALSE }, +}; + +static WNDPROC real_class_wndproc; +static LRESULT WINAPI super_class_test_win_proc_w(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (real_class_wndproc) + CallWindowProcW(real_class_wndproc, hwnd, msg, wparam, lparam); + + if (msg == WM_NCCREATE) + return 1; + + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +static LRESULT WINAPI super_class_test_win_proc_a(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (real_class_wndproc) + CallWindowProcA(real_class_wndproc, hwnd, msg, wparam, lparam); + + if (msg == WM_NCCREATE) + return 1; + + return DefWindowProcA(hwnd, msg, wparam, lparam); +} + +#define test_hwnd_real_class_name_str( hwnd, exp_real_class_name_str, todo ) \ + test_hwnd_real_class_name_str_( (hwnd), (exp_real_class_name_str), (todo), __FILE__, __LINE__ ) +static void test_hwnd_real_class_name_str_(HWND hwnd, const char *exp_real_class_name_str, BOOL todo, const char *file, int line) +{ + WCHAR exp_real_class_name_w[256] = { 0 }; + WCHAR real_class_name_w[256] = { 0 }; + char real_class_name_a[256] = { 0 }; + ULONG len; + + len = RealGetWindowClassA(hwnd, real_class_name_a, ARRAY_SIZE(real_class_name_a)); + todo_wine_if(todo) ok(!strcmp(real_class_name_a, exp_real_class_name_str), "got %s\n", real_class_name_a); + todo_wine_if(todo) ok(len == strlen(exp_real_class_name_str), "got %ld, expected %d\n", len, lstrlenA(exp_real_class_name_str)); + + MultiByteToWideChar(CP_ACP, 0, exp_real_class_name_str, -1, exp_real_class_name_w, ARRAY_SIZE(exp_real_class_name_w)); + len = RealGetWindowClassW(hwnd, real_class_name_w, ARRAY_SIZE(real_class_name_w)); + todo_wine_if(todo) ok(!lstrcmpW(real_class_name_w, exp_real_class_name_w), "got %s\n", debugstr_w(real_class_name_w)); + todo_wine_if(todo) ok(len == lstrlenW(exp_real_class_name_w), "got %ld, expected %d\n", len, lstrlenW(exp_real_class_name_w)); +} + +static void test_real_class_name(const struct real_class_test *class_test, BOOL wide_string, int idx) +{ + const CLIENTCREATESTRUCT client_cs = { NULL, 1 }; /* Needed for MDIClient. */ + HWND hwnd; + + if (wide_string) + hwnd = CreateWindowW(SUPER_CLASS_NAME_W, L"test", WS_OVERLAPPED, 0, 0, 50, 50, 0, 0, 0, (void *)&client_cs); + else + hwnd = CreateWindowA(SUPER_CLASS_NAME_A, "test", WS_OVERLAPPED, 0, 0, 50, 50, 0, 0, 0, (void *)&client_cs); + ok(!!hwnd, "hwnd == NULL\n"); + + test_hwnd_real_class_name_str(hwnd, class_test->real_class_name_a, class_test->wine_todo); + if (class_test->test_cross_process) + { + char path_name[MAX_PATH]; + PROCESS_INFORMATION info; + STARTUPINFOA startup; + char **argv; + + winetest_get_mainargs(&argv); + memset(&startup, 0, sizeof(startup)); + startup.cb = sizeof(startup); + sprintf( path_name, "%s %s test_cross_process_RealGetWindowClass %d %#lx", argv[0], argv[1], idx, HandleToUlong(hwnd)); + ok(CreateProcessA(NULL, path_name, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &info), "CreateProcess failed.\n"); + wait_child_process(info.hProcess); + CloseHandle(info.hProcess); + CloseHandle(info.hThread); + } + + DestroyWindow(hwnd); +} + +static void test_RealGetWindowClass(void) +{ + WCHAR class_name_w[256]; + char class_name[20]; + WNDCLASSW cls_w; + WNDCLASSA cls_a; + BOOL ret_b; + HWND hwnd; + UINT ret; + int i; + + hwnd = CreateWindowA("Button", "test", BS_CHECKBOX | WS_POPUP, 0, 0, 50, 14, 0, 0, 0, NULL); + ok(!!hwnd, "hwnd == NULL\n"); + + /* Basic tests. */ + memset(class_name, 0, sizeof(class_name)); + ret = RealGetWindowClassA(hwnd, class_name, ARRAY_SIZE(class_name)); + ok(!strcmp(class_name, "Button"), "got %s\n", class_name); + ok(ret == strlen(class_name), "got %d, %s\n", ret, class_name); + + memset(class_name_w, 0, sizeof(class_name_w)); + ret = RealGetWindowClassW(hwnd, class_name_w, ARRAY_SIZE(class_name_w)); + ok(!lstrcmpW(class_name_w, L"Button"), "got %s\n", debugstr_w(class_name_w)); + ok(ret == lstrlenW(class_name_w), "got %d, %s\n", ret, debugstr_w(class_name_w)); + + /* Shortened buffer tests. */ + memset(class_name, 0, sizeof(class_name)); + ret = RealGetWindowClassA(hwnd, class_name, 2); + ok(!strcmp(class_name, "B"), "got %s\n", class_name); + ok(ret == strlen(class_name), "got %d\n", ret); + + memset(class_name_w, 0, sizeof(class_name_w)); + ret = RealGetWindowClassW(hwnd, class_name_w, 2); + ok(!lstrcmpW(class_name_w, L"B"), "got %s\n", debugstr_w(class_name_w)); + ok(ret == lstrlenW(class_name_w), "got %d, %s\n", ret, debugstr_w(class_name_w)); + + /* A NULL buffer with a non-zero length will result in an access violation. */ + if (0) + { + RealGetWindowClassA(hwnd, NULL, ARRAY_SIZE(class_name)); + } + + /* Invalid length. */ + memset(class_name, 0, sizeof(class_name)); + SetLastError(0xdeadbeef); + ret = RealGetWindowClassA(hwnd, class_name, 0); + ok(!ret, "got %d\n", ret); + todo_wine ok((GetLastError() == ERROR_INSUFFICIENT_BUFFER), "Unexpected last error %ld\n", GetLastError()); + + memset(class_name_w, 0, sizeof(class_name_w)); + SetLastError(0xdeadbeef); + ret = RealGetWindowClassW(hwnd, class_name_w, 0); + ok(!ret, "got %d\n", ret); + ok((GetLastError() == ERROR_INSUFFICIENT_BUFFER), "Unexpected last error %ld\n", GetLastError()); + + DestroyWindow(hwnd); + + /* Custom class, RealGetWindowClass behaves the same as GetClassName. */ + real_class_wndproc = NULL; + memset(&cls_a, 0, sizeof(cls_a)); + cls_a.lpfnWndProc = super_class_test_win_proc_a; + cls_a.hInstance = GetModuleHandleA(NULL); + cls_a.lpszClassName = SUPER_CLASS_NAME_A; + RegisterClassA(&cls_a); + + hwnd = CreateWindowA(SUPER_CLASS_NAME_A, "test", WS_OVERLAPPED, 0, 0, 50, 50, 0, 0, 0, NULL); + ok(!!hwnd, "hwnd == NULL\n"); + + test_hwnd_real_class_name_str(hwnd, SUPER_CLASS_NAME_A, FALSE); + + DestroyWindow(hwnd); + UnregisterClassA(SUPER_CLASS_NAME_A, GetModuleHandleA(NULL)); + + /* Custom class, W functions. */ + memset(&cls_w, 0, sizeof(cls_w)); + cls_w.lpfnWndProc = super_class_test_win_proc_w; + cls_w.hInstance = GetModuleHandleW(NULL); + cls_w.lpszClassName = SUPER_CLASS_NAME_W; + RegisterClassW(&cls_w); + + hwnd = CreateWindowW(SUPER_CLASS_NAME_W, L"test", WS_OVERLAPPED, 0, 0, 50, 50, 0, 0, 0, NULL); + ok(!!hwnd, "hwnd == NULL\n"); + + test_hwnd_real_class_name_str(hwnd, SUPER_CLASS_NAME_A, FALSE); + + DestroyWindow(hwnd); + UnregisterClassW(SUPER_CLASS_NAME_W, GetModuleHandleW(NULL)); + + for (i = 0; i < ARRAY_SIZE(class_tests); i++) + { + const struct real_class_test *class_test = &class_tests[i]; + + /* Test A version of functions. */ + memset(&cls_a, 0, sizeof(cls_a)); + ret_b = GetClassInfoA(NULL, class_test->class_name_a, &cls_a); + ok(ret_b, "GetClassInfoA failed: %lu\n", GetLastError()); + + real_class_wndproc = cls_a.lpfnWndProc; + cls_a.lpfnWndProc = super_class_test_win_proc_a; + cls_a.hInstance = GetModuleHandleA(NULL); + cls_a.lpszClassName = SUPER_CLASS_NAME_A; + RegisterClassA(&cls_a); + + test_real_class_name(class_test, FALSE, i); + + UnregisterClassA(SUPER_CLASS_NAME_A, GetModuleHandleA(NULL)); + + /* Test W version of functions. */ + MultiByteToWideChar(CP_ACP, 0, class_test->class_name_a, -1, class_name_w, ARRAY_SIZE(class_name_w)); + + memset(&cls_w, 0, sizeof(cls_w)); + ret_b = GetClassInfoW(NULL, class_name_w, &cls_w); + ok(ret_b, "GetClassInfoW failed: %lu\n", GetLastError()); + + real_class_wndproc = cls_w.lpfnWndProc; + cls_w.lpfnWndProc = super_class_test_win_proc_w; + cls_w.hInstance = GetModuleHandleW(NULL); + cls_w.lpszClassName = SUPER_CLASS_NAME_W; + RegisterClassW(&cls_w); + + test_real_class_name(class_test, TRUE, i); + + UnregisterClassW(SUPER_CLASS_NAME_W, GetModuleHandleW(NULL)); + } + + real_class_wndproc = NULL; +} + +static void test_cross_process_RealGetWindowClass(int real_class_test_idx, HWND hwnd) +{ + const struct real_class_test *class_test = &class_tests[real_class_test_idx]; + + test_hwnd_real_class_name_str(hwnd, class_test->real_class_name_a, class_test->wine_todo); +} + START_TEST(class) { char **argv; @@ -1600,7 +1833,10 @@ START_TEST(class)
if (argc >= 3) { - test_comctl32_class( argv[2] ); + if (!strcmp(argv[2], "test_cross_process_RealGetWindowClass")) + test_cross_process_RealGetWindowClass(strtol(argv[3], NULL, 0), UlongToHandle(strtol(argv[4], NULL, 16)) ); + else + test_comctl32_class( argv[2] ); return; }
@@ -1626,6 +1862,7 @@ START_TEST(class) test_comctl32_classes(); test_actctx_classes(); test_class_name(); + test_RealGetWindowClass();
/* this test unregisters the Button class so it should be executed at the end */ test_instances();
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/comctl32/tests/misc.c | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+)
diff --git a/dlls/comctl32/tests/misc.c b/dlls/comctl32/tests/misc.c index d128c202c67..419c9020261 100644 --- a/dlls/comctl32/tests/misc.c +++ b/dlls/comctl32/tests/misc.c @@ -384,6 +384,38 @@ static void test_LoadIconWithScaleDown(void) FreeLibrary(hinst); }
+#define SUPER_CLASS_NAME_A "SuperClass Test" +static WNDPROC real_class_wndproc; +static LRESULT WINAPI super_class_test_win_proc_a(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (real_class_wndproc) + CallWindowProcA(real_class_wndproc, hwnd, msg, wparam, lparam); + + if (msg == WM_NCCREATE) + return 1; + + return DefWindowProcA(hwnd, msg, wparam, lparam); +} + +#define test_hwnd_real_class_name_str( hwnd, exp_real_class_name_str ) \ + test_hwnd_real_class_name_str_( (hwnd), (exp_real_class_name_str), __FILE__, __LINE__ ) +static void test_hwnd_real_class_name_str_(HWND hwnd, const char *exp_real_class_name_str, const char *file, int line) +{ + WCHAR exp_real_class_name_w[256] = { 0 }; + WCHAR real_class_name_w[256] = { 0 }; + char real_class_name_a[256] = { 0 }; + ULONG len; + + len = RealGetWindowClassA(hwnd, real_class_name_a, ARRAY_SIZE(real_class_name_a)); + ok(!strcmp(real_class_name_a, exp_real_class_name_str), "got %s\n", real_class_name_a); + ok(len == strlen(exp_real_class_name_str), "got %ld, expected %d\n", len, lstrlenA(exp_real_class_name_str)); + + MultiByteToWideChar(CP_ACP, 0, exp_real_class_name_str, -1, exp_real_class_name_w, ARRAY_SIZE(exp_real_class_name_w)); + len = RealGetWindowClassW(hwnd, real_class_name_w, ARRAY_SIZE(real_class_name_w)); + ok(!lstrcmpW(real_class_name_w, exp_real_class_name_w), "got %s\n", debugstr_w(real_class_name_w)); + ok(len == lstrlenW(exp_real_class_name_w), "got %ld, expected %d\n", len, lstrlenW(exp_real_class_name_w)); +} + static void check_class( const char *name, int must_exist, UINT style, UINT ignore, BOOL v6 ) { WNDCLASSA wc; @@ -410,6 +442,19 @@ static void check_class( const char *name, int must_exist, UINT style, UINT igno GetClassNameA(hwnd, buff, ARRAY_SIZE(buff)); ok( !strcmp(name, buff), "Unexpected class name %s, expected %s.\n", buff, name ); DestroyWindow(hwnd); + + real_class_wndproc = wc.lpfnWndProc; + wc.lpfnWndProc = super_class_test_win_proc_a; + wc.hInstance = GetModuleHandleA(NULL); + wc.lpszClassName = SUPER_CLASS_NAME_A; + RegisterClassA(&wc); + + hwnd = CreateWindowA(SUPER_CLASS_NAME_A, 0, 0, 0, 0, 0, 0, 0, NULL, GetModuleHandleA(NULL), 0); + test_hwnd_real_class_name_str(hwnd, SUPER_CLASS_NAME_A); + + DestroyWindow(hwnd); + UnregisterClassA(SUPER_CLASS_NAME_A, GetModuleHandleA(NULL)); + real_class_wndproc = NULL; } else ok( !must_exist, "System class %s does not exist\n", name );
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/win32u/tests/win32u.c | 54 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+)
diff --git a/dlls/win32u/tests/win32u.c b/dlls/win32u/tests/win32u.c index e118d081ead..1d70eb9fbb0 100644 --- a/dlls/win32u/tests/win32u.c +++ b/dlls/win32u/tests/win32u.c @@ -126,6 +126,18 @@ static void test_window_props(void) DestroyWindow( hwnd ); }
+static WNDPROC real_class_wndproc; +static LRESULT WINAPI real_class_test_win_proc_w(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (real_class_wndproc) + CallWindowProcW(real_class_wndproc, hwnd, msg, wparam, lparam); + + if (msg == WM_NCCREATE) + return 1; + + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + static void test_class(void) { UNICODE_STRING name; @@ -219,6 +231,48 @@ static void test_class(void) "NtUserGetAtomName returned %lx %lu\n", ret, GetLastError() ); ok( buf[0] == 0xcccc, "buf = %s\n", debugstr_w(buf) );
+ memset( &cls, 0, sizeof(cls) ); + ret = GetClassInfoW( NULL, L"Edit", &cls ); + ok( ret, "GetClassInfoW failed: %lu\n", GetLastError() ); + + real_class_wndproc = cls.lpfnWndProc; + cls.lpfnWndProc = real_class_test_win_proc_w; + cls.hInstance = GetModuleHandleW( NULL ); + cls.lpszClassName = L"test"; + + class = RegisterClassW( &cls ); + ok( class, "RegisterClassW failed: %lu\n", GetLastError() ); + + hwnd = CreateWindowW( L"test", L"test name", WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL, + CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, NULL, 0 ); + + /* Get real class, in this case Edit. */ + memset( buf, 0xcc, sizeof(buf) ); + name.Buffer = buf; + name.Length = 0xdead; + name.MaximumLength = sizeof(buf); + ret = NtUserGetClassName( hwnd, TRUE, &name ); + ok( ret == 4, "NtUserGetClassName returned %lu\n", ret ); + ok( name.Length == 0xdead, "Length = %u\n", name.Length ); + ok( name.MaximumLength == sizeof(buf), "MaximumLength = %u\n", name.MaximumLength ); + todo_wine ok( !wcscmp( buf, L"Edit" ), "buf = %s\n", debugstr_w(buf) ); + + /* Get normal class instead of real class. */ + memset( buf, 0xcc, sizeof(buf) ); + name.Buffer = buf; + name.Length = 0xdead; + name.MaximumLength = sizeof(buf); + ret = NtUserGetClassName( hwnd, FALSE, &name ); + ok( ret == 4, "NtUserGetClassName returned %lu\n", ret ); + ok( name.Length == 0xdead, "Length = %u\n", name.Length ); + ok( name.MaximumLength == sizeof(buf), "MaximumLength = %u\n", name.MaximumLength ); + ok( !wcscmp( buf, L"test" ), "buf = %s\n", debugstr_w(buf) ); + + DestroyWindow( hwnd ); + + ret = UnregisterClassW( L"test", GetModuleHandleW(NULL) ); + ok( ret, "UnregisterClassW failed: %lu\n", GetLastError() ); + real_class_wndproc = NULL; }
static void test_NtUserCreateInputContext(void)
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/user32/button.c | 1 + dlls/user32/combo.c | 1 + dlls/user32/defdlg.c | 2 ++ dlls/user32/edit.c | 1 + dlls/user32/listbox.c | 1 + dlls/user32/mdi.c | 1 + dlls/user32/menu.c | 4 +++- dlls/user32/scroll.c | 4 +++- dlls/user32/static.c | 1 + dlls/win32u/class.c | 38 +++++++++++++++++++++++++++++++++--- dlls/win32u/ntuser_private.h | 1 + dlls/win32u/tests/win32u.c | 2 +- dlls/win32u/window.c | 22 +++++++++++++++++++++ include/ntuser.h | 21 ++++++++++++++++++++ 14 files changed, 94 insertions(+), 6 deletions(-)
diff --git a/dlls/user32/button.c b/dlls/user32/button.c index 10a111562a7..24d5ac843e2 100644 --- a/dlls/user32/button.c +++ b/dlls/user32/button.c @@ -202,6 +202,7 @@ LRESULT ButtonWndProc_common(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, WIN_SetStyle( hWnd, style, BS_TYPEMASK & ~style ); } set_button_state( hWnd, BST_UNCHECKED ); + NtUserSetRealClassId( hWnd, REAL_CLASS_ID_BUTTON ); return 0;
case WM_ERASEBKGND: diff --git a/dlls/user32/combo.c b/dlls/user32/combo.c index 6afd841f82a..73fa2422e52 100644 --- a/dlls/user32/combo.c +++ b/dlls/user32/combo.c @@ -537,6 +537,7 @@ static LRESULT COMBO_Create( HWND hwnd, LPHEADCOMBO lphc, HWND hwndParent, LONG CBForceDummyResize(lphc); }
+ NtUserSetRealClassId(hwnd, REAL_CLASS_ID_COMBOBOX); TRACE("init done\n"); return 0; } diff --git a/dlls/user32/defdlg.c b/dlls/user32/defdlg.c index 88b64ee469c..c6462c0b879 100644 --- a/dlls/user32/defdlg.c +++ b/dlls/user32/defdlg.c @@ -416,6 +416,8 @@ static LRESULT USER_DefDlgProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPar
LRESULT WINAPI USER_DefDlgProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, BOOL unicode ) { + if (msg == WM_CREATE) + NtUserSetRealClassId( hwnd, REAL_CLASS_ID_DIALOG ); if (unicode) return USER_DefDlgProcW( hwnd, msg, wParam, lParam ); else diff --git a/dlls/user32/edit.c b/dlls/user32/edit.c index 39f429813d6..59459d41658 100644 --- a/dlls/user32/edit.c +++ b/dlls/user32/edit.c @@ -4928,6 +4928,7 @@ LRESULT EditWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, B result = EDIT_WM_Create(es, nameW); HeapFree(GetProcessHeap(), 0, nameW); } + NtUserSetRealClassId(hwnd, REAL_CLASS_ID_EDIT); break;
case WM_CUT: diff --git a/dlls/user32/listbox.c b/dlls/user32/listbox.c index 0e925973e84..3dc1e54e91d 100644 --- a/dlls/user32/listbox.c +++ b/dlls/user32/listbox.c @@ -2652,6 +2652,7 @@ LRESULT ListBoxWndProc_common( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam CREATESTRUCTW *lpcs = (CREATESTRUCTW *)lParam; if (lpcs->style & LBS_COMBOBOX) lphc = lpcs->lpCreateParams; if (!LISTBOX_Create( hwnd, lphc )) return -1; + NtUserSetRealClassId( hwnd, REAL_CLASS_ID_LISTBOX ); TRACE("creating hwnd %p descr %p\n", hwnd, (void *)GetWindowLongPtrW( hwnd, 0 ) ); return 0; } diff --git a/dlls/user32/mdi.c b/dlls/user32/mdi.c index f579298544a..a45ab3383f2 100644 --- a/dlls/user32/mdi.c +++ b/dlls/user32/mdi.c @@ -1025,6 +1025,7 @@ LRESULT MDIClientWndProc_common( HWND hwnd, UINT message, WPARAM wParam, LPARAM
if (!hBmpClose) hBmpClose = CreateMDIMenuBitmap();
+ NtUserSetRealClassId(hwnd, REAL_CLASS_ID_MDI_CLIENT); TRACE("Client created: hwnd %p, Window menu %p, idFirst = %04x\n", hwnd, ci->hWindowMenu, ci->idFirstChild ); return 0; diff --git a/dlls/user32/menu.c b/dlls/user32/menu.c index 4ba6fa71028..47457117b3a 100644 --- a/dlls/user32/menu.c +++ b/dlls/user32/menu.c @@ -192,8 +192,10 @@ LRESULT WINAPI PopupMenuWndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM { switch(message) { - case WM_DESTROY: case WM_CREATE: + NtUserSetRealClassId( hwnd, REAL_CLASS_ID_MENU ); + /* Fall through. */ + case WM_DESTROY: case WM_MOUSEACTIVATE: case WM_PAINT: case WM_PRINTCLIENT: diff --git a/dlls/user32/scroll.c b/dlls/user32/scroll.c index 788b5091698..b1f460c032f 100644 --- a/dlls/user32/scroll.c +++ b/dlls/user32/scroll.c @@ -237,6 +237,9 @@ LRESULT WINAPI USER_ScrollBarProc( HWND hwnd, UINT message, WPARAM wParam, LPARA { switch(message) { + case WM_CREATE: + NtUserSetRealClassId( hwnd, REAL_CLASS_ID_SCROLLBAR ); + /* Fall through. */ case WM_KEYDOWN: case WM_KEYUP: case WM_ENABLE: @@ -250,7 +253,6 @@ LRESULT WINAPI USER_ScrollBarProc( HWND hwnd, UINT message, WPARAM wParam, LPARA case WM_SYSTIMER: case WM_SETFOCUS: case WM_KILLFOCUS: - case WM_CREATE: case WM_ERASEBKGND: case WM_GETDLGCODE: case WM_PAINT: diff --git a/dlls/user32/static.c b/dlls/user32/static.c index f3953f7610a..40c2d9fbfb9 100644 --- a/dlls/user32/static.c +++ b/dlls/user32/static.c @@ -333,6 +333,7 @@ LRESULT StaticWndProc_common( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ERR("Unknown style 0x%02lx\n", style ); return -1; } + NtUserSetRealClassId(hwnd, REAL_CLASS_ID_STATIC); STATIC_InitColours(); break;
diff --git a/dlls/win32u/class.c b/dlls/win32u/class.c index 396e2285797..b35618d80c2 100644 --- a/dlls/win32u/class.c +++ b/dlls/win32u/class.c @@ -612,13 +612,26 @@ ULONG WINAPI NtUserGetAtomName( ATOM atom, UNICODE_STRING *name ) return size / sizeof(WCHAR); }
+static const WCHAR real_class_id_str[][MAX_ATOM_LEN + 1] = { + { 'B', 'u', 't', 't', 'o', 'n', 0, }, /* REAL_CLASS_ID_BUTTON */ + { 'C', 'o', 'm', 'b', 'o', 'B', 'o', 'x', 0, }, /* REAL_CLASS_ID_COMBOBOX */ + { 'E', 'd', 'i', 't', 0, }, /* REAL_CLASS_ID_EDIT */ + { 'L', 'i', 's', 't', 'B', 'o', 'x', 0, }, /* REAL_CLASS_ID_LISTBOX */ + { 'S', 'c', 'r', 'o', 'l', 'l', 'B', 'a', 'r', 0, }, /* REAL_CLASS_ID_SCROLLBAR */ + { 'S', 't', 'a', 't', 'i', 'c', 0, }, /* REAL_CLASS_ID_STATIC */ + { 'M', 'D', 'I', 'C', 'l', 'i', 'e', 'n', 't', 0, }, /* REAL_CLASS_ID_MDI_CLIENT */ + { '#', '3', '2', '7', '6', '8', 0, }, /* REAL_CLASS_ID_MENU */ + { '#', '3', '2', '7', '7', '0', 0, }, /* REAL_CLASS_ID_DIALOG */ +}; + /*********************************************************************** * NtUserGetClassName (win32u.@) */ INT WINAPI NtUserGetClassName( HWND hwnd, BOOL real, UNICODE_STRING *name ) { + const WCHAR *basename = NULL; CLASS *class; - int ret; + int ret = 0;
TRACE( "%p %x %p\n", hwnd, real, name );
@@ -648,9 +661,28 @@ INT WINAPI NtUserGetClassName( HWND hwnd, BOOL real, UNICODE_STRING *name ) return NtUserGetAtomName( atom, name ); }
- ret = min( name->MaximumLength / sizeof(WCHAR) - 1, lstrlenW(class->basename) ); - if (ret) memcpy( name->Buffer, class->basename, ret * sizeof(WCHAR) ); + if (real) + { + WND *wnd; + + if (!(wnd = get_win_ptr( hwnd ))) + { + RtlSetLastWin32Error( ERROR_INVALID_WINDOW_HANDLE ); + goto exit; + } + + if (wnd->real_class_id) + basename = real_class_id_str[wnd->real_class_id - 1]; + release_win_ptr(wnd); + } + + if (!basename) + basename = (const WCHAR *)class->basename; + + ret = min( name->MaximumLength / sizeof(WCHAR) - 1, lstrlenW(basename) ); + if (ret) memcpy( name->Buffer, basename, ret * sizeof(WCHAR) ); name->Buffer[ret] = 0; +exit: release_class_ptr( class ); return ret; } diff --git a/dlls/win32u/ntuser_private.h b/dlls/win32u/ntuser_private.h index b39e38db5d6..3b861f0200a 100644 --- a/dlls/win32u/ntuser_private.h +++ b/dlls/win32u/ntuser_private.h @@ -86,6 +86,7 @@ typedef struct tagWND struct tagDIALOGINFO *dlgInfo; /* Dialog additional info (dialogs only) */ int pixel_format; /* Pixel format set by the graphics driver */ int internal_pixel_format; /* Internal pixel format set via WGL_WINE_pixel_format_passthrough */ + unsigned int real_class_id; /* "Real" window class ID, set by user32 standard control window procedures. */ int cbWndExtra; /* class cbWndExtra at window creation */ DWORD_PTR userdata; /* User private data */ DWORD wExtra[1]; /* Window extra bytes */ diff --git a/dlls/win32u/tests/win32u.c b/dlls/win32u/tests/win32u.c index 1d70eb9fbb0..e2a84f448ab 100644 --- a/dlls/win32u/tests/win32u.c +++ b/dlls/win32u/tests/win32u.c @@ -255,7 +255,7 @@ static void test_class(void) ok( ret == 4, "NtUserGetClassName returned %lu\n", ret ); ok( name.Length == 0xdead, "Length = %u\n", name.Length ); ok( name.MaximumLength == sizeof(buf), "MaximumLength = %u\n", name.MaximumLength ); - todo_wine ok( !wcscmp( buf, L"Edit" ), "buf = %s\n", debugstr_w(buf) ); + ok( !wcscmp( buf, L"Edit" ), "buf = %s\n", debugstr_w(buf) );
/* Get normal class instead of real class. */ memset( buf, 0xcc, sizeof(buf) ); diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index 4a57e0abde7..ae9a24125e5 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -4491,6 +4491,25 @@ BOOL show_owned_popups( HWND owner, BOOL show ) return TRUE; }
+static BOOL set_real_window_class_id( HWND hwnd, DWORD param ) +{ + WND *win = get_win_ptr( hwnd ); + + if (!win || win == WND_OTHER_PROCESS || win == WND_DESKTOP) + { + ERR("Can't set real window class for a window belonging to another process\n"); + return FALSE; + } + + if (!win->real_class_id) + { + FIXME("Real class ID currently set in-process only.\n"); + win->real_class_id = param; + } + release_win_ptr( win ); + return TRUE; +} + /******************************************************************* * NtUserFlashWindowEx (win32u.@) */ @@ -5595,6 +5614,9 @@ ULONG_PTR WINAPI NtUserCallHwndParam( HWND hwnd, DWORD_PTR param, DWORD code ) return set_window_style( hwnd, style->styleNew, style->styleOld ); }
+ case NtUserCallHwndParam_SetRealClassId: + return set_real_window_class_id( hwnd, param ); + default: FIXME( "invalid code %u\n", (int)code ); return 0; diff --git a/include/ntuser.h b/include/ntuser.h index 171d32abe6e..f9211277eee 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -513,6 +513,21 @@ struct ime_driver_call_params
#define WM_SYSTIMER 0x0118
+/* Internal real window class ID values. */ +enum wine_real_class_id +{ + REAL_CLASS_ID_NONE = 0, + REAL_CLASS_ID_BUTTON, + REAL_CLASS_ID_COMBOBOX, + REAL_CLASS_ID_EDIT, + REAL_CLASS_ID_LISTBOX, + REAL_CLASS_ID_SCROLLBAR, + REAL_CLASS_ID_STATIC, + REAL_CLASS_ID_MDI_CLIENT, + REAL_CLASS_ID_MENU, + REAL_CLASS_ID_DIALOG, +}; +
HKL WINAPI NtUserActivateKeyboardLayout( HKL layout, UINT flags ); BOOL WINAPI NtUserAddClipboardFormatListener( HWND hwnd ); @@ -1208,6 +1223,7 @@ enum NtUserCallHwndParam_SetMDIClientInfo, NtUserCallHwndParam_SetWindowContextHelpId, NtUserCallHwndParam_ShowOwnedPopups, + NtUserCallHwndParam_SetRealClassId, /* temporary exports */ NtUserSetWindowStyle, }; @@ -1378,6 +1394,11 @@ static inline BOOL NtUserShowOwnedPopups( HWND hwnd, BOOL show ) return NtUserCallHwndParam( hwnd, show, NtUserCallHwndParam_ShowOwnedPopups ); }
+static inline BOOL NtUserSetRealClassId( HWND hwnd, DWORD real_class_id ) +{ + return NtUserCallHwndParam( hwnd, real_class_id, NtUserCallHwndParam_SetRealClassId ); +} + /* Wine extensions */ BOOL WINAPI __wine_send_input( HWND hwnd, const INPUT *input, const RAWINPUT *rawinput );
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/win32u/class.c | 52 ++++++++++++++++++++++---------------------- dlls/win32u/window.c | 8 ++++++- server/protocol.def | 16 ++++++++++++++ server/window.c | 21 ++++++++++++++++++ 4 files changed, 70 insertions(+), 27 deletions(-)
diff --git a/dlls/win32u/class.c b/dlls/win32u/class.c index b35618d80c2..3d7aad89338 100644 --- a/dlls/win32u/class.c +++ b/dlls/win32u/class.c @@ -630,8 +630,9 @@ static const WCHAR real_class_id_str[][MAX_ATOM_LEN + 1] = { INT WINAPI NtUserGetClassName( HWND hwnd, BOOL real, UNICODE_STRING *name ) { const WCHAR *basename = NULL; - CLASS *class; + DWORD real_class_id = 0; int ret = 0; + WND *wnd;
TRACE( "%p %x %p\n", hwnd, real, name );
@@ -641,49 +642,48 @@ INT WINAPI NtUserGetClassName( HWND hwnd, BOOL real, UNICODE_STRING *name ) return 0; }
- if (!(class = get_class_ptr( hwnd, FALSE ))) return 0; + if (!(wnd = get_win_ptr( hwnd ))) + { + RtlSetLastWin32Error( ERROR_INVALID_WINDOW_HANDLE ); + return 0; + }
- if (class == OBJ_OTHER_PROCESS) + if (wnd == WND_OTHER_PROCESS || wnd == WND_DESKTOP) { ATOM atom = 0;
- SERVER_START_REQ( set_class_info ) + SERVER_START_REQ( get_window_class_name ) { - req->window = wine_server_user_handle( hwnd ); - req->flags = 0; - req->extra_offset = -1; - req->extra_size = 0; + req->handle = wine_server_user_handle( hwnd ); if (!wine_server_call_err( req )) + { + real_class_id = reply->real_class_id; atom = reply->base_atom; + } } SERVER_END_REQ;
- return NtUserGetAtomName( atom, name ); + if (!real || (real && !real_class_id)) return NtUserGetAtomName( atom, name ); + wnd = NULL; } + else + real_class_id = wnd->real_class_id;
- if (real) + if (real && real_class_id) { - WND *wnd; - - if (!(wnd = get_win_ptr( hwnd ))) - { - RtlSetLastWin32Error( ERROR_INVALID_WINDOW_HANDLE ); - goto exit; - } - - if (wnd->real_class_id) - basename = real_class_id_str[wnd->real_class_id - 1]; - release_win_ptr(wnd); + assert(real_class_id <= ARRAY_SIZE(real_class_id_str)); + basename = real_class_id_str[real_class_id - 1]; + } + else + { + assert(wnd); + basename = (const WCHAR *)wnd->class->basename; } - - if (!basename) - basename = (const WCHAR *)class->basename;
ret = min( name->MaximumLength / sizeof(WCHAR) - 1, lstrlenW(basename) ); if (ret) memcpy( name->Buffer, basename, ret * sizeof(WCHAR) ); name->Buffer[ret] = 0; -exit: - release_class_ptr( class ); + if (wnd) release_win_ptr( wnd ); return ret; }
diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index ae9a24125e5..c705be93c0d 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -4503,7 +4503,13 @@ static BOOL set_real_window_class_id( HWND hwnd, DWORD param )
if (!win->real_class_id) { - FIXME("Real class ID currently set in-process only.\n"); + SERVER_START_REQ( set_real_window_class_id ) + { + req->handle = wine_server_user_handle( hwnd ); + req->real_class_id = param; + wine_server_call( req ); + } + SERVER_END_REQ; win->real_class_id = param; } release_win_ptr( win ); diff --git a/server/protocol.def b/server/protocol.def index e9195df6b65..6654f0ba7aa 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -3733,6 +3733,22 @@ struct handle_info @END
+/* Get class name atom/real class ID for a window. */ +@REQ(get_window_class_name) + user_handle_t handle; /* handle to the window */ +@REPLY + atom_t base_atom; /* Base class atom. */ + unsigned int real_class_id; /* Real window class ID value. */ +@END + + +/* Set real window class ID value for a window. */ +@REQ(set_real_window_class_id) + user_handle_t handle; /* handle to the window */ + unsigned int real_class_id; /* Real window class ID value. */ +@END + + /* Allocate an arbitrary user handle */ @REQ(alloc_user_handle) @REPLY diff --git a/server/window.c b/server/window.c index 242e93f303a..62e1086a90c 100644 --- a/server/window.c +++ b/server/window.c @@ -92,6 +92,7 @@ struct window int prop_inuse; /* number of in-use window properties */ int prop_alloc; /* number of allocated window properties */ struct property *properties; /* window properties array */ + unsigned int real_class_id; /* Real window class ID. */ int nb_extra_bytes; /* number of extra bytes */ char *extra_bytes; /* extra bytes storage */ }; @@ -582,6 +583,7 @@ static struct window *create_window( struct window *parent, struct window *owner win->prop_inuse = 0; win->prop_alloc = 0; win->properties = NULL; + win->real_class_id = 0; win->nb_extra_bytes = 0; win->extra_bytes = NULL; win->window_rect = win->visible_rect = win->surface_rect = win->client_rect = empty_rect; @@ -3010,3 +3012,22 @@ DECL_HANDLER(set_window_layered_info) } else set_win32_error( ERROR_INVALID_WINDOW_HANDLE ); } + +/* Get class name atom/real class ID for a window. */ +DECL_HANDLER(get_window_class_name) +{ + struct window *win = get_window( req->handle ); + + if (!win) return; + reply->base_atom = get_class_atom(win->class); + reply->real_class_id = win->real_class_id; +} + +/* Set real window class ID value for a window. */ +DECL_HANDLER(set_real_window_class_id) +{ + struct window *win = get_window( req->handle ); + + if (!win) return; + win->real_class_id = req->real_class_id; +}
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/comctl32/tests/misc.c | 6 +++++- dlls/user32/class.c | 12 ++++++++++-- dlls/user32/tests/class.c | 20 ++++++++++---------- 3 files changed, 25 insertions(+), 13 deletions(-)
diff --git a/dlls/comctl32/tests/misc.c b/dlls/comctl32/tests/misc.c index 419c9020261..1a22f7af29c 100644 --- a/dlls/comctl32/tests/misc.c +++ b/dlls/comctl32/tests/misc.c @@ -450,7 +450,11 @@ static void check_class( const char *name, int must_exist, UINT style, UINT igno RegisterClassA(&wc);
hwnd = CreateWindowA(SUPER_CLASS_NAME_A, 0, 0, 0, 0, 0, 0, 0, NULL, GetModuleHandleA(NULL), 0); - test_hwnd_real_class_name_str(hwnd, SUPER_CLASS_NAME_A); + /* + * Wine's comctl32 doesn't have a separate ScrollBar implementation + * from user32. + */ + todo_wine_if(!strcmp(name, "ScrollBar")) test_hwnd_real_class_name_str(hwnd, SUPER_CLASS_NAME_A);
DestroyWindow(hwnd); UnregisterClassA(SUPER_CLASS_NAME_A, GetModuleHandleA(NULL)); diff --git a/dlls/user32/class.c b/dlls/user32/class.c index 0cfdf552a5d..e2322ea2fc0 100644 --- a/dlls/user32/class.c +++ b/dlls/user32/class.c @@ -498,7 +498,14 @@ INT WINAPI GetClassNameW( HWND hwnd, LPWSTR buffer, INT count ) */ UINT WINAPI RealGetWindowClassA( HWND hwnd, LPSTR buffer, UINT count ) { - return GetClassNameA( hwnd, buffer, count ); + WCHAR tmpbuf[MAX_ATOM_LEN + 1]; + DWORD len; + + if (count <= 0) return 0; + if (!RealGetWindowClassW( hwnd, tmpbuf, ARRAY_SIZE( tmpbuf ))) return 0; + RtlUnicodeToMultiByteN( buffer, count - 1, &len, tmpbuf, lstrlenW(tmpbuf) * sizeof(WCHAR) ); + buffer[len] = 0; + return len; }
@@ -507,7 +514,8 @@ UINT WINAPI RealGetWindowClassA( HWND hwnd, LPSTR buffer, UINT count ) */ UINT WINAPI RealGetWindowClassW( HWND hwnd, LPWSTR buffer, UINT count ) { - return GetClassNameW( hwnd, buffer, count ); + UNICODE_STRING name = { .Buffer = buffer, .MaximumLength = count * sizeof(WCHAR) }; + return NtUserGetClassName( hwnd, TRUE, &name ); }
diff --git a/dlls/user32/tests/class.c b/dlls/user32/tests/class.c index 47a0ae60c7f..41512c09a6e 100644 --- a/dlls/user32/tests/class.c +++ b/dlls/user32/tests/class.c @@ -1602,16 +1602,16 @@ struct real_class_test { };
static const struct real_class_test class_tests[] = { - { "Button", "Button", TRUE, TRUE }, - { "ComboBox", "ComboBox", TRUE, TRUE }, - { "Edit", "Edit", TRUE, TRUE }, - { "ListBox", "ListBox", TRUE, TRUE }, - { "ScrollBar", "ScrollBar", TRUE, TRUE }, - { "Static", "Static", TRUE, TRUE }, - { "ComboLBox", "ListBox", TRUE, TRUE }, - { "MDIClient", "MDIClient", TRUE, TRUE }, - { "#32768", "#32768", TRUE, TRUE }, - { "#32770", "#32770", TRUE, TRUE }, + { "Button", "Button", TRUE, FALSE }, + { "ComboBox", "ComboBox", TRUE, FALSE }, + { "Edit", "Edit", TRUE, FALSE }, + { "ListBox", "ListBox", TRUE, FALSE }, + { "ScrollBar", "ScrollBar", TRUE, FALSE }, + { "Static", "Static", TRUE, FALSE }, + { "ComboLBox", "ListBox", TRUE, FALSE }, + { "MDIClient", "MDIClient", TRUE, FALSE }, + { "#32768", "#32768", TRUE, FALSE }, + { "#32770", "#32770", TRUE, FALSE }, /* Not all built-in classes set real window class. */ { "Message", SUPER_CLASS_NAME_A, FALSE, FALSE }, };
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=138825
Your paranoid android.
=== w7pro64 (64 bit report) ===
comctl32: 0608:misc: unhandled exception c0000005 at 000007FEF4994567
On Tue Oct 17 16:14:11 2023 +0000, Connor McAdams wrote:
It is. I tested this behavior locally and it doesn't do anything. I'm not sure if this is worth adding tests for, since it seems to me like a really unlikely thing to happen in practice.
Ah, ok. Thanks for sharing.
Jinoh Kang (@iamahuman) commented about dlls/comctl32/tests/misc.c:
FreeLibrary(hinst);
}
+#define SUPER_CLASS_NAME_A "SuperClass Test" +static WNDPROC real_class_wndproc; +static LRESULT WINAPI super_class_test_win_proc_a(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{
- if (real_class_wndproc)
CallWindowProcA(real_class_wndproc, hwnd, msg, wparam, lparam);
I don't think calling real wndproc *and then* calling defwndproc is a good idea, because the real wndproc can end up calling defwndproc anyway and this results in double operations.
```suggestion:-0+0 return CallWindowProcA(real_class_wndproc, hwnd, msg, wparam, lparam); ```
Otherwise, can you leave a comment as to why it had to be done this way?
On Tue Oct 17 14:44:40 2023 +0000, Connor McAdams wrote:
Mainly attempting to use a message that is expected to be passed to the procedure if super/subclassing at least once. `WM_NCCREATE` could be used as well I guess.
I think this is the most important part. What you need to test is what happens if NCCREATE does not reach control procedure.
On Tue Oct 17 18:30:26 2023 +0000, Nikolay Sivov wrote:
I think this is the most important part. What you need to test is what happens if NCCREATE does not reach control procedure.
Well, the tricky thing here is, it's different for each control.
For instance, any message being passed to the user32 `Static` control window procedure (even `WM_NULL`) will cause its "real" class to be set. For controls like `Edit`, if you pass only `WM_NCCREATE` and not `WM_CREATE`, `RealGetWindowClass()` will actually trigger an access violation.
I'm not sure how important matching the details here is on every control. I feel like it's safe to assume `WM_CREATE` will be passed to each control. In cases like `Static`, there's probably some overhead associated with calling into win32u for each and every message. Maybe that's negligible, but it feels a bit wasteful to me.