From 8a7ea75b0b00fa251ee7eda99a433d6c6e11d7f5 Mon Sep 17 00:00:00 2001
From: Alexandre Julliard <julliard@winehq.org>
Date: Fri, 22 Nov 2024 12:46:44 +0100
Subject: [PATCH] server: Add a new request to find sibling windows by class
 name.

Use it to reimplement NtUserFindWindowEx().
---
 dlls/win32u/window.c           | 53 ++++++++++++++---------
 include/wine/server_protocol.h | 23 +++++++++-
 server/protocol.def            | 12 ++++++
 server/request_handlers.h      |  8 ++++
 server/request_trace.h         | 17 ++++++++
 server/window.c                | 77 ++++++++++++++++++++++++++++------
 6 files changed, 156 insertions(+), 34 deletions(-)

diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c
index d0de4f2eab1..032ab96ad5c 100644
--- a/dlls/win32u/window.c
+++ b/dlls/win32u/window.c
@@ -3017,46 +3017,59 @@ NTSTATUS WINAPI NtUserBuildHwndList( HDESK desktop, HWND hwnd, BOOL children, BO
 HWND WINAPI NtUserFindWindowEx( HWND parent, HWND child, UNICODE_STRING *class, UNICODE_STRING *title,
                                 ULONG unk )
 {
-    HWND *list;
+    user_handle_t *list;
     HWND retvalue = 0;
-    int i = 0, len = 0, title_len;
-    WCHAR *buffer = NULL;
+    int i = 0, size = 128, title_len;
+    ATOM atom = class ? get_int_atom_value( class ) : 0;
+    NTSTATUS status;
 
-    if (!parent && child) parent = get_desktop_window();
-    else if (parent == HWND_MESSAGE) parent = get_hwnd_message_parent();
+    /* empty class is not the same as NULL class */
+    if (!atom && class && !class->Length) return 0;
 
-    if (title)
+    if (parent == HWND_MESSAGE) parent = get_hwnd_message_parent();
+
+    for (;;)
     {
-        len = title->Length / sizeof(WCHAR) + 1;  /* one extra char to check for chars beyond the end */
-        if (!(buffer = malloc( (len + 1) * sizeof(WCHAR) ))) return 0;
-    }
+        if (!(list = malloc( size * sizeof(*list) ))) return 0;
 
-    if (!(list = list_window_children( 0, parent, class, 0 ))) goto done;
+        SERVER_START_REQ( get_class_windows )
+        {
+            req->parent = wine_server_user_handle( parent );
+            req->child  = wine_server_user_handle( child );
+            req->atom   = atom;
+            if (!atom && class) wine_server_add_data( req, class->Buffer, class->Length );
+            wine_server_set_reply( req, list, size * sizeof(user_handle_t) );
+            status = wine_server_call( req );
+            size = reply->count;
+        }
+        SERVER_END_REQ;
 
-    if (child)
-    {
-        child = get_full_window_handle( child );
-        while (list[i] && list[i] != child) i++;
-        if (!list[i]) goto done;
-        i++;  /* start from next window */
+        if (!status && size) break;
+        free( list );
+        if (status != STATUS_BUFFER_TOO_SMALL) return 0;
     }
 
     if (title)
     {
-        while (list[i])
+        int len = title->Length / sizeof(WCHAR) + 1;  /* one extra char to check for chars beyond the end */
+        WCHAR *buffer = malloc( (len + 1) * sizeof(WCHAR) );
+
+        if (!buffer) goto done;
+        while (i < size)
         {
-            title_len = NtUserInternalGetWindowText( list[i], buffer, len + 1 );
+            title_len = NtUserInternalGetWindowText( wine_server_ptr_handle(list[i]), buffer, len + 1 );
             if (title_len * sizeof(WCHAR) == title->Length &&
                 (!title_len || !wcsnicmp( buffer, title->Buffer, title_len )))
                 break;
             i++;
         }
+        free( buffer );
     }
-    retvalue = list[i];
+
+    if (i < size) retvalue = wine_server_ptr_handle(list[i]);
 
  done:
     free( list );
-    free( buffer );
     return retvalue;
 }
 
diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h
index f0b9571aaed..3c53a5dd0df 100644
--- a/include/wine/server_protocol.h
+++ b/include/wine/server_protocol.h
@@ -3498,6 +3498,24 @@ struct get_window_list_reply
 
 
 
+struct get_class_windows_request
+{
+    struct request_header __header;
+    user_handle_t  parent;
+    user_handle_t  child;
+    atom_t         atom;
+    /* VARARG(class,unicode_str); */
+};
+struct get_class_windows_reply
+{
+    struct reply_header __header;
+    int            count;
+    /* VARARG(children,user_handles); */
+    char __pad_12[4];
+};
+
+
+
 struct get_window_children_request
 {
     struct request_header __header;
@@ -6023,6 +6041,7 @@ enum request
     REQ_set_parent,
     REQ_get_window_parents,
     REQ_get_window_list,
+    REQ_get_class_windows,
     REQ_get_window_children,
     REQ_get_window_children_from_point,
     REQ_get_window_tree,
@@ -6321,6 +6340,7 @@ union generic_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_class_windows_request get_class_windows_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;
@@ -6617,6 +6637,7 @@ union generic_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_class_windows_reply get_class_windows_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;
@@ -6760,6 +6781,6 @@ union generic_reply
     struct set_keyboard_repeat_reply set_keyboard_repeat_reply;
 };
 
-#define SERVER_PROTOCOL_VERSION 850
+#define SERVER_PROTOCOL_VERSION 851
 
 #endif /* __WINE_WINE_SERVER_PROTOCOL_H */
diff --git a/server/protocol.def b/server/protocol.def
index 8082ad0f7cd..d564d1a3045 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -2583,6 +2583,18 @@ enum message_type
 @END
 
 
+/* Get a list of the window siblings of a specified class */
+@REQ(get_class_windows)
+    user_handle_t  parent;        /* parent window */
+    user_handle_t  child;         /* first child window */
+    atom_t         atom;          /* class atom for the listed siblings */
+    VARARG(class,unicode_str);    /* class name */
+@REPLY
+    int            count;         /* total count of siblings */
+    VARARG(children,user_handles); /* siblings 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 5f2a5518fe7..c254743a980 100644
--- a/server/request_handlers.h
+++ b/server/request_handlers.h
@@ -157,6 +157,7 @@ DECL_HANDLER(set_window_info);
 DECL_HANDLER(set_parent);
 DECL_HANDLER(get_window_parents);
 DECL_HANDLER(get_window_list);
+DECL_HANDLER(get_class_windows);
 DECL_HANDLER(get_window_children);
 DECL_HANDLER(get_window_children_from_point);
 DECL_HANDLER(get_window_tree);
@@ -452,6 +453,7 @@ static const req_handler req_handlers[REQ_NB_REQUESTS] =
     (req_handler)req_set_parent,
     (req_handler)req_get_window_parents,
     (req_handler)req_get_window_list,
+    (req_handler)req_get_class_windows,
     (req_handler)req_get_window_children,
     (req_handler)req_get_window_children_from_point,
     (req_handler)req_get_window_tree,
@@ -1503,6 +1505,12 @@ 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_class_windows_request, parent) == 12 );
+C_ASSERT( offsetof(struct get_class_windows_request, child) == 16 );
+C_ASSERT( offsetof(struct get_class_windows_request, atom) == 20 );
+C_ASSERT( sizeof(struct get_class_windows_request) == 24 );
+C_ASSERT( offsetof(struct get_class_windows_reply, count) == 8 );
+C_ASSERT( sizeof(struct get_class_windows_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 76adb66cec8..be545cd290b 100644
--- a/server/request_trace.h
+++ b/server/request_trace.h
@@ -1776,6 +1776,20 @@ static void dump_get_window_list_reply( const struct get_window_list_reply *req
     dump_varargs_user_handles( ", windows=", cur_size );
 }
 
+static void dump_get_class_windows_request( const struct get_class_windows_request *req )
+{
+    fprintf( stderr, " parent=%08x", req->parent );
+    fprintf( stderr, ", child=%08x", req->child );
+    fprintf( stderr, ", atom=%04x", req->atom );
+    dump_varargs_unicode_str( ", class=", cur_size );
+}
+
+static void dump_get_class_windows_reply( const struct get_class_windows_reply *req )
+{
+    fprintf( stderr, " count=%d", req->count );
+    dump_varargs_user_handles( ", children=", cur_size );
+}
+
 static void dump_get_window_children_request( const struct get_window_children_request *req )
 {
     fprintf( stderr, " desktop=%04x", req->desktop );
@@ -3489,6 +3503,7 @@ static const dump_func req_dumpers[REQ_NB_REQUESTS] =
     (dump_func)dump_set_parent_request,
     (dump_func)dump_get_window_parents_request,
     (dump_func)dump_get_window_list_request,
+    (dump_func)dump_get_class_windows_request,
     (dump_func)dump_get_window_children_request,
     (dump_func)dump_get_window_children_from_point_request,
     (dump_func)dump_get_window_tree_request,
@@ -3784,6 +3799,7 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] =
     (dump_func)dump_set_parent_reply,
     (dump_func)dump_get_window_parents_reply,
     (dump_func)dump_get_window_list_reply,
+    (dump_func)dump_get_class_windows_reply,
     (dump_func)dump_get_window_children_reply,
     (dump_func)dump_get_window_children_from_point_reply,
     (dump_func)dump_get_window_tree_reply,
@@ -4079,6 +4095,7 @@ static const char * const req_names[REQ_NB_REQUESTS] =
     "set_parent",
     "get_window_parents",
     "get_window_list",
+    "get_class_windows",
     "get_window_children",
     "get_window_children_from_point",
     "get_window_tree",
diff --git a/server/window.c b/server/window.c
index 1ddf47c5bfd..07a35552cea 100644
--- a/server/window.c
+++ b/server/window.c
@@ -905,10 +905,11 @@ static int is_point_in_window( struct window *win, int *x, int *y, unsigned int
 }
 
 /* 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 )
+static void append_window_to_list( struct window *win, struct thread *thread, atom_t atom,
+                                   user_handle_t *handles, unsigned int *count, unsigned int max_count )
 {
     if (thread && win->thread != thread) return;
+    if (atom && get_class_atom( win->class ) != atom) return;
     if (*count < max_count) handles[*count] = win->handle;
     (*count)++;
 }
@@ -918,7 +919,6 @@ static void get_window_list( struct desktop *desktop, struct window *win, struct
                              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 */
@@ -926,35 +926,32 @@ static void get_window_list( struct desktop *desktop, struct window *win, struct
         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 );
+            append_window_to_list( child, thread, 0, 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 );
+            append_window_to_list( child, thread, 0, 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 );
+            append_window_to_list( child, thread, 0, 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 );
-        }
+        for (child = win; child; child = get_next_window( child ))
+            append_window_to_list( child, thread, 0, handles, count, max_count );
     }
     else  /* desktop window siblings */
     {
-        append_window_to_list( win, thread, handles, count, max_count );
+        append_window_to_list( win, thread, 0, 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 );
+            append_window_to_list( win->desktop->msg_window, thread, 0, handles, count, max_count );
     }
 }
 
@@ -2477,6 +2474,60 @@ DECL_HANDLER(get_window_list)
 }
 
 
+/* get a list of the window siblings of a specified class */
+DECL_HANDLER(get_class_windows)
+{
+    struct desktop *desktop = NULL;
+    struct window *parent = NULL, *win = NULL;
+    struct unicode_str cls_name = get_req_unicode_str();
+    atom_t atom = req->atom;
+    user_handle_t *data;
+    unsigned int count = 0, max_count = get_reply_max_size() / sizeof(*data);
+
+    if (cls_name.len && !(atom = find_global_atom( NULL, &cls_name ))) return;
+    if (req->parent && !(parent = get_window( req->parent ))) return;
+
+    if (req->child)
+    {
+        if (!parent) parent = get_desktop_window( current );
+        if (!(win = get_window( req->child ))) return;
+        if (win->parent != parent) return;
+        if (!(win = get_next_window( win ))) return;
+    }
+    else if (parent && !(win = get_first_child( parent ))) return;
+
+    if (!win && !(desktop = get_thread_desktop( current, 0 ))) return;
+
+    max_count = min( max_count, MAX_USER_HANDLES );
+    if ((data = mem_alloc( max_count * sizeof(*data) )))
+    {
+        if (desktop) /* top-level and message windows of current desktop */
+        {
+            if (desktop->top_window)
+                for (win = get_first_child( desktop->top_window ); win; win = get_next_window( win ))
+                    append_window_to_list( win, NULL, atom, data, &count, max_count );
+            if (desktop->msg_window)
+                for (win = get_first_child( desktop->msg_window ); win; win = get_next_window( win ))
+                    append_window_to_list( win, NULL, atom, data, &count, max_count );
+        }
+        else
+        {
+            for ( ; win; win = get_next_window( win ))
+                append_window_to_list( win, NULL, atom, 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 (desktop) release_object( desktop );
+}
+
+
 /* get a list of the window children */
 DECL_HANDLER(get_window_children)
 {
-- 
GitLab