From: Rémi Bernon rbernon@codeweavers.com
Pegasus Mail posts a WM_USER + 8991 message to its own window when it loses focus, then calls SetFocus on its window when processing this message. Several other applications have been seen calling SetWindowPos during WM_ACTIVATE message, which might also attempt to reactivate the window.
That processing happens shortly after we have changed the foreground window to the desktop window, when focus is lost, then SetFocus tries to change the foreground window again.
This SetFocus behavior is tested, and should work like this, but would only activate the window if the process is allowed to do so. Windows has various rules around this, and it seems to boil down to something like:
* Allow taking focus if the process never was foreground, ie: when process is starting and gets initial focus on its windows.
* Allow taking focus if the process was foreground but lost it recently because of a window being destroyed.
* Forbid taking focus back if the process had foreground and called SetForegroundWindow explicitly to give it to another process.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58167 --- dlls/user32/tests/win.c | 2 +- server/process.c | 1 + server/process.h | 1 + server/queue.c | 47 +++++++++++++++++++++++++++++++++++++---- server/winstation.c | 1 + 5 files changed, 47 insertions(+), 5 deletions(-)
diff --git a/dlls/user32/tests/win.c b/dlls/user32/tests/win.c index 354d6ed5ca3..f2b9888c63a 100644 --- a/dlls/user32/tests/win.c +++ b/dlls/user32/tests/win.c @@ -3834,7 +3834,7 @@ static void test_SetFocus(HWND hwnd) flush_events( TRUE ); ok( GetFocus() == 0, "got focus %p\n", GetFocus() ); ok( GetActiveWindow() == 0, "got active %p\n", GetActiveWindow() ); - todo_wine ok( GetForegroundWindow() == 0, "got foreground %p\n", GetForegroundWindow() ); + ok( GetForegroundWindow() == 0, "got foreground %p\n", GetForegroundWindow() );
SetFocus( other ); ok( GetFocus() == other, "got focus %p\n", GetFocus() ); diff --git a/server/process.c b/server/process.c index 52abaa21d75..4a06595c66e 100644 --- a/server/process.c +++ b/server/process.c @@ -682,6 +682,7 @@ struct process *create_process( int fd, struct process *parent, unsigned int fla process->is_system = 0; process->debug_children = 1; process->is_terminating = 0; + process->set_foreground = 0; process->imagelen = 0; process->image = NULL; process->job = NULL; diff --git a/server/process.h b/server/process.h index 5c136fb5103..7cfcc39c258 100644 --- a/server/process.h +++ b/server/process.h @@ -63,6 +63,7 @@ struct process unsigned int is_system:1; /* is it a system process? */ unsigned int debug_children:1;/* also debug all child processes */ unsigned int is_terminating:1;/* is process terminating? */ + unsigned int set_foreground:1;/* has process called set_foreground_window */ data_size_t imagelen; /* length of image path in bytes */ WCHAR *image; /* main exe image full path */ struct job *job; /* job object associated with this process */ diff --git a/server/queue.c b/server/queue.c index fb27524fd49..6ffb0ef81eb 100644 --- a/server/queue.c +++ b/server/queue.c @@ -22,6 +22,7 @@
#include <assert.h> #include <stdarg.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> @@ -112,6 +113,7 @@ struct thread_input int caret_hide; /* caret hide count */ int caret_state; /* caret on/off state */ struct list msg_list; /* list of hardware messages */ + timeout_t user_time; /* time of last user input */ unsigned char desktop_keystate[256]; /* desktop keystate when keystate was synced */ input_shm_t *shared; /* thread input in session shared memory */ }; @@ -253,6 +255,7 @@ static struct thread_input *create_thread_input( struct thread *thread ) if ((input = alloc_object( &thread_input_ops ))) { list_init( &input->msg_list ); + input->user_time = 0; input->shared = NULL;
if (!(input->desktop = get_thread_desktop( thread, 0 /* FIXME: access rights */ ))) @@ -665,6 +668,12 @@ static void set_foreground_input( struct desktop *desktop, struct thread_input * input_shm_t *input_shm, *old_input_shm; shared_object_t dummy_obj = {0};
+ if (input) + { + input->user_time = monotonic_time; + if (debug_level) fprintf( stderr, "%04x: updating input %p user_time %ju\n", current->id, input, (uintmax_t)input->user_time); + } + if (desktop->foreground_input == input) return; input_shm = input ? input->shared : &dummy_obj.shm.input; old_input_shm = desktop->foreground_input ? desktop->foreground_input->shared : &dummy_obj.shm.input; @@ -1927,6 +1936,13 @@ static void queue_hardware_message( struct desktop *desktop, struct message *msg
win = find_hardware_message_window( desktop, input, msg, &msg_code, &thread ); flags = thread ? get_rawinput_device_flags( thread->process, msg ) : 0; + if (thread) input = thread->queue->input; + if (input && (get_hardware_msg_bit( msg->msg ) & (QS_KEY | QS_MOUSEBUTTON))) + { + input->user_time = monotonic_time; + if (debug_level) fprintf( stderr, "%04x: updating input %p user_time %ju\n", current->id, input, (uintmax_t)input->user_time); + } + if (!win || !thread || (flags & RIDEV_NOLEGACY)) { if (input && !(flags & RIDEV_NOLEGACY)) update_thread_input_key_state( input, msg->msg, msg->wparam ); @@ -1935,8 +1951,6 @@ static void queue_hardware_message( struct desktop *desktop, struct message *msg return; }
- input = thread->queue->input; - if (win != msg->win) always_queue = 1; if (!always_queue || merge_message( input, msg )) free_message( msg ); else @@ -1994,12 +2008,24 @@ static struct thread *get_foreground_thread( struct desktop *desktop, user_handl if (desktop->foreground_input) { input_shm_t *input_shm = desktop->foreground_input->shared; - return get_window_thread( input_shm->focus ); + if (!(window = input_shm->focus)) window = input_shm->active; } if (window) return get_window_thread( window ); return NULL; }
+static int is_current_process_foreground( struct desktop *desktop ) +{ + struct thread *thread; + int ret; + + if (!(thread = get_foreground_thread( desktop, 0 ))) return 1; + ret = thread->process == current->process || thread->process->id == current->process->parent_id; + release_object( thread ); + + return ret; +} + /* user32 reserves 1 & 2 for winemouse and winekeyboard, * keep this in sync with user_private.h */ #define WINE_MOUSE_HANDLE 1 @@ -3802,11 +3828,24 @@ DECL_HANDLER(set_foreground_window) struct thread_input *input; struct msg_queue *queue = get_current_queue();
- if (!(desktop = get_thread_desktop( current, 0 ))) return; + if (!queue || !(desktop = get_thread_desktop( current, 0 ))) return;
if (!(input = desktop->foreground_input)) reply->previous = 0; else reply->previous = input->shared->active;
+ if (!req->internal) + { + if (!current->process->set_foreground) current->process->set_foreground = 1; + else if (!is_current_process_foreground( desktop ) && queue->input && input && queue->input->user_time < input->user_time) + { + if (debug_level) fprintf( stderr, "%04x: refusing set_foreground_window user_time %ju < %ju\n", current->id, + (uintmax_t)queue->input->user_time, (uintmax_t)input->user_time ); + set_win32_error( ERROR_ACCESS_DENIED ); + release_object( desktop ); + return; + } + } + reply->send_msg_old = (reply->previous && desktop->foreground_input != queue->input); reply->send_msg_new = FALSE;
diff --git a/server/winstation.c b/server/winstation.c index 52c3f1ce1eb..a36253e20db 100644 --- a/server/winstation.c +++ b/server/winstation.c @@ -864,6 +864,7 @@ DECL_HANDLER(set_thread_desktop) { if (old_desktop) remove_desktop_thread( old_desktop, current ); add_desktop_thread( new_desktop, current ); + current->process->set_foreground = 0; } reply->locator = get_shared_object_locator( new_desktop->shared ); }