-- v10: 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.
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/user32/tests/class.c | 209 +++++++++++++++++++++++++++++++++++++- 1 file changed, 208 insertions(+), 1 deletion(-)
diff --git a/dlls/user32/tests/class.c b/dlls/user32/tests/class.c index 686fced55ee..54792d92f06 100644 --- a/dlls/user32/tests/class.c +++ b/dlls/user32/tests/class.c @@ -1592,6 +1592,209 @@ 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) + 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. */ + memset(&cls_a, 0, sizeof(cls_a)); + cls_a.lpfnWndProc = ClassTest_WndProc2; + 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); + /* Only need to run cross-process tests once, the first time a "real" window class is set. */ + if (class_test->set_by_wm_null) 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); + /* Only need to run cross-process tests once, the first time a "real" window class is set. */ + if (class_test->set_by_wm_nccreate) 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")) + { + /* 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 +1803,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 +1832,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 | 55 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+)
diff --git a/dlls/comctl32/tests/misc.c b/dlls/comctl32/tests/misc.c index d128c202c67..64f750e66d8 100644 --- a/dlls/comctl32/tests/misc.c +++ b/dlls/comctl32/tests/misc.c @@ -384,6 +384,46 @@ 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; + + 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 +450,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 | 4 ++-- dlls/win32u/class.c | 30 +++++++++++++++++++++++++++--- dlls/win32u/message.c | 26 ++++++++++++++++++++++++++ dlls/win32u/ntuser_private.h | 8 ++++++++ dlls/win32u/tests/win32u.c | 4 ++-- include/ntuser.h | 1 + 6 files changed, 66 insertions(+), 7 deletions(-)
diff --git a/dlls/user32/static.c b/dlls/user32/static.c index f3953f7610a..8bbdb52c048 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; + if (uMsg != WM_NCCREATE) NtUserMessageCall( hwnd, uMsg, wParam, lParam, 0, NtUserStaticWndProc, !unicode );
switch (uMsg) { @@ -467,8 +468,7 @@ LRESULT StaticWndProc_common( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam /* SS_ENHMETAFILE: Despite what MSDN says, Windows does not load the enhanced metafile that was specified as the window text. */ } - return unicode ? DefWindowProcW(hwnd, uMsg, wParam, lParam) : - DefWindowProcA(hwnd, uMsg, wParam, lParam); + return NtUserMessageCall( hwnd, uMsg, wParam, lParam, 0, NtUserStaticWndProc, !unicode );
case WM_SETTEXT: if (hasTextStyle( full_style )) 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/message.c b/dlls/win32u/message.c index d2909339983..7c2919506e9 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -4241,6 +4241,27 @@ BOOL WINAPI NtUserPostThreadMessage( DWORD thread, UINT msg, WPARAM wparam, LPAR return put_message_in_queue( &info, NULL ); }
+static void set_real_window_class_id( HWND hwnd, unsigned int real_class_id ) +{ + WND *win; + + if (!(win = get_win_ptr( hwnd )) || (win == WND_OTHER_PROCESS || win == WND_DESKTOP)) + { + if (win == WND_OTHER_PROCESS || win == WND_DESKTOP) + ERR("Tried to set real window class on a window belonging to another process.\n"); + return; + } + + if (!win->real_class_id) + { + FIXME("Real class ID currently set in-process only.\n"); + win->real_class_id = real_class_id; + } + else if (win->real_class_id != real_class_id) + ERR("Tried to change real class ID value after it was already set.\n"); + release_win_ptr( win ); +} + LRESULT WINAPI NtUserMessageCall( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, void *result_info, DWORD type, BOOL ansi ) { @@ -4307,6 +4328,11 @@ LRESULT WINAPI NtUserMessageCall( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lpa case NtUserImeDriverCall: return ime_driver_call( hwnd, msg, wparam, lparam, result_info );
+ case NtUserStaticWndProc: + set_real_window_class_id( hwnd, REAL_CLASS_ID_STATIC ); + if (msg == WM_NCCREATE) return default_window_proc( hwnd, msg, wparam, lparam, ansi ); + return 0; + default: FIXME( "%p %x %lx %lx %p %x %x\n", hwnd, msg, (long)wparam, lparam, result_info, (int)type, ansi ); } diff --git a/dlls/win32u/ntuser_private.h b/dlls/win32u/ntuser_private.h index b39e38db5d6..92b78453b24 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 */ @@ -176,6 +177,13 @@ enum builtin_winprocs NB_BUILTIN_AW_WINPROCS = WINPROC_DESKTOP };
+/* Real window class ID values. */ +enum wine_real_class_id +{ + REAL_CLASS_ID_NONE = 0, + REAL_CLASS_ID_STATIC, +}; + /* FIXME: make it private to scroll.c */
/* data for a single scroll bar */ 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/include/ntuser.h b/include/ntuser.h index 171d32abe6e..777b4e8fd58 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -306,6 +306,7 @@ enum NtUserSpyEnter = 0x0303, NtUserSpyExit = 0x0304, NtUserImeDriverCall = 0x0305, + NtUserStaticWndProc = 0x0306, };
/* NtUserThunkedMenuItemInfo codes */
From: Connor McAdams cmcadams@codeweavers.com
Signed-off-by: Connor McAdams cmcadams@codeweavers.com --- dlls/win32u/class.c | 54 ++++++++++++++++++++----------------------- dlls/win32u/message.c | 8 ++++++- server/protocol.def | 16 +++++++++++++ server/window.c | 21 +++++++++++++++++ 4 files changed, 69 insertions(+), 30 deletions(-)
diff --git a/dlls/win32u/class.c b/dlls/win32u/class.c index 8e640ddb525..fcc8cdf4fd6 100644 --- a/dlls/win32u/class.c +++ b/dlls/win32u/class.c @@ -622,8 +622,10 @@ 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; + ATOM atom = 0; int ret = 0; + WND *wnd;
TRACE( "%p %x %p\n", hwnd, real, name );
@@ -633,49 +635,43 @@ INT WINAPI NtUserGetClassName( HWND hwnd, BOOL real, UNICODE_STRING *name ) return 0; }
- if (!(class = get_class_ptr( hwnd, FALSE ))) return 0; - - if (class == OBJ_OTHER_PROCESS) + if (!(wnd = get_win_ptr( hwnd ))) { - ATOM atom = 0; + RtlSetLastWin32Error( ERROR_INVALID_WINDOW_HANDLE ); + return 0; + }
- SERVER_START_REQ( set_class_info ) + if (wnd == WND_OTHER_PROCESS || wnd == WND_DESKTOP) + { + 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 ); + 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]; } - - if (!basename) - basename = (const WCHAR *)class->basename; + else if (wnd) + basename = (const WCHAR *)wnd->class->basename; + else + return NtUserGetAtomName( atom, name );
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/message.c b/dlls/win32u/message.c index 7c2919506e9..0e82ae14e7c 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -4254,7 +4254,13 @@ static void set_real_window_class_id( HWND hwnd, unsigned int real_class_id )
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 = real_class_id; + wine_server_call( req ); + } + SERVER_END_REQ; win->real_class_id = real_class_id; } else if (win->real_class_id != real_class_id) 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 54792d92f06..431aa380b42 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 },
On Wed Nov 1 15:52:22 2023 +0000, Connor McAdams wrote:
Well, at least in this case, we could avoid calling `NtUserMessageCall` at the start of the procedure if the message is `WM_NCCREATE`, and then call it at the end of the `WM_NCCREATE` switch case instead of:
return unicode ? DefWindowProcW(hwnd, uMsg, wParam, lParam) : DefWindowProcA(hwnd, uMsg, wParam, lParam);
which would avoid a double call to `NtUserMessageCall` I presume.
I've changed this in the most recent revision to only call `NtUserMessageCall` once instead of twice in the case of `WM_NCCREATE`.
Why isn't this using an atom like the normal class name? And why does it need new requests as opposed to extending `set_class_info`?
On Mon Nov 20 14:47:33 2023 +0000, Alexandre Julliard wrote:
Why isn't this using an atom like the normal class name? And why does it need new requests as opposed to extending `set_class_info`?
1. The idea behind using an enum instead of an atom was that there's a fixed set of builtin user32 window classes that can set "real" window class for an HWND, and looking up the atom each time was added complexity. We could store an atom instead though if you'd prefer that, although AFAIU that'd require a lookup each time since I don't think class atoms stay constant.
2. `set_class_info` is per window class, and my understanding was that that request was meant to set information for a specific window class. The real window class value is a per-HWND thing, meaning multiple HWNDs of the same window class could have different real class values. Pulling in the information to do this inside of `set_class_info` seems tricky, since we can't access the `struct window` without importing the definition into `server/class.c`.
Why isn't this using an atom like the normal class name?
We certainly can. Pros:
1. `NtUserGetClassName` can be slightly simplified in other-process-window case. 2. `NtUserGetClassName` will no longer need fallback-to-non-real-class behavior. We can simply set the real-class-atom to the normal-class-atom. 3. We'll no longer need `enum wine_real_class_id` as well as `real_class_id_str`.
However, there are a few questions that I couldn't find an optimal answer that you'll certainly like.
1. **Name-to-atom** mapping: a real-class-name (e.g. `Static`) has to be mapped *at some point* to an atom for use in `struct class`. When should the atoms be created?
1. Reserve (and possibly pin) the atoms on initialization[^init-by-whom]. If we go this route, we probably want to keep `wine_real_class_id` in some form, which means this is merely swapping one indirection for another.
2. Look up the atoms when `set_real_window_class_id` is called. This allows us to avoid reserving atoms at startup, but slows down initialization of certain user32 controls.
3. Other solutions: I don't believe there are any; please correct me if this is not the case.
2. **Atom-to-name** mapping: the set of possible "real class names" is fixed; the user cannot add more. On the other hand, the real-class-atom is an *indirection* that allows arbitrary "real class names." This is arguably a bloat[^that-can-be-justified]. Where should we introduce it?
1. Add a field (e.g. `WCHAR[9] real_class_name;`[^why-nine], +18 bytes[^mem] of memory use) to `WND` structure in `ntuser_private.h` for the cached real-class-name.
2. Add a server call (process context switch) in `NtUserGetClassName()` to fetch the real-class-name from the atom. This can slow down oleacc and/or UIAutomation APIs, although @cmcadams has a better insight.
3. Other solutions: I don't believe there are any; please correct me if this is not the case.
I'm okay with any of the choices above. I was merely raising this issue because the approaches above may end up adding more complexity.
[^init-by-whom]: This can happen in the server, `win32u.dll`, or `win32u.so`. [^that-can-be-justified]: I said "bloat" because we're allocating unnecessary resources, but this is not necessarily a bad thing if it makes code readable. [^why-nine]: The longest known real-class-names in the test are `ScrollBar` (9 characters) and `MDIClient` (9 characters). [^mem]: +6.25% for 64-bit. +9% for 32-bit.
And why does it need new requests as opposed to extending `set_class_info`?
Two windows can have the same class but *different* "real class names[^intended]."
To allow this, we have to change the role of `set_class_info` from
- "Fetch or modify the class used by the given window"
to
- "Fetch or modify the class used by the given window *as well as* window-specific information (real-class-name) not specified in the class."
[^intended]: Subclassing USER32 controls happens in per-window fashion, not per-class. The application can reuse the same custom class to subclass BUTTON and STATIC, for example.