aboutsummaryrefslogtreecommitdiffstats
path: root/vncviewer/Viewport.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'vncviewer/Viewport.cxx')
-rw-r--r--vncviewer/Viewport.cxx364
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();
}