From bda5a3d1e0c0921b02a35a6fc3d64e23834c3efa 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 | 39 +++++++++++++++++++++++++++++++++---- common/rfb/CMsgWriter.h | 2 +- common/rfb/ServerParams.cxx | 2 +- common/rfb/ServerParams.h | 1 + tests/unit/emulatemb.cxx | 6 +++--- vncviewer/DesktopWindow.cxx | 4 ++-- vncviewer/EmulateMB.cxx | 8 ++++---- vncviewer/EmulateMB.h | 12 ++++++------ vncviewer/Viewport.cxx | 30 +++++++++++++++++++++------- vncviewer/Viewport.h | 6 +++--- win/rfb_win32/SDisplay.cxx | 2 +- win/rfb_win32/SDisplay.h | 2 +- win/rfb_win32/SInput.cxx | 2 +- win/rfb_win32/SInput.h | 2 +- 18 files changed, 94 insertions(+), 35 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..e93a67a5bc 100644 --- a/common/rfb/CMsgWriter.cxx +++ b/common/rfb/CMsgWriter.cxx @@ -22,6 +22,7 @@ #endif #include +#include #include #include @@ -173,18 +174,48 @@ 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/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 2d3637815c..e7dee48d3b 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -174,7 +174,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name, #ifdef __APPLE__ // On OS X we can do the maximize thing properly before the // window is showned. Other platforms handled further down... - if (maximize) { + if (false) { int dummy; Fl::screen_work_area(dummy, dummy, w, h, geom_x, geom_y); } @@ -208,7 +208,7 @@ DesktopWindow::DesktopWindow(int w, int h, const char *name, // maximized property on Windows and X11 before showing the window. // See STR #2083 and STR #2178 #ifndef __APPLE__ - if (maximize) { + if (false) { maximizeWindow(); } #endif 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..c718cf0b85 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -115,6 +115,7 @@ enum { ID_DISCONNECT, ID_FULLSCREEN, ID_MINIMIZE, ID_RESIZE, static const WORD SCAN_FAKE = 0xaa; #endif + Viewport::Viewport(int w, int h, const rfb::PixelFormat& /*serverPF*/, CConn* cc_) : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(nullptr), lastPointerPos(0, 0), lastButtonMask(0), @@ -606,6 +607,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) @@ -622,7 +637,6 @@ int Viewport::handle(int event) handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), buttonMask | wheelMask); } - handlePointerEvent(Point(Fl::event_x() - x(), Fl::event_y() - y()), buttonMask); return 1; @@ -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 @@ -1234,20 +1250,20 @@ void Viewport::initContextMenu() fltk_menu_add(contextMenu, p_("ContextMenu|", "&Full screen"), 0, nullptr, (void*)ID_FULLSCREEN, - FL_MENU_TOGGLE | (window()->fullscreen_active()?FL_MENU_VALUE:0)); + 2 | (window()->fullscreen_active()?4:0)); fltk_menu_add(contextMenu, p_("ContextMenu|", "Minimi&ze"), 0, nullptr, (void*)ID_MINIMIZE, 0); fltk_menu_add(contextMenu, p_("ContextMenu|", "Resize &window to session"), 0, nullptr, (void*)ID_RESIZE, - (window()->fullscreen_active()?FL_MENU_INACTIVE:0) | + (window()->fullscreen_active()?1:0) | FL_MENU_DIVIDER); fltk_menu_add(contextMenu, p_("ContextMenu|", "&Ctrl"), 0, nullptr, (void*)ID_CTRL, - FL_MENU_TOGGLE | (menuCtrlKey?FL_MENU_VALUE:0)); + 2 | (menuCtrlKey?4:0)); fltk_menu_add(contextMenu, p_("ContextMenu|", "&Alt"), 0, nullptr, (void*)ID_ALT, - FL_MENU_TOGGLE | (menuAltKey?FL_MENU_VALUE:0)); + 2 | (menuAltKey?4:0)); if (menuKeySym) { char sendMenuKey[64]; 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/SDisplay.cxx b/win/rfb_win32/SDisplay.cxx index 0ec5e231f4..dee521e5d6 100644 --- a/win/rfb_win32/SDisplay.cxx +++ b/win/rfb_win32/SDisplay.cxx @@ -312,7 +312,7 @@ void SDisplay::handleClipboardData(const char* data) { } -void SDisplay::pointerEvent(const Point& pos, uint8_t buttonmask) { +void SDisplay::pointerEvent(const Point& pos, uint16_t buttonmask) { if (pb->getRect().contains(pos)) { Point screenPos = pos.translate(screenRect.tl); // - Check that the SDesktop doesn't need restarting diff --git a/win/rfb_win32/SDisplay.h b/win/rfb_win32/SDisplay.h index d4cf23e46f..aa1a69e553 100644 --- a/win/rfb_win32/SDisplay.h +++ b/win/rfb_win32/SDisplay.h @@ -80,7 +80,7 @@ namespace rfb { void handleClipboardRequest() override; void handleClipboardAnnounce(bool available) override; void handleClipboardData(const char* data) override; - void pointerEvent(const Point& pos, uint8_t buttonmask) override; + void pointerEvent(const Point& pos, uint16_t buttonmask) override; void keyEvent(uint32_t keysym, uint32_t keycode, bool down) override; // -=- Clipboard events diff --git a/win/rfb_win32/SInput.cxx b/win/rfb_win32/SInput.cxx index 65d4a703c5..6441129d17 100644 --- a/win/rfb_win32/SInput.cxx +++ b/win/rfb_win32/SInput.cxx @@ -65,7 +65,7 @@ win32::SPointer::SPointer() } void -win32::SPointer::pointerEvent(const Point& pos, uint8_t buttonmask) +win32::SPointer::pointerEvent(const Point& pos, uint16_t buttonmask) { // - We are specifying absolute coordinates DWORD flags = MOUSEEVENTF_ABSOLUTE; diff --git a/win/rfb_win32/SInput.h b/win/rfb_win32/SInput.h index 29e1df41c8..c02d94e756 100644 --- a/win/rfb_win32/SInput.h +++ b/win/rfb_win32/SInput.h @@ -44,7 +44,7 @@ namespace rfb { // - Create a pointer event at a the given coordinates, with the // specified button state. The event must be specified using // Screen coordinates. - void pointerEvent(const Point& pos, uint8_t buttonmask); + void pointerEvent(const Point& pos, uint16_t buttonmask); protected: Point last_position; uint8_t last_buttonmask;