[PATCH v2 0/5] MR4092: user32: Actually implement RealGetWindowClass{A/W}.
-- v2: user32: Pass real argument to NtUserGetClassName in RealGetWindowClass. win32u: Add support for retrieving real window class ID across processes. user32: Set real window class ID for user32 standard controls. https://gitlab.winehq.org/wine/wine/-/merge_requests/4092
From: Connor McAdams <cmcadams(a)codeweavers.com> Signed-off-by: Connor McAdams <cmcadams(a)codeweavers.com> --- dlls/user32/tests/class.c | 293 +++++++++++++++++++++++++++++++++++++- 1 file changed, 292 insertions(+), 1 deletion(-) diff --git a/dlls/user32/tests/class.c b/dlls/user32/tests/class.c index 686fced55ee..57e2dd19e3f 100644 --- a/dlls/user32/tests/class.c +++ b/dlls/user32/tests/class.c @@ -1592,6 +1592,291 @@ 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 WCHAR *class_name_w; + const char *class_name_a; + const WCHAR *real_class_name_w; + const char *real_class_name_a; + BOOL test_cross_process; + BOOL wine_todo; +}; + +static const struct real_class_test class_tests[] = { + { L"Button", "Button", L"Button", "Button", TRUE, TRUE }, + { L"ComboBox", "ComboBox", L"ComboBox", "ComboBox", TRUE, TRUE }, + { L"Edit", "Edit", L"Edit", "Edit", TRUE, TRUE }, + { L"ListBox", "ListBox", L"ListBox", "ListBox", TRUE, TRUE }, + { L"ScrollBar", "ScrollBar", L"ScrollBar", "ScrollBar", TRUE, TRUE }, + { L"Static", "Static", L"Static", "Static", TRUE, TRUE }, + { L"ComboLBox", "ComboLBox", L"ListBox", "ListBox", TRUE, TRUE }, + { L"MDIClient", "MDIClient", L"MDIClient", "MDIClient", TRUE, TRUE }, + { L"#32768", "#32768", L"#32768", "#32768", TRUE, TRUE }, + { L"#32770", "#32770", L"#32770", "#32770", TRUE, TRUE }, + /* Not all built-in classes set real window class. */ + { L"Message", "Message", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, FALSE, FALSE }, +}; + +static const struct real_class_test comctl32_class_tests[] = { + { L"Button", "Button", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, + { L"ComboBox", "ComboBox", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, + { L"Edit", "Edit", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, + { L"ListBox", "ListBox", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, + { L"ScrollBar", "ScrollBar", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, + { L"Static", "Static", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, + { L"ComboLBox", "ComboLBox", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, +}; + +static WNDPROC real_class_wndproc; +static BOOL pass_msg_to_real_class_wndproc; +static LRESULT WINAPI super_class_test_win_proc_w(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if (pass_msg_to_real_class_wndproc && 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 (pass_msg_to_real_class_wndproc && 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_a_w( hwnd, exp_real_class_name_w, exp_real_class_name_a, todo ) \ + test_hwnd_real_class_name_a_w_( (hwnd), (exp_real_class_name_w), (exp_real_class_name_a), (todo), __FILE__, __LINE__ ) +static void test_hwnd_real_class_name_a_w_(HWND hwnd, const WCHAR *exp_real_class_name_w, + const char *exp_real_class_name_a, BOOL todo, const char *file, int line) +{ + WCHAR real_class_name_w[256] = { 0 }; + char real_class_name_a[256] = { 0 }; + ULONG len; + + 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, %s\n", len, debugstr_w(exp_real_class_name_w)); + + 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_a), "got %s\n", real_class_name_a); + todo_wine_if(todo) ok(len == strlen(exp_real_class_name_a), "got %ld, %s\n", len, exp_real_class_name_a); +} + +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; + int i; + + /* + * First pass doesn't pass messages through to the class window procedure, + * second pass does. + */ + for (i = 0; i < 2; i++) + { + pass_msg_to_real_class_wndproc = i; + + 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"); + + if (!i) + test_hwnd_real_class_name_a_w(hwnd, SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, FALSE); + else + { + test_hwnd_real_class_name_a_w(hwnd, class_test->real_class_name_w, 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_real_class_names(const struct real_class_test *tests, int tests_count) +{ + WNDCLASSW cls_w; + WNDCLASSA cls_a; + BOOL ret; + int i; + + for (i = 0; i < tests_count; i++) + { + const struct real_class_test *class_test = &tests[i]; + + /* Test W version of functions. */ + memset(&cls_w, 0, sizeof(cls_w)); + ret = GetClassInfoW(NULL, class_test->class_name_w, &cls_w); + ok(ret, "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)); + + /* Test A version of functions. */ + memset(&cls_a, 0, sizeof(cls_a)); + ret = GetClassInfoA(NULL, class_test->class_name_a, &cls_a); + ok(ret, "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)); + } +} + +static void test_RealGetWindowClass(void) +{ + char path_name[MAX_PATH]; + PROCESS_INFORMATION info; + WCHAR class_name_w[20]; + STARTUPINFOA startup; + char class_name[20]; + char **argv; + HWND hwnd; + UINT ret; + + hwnd = CreateWindowA("Button", "test", BS_CHECKBOX | WS_POPUP, 0, 0, 50, 14, 0, 0, 0, NULL); + ok(!!hwnd, "hwnd == NULL\n"); + + /* Normal tests, just get the name of the class, no shenanigans. */ + 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); + + test_real_class_names(class_tests, ARRAY_SIZE(class_tests)); + + /* Test RealGetWindowClass on comctl32 version of these controls. */ + winetest_get_mainargs(&argv); + memset(&startup, 0, sizeof(startup)); + startup.cb = sizeof(startup); + sprintf( path_name, "%s %s test_comctl32_RealGetWindowClass", argv[0], argv[1]); + 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); +} + +static void test_comctl32_RealGetWindowClass(void) +{ + char path[MAX_PATH]; + ULONG_PTR cookie; + HMODULE module; + HANDLE context; + BOOL ret; + + /* + * If the comctl32 version of standard controls are used, "real" window + * class is not set. Applications are expected to use + * OBJID_QUERYCLASSNAMEIDX instead. + */ + GetTempPathA(ARRAY_SIZE(path), path); + strcat(path, "comctl32_class.manifest"); + + create_manifest_file(path, comctl32_manifest); + context = create_test_actctx(path); + ret = DeleteFileA(path); + ok(ret, "Failed to delete manifest file, error %ld.\n", GetLastError()); + + module = GetModuleHandleA("comctl32"); + ok(!module, "comctl32 already loaded\n"); + + ret = ActivateActCtx(context, &cookie); + ok(ret, "Failed to activate context.\n"); + module = GetModuleHandleA("comctl32"); + ok(!module, "comctl32 already loaded\n"); + + test_real_class_names(comctl32_class_tests, ARRAY_SIZE(comctl32_class_tests)); + + module = GetModuleHandleA("comctl32"); + ok(!!module, "comctl32 not loaded\n"); + + ret = DeactivateActCtx(0, cookie); + ok(ret, "Failed to deactivate context.\n"); + ReleaseActCtx(context); +} + +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_a_w(hwnd, class_test->real_class_name_w, class_test->real_class_name_a, class_test->wine_todo); +} + START_TEST(class) { char **argv; @@ -1600,7 +1885,12 @@ START_TEST(class) if (argc >= 3) { - test_comctl32_class( argv[2] ); + if (!strcmp(argv[2], "test_comctl32_RealGetWindowClass")) + test_comctl32_RealGetWindowClass(); + else 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 +1916,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(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/4092
From: Connor McAdams <cmcadams(a)codeweavers.com> Signed-off-by: Connor McAdams <cmcadams(a)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) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/4092
From: Connor McAdams <cmcadams(a)codeweavers.com> Signed-off-by: Connor McAdams <cmcadams(a)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 ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/4092
From: Connor McAdams <cmcadams(a)codeweavers.com> Signed-off-by: Connor McAdams <cmcadams(a)codeweavers.com> --- dlls/win32u/class.c | 52 +++++++++++++++++----------------- dlls/win32u/window.c | 8 +++++- include/wine/server_protocol.h | 36 ++++++++++++++++++++++- server/protocol.def | 16 +++++++++++ server/request.h | 12 ++++++++ server/trace.c | 23 +++++++++++++++ server/window.c | 21 ++++++++++++++ 7 files changed, 140 insertions(+), 28 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/include/wine/server_protocol.h b/include/wine/server_protocol.h index a392b532f4e..4fe1d20352b 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -5377,6 +5377,34 @@ struct set_window_layered_info_reply +struct get_window_class_name_request +{ + struct request_header __header; + user_handle_t handle; +}; +struct get_window_class_name_reply +{ + struct reply_header __header; + atom_t base_atom; + unsigned int real_class_id; +}; + + + +struct set_real_window_class_id_request +{ + struct request_header __header; + user_handle_t handle; + unsigned int real_class_id; + char __pad_20[4]; +}; +struct set_real_window_class_id_reply +{ + struct reply_header __header; +}; + + + struct alloc_user_handle_request { struct request_header __header; @@ -5903,6 +5931,8 @@ enum request REQ_set_fd_eof_info, REQ_get_window_layered_info, REQ_set_window_layered_info, + REQ_get_window_class_name, + REQ_set_real_window_class_id, REQ_alloc_user_handle, REQ_free_user_handle, REQ_set_cursor, @@ -6194,6 +6224,8 @@ union generic_request struct set_fd_eof_info_request set_fd_eof_info_request; struct get_window_layered_info_request get_window_layered_info_request; struct set_window_layered_info_request set_window_layered_info_request; + struct get_window_class_name_request get_window_class_name_request; + struct set_real_window_class_id_request set_real_window_class_id_request; struct alloc_user_handle_request alloc_user_handle_request; struct free_user_handle_request free_user_handle_request; struct set_cursor_request set_cursor_request; @@ -6483,6 +6515,8 @@ union generic_reply struct set_fd_eof_info_reply set_fd_eof_info_reply; struct get_window_layered_info_reply get_window_layered_info_reply; struct set_window_layered_info_reply set_window_layered_info_reply; + struct get_window_class_name_reply get_window_class_name_reply; + struct set_real_window_class_id_reply set_real_window_class_id_reply; struct alloc_user_handle_reply alloc_user_handle_reply; struct free_user_handle_reply free_user_handle_reply; struct set_cursor_reply set_cursor_reply; @@ -6504,7 +6538,7 @@ union generic_reply /* ### protocol_version begin ### */ -#define SERVER_PROTOCOL_VERSION 784 +#define SERVER_PROTOCOL_VERSION 785 /* ### protocol_version end ### */ 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. */ +(a)REQ(get_window_class_name) + user_handle_t handle; /* handle to the window */ +(a)REPLY + atom_t base_atom; /* Base class atom. */ + unsigned int real_class_id; /* Real window class ID value. */ +(a)END + + +/* Set real window class ID value for a window. */ +(a)REQ(set_real_window_class_id) + user_handle_t handle; /* handle to the window */ + unsigned int real_class_id; /* Real window class ID value. */ +(a)END + + /* Allocate an arbitrary user handle */ @REQ(alloc_user_handle) @REPLY diff --git a/server/request.h b/server/request.h index d6043c5fdc3..042268c16ea 100644 --- a/server/request.h +++ b/server/request.h @@ -386,6 +386,8 @@ DECL_HANDLER(set_fd_name_info); DECL_HANDLER(set_fd_eof_info); DECL_HANDLER(get_window_layered_info); DECL_HANDLER(set_window_layered_info); +DECL_HANDLER(get_window_class_name); +DECL_HANDLER(set_real_window_class_id); DECL_HANDLER(alloc_user_handle); DECL_HANDLER(free_user_handle); DECL_HANDLER(set_cursor); @@ -676,6 +678,8 @@ static const req_handler req_handlers[REQ_NB_REQUESTS] = (req_handler)req_set_fd_eof_info, (req_handler)req_get_window_layered_info, (req_handler)req_set_window_layered_info, + (req_handler)req_get_window_class_name, + (req_handler)req_set_real_window_class_id, (req_handler)req_alloc_user_handle, (req_handler)req_free_user_handle, (req_handler)req_set_cursor, @@ -2263,6 +2267,14 @@ C_ASSERT( FIELD_OFFSET(struct set_window_layered_info_request, color_key) == 16 C_ASSERT( FIELD_OFFSET(struct set_window_layered_info_request, alpha) == 20 ); C_ASSERT( FIELD_OFFSET(struct set_window_layered_info_request, flags) == 24 ); C_ASSERT( sizeof(struct set_window_layered_info_request) == 32 ); +C_ASSERT( FIELD_OFFSET(struct get_window_class_name_request, handle) == 12 ); +C_ASSERT( sizeof(struct get_window_class_name_request) == 16 ); +C_ASSERT( FIELD_OFFSET(struct get_window_class_name_reply, base_atom) == 8 ); +C_ASSERT( FIELD_OFFSET(struct get_window_class_name_reply, real_class_id) == 12 ); +C_ASSERT( sizeof(struct get_window_class_name_reply) == 16 ); +C_ASSERT( FIELD_OFFSET(struct set_real_window_class_id_request, handle) == 12 ); +C_ASSERT( FIELD_OFFSET(struct set_real_window_class_id_request, real_class_id) == 16 ); +C_ASSERT( sizeof(struct set_real_window_class_id_request) == 24 ); C_ASSERT( sizeof(struct alloc_user_handle_request) == 16 ); C_ASSERT( FIELD_OFFSET(struct alloc_user_handle_reply, handle) == 8 ); C_ASSERT( sizeof(struct alloc_user_handle_reply) == 16 ); diff --git a/server/trace.c b/server/trace.c index 55ccefa1746..9c7cb003bcc 100644 --- a/server/trace.c +++ b/server/trace.c @@ -4452,6 +4452,23 @@ static void dump_set_window_layered_info_request( const struct set_window_layere fprintf( stderr, ", flags=%08x", req->flags ); } +static void dump_get_window_class_name_request( const struct get_window_class_name_request *req ) +{ + fprintf( stderr, " handle=%08x", req->handle ); +} + +static void dump_get_window_class_name_reply( const struct get_window_class_name_reply *req ) +{ + fprintf( stderr, " base_atom=%04x", req->base_atom ); + fprintf( stderr, ", real_class_id=%08x", req->real_class_id ); +} + +static void dump_set_real_window_class_id_request( const struct set_real_window_class_id_request *req ) +{ + fprintf( stderr, " handle=%08x", req->handle ); + fprintf( stderr, ", real_class_id=%08x", req->real_class_id ); +} + static void dump_alloc_user_handle_request( const struct alloc_user_handle_request *req ) { } @@ -4874,6 +4891,8 @@ static const dump_func req_dumpers[REQ_NB_REQUESTS] = { (dump_func)dump_set_fd_eof_info_request, (dump_func)dump_get_window_layered_info_request, (dump_func)dump_set_window_layered_info_request, + (dump_func)dump_get_window_class_name_request, + (dump_func)dump_set_real_window_class_id_request, (dump_func)dump_alloc_user_handle_request, (dump_func)dump_free_user_handle_request, (dump_func)dump_set_cursor_request, @@ -5161,6 +5180,8 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = { NULL, (dump_func)dump_get_window_layered_info_reply, NULL, + (dump_func)dump_get_window_class_name_reply, + NULL, (dump_func)dump_alloc_user_handle_reply, NULL, (dump_func)dump_set_cursor_reply, @@ -5448,6 +5469,8 @@ static const char * const req_names[REQ_NB_REQUESTS] = { "set_fd_eof_info", "get_window_layered_info", "set_window_layered_info", + "get_window_class_name", + "set_real_window_class_id", "alloc_user_handle", "free_user_handle", "set_cursor", 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; +} -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/4092
From: Connor McAdams <cmcadams(a)codeweavers.com> Signed-off-by: Connor McAdams <cmcadams(a)codeweavers.com> --- dlls/user32/class.c | 12 ++++++++++-- dlls/user32/tests/class.c | 23 ++++++++++++----------- 2 files changed, 22 insertions(+), 13 deletions(-) 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 57e2dd19e3f..1d8d5ee06d6 100644 --- a/dlls/user32/tests/class.c +++ b/dlls/user32/tests/class.c @@ -1604,16 +1604,16 @@ struct real_class_test { }; static const struct real_class_test class_tests[] = { - { L"Button", "Button", L"Button", "Button", TRUE, TRUE }, - { L"ComboBox", "ComboBox", L"ComboBox", "ComboBox", TRUE, TRUE }, - { L"Edit", "Edit", L"Edit", "Edit", TRUE, TRUE }, - { L"ListBox", "ListBox", L"ListBox", "ListBox", TRUE, TRUE }, - { L"ScrollBar", "ScrollBar", L"ScrollBar", "ScrollBar", TRUE, TRUE }, - { L"Static", "Static", L"Static", "Static", TRUE, TRUE }, - { L"ComboLBox", "ComboLBox", L"ListBox", "ListBox", TRUE, TRUE }, - { L"MDIClient", "MDIClient", L"MDIClient", "MDIClient", TRUE, TRUE }, - { L"#32768", "#32768", L"#32768", "#32768", TRUE, TRUE }, - { L"#32770", "#32770", L"#32770", "#32770", TRUE, TRUE }, + { L"Button", "Button", L"Button", "Button", TRUE, FALSE }, + { L"ComboBox", "ComboBox", L"ComboBox", "ComboBox", TRUE, FALSE }, + { L"Edit", "Edit", L"Edit", "Edit", TRUE, FALSE }, + { L"ListBox", "ListBox", L"ListBox", "ListBox", TRUE, FALSE }, + { L"ScrollBar", "ScrollBar", L"ScrollBar", "ScrollBar", TRUE, FALSE }, + { L"Static", "Static", L"Static", "Static", TRUE, FALSE }, + { L"ComboLBox", "ComboLBox", L"ListBox", "ListBox", TRUE, FALSE }, + { L"MDIClient", "MDIClient", L"MDIClient", "MDIClient", TRUE, FALSE }, + { L"#32768", "#32768", L"#32768", "#32768", TRUE, FALSE }, + { L"#32770", "#32770", L"#32770", "#32770", TRUE, FALSE }, /* Not all built-in classes set real window class. */ { L"Message", "Message", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, FALSE, FALSE }, }; @@ -1623,7 +1623,8 @@ static const struct real_class_test comctl32_class_tests[] = { { L"ComboBox", "ComboBox", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, { L"Edit", "Edit", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, { L"ListBox", "ListBox", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, - { L"ScrollBar", "ScrollBar", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, + /* We don't have a comctl32 scroll control in Wine. */ + { L"ScrollBar", "ScrollBar", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, FALSE, TRUE }, { L"Static", "Static", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, { L"ComboLBox", "ComboLBox", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, }, }; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/4092
V2: Remove accidental whitespace introduction. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48666
Nikolay Sivov (@nsivov) commented about dlls/user32/tests/class.c:
+ strcat(path, "comctl32_class.manifest"); + + create_manifest_file(path, comctl32_manifest); + context = create_test_actctx(path); + ret = DeleteFileA(path); + ok(ret, "Failed to delete manifest file, error %ld.\n", GetLastError()); + + module = GetModuleHandleA("comctl32"); + ok(!module, "comctl32 already loaded\n"); + + ret = ActivateActCtx(context, &cookie); + ok(ret, "Failed to activate context.\n"); + module = GetModuleHandleA("comctl32"); + ok(!module, "comctl32 already loaded\n"); + + test_real_class_names(comctl32_class_tests, ARRAY_SIZE(comctl32_class_tests)); This is testing comctl32 behavior, so it should be moved there.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48668
Nikolay Sivov (@nsivov) commented about dlls/user32/tests/class.c:
+}; + +static const struct real_class_test class_tests[] = { + { L"Button", "Button", L"Button", "Button", TRUE, TRUE }, + { L"ComboBox", "ComboBox", L"ComboBox", "ComboBox", TRUE, TRUE }, + { L"Edit", "Edit", L"Edit", "Edit", TRUE, TRUE }, + { L"ListBox", "ListBox", L"ListBox", "ListBox", TRUE, TRUE }, + { L"ScrollBar", "ScrollBar", L"ScrollBar", "ScrollBar", TRUE, TRUE }, + { L"Static", "Static", L"Static", "Static", TRUE, TRUE }, + { L"ComboLBox", "ComboLBox", L"ListBox", "ListBox", TRUE, TRUE }, + { L"MDIClient", "MDIClient", L"MDIClient", "MDIClient", TRUE, TRUE }, + { L"#32768", "#32768", L"#32768", "#32768", TRUE, TRUE }, + { L"#32770", "#32770", L"#32770", "#32770", TRUE, TRUE }, + /* Not all built-in classes set real window class. */ + { L"Message", "Message", SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A, FALSE, FALSE }, +}; Why do you need to duplicate all of that? E.g. what's a reason to expect W-name being different from A-name?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48669
Auto-generated changes should not be a part of a patch proposed for upstream.[^1] [^2] [^1]: [Wineserver § Help to create a server request](https://wiki.winehq.org/Wineserver#Help_to_create_a_server_request), WineHQ wiki: "As with all generated files, the files generated by make_requests shouldn't be included in your patch. [^2]: [Re: [PATCH] server: Allow skipping debug handle retrieval in get_process_debug_info.](https://www.winehq.org/mailman3/hyperkitty/list/wine-devel(a)winehq.org/mess...), wine-devel Mailing List Archives: "Also, we do not include automatically generated changes (make_requests) in the patches, they are generated during upstream commit." -- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48681
Jinoh Kang (@iamahuman) commented about dlls/win32u/window.c:
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; + }
This code path is not tested, so it's not clear if this behavior matches that of Windows (current and future versions). You should either test that cross-process NtUserSetRealClassId always fails, or convert the ERR to FIXME: ```suggestion:-0+0 if (!win) return FALSE; if (win == WND_OTHER_PROCESS || win == WND_DESKTOP) { FIXME("Can't set real window class for a window belonging to another process\n"); return FALSE; } ``` -- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48704
Jinoh Kang (@iamahuman) commented about dlls/user32/scroll.c:
{ switch(message) { + case WM_CREATE: Do you have a reason for this particular choice of message (`WM_CREATE`)? How about `WM_NCCREATE`?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48705
Esme Povirk (@madewokherd) commented about dlls/user32/tests/class.c:
+ 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, %s\n", len, debugstr_w(exp_real_class_name_w)); + + 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_a), "got %s\n", real_class_name_a); + todo_wine_if(todo) ok(len == strlen(exp_real_class_name_a), "got %ld, %s\n", len, exp_real_class_name_a); +} + +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; + int i; + + /* + * First pass doesn't pass messages through to the class window procedure, What's the point of doing this first test multiple times? Isn't the API usage identical?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48723
On Sat Oct 14 02:21:51 2023 +0000, Jinoh Kang wrote:
This code path is not tested, so it's not clear if this behavior matches that of Windows (current and future versions). You should either test that cross-process NtUserSetRealClassId always fails, or convert the ERR to FIXME: ```suggestion:-4+0 if (!win) return FALSE; if (win == WND_OTHER_PROCESS || win == WND_DESKTOP) { FIXME("Can't set real window class for a window belonging to another process\n"); return FALSE; } ``` Depending on whether any of the builtin winprocs tolerate being called on another process's window, it may not be possible to test.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48724
On Fri Oct 13 18:32:58 2023 +0000, Nikolay Sivov wrote:
Why do you need to duplicate all of that? E.g. what's a reason to expect W-name being different from A-name? No reason in particular other than trying to be thorough. If it's acceptable to just test the A or W version of the functions I can do that instead.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48775
On Mon Oct 16 12:04:39 2023 +0000, Connor McAdams wrote:
No reason in particular other than trying to be thorough. If it's acceptable to just test the A or W version of the functions I can do that instead. You can convert them in the test function to test both.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48776
On Sat Oct 14 16:04:38 2023 +0000, Esme Povirk wrote:
What's the point of doing this first test multiple times? Isn't the API usage identical? I'm not sure which test in particular you mean. If you mean `test_real_class_name`, it's called for both A/W registered classes, and inside of `test_real_class_name` real class name is tested first without having messages passed through to the real class window procedure, and then with messages passed through.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48777
Depending on whether any of the builtin winprocs tolerate being called on another process's window,
AFAIK the real-window-class changing behavior can be observed via mere `WM_NULL`, which is unlikely to rely on process-specific state.
it may not be possible to test.
You mean it may not be possible to test NtUserSetRealClassId *indirectly* via builtin winprocs? If so, what prevents us from calling it directly? `win32u:win32u` already has a few tests for class names. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48778
On Mon Oct 16 12:06:49 2023 +0000, Nikolay Sivov wrote:
You can convert them in the test function to test both. So IIUC, your suggestion is to just store either a wide string or an ascii string in this structure, and then convert the stored constant for testing?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48779
On Mon Oct 16 12:54:55 2023 +0000, Connor McAdams wrote:
So IIUC, your suggestion is to just store either a wide string or an ascii string in this structure, and then convert the stored constant for testing? Yes, I don't see what value is there for storing duplicates.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48780
On Mon Oct 16 12:14:49 2023 +0000, Connor McAdams wrote:
I'm not sure which test in particular you mean. If you mean `test_real_class_name`, it's called for both A/W registered classes, and inside of `test_real_class_name` real class name is tested first without having messages passed through to the real class window procedure, and then with messages passed through. The global variable `pass_msg_to_real_class_wndproc` is being used to pass state implicitly to the window procedure.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48782
On Mon Oct 16 13:21:59 2023 +0000, Jinoh Kang wrote:
The global variable `pass_msg_to_real_class_wndproc` is being used to pass state implicitly to the window procedure. **EDIT**: Actually nevermind, this does not answer your question. Sorry. Hm, now I understand what you mean.
In the `pass_msg_to_real_class_wndproc = 0` case, we're not really testing NtUserSetRealClassId at all. We're merely testing RealGetWindowClass against *slightly* different (mostly just `cbWndExtra` and `style`) classes, all with the same class name (`"SuperClass Test"`) as well as the same wndproc behavior (simply delegates to `DefWindowProcW()`, except for `WM_NCCREATE`). The `pass_msg_to_real_class_wndproc = 0` case should simply be factored out of `test_real_class_name` and be tested separately, instead of being tested for every built-in class (which doesn't make a difference since its builtin wndproc is being ignored). -- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48783
On Mon Oct 16 13:27:21 2023 +0000, Jinoh Kang wrote:
Hm, now I understand what you mean. In the `pass_msg_to_real_class_wndproc = 0` case, we're not really testing NtUserSetRealClassId at all. We're merely testing RealGetWindowClass against *slightly* different (mostly just `cbWndExtra` and `style`) classes, all with the same class name (`"SuperClass Test"`) as well as the same wndproc behavior (simply delegates to `DefWindowProcW()`, except for `WM_NCCREATE`). The `pass_msg_to_real_class_wndproc = 0` case should simply be factored out of `test_real_class_name` and be tested separately, instead of being tested for every built-in class (which doesn't make a difference since its builtin wndproc is being ignored). In short,
```c test_real_class_name() { for (i = 0; i < 2; i++) { pass_msg_to_real_class_wndproc = i; if (!i) test_hwnd_real_class_name_a_w(hwnd, SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A); else test_hwnd_real_class_name_a_w(hwnd, class_test->real_class_name_w, class_test->real_class_name_a); } } test_real_class_names() { for (i = 0; i < tests_count; i++) test_real_class_name(); } ``` This should be just ```c test_real_class_name() { for (i = 0; i < 2; i++) { test_hwnd_real_class_name_a_w(hwnd, class_test->real_class_name_w, class_test->real_class_name_a); } } test_real_class_names() { pass_msg_to_real_class_wndproc = 0; test_hwnd_real_class_name_a_w(hwnd, SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A); pass_msg_to_real_class_wndproc = 1; for (i = 0; i < tests_count; i++) { test_real_class_name(); } } ``` Note that the `pass_msg_to_real_class_wndproc = 0` case is being tested only once in the latter code snippet. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48784
On Mon Oct 16 13:30:46 2023 +0000, Jinoh Kang wrote:
In short, ```c test_real_class_name() { for (i = 0; i < 2; i++) { pass_msg_to_real_class_wndproc = i; if (!i) test_hwnd_real_class_name_a_w(hwnd, SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A); else test_hwnd_real_class_name_a_w(hwnd, class_test->real_class_name_w, class_test->real_class_name_a); } } test_real_class_names() { for (i = 0; i < tests_count; i++) test_real_class_name(); } ``` This should be just ```c test_real_class_name() { for (i = 0; i < 2; i++) { test_hwnd_real_class_name_a_w(hwnd, class_test->real_class_name_w, class_test->real_class_name_a); } } test_real_class_names() { pass_msg_to_real_class_wndproc = 0; test_hwnd_real_class_name_a_w(hwnd, SUPER_CLASS_NAME_W, SUPER_CLASS_NAME_A); pass_msg_to_real_class_wndproc = 1; for (i = 0; i < tests_count; i++) { test_real_class_name(); } } ``` Note that the `pass_msg_to_real_class_wndproc = 0` case is being tested only once in the latter code snippet. Ah, yeah that makes sense. I guess I was mainly going for a clear before->after case here, but doing the first case on each class is definitely redundant.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48785
On Mon Oct 16 12:52:43 2023 +0000, Jinoh Kang wrote:
Depending on whether any of the builtin winprocs tolerate being called on another process's window, AFAIK the real-window-class changing behavior can be observed via mere `WM_NULL`, which is unlikely to rely on process-specific state. it may not be possible to test. You mean it may not be possible to test NtUserSetRealClassId *indirectly* via builtin winprocs? If so, what prevents us from calling it directly? `win32u:win32u` already has a few tests for class names. AFAICT it's a Wine-specific internal introduced by this MR and doesn't exist on Windows.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/4092#note_48814
participants (5)
-
Connor McAdams -
Connor McAdams (@cmcadams) -
Esme Povirk (@madewokherd) -
Jinoh Kang (@iamahuman) -
Nikolay Sivov (@nsivov)