-- v7: 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 static 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 | 211 +++++++++++++++++++++++++++++++++++++- 1 file changed, 210 insertions(+), 1 deletion(-)
diff --git a/dlls/user32/tests/class.c b/dlls/user32/tests/class.c index 686fced55ee..5455358edfe 100644 --- a/dlls/user32/tests/class.c +++ b/dlls/user32/tests/class.c @@ -1592,6 +1592,211 @@ static void test_class_name(void) UnregisterClassW(class_name, hinst); }
+#define SUPER_CLASS_NAME_A "SuperClass Test" +struct real_class_test { + const char *class_name; + const char *real_class_name; + BOOL set_by_wm_null; + BOOL set_by_wm_create; + BOOL set_by_wm_nccreate; + BOOL test_cross_process; + BOOL wine_todo; +}; + +static const struct real_class_test class_tests[] = { + { "Button", "Button", FALSE, FALSE, TRUE, TRUE, TRUE }, + { "ComboBox", "ComboBox", FALSE, TRUE, TRUE, TRUE, TRUE }, + { "Edit", "Edit", FALSE, FALSE, TRUE, TRUE, TRUE }, + { "ListBox", "ListBox", FALSE, TRUE, TRUE, TRUE, TRUE }, + { "ScrollBar", "ScrollBar", FALSE, TRUE, FALSE, TRUE, TRUE }, + { "Static", "Static", TRUE, TRUE, TRUE, TRUE, TRUE }, + { "ComboLBox", "ListBox", FALSE, TRUE, TRUE, TRUE, TRUE }, + { "MDIClient", "MDIClient", TRUE, TRUE, TRUE, TRUE, TRUE }, + { "#32768", "#32768", FALSE, FALSE, TRUE, TRUE, TRUE }, + { "#32770", "#32770", TRUE, TRUE, TRUE, TRUE, TRUE }, + /* Not all built-in classes set real window class. */ + { "Message", SUPER_CLASS_NAME_A, FALSE, FALSE, FALSE, FALSE, FALSE }, +}; + +static WNDPROC real_class_wndproc; +static UINT real_class_wndproc_passthrough_msg; +static LRESULT WINAPI super_class_test_win_proc_a(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + if ((msg == real_class_wndproc_passthrough_msg) && real_class_wndproc) + CallWindowProcA(real_class_wndproc, hwnd, msg, wparam, lparam); + + if (msg == WM_NCCREATE) + return 1; + + return 0; +} + +#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_set_msg(const char *real_class_str, UINT msg, BOOL exp_set, BOOL test_cross_proc, + BOOL todo) +{ + const char *exp_real_class_str = exp_set ? real_class_str : SUPER_CLASS_NAME_A; + const CLIENTCREATESTRUCT client_cs = { NULL, 1 }; /* Needed for MDIClient. */ + HWND hwnd; + + real_class_wndproc_passthrough_msg = msg; + 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 (msg == WM_NULL) SendMessageA(hwnd, WM_NULL, 0, 0); + test_hwnd_real_class_name_str(hwnd, exp_real_class_str, exp_set ? todo : FALSE); + if (exp_set && test_cross_proc) + { + 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_RealGetWindowClass %#lx %s %d", argv[0], argv[1], HandleToUlong(hwnd), exp_real_class_str, todo); + 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]; + 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)); + + for (i = 0; i < ARRAY_SIZE(class_tests); i++) + { + const struct real_class_test *class_test = &class_tests[i]; + BOOL test_cross_process = class_test->test_cross_process; + + memset(&cls_a, 0, sizeof(cls_a)); + ret_b = GetClassInfoA(NULL, class_test->class_name, &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 if we only pass through WM_NULL. */ + test_real_class_name_set_msg(class_test->real_class_name, WM_NULL, class_test->set_by_wm_null, test_cross_process, + class_test->wine_todo); + if (class_test->set_by_wm_null && test_cross_process) test_cross_process = FALSE; + + /* Test real class name if we only pass through WM_NCCREATE. */ + test_real_class_name_set_msg(class_test->real_class_name, WM_NCCREATE, class_test->set_by_wm_nccreate, test_cross_process, + class_test->wine_todo); + if (class_test->set_by_wm_nccreate && test_cross_process) test_cross_process = FALSE; + + /* + * If we pass WM_CREATE without WM_NCCREATE first to the Edit window + * procedure, it will trigger a divide by zero exception. Just skip this test. + */ + if (!strcmp(class_test->class_name, "Edit")) + { + UnregisterClassA(SUPER_CLASS_NAME_A, GetModuleHandleA(NULL)); + continue; + } + + /* Test real class name if we only pass through WM_CREATE. */ + test_real_class_name_set_msg(class_test->real_class_name, WM_CREATE, class_test->set_by_wm_create, test_cross_process, + class_test->wine_todo); + UnregisterClassA(SUPER_CLASS_NAME_A, GetModuleHandleA(NULL)); + } + + real_class_wndproc = NULL; +} + START_TEST(class) { char **argv; @@ -1600,7 +1805,10 @@ START_TEST(class)
if (argc >= 3) { - test_comctl32_class( argv[2] ); + if (!strcmp(argv[2], "test_RealGetWindowClass")) + test_hwnd_real_class_name_str( UlongToHandle(strtol(argv[3], NULL, 16)), argv[4], strtol(argv[5], NULL, 0) ); + else + test_comctl32_class( argv[2] ); return; }
@@ -1626,6 +1834,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 | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+)
diff --git a/dlls/comctl32/tests/misc.c b/dlls/comctl32/tests/misc.c index d128c202c67..c80082cfca0 100644 --- a/dlls/comctl32/tests/misc.c +++ b/dlls/comctl32/tests/misc.c @@ -384,6 +384,48 @@ static void test_LoadIconWithScaleDown(void) FreeLibrary(hinst); }
+#define SUPER_CLASS_NAME_A "SuperClass Test" +static WNDPROC real_class_wndproc; +static const char *real_class_str; +static LRESULT WINAPI super_class_test_win_proc_a(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + LRESULT lr = 0; + + /* + * Passing through WM_GETTEXT to the WC_IPADDRESSA procedure will cause an + * access violation on Windows 7 64-bit. + */ + if ((msg == WM_GETTEXT) && !strcmp(real_class_str, WC_IPADDRESSA)) + return lr; + + if (real_class_wndproc) + lr = CallWindowProcA(real_class_wndproc, hwnd, msg, wparam, lparam); + + if (msg == WM_NCCREATE) + lr = 1; + + return lr; +} + +#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 +452,21 @@ 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; + real_class_str = name; + 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; + real_class_str = 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 | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+)
diff --git a/dlls/win32u/tests/win32u.c b/dlls/win32u/tests/win32u.c index e118d081ead..e1b21917682 100644 --- a/dlls/win32u/tests/win32u.c +++ b/dlls/win32u/tests/win32u.c @@ -126,6 +126,20 @@ 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) +{ + LRESULT lr = 0; + + if (real_class_wndproc) + lr = CallWindowProcW(real_class_wndproc, hwnd, msg, wparam, lparam); + + if (msg == WM_NCCREATE) + lr = 1; + + return lr; +} + static void test_class(void) { UNICODE_STRING name; @@ -219,6 +233,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"Static", &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 Static. */ + memset( buf, 0xcc, sizeof(buf) ); + name.Buffer = buf; + name.Length = 0xdead; + name.MaximumLength = sizeof(buf); + ret = NtUserGetClassName( hwnd, TRUE, &name ); + todo_wine ok( ret == 6, "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"Static" ), "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/static.c | 1 + dlls/win32u/class.c | 30 +++++++++++++++++++++++++++--- dlls/win32u/ntuser_private.h | 1 + dlls/win32u/tests/win32u.c | 4 ++-- dlls/win32u/window.c | 23 +++++++++++++++++++++++ include/ntuser.h | 13 +++++++++++++ 6 files changed, 67 insertions(+), 5 deletions(-)
diff --git a/dlls/user32/static.c b/dlls/user32/static.c index f3953f7610a..431381e3421 100644 --- a/dlls/user32/static.c +++ b/dlls/user32/static.c @@ -324,6 +324,7 @@ LRESULT StaticWndProc_common( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam LONG style = full_style & SS_TYPEMASK;
if (!IsWindow( hwnd )) return 0; + NtUserSetRealClassId(hwnd, REAL_CLASS_ID_STATIC);
switch (uMsg) { diff --git a/dlls/win32u/class.c b/dlls/win32u/class.c index 396e2285797..8e640ddb525 100644 --- a/dlls/win32u/class.c +++ b/dlls/win32u/class.c @@ -612,13 +612,18 @@ ULONG WINAPI NtUserGetAtomName( ATOM atom, UNICODE_STRING *name ) return size / sizeof(WCHAR); }
+static const WCHAR real_class_id_str[][MAX_ATOM_LEN + 1] = { + { 'S', 't', 'a', 't', 'i', 'c', 0, }, /* REAL_CLASS_ID_STATIC */ +}; + /*********************************************************************** * 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 +653,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 e1b21917682..ec051d806bd 100644 --- a/dlls/win32u/tests/win32u.c +++ b/dlls/win32u/tests/win32u.c @@ -254,10 +254,10 @@ static void test_class(void) name.Length = 0xdead; name.MaximumLength = sizeof(buf); ret = NtUserGetClassName( hwnd, TRUE, &name ); - todo_wine ok( ret == 6, "NtUserGetClassName returned %lu\n", ret ); + ok( ret == 6, "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"Static" ), "buf = %s\n", debugstr_w(buf) ); + ok( !wcscmp( buf, L"Static" ), "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..412f1d73bd2 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -4491,6 +4491,26 @@ 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) return FALSE; + if (win == WND_OTHER_PROCESS || win == WND_DESKTOP) + { + ERR("Tried to set real window class on 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.@) */ @@ -5588,6 +5608,9 @@ ULONG_PTR WINAPI NtUserCallHwndParam( HWND hwnd, DWORD_PTR param, DWORD code ) case NtUserCallHwndParam_ShowOwnedPopups: return show_owned_popups( hwnd, param );
+ case NtUserCallHwndParam_SetRealClassId: + return set_real_window_class_id( hwnd, param ); + /* temporary exports */ case NtUserSetWindowStyle: { diff --git a/include/ntuser.h b/include/ntuser.h index 171d32abe6e..1b3fc943a07 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -513,6 +513,13 @@ 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_STATIC, +}; +
HKL WINAPI NtUserActivateKeyboardLayout( HKL layout, UINT flags ); BOOL WINAPI NtUserAddClipboardFormatListener( HWND hwnd ); @@ -1208,6 +1215,7 @@ enum NtUserCallHwndParam_SetMDIClientInfo, NtUserCallHwndParam_SetWindowContextHelpId, NtUserCallHwndParam_ShowOwnedPopups, + NtUserCallHwndParam_SetRealClassId, /* temporary exports */ NtUserSetWindowStyle, }; @@ -1378,6 +1386,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 8e640ddb525..e4d765c295e 100644 --- a/dlls/win32u/class.c +++ b/dlls/win32u/class.c @@ -622,8 +622,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 );
@@ -633,49 +634,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 412f1d73bd2..462da484be3 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -4504,7 +4504,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/user32/class.c | 12 ++++++++++-- dlls/user32/tests/class.c | 2 +- 2 files changed, 11 insertions(+), 3 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 5455358edfe..ab93bcd4ac1 100644 --- a/dlls/user32/tests/class.c +++ b/dlls/user32/tests/class.c @@ -1609,7 +1609,7 @@ static const struct real_class_test class_tests[] = { { "Edit", "Edit", FALSE, FALSE, TRUE, TRUE, TRUE }, { "ListBox", "ListBox", FALSE, TRUE, TRUE, TRUE, TRUE }, { "ScrollBar", "ScrollBar", FALSE, TRUE, FALSE, TRUE, TRUE }, - { "Static", "Static", TRUE, TRUE, TRUE, TRUE, TRUE }, + { "Static", "Static", TRUE, TRUE, TRUE, TRUE, FALSE }, { "ComboLBox", "ListBox", FALSE, TRUE, TRUE, TRUE, TRUE }, { "MDIClient", "MDIClient", TRUE, TRUE, TRUE, TRUE, TRUE }, { "#32768", "#32768", FALSE, FALSE, TRUE, TRUE, TRUE },
This merge request was approved by Esme Povirk.
Jinoh Kang (@iamahuman) commented about dlls/comctl32/tests/misc.c:
+#define SUPER_CLASS_NAME_A "SuperClass Test" +static WNDPROC real_class_wndproc; +static const char *real_class_str; +static LRESULT WINAPI super_class_test_win_proc_a(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{
- LRESULT lr = 0;
- /*
* Passing through WM_GETTEXT to the WC_IPADDRESSA procedure will cause an
* access violation on Windows 7 64-bit.
*/
- if ((msg == WM_GETTEXT) && !strcmp(real_class_str, WC_IPADDRESSA))
return lr;
- if (real_class_wndproc)
This condition is always true (in comctl32:misc).
Jinoh Kang (@iamahuman) commented about dlls/user32/tests/class.c:
- 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;
We don't need `super_class_test_win_proc_a` here.
```suggestion:-2+0 memset(&cls_a, 0, sizeof(cls_a)); cls_a.lpfnWndProc = ClassTest_WndProc2; ```
Jinoh Kang (@iamahuman) commented about dlls/user32/tests/class.c:
- { "ListBox", "ListBox", FALSE, TRUE, TRUE, TRUE, TRUE },
- { "ScrollBar", "ScrollBar", FALSE, TRUE, FALSE, TRUE, TRUE },
- { "Static", "Static", TRUE, TRUE, TRUE, TRUE, FALSE },
- { "ComboLBox", "ListBox", FALSE, TRUE, TRUE, TRUE, TRUE },
- { "MDIClient", "MDIClient", TRUE, TRUE, TRUE, TRUE, TRUE },
- { "#32768", "#32768", FALSE, FALSE, TRUE, TRUE, TRUE },
- { "#32770", "#32770", TRUE, TRUE, TRUE, TRUE, TRUE },
- /* Not all built-in classes set real window class. */
- { "Message", SUPER_CLASS_NAME_A, FALSE, FALSE, FALSE, FALSE, FALSE },
+};
+static WNDPROC real_class_wndproc; +static UINT real_class_wndproc_passthrough_msg; +static LRESULT WINAPI super_class_test_win_proc_a(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{
- if ((msg == real_class_wndproc_passthrough_msg) && real_class_wndproc)
If we eliminate the `real_class_wndproc = NULL` case, then we can simplify this to:
```suggestion:-0+0 if (msg == real_class_wndproc_passthrough_msg) ```
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) return FALSE;
- if (win == WND_OTHER_PROCESS || win == WND_DESKTOP)
- {
ERR("Tried to set real window class on a window belonging to another process.\n");
return FALSE;
- }
- if (!win->real_class_id)
Is this merely an optimization to opportunistically avoid server calls? If so, it should behave the same as if the `if()` guard did not exist.
```suggestion:-0+0 if (win->real_class_id != param) ```
Jinoh Kang (@iamahuman) commented about dlls/user32/tests/class.c:
BOOL test_cross_process = class_test->test_cross_process;
memset(&cls_a, 0, sizeof(cls_a));
ret_b = GetClassInfoA(NULL, class_test->class_name, &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 if we only pass through WM_NULL. */
test_real_class_name_set_msg(class_test->real_class_name, WM_NULL, class_test->set_by_wm_null, test_cross_process,
class_test->wine_todo);
if (class_test->set_by_wm_null && test_cross_process) test_cross_process = FALSE;
The condition can be simplified a bit. ```suggestion:-0+0 /* Please explain why we're skipping further cross-process tests here. */ if (class_test->set_by_wm_null) test_cross_process = FALSE; ```
Jinoh Kang (@iamahuman) commented about dlls/user32/tests/class.c:
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 if we only pass through WM_NULL. */
test_real_class_name_set_msg(class_test->real_class_name, WM_NULL, class_test->set_by_wm_null, test_cross_process,
class_test->wine_todo);
if (class_test->set_by_wm_null && test_cross_process) test_cross_process = FALSE;
/* Test real class name if we only pass through WM_NCCREATE. */
test_real_class_name_set_msg(class_test->real_class_name, WM_NCCREATE, class_test->set_by_wm_nccreate, test_cross_process,
class_test->wine_todo);
if (class_test->set_by_wm_nccreate && test_cross_process) test_cross_process = FALSE;
Ditto. ```suggestion:-0+0 /* Please explain why we're skipping further cross-process tests here. */ if (class_test->set_by_wm_nccreate) test_cross_process = FALSE; ```
Jinoh Kang (@iamahuman) commented about dlls/user32/tests/class.c:
class_test->wine_todo);
if (class_test->set_by_wm_nccreate && test_cross_process) test_cross_process = FALSE;
/*
* If we pass WM_CREATE without WM_NCCREATE first to the Edit window
* procedure, it will trigger a divide by zero exception. Just skip this test.
*/
if (!strcmp(class_test->class_name, "Edit"))
{
UnregisterClassA(SUPER_CLASS_NAME_A, GetModuleHandleA(NULL));
continue;
}
/* Test real class name if we only pass through WM_CREATE. */
test_real_class_name_set_msg(class_test->real_class_name, WM_CREATE, class_test->set_by_wm_create, test_cross_process,
class_test->wine_todo);
```suggestion:-8+0 if (stricmp(class_test->class_name, "Edit") != 0) { /* Test real class name if we only pass through WM_CREATE. */ test_real_class_name_set_msg(class_test->real_class_name, WM_CREATE, class_test->set_by_wm_create, test_cross_process, class_test->wine_todo); } ```
Jinoh Kang (@iamahuman) commented about dlls/win32u/class.c:
{
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 );
This condition is exactly equivalent to `!real || !real_class_id`[^1], which is the inverted version of `real && real_class_id` below.
Code duplication often encourages code rot: someone could update the latter code but not former[^2], or vice versa.
Instead, I suggest deleting this code...
```suggestion:-0+0 ```
[^1]: `(~P | (P & ~Q)) = ((~P | P) & (~P | ~Q)) = (1 & (~P | ~Q)) = (~P | ~Q)` [^2]: I've dealt with a similar problem before, in https://gitlab.winehq.org/wine/wine/-/merge_requests/3322.
Jinoh Kang (@iamahuman) commented about dlls/win32u/class.c:
}
- else
real_class_id = wnd->real_class_id;
- ret = min( name->MaximumLength / sizeof(WCHAR) - 1, lstrlenW(class->basename) );
- if (ret) memcpy( name->Buffer, class->basename, ret * sizeof(WCHAR) );
- if (real && real_class_id)
- {
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;
- }
...and deal with the atom in a centralized place. I think this is more straightforward: _real_ first, _local_ second, and _remote_ last.
```suggestion:-4+0 else if (wnd) { basename = (const WCHAR *)wnd->class->basename; } else return NtUserGetAtomName( atom, name ); ```
Of course, the `ATOM atom;` declaration has to be hoisted out of the prior `if` scope.
Jinoh Kang (@iamahuman) commented about include/ntuser.h:
return NtUserCallHwndParam( hwnd, show, NtUserCallHwndParam_ShowOwnedPopups );
}
+static inline BOOL NtUserSetRealClassId( HWND hwnd, DWORD real_class_id )
Adding `NtUser`- call that does not exist on Windows is unprecedented.
See `NtUserSetWindowStyle` above (in "temporary exports" section) for how Wine-specific win32k calls are usually done.
On Fri Oct 20 21:30:59 2023 +0000, Jinoh Kang wrote:
Is this merely an optimization to opportunistically avoid server calls? If so, it should behave the same as if the `if()` guard did not exist.
if (win->real_class_id != param)
I didn't include it in the tests, but locally I've tested whether or not you can change the real class for an HWND after it has been set, e.g setting it to edit by passing the HWND to the edit control window procedure, and then passing it into the static control window procedure. It's only able to be set once.
If we did your suggestion, this would technically break. So it serves two purposes, avoids unnecessary server calls, and avoids setting it to something else after it has already been set.
On Mon Oct 23 12:52:08 2023 +0000, Connor McAdams wrote:
I didn't include it in the tests, but locally I've tested whether or not you can change the real class for an HWND after it has been set, e.g setting it to edit by passing the HWND to the edit control window procedure, and then passing it into the static control window procedure. It's only able to be set once. If we did your suggestion, this would technically break. So it serves two purposes, avoids unnecessary server calls, and avoids setting it to something else after it has already been set.
I see. In that case, it would be nice to leave an one-liner comment to describe that. That, or `ERR()` if the real class is already set.
Note that normal apps (subclassing applications, WinForms) won't trigger the `ERR()` path. Misbehaving apps will, but we can skip testing for this until such an app appears.
Just in case: feel free to close/resolve my threads if you don't think they are necessary. I wouldn't block this MR on the grounds of minor nits, and I'm not a maintainer of user32/win32u either. Thanks!