From b2d118d4f1e8fcb43f20453df2d8545a6f1b637f Mon Sep 17 00:00:00 2001
From: Alexandre Julliard <julliard@winehq.org>
Date: Thu, 21 Nov 2024 11:02:26 +0100
Subject: [PATCH] win32u: Implement the remaining arguments of
 NtUserBuildHwndList().

---
 dlls/win32u/main.c             |   2 +-
 dlls/win32u/tests/win32u.c     | 216 +++++++++++++++++++++++++++++----
 dlls/win32u/window.c           |  18 +--
 dlls/wow64win/user.c           |  12 +-
 include/ntuser.h               |   2 +-
 include/wine/server_protocol.h |  24 +++-
 server/protocol.def            |  12 ++
 server/request_handlers.h      |   9 ++
 server/request_trace.h         |  17 +++
 server/user.c                  |   2 +-
 server/user.h                  |   2 +
 server/window.c                |  97 +++++++++++++++
 12 files changed, 372 insertions(+), 41 deletions(-)

diff --git a/dlls/win32u/main.c b/dlls/win32u/main.c
index 5246da89991..11628dcccca 100644
--- a/dlls/win32u/main.c
+++ b/dlls/win32u/main.c
@@ -1127,7 +1127,7 @@ NTSTATUS SYSCALL_API NtUserBuildHimcList( UINT thread_id, UINT count, HIMC *buff
     SYSCALL_FUNC( NtUserBuildHimcList );
 }
 
-NTSTATUS SYSCALL_API NtUserBuildHwndList( HDESK desktop, ULONG unk2, ULONG unk3, ULONG unk4,
+NTSTATUS SYSCALL_API NtUserBuildHwndList( HDESK desktop, HWND hwnd, BOOL children, BOOL non_immersive,
                                           ULONG thread_id, ULONG count, HWND *buffer, ULONG *size )
 {
     SYSCALL_FUNC( NtUserBuildHwndList );
diff --git a/dlls/win32u/tests/win32u.c b/dlls/win32u/tests/win32u.c
index 631b722ea22..aa517a32ae8 100644
--- a/dlls/win32u/tests/win32u.c
+++ b/dlls/win32u/tests/win32u.c
@@ -496,71 +496,241 @@ static BOOL WINAPI count_win( HWND hwnd, LPARAM lparam )
 
 static void test_NtUserBuildHwndList(void)
 {
-    ULONG size, desktop_windows_cnt;
-    HWND buf[512], hwnd;
+    ULONG i, size, count, desktop_windows_cnt;
+    HWND buf[512], hwnd, msg, msg_window, child, child2, grandchild;
     NTSTATUS status;
 
-    size = 0;
-    status = NtUserBuildHwndList( 0, 0, 0, 0, GetCurrentThreadId(), ARRAYSIZE(buf), buf, &size );
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, 0, FALSE, FALSE, GetCurrentThreadId(), ARRAYSIZE(buf), buf, &size );
     ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
     ok( size == 1, "size = %lu\n", size );
-    ok( buf[0] == HWND_BOTTOM, "buf[0] = %p\n", buf[0] );
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    msg = CreateWindowExA( 0, "static", "", WS_POPUP, 0,0,0,0,HWND_MESSAGE,0,0, NULL );
+    msg_window = GetAncestor( msg, GA_PARENT );
 
-    hwnd = CreateWindowExA( 0, "static", NULL, WS_POPUP, 0,0,0,0,GetDesktopWindow(),0,0, NULL );
+    hwnd = CreateWindowExA( 0, "static", "test static", WS_POPUP, 0,0,0,0,GetDesktopWindow(),0,0, NULL );
+    child = CreateWindowExA( 0, "static", "child static", WS_CHILD, 0,0,0,0,hwnd,0,0, NULL );
+    child2 = CreateWindowExA( 0, "static", "child2 static", WS_CHILD, 0,0,0,0,hwnd,0,0, NULL );
+    grandchild = CreateWindowExA( 0, "static", "grandchild static", WS_CHILD, 0,0,0,0,child,0,0, NULL );
 
-    size = 0;
-    status = NtUserBuildHwndList( 0, 0, 0, 0, GetCurrentThreadId(), ARRAYSIZE(buf), buf, &size );
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, 0, FALSE, FALSE, GetCurrentThreadId(), ARRAYSIZE(buf), buf, &size );
     ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
     ok( size == 3, "size = %lu\n", size );
     ok( buf[0] == hwnd, "buf[0] = %p\n", buf[0] );
-    ok( buf[2] == HWND_BOTTOM, "buf[0] = %p\n", buf[2] );
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
 
-    size = 0;
-    status = NtUserBuildHwndList( 0, 0, 0, 0, GetCurrentThreadId(), 3, buf, &size );
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, 0, FALSE, FALSE, GetCurrentThreadId(), 3, buf, &size );
     ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
     ok( size == 3, "size = %lu\n", size );
 
-    size = 0;
-    status = NtUserBuildHwndList( 0, 0, 0, 0, GetCurrentThreadId(), 2, buf, &size );
+    size = 0xdeadbeef;
+    memset( buf, 0xcc, sizeof(buf) );
+    status = NtUserBuildHwndList( 0, 0, FALSE, FALSE, GetCurrentThreadId(), 2, buf, &size );
     ok( status == STATUS_BUFFER_TOO_SMALL, "NtUserBuildHwndList failed: %#lx\n", status );
     ok( size == 3, "size = %lu\n", size );
+    ok( HandleToUlong(buf[0]) == 0xcccccccc, "buf[0] initialized\n" );
 
-    size = 0;
-    status = NtUserBuildHwndList( 0, 0, 0, 0, GetCurrentThreadId(), 1, buf, &size );
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, 0, FALSE, FALSE, GetCurrentThreadId(), 1, buf, &size );
     ok( status == STATUS_BUFFER_TOO_SMALL, "NtUserBuildHwndList failed: %#lx\n", status );
     ok( size == 3, "size = %lu\n", size );
 
     desktop_windows_cnt = 0;
     EnumDesktopWindows( 0, count_win, (LPARAM)&desktop_windows_cnt );
 
-    size = 0;
-    status = NtUserBuildHwndList( 0, 0, 0, 1, 0, ARRAYSIZE(buf), buf, &size );
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, 0, FALSE, TRUE, 0, ARRAYSIZE(buf), buf, &size );
     ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    todo_wine /* EnumDesktopWindows also returns message windows */
     ok( size == desktop_windows_cnt + 1, "size = %lu, expected %lu\n", size, desktop_windows_cnt + 1 );
 
     desktop_windows_cnt = 0;
     EnumDesktopWindows( GetThreadDesktop( GetCurrentThreadId() ), count_win, (LPARAM)&desktop_windows_cnt );
 
-    size = 0;
-    status = NtUserBuildHwndList( GetThreadDesktop(GetCurrentThreadId()), 0, 0, 1, 0,
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( GetThreadDesktop(GetCurrentThreadId()), 0, FALSE, TRUE, 0,
                                   ARRAYSIZE(buf), buf, &size );
     ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
     ok( size == desktop_windows_cnt + 1, "size = %lu, expected %lu\n", size, desktop_windows_cnt + 1 );
+    for (i = 0; i < size; i++) if (buf[i] == msg_window) break;
+    ok( i == size, "message window was enumerated\n" );
 
-    size = 0;
-    status = NtUserBuildHwndList( GetThreadDesktop(GetCurrentThreadId()), 0, 0, 0, 0,
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( GetThreadDesktop(GetCurrentThreadId()), 0, FALSE, FALSE, 0,
                                   ARRAYSIZE(buf), buf, &size );
     ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
     todo_wine
     ok( size > desktop_windows_cnt + 1, "size = %lu, expected %lu\n", size, desktop_windows_cnt + 1 );
 
     size = 0xdeadbeef;
-    status = NtUserBuildHwndList( UlongToHandle(0xdeadbeef), 0, 0, 0, 0,
+    status = NtUserBuildHwndList( GetThreadDesktop(GetCurrentThreadId()), hwnd, FALSE, TRUE, 0,
+                                  ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == desktop_windows_cnt + 1, "size = %lu, expected %lu\n", size, desktop_windows_cnt + 1 );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( GetThreadDesktop(GetCurrentThreadId()), 0, TRUE, TRUE, 0,
                                   ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == 1, "size = %lu\n", size );
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, 0, TRUE, TRUE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == desktop_windows_cnt + 1, "size = %lu, expected %lu\n", size, desktop_windows_cnt + 1 );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( UlongToHandle(0xdeadbeef), 0, FALSE, FALSE, 0,
+                                  ARRAYSIZE(buf), buf, &size );
+    ok( status == STATUS_INVALID_HANDLE, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == 0xdeadbeef, "size = %lu\n", size );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, GetDesktopWindow(), TRUE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size > 4, "size = %lu\n", size );
+    for (i = 0; i < size; i++)
+    {
+        if (buf[i] != hwnd) continue;
+        ok( buf[i] == hwnd, "buf[i] = %p / %p\n", buf[i], hwnd );
+        ok( buf[i + 1] == child, "buf[i + 1] = %p / %p\n", buf[i + 1], child );
+        ok( buf[i + 2] == grandchild, "buf[i + 2] = %p / %p\n", buf[i + 2], grandchild );
+        ok( buf[i + 3] == child2, "buf[i + 3] = %p / %p\n", buf[i + 3], child2 );
+        break;
+    }
+    ok( i < size, "window not found\n" );
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, 0, FALSE, FALSE, GetCurrentThreadId(), ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size >= 2, "size = %lu\n", size );
+    ok( buf[0] == hwnd, "buf[0] = %p / %p\n", buf[0], hwnd );
+    ok( buf[1] != child, "buf[1] = %p\n", buf[1] );
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, hwnd, TRUE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == 4, "size = %lu\n", size );
+    ok( buf[0] == child, "buf[0] = %p / %p\n", buf[0], child );
+    ok( buf[1] == grandchild, "buf[1] = %p / %p\n", buf[1], grandchild );
+    ok( buf[2] == child2, "buf[2] = %p / %p\n", buf[2], child2 );
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, child, FALSE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == 3, "size = %lu\n", size );
+    ok( buf[0] == child, "buf[0] = %p / %p\n", buf[0], child );
+    ok( buf[1] == child2, "buf[1] = %p / %p\n", buf[1], child2 );
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, grandchild, FALSE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == 2, "size = %lu\n", size );
+    ok( buf[0] == grandchild, "buf[0] = %p / %p\n", buf[0], grandchild );
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, grandchild, TRUE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == 1, "size = %lu\n", size );
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, hwnd, FALSE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size > 2, "size = %lu\n", size );
+    ok( buf[0] == hwnd, "buf[0] = %p / %p\n", buf[0], hwnd );
+    /* ... followed by other siblings */
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, hwnd, FALSE, FALSE, GetCurrentThreadId(), ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == 2 || size == 3, "size = %lu\n", size );
+    ok( buf[0] == hwnd, "buf[0] = %p / %p\n", buf[0], hwnd );
+    /* ... possibly followed by IME window */
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, GetDesktopWindow(), FALSE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size > 2, "size = %lu\n", size );
+    ok( buf[0] == GetDesktopWindow(), "buf[0] = %p / %p\n", buf[0], GetDesktopWindow() );
+    /* ... followed by desktop window siblings */
+    for (i = 0; i < size; i++) if (buf[i] == msg_window) break;
+    ok( i < size, "message window was not enumerated\n" );
+    count = size - i;
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, msg_window, FALSE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == count, "size = %lu / %lu\n", size, count );
+    ok( buf[0] == msg_window, "buf[0] = %p / %p\n", buf[0], msg_window );
+    for (i = 0; i < size; i++) if (buf[i] == GetDesktopWindow()) break;
+    ok( i == size, "desktop window was enumerated\n" );
+    /* ... followed by msg window siblings */
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, GetDesktopWindow(), TRUE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    /* includes all desktop window children */
+    ok( size > desktop_windows_cnt + 1, "size = %lu, expected %lu\n", size, desktop_windows_cnt + 1 );
+    for (i = 0; i < size; i++)
+    {
+        ok( buf[i] != GetDesktopWindow(), "desktop window enumerated\n" );
+        ok( buf[i] != msg_window, "message window enumerated\n" );
+    }
+    for (i = 0; i < size; i++) if (buf[i] == hwnd) break;
+    ok( i < size, "window was not enumerated\n" );
+    for (i = 0; i < size; i++) if (buf[i] == child) break;
+    ok( i < size, "child window was not enumerated\n" );
+    for (i = 0; i < size; i++) if (buf[i] == grandchild) break;
+    ok( i < size, "grandchild window was not enumerated\n" );
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, msg_window, TRUE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( !status, "NtUserBuildHwndList failed: %#lx\n", status );
+    /* includes all HWND_MESSAGE windows */
+    ok( size > 1, "size = %lu\n", size );
+    for (i = 0; i < size; i++)
+    {
+        ok( buf[i] != GetDesktopWindow(), "desktop window enumerated\n" );
+        ok( buf[i] != msg_window, "message window enumerated\n" );
+        ok( buf[i] != hwnd, "window enumerated\n" );
+        ok( buf[i] != child, "child window enumerated\n" );
+        ok( buf[i] != grandchild, "grandchild window enumerated\n" );
+    }
+    ok( buf[size - 1] == HWND_BOTTOM, "buf[size - 1] = %p\n", buf[size - 1] );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, (HWND)0xdeadbeef, FALSE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( status == STATUS_INVALID_HANDLE, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == 0xdeadbeef, "size = %lu\n", size );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( GetThreadDesktop(GetCurrentThreadId()), (HWND)0xdeadbeef,
+                                  FALSE, FALSE, 0, ARRAYSIZE(buf), buf, &size );
+    ok( status == STATUS_INVALID_HANDLE, "NtUserBuildHwndList failed: %#lx\n", status );
+    ok( size == 0xdeadbeef, "size = %lu\n", size );
+
+    size = 0xdeadbeef;
+    status = NtUserBuildHwndList( 0, 0, FALSE, FALSE, 0xdeadbeef, ARRAYSIZE(buf), buf, &size );
     ok( status == STATUS_INVALID_HANDLE, "NtUserBuildHwndList failed: %#lx\n", status );
     ok( size == 0xdeadbeef, "size = %lu\n", size );
 
     DestroyWindow( hwnd );
+    DestroyWindow( msg );
 }
 
 static BOOL CALLBACK enum_names( LPWSTR name, LPARAM lp )
@@ -580,7 +750,7 @@ static void test_NtUserBuildNameList(void)
     struct ntuser_name_list *buffer;
     WCHAR *p;
     NTSTATUS status;
-    ULONG i, count, ret_size, expect, size = offsetof( struct ntuser_name_list, strings[256] );
+    ULONG i, count, ret_size, expect, size = offsetof( struct ntuser_name_list, strings[1024] );
 
     buffer = malloc( size );
     memset( buffer, 0xcc, size );
diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c
index eb313528f69..59f12a439c2 100644
--- a/dlls/win32u/window.c
+++ b/dlls/win32u/window.c
@@ -2969,24 +2969,26 @@ BOOL WINAPI NtUserSetWindowPlacement( HWND hwnd, const WINDOWPLACEMENT *wpl )
 /*****************************************************************************
  *           NtUserBuildHwndList (win32u.@)
  */
-NTSTATUS WINAPI NtUserBuildHwndList( HDESK desktop, ULONG unk2, ULONG unk3, ULONG unk4,
+NTSTATUS WINAPI NtUserBuildHwndList( HDESK desktop, HWND hwnd, BOOL children, BOOL non_immersive,
                                      ULONG thread_id, ULONG count, HWND *buffer, ULONG *size )
 {
     user_handle_t *list = (user_handle_t *)buffer;
     int i;
     NTSTATUS status;
 
-    SERVER_START_REQ( get_window_children )
+    SERVER_START_REQ( get_window_list )
     {
-        req->desktop = wine_server_obj_handle( desktop );
-        req->tid = thread_id;
-        if (count) wine_server_set_reply( req, list, (count - 1) * sizeof(user_handle_t) );
+        req->desktop  = wine_server_obj_handle( desktop );
+        req->handle   = wine_server_user_handle( hwnd );
+        req->tid      = thread_id;
+        req->children = children;
+        if (count) wine_server_set_reply( req, list, (count - 1) * sizeof(*list) );
         status = wine_server_call( req );
-        if (status && status != STATUS_BUFFER_TOO_SMALL) return status;
-        *size = reply->count + 1;
+        if (!status || status == STATUS_BUFFER_TOO_SMALL) *size = reply->count + 1;
     }
     SERVER_END_REQ;
-    if (*size > count) return STATUS_BUFFER_TOO_SMALL;
+
+    if (status) return status;
 
     /* start from the end since HWND is potentially larger than user_handle_t */
     for (i = *size - 2; i >= 0; i--)
diff --git a/dlls/wow64win/user.c b/dlls/wow64win/user.c
index 1daa577775b..28944f15d07 100644
--- a/dlls/wow64win/user.c
+++ b/dlls/wow64win/user.c
@@ -1611,9 +1611,9 @@ NTSTATUS WINAPI wow64_NtUserBuildHimcList( UINT *args )
 NTSTATUS WINAPI wow64_NtUserBuildHwndList( UINT *args )
 {
     HDESK desktop = get_handle( &args );
-    ULONG unk2 = get_ulong( &args );
-    ULONG unk3 = get_ulong( &args );
-    ULONG unk4 = get_ulong( &args );
+    HWND hwnd = get_handle( &args );
+    BOOL children = get_ulong( &args );
+    BOOL non_immersive = get_ulong( &args );
     ULONG thread_id = get_ulong( &args );
     ULONG count = get_ulong( &args );
     UINT32 *buffer32 = get_ptr( &args );
@@ -1625,11 +1625,11 @@ NTSTATUS WINAPI wow64_NtUserBuildHwndList( UINT *args )
 
     if (!(buffer = Wow64AllocateTemp( count * sizeof(*buffer) ))) return STATUS_NO_MEMORY;
 
-    if ((status = NtUserBuildHwndList( desktop, unk2, unk3, unk4, thread_id, count, buffer, size )))
+    if ((status = NtUserBuildHwndList( desktop, hwnd, children, non_immersive,
+                                       thread_id, count, buffer, size )))
         return status;
 
-    for (i = 0; i < *size; i++)
-        buffer32[i] = HandleToUlong( buffer[i] );
+    for (i = 0; i < *size; i++) buffer32[i] = HandleToUlong( buffer[i] );
     return status;
 }
 
diff --git a/include/ntuser.h b/include/ntuser.h
index 9ed470e2fc5..2475792e702 100644
--- a/include/ntuser.h
+++ b/include/ntuser.h
@@ -705,7 +705,7 @@ W32KAPI UINT    WINAPI NtUserAssociateInputContext( HWND hwnd, HIMC ctx, ULONG f
 W32KAPI BOOL    WINAPI NtUserAttachThreadInput( DWORD from, DWORD to, BOOL attach );
 W32KAPI HDC     WINAPI NtUserBeginPaint( HWND hwnd, PAINTSTRUCT *ps );
 W32KAPI NTSTATUS WINAPI NtUserBuildHimcList( UINT thread_id, UINT count, HIMC *buffer, UINT *size );
-W32KAPI NTSTATUS WINAPI NtUserBuildHwndList( HDESK desktop, ULONG unk2, ULONG unk3, ULONG unk4,
+W32KAPI NTSTATUS WINAPI NtUserBuildHwndList( HDESK desktop, HWND hwnd, BOOL children, BOOL non_immersive,
                                              ULONG thread_id, ULONG count, HWND *buffer, ULONG *size );
 W32KAPI NTSTATUS WINAPI NtUserBuildNameList( HWINSTA winsta, ULONG size, struct ntuser_name_list *buffer, ULONG *ret_size );
 W32KAPI NTSTATUS WINAPI NtUserBuildPropList( HWND hwnd, ULONG count, struct ntuser_property_list *buffer, ULONG *ret_count );
diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h
index 075d41b9467..f0b9571aaed 100644
--- a/include/wine/server_protocol.h
+++ b/include/wine/server_protocol.h
@@ -3479,6 +3479,25 @@ struct get_window_parents_reply
 
 
 
+struct get_window_list_request
+{
+    struct request_header __header;
+    obj_handle_t   desktop;
+    user_handle_t  handle;
+    thread_id_t    tid;
+    int            children;
+    char __pad_28[4];
+};
+struct get_window_list_reply
+{
+    struct reply_header __header;
+    int            count;
+    /* VARARG(windows,user_handles); */
+    char __pad_12[4];
+};
+
+
+
 struct get_window_children_request
 {
     struct request_header __header;
@@ -6003,6 +6022,7 @@ enum request
     REQ_set_window_info,
     REQ_set_parent,
     REQ_get_window_parents,
+    REQ_get_window_list,
     REQ_get_window_children,
     REQ_get_window_children_from_point,
     REQ_get_window_tree,
@@ -6300,6 +6320,7 @@ union generic_request
     struct set_window_info_request set_window_info_request;
     struct set_parent_request set_parent_request;
     struct get_window_parents_request get_window_parents_request;
+    struct get_window_list_request get_window_list_request;
     struct get_window_children_request get_window_children_request;
     struct get_window_children_from_point_request get_window_children_from_point_request;
     struct get_window_tree_request get_window_tree_request;
@@ -6595,6 +6616,7 @@ union generic_reply
     struct set_window_info_reply set_window_info_reply;
     struct set_parent_reply set_parent_reply;
     struct get_window_parents_reply get_window_parents_reply;
+    struct get_window_list_reply get_window_list_reply;
     struct get_window_children_reply get_window_children_reply;
     struct get_window_children_from_point_reply get_window_children_from_point_reply;
     struct get_window_tree_reply get_window_tree_reply;
@@ -6738,6 +6760,6 @@ union generic_reply
     struct set_keyboard_repeat_reply set_keyboard_repeat_reply;
 };
 
-#define SERVER_PROTOCOL_VERSION 849
+#define SERVER_PROTOCOL_VERSION 850
 
 #endif /* __WINE_WINE_SERVER_PROTOCOL_H */
diff --git a/server/protocol.def b/server/protocol.def
index 0abbb48f3b3..8082ad0f7cd 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -2571,6 +2571,18 @@ enum message_type
 @END
 
 
+/* Get a list of window siblings or children */
+@REQ(get_window_list)
+    obj_handle_t   desktop;       /* handle to desktop */
+    user_handle_t  handle;        /* handle to window to start from */
+    thread_id_t    tid;           /* thread owning the listed windows */
+    int            children;      /* enumerate children? */
+@REPLY
+    int            count;         /* total count of windows */
+    VARARG(windows,user_handles); /* window handles */
+@END
+
+
 /* Get a list of the window children */
 @REQ(get_window_children)
     obj_handle_t   desktop;       /* handle to desktop */
diff --git a/server/request_handlers.h b/server/request_handlers.h
index c5c5b387946..5f2a5518fe7 100644
--- a/server/request_handlers.h
+++ b/server/request_handlers.h
@@ -156,6 +156,7 @@ DECL_HANDLER(get_window_info);
 DECL_HANDLER(set_window_info);
 DECL_HANDLER(set_parent);
 DECL_HANDLER(get_window_parents);
+DECL_HANDLER(get_window_list);
 DECL_HANDLER(get_window_children);
 DECL_HANDLER(get_window_children_from_point);
 DECL_HANDLER(get_window_tree);
@@ -450,6 +451,7 @@ static const req_handler req_handlers[REQ_NB_REQUESTS] =
     (req_handler)req_set_window_info,
     (req_handler)req_set_parent,
     (req_handler)req_get_window_parents,
+    (req_handler)req_get_window_list,
     (req_handler)req_get_window_children,
     (req_handler)req_get_window_children_from_point,
     (req_handler)req_get_window_tree,
@@ -1494,6 +1496,13 @@ C_ASSERT( offsetof(struct get_window_parents_request, handle) == 12 );
 C_ASSERT( sizeof(struct get_window_parents_request) == 16 );
 C_ASSERT( offsetof(struct get_window_parents_reply, count) == 8 );
 C_ASSERT( sizeof(struct get_window_parents_reply) == 16 );
+C_ASSERT( offsetof(struct get_window_list_request, desktop) == 12 );
+C_ASSERT( offsetof(struct get_window_list_request, handle) == 16 );
+C_ASSERT( offsetof(struct get_window_list_request, tid) == 20 );
+C_ASSERT( offsetof(struct get_window_list_request, children) == 24 );
+C_ASSERT( sizeof(struct get_window_list_request) == 32 );
+C_ASSERT( offsetof(struct get_window_list_reply, count) == 8 );
+C_ASSERT( sizeof(struct get_window_list_reply) == 16 );
 C_ASSERT( offsetof(struct get_window_children_request, desktop) == 12 );
 C_ASSERT( offsetof(struct get_window_children_request, parent) == 16 );
 C_ASSERT( offsetof(struct get_window_children_request, atom) == 20 );
diff --git a/server/request_trace.h b/server/request_trace.h
index c099d709fe7..76adb66cec8 100644
--- a/server/request_trace.h
+++ b/server/request_trace.h
@@ -1762,6 +1762,20 @@ static void dump_get_window_parents_reply( const struct get_window_parents_reply
     dump_varargs_user_handles( ", parents=", cur_size );
 }
 
+static void dump_get_window_list_request( const struct get_window_list_request *req )
+{
+    fprintf( stderr, " desktop=%04x", req->desktop );
+    fprintf( stderr, ", handle=%08x", req->handle );
+    fprintf( stderr, ", tid=%04x", req->tid );
+    fprintf( stderr, ", children=%d", req->children );
+}
+
+static void dump_get_window_list_reply( const struct get_window_list_reply *req )
+{
+    fprintf( stderr, " count=%d", req->count );
+    dump_varargs_user_handles( ", windows=", cur_size );
+}
+
 static void dump_get_window_children_request( const struct get_window_children_request *req )
 {
     fprintf( stderr, " desktop=%04x", req->desktop );
@@ -3474,6 +3488,7 @@ static const dump_func req_dumpers[REQ_NB_REQUESTS] =
     (dump_func)dump_set_window_info_request,
     (dump_func)dump_set_parent_request,
     (dump_func)dump_get_window_parents_request,
+    (dump_func)dump_get_window_list_request,
     (dump_func)dump_get_window_children_request,
     (dump_func)dump_get_window_children_from_point_request,
     (dump_func)dump_get_window_tree_request,
@@ -3768,6 +3783,7 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] =
     (dump_func)dump_set_window_info_reply,
     (dump_func)dump_set_parent_reply,
     (dump_func)dump_get_window_parents_reply,
+    (dump_func)dump_get_window_list_reply,
     (dump_func)dump_get_window_children_reply,
     (dump_func)dump_get_window_children_from_point_reply,
     (dump_func)dump_get_window_tree_reply,
@@ -4062,6 +4078,7 @@ static const char * const req_names[REQ_NB_REQUESTS] =
     "set_window_info",
     "set_parent",
     "get_window_parents",
+    "get_window_list",
     "get_window_children",
     "get_window_children_from_point",
     "get_window_tree",
diff --git a/server/user.c b/server/user.c
index c529fc729de..2d038a6ddbf 100644
--- a/server/user.c
+++ b/server/user.c
@@ -67,7 +67,7 @@ static inline struct user_handle *alloc_user_entry(void)
         struct user_handle *new_handles;
         /* grow array by 50% (but at minimum 32 entries) */
         int growth = max( 32, allocated_handles / 2 );
-        int new_size = min( allocated_handles + growth, (LAST_USER_HANDLE-FIRST_USER_HANDLE+1) >> 1 );
+        int new_size = min( allocated_handles + growth, MAX_USER_HANDLES );
         if (new_size <= allocated_handles) return NULL;
         if (!(new_handles = realloc( handles, new_size * sizeof(*handles) )))
             return NULL;
diff --git a/server/user.h b/server/user.h
index c4778d810ec..a57e11b5a97 100644
--- a/server/user.h
+++ b/server/user.h
@@ -43,6 +43,8 @@ enum user_object
 
 #define DESKTOP_ATOM  ((atom_t)32769)
 
+#define MAX_USER_HANDLES ((LAST_USER_HANDLE - FIRST_USER_HANDLE + 1) >> 1)
+
 struct winstation
 {
     struct object      obj;                /* object header */
diff --git a/server/window.c b/server/window.c
index 5d84f3e55c7..1ddf47c5bfd 100644
--- a/server/window.c
+++ b/server/window.c
@@ -904,6 +904,60 @@ static int is_point_in_window( struct window *win, int *x, int *y, unsigned int
     return 1;
 }
 
+/* helper for get_window_list */
+static void append_window_to_list( struct window *win, struct thread *thread, user_handle_t *handles,
+                                   unsigned int *count, unsigned int max_count )
+{
+    if (thread && win->thread != thread) return;
+    if (*count < max_count) handles[*count] = win->handle;
+    (*count)++;
+}
+
+/* fill an array with the handles of siblings or children */
+static void get_window_list( struct desktop *desktop, struct window *win, struct thread *thread,
+                             int children, user_handle_t *handles,
+                             unsigned int *count, unsigned int max_count )
+{
+    struct list *ptr;
+    struct window *child;
+
+    if (desktop)  /* top-level windows of specified desktop */
+    {
+        if (children) return;
+        if (!desktop->top_window) return;
+        LIST_FOR_EACH_ENTRY( child, &desktop->top_window->children, struct window, entry )
+            append_window_to_list( child, thread, handles, count, max_count );
+    }
+    else if (!win)  /* top-level windows of current desktop */
+    {
+        if (!(win = get_desktop_window( current ))) return;
+        LIST_FOR_EACH_ENTRY( child, &win->children, struct window, entry )
+            append_window_to_list( child, thread, handles, count, max_count );
+    }
+    else if (children)  /* children (recursively) of specified window */
+    {
+        LIST_FOR_EACH_ENTRY( child, &win->children, struct window, entry )
+        {
+            append_window_to_list( child, thread, handles, count, max_count );
+            get_window_list( NULL, child, thread, TRUE, handles, count, max_count );
+        }
+    }
+    else if (!is_desktop_window( win ))  /* siblings starting from specified window */
+    {
+        for (ptr = &win->entry; ptr; ptr = list_next( &win->parent->children, ptr ))
+        {
+            struct window *sibling = LIST_ENTRY( ptr, struct window, entry );
+            append_window_to_list( sibling, thread, handles, count, max_count );
+        }
+    }
+    else  /* desktop window siblings */
+    {
+        append_window_to_list( win, thread, handles, count, max_count );
+        if (win == win->desktop->top_window && win->desktop->msg_window)
+            append_window_to_list( win->desktop->msg_window, thread, handles, count, max_count );
+    }
+}
+
 /* fill an array with the handles of the children of a specified window */
 static unsigned int get_children_windows( struct window *parent, atom_t atom, thread_id_t tid,
                                           user_handle_t *handles, unsigned int max_count )
@@ -2380,6 +2434,49 @@ DECL_HANDLER(get_window_parents)
 }
 
 
+/* get a list of window siblings or children */
+DECL_HANDLER(get_window_list)
+{
+    struct window *win = NULL;
+    struct desktop *desktop = NULL;
+    struct thread *thread = NULL;
+    user_handle_t *data;
+    unsigned int count = 0, max_count = get_reply_max_size() / sizeof(*data);
+
+    if (req->handle && !(win = get_window( req->handle )))
+    {
+        set_error( STATUS_INVALID_HANDLE );
+        return;
+    }
+    if (req->tid && !(thread = get_thread_from_id( req->tid )))
+    {
+        set_error( STATUS_INVALID_HANDLE );
+        return;
+    }
+    if (req->desktop && !(desktop = get_desktop_obj( current->process, req->desktop, DESKTOP_ENUMERATE )))
+    {
+        if (thread) release_object( thread );
+        return;
+    }
+
+    max_count = min( max_count, MAX_USER_HANDLES );
+    if ((data = mem_alloc( max_count * sizeof(*data) )))
+    {
+        get_window_list( desktop, win, thread, req->children, data, &count, max_count );
+        if (count > max_count)
+        {
+            free( data );
+            set_error( STATUS_BUFFER_TOO_SMALL );
+        }
+        else set_reply_data_ptr( data, count * sizeof(*data) );
+        reply->count = count;
+    }
+
+    if (thread) release_object( thread );
+    if (desktop) release_object( desktop );
+}
+
+
 /* get a list of the window children */
 DECL_HANDLER(get_window_children)
 {
-- 
GitLab