winex11.drv: Send missed KEYUP events on KeymapNotify.

Full focus lost / focus gained events on the Windows side are not
feasible for X11's FocusIn/FocusOut events generated by keyboard grabs
(see XGrabKeyboard()) that are used for example for Atl+Tab handling.
Using them would degrade user's experience by causing the window to
minimize or flash multiple times depending on a game/window manager
combo.

Because of that the programs may miss on some KEYUP events that happen
during the grab, and since there are no focus changes on the Windows
side the state doesn't get resynced.

This change attempts to improve user experience by syncing any missed
key release events that happened while the window haven't had focus on
the X11 side.

There's no syncing of key presses as those are more problematic because
of window manager quirks, e.g. on KDE it may end up syncing the Tab
press portion of Alt+Tab. Luckily missing key events for keys that were
pressed and not released while the WM had the keyboard grab is not
nearly as confusing as stuck keys.

For Warhammer: Chaosbane, theHunter: Call of the Wild, Far Cry Primal
and many other games that end up with stuck Alt after Alt+Tabbing.
This commit is contained in:
Arkadiusz Hiler 2023-10-17 13:10:44 +03:00 committed by Alexandre Julliard
parent 6631e6bc2d
commit 2bfe81e41f
4 changed files with 46 additions and 2 deletions

View file

@ -775,6 +775,8 @@ static BOOL X11DRV_FocusIn( HWND hwnd, XEvent *xev )
if (is_virtual_desktop() && hwnd == NtUserGetDesktopWindow()) retry_grab_clipping_window();
if (hwnd == NtUserGetDesktopWindow()) return FALSE;
x11drv_thread_data()->keymapnotify_hwnd = hwnd;
/* when keyboard grab is released, re-apply the cursor clipping rect */
was_grabbed = keyboard_grabbed;
keyboard_grabbed = event->mode == NotifyGrab || event->mode == NotifyWhileGrabbed;

View file

@ -1192,11 +1192,19 @@ BOOL X11DRV_KeymapNotify( HWND hwnd, XEvent *event )
int i, j;
BYTE keystate[256];
WORD vkey;
DWORD flags;
KeyCode keycode;
HWND keymapnotify_hwnd;
BOOL changed = FALSE;
struct {
WORD vkey;
WORD scan;
WORD pressed;
} keys[256];
struct x11drv_thread_data *thread_data = x11drv_thread_data();
keymapnotify_hwnd = thread_data->keymapnotify_hwnd;
thread_data->keymapnotify_hwnd = NULL;
if (!get_async_key_state( keystate )) return FALSE;
@ -1211,11 +1219,17 @@ BOOL X11DRV_KeymapNotify( HWND hwnd, XEvent *event )
{
for (j = 0; j < 8; j++)
{
vkey = keyc2vkey[(i * 8) + j];
keycode = (i * 8) + j;
vkey = keyc2vkey[keycode];
/* If multiple keys map to the same vkey, we want to report it as
* pressed iff any of them are pressed. */
if (!keys[vkey & 0xff].vkey) keys[vkey & 0xff].vkey = vkey;
if (!keys[vkey & 0xff].vkey)
{
keys[vkey & 0xff].vkey = vkey;
keys[vkey & 0xff].scan = keyc2scan[keycode] & 0xff;
}
if (event->xkeymap.key_vector[i] & (1<<j)) keys[vkey & 0xff].pressed = TRUE;
}
}
@ -1227,6 +1241,31 @@ BOOL X11DRV_KeymapNotify( HWND hwnd, XEvent *event )
TRACE( "Adjusting state for vkey %#.2x. State before %#.2x\n",
keys[vkey].vkey, keystate[vkey]);
/* This KeymapNotify follows a FocusIn(mode=NotifyUngrab) event,
* which is caused by a keyboard grab being released.
* See XGrabKeyboard().
*
* We might have missed some key press/release events while the
* keyboard was grabbed, but keyboard grab doesn't generate focus
* lost / focus gained events on the Windows side, so the affected
* program is not aware that it needs to resync the keyboard state.
*
* This, for example, may cause Alt being stuck after using Alt+Tab.
*
* To work around that problem we sync any possible key releases.
*
* Syncing key presses is not feasible as window managers differ in
* event sequences, e.g. KDE performs two keyboard grabs for
* Alt+Tab, which would sync the Tab press.
*/
if (keymapnotify_hwnd && !keys[vkey].pressed)
{
TRACE( "Sending KEYUP for a modifier %#.2x\n", vkey);
flags = KEYEVENTF_KEYUP;
if (keys[vkey].vkey & 0x1000) flags |= KEYEVENTF_EXTENDEDKEY;
X11DRV_send_keyboard_input( keymapnotify_hwnd, vkey, keys[vkey].scan, flags, NtGetTickCount() );
}
update_key_state( keystate, vkey, keys[vkey].pressed );
changed = TRUE;
}

View file

@ -1611,6 +1611,8 @@ BOOL X11DRV_EnterNotify( HWND hwnd, XEvent *xev )
TRACE( "hwnd %p/%lx pos %d,%d detail %d\n", hwnd, event->window, event->x, event->y, event->detail );
x11drv_thread_data()->keymapnotify_hwnd = hwnd;
if (hwnd == x11drv_thread_data()->grab_hwnd) return FALSE;
/* simulate a mouse motion event */

View file

@ -382,6 +382,7 @@ struct x11drv_thread_data
XEvent *current_event; /* event currently being processed */
HWND grab_hwnd; /* window that currently grabs the mouse */
HWND last_focus; /* last window that had focus */
HWND keymapnotify_hwnd; /* window that should receive modifier release events */
XIM xim; /* input method */
HWND last_xic_hwnd; /* last xic window */
XFontSet font_set; /* international text drawing font set */