diff options
Diffstat (limited to 'vncviewer/Viewport.cxx')
-rw-r--r-- | vncviewer/Viewport.cxx | 364 |
1 files changed, 288 insertions, 76 deletions
diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 8c3b5dc5..03e6fb09 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2011-2021 Pierre Ossman for Cendio AB + * Copyright 2011-2025 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 @@ -25,17 +25,27 @@ #include <stdio.h> #include <string.h> +#include <stdexcept> + +#include <core/LogWriter.h> +#include <core/string.h> + #include <rfb/CMsgWriter.h> -#include <rfb/LogWriter.h> +#include <rfb/Cursor.h> +#include <rfb/KeysymStr.h> #include <rfb/ledStates.h> -#include <rfb/util.h> // FLTK can pull in the X11 headers on some systems #ifndef XK_VoidSymbol +#define XK_LATIN1 #define XK_MISCELLANY #include <rfb/keysymdef.h> #endif +#ifndef NoSymbol +#define NoSymbol 0 +#endif + #include "fltk/layout.h" #include "fltk/util.h" #include "Viewport.h" @@ -44,7 +54,6 @@ #include "DesktopWindow.h" #include "i18n.h" #include "parameters.h" -#include "menukey.h" #include "vncviewer.h" #include "PlatformPixelBuffer.h" @@ -68,14 +77,12 @@ #include "cocoa.h" #endif -using namespace rfb; - -static rfb::LogWriter vlog("Viewport"); +static core::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 @@ -86,12 +93,13 @@ static const int FAKE_DEL_KEY_CODE = 0x10003; // Used for fake key presses for lock key sync static const int FAKE_KEY_CODE = 0xffff; -Viewport::Viewport(int w, int h, const rfb::PixelFormat& /*serverPF*/, CConn* cc_) +Viewport::Viewport(int w, int h, CConn* cc_) : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(nullptr), lastPointerPos(0, 0), lastButtonMask(0), - keyboard(nullptr), + keyboard(nullptr), shortcutBypass(false), shortcutActive(false), firstLEDState(true), pendingClientClipboard(false), - menuCtrlKey(false), menuAltKey(false), cursor(nullptr) + menuCtrlKey(false), menuAltKey(false), cursor(nullptr), + cursorIsBlank(false) { #if defined(WIN32) keyboard = new KeyboardWin32(this); @@ -106,6 +114,12 @@ Viewport::Viewport(int w, int h, const rfb::PixelFormat& /*serverPF*/, CConn* cc // We need to intercept keyboard events early Fl::add_system_handler(handleSystemEvent, this); + // FIXME: We should only disable this whilst we have keyboard focus, + // but we also need to keep it disabled when we lose focus to + // any layout selector so it can properly filter out the + // layouts we don't support + Fl::disable_im(); + frameBuffer = new PlatformPixelBuffer(w, h); assert(frameBuffer); cc->setFramebuffer(frameBuffer); @@ -121,12 +135,16 @@ 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(); + unsigned modifierMask = 0; + for (core::EnumListEntry key : shortcutModifiers) + modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str()); + + shortcutHandler.setModifiers(modifierMask); OptionsDialog::addCallback(handleOptions, this); // Make sure we have an initial blank cursor set - setCursor(0, 0, rfb::Point(0, 0), nullptr); + setCursor(); } @@ -149,6 +167,7 @@ Viewport::~Viewport() } delete keyboard; + Fl::enable_im(); // FLTK automatically deletes all child widgets, so we shouldn't touch // them ourselves here @@ -166,7 +185,7 @@ const rfb::PixelFormat &Viewport::getPreferredPF() void Viewport::updateWindow() { - Rect r; + core::Rect r; r = frameBuffer->getDamage(); damage(FL_DAMAGE_USER1, r.tl.x + x(), r.tl.y + y(), r.width(), r.height()); @@ -182,9 +201,12 @@ static const char * dotcursor_xpm[] = { " ... ", " "}; -void Viewport::setCursor(int width, int height, const Point& hotspot, - const uint8_t* data) +void Viewport::setCursor() { + int width, height; + core::Point hotspot; + const uint8_t* data; + int i; if (cursor) { @@ -193,10 +215,20 @@ void Viewport::setCursor(int width, int height, const Point& hotspot, delete cursor; } + width = cc->server.cursor().width(); + height = cc->server.cursor().height(); + hotspot = cc->server.cursor().hotspot(); + data = cc->server.cursor().getBuffer(); + for (i = 0; i < width*height; i++) if (data[i*4 + 3] != 0) break; - if ((i == width*height) && dotWhenNoCursor) { + cursorIsBlank = i == width*height; + + if (cursorIsBlank && alwaysCursor) { + // This is the default in case the local cursor should be displayed yet cursorType is invalid. + // Since the cursor variable isn't used if the cursorType is system, we can do this without checking the current + // type which helps handle changing the type while the viewer is running. vlog.debug("Cursor is empty, using dot"); Fl_Pixmap pxm(dotcursor_xpm); @@ -217,16 +249,36 @@ void Viewport::setCursor(int width, int height, const Point& hotspot, } if (Fl::belowmouse() == this) + showCursor(); +} + +void Viewport::showCursor() +{ + if (viewOnly) { + window()->cursor(FL_CURSOR_DEFAULT); + return; + } + + if (cursorIsBlank && alwaysCursor && (cursorType == "system")) { + window()->cursor(FL_CURSOR_DEFAULT); + } else { window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y); + } } void Viewport::handleClipboardRequest() { + if (viewOnly) + return; + Fl::paste(*this, clipboardSource); } void Viewport::handleClipboardAnnounce(bool available) { + if (viewOnly) + return; + if (!acceptClipboard) return; @@ -281,6 +333,9 @@ void Viewport::setLEDState(unsigned int ledState) return; } + if (viewOnly) + return; + if (!hasFocus()) return; @@ -291,30 +346,36 @@ void Viewport::pushLEDState() { unsigned int ledState; + if (viewOnly) + return; + // Server support? - if (cc->server.ledState() == ledUnknown) + if (cc->server.ledState() == rfb::ledUnknown) return; ledState = keyboard->getLEDState(); - if (ledState == ledUnknown) + if (ledState == rfb::ledUnknown) return; #if defined(__APPLE__) // No support for Scroll Lock // - ledState |= (cc->server.ledState() & ledScrollLock); + ledState |= (cc->server.ledState() & rfb::ledScrollLock); #endif - if ((ledState & ledCapsLock) != (cc->server.ledState() & ledCapsLock)) { + if ((ledState & rfb::ledCapsLock) != + (cc->server.ledState() & rfb::ledCapsLock)) { vlog.debug("Inserting fake CapsLock to get in sync with server"); handleKeyPress(FAKE_KEY_CODE, 0x3a, XK_Caps_Lock); handleKeyRelease(FAKE_KEY_CODE); } - if ((ledState & ledNumLock) != (cc->server.ledState() & ledNumLock)) { + if ((ledState & rfb::ledNumLock) != + (cc->server.ledState() & rfb::ledNumLock)) { vlog.debug("Inserting fake NumLock to get in sync with server"); handleKeyPress(FAKE_KEY_CODE, 0x45, XK_Num_Lock); handleKeyRelease(FAKE_KEY_CODE); } - if ((ledState & ledScrollLock) != (cc->server.ledState() & ledScrollLock)) { + if ((ledState & rfb::ledScrollLock) != + (cc->server.ledState() & rfb::ledScrollLock)) { vlog.debug("Inserting fake ScrollLock to get in sync with server"); handleKeyPress(FAKE_KEY_CODE, 0x46, XK_Scroll_Lock); handleKeyRelease(FAKE_KEY_CODE); @@ -370,12 +431,20 @@ int Viewport::handle(int event) switch (event) { case FL_PASTE: - if (!isValidUTF8(Fl::event_text(), Fl::event_length())) { + if (!core::isValidUTF8(Fl::event_text(), Fl::event_length())) { vlog.error("Invalid UTF-8 sequence in system clipboard"); + // Reset the state as if we don't have any clipboard data at all + this->pendingClientClipboard = false; + try { + this->cc->announceClipboard(false); + } catch (std::exception& e) { + vlog.error("%s", e.what()); + abort_connection_with_unexpected_error(e); + } return 1; } - filtered = convertLF(Fl::event_text(), Fl::event_length()); + filtered = core::convertLF(Fl::event_text(), Fl::event_length()); vlog.debug("Sending clipboard data (%d bytes)", (int)filtered.size()); @@ -389,14 +458,14 @@ int Viewport::handle(int event) return 1; case FL_ENTER: - window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y); + showCursor(); // Yes, we would like some pointer events please! return 1; case FL_LEAVE: window()->cursor(FL_CURSOR_DEFAULT); // We want a last move event to help trigger edge stuff - handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), 0); + handlePointerEvent({Fl::event_x() - x(), Fl::event_y() - y()}, 0); return 1; case FL_PUSH: @@ -439,16 +508,14 @@ int Viewport::handle(int event) // A quick press of the wheel "button", followed by a immediate // release below - handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), + handlePointerEvent({Fl::event_x() - x(), Fl::event_y() - y()}, buttonMask | wheelMask); } - handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), buttonMask); + handlePointerEvent({Fl::event_x() - x(), Fl::event_y() - y()}, buttonMask); return 1; case FL_FOCUS: - Fl::disable_im(); - flushPendingClipboard(); // We may have gotten our lock keys out of sync with the server @@ -467,8 +534,6 @@ int Viewport::handle(int event) case FL_UNFOCUS: // We won't get more key events, so reset our knowledge about keys resetKeyboard(); - - Fl::enable_im(); return 1; case FL_KEYDOWN: @@ -480,7 +545,8 @@ int Viewport::handle(int event) return Fl_Widget::handle(event); } -void Viewport::sendPointerEvent(const rfb::Point& pos, uint16_t buttonMask) +void Viewport::sendPointerEvent(const core::Point& pos, + uint16_t buttonMask) { if (viewOnly) return; @@ -518,6 +584,9 @@ void Viewport::handleClipboardChange(int source, void *data) assert(self); + if (viewOnly) + return; + if (!sendClipboard) return; @@ -530,7 +599,12 @@ void Viewport::handleClipboardChange(int source, void *data) vlog.debug("Got non-plain text in local clipboard, ignoring."); // Reset the state as if we don't have any clipboard data at all self->pendingClientClipboard = false; - self->cc->announceClipboard(false); + try { + self->cc->announceClipboard(false); + } catch (std::exception& e) { + vlog.error("%s", e.what()); + abort_connection_with_unexpected_error(e); + } return; } @@ -540,7 +614,12 @@ void Viewport::handleClipboardChange(int source, void *data) vlog.debug("Local clipboard changed whilst not focused, will notify server later"); self->pendingClientClipboard = true; // Clear any older client clipboard from the server - self->cc->announceClipboard(false); + try { + self->cc->announceClipboard(false); + } catch (std::exception& e) { + vlog.error("%s", e.what()); + abort_connection_with_unexpected_error(e); + } return; } @@ -570,7 +649,8 @@ void Viewport::flushPendingClipboard() } -void Viewport::handlePointerEvent(const rfb::Point& pos, uint16_t buttonMask) +void Viewport::handlePointerEvent(const core::Point& pos, + uint16_t buttonMask) { filterPointerEvent(pos, buttonMask); } @@ -602,23 +682,122 @@ void Viewport::resetKeyboard() } keyboard->reset(); + + shortcutHandler.reset(); + shortcutBypass = false; + shortcutActive = false; + pressedKeys.clear(); } 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; - return; + pressedKeys.insert(systemKeyCode); + + // Possible keyboard shortcut? + + if (!shortcutBypass) { + ShortcutHandler::KeyAction action; + + action = shortcutHandler.handleKeyPress(systemKeyCode, keySym); + + if (action == ShortcutHandler::KeyIgnore) { + vlog.debug("Ignoring key press %d => 0x%02x / XK_%s (0x%04x)", + systemKeyCode, keyCode, KeySymName(keySym), keySym); + return; + } + + if (action == ShortcutHandler::KeyShortcut) { + std::list<uint32_t> keySyms; + std::list<uint32_t>::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_G: + case XK_g: + 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; + } + + vlog.debug("Detected shortcut %d => 0x%02x / XK_%s (0x%04x)", + systemKeyCode, keyCode, KeySymName(keySym), keySym); + + // Special case which we need to handle first + if (keySym == XK_space) { + // If another shortcut has already fired, then we're too late as + // we've already released the modifier keys + if (!shortcutActive) { + shortcutBypass = true; + shortcutHandler.reset(); + } + return; + } + + shortcutActive = 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 (keySym) { + case XK_G: + case XK_g: + ((DesktopWindow*)window())->grabKeyboard(); + break; + 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 keyboard shortcut + break; + } + + return; + } } + // Normal key, so send to server... + if (viewOnly) return; @@ -633,6 +812,54 @@ void Viewport::handleKeyPress(int systemKeyCode, void Viewport::handleKeyRelease(int systemKeyCode) { + pressedKeys.erase(systemKeyCode); + + if (pressedKeys.empty()) + shortcutActive = false; + + // Possible keyboard shortcut? + + if (!shortcutBypass) { + ShortcutHandler::KeyAction action; + + action = shortcutHandler.handleKeyRelease(systemKeyCode); + + if (action == ShortcutHandler::KeyIgnore) { + vlog.debug("Ignoring key release %d", systemKeyCode); + return; + } + + if (action == ShortcutHandler::KeyShortcut) { + vlog.debug("Shortcut release %d", systemKeyCode); + return; + } + + if (action == ShortcutHandler::KeyUnarm) { + DesktopWindow *win; + + vlog.debug("Detected shortcut to release grab"); + + 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()); + } + + win = dynamic_cast<DesktopWindow*>(window()); + assert(win); + win->ungrabKeyboard(); + + return; + } + } + + if (pressedKeys.empty()) + shortcutBypass = false; + + // Normal key, so send to server... + if (viewOnly) return; @@ -655,13 +882,11 @@ int Viewport::handleSystemEvent(void *event, void *data) if (!self->hasFocus()) return 0; -#ifdef __APPLE__ // Special event that means we temporarily lost some input - if (KeyboardMacOS::isKeyboardSync(event)) { + if (self->keyboard->isKeyboardReset(event)) { self->resetKeyboard(); return 1; } -#endif consumed = self->keyboard->handleEvent(event); if (consumed) @@ -697,15 +922,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); @@ -716,7 +932,7 @@ void Viewport::initContextMenu() 0, nullptr, (void*)ID_OPTIONS, 0); fltk_menu_add(contextMenu, p_("ContextMenu|", "Connection &info..."), 0, nullptr, (void*)ID_INFO, 0); - fltk_menu_add(contextMenu, p_("ContextMenu|", "About &TigerVNC viewer..."), + fltk_menu_add(contextMenu, p_("ContextMenu|", "About &TigerVNC..."), 0, nullptr, (void*)ID_ABOUT, 0); } #pragma GCC diagnostic pop @@ -739,15 +955,15 @@ void Viewport::popupContextMenu() window()->cursor(FL_CURSOR_DEFAULT); // FLTK also doesn't switch focus properly for menus - handle(FL_UNFOCUS); + Fl::handle(FL_UNFOCUS, window()); m = contextMenu->popup(); - handle(FL_FOCUS); + Fl::handle(FL_FOCUS, window()); // Back to our proper mouse pointer. - if (Fl::belowmouse()) - window()->cursor(cursor, cursorHotspot.x, cursorHotspot.y); + if (Fl::belowmouse() == this) + showCursor(); if (m == nullptr) return; @@ -790,10 +1006,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 +1034,17 @@ void Viewport::popupContextMenu() } } - -void Viewport::setMenuKey() -{ - getMenuKey(&menuKeyFLTK, &menuKeyCode, &menuKeySym); -} - - void Viewport::handleOptions(void *data) { Viewport *self = (Viewport*)data; + unsigned modifierMask; + + modifierMask = 0; + for (core::EnumListEntry key : shortcutModifiers) + modifierMask |= ShortcutHandler::parseModifier(key.getValueStr().c_str()); + + self->shortcutHandler.setModifiers(modifierMask); - self->setMenuKey(); - // FIXME: Need to recheck cursor for dotWhenNoCursor + if (Fl::belowmouse() == self) + self->showCursor(); } |