[PATCH v2 0/5] MR11012: Draft: win32u: Implement real class name for static window class.
-- v2: win32u: Implement real class names using builtin class FNID. user32: Pass real argument to NtUserGetClassName in RealGetWindowClass{A/W}. win32u/tests: Add a test for real window class name retrieval. comctl32/tests: Add tests for RealGetWindowClass. user32/tests: Add tests for RealGetWindowClass. https://gitlab.winehq.org/wine/wine/-/merge_requests/11012
From: Connor McAdams <cmcadams@codeweavers.com> --- dlls/user32/tests/class.c | 214 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/dlls/user32/tests/class.c b/dlls/user32/tests/class.c index 49bec91ff9c..bfc932e25bc 100644 --- a/dlls/user32/tests/class.c +++ b/dlls/user32/tests/class.c @@ -50,6 +50,31 @@ static const BOOL is_win64 = (sizeof(void *) > sizeof(int)); +#define run_in_process( a ) run_in_process_( __FILE__, __LINE__, a ) +static void run_in_process_( const char *file, int line, const char *args ) +{ + char cmdline[MAX_PATH * 2], test[MAX_PATH], *tmp, **argv; + STARTUPINFOA startup = {.cb = sizeof(STARTUPINFOA)}; + PROCESS_INFORMATION info = {0}; + const char *name; + DWORD ret; + int argc; + + name = file; + if ((tmp = strrchr( name, '\\' ))) name = tmp; + if ((tmp = strrchr( name, '/' ))) name = tmp; + strcpy( test, name ); + if ((tmp = strrchr( test, '.' ))) *tmp = 0; + + argc = winetest_get_mainargs( &argv ); + sprintf( cmdline, "%s %s %s", argv[0], argc > 1 ? argv[1] : test, args ); + ret = CreateProcessA( NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &info ); + ok_(file, line)( ret, "CreateProcessA failed, error %lu\n", GetLastError() ); + if (!ret) return; + + wait_child_process( &info ); +} + static const char comctl32_manifest[] = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" "<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n" @@ -2482,12 +2507,200 @@ void test_class_multithread(void) CloseHandle(thread_unregister); } +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", NULL, FALSE, FALSE, FALSE, FALSE, FALSE }, +}; + +static WNDPROC real_class_wndproc; +static UINT real_class_message; +static WNDCLASSA test_class; + +static LRESULT WINAPI test_real_class_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) +{ + if (msg == real_class_message) CallWindowProcA( real_class_wndproc, hwnd, msg, wparam, lparam ); + if (msg == WM_NCCREATE) return 1; + return 0; +} + +#define check_real_class_name( a, b, c ) check_real_class_name_( __LINE__, a, b, c ) +static void check_real_class_name_( int line, HWND hwnd, const char *expect, BOOL todo ) +{ + WCHAR expectW[256], nameW[256]; + char nameA[256]; + ULONG len; + + len = RealGetWindowClassA( hwnd, nameA, ARRAY_SIZE(nameA) ); + todo_wine_if( todo ) ok_(__FILE__, line)( !strcmp( nameA, expect ), "got %s\n", nameA ); + todo_wine_if( todo ) ok_(__FILE__, line)( len == strlen( expect ), "got %ld\n", len ); + + MultiByteToWideChar( CP_ACP, 0, expect, -1, expectW, ARRAY_SIZE(expectW)); + len = RealGetWindowClassW( hwnd, nameW, ARRAY_SIZE(nameW)); + todo_wine_if( todo ) ok_(__FILE__, line)( !wcscmp( nameW, expectW ), "got %s\n", debugstr_w(nameW)); + todo_wine_if( todo ) ok_(__FILE__, line)( len == wcslen( expectW ), "got %ld\n", len ); +} + +static void test_real_class_name_msg( UINT msg, const char *expect, BOOL cross_process, BOOL todo ) +{ + const CLIENTCREATESTRUCT client_cs = {NULL, 1}; /* Needed for MDIClient. */ + HWND hwnd; + + real_class_message = msg; + hwnd = CreateWindowA( test_class.lpszClassName, "test", WS_OVERLAPPED, 0, 0, 50, 50, 0, 0, 0, (void *)&client_cs ); + ok( !!hwnd, "got %p\n", hwnd ); + if (msg == WM_NULL) SendMessageA( hwnd, WM_NULL, 0, 0 ); + + check_real_class_name( hwnd, expect ? expect : test_class.lpszClassName, expect ? todo : FALSE ); + if (expect && cross_process) + { + char cmdline[MAX_PATH]; + sprintf( cmdline, "test_RealGetWindowClass %p %s %d", hwnd, expect, todo ); + run_in_process( cmdline ); + } + + DestroyWindow( hwnd ); +} + +static void test_RealGetWindowClass( void ) +{ + WCHAR class_name_w[256]; + char class_name[20]; + 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( &test_class, 0, sizeof(test_class) ); + test_class.lpfnWndProc = ClassTest_WndProc2; + test_class.hInstance = GetModuleHandleA( NULL ); + test_class.lpszClassName = "WineTest Class"; + RegisterClassA( &test_class ); + + hwnd = CreateWindowA( test_class.lpszClassName, "test", WS_OVERLAPPED, 0, 0, 50, 50, 0, 0, 0, NULL ); + ok( !!hwnd, "hwnd == NULL\n" ); + + check_real_class_name( hwnd, test_class.lpszClassName, FALSE ); + + DestroyWindow( hwnd ); + UnregisterClassA( test_class.lpszClassName, 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( &test_class, 0, sizeof(test_class) ); + ret = GetClassInfoA( NULL, class_test->class_name, &test_class ); + ok( ret, "GetClassInfoA failed: %lu\n", GetLastError() ); + real_class_wndproc = test_class.lpfnWndProc; + test_class.lpfnWndProc = test_real_class_wndproc; + test_class.hInstance = GetModuleHandleA( NULL ); + test_class.lpszClassName = "WineTest Class"; + RegisterClassA( &test_class ); + + test_real_class_name_msg( WM_NULL, class_test->set_by_wm_null ? class_test->real_class_name : NULL, + test_cross_process, class_test->wine_todo ); + if (class_test->set_by_wm_null) test_cross_process = FALSE; + + test_real_class_name_msg( WM_NCCREATE, class_test->set_by_wm_nccreate ? class_test->real_class_name : NULL, + test_cross_process, class_test->wine_todo ); + 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" )) skip( "Skipping edit class\n"); + else test_real_class_name_msg( WM_CREATE, class_test->set_by_wm_create ? class_test->real_class_name : NULL, + test_cross_process, class_test->wine_todo ); + UnregisterClassA( test_class.lpszClassName, GetModuleHandleA( NULL ) ); + } + + real_class_wndproc = NULL; +} + +static void test_RealGetWindowClass_process( HWND hwnd, const char *expect, BOOL todo ) +{ + check_real_class_name( hwnd, expect, todo ); +} + START_TEST(class) { char **argv; HANDLE hInstance = GetModuleHandleA( NULL ); int argc = winetest_get_mainargs( &argv ); + if (argc >= 3 && !strcmp( argv[2], "test_RealGetWindowClass" )) + return test_RealGetWindowClass_process( UlongToHandle( strtol( argv[3], NULL, 16 ) ), argv[4], strtol( argv[5], NULL, 0 ) ); if (argc >= 3) { test_comctl32_class( argv[2] ); @@ -2519,6 +2732,7 @@ START_TEST(class) test_actctx_classes(); test_class_name(); test_class_multithread(); + 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/11012
From: Connor McAdams <cmcadams@codeweavers.com> --- dlls/comctl32/tests/misc.c | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/dlls/comctl32/tests/misc.c b/dlls/comctl32/tests/misc.c index be31bedc848..83dc93eaed3 100644 --- a/dlls/comctl32/tests/misc.c +++ b/dlls/comctl32/tests/misc.c @@ -394,6 +394,42 @@ static void test_LoadIconWithScaleDown(void) FreeLibrary(hinst); } +static WNDPROC real_class_wndproc; +static const char *real_class_str; + +static LRESULT WINAPI test_real_class_wndproc( 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 check_real_class_name( a, b ) check_real_class_name_( __LINE__, a, b ) +static void check_real_class_name_( int line, HWND hwnd, const char *expect ) +{ + WCHAR expectW[256], nameW[256]; + char nameA[256]; + ULONG len; + + len = RealGetWindowClassA( hwnd, nameA, ARRAY_SIZE(nameA) ); + ok_(__FILE__, line)( !strcmp( nameA, expect ), "got %s\n", nameA ); + ok_(__FILE__, line)( len == strlen( expect ), "got %ld\n", len ); + + MultiByteToWideChar( CP_ACP, 0, expect, -1, expectW, ARRAY_SIZE(expectW)); + len = RealGetWindowClassW( hwnd, nameW, ARRAY_SIZE(nameW)); + ok_(__FILE__, line)( !wcscmp( nameW, expectW ), "got %s\n", debugstr_w(nameW)); + ok_(__FILE__, line)( len == wcslen( expectW ), "got %ld\n", len ); +} + static void check_class( const char *name, int must_exist, UINT style, UINT ignore, BOOL v6, DWORD classnameidx, BOOL classnameidx_todo ) { WNDCLASSA wc; @@ -431,6 +467,21 @@ static void check_class( const char *name, int must_exist, UINT style, UINT igno name, objid, classnameidx); DestroyWindow(hwnd); + + real_class_wndproc = wc.lpfnWndProc; + real_class_str = name; + wc.lpfnWndProc = test_real_class_wndproc; + wc.hInstance = GetModuleHandleA( NULL ); + wc.lpszClassName = "WineTest Class"; + RegisterClassA( &wc ); + + hwnd = CreateWindowA( wc.lpszClassName, 0, 0, 0, 0, 0, 0, 0, NULL, GetModuleHandleA( NULL ), 0 ); + check_real_class_name( hwnd, wc.lpszClassName ); + + DestroyWindow( hwnd ); + UnregisterClassA( wc.lpszClassName, GetModuleHandleA( NULL ) ); + real_class_wndproc = NULL; + real_class_str = NULL; } else ok( !must_exist, "System class %s does not exist\n", name ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11012
From: Connor McAdams <cmcadams@codeweavers.com> --- dlls/win32u/tests/win32u.c | 55 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/dlls/win32u/tests/win32u.c b/dlls/win32u/tests/win32u.c index 19c05a97835..59c2016347c 100644 --- a/dlls/win32u/tests/win32u.c +++ b/dlls/win32u/tests/win32u.c @@ -175,6 +175,16 @@ static void test_window_props(void) DestroyWindow( hwnd ); } +static WNDPROC real_class_wndproc; + +static LRESULT WINAPI test_real_class_wndproc( 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) { struct pinned_atom @@ -416,6 +426,51 @@ 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 = test_real_class_wndproc; + cls.hInstance = GetModuleHandleW( NULL ); + cls.lpszClassName = L"WineTest Class"; + + class = RegisterClassW( &cls ); + ok( class, "RegisterClassW failed: %lu\n", GetLastError() ); + + hwnd = CreateWindowW( cls.lpszClassName, 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 == 14, "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, cls.lpszClassName ), "buf = %s\n", debugstr_w(buf) ); + + DestroyWindow( hwnd ); + + ret = UnregisterClassW( cls.lpszClassName, GetModuleHandleW( NULL ) ); + ok( ret, "UnregisterClassW failed: %lu\n", GetLastError() ); + real_class_wndproc = NULL; + + cls.lpszClassName = L"#1"; class = RegisterClassW( &cls ); ok( class == 1, "RegisterClassW failed: %lu\n", GetLastError() ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11012
From: Connor McAdams <cmcadams@codeweavers.com> Signed-off-by: Connor McAdams <cmcadams@codeweavers.com> --- dlls/user32/class.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/dlls/user32/class.c b/dlls/user32/class.c index eb28b03479a..8c80126c90b 100644 --- a/dlls/user32/class.c +++ b/dlls/user32/class.c @@ -501,7 +501,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; } @@ -510,7 +517,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 ); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11012
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/user32/static.c | 3 +-- dlls/user32/tests/class.c | 2 +- dlls/win32u/class.c | 9 +++++++++ dlls/win32u/tests/win32u.c | 4 ++-- dlls/win32u/win32u_private.h | 1 + dlls/win32u/window.c | 13 +++++++++++++ server/protocol.def | 2 ++ server/window.c | 6 ++++-- 8 files changed, 33 insertions(+), 7 deletions(-) diff --git a/dlls/user32/static.c b/dlls/user32/static.c index f36c35bcec4..7cb138c0fd3 100644 --- a/dlls/user32/static.c +++ b/dlls/user32/static.c @@ -325,6 +325,7 @@ LRESULT StaticWndProc_common( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam LONG style = full_style & SS_TYPEMASK; if (!IsWindow( hwnd )) return 0; + NtUserSetWindowFNID( hwnd, MAKE_FNID(NTUSER_WNDPROC_STATIC) ); switch (uMsg) { @@ -399,8 +400,6 @@ LRESULT StaticWndProc_common( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam { CREATESTRUCTW *cs = (CREATESTRUCTW *)lParam; - NtUserSetWindowFNID( hwnd, MAKE_FNID(NTUSER_WNDPROC_STATIC) ); - if (full_style & SS_SUNKEN || style == SS_ETCHEDHORZ || style == SS_ETCHEDVERT) SetWindowLongW( hwnd, GWL_EXSTYLE, GetWindowLongW( hwnd, GWL_EXSTYLE ) | WS_EX_STATICEDGE ); diff --git a/dlls/user32/tests/class.c b/dlls/user32/tests/class.c index bfc932e25bc..841b8d681ee 100644 --- a/dlls/user32/tests/class.c +++ b/dlls/user32/tests/class.c @@ -2525,7 +2525,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 }, diff --git a/dlls/win32u/class.c b/dlls/win32u/class.c index 1bde8453ace..708e0f04f0c 100644 --- a/dlls/win32u/class.c +++ b/dlls/win32u/class.c @@ -889,6 +889,8 @@ INT WINAPI NtUserGetClassName( HWND hwnd, BOOL real, UNICODE_STRING *name ) WCHAR buffer[MAX_ATOM_LEN]; NTSTATUS status; UINT len = 0; + ATOM atom; + WORD fnid; int ret; TRACE( "%p %x %p\n", hwnd, real, name ); @@ -899,6 +901,13 @@ INT WINAPI NtUserGetClassName( HWND hwnd, BOOL real, UNICODE_STRING *name ) return 0; } + if (real && (fnid = get_window_fnid( hwnd )) && (fnid & 0x7fff) < ARRAY_SIZE(builtin_classes)) + { + get_desktop_window(); /* create the desktop window to trigger builtin class registration */ + atom = builtin_classes[fnid & 0x7fff].atom; + return NtUserGetAtomName( atom, name ); + } + while ((status = get_shared_window_class( hwnd, &lock, &class_shm )) == STATUS_PENDING) { len = class_shm->name_len - class_shm->name_offset * sizeof(WCHAR); diff --git a/dlls/win32u/tests/win32u.c b/dlls/win32u/tests/win32u.c index 59c2016347c..4481ffa1b32 100644 --- a/dlls/win32u/tests/win32u.c +++ b/dlls/win32u/tests/win32u.c @@ -448,10 +448,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/win32u_private.h b/dlls/win32u/win32u_private.h index bf3e871ae96..be303ae4d13 100644 --- a/dlls/win32u/win32u_private.h +++ b/dlls/win32u/win32u_private.h @@ -292,6 +292,7 @@ extern BOOL is_zoomed( HWND hwnd ); extern BOOL set_window_pixel_format( HWND hwnd, int format, BOOL internal ); extern int get_window_pixel_format( HWND hwnd ); extern DWORD get_window_long( HWND hwnd, INT offset ); +extern UINT get_window_fnid( HWND hwnd ); extern ULONG_PTR get_window_long_ptr( HWND hwnd, INT offset, BOOL ansi ); extern BOOL get_window_rect( HWND hwnd, RECT *rect, UINT dpi ); enum coords_relative; diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index 4d3cdd9fff4..4b53686dba1 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -1161,6 +1161,17 @@ BOOL is_zoomed( HWND hwnd ) return (get_window_long( hwnd, GWL_STYLE ) & WS_MAXIMIZE) != 0; } +UINT get_window_fnid( HWND hwnd ) +{ + struct object_lock lock = OBJECT_LOCK_INIT; + const window_shm_t *window_shm = NULL; + UINT status, fnid = 0; + + while ((status = get_shared_window( hwnd, &lock, &window_shm )) == STATUS_PENDING) + fnid = window_shm->fnid; + return status ? 0 : fnid; +} + static LONG_PTR get_window_long_size( HWND hwnd, INT offset, UINT size, BOOL ansi, BOOL internal ) { LONG_PTR retval = 0; @@ -1577,11 +1588,13 @@ BOOL WINAPI NtUserSetWindowFNID( HWND hwnd, WORD fnid ) RtlSetLastWin32Error( ERROR_INVALID_PARAMETER ); return FALSE; } + if (fnid == get_window_fnid( hwnd )) return TRUE; SERVER_START_REQ( set_window_fnid ) { req->handle = wine_server_user_handle( hwnd ); req->atom = get_builtin_class_atom( fnid & 0x7fff ); + req->fnid = fnid; ret = !wine_server_call_err( req ); } SERVER_END_REQ; diff --git a/server/protocol.def b/server/protocol.def index c24eb3d9e11..4f4f90c80e1 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1067,6 +1067,7 @@ typedef volatile struct { struct obj_locator class; /* object locator for the window class shared object */ unsigned int dpi_context; /* DPI awareness context */ + unsigned int fnid; /* builtin class FNID, or 0 */ data_size_t private_size; /* length of private extra bytes range */ } window_shm_t; @@ -2698,6 +2699,7 @@ enum message_type @REQ(set_window_fnid) user_handle_t handle; /* handle to the window */ atom_t atom; /* class atom */ + unsigned int fnid; /* builtin class FNID */ @END diff --git a/server/window.c b/server/window.c index 9d0568130f8..3fa71fc3f1c 100644 --- a/server/window.c +++ b/server/window.c @@ -689,6 +689,7 @@ static struct window *create_window( struct window *parent, struct window *owner { shared->class = class_locator; shared->dpi_context = NTUSER_DPI_PER_MONITOR_AWARE; + shared->fnid = 0; shared->private_size = 0; } SHARED_WRITE_END; @@ -2285,12 +2286,13 @@ DECL_HANDLER(set_window_fnid) if (!(win = get_window( req->handle ))) return; if (is_desktop_window( win ) && win->thread != current) return set_error( STATUS_ACCESS_DENIED ); - if (win->shared->private_size) return set_error( STATUS_INVALID_PARAMETER ); + if (win->shared->fnid && win->shared->fnid != req->fnid) return set_error( STATUS_INVALID_PARAMETER ); if (!(class = grab_class( current->process, req->atom, 0, &extra_bytes, &class_locator ))) return; SHARED_WRITE_BEGIN( win->shared, window_shm_t ) { - shared->private_size = extra_bytes; + shared->fnid = req->fnid; + shared->private_size = extra_bytes; } SHARED_WRITE_END; release_class( class ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11012
v2: Fix test crash on windows caused by a typo. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11012#note_141498
Nikolay Sivov (@nsivov) commented about dlls/user32/static.c:
LONG style = full_style & SS_TYPEMASK;
if (!IsWindow( hwnd )) return 0; + NtUserSetWindowFNID( hwnd, MAKE_FNID(NTUSER_WNDPROC_STATIC) );
switch (uMsg)
I recall this was depending on WM_NCCREATE, if you don't pass it to original procedure this class type won't be set. I don't know if it's worth replicating, unless SetWindowFNID is for some reason expensive to call on every message. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11012#note_141503
On Thu May 28 11:17:12 2026 +0000, Nikolay Sivov wrote:
I recall this was depending on WM_NCCREATE, if you don't pass it to original procedure this class type won't be set. I don't know if it's worth replicating, unless SetWindowFNID is for some reason expensive to call on every message. The tests indicate that it depends on the class but for static test shows that real class is set whenever any of WM_NCCREATE / WM_CREATE / WM_NULL messages is sent. I don't think the tests are exhaustive, and in such case I decided to set FNID on every message. For other classes where only WM_NCCREATE or WM_CREATE trigger the real class name change I filtered on message type. In any case NtUserSetWindowFNID shouldn't be expensive to call.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11012#note_141515
participants (4)
-
Connor McAdams -
Nikolay Sivov (@nsivov) -
Rémi Bernon -
Rémi Bernon (@rbernon)