From ec75b806a6d7e10d8da3aa2626add096b6254f29 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 23 Dec 2021 13:28:35 +0100 Subject: [PATCH 1/5] 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 584debd1e..613f86ec8 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 64acefbfb..06a1fedd4 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 1d63b7502..ebdca8d25 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 2/5] 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 613f86ec8..9b66d1ca5 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 06a1fedd4..146bdf5f7 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 ebdca8d25..daba054a4 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 3/5] 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 c5f21b248..101a15100 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 f6ca89b1b..56cf60352 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 146bdf5f7..b2cae4213 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 daba054a4..fda23821f 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 4efe6e931..dfbf2c82e 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 4/5] 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 9b66d1ca5..10ab80712 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 fda23821f..76740c6a8 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 5/5] 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 f8e804297..6e0953f42 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 10ab80712..204213172 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 b2cae4213..b6fd1a6db 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 76740c6a8..1ade4cc9d 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 dfbf2c82e..7065e113e 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);