From ec75b806a6d7e10d8da3aa2626add096b6254f29 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 23 Dec 2021 13:28:35 +0100 Subject: [PATCH 01/21] TEMP: Use event taps for keyboard grab on macOS These are the slightly more proper way to grab the keyboard and macOS and is what similar applications do. It avoids a lot of issues we have, e.g., problems with multiple monitors. Unfortunately we need to have the user explicitly approve this (which really is a good thing, security wise), and Apple have chosen to mark this feature as only for accessibility. --- vncviewer/DesktopWindow.cxx | 22 +--- vncviewer/cocoa.h | 4 +- vncviewer/cocoa.mm | 214 ++++++++++++++++++++++++++---------- 3 files changed, 162 insertions(+), 78 deletions(-) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 584debd1e2..613f86ec84 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -677,10 +677,6 @@ void DesktopWindow::resize(int x, int y, int w, int h) repositionWidgets(); } - - // Some systems require a grab after the window size has been changed. - // Otherwise they might hold on to displays, resulting in them being unusable. - maybeGrabKeyboard(); } @@ -941,14 +937,6 @@ int DesktopWindow::fltkHandle(int event) // not be resized to cover the new screen. A timer makes sense // also on other systems, to make sure that whatever desktop // environment has a chance to deal with things before we do. - // Please note that when using FullscreenSystemKeys on macOS, the - // display configuration cannot be changed: macOS will not detect - // added or removed screens and there will be no - // FL_SCREEN_CONFIGURATION_CHANGED event. This is by design: - // "When you capture a display, you have exclusive use of the - // display. Other applications and system services are not allowed - // to use the display or change its configuration. In addition, - // they are not notified of display changes" Fl::remove_timeout(reconfigureFullscreen); Fl::add_timeout(0.5, reconfigureFullscreen); } @@ -1095,10 +1083,10 @@ void DesktopWindow::grabKeyboard() return; } #elif defined(__APPLE__) - int ret; - - ret = cocoa_capture_displays(this); - if (ret != 0) { + bool ret; + + ret = cocoa_tap_keyboard(); + if (!ret) { vlog.error(_("Failure grabbing keyboard")); return; } @@ -1153,7 +1141,7 @@ void DesktopWindow::ungrabKeyboard() #if defined(WIN32) win32_disable_lowlevel_keyboard(fl_xid(this)); #elif defined(__APPLE__) - cocoa_release_displays(this); + cocoa_untap_keyboard(); #else // FLTK has a grab so lets not mess with it if (Fl::grab()) diff --git a/vncviewer/cocoa.h b/vncviewer/cocoa.h index 64acefbfb4..06a1fedd46 100644 --- a/vncviewer/cocoa.h +++ b/vncviewer/cocoa.h @@ -24,8 +24,8 @@ class Fl_Window; int cocoa_get_level(Fl_Window *win); void cocoa_set_level(Fl_Window *win, int level); -int cocoa_capture_displays(Fl_Window *win); -void cocoa_release_displays(Fl_Window *win); +bool cocoa_tap_keyboard(); +void cocoa_untap_keyboard(); typedef struct CGColorSpace *CGColorSpaceRef; diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm index 1d63b75024..ebdca8d251 100644 --- a/vncviewer/cocoa.mm +++ b/vncviewer/cocoa.mm @@ -20,15 +20,14 @@ #include #endif -#include +#include +#include + #include #include #import - -#include - -static bool captured = false; +#import int cocoa_get_level(Fl_Window *win) { @@ -44,84 +43,181 @@ void cocoa_set_level(Fl_Window *win, int level) [nsw setLevel:level]; } -int cocoa_capture_displays(Fl_Window *win) +static CFMachPortRef event_tap; +static CFRunLoopSourceRef tap_source; + +static bool cocoa_is_trusted() { - NSWindow *nsw; + CFStringRef keys[1]; + CFBooleanRef values[1]; + CFDictionaryRef options; - nsw = (NSWindow*)fl_xid(win); + Boolean trusted; - CGDisplayCount count; - CGDirectDisplayID displays[16]; +#if !defined(MAC_OS_X_VERSION_10_9) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9 + // FIXME: Raise system requirements so this isn't needed + void *lib; + typedef Boolean (*AXIsProcessTrustedWithOptionsRef)(CFDictionaryRef); + AXIsProcessTrustedWithOptionsRef AXIsProcessTrustedWithOptions; + CFStringRef kAXTrustedCheckOptionPrompt; - int sx, sy, sw, sh; - rfb::Rect windows_rect, screen_rect; + lib = dlopen(nullptr, 0); + if (lib == nullptr) + return false; - windows_rect.setXYWH(win->x(), win->y(), win->w(), win->h()); + AXIsProcessTrustedWithOptions = (AXIsProcessTrustedWithOptionsRef)dlsym(lib, "AXIsProcessTrustedWithOptions"); - if (CGGetActiveDisplayList(16, displays, &count) != kCGErrorSuccess) - return 1; + dlclose(lib); - if (count != (unsigned)Fl::screen_count()) - return 1; + if (AXIsProcessTrustedWithOptions == nullptr) + return false; - for (int i = 0; i < Fl::screen_count(); i++) { - Fl::screen_xywh(sx, sy, sw, sh, i); + kAXTrustedCheckOptionPrompt = CFSTR("AXTrustedCheckOptionPrompt"); +#endif - screen_rect.setXYWH(sx, sy, sw, sh); - if (screen_rect.enclosed_by(windows_rect)) { - if (CGDisplayCapture(displays[i]) != kCGErrorSuccess) - return 1; + keys[0] = kAXTrustedCheckOptionPrompt; + values[0] = kCFBooleanTrue; + options = CFDictionaryCreate(kCFAllocatorDefault, + (const void**)keys, + (const void**)values, 1, + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (options == nullptr) + return false; - } else { - // A display might have been captured with the previous - // monitor selection. In that case we don't want to keep - // it when its no longer inside the window_rect. - CGDisplayRelease(displays[i]); - } - } + trusted = AXIsProcessTrustedWithOptions(options); + CFRelease(options); + + return trusted; +} + +// http://atnan.com/blog/2012/02/29/modern-privileged-helper-tools-using-smjobbless-plus-xpc +// https://github.com/numist/Switch/issues/7 +// + +static CGEventRef cocoa_event_tap(CGEventTapProxy /*proxy*/, + CGEventType type, CGEventRef event, + void* /*refcon*/) +{ + fprintf(stderr, "Got event of type %d\n", type); + + if ((type != kCGEventKeyDown) && (type != kCGEventKeyUp) && (type != kCGEventFlagsChanged)) + return event; + + fprintf(stderr, "Code: %d, Flags: 0x%08x\n", + (int)CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode), + (int)CGEventGetFlags(event)); + + fprintf(stderr, "Heading for PSN: %lld\n", + CGEventGetIntegerValueField(event, kCGEventTargetProcessSerialNumber)); + + fprintf(stderr, "Heading for PID: %lld (I am %d)\n", + CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID), + getpid()); + + +#if 0 + ProcessSerialNumber psn; + OSErr err; - captured = true; + psn.highLongOfPSN = 0; + psn.lowLongOfPSN = kCurrentProcess; - if ([nsw level] == CGShieldingWindowLevel()) - return 0; + fprintf(stderr, "0x%08x%08x\n", + psn.highLongOfPSN, psn.lowLongOfPSN); - [nsw setLevel:CGShieldingWindowLevel()]; + err = GetCurrentProcess(&psn); + fprintf(stderr, "GetCurrentProcess(0x%08x%08x) = %d\n", + psn.highLongOfPSN, psn.lowLongOfPSN, err); - // We're not getting put in front of the shielding window in many - // cases on macOS 13, despite setLevel: being documented as also - // pushing the window to the front. So let's explicitly move it. - [nsw orderFront:nsw]; + CGEventPostToPSN(&psn, event); - return 0; + return nullptr; +#elif 1 + pid_t target; + ProcessSerialNumber psn; + OSErr err; + + target = CGEventGetIntegerValueField(event, kCGEventTargetUnixProcessID); + if (target == getpid()) + return event; + + err = GetCurrentProcess(&psn); + fprintf(stderr, "GetCurrentProcess(0x%08x%08x) = %d\n", + psn.highLongOfPSN, psn.lowLongOfPSN, err); + + // FIXME: CGEventPostToPid() in macOS 10.11+ + CGEventPostToPSN(&psn, event); + + return nullptr; +#elif 1 + NSEvent* nsevent; + + nsevent = [NSEvent eventWithCGEvent:event]; + if (nsevent == nil) + return event; + + [NSApp postEvent:nsevent atStart:NO]; + + // Documentation is unclear on ownership here, but doing a release + // here results in a crash, so I guess we shouldn't do that... + //[nsevent release]; + + fprintf(stderr, "Posted to NSApp\n"); + + return nullptr; +#else + return event; +#endif } -void cocoa_release_displays(Fl_Window *win) +bool cocoa_tap_keyboard() { - NSWindow *nsw; - int newlevel; + CGEventMask mask; - if (captured) - CGReleaseAllDisplays(); + if (event_tap != nullptr) + return true; - captured = false; + if (!cocoa_is_trusted()) + return false; - nsw = (NSWindow*)fl_xid(win); + mask = CGEventMaskBit(kCGEventKeyDown) | + CGEventMaskBit(kCGEventKeyUp) | + CGEventMaskBit(kCGEventFlagsChanged); + + // FIXME: Right modes/prio? (doesnt get Ctrl+arrows) + // Test CGEventTapCreateForPSN()/ForPid() + event_tap = CGEventTapCreate(kCGAnnotatedSessionEventTap, + kCGHeadInsertEventTap, + kCGEventTapOptionDefault, + mask, cocoa_event_tap, nullptr); + if (event_tap == nullptr) + return false; + + tap_source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, + event_tap, 0); + CFRunLoopAddSource(CFRunLoopGetCurrent(), tap_source, + kCFRunLoopCommonModes); + + // FIXME: Needed? + CGEventTapEnable(event_tap, true); - // Someone else has already changed the level of this window - if ([nsw level] != CGShieldingWindowLevel()) + return true; +} + +// FIXME: Called in destructor? +void cocoa_untap_keyboard() +{ + if (event_tap == nullptr) return; - // FIXME: Store the previous level somewhere so we don't have to hard - // code a level here. - if (win->fullscreen_active() && win->contains(Fl::focus())) - newlevel = NSStatusWindowLevel; - else - newlevel = NSNormalWindowLevel; - - // Only change if different as the level change also moves the window - // to the top of that level. - if ([nsw level] != newlevel) - [nsw setLevel:newlevel]; + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), tap_source, + kCFRunLoopCommonModes); + CFRelease(tap_source); + tap_source = nullptr; + + CFRelease(event_tap); + event_tap = nullptr; } CGColorSpaceRef cocoa_win_color_space(Fl_Window *win) From ea5786797b12bf2d61c1d21c3c93b258cb93a265 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 27 Dec 2021 10:38:41 +0100 Subject: [PATCH 02/21] TEMP: remove level workaround? --- vncviewer/DesktopWindow.cxx | 9 +++++---- vncviewer/cocoa.h | 3 --- vncviewer/cocoa.mm | 14 -------------- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 613f86ec84..9b66d1ca54 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -1014,14 +1014,15 @@ void DesktopWindow::fullscreen_on() } #ifdef __APPLE__ // This is a workaround for a bug in FLTK, see: https://github.com/fltk/fltk/pull/277 - int savedLevel; - savedLevel = cocoa_get_level(this); + // FIXME: Does this still happen? Maybe side effect of releasing displays + //int savedLevel; + //savedLevel = cocoa_get_level(this); #endif fullscreen_screens(top, bottom, left, right); #ifdef __APPLE__ // This is a workaround for a bug in FLTK, see: https://github.com/fltk/fltk/pull/277 - if (cocoa_get_level(this) != savedLevel) - cocoa_set_level(this, savedLevel); + //if (cocoa_get_level(this) != savedLevel) + // cocoa_set_level(this, savedLevel); #endif if (!fullscreen_active()) diff --git a/vncviewer/cocoa.h b/vncviewer/cocoa.h index 06a1fedd46..146bdf5f79 100644 --- a/vncviewer/cocoa.h +++ b/vncviewer/cocoa.h @@ -21,9 +21,6 @@ class Fl_Window; -int cocoa_get_level(Fl_Window *win); -void cocoa_set_level(Fl_Window *win, int level); - bool cocoa_tap_keyboard(); void cocoa_untap_keyboard(); diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm index ebdca8d251..daba054a4b 100644 --- a/vncviewer/cocoa.mm +++ b/vncviewer/cocoa.mm @@ -29,20 +29,6 @@ #import #import -int cocoa_get_level(Fl_Window *win) -{ - NSWindow *nsw; - nsw = (NSWindow*)fl_xid(win); - return [nsw level]; -} - -void cocoa_set_level(Fl_Window *win, int level) -{ - NSWindow *nsw; - nsw = (NSWindow*)fl_xid(win); - [nsw setLevel:level]; -} - static CFMachPortRef event_tap; static CFRunLoopSourceRef tap_source; From 73e9ebf2367ab39c474e6beccfa73eb82544dc7d Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 21 Jan 2022 17:29:02 +0100 Subject: [PATCH 03/21] Ask user for keyboard access when needed The user needs to authorize vncviewer in order to get the access needed to grab the keyboard. Show this dialog at suitable times to make sure there are no surprises why the keyboard grab isn't working. --- vncviewer/OptionsDialog.cxx | 19 +++++++++++++++++++ vncviewer/OptionsDialog.h | 2 ++ vncviewer/cocoa.h | 2 ++ vncviewer/cocoa.mm | 6 ++++-- vncviewer/vncviewer.cxx | 13 +++++++++++++ 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/vncviewer/OptionsDialog.cxx b/vncviewer/OptionsDialog.cxx index c5f21b2484..101a151004 100644 --- a/vncviewer/OptionsDialog.cxx +++ b/vncviewer/OptionsDialog.cxx @@ -44,6 +44,10 @@ #include "fltk/Fl_Monitor_Arrangement.h" #include "fltk/Fl_Navigation.h" +#ifdef __APPLE__ +#include "cocoa.h" +#endif + #include #include #include @@ -872,6 +876,7 @@ void OptionsDialog::createInputPage(int tx, int ty, int tw, int th) CHECK_MIN_WIDTH, CHECK_HEIGHT, _("Pass system keys directly to server (full screen)"))); + systemKeysCheckbox->callback(handleSystemKeys, this); ty += CHECK_HEIGHT + TIGHT_MARGIN; menuKeyChoice = new Fl_Choice(LBLLEFT(tx, ty, 150, CHOICE_HEIGHT, _("Menu key"))); @@ -1117,6 +1122,20 @@ void OptionsDialog::handleRSAAES(Fl_Widget* /*widget*/, void *data) } +void OptionsDialog::handleSystemKeys(Fl_Widget* /*widget*/, void* data) +{ +#ifdef __APPLE__ + OptionsDialog* dialog = (OptionsDialog*)data; + + // Pop up the access dialog if needed + if (dialog->systemKeysCheckbox->value()) + cocoa_is_trusted(true); +#else + (void)data; +#endif +} + + void OptionsDialog::handleClipboard(Fl_Widget* /*widget*/, void *data) { (void)data; diff --git a/vncviewer/OptionsDialog.h b/vncviewer/OptionsDialog.h index f6ca89b1ba..56cf60352d 100644 --- a/vncviewer/OptionsDialog.h +++ b/vncviewer/OptionsDialog.h @@ -64,6 +64,8 @@ class OptionsDialog : public Fl_Window { static void handleX509(Fl_Widget *widget, void *data); static void handleRSAAES(Fl_Widget *widget, void *data); + static void handleSystemKeys(Fl_Widget *widget, void *data); + static void handleClipboard(Fl_Widget *widget, void *data); static void handleFullScreenMode(Fl_Widget *widget, void *data); diff --git a/vncviewer/cocoa.h b/vncviewer/cocoa.h index 146bdf5f79..b2cae4213a 100644 --- a/vncviewer/cocoa.h +++ b/vncviewer/cocoa.h @@ -21,6 +21,8 @@ class Fl_Window; +bool cocoa_is_trusted(bool prompt=false); + bool cocoa_tap_keyboard(); void cocoa_untap_keyboard(); diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm index daba054a4b..fda23821f1 100644 --- a/vncviewer/cocoa.mm +++ b/vncviewer/cocoa.mm @@ -29,10 +29,12 @@ #import #import +#include "cocoa.h" + static CFMachPortRef event_tap; static CFRunLoopSourceRef tap_source; -static bool cocoa_is_trusted() +bool cocoa_is_trusted(bool prompt) { CFStringRef keys[1]; CFBooleanRef values[1]; @@ -62,7 +64,7 @@ static bool cocoa_is_trusted() #endif keys[0] = kAXTrustedCheckOptionPrompt; - values[0] = kCFBooleanTrue; + values[0] = prompt ? kCFBooleanTrue : kCFBooleanFalse; options = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 1, diff --git a/vncviewer/vncviewer.cxx b/vncviewer/vncviewer.cxx index 4efe6e931d..dfbf2c82e5 100644 --- a/vncviewer/vncviewer.cxx +++ b/vncviewer/vncviewer.cxx @@ -74,6 +74,10 @@ #include "touch.h" #include "vncviewer.h" +#ifdef __APPLE__ +#include "cocoa.h" +#endif + #ifdef WIN32 #include "resource.h" #include "win32.h" @@ -747,6 +751,15 @@ int main(int argc, char** argv) } #endif + +#ifdef __APPLE__ + // FIXME: Should we disable full screen if keyboard grab is active but + // we don't have permissions? Otherwise the permissions dialog + // can be hidden behind our full-screen window. + if (fullscreenSystemKeys) + cocoa_is_trusted(true); +#endif + if (listenMode) { std::list listeners; try { From ec157df0095659f3fe19e55215c3b6d5a76e292b Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 24 Jan 2022 15:43:20 +0100 Subject: [PATCH 04/21] TEMP: release grab in desktopwindow destructor --- vncviewer/DesktopWindow.cxx | 2 ++ vncviewer/cocoa.mm | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 9b66d1ca54..10ab807123 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -260,6 +260,8 @@ DesktopWindow::~DesktopWindow() Fl::remove_timeout(menuOverlay, this); Fl::remove_timeout(updateOverlay, this); + ungrabKeyboard(); + OptionsDialog::removeCallback(handleOptions); delete overlay; diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm index fda23821f1..76740c6a8d 100644 --- a/vncviewer/cocoa.mm +++ b/vncviewer/cocoa.mm @@ -193,7 +193,6 @@ bool cocoa_tap_keyboard() return true; } -// FIXME: Called in destructor? void cocoa_untap_keyboard() { if (event_tap == nullptr) From 16da7c156358471c183821e36dd57fc77c09accf Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 4 Jan 2022 10:57:56 +0100 Subject: [PATCH 05/21] TEMP: experiment giving focus to auth dialog --- vncviewer/CConn.cxx | 1 + vncviewer/DesktopWindow.cxx | 15 +++++++++------ vncviewer/cocoa.h | 2 +- vncviewer/cocoa.mm | 10 ++++++++-- vncviewer/vncviewer.cxx | 5 ++++- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/vncviewer/CConn.cxx b/vncviewer/CConn.cxx index f8e8042974..6e0953f42d 100644 --- a/vncviewer/CConn.cxx +++ b/vncviewer/CConn.cxx @@ -95,6 +95,7 @@ CConn::CConn(const char* vncServerName, network::Socket* socket=nullptr) if(sock == nullptr) { try { + // FIXME: This shouldn't block #ifndef WIN32 if (strchr(vncServerName, '/') != nullptr) { sock = new network::UnixSocket(vncServerName); diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 10ab807123..2042131721 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -195,15 +195,15 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name, #else delayedFullscreen = true; #endif + + // Full screen events are not sent out for a hidden window, + // so send a fake one here to set up things properly. + if (fullscreen_active()) + handle(FL_FULLSCREEN); } show(); - // Full screen events are not sent out for a hidden window, - // so send a fake one here to set up things properly. - if (fullscreen_active()) - handle(FL_FULLSCREEN); - // Unfortunately, current FLTK does not allow us to set the // maximized property on Windows and X11 before showing the window. // See STR #2083 and STR #2178 @@ -821,6 +821,7 @@ int DesktopWindow::handle(int event) { switch (event) { case FL_FULLSCREEN: + vlog.error("FL_FULLSCREEN"); fullScreen.setParam(fullscreen_active()); // Update scroll bars @@ -906,9 +907,11 @@ int DesktopWindow::fltkDispatch(int event, Fl_Window *win) // all monitors and the user clicked on another application. // Make sure we update our grabs with the focus changes. case FL_FOCUS: + vlog.error("FL_FOCUS"); dw->maybeGrabKeyboard(); break; case FL_UNFOCUS: + vlog.error("FL_UNFOCUS"); if (fullscreenSystemKeys) { dw->ungrabKeyboard(); } @@ -1088,7 +1091,7 @@ void DesktopWindow::grabKeyboard() #elif defined(__APPLE__) bool ret; - ret = cocoa_tap_keyboard(); + ret = cocoa_tap_keyboard(this); if (!ret) { vlog.error(_("Failure grabbing keyboard")); return; diff --git a/vncviewer/cocoa.h b/vncviewer/cocoa.h index b2cae4213a..b6fd1a6dbf 100644 --- a/vncviewer/cocoa.h +++ b/vncviewer/cocoa.h @@ -23,7 +23,7 @@ class Fl_Window; bool cocoa_is_trusted(bool prompt=false); -bool cocoa_tap_keyboard(); +bool cocoa_tap_keyboard(Fl_Window* win); void cocoa_untap_keyboard(); typedef struct CGColorSpace *CGColorSpaceRef; diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm index 76740c6a8d..1ade4cc9da 100644 --- a/vncviewer/cocoa.mm +++ b/vncviewer/cocoa.mm @@ -159,15 +159,21 @@ static CGEventRef cocoa_event_tap(CGEventTapProxy /*proxy*/, #endif } -bool cocoa_tap_keyboard() +bool cocoa_tap_keyboard(Fl_Window* win) { CGEventMask mask; if (event_tap != nullptr) return true; - if (!cocoa_is_trusted()) + if (!cocoa_is_trusted()) { + NSWindow *nsw; + nsw = (NSWindow*)fl_xid(win); + [nsw setLevel:NSNormalWindowLevel]; + [nsw orderBack:nullptr]; + [NSApp deactivate]; return false; + } mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | diff --git a/vncviewer/vncviewer.cxx b/vncviewer/vncviewer.cxx index dfbf2c82e5..7065e113e9 100644 --- a/vncviewer/vncviewer.cxx +++ b/vncviewer/vncviewer.cxx @@ -712,7 +712,10 @@ int main(int argc, char** argv) } } - usage(argv[0]); + vlog.error(_("Unknown parameter: %s"), argv[i]); + + if (false) + usage(argv[0]); } strncpy(vncServerName, argv[i], VNCSERVERNAMELEN); From 5f68df5cd7562d713ae6e2c2ac13d76778ec5666 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 27 Dec 2021 10:37:48 +0100 Subject: [PATCH 06/21] Log invalid screen layout To be able to debug exactly what is wrong with the layout. Unfortunately we don't know what log level is used for actual "invalid layout" message, or if it is even logged as all, since that happens elsewhere. So let's be cautious and use a debug log level here. --- common/rfb/ClientParams.cxx | 11 ++++++++++- common/rfb/ServerParams.cxx | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/common/rfb/ClientParams.cxx b/common/rfb/ClientParams.cxx index e5fd105e38..804c0a780b 100644 --- a/common/rfb/ClientParams.cxx +++ b/common/rfb/ClientParams.cxx @@ -28,10 +28,13 @@ #include #include #include +#include #include using namespace rfb; +static LogWriter vlog("ClientParams"); + ClientParams::ClientParams() : majorVersion(0), minorVersion(0), compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), @@ -63,8 +66,14 @@ void ClientParams::setDimensions(int width, int height) void ClientParams::setDimensions(int width, int height, const ScreenSet& layout) { - if (!layout.validate(width, height)) + if (!layout.validate(width, height)) { + char buffer[2048]; + vlog.debug("Invalid screen layout for %dx%d:", width, height); + layout.print(buffer, sizeof(buffer)); + vlog.debug("%s", buffer); + throw std::invalid_argument("Attempted to configure an invalid screen layout"); + } width_ = width; height_ = height; diff --git a/common/rfb/ServerParams.cxx b/common/rfb/ServerParams.cxx index b7432b8f2a..31eb070c87 100644 --- a/common/rfb/ServerParams.cxx +++ b/common/rfb/ServerParams.cxx @@ -25,11 +25,14 @@ #include #include +#include #include #include using namespace rfb; +static LogWriter vlog("ServerParams"); + ServerParams::ServerParams() : majorVersion(0), minorVersion(0), supportsQEMUKeyEvent(false), @@ -60,8 +63,14 @@ void ServerParams::setDimensions(int width, int height) void ServerParams::setDimensions(int width, int height, const ScreenSet& layout) { - if (!layout.validate(width, height)) + if (!layout.validate(width, height)) { + char buffer[2048]; + vlog.debug("Invalid screen layout for %dx%d:", width, height); + layout.print(buffer, sizeof(buffer)); + vlog.debug("%s", buffer); + throw std::invalid_argument("Attempted to configure an invalid screen layout"); + } width_ = width; height_ = height; From e9ba685f077225f8cbe3ed58f314b85ab7c7539c Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 22 Nov 2021 16:57:46 +0100 Subject: [PATCH 07/21] Better fix for fake focus grab events The fake ones have a special mode, so we can simply filter them before they are passed on as FLTK events. --- vncviewer/DesktopWindow.cxx | 56 ++++++++----------------------------- 1 file changed, 11 insertions(+), 45 deletions(-) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 2042131721..abdd5bf905 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -891,6 +891,17 @@ int DesktopWindow::fltkDispatch(int event, Fl_Window *win) if ((event == FL_MOVE) && (win == nullptr)) return 0; +#if !defined(WIN32) && !defined(__APPLE__) + // FLTK passes through the fake grab focus events that can cause us + // to end up in an infinite loop + // https://github.com/fltk/fltk/issues/295 + if ((event == FL_FOCUS) || (event == FL_UNFOCUS)) { + const XFocusChangeEvent* xfocus = &fl_xevent->xfocus; + if ((xfocus->mode == NotifyGrab) || (xfocus->mode == NotifyUngrab)) + return 0; + } +#endif + ret = Fl::handle_(event, win); // This is hackish and the result of the dodgy focus handling in FLTK. @@ -1034,24 +1045,6 @@ void DesktopWindow::fullscreen_on() fullscreen(); } -#if !defined(WIN32) && !defined(__APPLE__) -Bool eventIsFocusWithSerial(Display* /*display*/, XEvent *event, - XPointer arg) -{ - unsigned long serial; - - serial = *(unsigned long*)arg; - - if (event->xany.serial != serial) - return False; - - if ((event->type != FocusIn) && (event->type != FocusOut)) - return False; - - return True; -} -#endif - bool DesktopWindow::hasFocus() { Fl_Widget* focus; @@ -1099,11 +1092,6 @@ void DesktopWindow::grabKeyboard() #else int ret; - XEvent xev; - unsigned long serial; - - serial = XNextRequest(fl_display); - ret = XGrabKeyboard(fl_display, fl_xid(this), True, GrabModeAsync, GrabModeAsync, CurrentTime); if (ret) { @@ -1117,16 +1105,6 @@ void DesktopWindow::grabKeyboard() } return; } - - // Xorg 1.20+ generates FocusIn/FocusOut even when there is no actual - // change of focus. This causes us to get stuck in an endless loop - // grabbing and ungrabbing the keyboard. Avoid this by filtering out - // any focus events generated by XGrabKeyboard(). - XSync(fl_display, False); - while (XCheckIfEvent(fl_display, &xev, &eventIsFocusWithSerial, - (XPointer)&serial) == True) { - vlog.debug("Ignored synthetic focus event cause by grab change"); - } #endif keyboardGrabbed = true; @@ -1153,19 +1131,7 @@ void DesktopWindow::ungrabKeyboard() if (Fl::grab()) return; - XEvent xev; - unsigned long serial; - - serial = XNextRequest(fl_display); - XUngrabKeyboard(fl_display, CurrentTime); - - // See grabKeyboard() - XSync(fl_display, False); - while (XCheckIfEvent(fl_display, &xev, &eventIsFocusWithSerial, - (XPointer)&serial) == True) { - vlog.debug("Ignored synthetic focus event cause by grab change"); - } #endif } From 25f2b91479db0ae9931951a2644636bdc764f6b7 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 31 Oct 2017 09:13:23 +0100 Subject: [PATCH 08/21] Use generic hot key combo for client control We want to be able to define more keyboard combinations that allows the user to control the client instead of sending the keys to the server. Instead of adding a never ending stream of random keys that just confuses things, we'll define a set of modifiers that are the base for all client control commands. --- tests/unit/CMakeLists.txt | 3 + tests/unit/hotkeyhandler.cxx | 761 +++++++++++++++++++++++++++++++++++ vncviewer/CMakeLists.txt | 2 +- vncviewer/DesktopWindow.cxx | 13 +- vncviewer/HotKeyHandler.cxx | 307 ++++++++++++++ vncviewer/HotKeyHandler.h | 81 ++++ vncviewer/Keyboard.h | 3 + vncviewer/KeyboardMacOS.h | 6 +- vncviewer/KeyboardMacOS.mm | 157 +++++--- vncviewer/KeyboardWin32.cxx | 148 ++++++- vncviewer/KeyboardWin32.h | 4 +- vncviewer/KeyboardX11.cxx | 63 +++ vncviewer/KeyboardX11.h | 8 + vncviewer/OptionsDialog.cxx | 177 +++++++- vncviewer/OptionsDialog.h | 15 +- vncviewer/Viewport.cxx | 127 ++++-- vncviewer/Viewport.h | 6 +- vncviewer/menukey.cxx | 86 ---- vncviewer/menukey.h | 34 -- vncviewer/parameters.cxx | 10 +- vncviewer/parameters.h | 4 +- vncviewer/vncviewer.man | 45 +-- 22 files changed, 1763 insertions(+), 297 deletions(-) create mode 100644 tests/unit/hotkeyhandler.cxx create mode 100644 vncviewer/HotKeyHandler.cxx create mode 100644 vncviewer/HotKeyHandler.h delete mode 100644 vncviewer/menukey.cxx delete mode 100644 vncviewer/menukey.h diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 8e22305285..3f5f46cf04 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -13,6 +13,9 @@ target_link_libraries(gesturehandler rfb) add_executable(hostport hostport.cxx) target_link_libraries(hostport rfb) +add_executable(hotkeyhandler hotkeyhandler.cxx ../../vncviewer/HotKeyHandler.cxx) +target_link_libraries(hotkeyhandler rfb ${GETTEXT_LIBRARIES}) + add_executable(pixelformat pixelformat.cxx) target_link_libraries(pixelformat rfb) diff --git a/tests/unit/hotkeyhandler.cxx b/tests/unit/hotkeyhandler.cxx new file mode 100644 index 0000000000..3fc32ac899 --- /dev/null +++ b/tests/unit/hotkeyhandler.cxx @@ -0,0 +1,761 @@ +/* Copyright 2021 Pierre Ossman for Cendio AB + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#define XK_LATIN1 +#define XK_MISCELLANY +#include + +#include "HotKeyHandler.h" + +#define ASSERT_EQ(expr, val) { \ + int _expr = (expr); \ + int _val = (val); \ + if (_expr != _val) { \ + printf("FAILED on line %d (%s equals %d, expected %d (%s))\n", __LINE__, #expr, _expr, _val, #val); \ + return; \ + } \ +} + +static void testNoCombo() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo(""); + + ASSERT_EQ(handler.handleKeyPress(1, XK_a), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Shift_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(4, XK_Hyper_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(5, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(5), HotKeyHandler::KeyNormal); + + printf("OK\n"); +} + +static void testSingleArmed() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyUnarm); + + printf("OK\n"); +} + +static void testSingleDualArmed() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyUnarm); + + printf("OK\n"); +} + +static void testSingleCombo() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testSingleRightCombo() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_R), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testSingleDualCombo() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(3, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testSingleComboReordered() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); + + printf("OK\n"); +} + +static void testSingleDualComboReordered() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(3, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testSingleComboRepeated() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testSingleComboMultipleKeys() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyPress(3, XK_b), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyPress(4, XK_c), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testSingleWedgeNormal() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_b), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_a), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); + + printf("OK\n"); +} + +static void testSingleWedgeModifier() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Shift_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_a), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); + + printf("OK\n"); +} + +static void testSingleWedgeModifierArmed() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_Shift_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_a), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); + + printf("OK\n"); +} + +static void testSingleWedgeModifierFiring() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testSingleUnwedge() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl"); + + handler.handleKeyPress(1, XK_Shift_L); + handler.handleKeyPress(2, XK_Control_L); + handler.handleKeyRelease(1); + handler.handleKeyRelease(2); + + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(3, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testMultiArmed() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyUnarm); + + printf("OK\n"); +} + +static void testMultiRearmed() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyUnarm); + + printf("OK\n"); +} + +static void testMultiFailedArm() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); + + printf("OK\n"); +} + +static void testMultiDualArmed() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_R), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(4, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyUnarm); + + printf("OK\n"); +} + +static void testMultiCombo() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testMultiRightCombo() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_R), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_R), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_R), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testMultiDualCombo() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(4, XK_Alt_R), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(5, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(6, XK_Shift_R), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(7, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(7), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(6), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(5), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testMultiComboReordered() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); + + printf("OK\n"); +} + +static void testMultiDualComboReordered() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(5, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(7, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyPress(4, XK_Alt_R), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyPress(6, XK_Shift_R), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(6), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(7), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(5), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testMultiComboRepeated() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testMultiComboMultipleKeys() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyPress(5, XK_b), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(5), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyPress(6, XK_c), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(6), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testMultiWedgeNormal() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_b), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(4, XK_Shift_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(5, XK_a), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(5), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); + + printf("OK\n"); +} + +static void testMultiWedgeModifier() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Super_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(4, XK_Shift_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(5, XK_a), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(5), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); + + printf("OK\n"); +} + +static void testMultiWedgeArming() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(1, XK_b), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(4, XK_Shift_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(5, XK_a), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(5), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); + + printf("OK\n"); +} + +static void testMultiWedgeModifierArming() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(4, XK_Super_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); + + printf("OK\n"); +} + +static void testMultiWedgeModifierArmed() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(4, XK_Super_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); + + printf("OK\n"); +} + +static void testMultiWedgeModifierFiring() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyPress(5, XK_Super_L), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(5), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +static void testMultiUnwedge() +{ + HotKeyHandler handler; + + printf("%s: ", __func__); + + handler.setHotKeyCombo("Ctrl,Shift,Alt"); + + handler.handleKeyPress(1, XK_Super_L); + handler.handleKeyPress(2, XK_Control_L); + handler.handleKeyPress(3, XK_Alt_L); + handler.handleKeyPress(4, XK_Shift_L); + handler.handleKeyRelease(1); + handler.handleKeyRelease(2); + handler.handleKeyRelease(3); + handler.handleKeyRelease(4); + + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(4, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(5, XK_a), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(5), HotKeyHandler::KeyHotKey); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); + + printf("OK\n"); +} + +int main(int /*argc*/, char** /*argv*/) +{ + testNoCombo(); + + /* Single combo key */ + + testSingleArmed(); + testSingleDualArmed(); + + testSingleCombo(); + testSingleRightCombo(); + testSingleDualCombo(); + + testSingleComboReordered(); + testSingleDualComboReordered(); + + testSingleComboRepeated(); + testSingleComboMultipleKeys(); + + testSingleWedgeNormal(); + testSingleWedgeModifier(); + testSingleWedgeModifierArmed(); + testSingleWedgeModifierFiring(); + + testSingleUnwedge(); + + /* Multiple combo keys */ + + testMultiArmed(); + testMultiRearmed(); + testMultiFailedArm(); + testMultiDualArmed(); + + testMultiCombo(); + testMultiRightCombo(); + testMultiDualCombo(); + + testMultiComboReordered(); + testMultiDualComboReordered(); + + testMultiComboRepeated(); + testMultiComboMultipleKeys(); + + testMultiWedgeNormal(); + testMultiWedgeArming(); + testMultiWedgeModifier(); + testMultiWedgeModifierArming(); + testMultiWedgeModifierArmed(); + testMultiWedgeModifierFiring(); + + testMultiUnwedge(); + + return 0; +} diff --git a/vncviewer/CMakeLists.txt b/vncviewer/CMakeLists.txt index 72904b25f3..aa40eb6d86 100644 --- a/vncviewer/CMakeLists.txt +++ b/vncviewer/CMakeLists.txt @@ -6,11 +6,11 @@ add_executable(vncviewer fltk/Fl_Monitor_Arrangement.cxx fltk/Fl_Navigation.cxx fltk/theme.cxx - menukey.cxx BaseTouchHandler.cxx CConn.cxx DesktopWindow.cxx EmulateMB.cxx + HotKeyHandler.cxx UserDialog.cxx ServerDialog.cxx Surface.cxx diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index abdd5bf905..1be7bbe43a 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -230,7 +230,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name, Fl::add_timeout(0, handleStatsTimeout, this); } - // Show hint about menu key + // Show hint about menu hot key Fl::add_timeout(0.5, menuOverlay, this); // By default we get a slight delay when we warp the pointer, something @@ -685,13 +685,14 @@ void DesktopWindow::resize(int x, int y, int w, int h) void DesktopWindow::menuOverlay(void* data) { DesktopWindow *self; + const char *combo; - self = (DesktopWindow*)data; + combo = HotKeyHandler::comboPrefix(hotKeyCombo); + if (combo[0] == '\0') + return; - if (strcmp((const char*)menuKey, "") != 0) { - self->setOverlay(_("Press %s to open the context menu"), - (const char*)menuKey); - } + self = (DesktopWindow*)data; + self->setOverlay(_("Press %sM to open the context menu"), combo); } void DesktopWindow::setOverlay(const char* text, ...) diff --git a/vncviewer/HotKeyHandler.cxx b/vncviewer/HotKeyHandler.cxx new file mode 100644 index 0000000000..cca2bf4cd0 --- /dev/null +++ b/vncviewer/HotKeyHandler.cxx @@ -0,0 +1,307 @@ +/* Copyright 2021-2023 Pierre Ossman for Cendio AB + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#define XK_MISCELLANY +#include + +#include "HotKeyHandler.h" +#include "i18n.h" + +HotKeyHandler::HotKeyHandler() : + comboMask(0), state(Idle) +{ +} + +void HotKeyHandler::setHotKeyCombo(const char* combo) +{ + comboMask = parseHotKeyCombo(combo); + reset(); +} + +HotKeyHandler::KeyAction HotKeyHandler::handleKeyPress(int keyCode, + uint32_t keySym) +{ + unsigned mask, pressedMask; + std::map::const_iterator iter; + + pressedKeys[keyCode] = keySym; + + if (comboMask == 0) + return KeyNormal; + + mask = keySymToMask(keySym); + + pressedMask = 0; + for (iter = pressedKeys.begin(); iter != pressedKeys.end(); ++iter) + pressedMask |= keySymToMask(iter->second); + + switch (state) { + case Idle: + case Arming: + case Rearming: + if (pressedMask == comboMask) { + // All combo keys are pressed + state = Armed; + return KeyArm; + } if (mask && ((mask & comboMask) == mask)) { + // The new key is part of the combo + if (state == Idle) + state = Arming; + return KeyArming; + } else { + // The new key was something else + state = Wedged; + return KeyNormal; + } + break; + case Armed: + if (mask && ((mask & comboMask) == mask)) { + // The new key is part of the combo + return KeyArm; + } else if (mask) { + // The new key is some other modifier + state = Wedged; + return KeyNormal; + } else { + // The new key was something else + state = Firing; + firedKeys.insert(keyCode); + return KeyHotKey; + } + break; + case Firing: + if (mask) { + // The new key is a modifier (may or may not be part of the combo) + return KeyIgnore; + } else { + // The new key was something else + firedKeys.insert(keyCode); + return KeyHotKey; + } + default: + break; + } + + return KeyNormal; +} + +HotKeyHandler::KeyAction HotKeyHandler::handleKeyRelease(int keyCode) +{ + bool firedKey; + unsigned pressedMask; + std::map::const_iterator iter; + KeyAction action; + + firedKey = firedKeys.count(keyCode) != 0; + + firedKeys.erase(keyCode); + pressedKeys.erase(keyCode); + + pressedMask = 0; + for (iter = pressedKeys.begin(); iter != pressedKeys.end(); ++iter) + pressedMask |= keySymToMask(iter->second); + + switch (state) { + case Arming: + if (pressedKeys.empty()) + action = KeyNormal; + else + action = KeyArming; + break; + case Armed: + if (pressedKeys.empty()) + action = KeyUnarm; + else if (pressedMask == comboMask) + action = KeyArm; + else { + action = KeyArming; + state = Rearming; + } + break; + case Rearming: + if (pressedKeys.empty()) + action = KeyUnarm; + else + action = KeyArming; + break; + case Firing: + if (firedKey) + action = KeyHotKey; + else + action = KeyIgnore; + break; + default: + action = KeyNormal; + } + + if (pressedKeys.empty()) + state = Idle; + + return action; +} + +void HotKeyHandler::reset() +{ + state = Idle; + firedKeys.clear(); + pressedKeys.clear(); +} + +unsigned HotKeyHandler::parseHotKeyCombo(const char* combo) +{ + unsigned comboMask; + std::vector keys; + + comboMask = 0; + keys = rfb::split(combo, ','); + for (size_t i = 0; i < keys.size(); i++) { + if (strcasecmp(keys[i].c_str(), "Ctrl") == 0) + comboMask |= Control; + else if (strcasecmp(keys[i].c_str(), "Shift") == 0) + comboMask |= Shift; + else if (strcasecmp(keys[i].c_str(), "Alt") == 0) + comboMask |= Alt; + else if (strcasecmp(keys[i].c_str(), "Win") == 0) + comboMask |= Super; + else if (strcasecmp(keys[i].c_str(), "Super") == 0) + comboMask |= Super; + else if (strcasecmp(keys[i].c_str(), "Option") == 0) + comboMask |= Alt; + else if (strcasecmp(keys[i].c_str(), "Cmd") == 0) + comboMask |= Super; + else + continue; + } + + return comboMask; +} + +const char* HotKeyHandler::hotKeyComboString(unsigned mask) +{ + static char buffer[1024]; + + buffer[0] = '\0'; + + if (mask & Control) + strcat(buffer, "Ctrl"); + if (mask & Shift) { + if (buffer[0] != '\0') + strcat(buffer, ","); + strcat(buffer, "Shift"); + } + if (mask & Alt) { + if (buffer[0] != '\0') + strcat(buffer, ","); + strcat(buffer, "Alt"); + } + if (mask & Super) { + if (buffer[0] != '\0') + strcat(buffer, ","); + strcat(buffer, "Super"); + } + + return buffer; +} + +const char* HotKeyHandler::comboPrefix(const char* combo, bool justCombo) +{ + return comboPrefix(parseHotKeyCombo(combo), justCombo); +} + +const char* HotKeyHandler::comboPrefix(unsigned mask, bool justCombo) +{ + static char combo[256]; + + combo[0] = '\0'; + if (mask & Control) { +#ifdef __APPLE__ + strcat(combo, "⌃"); +#else + strcat(combo, _("Ctrl")); + strcat(combo, "+"); +#endif + } + if (mask & Shift) { +#ifdef __APPLE__ + strcat(combo, "⇧"); +#else + strcat(combo, _("Shift")); + strcat(combo, "+"); +#endif + } + if (mask & Alt) { +#ifdef __APPLE__ + strcat(combo, "⌥"); +#else + strcat(combo, _("Alt")); + strcat(combo, "+"); +#endif + } + if (mask & Super) { +#ifdef __APPLE__ + strcat(combo, "⌘"); +#else + strcat(combo, _("Win")); + strcat(combo, "+"); +#endif + } + + if (combo[0] == '\0') + return ""; + + if (justCombo) { +#ifndef __APPLE__ + combo[strlen(combo)-1] = '\0'; +#endif + return combo; + } + +#ifdef __APPLE__ + strcat(combo, "\xc2\xa0"); // U+00A0 NO-BREAK SPACE +#endif + + return combo; +} + +unsigned HotKeyHandler::keySymToMask(uint32_t keySym) +{ + switch (keySym) { + case XK_Control_L: + case XK_Control_R: + return Control; + case XK_Shift_L: + case XK_Shift_R: + return Shift; + case XK_Alt_L: + case XK_Alt_R: + return Alt; + case XK_Super_L: + case XK_Super_R: + case XK_Hyper_L: + case XK_Hyper_R: + return Super; + } + + return 0; +} diff --git a/vncviewer/HotKeyHandler.h b/vncviewer/HotKeyHandler.h new file mode 100644 index 0000000000..c382ad9ff2 --- /dev/null +++ b/vncviewer/HotKeyHandler.h @@ -0,0 +1,81 @@ +/* Copyright 2021 Pierre Ossman for Cendio AB + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef __HOTKEYHANDLER__ +#define __HOTKEYHANDLER__ + +#include +#include + +#include + +class HotKeyHandler { +public: + HotKeyHandler(); + + void setHotKeyCombo(const char* combo); + + enum KeyAction { + KeyNormal, + KeyArming, + KeyArm, + KeyUnarm, + KeyHotKey, + KeyIgnore, + }; + + KeyAction handleKeyPress(int keyCode, uint32_t keySym); + KeyAction handleKeyRelease(int keyCode); + + void reset(); + +public: + enum ComboMask { + Control = (1<<0), + Shift = (1<<1), + Alt = (1<<2), + Super = (1<<3), + }; + + static unsigned parseHotKeyCombo(const char* combo); + static const char* hotKeyComboString(unsigned mask); + + static const char* comboPrefix(const char* combo, bool justCombo=false); + static const char* comboPrefix(unsigned mask, bool justCombo=false); + +private: + unsigned keySymToMask(uint32_t keySym); + +private: + unsigned comboMask; + + enum State { + Idle, + Arming, + Armed, + Rearming, + Firing, + Wedged, + }; + State state; + + std::set firedKeys; + std::map pressedKeys; +}; + +#endif diff --git a/vncviewer/Keyboard.h b/vncviewer/Keyboard.h index 81360252ae..695a30fd5f 100644 --- a/vncviewer/Keyboard.h +++ b/vncviewer/Keyboard.h @@ -21,6 +21,8 @@ #include +#include + class KeyboardHandler { public: @@ -36,6 +38,7 @@ class Keyboard virtual ~Keyboard() {}; virtual bool handleEvent(const void* event) = 0; + virtual std::list translateToKeySyms(int systemKeyCode) = 0; virtual void reset() {}; diff --git a/vncviewer/KeyboardMacOS.h b/vncviewer/KeyboardMacOS.h index 0901664b1e..4bcdbd6c68 100644 --- a/vncviewer/KeyboardMacOS.h +++ b/vncviewer/KeyboardMacOS.h @@ -23,10 +23,8 @@ #ifdef __OBJC__ @class NSEvent; -@class NSString; #else class NSEvent; -class NSString; #endif class KeyboardMacOS : public Keyboard @@ -36,6 +34,7 @@ class KeyboardMacOS : public Keyboard virtual ~KeyboardMacOS(); bool handleEvent(const void* event) override; + std::list translateToKeySyms(int systemKeyCode) override; unsigned getLEDState() override; void setLEDState(unsigned state) override; @@ -49,8 +48,7 @@ class KeyboardMacOS : public Keyboard uint32_t translateSystemKeyCode(int systemKeyCode); unsigned getSystemKeyCode(const NSEvent* nsevent); - NSString* keyTranslate(unsigned keyCode, unsigned modifierFlags); - uint32_t translateEventKeysym(const NSEvent* nsevent); + uint32_t translateToKeySym(unsigned keyCode, unsigned modifierFlags); int openHID(unsigned int* ioc); int getModifierLockState(int modifier, bool* on); diff --git a/vncviewer/KeyboardMacOS.mm b/vncviewer/KeyboardMacOS.mm index e0a10dc8d2..d00203635d 100644 --- a/vncviewer/KeyboardMacOS.mm +++ b/vncviewer/KeyboardMacOS.mm @@ -22,6 +22,8 @@ #include +#include + #import #import @@ -53,7 +55,7 @@ static rfb::LogWriter vlog("KeyboardMacOS"); -static const int kvk_map[][2] = { +static const unsigned kvk_map[][2] = { { kVK_Return, XK_Return }, { kVK_Tab, XK_Tab }, { kVK_Space, XK_space }, @@ -154,10 +156,25 @@ if (isKeyPress(nsevent)) { uint32_t keyCode; uint32_t keySym; + unsigned modifiers; keyCode = translateSystemKeyCode(systemKeyCode); - keySym = translateEventKeysym(nsevent); + // We want a "normal" symbol out of the event, which basically means + // we only respect the shift and alt/altgr modifiers. Cocoa can help + // us if we only wanted shift, but as we also want alt/altgr, we'll + // have to do some lookup ourselves. This matches our behaviour on + // other platforms. + + modifiers = 0; + if ([nsevent modifierFlags] & NSAlphaShiftKeyMask) + modifiers |= alphaLock; + if ([nsevent modifierFlags] & NSShiftKeyMask) + modifiers |= shiftKey; + if ([nsevent modifierFlags] & NSAlternateKeyMask) + modifiers |= optionKey; + + keySym = translateToKeySym([nsevent keyCode], modifiers); if (keySym == NoSymbol) { vlog.error(_("No symbol for key code 0x%02x (in the current state)"), systemKeyCode); @@ -176,6 +193,51 @@ return true; } +std::list KeyboardMacOS::translateToKeySyms(int systemKeyCode) +{ + std::list keySyms; + unsigned mods; + + uint32_t ks; + + // Start with no modifiers + ks = translateToKeySym(systemKeyCode, 0); + if (ks != NoSymbol) + keySyms.push_back(ks); + + // Next just a single modifier at a time + for (mods = cmdKey; mods <= controlKey; mods <<= 1) { + std::list::const_iterator iter; + + ks = translateToKeySym(systemKeyCode, mods); + if (ks == NoSymbol) + continue; + + iter = std::find(keySyms.begin(), keySyms.end(), ks); + if (iter != keySyms.end()) + continue; + + keySyms.push_back(ks); + } + + // Finally everything + for (mods = cmdKey; mods < (controlKey << 1); mods += cmdKey) { + std::list::const_iterator iter; + + ks = translateToKeySym(systemKeyCode, mods); + if (ks == NoSymbol) + continue; + + iter = std::find(keySyms.begin(), keySyms.end(), ks); + if (iter != keySyms.end()) + continue; + + keySyms.push_back(ks); + } + + return keySyms; +} + unsigned KeyboardMacOS::getLEDState() { unsigned state; @@ -338,30 +400,35 @@ return code_map_osx_to_qnum[systemKeyCode]; } -NSString* KeyboardMacOS::keyTranslate(unsigned keyCode, - unsigned modifierFlags) +uint32_t KeyboardMacOS::translateToKeySym(unsigned keyCode, + unsigned modifierFlags) { const UCKeyboardLayout *layout; OSStatus err; - layout = nullptr; - TISInputSourceRef keyboard; CFDataRef uchr; + UInt32 dead_state; + UniCharCount max_len, actual_len; + UniChar string[255]; + + // Start with keys that either don't generate a symbol, or + // generate the same symbol as some other key. + for (size_t i = 0;i < sizeof(kvk_map)/sizeof(kvk_map[0]);i++) { + if (keyCode == kvk_map[i][0]) + return kvk_map[i][1]; + } + keyboard = TISCopyCurrentKeyboardLayoutInputSource(); uchr = (CFDataRef)TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData); if (uchr == nullptr) - return nil; + return NoSymbol; layout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); if (layout == nullptr) - return nil; - - UInt32 dead_state; - UniCharCount max_len, actual_len; - UniChar string[255]; + return NoSymbol; dead_state = 0; max_len = sizeof(string)/sizeof(*string); @@ -372,10 +439,12 @@ LMGetKbdType(), 0, &dead_state, max_len, &actual_len, string); if (err != noErr) - return nil; + return NoSymbol; // Dead key? if (dead_state != 0) { + unsigned combining; + // We have no fool proof way of asking what dead key this is. // Assume we get a spacing equivalent if we press the // same key again, and try to deduce something from that. @@ -383,34 +452,28 @@ LMGetKbdType(), 0, &dead_state, max_len, &actual_len, string); if (err != noErr) - return nil; - } - - return [NSString stringWithCharacters:string length:actual_len]; -} - -uint32_t KeyboardMacOS::translateEventKeysym(const NSEvent* nsevent) -{ - UInt16 key_code; - size_t i; + return NoSymbol; - NSString *chars; - UInt32 modifiers; + // FIXME: Some dead keys are given as NBSP + combining character + if (actual_len != 1) + return NoSymbol; - key_code = [nsevent keyCode]; + combining = ucs2combining(string[0]); + if (combining == (unsigned)-1) + return NoSymbol; - // Start with keys that either don't generate a symbol, or - // generate the same symbol as some other key. - for (i = 0;i < sizeof(kvk_map)/sizeof(kvk_map[0]);i++) { - if (key_code == kvk_map[i][0]) - return kvk_map[i][1]; + return ucs2keysym(combining); } + // Sanity check + if (actual_len != 1) + return NoSymbol; + // OS X always sends the same key code for the decimal key on the // num pad, but X11 wants different keysyms depending on if it should // be a comma or full stop. - if (key_code == 0x41) { - switch ([[nsevent charactersIgnoringModifiers] UTF8String][0]) { + if (keyCode == 0x41) { + switch (string[0]) { case ',': return XK_KP_Separator; case '.': @@ -420,33 +483,7 @@ } } - // We want a "normal" symbol out of the event, which basically means - // we only respect the shift and alt/altgr modifiers. Cocoa can help - // us if we only wanted shift, but as we also want alt/altgr, we'll - // have to do some lookup ourselves. This matches our behaviour on - // other platforms. - - modifiers = 0; - if ([nsevent modifierFlags] & NSAlphaShiftKeyMask) - modifiers |= alphaLock; - if ([nsevent modifierFlags] & NSShiftKeyMask) - modifiers |= shiftKey; - if ([nsevent modifierFlags] & NSAlternateKeyMask) - modifiers |= optionKey; - - chars = keyTranslate(key_code, modifiers); - if (chars == nil) - return NoSymbol; - - // FIXME: Some dead keys are given as NBSP + combining character - if ([chars length] != 1) - return NoSymbol; - - // Dead key? - if ([[nsevent characters] length] == 0) - return ucs2keysym(ucs2combining([chars characterAtIndex:0])); - - return ucs2keysym([chars characterAtIndex:0]); + return ucs2keysym(string[0]); } int KeyboardMacOS::openHID(unsigned int* ioc) diff --git a/vncviewer/KeyboardWin32.cxx b/vncviewer/KeyboardWin32.cxx index 1caf48637c..c15629b2af 100644 --- a/vncviewer/KeyboardWin32.cxx +++ b/vncviewer/KeyboardWin32.cxx @@ -24,6 +24,8 @@ #include +#include + // Missing in at least some versions of MinGW #ifndef MAPVK_VK_TO_CHAR #define MAPVK_VK_TO_CHAR 2 @@ -202,6 +204,7 @@ bool KeyboardWin32::handleEvent(const void* event) bool isExtended; int systemKeyCode, keyCode; uint32_t keySym; + BYTE state[256]; vKey = msg->wParam; isExtended = (msg->lParam & (1 << 24)) != 0; @@ -256,7 +259,23 @@ bool KeyboardWin32::handleEvent(const void* event) keyCode = translateSystemKeyCode(systemKeyCode); - keySym = translateVKey(vKey, isExtended); + GetKeyboardState(state); + + // Pressing Ctrl wreaks havoc with the symbol lookup, so turn + // that off. But AltGr shows up as Ctrl+Alt in Windows, so keep + // Ctrl if Alt is active. + if (!(state[VK_LCONTROL] & 0x80) || !(state[VK_RMENU] & 0x80)) + state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; + + keySym = translateVKey(vKey, isExtended, state); + + if (keySym == NoSymbol) { + // Most Ctrl+Alt combinations will fail to produce a symbol, so + // try it again with Ctrl unconditionally disabled. + state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; + keySym = translateVKey(vKey, isExtended, state); + } + if (keySym == NoSymbol) { if (isExtended) vlog.error(_("No symbol for extended virtual key 0x%02x"), (int)vKey); @@ -354,6 +373,114 @@ bool KeyboardWin32::handleEvent(const void* event) return false; } +std::list KeyboardWin32::translateToKeySyms(int systemKeyCode) +{ + unsigned vkey; + bool extended; + + std::list keySyms; + unsigned mods; + + BYTE state[256]; + + uint32_t ks; + + UINT ch; + + extended = systemKeyCode & 0x80; + if (extended) + systemKeyCode = 0xe0 | (systemKeyCode & 0x7f); + + vkey = MapVirtualKey(systemKeyCode, MAPVK_VSC_TO_VK_EX); + if (vkey == 0) + return keySyms; + + // Start with no modifiers + memset(state, 0, sizeof(state)); + ks = translateVKey(vkey, extended, state); + if (ks != NoSymbol) + keySyms.push_back(ks); + + // Next just a single modifier at a time + for (mods = 1; mods < 16; mods <<= 1) { + std::list::const_iterator iter; + + memset(state, 0, sizeof(state)); + if (mods & 0x1) + state[VK_CONTROL] = state[VK_LCONTROL] = 0x80; + if (mods & 0x2) + state[VK_SHIFT] = state[VK_LSHIFT] = 0x80; + if (mods & 0x4) + state[VK_MENU] = state[VK_LMENU] = 0x80; + if (mods & 0x8) { + state[VK_CONTROL] = state[VK_LCONTROL] = 0x80; + state[VK_MENU] = state[VK_RMENU] = 0x80; + } + + ks = translateVKey(vkey, extended, state); + if (ks == NoSymbol) + continue; + + iter = std::find(keySyms.begin(), keySyms.end(), ks); + if (iter != keySyms.end()) + continue; + + keySyms.push_back(ks); + } + + // Finally everything + for (mods = 0; mods < 16; mods++) { + std::list::const_iterator iter; + + memset(state, 0, sizeof(state)); + if (mods & 0x1) + state[VK_CONTROL] = state[VK_LCONTROL] = 0x80; + if (mods & 0x2) + state[VK_SHIFT] = state[VK_LSHIFT] = 0x80; + if (mods & 0x4) + state[VK_MENU] = state[VK_LMENU] = 0x80; + if (mods & 0x8) { + state[VK_CONTROL] = state[VK_LCONTROL] = 0x80; + state[VK_MENU] = state[VK_RMENU] = 0x80; + } + + ks = translateVKey(vkey, extended, state); + if (ks == NoSymbol) + continue; + + iter = std::find(keySyms.begin(), keySyms.end(), ks); + if (iter != keySyms.end()) + continue; + + keySyms.push_back(ks); + } + + // As a final resort we use MapVirtualKey() as that gives us a Latin + // character even on non-Latin keyboards, which is useful for + // shortcuts + // + // FIXME: Can this give us anything but ASCII? + + ch = MapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR); + if (ch != 0) { + if (ch & 0x80000000) + ch = ucs2combining(ch & 0xffff); + else + ch = ch & 0xffff; + + ks = ucs2keysym(ch); + if (ks != NoSymbol) { + std::list::const_iterator iter; + + iter = std::find(keySyms.begin(), keySyms.end(), ks); + if (iter == keySyms.end()) + keySyms.push_back(ks); + } + } + + return keySyms; +} + void KeyboardWin32::reset() { altGrArmed = false; @@ -465,12 +592,12 @@ uint32_t KeyboardWin32::lookupVKeyMap(unsigned vkey, bool extended, return NoSymbol; } -uint32_t KeyboardWin32::translateVKey(unsigned vkey, bool extended) +uint32_t KeyboardWin32::translateVKey(unsigned vkey, bool extended, + const unsigned char state[256]) { HKL layout; WORD lang, primary_lang; - BYTE state[256]; int ret; WCHAR wstr[10]; @@ -524,25 +651,10 @@ uint32_t KeyboardWin32::translateVKey(unsigned vkey, bool extended) // does what we want though. Unfortunately it keeps state, so // we have to be careful around dead characters. - GetKeyboardState(state); - - // Pressing Ctrl wreaks havoc with the symbol lookup, so turn - // that off. But AltGr shows up as Ctrl+Alt in Windows, so keep - // Ctrl if Alt is active. - if (!(state[VK_LCONTROL] & 0x80) || !(state[VK_RMENU] & 0x80)) - state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; - // FIXME: Multi character results, like U+0644 U+0627 // on Arabic layout ret = ToUnicode(vkey, 0, state, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); - if (ret == 0) { - // Most Ctrl+Alt combinations will fail to produce a symbol, so - // try it again with Ctrl unconditionally disabled. - state[VK_CONTROL] = state[VK_LCONTROL] = state[VK_RCONTROL] = 0; - ret = ToUnicode(vkey, 0, state, wstr, sizeof(wstr)/sizeof(wstr[0]), 0); - } - if (ret == 1) return ucs2keysym(wstr[0]); diff --git a/vncviewer/KeyboardWin32.h b/vncviewer/KeyboardWin32.h index 336fe6daf9..ecab92684a 100644 --- a/vncviewer/KeyboardWin32.h +++ b/vncviewer/KeyboardWin32.h @@ -28,6 +28,7 @@ class KeyboardWin32 : public Keyboard virtual ~KeyboardWin32(); bool handleEvent(const void* event) override; + std::list translateToKeySyms(int systemKeyCode) override; void reset() override; @@ -38,7 +39,8 @@ class KeyboardWin32 : public Keyboard uint32_t translateSystemKeyCode(int systemKeyCode); uint32_t lookupVKeyMap(unsigned vkey, bool extended, const UINT map[][3], size_t size); - uint32_t translateVKey(unsigned vkey, bool extended); + uint32_t translateVKey(unsigned vkey, bool extended, + const unsigned char state[256]); bool hasAltGr(); static void handleAltGrTimeout(void *data); diff --git a/vncviewer/KeyboardX11.cxx b/vncviewer/KeyboardX11.cxx index 0ee1d4f65b..a4706014c2 100644 --- a/vncviewer/KeyboardX11.cxx +++ b/vncviewer/KeyboardX11.cxx @@ -22,6 +22,7 @@ #include +#include #include #include @@ -115,6 +116,31 @@ bool KeyboardX11::handleEvent(const void* event) return false; } +std::list KeyboardX11::translateToKeySyms(int systemKeyCode) +{ + Status status; + XkbStateRec state; + std::list keySyms; + unsigned char group; + + status = XkbGetState(fl_display, XkbUseCoreKbd, &state); + if (status != Success) + return keySyms; + + // Start with the currently used group + translateToKeySyms(systemKeyCode, state.group, &keySyms); + + // Then all other groups + for (group = 0; group < XkbNumKbdGroups; group++) { + if (group == state.group) + continue; + + translateToKeySyms(systemKeyCode, group, &keySyms); + } + + return keySyms; +} + unsigned KeyboardX11::getLEDState() { unsigned state; @@ -218,3 +244,40 @@ unsigned KeyboardX11::getModifierMask(uint32_t keysym) return mask; } + +void KeyboardX11::translateToKeySyms(int systemKeyCode, + unsigned char group, + std::list* keySyms) +{ + unsigned int mods; + + // Start with no modifiers + translateToKeySyms(systemKeyCode, group, 0, keySyms); + + // Next just a single modifier at a time + for (mods = 1; mods < (Mod5Mask+1); mods <<= 1) + translateToKeySyms(systemKeyCode, group, mods, keySyms); + + // Finally everything + for (mods = 0; mods < (Mod5Mask<<1); mods++) + translateToKeySyms(systemKeyCode, group, mods, keySyms); +} + +void KeyboardX11::translateToKeySyms(int systemKeyCode, + unsigned char group, + unsigned char mods, + std::list* keySyms) +{ + KeySym ks; + std::list::const_iterator iter; + + ks = XkbKeycodeToKeysym(fl_display, systemKeyCode, group, mods); + if (ks == NoSymbol) + return; + + iter = std::find(keySyms->begin(), keySyms->end(), ks); + if (iter != keySyms->end()) + return; + + keySyms->push_back(ks); +} diff --git a/vncviewer/KeyboardX11.h b/vncviewer/KeyboardX11.h index ba9a88f981..d0ab2aa65e 100644 --- a/vncviewer/KeyboardX11.h +++ b/vncviewer/KeyboardX11.h @@ -28,6 +28,7 @@ class KeyboardX11 : public Keyboard virtual ~KeyboardX11(); bool handleEvent(const void* event) override; + std::list translateToKeySyms(int systemKeyCode) override; unsigned getLEDState() override; void setLEDState(unsigned state) override; @@ -35,6 +36,13 @@ class KeyboardX11 : public Keyboard protected: unsigned getModifierMask(uint32_t keysym); +private: + void translateToKeySyms(int systemKeyCode, unsigned char group, + std::list* keySyms); + void translateToKeySyms(int systemKeyCode, + unsigned char group, unsigned char mods, + std::list* keySyms); + private: int code_map_keycode_to_qnum[256]; }; diff --git a/vncviewer/OptionsDialog.cxx b/vncviewer/OptionsDialog.cxx index 101a151004..ffc68f5100 100644 --- a/vncviewer/OptionsDialog.cxx +++ b/vncviewer/OptionsDialog.cxx @@ -35,8 +35,8 @@ #endif #include "OptionsDialog.h" +#include "HotKeyHandler.h" #include "i18n.h" -#include "menukey.h" #include "parameters.h" #include "fltk/layout.h" @@ -49,11 +49,13 @@ #endif #include +#include #include #include #include #include #include +#include #include #include @@ -89,6 +91,7 @@ OptionsDialog::OptionsDialog() createCompressionPage(tx, ty, tw, th); createSecurityPage(tx, ty, tw, th); createInputPage(tx, ty, tw, th); + createHotKeysPage(tx, ty, tw, th); createDisplayPage(tx, ty, tw, th); createMiscPage(tx, ty, tw, th); } @@ -315,8 +318,6 @@ void OptionsDialog::loadOptions(void) #endif /* Input */ - const char *menuKeyBuf; - viewOnlyCheckbox->value(viewOnly); emulateMBCheckbox->value(emulateMiddleButton); acceptClipboardCheckbox->value(acceptClipboard); @@ -329,12 +330,17 @@ void OptionsDialog::loadOptions(void) #endif systemKeysCheckbox->value(fullscreenSystemKeys); - menuKeyChoice->value(0); + /* Hot Keys */ + unsigned comboMask; + + comboMask = HotKeyHandler::parseHotKeyCombo(hotKeyCombo); + + ctrlButton->value(comboMask & HotKeyHandler::Control); + shiftButton->value(comboMask & HotKeyHandler::Shift); + altButton->value(comboMask & HotKeyHandler::Alt); + superButton->value(comboMask & HotKeyHandler::Super); - menuKeyBuf = menuKey; - for (int idx = 0; idx < getMenuKeySymbolCount(); idx++) - if (!strcmp(getMenuKeySymbols()[idx].name, menuKeyBuf)) - menuKeyChoice->value(idx + 1); + handleHotKey(nullptr, this); /* Display */ if (!fullScreen) { @@ -464,11 +470,20 @@ void OptionsDialog::storeOptions(void) #endif fullscreenSystemKeys.setParam(systemKeysCheckbox->value()); - if (menuKeyChoice->value() == 0) - menuKey.setParam(""); - else { - menuKey.setParam(menuKeyChoice->text()); - } + /* Hot Keys */ + unsigned comboMask; + + comboMask = 0; + if (ctrlButton->value()) + comboMask |= HotKeyHandler::Control; + if (shiftButton->value()) + comboMask |= HotKeyHandler::Shift; + if (altButton->value()) + comboMask |= HotKeyHandler::Alt; + if (superButton->value()) + comboMask |= HotKeyHandler::Super; + + hotKeyCombo.setParam(HotKeyHandler::hotKeyComboString(comboMask)); /* Display */ if (windowedButton->value()) { @@ -878,14 +893,6 @@ void OptionsDialog::createInputPage(int tx, int ty, int tw, int th) _("Pass system keys directly to server (full screen)"))); systemKeysCheckbox->callback(handleSystemKeys, this); ty += CHECK_HEIGHT + TIGHT_MARGIN; - - menuKeyChoice = new Fl_Choice(LBLLEFT(tx, ty, 150, CHOICE_HEIGHT, _("Menu key"))); - - fltk_menu_add(menuKeyChoice, _("None"), 0, nullptr, nullptr, FL_MENU_DIVIDER); - for (int idx = 0; idx < getMenuKeySymbolCount(); idx++) - fltk_menu_add(menuKeyChoice, getMenuKeySymbols()[idx].name, 0, nullptr, nullptr, 0); - - ty += CHOICE_HEIGHT + TIGHT_MARGIN; } ty -= TIGHT_MARGIN; @@ -954,6 +961,76 @@ void OptionsDialog::createInputPage(int tx, int ty, int tw, int th) } +void OptionsDialog::createHotKeysPage(int tx, int ty, int tw, int th) +{ + Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Hot Keys")); + + tx += OUTER_MARGIN; + ty += OUTER_MARGIN; + + Fl_Box *intro = new Fl_Box(tx, ty, tw - OUTER_MARGIN * 2, INPUT_HEIGHT); + intro->align(FL_ALIGN_TOP_LEFT|FL_ALIGN_INSIDE); + intro->label(_("Currently active hot keys:")); + + ty += INPUT_HEIGHT + INNER_MARGIN; + + int width; + + width = (tw - OUTER_MARGIN * 2 - INNER_MARGIN * 3) / 4; + + ctrlButton = new Fl_Toggle_Button(tx, ty, + /* + * TRANSLATORS: This refers to the + * keyboard key + * */ + width, BUTTON_HEIGHT, _("Ctrl")); + ctrlButton->selection_color(FL_SELECTION_COLOR); + ctrlButton->callback(handleHotKey, this); + shiftButton = new Fl_Toggle_Button(tx + width + INNER_MARGIN, ty, + /* + * TRANSLATORS: This refers to the + * keyboard key + * */ + width, BUTTON_HEIGHT, _("Shift")); + shiftButton->selection_color(FL_SELECTION_COLOR); + shiftButton->callback(handleHotKey, this); + altButton = new Fl_Toggle_Button(tx + width * 2 + INNER_MARGIN * 2, ty, + /* + * TRANSLATORS: This refers to the + * keyboard key + * */ + width, BUTTON_HEIGHT, _("Alt")); + altButton->selection_color(FL_SELECTION_COLOR); + altButton->callback(handleHotKey, this); + superButton = new Fl_Toggle_Button(tx + width * 3 + INNER_MARGIN * 3, ty, + /* + * TRANSLATORS: This refers to the + * keyboard key + * */ + width, BUTTON_HEIGHT, _("Win")); + superButton->selection_color(FL_SELECTION_COLOR); + superButton->callback(handleHotKey, this); + +#ifdef __APPLE__ + /* TRANSLATORS: This refers to the keyboard key */ + ctrlButton->label(_("⌃ Ctrl")); + /* TRANSLATORS: This refers to the keyboard key */ + shiftButton->label(_("⇧ Shift")); + /* TRANSLATORS: This refers to the keyboard key */ + altButton->label(_("⌥ Option")); + /* TRANSLATORS: This refers to the keyboard key */ + superButton->label(_("⌘ Cmd")); +#endif + + ty += BUTTON_HEIGHT + INNER_MARGIN; + + hotKeyText = new Fl_Box(tx, ty, tw - OUTER_MARGIN * 2, th - ty - OUTER_MARGIN); + hotKeyText->align(FL_ALIGN_TOP_LEFT|FL_ALIGN_INSIDE); + + group->end(); +} + + void OptionsDialog::createDisplayPage(int tx, int ty, int tw, int th) { Fl_Group *group = new Fl_Group(tx, ty, tw, th, _("Display")); @@ -1153,6 +1230,64 @@ void OptionsDialog::handleClipboard(Fl_Widget* /*widget*/, void *data) #endif } +void OptionsDialog::handleHotKey(Fl_Widget* /*widget*/, void *data) +{ + OptionsDialog *dialog = (OptionsDialog*)data; + unsigned mask; + + mask = 0; + if (dialog->ctrlButton->value()) + mask |= HotKeyHandler::Control; + if (dialog->shiftButton->value()) + mask |= HotKeyHandler::Shift; + if (dialog->altButton->value()) + mask |= HotKeyHandler::Alt; + if (dialog->superButton->value()) + mask |= HotKeyHandler::Super; + + if (mask == 0) { + snprintf(dialog->hotKeyTextBuffer, + sizeof(dialog->hotKeyTextBuffer), "%s", + _("All hot key combinations are disabled.")); + } else { + char combo[256]; + char combo_noplus[256]; + + strcpy(combo, HotKeyHandler::comboPrefix(mask)); + strcpy(combo_noplus, HotKeyHandler::comboPrefix(mask, true)); + + snprintf(dialog->hotKeyTextBuffer, + sizeof(dialog->hotKeyTextBuffer), + _("To open session context menu, press %sM."), + combo); + } + + /* FLTK can't reflow labels, so we'll have to do it manually */ + + char *prevdiv, *curdiv; + int maxw; + + maxw = dialog->hotKeyText->w(); + fl_font(FL_HELVETICA, FL_NORMAL_SIZE); + prevdiv = curdiv = strchr(dialog->hotKeyTextBuffer, ' '); + while (curdiv != nullptr) { + int w, h; + + *curdiv = '\0'; + w = h = 0; + fl_measure(dialog->hotKeyTextBuffer, w, h); + *curdiv = ' '; + + if (w > maxw) + *prevdiv = '\n'; + + prevdiv = curdiv; + curdiv = strchr(curdiv + 1, ' '); + } + + dialog->hotKeyText->label(dialog->hotKeyTextBuffer); +} + void OptionsDialog::handleFullScreenMode(Fl_Widget* /*widget*/, void *data) { OptionsDialog *dialog = (OptionsDialog*)data; diff --git a/vncviewer/OptionsDialog.h b/vncviewer/OptionsDialog.h index 56cf60352d..61aa959d79 100644 --- a/vncviewer/OptionsDialog.h +++ b/vncviewer/OptionsDialog.h @@ -24,9 +24,11 @@ #include class Fl_Widget; +class Fl_Box; class Fl_Group; class Fl_Check_Button; class Fl_Round_Button; +class Fl_Toggle_Button; class Fl_Input; class Fl_Int_Input; class Fl_Choice; @@ -54,6 +56,7 @@ class OptionsDialog : public Fl_Window { void createCompressionPage(int tx, int ty, int tw, int th); void createSecurityPage(int tx, int ty, int tw, int th); void createInputPage(int tx, int ty, int tw, int th); + void createHotKeysPage(int tx, int ty, int tw, int th); void createDisplayPage(int tx, int ty, int tw, int th); void createMiscPage(int tx, int ty, int tw, int th); @@ -68,6 +71,8 @@ class OptionsDialog : public Fl_Window { static void handleClipboard(Fl_Widget *widget, void *data); + static void handleHotKey(Fl_Widget *widget, void *data); + static void handleFullScreenMode(Fl_Widget *widget, void *data); static void handleCancel(Fl_Widget *widget, void *data); @@ -120,7 +125,6 @@ class OptionsDialog : public Fl_Window { Fl_Check_Button *dotCursorCheckbox; Fl_Group *keyboardGroup; Fl_Check_Button *systemKeysCheckbox; - Fl_Choice *menuKeyChoice; Fl_Group *clipboardGroup; Fl_Check_Button *acceptClipboardCheckbox; #if !defined(WIN32) && !defined(__APPLE__) @@ -131,6 +135,15 @@ class OptionsDialog : public Fl_Window { Fl_Check_Button *sendPrimaryCheckbox; #endif + /* Hot keys */ + Fl_Toggle_Button *ctrlButton; + Fl_Toggle_Button *altButton; + Fl_Toggle_Button *shiftButton; + Fl_Toggle_Button *superButton; + + Fl_Box *hotKeyText; + char hotKeyTextBuffer[1024]; + /* Display */ Fl_Group *displayModeGroup; Fl_Round_Button *windowedButton; diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 8c3b5dc549..328443f9b0 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -27,15 +27,21 @@ #include #include +#include #include #include // FLTK can pull in the X11 headers on some systems #ifndef XK_VoidSymbol +#define XK_LATIN1 #define XK_MISCELLANY #include #endif +#ifndef NoSymbol +#define NoSymbol 0 +#endif + #include "fltk/layout.h" #include "fltk/util.h" #include "Viewport.h" @@ -44,7 +50,6 @@ #include "DesktopWindow.h" #include "i18n.h" #include "parameters.h" -#include "menukey.h" #include "vncviewer.h" #include "PlatformPixelBuffer.h" @@ -75,7 +80,7 @@ static rfb::LogWriter vlog("Viewport"); // Menu constants enum { ID_DISCONNECT, ID_FULLSCREEN, ID_MINIMIZE, ID_RESIZE, - ID_CTRL, ID_ALT, ID_MENUKEY, ID_CTRLALTDEL, + ID_CTRL, ID_ALT, ID_CTRLALTDEL, ID_REFRESH, ID_OPTIONS, ID_INFO, ID_ABOUT }; // Used for fake key presses from the menu @@ -121,7 +126,7 @@ Viewport::Viewport(int w, int h, const rfb::PixelFormat& /*serverPF*/, CConn* cc // reparenting to the current window works for most cases. window()->add(contextMenu); - setMenuKey(); + hotKeyHandler.setHotKeyCombo(hotKeyCombo); OptionsDialog::addCallback(handleOptions, this); @@ -602,23 +607,82 @@ void Viewport::resetKeyboard() } keyboard->reset(); + hotKeyHandler.reset(); } void Viewport::handleKeyPress(int systemKeyCode, uint32_t keyCode, uint32_t keySym) { - static bool menuRecursion = false; - - // Prevent recursion if the menu wants to send its own - // activation key. - if (menuKeySym && (keySym == menuKeySym) && !menuRecursion) { - menuRecursion = true; - popupContextMenu(); - menuRecursion = false; + HotKeyHandler::KeyAction action; + + // Possible hot key combo? + + action = hotKeyHandler.handleKeyPress(systemKeyCode, keySym); + + if (action == HotKeyHandler::KeyIgnore) { + vlog.debug("Ignoring key press %d / 0x%04x / %s (0x%04x)", + systemKeyCode, keyCode, KeySymName(keySym), keySym); return; } + if (action == HotKeyHandler::KeyHotKey) { + std::list keySyms; + std::list::const_iterator iter; + + // Modifiers can change the KeySym that's been resolved, so we need + // to check all possible KeySyms for this physical key, not just the + // current one + keySyms = keyboard->translateToKeySyms(systemKeyCode); + + // Then we pick the one that matches first + keySym = NoSymbol; + for (iter = keySyms.begin(); iter != keySyms.end(); iter++) { + bool found; + + switch (*iter) { + case XK_M: + case XK_m: + keySym = *iter; + found = true; + break; + default: + found = false; + break; + } + + if (found) + break; + } + + vlog.debug("Detected hot key %d / 0x%04x / %s (0x%04x)", + systemKeyCode, keyCode, KeySymName(keySym), keySym); + + // The remote session won't see any more keys, so release the ones + // currently down + try { + cc->releaseAllKeys(); + } catch (std::exception& e) { + vlog.error("%s", e.what()); + abort_connection(_("An unexpected error occurred when communicating " + "with the server:\n\n%s"), e.what()); + } + + switch (keySym) { + case XK_M: + case XK_m: + popupContextMenu(); + break; + default: + // Unknown/Unused hot key combo + break; + } + + return; + } + + // Normal key, so send to server... + if (viewOnly) return; @@ -633,6 +697,24 @@ void Viewport::handleKeyPress(int systemKeyCode, void Viewport::handleKeyRelease(int systemKeyCode) { + HotKeyHandler::KeyAction action; + + // Possible hot key combo? + + action = hotKeyHandler.handleKeyRelease(systemKeyCode); + + if (action == HotKeyHandler::KeyIgnore) { + vlog.debug("Ignoring key release %d", systemKeyCode); + return; + } + + if (action == HotKeyHandler::KeyHotKey) { + vlog.debug("Hot key release %d", systemKeyCode); + return; + } + + // Normal key, so send to server... + if (viewOnly) return; @@ -697,15 +779,6 @@ void Viewport::initContextMenu() 0, nullptr, (void*)ID_ALT, FL_MENU_TOGGLE | (menuAltKey?FL_MENU_VALUE:0)); - if (menuKeySym) { - char sendMenuKey[64]; - snprintf(sendMenuKey, 64, p_("ContextMenu|", "Send %s"), (const char *)menuKey); - fltk_menu_add(contextMenu, sendMenuKey, 0, nullptr, (void*)ID_MENUKEY, 0); - fltk_menu_add(contextMenu, "Secret shortcut menu key", - menuKeyFLTK, nullptr, - (void*)ID_MENUKEY, FL_MENU_INVISIBLE); - } - fltk_menu_add(contextMenu, p_("ContextMenu|", "Send Ctrl-Alt-&Del"), 0, nullptr, (void*)ID_CTRLALTDEL, FL_MENU_DIVIDER); @@ -790,10 +863,6 @@ void Viewport::popupContextMenu() handleKeyRelease(FAKE_ALT_KEY_CODE); menuAltKey = !menuAltKey; break; - case ID_MENUKEY: - handleKeyPress(FAKE_KEY_CODE, menuKeyCode, menuKeySym); - handleKeyRelease(FAKE_KEY_CODE); - break; case ID_CTRLALTDEL: handleKeyPress(FAKE_CTRL_KEY_CODE, 0x1d, XK_Control_L); handleKeyPress(FAKE_ALT_KEY_CODE, 0x38, XK_Alt_L); @@ -822,17 +891,11 @@ void Viewport::popupContextMenu() } } - -void Viewport::setMenuKey() -{ - getMenuKey(&menuKeyFLTK, &menuKeyCode, &menuKeySym); -} - - void Viewport::handleOptions(void *data) { Viewport *self = (Viewport*)data; - self->setMenuKey(); + self->hotKeyHandler.setHotKeyCombo(hotKeyCombo); + // FIXME: Need to recheck cursor for dotWhenNoCursor } diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index 41696d9d74..371ca13311 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -26,6 +26,7 @@ #include "EmulateMB.h" #include "Keyboard.h" +#include "HotKeyHandler.h" class Fl_Menu_Button; class Fl_RGB_Image; @@ -96,8 +97,6 @@ class Viewport : public Fl_Widget, protected EmulateMB, void initContextMenu(); void popupContextMenu(); - void setMenuKey(); - static void handleOptions(void *data); private: @@ -109,6 +108,7 @@ class Viewport : public Fl_Widget, protected EmulateMB, uint16_t lastButtonMask; Keyboard* keyboard; + HotKeyHandler hotKeyHandler; bool firstLEDState; @@ -116,8 +116,6 @@ class Viewport : public Fl_Widget, protected EmulateMB, int clipboardSource; - uint32_t menuKeySym; - int menuKeyCode, menuKeyFLTK; Fl_Menu_Button *contextMenu; bool menuCtrlKey; diff --git a/vncviewer/menukey.cxx b/vncviewer/menukey.cxx deleted file mode 100644 index c12d8c9393..0000000000 --- a/vncviewer/menukey.cxx +++ /dev/null @@ -1,86 +0,0 @@ -/* Copyright 2011 Martin Koegler - * Copyright 2011 Pierre Ossman for Cendio AB - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include - -// FLTK can pull in the X11 headers on some systems -#ifndef XK_VoidSymbol -#define XK_MISCELLANY -#include -#endif - -#include "menukey.h" -#include "parameters.h" - -static const MenuKeySymbol menuSymbols[] = { - {"F1", FL_F + 1, 0x3b, XK_F1}, - {"F2", FL_F + 2, 0x3c, XK_F2}, - {"F3", FL_F + 3, 0x3d, XK_F3}, - {"F4", FL_F + 4, 0x3e, XK_F4}, - {"F5", FL_F + 5, 0x3f, XK_F5}, - {"F6", FL_F + 6, 0x40, XK_F6}, - {"F7", FL_F + 7, 0x41, XK_F7}, - {"F8", FL_F + 8, 0x42, XK_F8}, - {"F9", FL_F + 9, 0x43, XK_F9}, - {"F10", FL_F + 10, 0x44, XK_F10}, - {"F11", FL_F + 11, 0x57, XK_F11}, - {"F12", FL_F + 12, 0x58, XK_F12}, - {"Pause", FL_Pause, 0xc6, XK_Pause}, - {"Scroll_Lock", FL_Scroll_Lock, 0x46, XK_Scroll_Lock}, - {"Escape", FL_Escape, 0x01, XK_Escape}, - {"Insert", FL_Insert, 0xd2, XK_Insert}, - {"Delete", FL_Delete, 0xd3, XK_Delete}, - {"Home", FL_Home, 0xc7, XK_Home}, - {"Page_Up", FL_Page_Up, 0xc9, XK_Page_Up}, - {"Page_Down", FL_Page_Down, 0xd1, XK_Page_Down}, -}; - -int getMenuKeySymbolCount() -{ - return sizeof(menuSymbols)/sizeof(menuSymbols[0]); -} - -const MenuKeySymbol* getMenuKeySymbols() -{ - return menuSymbols; -} - -void getMenuKey(int *fltkcode, int *keycode, uint32_t *keysym) -{ - const char *menuKeyStr; - - menuKeyStr = menuKey; - for(int i = 0; i < getMenuKeySymbolCount(); i++) { - if (!strcmp(menuSymbols[i].name, menuKeyStr)) { - *fltkcode = menuSymbols[i].fltkcode; - *keycode = menuSymbols[i].keycode; - *keysym = menuSymbols[i].keysym; - return; - } - } - - *fltkcode = 0; - *keycode = 0; - *keysym = 0; -} diff --git a/vncviewer/menukey.h b/vncviewer/menukey.h deleted file mode 100644 index 50106955d0..0000000000 --- a/vncviewer/menukey.h +++ /dev/null @@ -1,34 +0,0 @@ -/* Copyright 2011 Martin Koegler - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, - * USA. - */ -#ifndef __KEYSYM_H__ -#define __KEYSYM_H__ - -#include - -typedef struct { - const char* name; - int fltkcode; - int keycode; - uint32_t keysym; -} MenuKeySymbol; - -void getMenuKey(int *fltkcode, int *keycode, uint32_t *keysym); -int getMenuKeySymbolCount(); -const MenuKeySymbol* getMenuKeySymbols(); - -#endif diff --git a/vncviewer/parameters.cxx b/vncviewer/parameters.cxx index a6229a349c..159f7a3f14 100644 --- a/vncviewer/parameters.cxx +++ b/vncviewer/parameters.cxx @@ -157,8 +157,9 @@ StringParameter display("display", ""); #endif -StringParameter menuKey("MenuKey", "The key which brings up the popup menu", - "F8"); +StringParameter hotKeyCombo("HotKeyCombo", "The key combination that " + "triggers special actions in the viewer", + "Ctrl,Alt"); BoolParameter fullscreenSystemKeys("FullscreenSystemKeys", "Pass special keys (like Alt+Tab) directly " @@ -208,8 +209,9 @@ static VoidParameter* parameterArray[] = { &sendPrimary, &setPrimary, #endif - &menuKey, - &fullscreenSystemKeys + &fullscreenSystemKeys, + /* Hot keys */ + &hotKeyCombo, }; static VoidParameter* readOnlyParameterArray[] = { diff --git a/vncviewer/parameters.h b/vncviewer/parameters.h index a25c932d95..98f842f187 100644 --- a/vncviewer/parameters.h +++ b/vncviewer/parameters.h @@ -20,6 +20,8 @@ #ifndef __PARAMETERS_H__ #define __PARAMETERS_H__ +#include + #include #include "MonitorIndicesParameter.h" @@ -70,7 +72,7 @@ extern rfb::BoolParameter sendPrimary; extern rfb::StringParameter display; #endif -extern rfb::StringParameter menuKey; +extern rfb::StringParameter hotKeyCombo; extern rfb::BoolParameter fullscreenSystemKeys; extern rfb::BoolParameter alertOnFatalError; diff --git a/vncviewer/vncviewer.man b/vncviewer/vncviewer.man index ade45b78a8..7b3928d2aa 100644 --- a/vncviewer/vncviewer.man +++ b/vncviewer/vncviewer.man @@ -77,24 +77,20 @@ safely. Automatic selection can be turned off by setting the \fBAutoSelect\fP parameter to false, or from the options dialog. -.SH POPUP MENU -The viewer has a popup menu containing entries which perform various actions. -It is usually brought up by pressing F8, but this can be configured with the -MenuKey parameter. Actions which the popup menu can perform include: -.RS 2 -.IP * 2 -switching in and out of full-screen mode -.IP * -quitting the viewer -.IP * -generating key events, e.g. sending ctrl-alt-del -.IP * -accessing the options dialog and various other dialogs -.RE -.PP -By default, key presses in the popup menu get sent to the VNC server and -dismiss the popup. So to get an F8 through to the VNC server simply press it -twice. +.SH HOT KEY COMBINATIONS + +The viewer can be controlled using certain key combinations, invoking special +actions instead of passing the keyboard events on to the remote session. By +default pressing Ctrl+Alt and something else will be interpreted as a viewer +hot key, but this can be changed witht the \fBHotKeyCombo\fP parameter. + +The possible hot key combinations are: + +.TP +Ctrl+Alt+M +Opens a popup menu that can perform various extra actions, such as quitting the +viewer or opening the options dialog. +. .SH FULL-SCREEN MODE A full-screen mode is supported. This is particularly useful when connecting @@ -299,6 +295,13 @@ Emulate middle mouse button by pressing left and right mouse buttons simultaneously. Default is off. . .TP +.B \-HotKeyCombo \fIkeys\fP +This option specifies which key combination trigger special actions in the +viewer instead of being sent to the remote session. Possible values are a +combination of \fBCtrl\fP, \fBShift\fP, \fBAlt\fP, and \fBSuper\fP. Default is +\fBCtrl,Alt\fP. +. +.TP .B \-Log \fIlogname\fP:\fIdest\fP:\fIlevel\fP Configures the debug log settings. \fIdest\fP can currently be \fBstderr\fP or \fBstdout\fP, and \fIlevel\fP is between 0 and 100, 100 meaning most verbose @@ -307,12 +310,6 @@ specific source file if you know the name of its "LogWriter". Default is \fB*:stderr:30\fP. . .TP -.B \-MenuKey \fIkeysym-name\fP -This option specifies the key which brings up the popup menu. The currently -supported list is: F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, Pause, -Scroll_Lock, Escape, Insert, Delete, Home, Page_Up, Page_Down). Default is F8. -. -.TP \fB\-via\fR \fIgateway\fR Automatically create encrypted TCP tunnel to the \fIgateway\fR machine before connection, connect to the \fIhost\fR through that tunnel From d578511ed1d1333767f33267baec60add4c56154 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 14 Dec 2021 13:57:01 +0100 Subject: [PATCH 09/21] Add hot key for full screen Allows you to quickly enter and leave full-screen mode. --- vncviewer/OptionsDialog.cxx | 6 ++++-- vncviewer/Viewport.cxx | 12 ++++++++++++ vncviewer/vncviewer.man | 4 ++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/vncviewer/OptionsDialog.cxx b/vncviewer/OptionsDialog.cxx index ffc68f5100..cc3ba00737 100644 --- a/vncviewer/OptionsDialog.cxx +++ b/vncviewer/OptionsDialog.cxx @@ -1258,8 +1258,10 @@ void OptionsDialog::handleHotKey(Fl_Widget* /*widget*/, void *data) snprintf(dialog->hotKeyTextBuffer, sizeof(dialog->hotKeyTextBuffer), - _("To open session context menu, press %sM."), - combo); + _("To toggle full-screen mode, press %sEnter.\n" + "\n" + "To open session context menu, press %sM."), + combo, combo); } /* FLTK can't reflow labels, so we'll have to do it manually */ diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 328443f9b0..a4bc8d4c50 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -643,6 +643,8 @@ void Viewport::handleKeyPress(int systemKeyCode, switch (*iter) { case XK_M: case XK_m: + case XK_KP_Enter: + case XK_Return: keySym = *iter; found = true; break; @@ -673,6 +675,16 @@ void Viewport::handleKeyPress(int systemKeyCode, case XK_m: popupContextMenu(); break; + case XK_KP_Enter: + case XK_Return: + if (window()->fullscreen_active()) { + fullScreen.setParam(false); + window()->fullscreen_off(); + } else { + fullScreen.setParam(true); + ((DesktopWindow*)window())->fullscreen_on(); + } + break; default: // Unknown/Unused hot key combo break; diff --git a/vncviewer/vncviewer.man b/vncviewer/vncviewer.man index 7b3928d2aa..fcd6e9e0c8 100644 --- a/vncviewer/vncviewer.man +++ b/vncviewer/vncviewer.man @@ -86,6 +86,10 @@ hot key, but this can be changed witht the \fBHotKeyCombo\fP parameter. The possible hot key combinations are: +.TP +Ctrl+Alt+Enter +Toggles full-screen mode. +. .TP Ctrl+Alt+M Opens a popup menu that can perform various extra actions, such as quitting the From 32687d2c61a1c8008fd24b93b849882bffa386da Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 30 Sep 2021 09:12:36 +0200 Subject: [PATCH 10/21] Add a bypass for hot key combinations No matter how carefully you choose your hot key modifiers, there might still be situations where you need to send those hot key combinations to the server instead. This commit adds a method for this by letting the +Space combination temporarily bypass the hot key logic and send everything to the server (until all keys are released again). --- vncviewer/OptionsDialog.cxx | 8 +- vncviewer/Viewport.cxx | 170 +++++++++++++++++++++--------------- vncviewer/Viewport.h | 3 + vncviewer/vncviewer.man | 5 ++ 4 files changed, 115 insertions(+), 71 deletions(-) diff --git a/vncviewer/OptionsDialog.cxx b/vncviewer/OptionsDialog.cxx index cc3ba00737..b909c4865a 100644 --- a/vncviewer/OptionsDialog.cxx +++ b/vncviewer/OptionsDialog.cxx @@ -1260,8 +1260,12 @@ void OptionsDialog::handleHotKey(Fl_Widget* /*widget*/, void *data) sizeof(dialog->hotKeyTextBuffer), _("To toggle full-screen mode, press %sEnter.\n" "\n" - "To open session context menu, press %sM."), - combo, combo); + "To open session context menu, press %sM.\n" + "\n" + "To send a key combination that includes %s directly to the session, " + "press %sSpace, release the space bar without releasing %s, and " + "press the desired key."), + combo, combo, combo_noplus, combo, combo_noplus); } /* FLTK can't reflow labels, so we'll have to do it manually */ diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index a4bc8d4c50..10ead35dc3 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -94,7 +94,7 @@ static const int FAKE_KEY_CODE = 0xffff; Viewport::Viewport(int w, int h, const rfb::PixelFormat& /*serverPF*/, CConn* cc_) : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(nullptr), lastPointerPos(0, 0), lastButtonMask(0), - keyboard(nullptr), + keyboard(nullptr), hotKeyBypass(false), hotKeyActive(false), firstLEDState(true), pendingClientClipboard(false), menuCtrlKey(false), menuAltKey(false), cursor(nullptr) { @@ -607,90 +607,112 @@ void Viewport::resetKeyboard() } keyboard->reset(); + hotKeyHandler.reset(); + hotKeyBypass = false; + hotKeyActive = false; + pressedKeys.clear(); } void Viewport::handleKeyPress(int systemKeyCode, uint32_t keyCode, uint32_t keySym) { - HotKeyHandler::KeyAction action; + pressedKeys.insert(systemKeyCode); // Possible hot key combo? - action = hotKeyHandler.handleKeyPress(systemKeyCode, keySym); + if (!hotKeyBypass) { + HotKeyHandler::KeyAction action; - if (action == HotKeyHandler::KeyIgnore) { - vlog.debug("Ignoring key press %d / 0x%04x / %s (0x%04x)", - systemKeyCode, keyCode, KeySymName(keySym), keySym); - return; - } + action = hotKeyHandler.handleKeyPress(systemKeyCode, keySym); + + if (action == HotKeyHandler::KeyIgnore) { + vlog.debug("Ignoring key press %d / 0x%04x / %s (0x%04x)", + systemKeyCode, keyCode, KeySymName(keySym), keySym); + return; + } - if (action == HotKeyHandler::KeyHotKey) { - std::list keySyms; - std::list::const_iterator iter; + if (action == HotKeyHandler::KeyHotKey) { + std::list keySyms; + std::list::const_iterator iter; + + // Modifiers can change the KeySym that's been resolved, so we + // need to check all possible KeySyms for this physical key, not + // just the current one + keySyms = keyboard->translateToKeySyms(systemKeyCode); + + // Then we pick the one that matches first + keySym = NoSymbol; + for (iter = keySyms.begin(); iter != keySyms.end(); iter++) { + bool found; + + switch (*iter) { + case XK_space: + case XK_M: + case XK_m: + case XK_KP_Enter: + case XK_Return: + keySym = *iter; + found = true; + break; + default: + found = false; + break; + } + + if (found) + break; + } - // Modifiers can change the KeySym that's been resolved, so we need - // to check all possible KeySyms for this physical key, not just the - // current one - keySyms = keyboard->translateToKeySyms(systemKeyCode); + vlog.debug("Detected hot key %d / 0x%04x / %s (0x%04x)", + systemKeyCode, keyCode, KeySymName(keySym), keySym); + + // Special case which we need to handle first + if (keySym == XK_space) { + // If another hot key has already fired, then we're too late as + // we've already released the modifier keys + if (!hotKeyActive) { + hotKeyBypass = true; + hotKeyHandler.reset(); + } + return; + } - // Then we pick the one that matches first - keySym = NoSymbol; - for (iter = keySyms.begin(); iter != keySyms.end(); iter++) { - bool found; + hotKeyActive = true; + + // The remote session won't see any more keys, so release the ones + // currently down + try { + cc->releaseAllKeys(); + } catch (std::exception& e) { + vlog.error("%s", e.what()); + abort_connection(_("An unexpected error occurred when communicating " + "with the server:\n\n%s"), e.what()); + } - switch (*iter) { + switch (keySym) { case XK_M: case XK_m: + popupContextMenu(); + break; case XK_KP_Enter: case XK_Return: - keySym = *iter; - found = true; + if (window()->fullscreen_active()) { + fullScreen.setParam(false); + window()->fullscreen_off(); + } else { + fullScreen.setParam(true); + ((DesktopWindow*)window())->fullscreen_on(); + } break; default: - found = false; + // Unknown/Unused hot key combo break; } - if (found) - break; - } - - vlog.debug("Detected hot key %d / 0x%04x / %s (0x%04x)", - systemKeyCode, keyCode, KeySymName(keySym), keySym); - - // The remote session won't see any more keys, so release the ones - // currently down - try { - cc->releaseAllKeys(); - } catch (std::exception& e) { - vlog.error("%s", e.what()); - abort_connection(_("An unexpected error occurred when communicating " - "with the server:\n\n%s"), e.what()); - } - - switch (keySym) { - case XK_M: - case XK_m: - popupContextMenu(); - break; - case XK_KP_Enter: - case XK_Return: - if (window()->fullscreen_active()) { - fullScreen.setParam(false); - window()->fullscreen_off(); - } else { - fullScreen.setParam(true); - ((DesktopWindow*)window())->fullscreen_on(); - } - break; - default: - // Unknown/Unused hot key combo - break; + return; } - - return; } // Normal key, so send to server... @@ -709,22 +731,32 @@ void Viewport::handleKeyPress(int systemKeyCode, void Viewport::handleKeyRelease(int systemKeyCode) { - HotKeyHandler::KeyAction action; + pressedKeys.erase(systemKeyCode); + + if (pressedKeys.empty()) + hotKeyActive = false; // Possible hot key combo? - action = hotKeyHandler.handleKeyRelease(systemKeyCode); + if (!hotKeyBypass) { + HotKeyHandler::KeyAction action; - if (action == HotKeyHandler::KeyIgnore) { - vlog.debug("Ignoring key release %d", systemKeyCode); - return; - } + action = hotKeyHandler.handleKeyRelease(systemKeyCode); - if (action == HotKeyHandler::KeyHotKey) { - vlog.debug("Hot key release %d", systemKeyCode); - return; + if (action == HotKeyHandler::KeyIgnore) { + vlog.debug("Ignoring key release %d", systemKeyCode); + return; + } + + if (action == HotKeyHandler::KeyHotKey) { + vlog.debug("Hot key release %d", systemKeyCode); + return; + } } + if (pressedKeys.empty()) + hotKeyBypass = false; + // Normal key, so send to server... if (viewOnly) diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index 371ca13311..ff2e69336d 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -109,6 +109,9 @@ class Viewport : public Fl_Widget, protected EmulateMB, Keyboard* keyboard; HotKeyHandler hotKeyHandler; + bool hotKeyBypass; + bool hotKeyActive; + std::set pressedKeys; bool firstLEDState; diff --git a/vncviewer/vncviewer.man b/vncviewer/vncviewer.man index fcd6e9e0c8..5844ccef1a 100644 --- a/vncviewer/vncviewer.man +++ b/vncviewer/vncviewer.man @@ -95,6 +95,11 @@ Ctrl+Alt+M Opens a popup menu that can perform various extra actions, such as quitting the viewer or opening the options dialog. . +.TP +Ctrl+Alt+Space +Temporarily bypasses the hot key combinations, allowing the same combinations +to be sent to the remote session. +. .SH FULL-SCREEN MODE A full-screen mode is supported. This is particularly useful when connecting From 1e9e94d7ca59482f644faa3391955120ce72b871 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 16 Nov 2021 08:59:09 +0100 Subject: [PATCH 11/21] Add hot key to release keyboard grab --- vncviewer/DesktopWindow.h | 5 ++++- vncviewer/OptionsDialog.cxx | 6 ++++-- vncviewer/Viewport.cxx | 14 ++++++++++++++ vncviewer/vncviewer.man | 4 ++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index ce7960ce6e..8fe8d229f6 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011 Pierre Ossman for Cendio AB + * Copyright 2011-2021 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -93,7 +93,10 @@ class DesktopWindow : public Fl_Window { void maybeGrabKeyboard(); void grabKeyboard(); +public: void ungrabKeyboard(); +private: + void grabPointer(); void ungrabPointer(); diff --git a/vncviewer/OptionsDialog.cxx b/vncviewer/OptionsDialog.cxx index b909c4865a..141bc3d147 100644 --- a/vncviewer/OptionsDialog.cxx +++ b/vncviewer/OptionsDialog.cxx @@ -1258,14 +1258,16 @@ void OptionsDialog::handleHotKey(Fl_Widget* /*widget*/, void *data) snprintf(dialog->hotKeyTextBuffer, sizeof(dialog->hotKeyTextBuffer), - _("To toggle full-screen mode, press %sEnter.\n" + _("To release control from the session, press %s.\n" + "\n" + "To toggle full-screen mode, press %sEnter.\n" "\n" "To open session context menu, press %sM.\n" "\n" "To send a key combination that includes %s directly to the session, " "press %sSpace, release the space bar without releasing %s, and " "press the desired key."), - combo, combo, combo_noplus, combo, combo_noplus); + combo_noplus, combo, combo, combo_noplus, combo, combo_noplus); } /* FLTK can't reflow labels, so we'll have to do it manually */ diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 10ead35dc3..0323249f44 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -752,6 +752,20 @@ void Viewport::handleKeyRelease(int systemKeyCode) vlog.debug("Hot key release %d", systemKeyCode); return; } + + if (action == HotKeyHandler::KeyUnarm) { + DesktopWindow *win; + + vlog.debug("Detected hot key grab release"); + + cc->releaseAllKeys(); + + win = dynamic_cast(window()); + assert(win); + win->ungrabKeyboard(); + + return; + } } if (pressedKeys.empty()) diff --git a/vncviewer/vncviewer.man b/vncviewer/vncviewer.man index 5844ccef1a..87d0833a9d 100644 --- a/vncviewer/vncviewer.man +++ b/vncviewer/vncviewer.man @@ -86,6 +86,10 @@ hot key, but this can be changed witht the \fBHotKeyCombo\fP parameter. The possible hot key combinations are: +.TP +Ctrl+Alt +Releases control of the keyboard and allows system keys to be used locally. +. .TP Ctrl+Alt+Enter Toggles full-screen mode. From 0c6d46c01b01a0f06dfb769c9203db0fd0bb7fe2 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 13 Nov 2021 16:28:43 +0100 Subject: [PATCH 12/21] Add hot key to grab keyboard Allows you to grab the keyboard input from the desktop environment even in windowed mode. --- vncviewer/DesktopWindow.cxx | 42 ++++++++++++++++--------------------- vncviewer/DesktopWindow.h | 10 ++++----- vncviewer/OptionsDialog.cxx | 7 +++++-- vncviewer/Viewport.cxx | 8 +++++++ vncviewer/vncviewer.man | 9 ++++++-- 5 files changed, 42 insertions(+), 34 deletions(-) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 1be7bbe43a..2be617a554 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011 Pierre Ossman for Cendio AB + * Copyright 2011-2021 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -828,10 +828,13 @@ int DesktopWindow::handle(int event) // Update scroll bars repositionWidgets(); - if (fullscreen_active()) - maybeGrabKeyboard(); - else - ungrabKeyboard(); + // Automatically toggle keyboard grab? + if (fullscreenSystemKeys) { + if (fullscreen_active()) + grabKeyboard(); + else + ungrabKeyboard(); + } break; @@ -915,18 +918,17 @@ int DesktopWindow::fltkDispatch(int event, Fl_Window *win) if (dw) { switch (event) { // Focus might not stay with us just because we have grabbed the - // keyboard. E.g. we might have sub windows, or we're not using - // all monitors and the user clicked on another application. - // Make sure we update our grabs with the focus changes. + // keyboard. E.g. we might have sub windows, or the user clicked on + // another application. Make sure we update our grabs with the focus + // changes. case FL_FOCUS: vlog.error("FL_FOCUS"); - dw->maybeGrabKeyboard(); + if (fullscreenSystemKeys && dw->fullscreen_active()) + dw->grabKeyboard(); break; case FL_UNFOCUS: vlog.error("FL_UNFOCUS"); - if (fullscreenSystemKeys) { - dw->ungrabKeyboard(); - } + dw->ungrabKeyboard(); break; case FL_RELEASE: @@ -1060,12 +1062,6 @@ bool DesktopWindow::hasFocus() return focus->window() == this; } -void DesktopWindow::maybeGrabKeyboard() -{ - if (fullscreenSystemKeys && fullscreen_active() && hasFocus()) - grabKeyboard(); -} - void DesktopWindow::grabKeyboard() { // Grabbing the keyboard is fairly safe as FLTK reroutes events to the @@ -1074,6 +1070,9 @@ void DesktopWindow::grabKeyboard() // FIXME: Push this stuff into FLTK. + if (!hasFocus()) + return; + #if defined(WIN32) int ret; @@ -1169,7 +1168,7 @@ void DesktopWindow::handleGrab(void *data) assert(self); - self->maybeGrabKeyboard(); + self->grabKeyboard(); } @@ -1476,11 +1475,6 @@ void DesktopWindow::handleOptions(void *data) { DesktopWindow *self = (DesktopWindow*)data; - if (fullscreenSystemKeys) - self->maybeGrabKeyboard(); - else - self->ungrabKeyboard(); - // Call fullscreen_on even if active since it handles // fullScreenMode if (fullScreen) diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index 8fe8d229f6..25bdf9adf0 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -79,6 +79,10 @@ class DesktopWindow : public Fl_Window { void fullscreen_on(); + // Grab keyboard events from desktop environment + void grabKeyboard(); + void ungrabKeyboard(); + private: static void menuOverlay(void *data); @@ -91,12 +95,6 @@ class DesktopWindow : public Fl_Window { bool hasFocus(); - void maybeGrabKeyboard(); - void grabKeyboard(); -public: - void ungrabKeyboard(); -private: - void grabPointer(); void ungrabPointer(); diff --git a/vncviewer/OptionsDialog.cxx b/vncviewer/OptionsDialog.cxx index 141bc3d147..c6462e7f5b 100644 --- a/vncviewer/OptionsDialog.cxx +++ b/vncviewer/OptionsDialog.cxx @@ -890,7 +890,7 @@ void OptionsDialog::createInputPage(int tx, int ty, int tw, int th) systemKeysCheckbox = new Fl_Check_Button(LBLRIGHT(tx, ty, CHECK_MIN_WIDTH, CHECK_HEIGHT, - _("Pass system keys directly to server (full screen)"))); + _("Always grab control in full screen"))); systemKeysCheckbox->callback(handleSystemKeys, this); ty += CHECK_HEIGHT + TIGHT_MARGIN; } @@ -1259,6 +1259,8 @@ void OptionsDialog::handleHotKey(Fl_Widget* /*widget*/, void *data) snprintf(dialog->hotKeyTextBuffer, sizeof(dialog->hotKeyTextBuffer), _("To release control from the session, press %s.\n" + "\n" + "To grab control to the session, press %sG.\n" "\n" "To toggle full-screen mode, press %sEnter.\n" "\n" @@ -1267,7 +1269,8 @@ void OptionsDialog::handleHotKey(Fl_Widget* /*widget*/, void *data) "To send a key combination that includes %s directly to the session, " "press %sSpace, release the space bar without releasing %s, and " "press the desired key."), - combo_noplus, combo, combo, combo_noplus, combo, combo_noplus); + combo_noplus, combo, combo, combo, + combo_noplus, combo, combo_noplus); } /* FLTK can't reflow labels, so we'll have to do it manually */ diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 0323249f44..2268ed3144 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -649,6 +649,8 @@ void Viewport::handleKeyPress(int systemKeyCode, switch (*iter) { case XK_space: + case XK_G: + case XK_g: case XK_M: case XK_m: case XK_KP_Enter: @@ -692,6 +694,10 @@ void Viewport::handleKeyPress(int systemKeyCode, } switch (keySym) { + case XK_G: + case XK_g: + ((DesktopWindow*)window())->grabKeyboard(); + break; case XK_M: case XK_m: popupContextMenu(); @@ -871,6 +877,8 @@ void Viewport::popupContextMenu() // FLTK also doesn't switch focus properly for menus handle(FL_UNFOCUS); + // Similarly DesktopWindow isn't notified the grab is stolen + ((DesktopWindow*)window())->ungrabKeyboard(); m = contextMenu->popup(); diff --git a/vncviewer/vncviewer.man b/vncviewer/vncviewer.man index 87d0833a9d..78b9adfda6 100644 --- a/vncviewer/vncviewer.man +++ b/vncviewer/vncviewer.man @@ -91,6 +91,11 @@ Ctrl+Alt Releases control of the keyboard and allows system keys to be used locally. . .TP +Ctrl+Alt+G +Grabs control of the keyboard and allows system keys to be sent to the remote +session. +. +.TP Ctrl+Alt+Enter Toggles full-screen mode. . @@ -238,8 +243,8 @@ The default is "1". . .TP .B \-FullscreenSystemKeys -Pass special keys (like Alt+Tab) directly to the server when in full-screen -mode. +Automatically grab control of the keyboard when entering full-screen and pass +special keys (like Alt+Tab) directly to the server. . .TP .B \-DesktopSize \fIwidth\fPx\fIheight\fP From d1f9705b2b9e2349c463af552c3984532a078498 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 19 Nov 2021 17:13:21 +0100 Subject: [PATCH 13/21] Add support for more overlays We want to show tips for more things, so rework the overlay infrastructure so it can handle an arbitrary amount of messages. --- vncviewer/DesktopWindow.cxx | 84 +++++++++++++++++++++---------------- vncviewer/DesktopWindow.h | 16 ++++--- 2 files changed, 57 insertions(+), 43 deletions(-) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 2be617a554..f78f9a7885 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -81,7 +81,7 @@ static std::set instances; DesktopWindow::DesktopWindow(int w, int h, const char *name, const rfb::PixelFormat& serverPF, CConn* cc_) - : Fl_Window(w, h), cc(cc_), offscreen(nullptr), overlay(nullptr), + : Fl_Window(w, h), cc(cc_), offscreen(nullptr), firstUpdate(true), delayedFullscreen(false), delayedDesktopSize(false), keyboardGrabbed(false), mouseGrabbed(false), @@ -231,7 +231,11 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name, } // Show hint about menu hot key - Fl::add_timeout(0.5, menuOverlay, this); + const char *combo; + + combo = HotKeyHandler::comboPrefix(hotKeyCombo); + if (combo[0] != '\0') + addOverlay(_("Press %sM to open the context menu"), combo); // By default we get a slight delay when we warp the pointer, something // we don't want or we'll get jerky movement @@ -257,14 +261,16 @@ DesktopWindow::~DesktopWindow() Fl::remove_timeout(handleFullscreenTimeout, this); Fl::remove_timeout(handleEdgeScroll, this); Fl::remove_timeout(handleStatsTimeout, this); - Fl::remove_timeout(menuOverlay, this); Fl::remove_timeout(updateOverlay, this); ungrabKeyboard(); OptionsDialog::removeCallback(handleOptions); - delete overlay; + while (!overlays.empty()) { + delete overlays.front().surface; + overlays.pop_front(); + } delete offscreen; delete statsGraph; @@ -500,9 +506,12 @@ void DesktopWindow::draw() } // Overlay (if active) - if (overlay) { + if (!overlays.empty()) { int ox, oy, ow, oh; int sx, sy, sw, sh; + struct Overlay overlay; + + overlay = overlays.front(); // Make sure it's properly seen by adjusting it relative to the // primary screen rather than the entire window @@ -540,18 +549,20 @@ void DesktopWindow::draw() sw = w(); } - ox = X = sx + (sw - overlay->width()) / 2; + ox = X = sx + (sw - overlay.surface->width()) / 2; oy = Y = sy + 50; - ow = overlay->width(); - oh = overlay->height(); + ow = overlay.surface->width(); + oh = overlay.surface->height(); fl_clip_box(ox, oy, ow, oh, ox, oy, ow, oh); if ((ow != 0) && (oh != 0)) { if (offscreen) - overlay->blend(offscreen, ox - X, oy - Y, ox, oy, ow, oh, overlayAlpha); + overlay.surface->blend(offscreen, ox - X, oy - Y, + ox, oy, ow, oh, overlay.alpha); else - overlay->blend(ox - X, oy - Y, ox, oy, ow, oh, overlayAlpha); + overlay.surface->blend(ox - X, oy - Y, + ox, oy, ow, oh, overlay.alpha); } } @@ -681,21 +692,7 @@ void DesktopWindow::resize(int x, int y, int w, int h) } } - -void DesktopWindow::menuOverlay(void* data) -{ - DesktopWindow *self; - const char *combo; - - combo = HotKeyHandler::comboPrefix(hotKeyCombo); - if (combo[0] == '\0') - return; - - self = (DesktopWindow*)data; - self->setOverlay(_("Press %sM to open the context menu"), combo); -} - -void DesktopWindow::setOverlay(const char* text, ...) +void DesktopWindow::addOverlay(const char* text, ...) { const Fl_Fontsize fontsize = 16; const int margin = 10; @@ -716,8 +713,7 @@ void DesktopWindow::setOverlay(const char* text, ...) unsigned char* a; const unsigned char* b; - delete overlay; - Fl::remove_timeout(updateOverlay, this); + struct Overlay overlay; va_start(ap, text); vsnprintf(textbuf, sizeof(textbuf), text, ap); @@ -781,39 +777,53 @@ void DesktopWindow::setOverlay(const char* text, ...) delete imageText; - overlay = new Surface(image); - overlayAlpha = 0; - gettimeofday(&overlayStart, nullptr); + overlay.surface = new Surface(image); + overlay.alpha = 0; + memset(&overlay.start, 0, sizeof(overlay.start)); + overlays.push_back(overlay); delete image; delete [] buffer; - Fl::add_timeout(1.0/60, updateOverlay, this); + if (overlays.size() == 1) + Fl::add_timeout(0.5, updateOverlay, this); } void DesktopWindow::updateOverlay(void *data) { DesktopWindow *self; + struct Overlay* overlay; unsigned elapsed; self = (DesktopWindow*)data; - elapsed = msSince(&self->overlayStart); + if (self->overlays.empty()) + return; + + overlay = &self->overlays.front(); + + if (overlay->start.tv_sec == 0) + gettimeofday(&overlay->start, nullptr); + + elapsed = msSince(&overlay->start); if (elapsed < 500) { - self->overlayAlpha = (unsigned)255 * elapsed / 500; + overlay->alpha = (unsigned)255 * elapsed / 500; Fl::add_timeout(1.0/60, updateOverlay, self); } else if (elapsed < 3500) { - self->overlayAlpha = 255; + overlay->alpha = 255; Fl::add_timeout(3.0, updateOverlay, self); } else if (elapsed < 4000) { - self->overlayAlpha = (unsigned)255 * (4000 - elapsed) / 500; + overlay->alpha = (unsigned)255 * (4000 - elapsed) / 500; Fl::add_timeout(1.0/60, updateOverlay, self); } else { - delete self->overlay; - self->overlay = nullptr; + delete overlay->surface; + self->overlays.pop_front(); + if (!self->overlays.empty()) + Fl::add_timeout(0.5, updateOverlay, self); } + // FIXME: Only damage relevant area self->damage(FL_DAMAGE_USER1); } diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index 25bdf9adf0..79869e551f 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -20,6 +20,7 @@ #ifndef __DESKTOPWINDOW_H__ #define __DESKTOPWINDOW_H__ +#include #include #include @@ -84,9 +85,7 @@ class DesktopWindow : public Fl_Window { void ungrabKeyboard(); private: - static void menuOverlay(void *data); - - void setOverlay(const char *text, ...) + void addOverlay(const char *text, ...) __attribute__((__format__ (__printf__, 2, 3))); static void updateOverlay(void *data); @@ -126,9 +125,14 @@ class DesktopWindow : public Fl_Window { Fl_Scrollbar *hscroll, *vscroll; Viewport *viewport; Surface *offscreen; - Surface *overlay; - unsigned char overlayAlpha; - struct timeval overlayStart; + + struct Overlay { + Surface *surface; + unsigned char alpha; + struct timeval start; + }; + + std::list overlays; bool firstUpdate; bool delayedFullscreen; From 9336a6918677f8112be1add34ac5074dbabffc55 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 19 Nov 2021 17:13:21 +0100 Subject: [PATCH 14/21] Add overlays for grab and full screen These modes can be non-obvious for users how to get out of, so show an overlay with how to get back to normal when these modes are enabled. --- vncviewer/DesktopWindow.cxx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index f78f9a7885..4e4efe5069 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -838,6 +838,15 @@ int DesktopWindow::handle(int event) // Update scroll bars repositionWidgets(); + // Show how to get out of full screen + if (fullscreen_active()) { + const char *combo; + + combo = HotKeyHandler::comboPrefix(hotKeyCombo); + if (combo[0] != '\0') + addOverlay(_("Press %sEnter to leave full-screen mode"), combo); + } + // Automatically toggle keyboard grab? if (fullscreenSystemKeys) { if (fullscreen_active()) @@ -1074,6 +1083,8 @@ bool DesktopWindow::hasFocus() void DesktopWindow::grabKeyboard() { + const char *combo; + // Grabbing the keyboard is fairly safe as FLTK reroutes events to the // correct widget regardless of which low level window got the system // event. @@ -1121,6 +1132,10 @@ void DesktopWindow::grabKeyboard() if (contains(Fl::belowmouse())) grabPointer(); + + combo = (char*)HotKeyHandler::comboPrefix(hotKeyCombo, true); + if (combo[0] != '\0') + addOverlay(_("Press %s to release control from the session"), combo); } From 3fbdca7dfcbb08739552e137637b462aa2a2c6a9 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 22 Dec 2021 08:42:41 +0100 Subject: [PATCH 15/21] Avoid repeating overlay tips It can get a bit annoying if you keep getting these constantly, so only show them again if enough time has passed since we last showed them. --- vncviewer/DesktopWindow.cxx | 20 ++++++++++++++++++++ vncviewer/DesktopWindow.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 4e4efe5069..8c4d57cea7 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -70,6 +71,9 @@ static int edge_scroll_size_y = 96; // default: roughly 60 fps for smooth motion #define EDGE_SCROLL_SECONDS_PER_FRAME 0.016666 +// Time before we show an overlay tip again +const time_t OVERLAY_REPEAT_TIMEOUT = 600; + using namespace rfb; static rfb::LogWriter vlog("DesktopWindow"); @@ -700,6 +704,8 @@ void DesktopWindow::addOverlay(const char* text, ...) va_list ap; char textbuf[1024]; + std::map::iterator iter; + Fl_Image_Surface *surface; Fl_RGB_Image* imageText; @@ -720,6 +726,20 @@ void DesktopWindow::addOverlay(const char* text, ...) textbuf[sizeof(textbuf)-1] = '\0'; va_end(ap); + // Purge all old entries + for (iter = overlayTimes.begin(); iter != overlayTimes.end(); ) { + if ((time(nullptr) - iter->second) >= OVERLAY_REPEAT_TIMEOUT) + overlayTimes.erase(iter++); + else + iter++; + } + + // Recently shown? + if (overlayTimes.count(textbuf) > 0) + return; + + overlayTimes[textbuf] = time(nullptr); + #if !defined(WIN32) && !defined(__APPLE__) // FLTK < 1.3.5 crashes if fl_gc is unset if (!fl_gc) diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index 79869e551f..1203cfc442 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -22,6 +22,7 @@ #include #include +#include #include @@ -133,6 +134,7 @@ class DesktopWindow : public Fl_Window { }; std::list overlays; + std::map overlayTimes; bool firstUpdate; bool delayedFullscreen; From a359f8bfc7fde30709131f9c5ebf3f6b7d04dc2a Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 14 Oct 2022 16:56:07 +0200 Subject: [PATCH 16/21] More dynamic window title cropping Let's add an ellipsis if we have to crop the title. Also reduce the maximum length to something reasonable. We want to do the cropping, not let the window manager do it, as otherwise "TigerVNC" might get cropped. --- vncviewer/DesktopWindow.cxx | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 8c4d57cea7..6f8082fd56 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -299,11 +299,35 @@ const rfb::PixelFormat &DesktopWindow::getPreferredPF() void DesktopWindow::setName(const char *name) { - char windowNameStr[256]; + char label[100]; + const char *format; + size_t name_len; + const char *ellipsis; + + // FIXME: Another copy below + format = "%*s%s - TigerVNC"; + + // Ignore the length of '%*s' since it is + // a format marker which won't take up space + name_len = sizeof(label)-1 - (strlen(format) - 2); + + if (name_len > strlen(name)) { + // Guaranteed to fit, no need to truncate + ellipsis = ""; + } else if (name_len <= strlen("...")) { + // Even an ellipsis won't fit + ellipsis = ""; + } else { + // We need to truncate, add an ellipsis + name_len -= strlen("..."); + ellipsis = "..."; + } - snprintf(windowNameStr, 256, "%.240s - TigerVNC", name); + // FIXME: Copy of literal above to avoid format-nonliteral warning + snprintf(label, sizeof(label), "%*s%s - TigerVNC", + (int)name_len, name, ellipsis); - copy_label(windowNameStr); + copy_label(label); } From 4526d4bf3d7530ffbb52862eb2370c407c5dd3e3 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 27 Dec 2021 10:38:12 +0100 Subject: [PATCH 17/21] Update window caption on grab Provide some UI feedback that the keyboard is currently grabbed and local input is prevented. --- vncviewer/CConn.cxx | 4 ++-- vncviewer/DesktopWindow.cxx | 25 ++++++++++++++++++------- vncviewer/DesktopWindow.h | 4 ++-- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/vncviewer/CConn.cxx b/vncviewer/CConn.cxx index 6e0953f42d..47cdcd41b0 100644 --- a/vncviewer/CConn.cxx +++ b/vncviewer/CConn.cxx @@ -308,7 +308,7 @@ void CConn::initDone() serverPF = server.pf(); desktop = new DesktopWindow(server.width(), server.height(), - server.name(), serverPF, this); + serverPF, this); fullColourPF = desktop->getPreferredPF(); // Force a switch to the format and encoding we'd like @@ -344,7 +344,7 @@ void CConn::setExtendedDesktopSize(unsigned reason, unsigned result, void CConn::setName(const char* name) { CConnection::setName(name); - desktop->setName(name); + desktop->updateCaption(); } // framebufferUpdateStart() is called at the beginning of an update. diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 6f8082fd56..26843aac0a 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -82,7 +82,7 @@ static rfb::LogWriter vlog("DesktopWindow"); // issue for Fl::event_dispatch. static std::set instances; -DesktopWindow::DesktopWindow(int w, int h, const char *name, +DesktopWindow::DesktopWindow(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_) : Fl_Window(w, h), cc(cc_), offscreen(nullptr), @@ -112,7 +112,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name, callback(handleClose, this); - setName(name); + updateCaption(); OptionsDialog::addCallback(handleOptions, this); @@ -297,7 +297,7 @@ const rfb::PixelFormat &DesktopWindow::getPreferredPF() } -void DesktopWindow::setName(const char *name) +void DesktopWindow::updateCaption() { char label[100]; const char *format; @@ -305,13 +305,16 @@ void DesktopWindow::setName(const char *name) const char *ellipsis; // FIXME: Another copy below - format = "%*s%s - TigerVNC"; + if (keyboardGrabbed) + format = _("%*s%s - TigerVNC (grabbed)"); + else + format = _("%*s%s - TigerVNC"); // Ignore the length of '%*s' since it is // a format marker which won't take up space name_len = sizeof(label)-1 - (strlen(format) - 2); - if (name_len > strlen(name)) { + if (name_len > strlen(cc->server.name())) { // Guaranteed to fit, no need to truncate ellipsis = ""; } else if (name_len <= strlen("...")) { @@ -324,8 +327,12 @@ void DesktopWindow::setName(const char *name) } // FIXME: Copy of literal above to avoid format-nonliteral warning - snprintf(label, sizeof(label), "%*s%s - TigerVNC", - (int)name_len, name, ellipsis); + if (keyboardGrabbed) + snprintf(label, sizeof(label), _("%*s%s - TigerVNC (grabbed)"), + (int)name_len, cc->server.name(), ellipsis); + else + snprintf(label, sizeof(label), _("%*s%s - TigerVNC"), + (int)name_len, cc->server.name(), ellipsis); copy_label(label); } @@ -1177,6 +1184,8 @@ void DesktopWindow::grabKeyboard() if (contains(Fl::belowmouse())) grabPointer(); + updateCaption(); + combo = (char*)HotKeyHandler::comboPrefix(hotKeyCombo, true); if (combo[0] != '\0') addOverlay(_("Press %s to release control from the session"), combo); @@ -1191,6 +1200,8 @@ void DesktopWindow::ungrabKeyboard() ungrabPointer(); + updateCaption(); + #if defined(WIN32) win32_disable_lowlevel_keyboard(fl_xid(this)); #elif defined(__APPLE__) diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index 1203cfc442..f2612defb4 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -41,7 +41,7 @@ class Fl_Scrollbar; class DesktopWindow : public Fl_Window { public: - DesktopWindow(int w, int h, const char *name, + DesktopWindow(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_); ~DesktopWindow(); @@ -52,7 +52,7 @@ class DesktopWindow : public Fl_Window { void updateWindow(); // Updated session title - void setName(const char *name); + void updateCaption(); // Resize the current framebuffer, but retain the contents void resizeFramebuffer(int new_w, int new_h); From b21e2486e7814066b420b655564cff2bb815ca0f Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 30 Dec 2021 16:07:17 +0100 Subject: [PATCH 18/21] Synchronously retry X11 keyboard grab Make this less complex by removing the timer and retrying things synchronously. This can freeze the UI, but we give up after half a second so it should hopefully not be noticable. The advantage is that we can directly determine if we succeeded or failed grabbing the keyboard. The previous code could in theory continue retrying forever. --- vncviewer/DesktopWindow.cxx | 36 +++++++++++++++++------------------- vncviewer/DesktopWindow.h | 2 -- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 26843aac0a..8a70da35f8 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -260,7 +261,6 @@ DesktopWindow::~DesktopWindow() // Unregister all timeouts in case they get a change tro trigger // again later when this object is already gone. - Fl::remove_timeout(handleGrab, this); Fl::remove_timeout(handleResizeTimeout, this); Fl::remove_timeout(handleFullscreenTimeout, this); Fl::remove_timeout(handleEdgeScroll, this); @@ -1168,14 +1168,24 @@ void DesktopWindow::grabKeyboard() GrabModeAsync, GrabModeAsync, CurrentTime); if (ret) { if (ret == AlreadyGrabbed) { - // It seems like we can race with the WM in some cases. - // Try again in a bit. - if (!Fl::has_timeout(handleGrab, this)) - Fl::add_timeout(0.500, handleGrab, this); - } else { + // It seems like we can race with the WM in some cases, e.g. when + // the WM holds the keyboard as part of handling Alt+Tab. + // Repeat the request a few times and see if we get it... + for (int attempt = 0; attempt < 5; attempt++) { + usleep(100000); + // Also throttle based on how busy the X server is + XSync(fl_display, False); + ret = XGrabKeyboard(fl_display, fl_xid(this), True, + GrabModeAsync, GrabModeAsync, CurrentTime); + if (ret != AlreadyGrabbed) + break; + } + } + + if (ret) { vlog.error(_("Failure grabbing keyboard")); + return; } - return; } #endif @@ -1194,8 +1204,6 @@ void DesktopWindow::grabKeyboard() void DesktopWindow::ungrabKeyboard() { - Fl::remove_timeout(handleGrab, this); - keyboardGrabbed = false; ungrabPointer(); @@ -1242,16 +1250,6 @@ void DesktopWindow::ungrabPointer() } -void DesktopWindow::handleGrab(void *data) -{ - DesktopWindow *self = (DesktopWindow*)data; - - assert(self); - - self->grabKeyboard(); -} - - #define _NET_WM_STATE_ADD 1 /* add/set property */ void DesktopWindow::maximizeWindow() { diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index f2612defb4..0152be8fe8 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -98,8 +98,6 @@ class DesktopWindow : public Fl_Window { void grabPointer(); void ungrabPointer(); - static void handleGrab(void *data); - void maximizeWindow(); void handleDesktopSize(); From 98294ff12bb6b92b34c2f323c4743b2f426158c7 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 8 Dec 2024 00:24:30 +0100 Subject: [PATCH 19/21] Show error grabbing keyboard It is now very likely that this happens as we will not have permission to do this by default on macOS. So present a message to the user so they understand why the keyboard isn't working as expected. --- vncviewer/DesktopWindow.cxx | 57 ++++++++++++++++++++++++++----------- vncviewer/DesktopWindow.h | 5 +++- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 8a70da35f8..f7db34fa1b 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -240,7 +240,7 @@ DesktopWindow::DesktopWindow(int w, int h, combo = HotKeyHandler::comboPrefix(hotKeyCombo); if (combo[0] != '\0') - addOverlay(_("Press %sM to open the context menu"), combo); + addOverlayTip(_("Press %sM to open the context menu"), combo); // By default we get a slight delay when we warp the pointer, something // we don't want or we'll get jerky movement @@ -727,14 +727,37 @@ void DesktopWindow::resize(int x, int y, int w, int h) } } -void DesktopWindow::addOverlay(const char* text, ...) +void DesktopWindow::addOverlayTip(const char* text, ...) { - const Fl_Fontsize fontsize = 16; - const int margin = 10; + va_list ap; + char textbuf[1024]; + + va_start(ap, text); + vsnprintf(textbuf, sizeof(textbuf), text, ap); + textbuf[sizeof(textbuf)-1] = '\0'; + va_end(ap); + + addOverlay(textbuf, false); +} +void DesktopWindow::addOverlayError(const char* text, ...) +{ va_list ap; char textbuf[1024]; + va_start(ap, text); + vsnprintf(textbuf, sizeof(textbuf), text, ap); + textbuf[sizeof(textbuf)-1] = '\0'; + va_end(ap); + + addOverlay(textbuf, true); +} + +void DesktopWindow::addOverlay(const char *text, bool always) +{ + const Fl_Fontsize fontsize = 16; + const int margin = 10; + std::map::iterator iter; Fl_Image_Surface *surface; @@ -752,11 +775,6 @@ void DesktopWindow::addOverlay(const char* text, ...) struct Overlay overlay; - va_start(ap, text); - vsnprintf(textbuf, sizeof(textbuf), text, ap); - textbuf[sizeof(textbuf)-1] = '\0'; - va_end(ap); - // Purge all old entries for (iter = overlayTimes.begin(); iter != overlayTimes.end(); ) { if ((time(nullptr) - iter->second) >= OVERLAY_REPEAT_TIMEOUT) @@ -765,11 +783,13 @@ void DesktopWindow::addOverlay(const char* text, ...) iter++; } - // Recently shown? - if (overlayTimes.count(textbuf) > 0) - return; + if (!always) { + // Recently shown? + if (overlayTimes.count(text) > 0) + return; - overlayTimes[textbuf] = time(nullptr); + overlayTimes[text] = time(nullptr); + } #if !defined(WIN32) && !defined(__APPLE__) // FLTK < 1.3.5 crashes if fl_gc is unset @@ -779,7 +799,7 @@ void DesktopWindow::addOverlay(const char* text, ...) fl_font(FL_HELVETICA, fontsize); w = 0; - fl_measure(textbuf, w, h); + fl_measure(text, w, h); // Margins w += margin * 2 * 2; @@ -792,7 +812,7 @@ void DesktopWindow::addOverlay(const char* text, ...) fl_font(FL_HELVETICA, fontsize); fl_color(FL_WHITE); - fl_draw(textbuf, 0, 0, w, h, FL_ALIGN_CENTER); + fl_draw(text, 0, 0, w, h, FL_ALIGN_CENTER); imageText = surface->image(); delete surface; @@ -895,7 +915,7 @@ int DesktopWindow::handle(int event) combo = HotKeyHandler::comboPrefix(hotKeyCombo); if (combo[0] != '\0') - addOverlay(_("Press %sEnter to leave full-screen mode"), combo); + addOverlayTip(_("Press %sEnter to leave full-screen mode"), combo); } // Automatically toggle keyboard grab? @@ -1151,6 +1171,7 @@ void DesktopWindow::grabKeyboard() ret = win32_enable_lowlevel_keyboard(fl_xid(this)); if (ret != 0) { vlog.error(_("Failure grabbing keyboard")); + addOverlayError(_("Failure grabbing keyboard")); return; } #elif defined(__APPLE__) @@ -1159,6 +1180,7 @@ void DesktopWindow::grabKeyboard() ret = cocoa_tap_keyboard(this); if (!ret) { vlog.error(_("Failure grabbing keyboard")); + addOverlayError(_("Failure grabbing keyboard")); return; } #else @@ -1184,6 +1206,7 @@ void DesktopWindow::grabKeyboard() if (ret) { vlog.error(_("Failure grabbing keyboard")); + addOverlayError(_("Failure grabbing keyboard")); return; } } @@ -1198,7 +1221,7 @@ void DesktopWindow::grabKeyboard() combo = (char*)HotKeyHandler::comboPrefix(hotKeyCombo, true); if (combo[0] != '\0') - addOverlay(_("Press %s to release control from the session"), combo); + addOverlayTip(_("Press %s to release control from the session"), combo); } diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index 0152be8fe8..ec4310998b 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -86,8 +86,11 @@ class DesktopWindow : public Fl_Window { void ungrabKeyboard(); private: - void addOverlay(const char *text, ...) + void addOverlayTip(const char *text, ...) __attribute__((__format__ (__printf__, 2, 3))); + void addOverlayError(const char *text, ...) + __attribute__((__format__ (__printf__, 2, 3))); + void addOverlay(const char *text, bool always); static void updateOverlay(void *data); static int fltkDispatch(int event, Fl_Window *win); From 476c1701c377c8487a5f47c9f57f262e765fbb05 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 13 Nov 2021 16:47:52 +0100 Subject: [PATCH 20/21] TEMP remove unused actions --- tests/unit/hotkeyhandler.cxx | 164 +++++++++++++++++------------------ vncviewer/HotKeyHandler.cxx | 18 ++-- vncviewer/HotKeyHandler.h | 2 - 3 files changed, 88 insertions(+), 96 deletions(-) diff --git a/tests/unit/hotkeyhandler.cxx b/tests/unit/hotkeyhandler.cxx index 3fc32ac899..c6f2b62514 100644 --- a/tests/unit/hotkeyhandler.cxx +++ b/tests/unit/hotkeyhandler.cxx @@ -67,7 +67,7 @@ static void testSingleArmed() handler.setHotKeyCombo("Ctrl"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyUnarm); printf("OK\n"); @@ -81,9 +81,9 @@ static void testSingleDualArmed() handler.setHotKeyCombo("Ctrl"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); - ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyArm); - ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyUnarm); printf("OK\n"); @@ -97,7 +97,7 @@ static void testSingleCombo() handler.setHotKeyCombo("Ctrl"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); @@ -113,7 +113,7 @@ static void testSingleRightCombo() handler.setHotKeyCombo("Ctrl"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_R), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_R), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); @@ -129,8 +129,8 @@ static void testSingleDualCombo() handler.setHotKeyCombo("Ctrl"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); - ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(3, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); @@ -147,7 +147,7 @@ static void testSingleComboReordered() handler.setHotKeyCombo("Ctrl"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); @@ -163,7 +163,7 @@ static void testSingleDualComboReordered() handler.setHotKeyCombo("Ctrl"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(3, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyIgnore); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); @@ -181,12 +181,12 @@ static void testSingleComboRepeated() handler.setHotKeyCombo("Ctrl"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); @@ -202,7 +202,7 @@ static void testSingleComboMultipleKeys() handler.setHotKeyCombo("Ctrl"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyPress(3, XK_b), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyHotKey); @@ -258,7 +258,7 @@ static void testSingleWedgeModifierArmed() handler.setHotKeyCombo("Ctrl"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(2, XK_Shift_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(3, XK_a), HotKeyHandler::KeyNormal); @@ -276,7 +276,7 @@ static void testSingleWedgeModifierFiring() handler.setHotKeyCombo("Ctrl"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(2, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyIgnore); ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); @@ -299,7 +299,7 @@ static void testSingleUnwedge() handler.handleKeyRelease(1); handler.handleKeyRelease(2); - ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(3, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); @@ -315,11 +315,11 @@ static void testMultiArmed() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); - ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyUnarm); printf("OK\n"); @@ -333,17 +333,17 @@ static void testMultiRearmed() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); - ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); - ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyUnarm); printf("OK\n"); @@ -357,9 +357,9 @@ static void testMultiFailedArm() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); printf("OK\n"); @@ -373,13 +373,13 @@ static void testMultiDualArmed() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_R), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(4, XK_Shift_L), HotKeyHandler::KeyArm); - ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_R), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(4, XK_Shift_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyUnarm); printf("OK\n"); @@ -393,9 +393,9 @@ static void testMultiCombo() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); @@ -413,9 +413,9 @@ static void testMultiRightCombo() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_R), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_R), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_R), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_R), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_R), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_R), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); @@ -433,12 +433,12 @@ static void testMultiDualCombo() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(4, XK_Alt_R), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(5, XK_Shift_L), HotKeyHandler::KeyArm); - ASSERT_EQ(handler.handleKeyPress(6, XK_Shift_R), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(4, XK_Alt_R), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(5, XK_Shift_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(6, XK_Shift_R), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(7, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(7), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(6), HotKeyHandler::KeyIgnore); @@ -459,9 +459,9 @@ static void testMultiComboReordered() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); @@ -479,9 +479,9 @@ static void testMultiDualComboReordered() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(5, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(5, XK_Shift_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(7, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyPress(2, XK_Control_R), HotKeyHandler::KeyIgnore); ASSERT_EQ(handler.handleKeyPress(4, XK_Alt_R), HotKeyHandler::KeyIgnore); @@ -505,18 +505,18 @@ static void testMultiComboRepeated() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyIgnore); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyIgnore); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyIgnore); @@ -534,9 +534,9 @@ static void testMultiComboMultipleKeys() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyPress(5, XK_b), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyHotKey); @@ -602,8 +602,8 @@ static void testMultiWedgeArming() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(1, XK_b), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(4, XK_Shift_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(1), HotKeyHandler::KeyNormal); @@ -624,8 +624,8 @@ static void testMultiWedgeModifierArming() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(4, XK_Super_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(2), HotKeyHandler::KeyNormal); @@ -642,9 +642,9 @@ static void testMultiWedgeModifierArmed() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(4, XK_Super_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyRelease(3), HotKeyHandler::KeyNormal); @@ -662,9 +662,9 @@ static void testMultiWedgeModifierFiring() handler.setHotKeyCombo("Ctrl,Shift,Alt"); - ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(1, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(2, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Shift_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(4, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyPress(5, XK_Super_L), HotKeyHandler::KeyIgnore); ASSERT_EQ(handler.handleKeyRelease(5), HotKeyHandler::KeyIgnore); @@ -693,9 +693,9 @@ static void testMultiUnwedge() handler.handleKeyRelease(3); handler.handleKeyRelease(4); - ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyArming); - ASSERT_EQ(handler.handleKeyPress(4, XK_Shift_L), HotKeyHandler::KeyArm); + ASSERT_EQ(handler.handleKeyPress(2, XK_Control_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(3, XK_Alt_L), HotKeyHandler::KeyNormal); + ASSERT_EQ(handler.handleKeyPress(4, XK_Shift_L), HotKeyHandler::KeyNormal); ASSERT_EQ(handler.handleKeyPress(5, XK_a), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(5), HotKeyHandler::KeyHotKey); ASSERT_EQ(handler.handleKeyRelease(4), HotKeyHandler::KeyIgnore); diff --git a/vncviewer/HotKeyHandler.cxx b/vncviewer/HotKeyHandler.cxx index cca2bf4cd0..fc282c87dd 100644 --- a/vncviewer/HotKeyHandler.cxx +++ b/vncviewer/HotKeyHandler.cxx @@ -63,22 +63,19 @@ HotKeyHandler::KeyAction HotKeyHandler::handleKeyPress(int keyCode, if (pressedMask == comboMask) { // All combo keys are pressed state = Armed; - return KeyArm; } if (mask && ((mask & comboMask) == mask)) { // The new key is part of the combo if (state == Idle) state = Arming; - return KeyArming; } else { // The new key was something else state = Wedged; - return KeyNormal; } - break; + return KeyNormal; case Armed: if (mask && ((mask & comboMask) == mask)) { // The new key is part of the combo - return KeyArm; + return KeyNormal; } else if (mask) { // The new key is some other modifier state = Wedged; @@ -124,18 +121,15 @@ HotKeyHandler::KeyAction HotKeyHandler::handleKeyRelease(int keyCode) switch (state) { case Arming: - if (pressedKeys.empty()) - action = KeyNormal; - else - action = KeyArming; + action = KeyNormal; break; case Armed: if (pressedKeys.empty()) action = KeyUnarm; else if (pressedMask == comboMask) - action = KeyArm; + action = KeyNormal; else { - action = KeyArming; + action = KeyNormal; state = Rearming; } break; @@ -143,7 +137,7 @@ HotKeyHandler::KeyAction HotKeyHandler::handleKeyRelease(int keyCode) if (pressedKeys.empty()) action = KeyUnarm; else - action = KeyArming; + action = KeyNormal; break; case Firing: if (firedKey) diff --git a/vncviewer/HotKeyHandler.h b/vncviewer/HotKeyHandler.h index c382ad9ff2..5f01ab94de 100644 --- a/vncviewer/HotKeyHandler.h +++ b/vncviewer/HotKeyHandler.h @@ -32,8 +32,6 @@ class HotKeyHandler { enum KeyAction { KeyNormal, - KeyArming, - KeyArm, KeyUnarm, KeyHotKey, KeyIgnore, From 4991df3cb9112071149092544c29e25b39730739 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 9 Dec 2021 13:08:07 +0100 Subject: [PATCH 21/21] TEMP: low level keyboard events debug output --- vncviewer/KeyboardMacOS.mm | 5 +++++ vncviewer/KeyboardWin32.cxx | 6 ++++++ vncviewer/KeyboardX11.cxx | 3 +++ vncviewer/Viewport.cxx | 4 ++++ 4 files changed, 18 insertions(+) diff --git a/vncviewer/KeyboardMacOS.mm b/vncviewer/KeyboardMacOS.mm index d00203635d..b2dd4b57bc 100644 --- a/vncviewer/KeyboardMacOS.mm +++ b/vncviewer/KeyboardMacOS.mm @@ -180,6 +180,8 @@ systemKeyCode); } + vlog.debug("%d / 0x%02x => 0x%04x", systemKeyCode, modifiers, keySym); + handler->handleKeyPress(systemKeyCode, keyCode, keySym); // We don't get any release events for CapsLock, so we have to @@ -217,6 +219,8 @@ if (iter != keySyms.end()) continue; + vlog.debug("%d / 0x%02x => 0x%04x", systemKeyCode, mods, ks); + keySyms.push_back(ks); } @@ -232,6 +236,7 @@ if (iter != keySyms.end()) continue; + vlog.debug("%d / 0x%02x => 0x%04x", systemKeyCode, mods, ks); keySyms.push_back(ks); } diff --git a/vncviewer/KeyboardWin32.cxx b/vncviewer/KeyboardWin32.cxx index c15629b2af..220532e770 100644 --- a/vncviewer/KeyboardWin32.cxx +++ b/vncviewer/KeyboardWin32.cxx @@ -401,6 +401,8 @@ std::list KeyboardWin32::translateToKeySyms(int systemKeyCode) if (ks != NoSymbol) keySyms.push_back(ks); + vlog.debug("%d / 0x%x => 0x%02x %s => 0x%04x", systemKeyCode, 0, vkey, extended ? "(extended)" : "", ks); + // Next just a single modifier at a time for (mods = 1; mods < 16; mods <<= 1) { std::list::const_iterator iter; @@ -421,6 +423,8 @@ std::list KeyboardWin32::translateToKeySyms(int systemKeyCode) if (ks == NoSymbol) continue; + vlog.debug("%d / 0x%x => 0x%02x %s => 0x%04x", systemKeyCode, mods, vkey, extended ? "(extended)" : "", ks); + iter = std::find(keySyms.begin(), keySyms.end(), ks); if (iter != keySyms.end()) continue; @@ -448,6 +452,8 @@ std::list KeyboardWin32::translateToKeySyms(int systemKeyCode) if (ks == NoSymbol) continue; + vlog.debug("%d / 0x%x => 0x%02x %s => 0x%04x", systemKeyCode, mods, vkey, extended ? "(extended)" : "", ks); + iter = std::find(keySyms.begin(), keySyms.end(), ks); if (iter != keySyms.end()) continue; diff --git a/vncviewer/KeyboardX11.cxx b/vncviewer/KeyboardX11.cxx index a4706014c2..8f4ad819a0 100644 --- a/vncviewer/KeyboardX11.cxx +++ b/vncviewer/KeyboardX11.cxx @@ -279,5 +279,8 @@ void KeyboardX11::translateToKeySyms(int systemKeyCode, if (iter != keySyms->end()) return; + vlog.debug("%d / %d / 0x%02x => 0x%04x", systemKeyCode, + (int)group, (int)mods, (int)ks); + keySyms->push_back(ks); } diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 2268ed3144..4a4a9df509 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -642,6 +642,10 @@ void Viewport::handleKeyPress(int systemKeyCode, // just the current one keySyms = keyboard->translateToKeySyms(systemKeyCode); + vlog.debug("Hot key %d / 0x%04x:", systemKeyCode, keyCode); + for (iter = keySyms.begin(); iter != keySyms.end(); iter++) + vlog.debug("%s (0x%04x)", KeySymName(*iter), *iter); + // Then we pick the one that matches first keySym = NoSymbol; for (iter = keySyms.begin(); iter != keySyms.end(); iter++) {