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.
This doesn't implement all this rules, but rather keep rely on the host window management with some additional heuristics to avoid activating windows which lost foreground recently. This is mostly about keeping track of user input time, updating it on user input and on focus change.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58167 --- server/process.c | 1 + server/process.h | 1 + server/queue.c | 32 +++++++++++++++++++++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-)
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..b0705624a98 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_input */ 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 82d5a395957..157d00a6443 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,9 @@ 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};
+ 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 +1933,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 +1948,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 @@ -3802,11 +3813,26 @@ 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 (queue->input && input && queue->input != input && queue->input->user_time < input->user_time) + { + if (debug_level) fprintf( stderr, "%04x: refusing set_foreground_window %#x input %p/%ju, foreground %p/%ju\n", current->id, req->handle, + queue->input, (uintmax_t)queue->input->user_time, input, (uintmax_t)input->user_time ); + set_win32_error( ERROR_ACCESS_DENIED ); + release_object( desktop ); + return; + } + } + if (debug_level) fprintf( stderr, "%04x: allowing set_foreground_window %#x input %p/%ju, foreground %p/%ju\n", current->id, + req->handle, queue->input, (uintmax_t)queue->input->user_time, input, input ? (uintmax_t)input->user_time : 0 ); + reply->send_msg_old = (reply->previous && desktop->foreground_input != queue->input); reply->send_msg_new = FALSE;