diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index ec3d61221..7a0912185 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -78,14 +78,14 @@ 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 char *name_, const rfb::PixelFormat& serverPF, CConn* cc_) : Fl_Window(w, h), cc(cc_), offscreen(nullptr), overlay(nullptr), - firstUpdate(true), + name(nullptr), firstUpdate(true), delayedFullscreen(false), sentDesktopSize(false), pendingRemoteResize(false), lastResize({0, 0}), - keyboardGrabbed(false), mouseGrabbed(false), + keyboardGrabbed(false), mouseGrabbed(false), forceGrabbed(false), statsLastUpdates(0), statsLastPixels(0), statsLastPosition(0), statsGraph(nullptr) { @@ -109,7 +109,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name, callback(handleClose, this); - setName(name); + setName(name_); OptionsDialog::addCallback(handleOptions, this); @@ -272,6 +272,8 @@ DesktopWindow::~DesktopWindow() Fl::event_dispatch(Fl::handle_); + free(name); + // FLTK automatically deletes all child widgets, so we shouldn't touch // them ourselves here } @@ -282,53 +284,48 @@ const rfb::PixelFormat &DesktopWindow::getPreferredPF() return viewport->getPreferredPF(); } - -void DesktopWindow::setName(const char *name) +void DesktopWindow::setName(const char *new_name) { - char windowNameStr[100]; - const char *labelFormat; - size_t maxNameSize; - char truncatedName[sizeof(windowNameStr)]; - - labelFormat = "%s - TigerVNC"; - - // Ignore the length of '%s' since it is - // a format marker which won't take up space - maxNameSize = sizeof(windowNameStr) - 1 - strlen(labelFormat) + 2; - - if (maxNameSize > strlen(name)) { - // Guaranteed to fit, no need to truncate - strcpy(truncatedName, name); - } else if (maxNameSize <= strlen("...")) { - // Even an ellipsis won't fit - truncatedName[0] = '\0'; - } else { - int offset; - - // We need to truncate, add an ellipsis - offset = maxNameSize - strlen("..."); - strncpy(truncatedName, name, sizeof(truncatedName)); - strcpy(truncatedName + offset, "..."); + free(name); + name = nullptr; + if (new_name) { + name = strdup(new_name); } + updateLabel(); +} + +void DesktopWindow::updateLabel() { + const char *strTitle = " - TigerVNC"; + const char *strGrabbed = " [GRAB]"; + const size_t maxNameLen = 100; + + char *label = (char*)malloc(maxNameLen + strlen(strGrabbed) + strlen(strTitle) + 1); + strcpy(label, ""); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" + if (name) { + if (strlen(name) <= maxNameLen) { + strcat(label, name); + } else { + strncat(label, name, maxNameLen - 3); + strcat(label, "..."); + } + } else { + strcat(label, "unknown"); + } - if (snprintf(windowNameStr, sizeof(windowNameStr), labelFormat, - truncatedName) >= (int)sizeof(windowNameStr)) { - // This is just to shut up the compiler, as we've already made sure - // we won't truncate anything + if (keyboardGrabbed || mouseGrabbed) { + strcat(label, strGrabbed); } -#pragma GCC diagnostic pop + strcat(label, strTitle); - copy_label(windowNameStr); -} + copy_label(label); + free(label); +} // Copy the areas of the framebuffer that have been changed (damaged) // to the displayed window. - void DesktopWindow::updateWindow() { if (firstUpdate) { @@ -841,6 +838,29 @@ void DesktopWindow::updateOverlay(void *data) self->damage(FL_DAMAGE_USER1); } +void DesktopWindow::toggleForceGrab() { + if (keyboardGrabbed && mouseGrabbed) { + ungrabPointer(); + ungrabKeyboard(); + forceGrabbed = false; + } else { + grabPointer(); + grabKeyboard(); + forceGrabbed = true; + } +} + +bool DesktopWindow::isKeyboardGrabbed() const { + return keyboardGrabbed; +} + +bool DesktopWindow::isMouseGrabbed() const { + return mouseGrabbed; +} + +bool DesktopWindow::isForceGrabbed() const { + return forceGrabbed; +} int DesktopWindow::handle(int event) { @@ -877,7 +897,9 @@ int DesktopWindow::handle(int event) // We don't get FL_LEAVE with a grabbed pointer, so check manually if ((Fl::event_x() < 0) || (Fl::event_x() >= w()) || (Fl::event_y() < 0) || (Fl::event_y() >= h())) { - ungrabPointer(); + if (!forceGrabbed) { + ungrabPointer(); + } } #if !defined(WIN32) && !defined(__APPLE__) Window root, child; @@ -888,7 +910,9 @@ int DesktopWindow::handle(int event) if (XQueryPointer(fl_display, fl_xid(this), &root, &child, &x, &y, &wx, &wy, &mask) && (root != XRootWindow(fl_display, fl_screen))) { - ungrabPointer(); + if (!forceGrabbed) { + ungrabPointer(); + } } #endif } @@ -1183,6 +1207,7 @@ void DesktopWindow::grabKeyboard() #endif keyboardGrabbed = true; + updateLabel(); if (contains(Fl::belowmouse())) grabPointer(); @@ -1193,7 +1218,9 @@ void DesktopWindow::ungrabKeyboard() { Fl::remove_timeout(handleGrab, this); + forceGrabbed = false; keyboardGrabbed = false; + updateLabel(); ungrabPointer(); @@ -1236,12 +1263,15 @@ void DesktopWindow::grabPointer() #endif mouseGrabbed = true; + updateLabel(); } void DesktopWindow::ungrabPointer() { + forceGrabbed = false; mouseGrabbed = false; + updateLabel(); #if !defined(WIN32) && !defined(__APPLE__) x11_ungrab_pointer(fl_xid(this)); diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index 97c3c00de..dbbc71000 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -39,7 +39,7 @@ class Fl_Scrollbar; class DesktopWindow : public Fl_Window { public: - DesktopWindow(int w, int h, const char *name, + DesktopWindow(int w, int h, const char *name_, const rfb::PixelFormat& serverPF, CConn* cc_); ~DesktopWindow(); @@ -82,6 +82,12 @@ class DesktopWindow : public Fl_Window { void fullscreen_on(); + void toggleForceGrab(); + + bool isKeyboardGrabbed() const; + bool isMouseGrabbed() const; + bool isForceGrabbed() const; + private: static void menuOverlay(void *data); @@ -122,6 +128,8 @@ class DesktopWindow : public Fl_Window { static void handleStatsTimeout(void *data); + void updateLabel(); + private: CConn* cc; Fl_Scrollbar *hscroll, *vscroll; @@ -131,6 +139,8 @@ class DesktopWindow : public Fl_Window { unsigned char overlayAlpha; struct timeval overlayStart; + char *name; + bool firstUpdate; bool delayedFullscreen; bool sentDesktopSize; @@ -140,6 +150,7 @@ class DesktopWindow : public Fl_Window { bool keyboardGrabbed; bool mouseGrabbed; + bool forceGrabbed; struct statsEntry { unsigned ups; diff --git a/vncviewer/Keyboard.h b/vncviewer/Keyboard.h index 81360252a..862eb79a5 100644 --- a/vncviewer/Keyboard.h +++ b/vncviewer/Keyboard.h @@ -26,7 +26,8 @@ class KeyboardHandler public: virtual void handleKeyPress(int systemKeyCode, uint32_t keyCode, uint32_t keySym) = 0; - virtual void handleKeyRelease(int systemKeyCode) = 0; + virtual void handleKeyRelease(int systemKeyCode, + uint32_t keyCode, uint32_t keySym) = 0; }; class Keyboard diff --git a/vncviewer/KeyboardMacOS.mm b/vncviewer/KeyboardMacOS.mm index e0a10dc8d..579639b5f 100644 --- a/vncviewer/KeyboardMacOS.mm +++ b/vncviewer/KeyboardMacOS.mm @@ -168,9 +168,9 @@ // We don't get any release events for CapsLock, so we have to // send the release right away. if (keySym == XK_Caps_Lock) - handler->handleKeyRelease(systemKeyCode); + handler->handleKeyRelease(systemKeyCode, keyCode, keySym); } else { - handler->handleKeyRelease(systemKeyCode); + handler->handleKeyRelease(systemKeyCode, keyCode, keySym); } return true; diff --git a/vncviewer/KeyboardWin32.cxx b/vncviewer/KeyboardWin32.cxx index 1caf48637..02b9427c8 100644 --- a/vncviewer/KeyboardWin32.cxx +++ b/vncviewer/KeyboardWin32.cxx @@ -292,7 +292,7 @@ bool KeyboardWin32::handleEvent(const void* event) case XK_Katakana: case XK_Hiragana: case XK_Romaji: - handler->handleKeyRelease(systemKeyCode); + handler->handleKeyRelease(systemKeyCode, keyCode, keySym); } // Shift key tracking, see below @@ -335,7 +335,7 @@ bool KeyboardWin32::handleEvent(const void* event) keyCode = translateSystemKeyCode(systemKeyCode); - handler->handleKeyRelease(keyCode); + handler->handleKeyRelease(keyCode, keyCode, keySym); // Windows has a rather nasty bug where it won't send key release // events for a Shift button if the other Shift is still pressed diff --git a/vncviewer/KeyboardX11.cxx b/vncviewer/KeyboardX11.cxx index 0ee1d4f65..c3a70d0e3 100644 --- a/vncviewer/KeyboardX11.cxx +++ b/vncviewer/KeyboardX11.cxx @@ -92,7 +92,7 @@ bool KeyboardX11::handleEvent(const void* event) assert(event); - if (xevent->type == KeyPress) { + if (xevent->type == KeyPress || xevent->type == KeyRelease) { int keycode; char str; KeySym keysym; @@ -105,10 +105,11 @@ bool KeyboardX11::handleEvent(const void* event) (int)xevent->xkey.keycode); } - handler->handleKeyPress(xevent->xkey.keycode, keycode, keysym); - return true; - } else if (xevent->type == KeyRelease) { - handler->handleKeyRelease(xevent->xkey.keycode); + if (xevent->type == KeyPress) { + handler->handleKeyPress(xevent->xkey.keycode, keycode, keysym); + } else if (xevent->type == KeyRelease) { + handler->handleKeyRelease(xevent->xkey.keycode, keycode, keysym); + } return true; } diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 9c6dd2020..f4eee538b 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -235,7 +235,7 @@ void Viewport::setCursor(int width, int height, const Point& hotspot, void Viewport::showCursor() { - if (viewOnly) { + if (viewOnly || ungrabbedGrabOnlyMouse()) { window()->cursor(FL_CURSOR_DEFAULT); return; } @@ -314,7 +314,7 @@ void Viewport::setLEDState(unsigned int ledState) return; } - if (viewOnly) + if (viewOnly || ungrabbedGrabOnlyKeyboard()) return; if (!hasFocus()) @@ -327,7 +327,7 @@ void Viewport::pushLEDState() { unsigned int ledState; - if (viewOnly) + if (viewOnly || ungrabbedGrabOnlyKeyboard()) return; // Server support? @@ -346,17 +346,17 @@ void Viewport::pushLEDState() if ((ledState & ledCapsLock) != (cc->server.ledState() & ledCapsLock)) { vlog.debug("Inserting fake CapsLock to get in sync with server"); handleKeyPress(FAKE_KEY_CODE, 0x3a, XK_Caps_Lock); - handleKeyRelease(FAKE_KEY_CODE); + handleKeyRelease(FAKE_KEY_CODE, 0x3a, XK_Caps_Lock); } if ((ledState & ledNumLock) != (cc->server.ledState() & ledNumLock)) { vlog.debug("Inserting fake NumLock to get in sync with server"); handleKeyPress(FAKE_KEY_CODE, 0x45, XK_Num_Lock); - handleKeyRelease(FAKE_KEY_CODE); + handleKeyRelease(FAKE_KEY_CODE, 0x45, XK_Num_Lock); } if ((ledState & ledScrollLock) != (cc->server.ledState() & ledScrollLock)) { vlog.debug("Inserting fake ScrollLock to get in sync with server"); handleKeyPress(FAKE_KEY_CODE, 0x46, XK_Scroll_Lock); - handleKeyRelease(FAKE_KEY_CODE); + handleKeyRelease(FAKE_KEY_CODE, 0x46, XK_Scroll_Lock); } } @@ -517,7 +517,7 @@ int Viewport::handle(int event) void Viewport::sendPointerEvent(const rfb::Point& pos, uint16_t buttonMask) { - if (viewOnly) + if (viewOnly || ungrabbedGrabOnlyMouse()) return; if ((pointerEventInterval == 0) || (buttonMask != lastButtonMask)) { @@ -658,6 +658,11 @@ void Viewport::handleKeyPress(int systemKeyCode, { static bool menuRecursion = false; + // Right Ctrl + if (keySym == FL_Control_R) { + return; + } + // Prevent recursion if the menu wants to send its own // activation key. if (menuKeySym && (keySym == menuKeySym) && !menuRecursion) { @@ -667,7 +672,7 @@ void Viewport::handleKeyPress(int systemKeyCode, return; } - if (viewOnly) + if (viewOnly || ungrabbedGrabOnlyKeyboard()) return; try { @@ -679,11 +684,25 @@ void Viewport::handleKeyPress(int systemKeyCode, } -void Viewport::handleKeyRelease(int systemKeyCode) +void Viewport::handleKeyRelease(int systemKeyCode, + uint32_t keyCode, uint32_t keySym) { + (void)keyCode; // unused + (void)keySym; // unused + if (viewOnly) return; + // Right Ctrl + if (keySym == FL_Control_R) { + ((DesktopWindow*)window())->toggleForceGrab(); + showCursor(); + return; + } + + if (ungrabbedGrabOnlyKeyboard()) + return; + try { cc->sendKeyRelease(systemKeyCode); } catch (std::exception& e) { @@ -809,6 +828,7 @@ void Viewport::popupContextMenu() window()->fullscreen_off(); else ((DesktopWindow*)window())->fullscreen_on(); + showCursor(); break; case ID_MINIMIZE: #ifdef __APPLE__ @@ -828,28 +848,28 @@ void Viewport::popupContextMenu() if (m->value()) handleKeyPress(FAKE_CTRL_KEY_CODE, 0x1d, XK_Control_L); else - handleKeyRelease(FAKE_CTRL_KEY_CODE); + handleKeyRelease(FAKE_CTRL_KEY_CODE, 0x1d, XK_Control_L); menuCtrlKey = !menuCtrlKey; break; case ID_ALT: if (m->value()) handleKeyPress(FAKE_ALT_KEY_CODE, 0x38, XK_Alt_L); else - handleKeyRelease(FAKE_ALT_KEY_CODE); + handleKeyRelease(FAKE_ALT_KEY_CODE, 0x38, XK_Alt_L); menuAltKey = !menuAltKey; break; case ID_MENUKEY: handleKeyPress(FAKE_KEY_CODE, menuKeyCode, menuKeySym); - handleKeyRelease(FAKE_KEY_CODE); + handleKeyRelease(FAKE_KEY_CODE, menuKeyCode, menuKeySym); break; case ID_CTRLALTDEL: handleKeyPress(FAKE_CTRL_KEY_CODE, 0x1d, XK_Control_L); handleKeyPress(FAKE_ALT_KEY_CODE, 0x38, XK_Alt_L); handleKeyPress(FAKE_DEL_KEY_CODE, 0xd3, XK_Delete); - handleKeyRelease(FAKE_DEL_KEY_CODE); - handleKeyRelease(FAKE_ALT_KEY_CODE); - handleKeyRelease(FAKE_CTRL_KEY_CODE); + handleKeyRelease(FAKE_DEL_KEY_CODE, 0xd3, XK_Delete); + handleKeyRelease(FAKE_ALT_KEY_CODE, 0x38, XK_Alt_L); + handleKeyRelease(FAKE_CTRL_KEY_CODE, 0x1d, XK_Control_L); break; case ID_REFRESH: cc->refreshFramebuffer(); @@ -885,3 +905,17 @@ void Viewport::handleOptions(void *data) if (Fl::belowmouse() == self) self->showCursor(); } + +bool Viewport::ungrabbedGrabOnlyKeyboard() const { + if (grabOnly || grabOnlyKeyboard) { + return !((DesktopWindow*)window())->isKeyboardGrabbed(); + } + return false; +} + +bool Viewport::ungrabbedGrabOnlyMouse() const { + if (grabOnly || grabOnlyMouse) { + return !((DesktopWindow*)window())->isMouseGrabbed(); + } + return false; +} diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index b2ae0e627..35de7e428 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -90,7 +90,8 @@ class Viewport : public Fl_Widget, protected EmulateMB, void handleKeyPress(int systemKeyCode, uint32_t keyCode, uint32_t keySym) override; - void handleKeyRelease(int systemKeyCode) override; + void handleKeyRelease(int systemKeyCode, + uint32_t keyCode, uint32_t keySym) override; static int handleSystemEvent(void *event, void *data); @@ -103,6 +104,9 @@ class Viewport : public Fl_Widget, protected EmulateMB, static void handleOptions(void *data); + bool ungrabbedGrabOnlyKeyboard() const; + bool ungrabbedGrabOnlyMouse() const; + private: CConn* cc; diff --git a/vncviewer/parameters.cxx b/vncviewer/parameters.cxx index f0ca5353a..e9097fe16 100644 --- a/vncviewer/parameters.cxx +++ b/vncviewer/parameters.cxx @@ -141,6 +141,15 @@ BoolParameter remoteResize("RemoteResize", BoolParameter viewOnly("ViewOnly", "Don't send any mouse or keyboard events to the server", false); +BoolParameter grabOnlyKeyboard("GrabOnlyKeyboard", + "Send keyboard events to the server only when keyboard grab is active.", + false); +BoolParameter grabOnlyMouse("GrabOnlyMouse", + "Send mouse events to the server only when mouse grab is active.", + false); +BoolParameter grabOnly("GrabOnly", + "Activates both GrabOnlyKeyboard and GrabOnlyMouse.", + false); BoolParameter shared("Shared", "Don't disconnect other viewers upon connection - " "share the desktop instead", @@ -207,6 +216,9 @@ static VoidParameter* parameterArray[] = { &fullScreenSelectedMonitors, /* Input */ &viewOnly, + &grabOnlyKeyboard, + &grabOnlyMouse, + &grabOnly, &emulateMiddleButton, &alwaysCursor, &cursorType, diff --git a/vncviewer/parameters.h b/vncviewer/parameters.h index f9ee28fa6..2b2cd9dfe 100644 --- a/vncviewer/parameters.h +++ b/vncviewer/parameters.h @@ -62,6 +62,9 @@ extern rfb::BoolParameter remoteResize; extern rfb::BoolParameter listenMode; extern rfb::BoolParameter viewOnly; +extern rfb::BoolParameter grabOnlyKeyboard; +extern rfb::BoolParameter grabOnlyMouse; +extern rfb::BoolParameter grabOnly; extern rfb::BoolParameter shared; extern rfb::BoolParameter acceptClipboard;