From 4f6d4895c3420e5ac301a916bced4a0065a19183 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 14 Dec 2021 17:06:36 +0100 Subject: [PATCH] Handle macOS keyboard stealing The system steals keyboard events for certain system keyboard shortcuts, e.g. Cmd+Tab. Unfortunately this isn't considered a focus loss, so we don't realise we've lost a few keyboard events and can end up in a confused state. Fortunately it is possible to detect when this happens and reset the keyboard state, just like we do when focus is lost. --- vncviewer/Viewport.cxx | 20 ++++++++++++++++---- vncviewer/Viewport.h | 2 ++ vncviewer/cocoa.h | 1 + vncviewer/cocoa.mm | 21 +++++++++++++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 81ad982726..83872ced76 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -643,10 +643,9 @@ int Viewport::handle(int event) return 1; case FL_UNFOCUS: - // Release all keys that were pressed as that generally makes most - // sense (e.g. Alt+Tab where we only see the Alt press) - while (!downKeySym.empty()) - handleKeyRelease(downKeySym.begin()->first); + // We won't get more key events, so reset our knowledge about keys + resetKeyboard(); + Fl::enable_im(); return 1; @@ -823,6 +822,13 @@ void Viewport::handlePointerTimeout(void *data) } +void Viewport::resetKeyboard() +{ + while (!downKeySym.empty()) + handleKeyRelease(downKeySym.begin()->first); +} + + void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym) { static bool menuRecursion = false; @@ -1125,6 +1131,12 @@ int Viewport::handleSystemEvent(void *event, void *data) return 1; } #elif defined(__APPLE__) + // Special event that means we temporarily lost some input + if (cocoa_is_keyboard_sync(event)) { + self->resetKeyboard(); + return 1; + } + if (cocoa_is_keyboard_event(event)) { int keyCode; diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index 19def92c09..a42b06c683 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -84,6 +84,8 @@ class Viewport : public Fl_Widget, public EmulateMB { void handlePointerEvent(const rfb::Point& pos, int buttonMask); static void handlePointerTimeout(void *data); + void resetKeyboard(); + void handleKeyPress(int keyCode, rdr::U32 keySym); void handleKeyRelease(int keyCode); diff --git a/vncviewer/cocoa.h b/vncviewer/cocoa.h index ca17ddf9da..63b2a53509 100644 --- a/vncviewer/cocoa.h +++ b/vncviewer/cocoa.h @@ -34,6 +34,7 @@ CGColorSpaceRef cocoa_win_color_space(Fl_Window *win); bool cocoa_win_is_zoomed(Fl_Window *win); void cocoa_win_zoom(Fl_Window *win); +int cocoa_is_keyboard_sync(const void *event); int cocoa_is_keyboard_event(const void *event); int cocoa_is_key_press(const void *event); diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm index cf9c9a8e22..e8b202d6f2 100644 --- a/vncviewer/cocoa.mm +++ b/vncviewer/cocoa.mm @@ -175,6 +175,25 @@ void cocoa_win_zoom(Fl_Window *win) [nsw zoom:nsw]; } +int cocoa_is_keyboard_sync(const void *event) +{ + const NSEvent* nsevent = (const NSEvent*)event; + + assert(event); + + // If we get a NSFlagsChanged event with key code 0 then this isn't + // an actual keyboard event but rather the system trying to sync up + // modifier state after it has stolen input for some reason (e.g. + // Cmd+Tab) + + if ([nsevent type] != NSFlagsChanged) + return 0; + if ([nsevent keyCode] != 0) + return 0; + + return 1; +} + int cocoa_is_keyboard_event(const void *event) { NSEvent *nsevent; @@ -185,6 +204,8 @@ int cocoa_is_keyboard_event(const void *event) case NSKeyDown: case NSKeyUp: case NSFlagsChanged: + if (cocoa_is_keyboard_sync(event)) + return 0; return 1; default: return 0;