From 152e92bcab48d8d85ecb7a69775a3cebd8ab5fa2 Mon Sep 17 00:00:00 2001 From: Adam Halim Date: Fri, 4 Oct 2024 10:09:36 +0200 Subject: [PATCH] vncviewer: support for back/forward mouse buttons This commit implements the pseudo-encoding ExtendedMouseButtons which makes it possible to use the back/forward mouse buttons. This commit contains work originally done by PixelSmith . --- common/rfb/CConnection.cxx | 1 + common/rfb/CMsgHandler.cxx | 5 +++++ common/rfb/CMsgHandler.h | 1 + common/rfb/CMsgReader.cxx | 4 ++++ common/rfb/CMsgWriter.cxx | 38 +++++++++++++++++++++++++++++++++---- common/rfb/CMsgWriter.h | 2 +- common/rfb/ServerParams.cxx | 2 +- common/rfb/ServerParams.h | 1 + tests/unit/emulatemb.cxx | 6 +++--- unix/x0vncserver/XDesktop.h | 2 +- vncviewer/EmulateMB.cxx | 8 ++++---- vncviewer/EmulateMB.h | 12 ++++++------ vncviewer/Viewport.cxx | 20 +++++++++++++++++-- vncviewer/Viewport.h | 6 +++--- win/rfb_win32/SInput.h | 2 +- 15 files changed, 84 insertions(+), 26 deletions(-) diff --git a/common/rfb/CConnection.cxx b/common/rfb/CConnection.cxx index b4017dba8e..a6763c055a 100644 --- a/common/rfb/CConnection.cxx +++ b/common/rfb/CConnection.cxx @@ -835,6 +835,7 @@ void CConnection::updateEncodings() encodings.push_back(pseudoEncodingContinuousUpdates); encodings.push_back(pseudoEncodingFence); encodings.push_back(pseudoEncodingQEMUKeyEvent); + encodings.push_back(pseudoEncodingExtendedMouseButtons); if (Decoder::supported(preferredEncoding)) { encodings.push_back(preferredEncoding); diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx index 4489dbd4e4..0f3f6cd56b 100644 --- a/common/rfb/CMsgHandler.cxx +++ b/common/rfb/CMsgHandler.cxx @@ -75,6 +75,11 @@ void CMsgHandler::endOfContinuousUpdates() server.supportsContinuousUpdates = true; } +void CMsgHandler::supportsExtendedMouseButtons() +{ + server.supportsExtendedMouseButtons = true; +} + void CMsgHandler::supportsQEMUKeyEvent() { server.supportsQEMUKeyEvent = true; diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h index 9e5f7de21c..b484b69526 100644 --- a/common/rfb/CMsgHandler.h +++ b/common/rfb/CMsgHandler.h @@ -57,6 +57,7 @@ namespace rfb { virtual void fence(uint32_t flags, unsigned len, const uint8_t data[]); virtual void endOfContinuousUpdates(); virtual void supportsQEMUKeyEvent(); + virtual void supportsExtendedMouseButtons(); virtual void serverInit(int width, int height, const PixelFormat& pf, const char* name) = 0; diff --git a/common/rfb/CMsgReader.cxx b/common/rfb/CMsgReader.cxx index 8bcdbfd04e..d7cbc2fd75 100644 --- a/common/rfb/CMsgReader.cxx +++ b/common/rfb/CMsgReader.cxx @@ -202,6 +202,10 @@ bool CMsgReader::readMsg() handler->supportsQEMUKeyEvent(); ret = true; break; + case pseudoEncodingExtendedMouseButtons: + handler->supportsExtendedMouseButtons(); + ret = true; + break; default: ret = readRect(dataRect, rectEncoding); break; diff --git a/common/rfb/CMsgWriter.cxx b/common/rfb/CMsgWriter.cxx index 1bd8040f7a..8c379d660b 100644 --- a/common/rfb/CMsgWriter.cxx +++ b/common/rfb/CMsgWriter.cxx @@ -22,6 +22,7 @@ #endif #include +#include #include #include @@ -173,18 +174,47 @@ void CMsgWriter::writeKeyEvent(uint32_t keysym, uint32_t keycode, bool down) } -void CMsgWriter::writePointerEvent(const Point& pos, uint8_t buttonMask) +void CMsgWriter::writePointerEvent(const Point& pos, uint16_t buttonMask) { Point p(pos); + bool extendedMouseButtons; + if (p.x < 0) p.x = 0; if (p.y < 0) p.y = 0; if (p.x >= server->width()) p.x = server->width() - 1; if (p.y >= server->height()) p.y = server->height() - 1; + /* The highest bit in buttonMask is never sent to the server */ + assert(!(buttonMask & 0x8000)); + + /* Only send extended pointerEvent message when needed */ + extendedMouseButtons = buttonMask & 0x7f80; + startMsg(msgTypePointerEvent); - os->writeU8(buttonMask); - os->writeU16(p.x); - os->writeU16(p.y); + if (server->supportsExtendedMouseButtons && extendedMouseButtons) { + int higherBits; + int lowerBits; + + higherBits = (buttonMask >> 7) & 0xff; + assert(!(higherBits & 0xfc)); /* Bits 2-7 are reserved */ + + lowerBits = buttonMask & 0x7f; + lowerBits |= 0x80; /* Set marker bit to 1 */ + + os->writeU8(lowerBits); + os->writeU16(p.x); + os->writeU16(p.y); + os->writeU8(higherBits); + } else { + /* Marker bit must be set to 0, otherwise the server might confuse + * the marker bit with the highest bit in a normal PointerEvent + * message. + */ + buttonMask &= 0x7f; + os->writeU8(buttonMask); + os->writeU16(p.x); + os->writeU16(p.y); + } endMsg(); } diff --git a/common/rfb/CMsgWriter.h b/common/rfb/CMsgWriter.h index 61df567f1b..9cb4adec62 100644 --- a/common/rfb/CMsgWriter.h +++ b/common/rfb/CMsgWriter.h @@ -54,7 +54,7 @@ namespace rfb { void writeFence(uint32_t flags, unsigned len, const uint8_t data[]); void writeKeyEvent(uint32_t keysym, uint32_t keycode, bool down); - void writePointerEvent(const Point& pos, uint8_t buttonMask); + void writePointerEvent(const Point& pos, uint16_t buttonMask); void writeClientCutText(const char* str); diff --git a/common/rfb/ServerParams.cxx b/common/rfb/ServerParams.cxx index 9f6f530764..7c5960361a 100644 --- a/common/rfb/ServerParams.cxx +++ b/common/rfb/ServerParams.cxx @@ -32,7 +32,7 @@ ServerParams::ServerParams() : majorVersion(0), minorVersion(0), supportsQEMUKeyEvent(false), supportsSetDesktopSize(false), supportsFence(false), - supportsContinuousUpdates(false), + supportsContinuousUpdates(false), supportsExtendedMouseButtons(false), width_(0), height_(0), ledState_(ledUnknown) { diff --git a/common/rfb/ServerParams.h b/common/rfb/ServerParams.h index 791e3e7f33..d730b89139 100644 --- a/common/rfb/ServerParams.h +++ b/common/rfb/ServerParams.h @@ -79,6 +79,7 @@ namespace rfb { bool supportsSetDesktopSize; bool supportsFence; bool supportsContinuousUpdates; + bool supportsExtendedMouseButtons; private: diff --git a/tests/unit/emulatemb.cxx b/tests/unit/emulatemb.cxx index ae022c066c..6db8ea380a 100644 --- a/tests/unit/emulatemb.cxx +++ b/tests/unit/emulatemb.cxx @@ -42,14 +42,14 @@ rfb::BoolParameter emulateMiddleButton("dummy_name", "dummy_desc", true); class TestClass : public EmulateMB { public: - void sendPointerEvent(const rfb::Point& pos, uint8_t buttonMask) override; + void sendPointerEvent(const rfb::Point& pos, uint16_t buttonMask) override; - struct PointerEventParams {rfb::Point pos; uint8_t mask; }; + struct PointerEventParams {rfb::Point pos; uint16_t mask; }; std::vector results; }; -void TestClass::sendPointerEvent(const rfb::Point& pos, uint8_t buttonMask) +void TestClass::sendPointerEvent(const rfb::Point& pos, uint16_t buttonMask) { PointerEventParams params; params.pos = pos; diff --git a/unix/x0vncserver/XDesktop.h b/unix/x0vncserver/XDesktop.h index 66ac10ac70..711d6893d5 100644 --- a/unix/x0vncserver/XDesktop.h +++ b/unix/x0vncserver/XDesktop.h @@ -79,7 +79,7 @@ class XDesktop : public rfb::SDesktop, rfb::VNCServer* server; QueryConnectDialog* queryConnectDialog; network::Socket* queryConnectSock; - uint8_t oldButtonMask; + uint16_t oldButtonMask; bool haveXtest; bool haveDamage; int maxButtons; diff --git a/vncviewer/EmulateMB.cxx b/vncviewer/EmulateMB.cxx index fef8b3d90e..ef19ace487 100644 --- a/vncviewer/EmulateMB.cxx +++ b/vncviewer/EmulateMB.cxx @@ -199,7 +199,7 @@ EmulateMB::EmulateMB() { } -void EmulateMB::filterPointerEvent(const rfb::Point& pos, uint8_t buttonMask) +void EmulateMB::filterPointerEvent(const rfb::Point& pos, uint16_t buttonMask) { int btstate; int action1, action2; @@ -280,7 +280,7 @@ void EmulateMB::filterPointerEvent(const rfb::Point& pos, uint8_t buttonMask) void EmulateMB::handleTimeout(rfb::Timer *t) { int action1, action2; - uint8_t buttonMask; + uint16_t buttonMask; if (&timer != t) return; @@ -312,7 +312,7 @@ void EmulateMB::handleTimeout(rfb::Timer *t) state = stateTab[state][4][2]; } -void EmulateMB::sendAction(const rfb::Point& pos, uint8_t buttonMask, int action) +void EmulateMB::sendAction(const rfb::Point& pos, uint16_t buttonMask, int action) { assert(action != 0); @@ -325,7 +325,7 @@ void EmulateMB::sendAction(const rfb::Point& pos, uint8_t buttonMask, int action sendPointerEvent(pos, buttonMask); } -int EmulateMB::createButtonMask(uint8_t buttonMask) +int EmulateMB::createButtonMask(uint16_t buttonMask) { // Unset left and right buttons in the mask buttonMask &= ~0x5; diff --git a/vncviewer/EmulateMB.h b/vncviewer/EmulateMB.h index 1afa4881d3..127c34a404 100644 --- a/vncviewer/EmulateMB.h +++ b/vncviewer/EmulateMB.h @@ -26,22 +26,22 @@ class EmulateMB : public rfb::Timer::Callback { public: EmulateMB(); - void filterPointerEvent(const rfb::Point& pos, uint8_t buttonMask); + void filterPointerEvent(const rfb::Point& pos, uint16_t buttonMask); protected: - virtual void sendPointerEvent(const rfb::Point& pos, uint8_t buttonMask)=0; + virtual void sendPointerEvent(const rfb::Point& pos, uint16_t buttonMask)=0; void handleTimeout(rfb::Timer *t) override; private: - void sendAction(const rfb::Point& pos, uint8_t buttonMask, int action); + void sendAction(const rfb::Point& pos, uint16_t buttonMask, int action); - int createButtonMask(uint8_t buttonMask); + int createButtonMask(uint16_t buttonMask); private: int state; - uint8_t emulatedButtonMask; - uint8_t lastButtonMask; + uint16_t emulatedButtonMask; + uint16_t lastButtonMask; rfb::Point lastPos, origPos; rfb::Timer timer; }; diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 9d71a859c3..6cda65e9e1 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -606,6 +606,20 @@ int Viewport::handle(int event) if (Fl::event_button3()) buttonMask |= 1 << 2; + // The back/forward buttons are not supported by FTLK 1.3 and require + // a patch which adds these buttons to the FLTK API. These buttons + // will be part of the upcoming 1.4 API: + // * https://github.com/fltk/fltk/pull/1081 + // + // A backport for branch-1.3 is available here: + // * https://github.com/fltk/fltk/pull/1083 +#if defined(FL_BUTTON4) && defined(FL_BUTTON5) + if (Fl::event_button4()) + buttonMask |= 1 << 7; + if (Fl::event_button5()) + buttonMask |= 1 << 8; +#endif + if (event == FL_MOUSEWHEEL) { wheelMask = 0; if (Fl::event_dy() < 0) @@ -660,7 +674,7 @@ int Viewport::handle(int event) return Fl_Widget::handle(event); } -void Viewport::sendPointerEvent(const rfb::Point& pos, uint8_t buttonMask) +void Viewport::sendPointerEvent(const rfb::Point& pos, uint16_t buttonMask) { if (viewOnly) return; @@ -790,7 +804,7 @@ void Viewport::flushPendingClipboard() } -void Viewport::handlePointerEvent(const rfb::Point& pos, uint8_t buttonMask) +void Viewport::handlePointerEvent(const rfb::Point& pos, uint16_t buttonMask) { filterPointerEvent(pos, buttonMask); } @@ -937,6 +951,8 @@ int Viewport::handleSystemEvent(void *event, void *data) (msg->message == WM_RBUTTONUP) || (msg->message == WM_MBUTTONDOWN) || (msg->message == WM_MBUTTONUP) || + (msg->message == WM_XBUTTONDOWN) || + (msg->message == WM_XBUTTONUP) || (msg->message == WM_MOUSEWHEEL) || (msg->message == WM_MOUSEHWHEEL)) { // We can't get a mouse event in the middle of an AltGr sequence, so diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index 5f4c1ca7f1..c5222a883d 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -70,7 +70,7 @@ class Viewport : public Fl_Widget, public EmulateMB { int handle(int event) override; protected: - void sendPointerEvent(const rfb::Point& pos, uint8_t buttonMask) override; + void sendPointerEvent(const rfb::Point& pos, uint16_t buttonMask) override; private: bool hasFocus(); @@ -81,7 +81,7 @@ class Viewport : public Fl_Widget, public EmulateMB { void flushPendingClipboard(); - void handlePointerEvent(const rfb::Point& pos, uint8_t buttonMask); + void handlePointerEvent(const rfb::Point& pos, uint16_t buttonMask); static void handlePointerTimeout(void *data); void resetKeyboard(); @@ -111,7 +111,7 @@ class Viewport : public Fl_Widget, public EmulateMB { PlatformPixelBuffer* frameBuffer; rfb::Point lastPointerPos; - uint8_t lastButtonMask; + uint16_t lastButtonMask; typedef std::map DownMap; DownMap downKeySym; diff --git a/win/rfb_win32/SInput.h b/win/rfb_win32/SInput.h index c02d94e756..018bec55c4 100644 --- a/win/rfb_win32/SInput.h +++ b/win/rfb_win32/SInput.h @@ -47,7 +47,7 @@ namespace rfb { void pointerEvent(const Point& pos, uint16_t buttonmask); protected: Point last_position; - uint8_t last_buttonmask; + uint16_t last_buttonmask; }; // -=- Keyboard event handling