From 2fa63f8576e5d1c632efeeb2c185f11e943899d8 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 5 Dec 2016 15:26:21 +0100 Subject: Add client support for LED state sync --- common/rfb/CMsgHandler.cxx | 5 +++++ common/rfb/CMsgHandler.h | 2 ++ common/rfb/CMsgReader.cxx | 12 ++++++++++++ common/rfb/CMsgReader.h | 1 + common/rfb/CMsgWriter.cxx | 2 ++ common/rfb/ConnParams.cxx | 16 +++++++++++++--- common/rfb/ConnParams.h | 5 +++++ common/rfb/encodings.h | 1 + common/rfb/ledStates.h | 30 ++++++++++++++++++++++++++++++ 9 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 common/rfb/ledStates.h (limited to 'common') diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx index 11c979a3..74c7bf92 100644 --- a/common/rfb/CMsgHandler.cxx +++ b/common/rfb/CMsgHandler.cxx @@ -82,3 +82,8 @@ void CMsgHandler::framebufferUpdateStart() void CMsgHandler::framebufferUpdateEnd() { } + +void CMsgHandler::setLEDState(unsigned int state) +{ + cp.setLEDState(state); +} diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h index 993276ed..ef2cda20 100644 --- a/common/rfb/CMsgHandler.h +++ b/common/rfb/CMsgHandler.h @@ -69,6 +69,8 @@ namespace rfb { virtual void bell() = 0; virtual void serverCutText(const char* str, rdr::U32 len) = 0; + virtual void setLEDState(unsigned int state); + ConnParams cp; }; } diff --git a/common/rfb/CMsgReader.cxx b/common/rfb/CMsgReader.cxx index 9abe3f24..0aaf71fa 100644 --- a/common/rfb/CMsgReader.cxx +++ b/common/rfb/CMsgReader.cxx @@ -109,6 +109,9 @@ void CMsgReader::readMsg() case pseudoEncodingExtendedDesktopSize: readExtendedDesktopSize(x, y, w, h); break; + case pseudoEncodingLEDState: + readLEDState(); + break; default: readRect(Rect(x, y, x+w, y+h), encoding); break; @@ -382,3 +385,12 @@ void CMsgReader::readExtendedDesktopSize(int x, int y, int w, int h) handler->setExtendedDesktopSize(x, y, w, h, layout); } + +void CMsgReader::readLEDState() +{ + rdr::U8 state; + + state = is->readU8(); + + handler->setLEDState(state); +} diff --git a/common/rfb/CMsgReader.h b/common/rfb/CMsgReader.h index 7b52033f..99638276 100644 --- a/common/rfb/CMsgReader.h +++ b/common/rfb/CMsgReader.h @@ -65,6 +65,7 @@ namespace rfb { void readSetCursorWithAlpha(int width, int height, const Point& hotspot); void readSetDesktopName(int x, int y, int w, int h); void readExtendedDesktopSize(int x, int y, int w, int h); + void readLEDState(); CMsgHandler* handler; rdr::InStream* is; diff --git a/common/rfb/CMsgWriter.cxx b/common/rfb/CMsgWriter.cxx index fa784048..7a89a934 100644 --- a/common/rfb/CMsgWriter.cxx +++ b/common/rfb/CMsgWriter.cxx @@ -82,6 +82,8 @@ void CMsgWriter::writeSetEncodings(int preferredEncoding, bool useCopyRect) encodings[nEncodings++] = pseudoEncodingExtendedDesktopSize; if (cp->supportsDesktopRename) encodings[nEncodings++] = pseudoEncodingDesktopName; + if (cp->supportsLEDState) + encodings[nEncodings++] = pseudoEncodingLEDState; encodings[nEncodings++] = pseudoEncodingLastRect; encodings[nEncodings++] = pseudoEncodingContinuousUpdates; diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx index 9ee1d9c0..f0b69327 100644 --- a/common/rfb/ConnParams.cxx +++ b/common/rfb/ConnParams.cxx @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -34,10 +35,11 @@ ConnParams::ConnParams() supportsLocalCursorWithAlpha(false), supportsDesktopResize(false), supportsExtendedDesktopSize(false), supportsDesktopRename(false), supportsLastRect(false), - supportsSetDesktopSize(false), supportsFence(false), - supportsContinuousUpdates(false), + supportsLEDState(false), supportsSetDesktopSize(false), + supportsFence(false), supportsContinuousUpdates(false), compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), - subsampling(subsampleUndefined), name_(0), verStrPos(0) + subsampling(subsampleUndefined), name_(0), verStrPos(0), + ledState_(ledUnknown) { setName(""); cursor_ = new Cursor(0, 0, Point(), NULL); @@ -141,6 +143,9 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) case pseudoEncodingLastRect: supportsLastRect = true; break; + case pseudoEncodingLEDState: + supportsLEDState = true; + break; case pseudoEncodingFence: supportsFence = true; break; @@ -183,3 +188,8 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) encodings_.insert(encodings[i]); } } + +void ConnParams::setLEDState(unsigned int state) +{ + ledState_ = state; +} diff --git a/common/rfb/ConnParams.h b/common/rfb/ConnParams.h index 5e538933..d99d142c 100644 --- a/common/rfb/ConnParams.h +++ b/common/rfb/ConnParams.h @@ -84,6 +84,9 @@ namespace rfb { void setEncodings(int nEncodings, const rdr::S32* encodings); + unsigned int ledState() { return ledState_; } + void setLEDState(unsigned int state); + bool useCopyRect; bool supportsLocalCursor; @@ -93,6 +96,7 @@ namespace rfb { bool supportsExtendedDesktopSize; bool supportsDesktopRename; bool supportsLastRect; + bool supportsLEDState; bool supportsSetDesktopSize; bool supportsFence; @@ -111,6 +115,7 @@ namespace rfb { std::set encodings_; char verStr[13]; int verStrPos; + unsigned int ledState_; }; } #endif diff --git a/common/rfb/encodings.h b/common/rfb/encodings.h index a65d863b..adeecaa9 100644 --- a/common/rfb/encodings.h +++ b/common/rfb/encodings.h @@ -34,6 +34,7 @@ namespace rfb { const int pseudoEncodingXCursor = -240; const int pseudoEncodingCursor = -239; const int pseudoEncodingDesktopSize = -223; + const int pseudoEncodingLEDState = -261; const int pseudoEncodingExtendedDesktopSize = -308; const int pseudoEncodingDesktopName = -307; const int pseudoEncodingFence = -312; diff --git a/common/rfb/ledStates.h b/common/rfb/ledStates.h new file mode 100644 index 00000000..ef146828 --- /dev/null +++ b/common/rfb/ledStates.h @@ -0,0 +1,30 @@ +/* Copyright 2016 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +#ifndef __RFB_LEDSTATES_H__ +#define __RFB_LEDSTATES_H__ + +namespace rfb { + + const unsigned int ledScrollLock = 1 << 0; + const unsigned int ledNumLock = 1 << 1; + const unsigned int ledCapsLock = 1 << 2; + + const unsigned int ledUnknown = (unsigned int)-1; +} + +#endif -- cgit v1.2.3 From bb305ca3dbbc316c9742d36b0919226e15120add Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 11 Dec 2016 12:41:26 +0100 Subject: Add server side lock key sync heuristic Based on QEMU's behaviour. --- common/rfb/VNCSConnectionST.cxx | 69 +++++++++++++++++++++++++++++++++++++++++ common/rfb/VNCServer.h | 4 +++ common/rfb/VNCServerST.cxx | 8 ++++- common/rfb/VNCServerST.h | 2 ++ unix/xserver/hw/vnc/Input.c | 6 ---- unix/xserver/hw/vnc/Input.h | 2 -- unix/xserver/hw/vnc/InputXKB.c | 17 ---------- 7 files changed, 82 insertions(+), 26 deletions(-) (limited to 'common') diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 53dd364a..de41e8fc 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -34,10 +34,12 @@ #include #include #include +#include #include #include #include #include +#define XK_LATIN1 #define XK_MISCELLANY #define XK_XKB_KEYS #include @@ -560,6 +562,73 @@ void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) { } } + // Avoid lock keys if we don't know the server state + if ((server->ledState == ledUnknown) && + ((key == XK_Caps_Lock) || + (key == XK_Num_Lock) || + (key == XK_Scroll_Lock))) { + vlog.debug("Ignoring lock key (e.g. caps lock)"); + return; + } + // Always ignore ScrollLock though as we don't have a heuristic + // for that + if (key == XK_Scroll_Lock) { + vlog.debug("Ignoring lock key (e.g. caps lock)"); + return; + } + + if (down && (server->ledState != ledUnknown)) { + // CapsLock synchronisation heuristic + // (this assumes standard interaction between CapsLock the Shift + // keys and normal characters) + if (((key >= XK_A) && (key <= XK_Z)) || + ((key >= XK_a) && (key <= XK_z))) { + bool uppercase, shift, lock; + + uppercase = (key >= XK_A) && (key <= XK_Z); + shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() || + pressedKeys.find(XK_Shift_R) != pressedKeys.end(); + lock = server->ledState & ledCapsLock; + + if (lock == (uppercase == shift)) { + vlog.debug("Inserting fake CapsLock to get in sync with client"); + server->desktop->keyEvent(XK_Caps_Lock, true); + server->desktop->keyEvent(XK_Caps_Lock, false); + } + } + + // NumLock synchronisation heuristic + // (this is more cautious because of the differences between Unix, + // Windows and macOS) + if (((key >= XK_KP_Home) && (key <= XK_KP_Delete)) || + ((key >= XK_KP_0) && (key <= XK_KP_9)) || + (key == XK_KP_Separator) || (key == XK_KP_Decimal)) { + bool number, shift, lock; + + number = ((key >= XK_KP_0) && (key <= XK_KP_9)) || + (key == XK_KP_Separator) || (key == XK_KP_Decimal); + shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() || + pressedKeys.find(XK_Shift_R) != pressedKeys.end(); + lock = server->ledState & ledNumLock; + + if (shift) { + // We don't know the appropriate NumLock state for when Shift + // is pressed as it could be one of: + // + // a) A Unix client where Shift negates NumLock + // + // b) A Windows client where Shift only cancels NumLock + // + // c) A macOS client where Shift doesn't have any effect + // + } else if (lock == (number == shift)) { + vlog.debug("Inserting fake NumLock to get in sync with client"); + server->desktop->keyEvent(XK_Num_Lock, true); + server->desktop->keyEvent(XK_Num_Lock, false); + } + } + } + // Turn ISO_Left_Tab into shifted Tab. VNCSConnectionSTShiftPresser shiftPresser(server->desktop); if (key == XK_ISO_Left_Tab) { diff --git a/common/rfb/VNCServer.h b/common/rfb/VNCServer.h index 982a4ff5..c5335ad2 100644 --- a/common/rfb/VNCServer.h +++ b/common/rfb/VNCServer.h @@ -74,6 +74,10 @@ namespace rfb { // setName() tells the server what desktop title to supply to clients virtual void setName(const char* name) = 0; + + // setLEDState() tells the server what the current lock keys LED + // state is + virtual void setLEDState(unsigned int state) = 0; }; } #endif diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index ec5e962f..28f6a62b 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -58,6 +58,7 @@ #include #include #include +#include #include @@ -74,7 +75,7 @@ LogWriter VNCServerST::connectionsLog("Connections"); VNCServerST::VNCServerST(const char* name_, SDesktop* desktop_) : blHosts(&blacklist), desktop(desktop_), desktopStarted(false), - blockCounter(0), pb(0), + blockCounter(0), pb(0), ledState(ledUnknown), name(strDup(name_)), pointerClient(0), comparer(0), cursor(new Cursor(0, 0, Point(), NULL)), renderedCursorInvalid(false), @@ -458,6 +459,11 @@ void VNCServerST::setCursorPos(const Point& pos) } } +void VNCServerST::setLEDState(unsigned int state) +{ + ledState = state; +} + // Other public methods void VNCServerST::approveConnection(network::Socket* sock, bool accept, diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index 00f77c73..2dfdbbdb 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -101,6 +101,7 @@ namespace rfb { virtual void setCursor(int width, int height, const Point& hotspot, const rdr::U8* data); virtual void setCursorPos(const Point& p); + virtual void setLEDState(unsigned state); virtual void bell(); @@ -209,6 +210,7 @@ namespace rfb { int blockCounter; PixelBuffer* pb; ScreenSet screenLayout; + unsigned int ledState; CharArray name; diff --git a/unix/xserver/hw/vnc/Input.c b/unix/xserver/hw/vnc/Input.c index 33e8604b..1d7183b9 100644 --- a/unix/xserver/hw/vnc/Input.c +++ b/unix/xserver/hw/vnc/Input.c @@ -437,12 +437,6 @@ void vncKeyboardEvent(KeySym keysym, int down) } } - /* We don't have lock synchronisation... */ - if (vncIsLockModifier(keycode, new_state)) { - LOG_DEBUG("Ignoring lock key (e.g. caps lock)"); - return; - } - /* No matches. Will have to add a new entry... */ if (keycode == 0) { keycode = vncAddKeysym(keysym, state); diff --git a/unix/xserver/hw/vnc/Input.h b/unix/xserver/hw/vnc/Input.h index 11e88710..67b9c8db 100644 --- a/unix/xserver/hw/vnc/Input.h +++ b/unix/xserver/hw/vnc/Input.h @@ -53,8 +53,6 @@ size_t vncReleaseLevelThree(KeyCode *keys, size_t maxKeys); KeyCode vncKeysymToKeycode(KeySym keysym, unsigned state, unsigned *new_state); -int vncIsLockModifier(KeyCode keycode, unsigned state); - int vncIsAffectedByNumLock(KeyCode keycode); KeyCode vncAddKeysym(KeySym keysym, unsigned state); diff --git a/unix/xserver/hw/vnc/InputXKB.c b/unix/xserver/hw/vnc/InputXKB.c index 2a3f7afb..a9bd11d1 100644 --- a/unix/xserver/hw/vnc/InputXKB.c +++ b/unix/xserver/hw/vnc/InputXKB.c @@ -485,23 +485,6 @@ KeyCode vncKeysymToKeycode(KeySym keysym, unsigned state, unsigned *new_state) return 0; } -int vncIsLockModifier(KeyCode keycode, unsigned state) -{ - XkbDescPtr xkb; - XkbAction *act; - - xkb = GetMaster(vncKeyboardDev, KEYBOARD_OR_FLOAT)->key->xkbInfo->desc; - - act = XkbKeyActionPtr(xkb, keycode, state); - if (act == NULL) - return 0; - - if (act->type != XkbSA_LockMods) - return 0; - - return 1; -} - int vncIsAffectedByNumLock(KeyCode keycode) { unsigned state; -- cgit v1.2.3 From b45a84f9531c3659364676af49d589fab060633b Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 12 Dec 2016 16:59:15 +0100 Subject: Send lock LED state from server to client --- common/rfb/SMsgHandler.cxx | 9 ++- common/rfb/SMsgHandler.h | 6 ++ common/rfb/SMsgWriter.cxx | 42 +++++++++++- common/rfb/SMsgWriter.h | 5 ++ common/rfb/VNCSConnectionST.cxx | 140 +++++++++++++++++++++++++--------------- common/rfb/VNCSConnectionST.h | 3 + common/rfb/VNCServerST.cxx | 10 +++ 7 files changed, 161 insertions(+), 54 deletions(-) (limited to 'common') diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx index 388b21f8..8e48c673 100644 --- a/common/rfb/SMsgHandler.cxx +++ b/common/rfb/SMsgHandler.cxx @@ -41,10 +41,11 @@ void SMsgHandler::setPixelFormat(const PixelFormat& pf) void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings) { - bool firstFence, firstContinuousUpdates; + bool firstFence, firstContinuousUpdates, firstLEDState; firstFence = !cp.supportsFence; firstContinuousUpdates = !cp.supportsContinuousUpdates; + firstLEDState = !cp.supportsLEDState; cp.setEncodings(nEncodings, encodings); @@ -54,6 +55,8 @@ void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings) supportsFence(); if (cp.supportsContinuousUpdates && firstContinuousUpdates) supportsContinuousUpdates(); + if (cp.supportsLEDState && firstLEDState) + supportsLEDState(); } void SMsgHandler::supportsLocalCursor() @@ -68,6 +71,10 @@ void SMsgHandler::supportsContinuousUpdates() { } +void SMsgHandler::supportsLEDState() +{ +} + void SMsgHandler::setDesktopSize(int fb_width, int fb_height, const ScreenSet& layout) { diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h index 74509e0b..cf6b6b3b 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -74,6 +74,12 @@ namespace rfb { // this point if it is supported. virtual void supportsContinuousUpdates(); + // supportsLEDState() is called the first time we detect that the + // client supports the LED state extension. A LEDState message + // should be sent back to the client to inform it of the current + // server state. + virtual void supportsLEDState(); + ConnParams cp; }; } diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx index bc3f4398..d8adfbc1 100644 --- a/common/rfb/SMsgWriter.cxx +++ b/common/rfb/SMsgWriter.cxx @@ -27,6 +27,7 @@ #include #include #include +#include using namespace rfb; @@ -37,7 +38,8 @@ SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_) nRectsInUpdate(0), nRectsInHeader(0), needSetDesktopSize(false), needExtendedDesktopSize(false), needSetDesktopName(false), needSetCursor(false), - needSetXCursor(false), needSetCursorWithAlpha(false) + needSetXCursor(false), needSetCursorWithAlpha(false), + needLEDState(false) { } @@ -193,12 +195,26 @@ bool SMsgWriter::writeSetCursorWithAlpha() return true; } +bool SMsgWriter::writeLEDState() +{ + if (!cp->supportsLEDState) + return false; + if (cp->ledState() == ledUnknown) + return false; + + needLEDState = true; + + return true; +} + bool SMsgWriter::needFakeUpdate() { if (needSetDesktopName) return true; if (needSetCursor || needSetXCursor || needSetCursorWithAlpha) return true; + if (needLEDState) + return true; if (needNoDataUpdate()) return true; @@ -247,6 +263,8 @@ void SMsgWriter::writeFramebufferUpdateStart(int nRects) nRects++; if (needSetCursorWithAlpha) nRects++; + if (needLEDState) + nRects++; } os->writeU16(nRects); @@ -362,6 +380,11 @@ void SMsgWriter::writePseudoRects() writeSetDesktopNameRect(cp->name()); needSetDesktopName = false; } + + if (needLEDState) { + writeLEDStateRect(cp->ledState()); + needLEDState = false; + } } void SMsgWriter::writeNoDataRects() @@ -525,3 +548,20 @@ void SMsgWriter::writeSetCursorWithAlphaRect(int width, int height, data += 4; } } + +void SMsgWriter::writeLEDStateRect(rdr::U8 state) +{ + if (!cp->supportsLEDState) + throw Exception("Client does not support LED state updates"); + if (cp->ledState() == ledUnknown) + throw Exception("Server does not support LED state updates"); + if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) + throw Exception("SMsgWriter::writeLEDStateRect: nRects out of sync"); + + os->writeS16(0); + os->writeS16(0); + os->writeU16(0); + os->writeU16(0); + os->writeU32(pseudoEncodingLEDState); + os->writeU8(state); +} diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h index 548b8e8e..890b2b5b 100644 --- a/common/rfb/SMsgWriter.h +++ b/common/rfb/SMsgWriter.h @@ -82,6 +82,9 @@ namespace rfb { bool writeSetXCursor(); bool writeSetCursorWithAlpha(); + // Same for LED state message + bool writeLEDState(); + // needFakeUpdate() returns true when an immediate update is needed in // order to flush out pseudo-rectangles to the client. bool needFakeUpdate(); @@ -131,6 +134,7 @@ namespace rfb { void writeSetCursorWithAlphaRect(int width, int height, int hotspotX, int hotspotY, const rdr::U8* data); + void writeLEDStateRect(rdr::U8 state); ConnParams* cp; rdr::OutStream* os; @@ -145,6 +149,7 @@ namespace rfb { bool needSetCursor; bool needSetXCursor; bool needSetCursorWithAlpha; + bool needLEDState; typedef struct { rdr::U16 reason, result; diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index de41e8fc..232776fc 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -313,6 +313,16 @@ void VNCSConnectionST::setCursorOrClose() } +void VNCSConnectionST::setLEDStateOrClose(unsigned int state) +{ + try { + setLEDState(state); + } catch(rdr::Exception& e) { + close(e.str()); + } +} + + int VNCSConnectionST::checkIdleTimeout() { int idleTimeout = rfb::Server::idleTimeout; @@ -418,6 +428,7 @@ void VNCSConnectionST::authSuccess() cp.height = server->pb->height(); cp.screenLayout = server->screenLayout; cp.setName(server->getName()); + cp.setLEDState(server->ledState); // - Set the default pixel format cp.setPF(server->pb->getPF()); @@ -570,61 +581,66 @@ void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) { vlog.debug("Ignoring lock key (e.g. caps lock)"); return; } - // Always ignore ScrollLock though as we don't have a heuristic - // for that - if (key == XK_Scroll_Lock) { - vlog.debug("Ignoring lock key (e.g. caps lock)"); - return; - } - if (down && (server->ledState != ledUnknown)) { - // CapsLock synchronisation heuristic - // (this assumes standard interaction between CapsLock the Shift - // keys and normal characters) - if (((key >= XK_A) && (key <= XK_Z)) || - ((key >= XK_a) && (key <= XK_z))) { - bool uppercase, shift, lock; - - uppercase = (key >= XK_A) && (key <= XK_Z); - shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() || - pressedKeys.find(XK_Shift_R) != pressedKeys.end(); - lock = server->ledState & ledCapsLock; - - if (lock == (uppercase == shift)) { - vlog.debug("Inserting fake CapsLock to get in sync with client"); - server->desktop->keyEvent(XK_Caps_Lock, true); - server->desktop->keyEvent(XK_Caps_Lock, false); - } + // Lock key heuristics + // (only for clients that do not support the LED state extension) + if (!cp.supportsLEDState) { + // Always ignore ScrollLock as we don't have a heuristic + // for that + if (key == XK_Scroll_Lock) { + vlog.debug("Ignoring lock key (e.g. caps lock)"); + return; } - // NumLock synchronisation heuristic - // (this is more cautious because of the differences between Unix, - // Windows and macOS) - if (((key >= XK_KP_Home) && (key <= XK_KP_Delete)) || - ((key >= XK_KP_0) && (key <= XK_KP_9)) || - (key == XK_KP_Separator) || (key == XK_KP_Decimal)) { - bool number, shift, lock; - - number = ((key >= XK_KP_0) && (key <= XK_KP_9)) || - (key == XK_KP_Separator) || (key == XK_KP_Decimal); - shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() || - pressedKeys.find(XK_Shift_R) != pressedKeys.end(); - lock = server->ledState & ledNumLock; - - if (shift) { - // We don't know the appropriate NumLock state for when Shift - // is pressed as it could be one of: - // - // a) A Unix client where Shift negates NumLock - // - // b) A Windows client where Shift only cancels NumLock - // - // c) A macOS client where Shift doesn't have any effect - // - } else if (lock == (number == shift)) { - vlog.debug("Inserting fake NumLock to get in sync with client"); - server->desktop->keyEvent(XK_Num_Lock, true); - server->desktop->keyEvent(XK_Num_Lock, false); + if (down && (server->ledState != ledUnknown)) { + // CapsLock synchronisation heuristic + // (this assumes standard interaction between CapsLock the Shift + // keys and normal characters) + if (((key >= XK_A) && (key <= XK_Z)) || + ((key >= XK_a) && (key <= XK_z))) { + bool uppercase, shift, lock; + + uppercase = (key >= XK_A) && (key <= XK_Z); + shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() || + pressedKeys.find(XK_Shift_R) != pressedKeys.end(); + lock = server->ledState & ledCapsLock; + + if (lock == (uppercase == shift)) { + vlog.debug("Inserting fake CapsLock to get in sync with client"); + server->desktop->keyEvent(XK_Caps_Lock, true); + server->desktop->keyEvent(XK_Caps_Lock, false); + } + } + + // NumLock synchronisation heuristic + // (this is more cautious because of the differences between Unix, + // Windows and macOS) + if (((key >= XK_KP_Home) && (key <= XK_KP_Delete)) || + ((key >= XK_KP_0) && (key <= XK_KP_9)) || + (key == XK_KP_Separator) || (key == XK_KP_Decimal)) { + bool number, shift, lock; + + number = ((key >= XK_KP_0) && (key <= XK_KP_9)) || + (key == XK_KP_Separator) || (key == XK_KP_Decimal); + shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() || + pressedKeys.find(XK_Shift_R) != pressedKeys.end(); + lock = server->ledState & ledNumLock; + + if (shift) { + // We don't know the appropriate NumLock state for when Shift + // is pressed as it could be one of: + // + // a) A Unix client where Shift negates NumLock + // + // b) A Windows client where Shift only cancels NumLock + // + // c) A macOS client where Shift doesn't have any effect + // + } else if (lock == (number == shift)) { + vlog.debug("Inserting fake NumLock to get in sync with client"); + server->desktop->keyEvent(XK_Num_Lock, true); + server->desktop->keyEvent(XK_Num_Lock, false); + } } } } @@ -818,6 +834,11 @@ void VNCSConnectionST::supportsContinuousUpdates() writer()->writeEndOfContinuousUpdates(); } +void VNCSConnectionST::supportsLEDState() +{ + writer()->writeLEDState(); +} + bool VNCSConnectionST::handleTimeout(Timer* t) { @@ -1230,6 +1251,21 @@ void VNCSConnectionST::setDesktopName(const char *name) writeFramebufferUpdate(); } +void VNCSConnectionST::setLEDState(unsigned int ledstate) +{ + if (state() != RFBSTATE_NORMAL) + return; + + cp.setLEDState(ledstate); + + if (!writer()->writeLEDState()) { + // No client support + return; + } + + writeFramebufferUpdate(); +} + void VNCSConnectionST::setSocketTimeouts() { int timeoutms = rfb::Server::clientWaitTimeMillis; diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index 74a6946d..8f33962d 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -78,6 +78,7 @@ namespace rfb { void bellOrClose(); void serverCutTextOrClose(const char *str, int len); void setDesktopNameOrClose(const char *name); + void setLEDStateOrClose(unsigned int state); // checkIdleTimeout() returns the number of milliseconds left until the // idle timeout expires. If it has expired, the connection is closed and @@ -146,6 +147,7 @@ namespace rfb { virtual void supportsLocalCursor(); virtual void supportsFence(); virtual void supportsContinuousUpdates(); + virtual void supportsLEDState(); // setAccessRights() allows a security package to limit the access rights // of a VNCSConnectioST to the server. These access rights are applied @@ -174,6 +176,7 @@ namespace rfb { void screenLayoutChange(rdr::U16 reason); void setCursor(); void setDesktopName(const char *name); + void setLEDState(unsigned int state); void setSocketTimeouts(); network::Socket* sock; diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index 28f6a62b..43e8f3ed 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -461,7 +461,17 @@ void VNCServerST::setCursorPos(const Point& pos) void VNCServerST::setLEDState(unsigned int state) { + std::list::iterator ci, ci_next; + + if (state == ledState) + return; + ledState = state; + + for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { + ci_next = ci; ci_next++; + (*ci)->setLEDStateOrClose(state); + } } // Other public methods -- cgit v1.2.3 From 5ae282135f982505fab655d4f597e7329fb8b0d1 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 16 May 2017 14:30:38 +0200 Subject: Basic support for QEMU Extended Key Events This adds the basic infrastructure and handshake for the QEMU Extended Key Events extension. No viewer or server makes use of the extra functionality yet though. --- common/rfb/CMsgHandler.cxx | 5 +++ common/rfb/CMsgHandler.h | 1 + common/rfb/CMsgReader.cxx | 2 ++ common/rfb/CMsgWriter.cxx | 27 ++++++++++++---- common/rfb/CMsgWriter.h | 2 +- common/rfb/ConnParams.cxx | 8 +++-- common/rfb/ConnParams.h | 1 + common/rfb/InputHandler.h | 2 +- common/rfb/SConnection.cxx | 5 +++ common/rfb/SConnection.h | 1 + common/rfb/SMsgHandler.cxx | 10 +++++- common/rfb/SMsgHandler.h | 5 +++ common/rfb/SMsgReader.cxx | 29 ++++++++++++++++- common/rfb/SMsgReader.h | 3 ++ common/rfb/SMsgWriter.cxx | 35 +++++++++++++++++++- common/rfb/SMsgWriter.h | 5 +++ common/rfb/VNCSConnectionST.cxx | 61 ++++++++++++++++++----------------- common/rfb/VNCSConnectionST.h | 2 +- common/rfb/encodings.h | 1 + common/rfb/msgTypes.h | 2 ++ common/rfb/qemuTypes.h | 25 ++++++++++++++ unix/x0vncserver/x0vncserver.cxx | 4 +-- unix/xserver/hw/vnc/XserverDesktop.cc | 2 +- unix/xserver/hw/vnc/XserverDesktop.h | 2 +- vncviewer/Viewport.cxx | 12 +++---- win/rfb_win32/SDisplay.cxx | 4 +-- win/rfb_win32/SDisplay.h | 2 +- win/rfb_win32/SInput.cxx | 2 +- win/rfb_win32/SInput.h | 2 +- 29 files changed, 203 insertions(+), 59 deletions(-) create mode 100644 common/rfb/qemuTypes.h (limited to 'common') diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx index 74c7bf92..b89bc184 100644 --- a/common/rfb/CMsgHandler.cxx +++ b/common/rfb/CMsgHandler.cxx @@ -75,6 +75,11 @@ void CMsgHandler::endOfContinuousUpdates() cp.supportsContinuousUpdates = true; } +void CMsgHandler::supportsQEMUKeyEvent() +{ + cp.supportsQEMUKeyEvent = true; +} + void CMsgHandler::framebufferUpdateStart() { } diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h index ef2cda20..903ee156 100644 --- a/common/rfb/CMsgHandler.h +++ b/common/rfb/CMsgHandler.h @@ -55,6 +55,7 @@ namespace rfb { virtual void setName(const char* name); virtual void fence(rdr::U32 flags, unsigned len, const char data[]); virtual void endOfContinuousUpdates(); + virtual void supportsQEMUKeyEvent(); virtual void serverInit() = 0; virtual void readAndDecodeRect(const Rect& r, int encoding, diff --git a/common/rfb/CMsgReader.cxx b/common/rfb/CMsgReader.cxx index 0aaf71fa..eee6d277 100644 --- a/common/rfb/CMsgReader.cxx +++ b/common/rfb/CMsgReader.cxx @@ -111,6 +111,8 @@ void CMsgReader::readMsg() break; case pseudoEncodingLEDState: readLEDState(); + case pseudoEncodingQEMUKeyEvent: + handler->supportsQEMUKeyEvent(); break; default: readRect(Rect(x, y, x+w, y+h), encoding); diff --git a/common/rfb/CMsgWriter.cxx b/common/rfb/CMsgWriter.cxx index 7a89a934..57d1283a 100644 --- a/common/rfb/CMsgWriter.cxx +++ b/common/rfb/CMsgWriter.cxx @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -88,6 +89,7 @@ void CMsgWriter::writeSetEncodings(int preferredEncoding, bool useCopyRect) encodings[nEncodings++] = pseudoEncodingLastRect; encodings[nEncodings++] = pseudoEncodingContinuousUpdates; encodings[nEncodings++] = pseudoEncodingFence; + encodings[nEncodings++] = pseudoEncodingQEMUKeyEvent; if (Decoder::supported(preferredEncoding)) { encodings[nEncodings++] = preferredEncoding; @@ -215,13 +217,26 @@ void CMsgWriter::writeFence(rdr::U32 flags, unsigned len, const char data[]) endMsg(); } -void CMsgWriter::keyEvent(rdr::U32 key, bool down) +void CMsgWriter::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) { - startMsg(msgTypeKeyEvent); - os->writeU8(down); - os->pad(2); - os->writeU32(key); - endMsg(); + if (!cp->supportsQEMUKeyEvent || !keycode) { + /* This event isn't meaningful without a valid keysym */ + if (!keysym) + return; + + startMsg(msgTypeKeyEvent); + os->writeU8(down); + os->pad(2); + os->writeU32(keysym); + endMsg(); + } else { + startMsg(msgTypeQEMUClientMessage); + os->writeU8(qemuExtendedKeyEvent); + os->writeU16(down); + os->writeU32(keysym); + os->writeU32(keycode); + endMsg(); + } } diff --git a/common/rfb/CMsgWriter.h b/common/rfb/CMsgWriter.h index 06ecbe7d..56e0b7b9 100644 --- a/common/rfb/CMsgWriter.h +++ b/common/rfb/CMsgWriter.h @@ -55,7 +55,7 @@ namespace rfb { // InputHandler implementation - virtual void keyEvent(rdr::U32 key, bool down); + virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); virtual void pointerEvent(const Point& pos, int buttonMask); virtual void clientCutText(const char* str, rdr::U32 len); diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx index f0b69327..23f02ed0 100644 --- a/common/rfb/ConnParams.cxx +++ b/common/rfb/ConnParams.cxx @@ -35,8 +35,9 @@ ConnParams::ConnParams() supportsLocalCursorWithAlpha(false), supportsDesktopResize(false), supportsExtendedDesktopSize(false), supportsDesktopRename(false), supportsLastRect(false), - supportsLEDState(false), supportsSetDesktopSize(false), - supportsFence(false), supportsContinuousUpdates(false), + supportsLEDState(false), supportsQEMUKeyEvent(false), + supportsSetDesktopSize(false), supportsFence(false), + supportsContinuousUpdates(false), compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), subsampling(subsampleUndefined), name_(0), verStrPos(0), ledState_(ledUnknown) @@ -109,6 +110,7 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) supportsExtendedDesktopSize = false; supportsLocalXCursor = false; supportsLastRect = false; + supportsQEMUKeyEvent = false; compressLevel = -1; qualityLevel = -1; fineQualityLevel = -1; @@ -145,6 +147,8 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) break; case pseudoEncodingLEDState: supportsLEDState = true; + case pseudoEncodingQEMUKeyEvent: + supportsQEMUKeyEvent = true; break; case pseudoEncodingFence: supportsFence = true; diff --git a/common/rfb/ConnParams.h b/common/rfb/ConnParams.h index d99d142c..b3222936 100644 --- a/common/rfb/ConnParams.h +++ b/common/rfb/ConnParams.h @@ -97,6 +97,7 @@ namespace rfb { bool supportsDesktopRename; bool supportsLastRect; bool supportsLEDState; + bool supportsQEMUKeyEvent; bool supportsSetDesktopSize; bool supportsFence; diff --git a/common/rfb/InputHandler.h b/common/rfb/InputHandler.h index b5e5e879..0344bc3f 100644 --- a/common/rfb/InputHandler.h +++ b/common/rfb/InputHandler.h @@ -31,7 +31,7 @@ namespace rfb { class InputHandler { public: virtual ~InputHandler() {} - virtual void keyEvent(rdr::U32 key, bool down) {} + virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) {} virtual void pointerEvent(const Point& pos, int buttonMask) {} virtual void clientCutText(const char* str, int len) {} }; diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx index 85cc6e82..c5c9038c 100644 --- a/common/rfb/SConnection.cxx +++ b/common/rfb/SConnection.cxx @@ -278,6 +278,11 @@ void SConnection::setEncodings(int nEncodings, const rdr::S32* encodings) SMsgHandler::setEncodings(nEncodings, encodings); } +void SConnection::supportsQEMUKeyEvent() +{ + writer()->writeQEMUKeyEvent(); +} + void SConnection::versionReceived() { } diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h index 63dc3146..bc435834 100644 --- a/common/rfb/SConnection.h +++ b/common/rfb/SConnection.h @@ -73,6 +73,7 @@ namespace rfb { virtual void setEncodings(int nEncodings, const rdr::S32* encodings); + virtual void supportsQEMUKeyEvent(); // Methods to be overridden in a derived class diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx index 8e48c673..c38458c3 100644 --- a/common/rfb/SMsgHandler.cxx +++ b/common/rfb/SMsgHandler.cxx @@ -41,11 +41,13 @@ void SMsgHandler::setPixelFormat(const PixelFormat& pf) void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings) { - bool firstFence, firstContinuousUpdates, firstLEDState; + bool firstFence, firstContinuousUpdates, firstLEDState, + firstQEMUKeyEvent; firstFence = !cp.supportsFence; firstContinuousUpdates = !cp.supportsContinuousUpdates; firstLEDState = !cp.supportsLEDState; + firstQEMUKeyEvent = !cp.supportsQEMUKeyEvent; cp.setEncodings(nEncodings, encodings); @@ -57,6 +59,8 @@ void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings) supportsContinuousUpdates(); if (cp.supportsLEDState && firstLEDState) supportsLEDState(); + if (cp.supportsQEMUKeyEvent && firstQEMUKeyEvent) + supportsQEMUKeyEvent(); } void SMsgHandler::supportsLocalCursor() @@ -75,6 +79,10 @@ void SMsgHandler::supportsLEDState() { } +void SMsgHandler::supportsQEMUKeyEvent() +{ +} + void SMsgHandler::setDesktopSize(int fb_width, int fb_height, const ScreenSet& layout) { diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h index cf6b6b3b..749f0560 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -80,6 +80,11 @@ namespace rfb { // server state. virtual void supportsLEDState(); + // supportsQEMUKeyEvent() is called the first time we detect that the + // client wants the QEMU Extended Key Event extension. The default + // handler will send a pseudo-rect back, signalling server support. + virtual void supportsQEMUKeyEvent(); + ConnParams cp; }; } diff --git a/common/rfb/SMsgReader.cxx b/common/rfb/SMsgReader.cxx index 3c08fd6f..cb71ac84 100644 --- a/common/rfb/SMsgReader.cxx +++ b/common/rfb/SMsgReader.cxx @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,9 @@ void SMsgReader::readMsg() case msgTypeClientCutText: readClientCutText(); break; + case msgTypeQEMUClientMessage: + readQEMUMessage(); + break; default: fprintf(stderr, "unknown message type %d\n", msgType); throw Exception("unknown message type"); @@ -184,7 +188,7 @@ void SMsgReader::readKeyEvent() bool down = is->readU8(); is->skip(2); rdr::U32 key = is->readU32(); - handler->keyEvent(key, down); + handler->keyEvent(key, 0, down); } void SMsgReader::readPointerEvent() @@ -214,3 +218,26 @@ void SMsgReader::readClientCutText() handler->clientCutText(ca.buf, len); } +void SMsgReader::readQEMUMessage() +{ + int subType = is->readU8(); + switch (subType) { + case qemuExtendedKeyEvent: + readQEMUKeyEvent(); + break; + default: + throw Exception("unknown QEMU submessage type %d", subType); + } +} + +void SMsgReader::readQEMUKeyEvent() +{ + bool down = is->readU16(); + rdr::U32 keysym = is->readU32(); + rdr::U32 keycode = is->readU32(); + if (!keycode) { + vlog.error("Key event without keycode - ignoring"); + return; + } + handler->keyEvent(keysym, keycode, down); +} diff --git a/common/rfb/SMsgReader.h b/common/rfb/SMsgReader.h index 00cb3031..146b29f8 100644 --- a/common/rfb/SMsgReader.h +++ b/common/rfb/SMsgReader.h @@ -55,6 +55,9 @@ namespace rfb { void readPointerEvent(); void readClientCutText(); + void readQEMUMessage(); + void readQEMUKeyEvent(); + SMsgHandler* handler; rdr::InStream* is; }; diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx index d8adfbc1..2d4998b3 100644 --- a/common/rfb/SMsgWriter.cxx +++ b/common/rfb/SMsgWriter.cxx @@ -39,7 +39,7 @@ SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_) needSetDesktopSize(false), needExtendedDesktopSize(false), needSetDesktopName(false), needSetCursor(false), needSetXCursor(false), needSetCursorWithAlpha(false), - needLEDState(false) + needLEDState(false), needQEMUKeyEvent(false) { } @@ -207,6 +207,16 @@ bool SMsgWriter::writeLEDState() return true; } +bool SMsgWriter::writeQEMUKeyEvent() +{ + if (!cp->supportsQEMUKeyEvent) + return false; + + needQEMUKeyEvent = true; + + return true; +} + bool SMsgWriter::needFakeUpdate() { if (needSetDesktopName) @@ -215,6 +225,8 @@ bool SMsgWriter::needFakeUpdate() return true; if (needLEDState) return true; + if (needQEMUKeyEvent) + return true; if (needNoDataUpdate()) return true; @@ -265,6 +277,8 @@ void SMsgWriter::writeFramebufferUpdateStart(int nRects) nRects++; if (needLEDState) nRects++; + if (needQEMUKeyEvent) + nRects++; } os->writeU16(nRects); @@ -385,6 +399,11 @@ void SMsgWriter::writePseudoRects() writeLEDStateRect(cp->ledState()); needLEDState = false; } + + if (needQEMUKeyEvent) { + writeQEMUKeyEventRect(); + needQEMUKeyEvent = false; + } } void SMsgWriter::writeNoDataRects() @@ -565,3 +584,17 @@ void SMsgWriter::writeLEDStateRect(rdr::U8 state) os->writeU32(pseudoEncodingLEDState); os->writeU8(state); } + +void SMsgWriter::writeQEMUKeyEventRect() +{ + if (!cp->supportsQEMUKeyEvent) + throw Exception("Client does not support QEMU extended key events"); + if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) + throw Exception("SMsgWriter::writeQEMUKeyEventRect: nRects out of sync"); + + os->writeS16(0); + os->writeS16(0); + os->writeU16(0); + os->writeU16(0); + os->writeU32(pseudoEncodingQEMUKeyEvent); +} diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h index 890b2b5b..f2adadca 100644 --- a/common/rfb/SMsgWriter.h +++ b/common/rfb/SMsgWriter.h @@ -85,6 +85,9 @@ namespace rfb { // Same for LED state message bool writeLEDState(); + // And QEMU keyboard event handshake + bool writeQEMUKeyEvent(); + // needFakeUpdate() returns true when an immediate update is needed in // order to flush out pseudo-rectangles to the client. bool needFakeUpdate(); @@ -135,6 +138,7 @@ namespace rfb { int hotspotX, int hotspotY, const rdr::U8* data); void writeLEDStateRect(rdr::U8 state); + void writeQEMUKeyEventRect(); ConnParams* cp; rdr::OutStream* os; @@ -150,6 +154,7 @@ namespace rfb { bool needSetXCursor; bool needSetCursorWithAlpha; bool needLEDState; + bool needQEMUKeyEvent; typedef struct { rdr::U16 reason, result; diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 232776fc..be496e73 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -103,7 +103,7 @@ VNCSConnectionST::~VNCSConnectionST() std::set::iterator i; for (i=pressedKeys.begin(); i!=pressedKeys.end(); i++) { vlog.debug("Releasing key 0x%x on client disconnect", *i); - server->desktop->keyEvent(*i, false); + server->desktop->keyEvent(*i, 0, false); } if (server->pointerClient == this) server->pointerClient = 0; @@ -538,12 +538,12 @@ public: ~VNCSConnectionSTShiftPresser() { if (pressed) { vlog.debug("Releasing fake Shift_L"); - desktop->keyEvent(XK_Shift_L, false); + desktop->keyEvent(XK_Shift_L, 0, false); } } void press() { vlog.debug("Pressing fake Shift_L"); - desktop->keyEvent(XK_Shift_L, true); + desktop->keyEvent(XK_Shift_L, 0, true); pressed = true; } SDesktop* desktop; @@ -552,32 +552,32 @@ public: // keyEvent() - record in the pressedKeys which keys were pressed. Allow // multiple down events (for autorepeat), but only allow a single up event. -void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) { +void VNCSConnectionST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) { lastEventTime = time(0); server->lastUserInputTime = lastEventTime; if (!(accessRights & AccessKeyEvents)) return; if (!rfb::Server::acceptKeyEvents) return; if (down) - vlog.debug("Key pressed: 0x%x", key); + vlog.debug("Key pressed: 0x%x / 0x%x", keysym, keycode); else - vlog.debug("Key released: 0x%x", key); + vlog.debug("Key released: 0x%x / 0x%x", keysym, keycode); // Remap the key if required if (server->keyRemapper) { rdr::U32 newkey; - newkey = server->keyRemapper->remapKey(key); - if (newkey != key) { + newkey = server->keyRemapper->remapKey(keysym); + if (newkey != keysym) { vlog.debug("Key remapped to 0x%x", newkey); - key = newkey; + keysym = newkey; } } // Avoid lock keys if we don't know the server state if ((server->ledState == ledUnknown) && - ((key == XK_Caps_Lock) || - (key == XK_Num_Lock) || - (key == XK_Scroll_Lock))) { + ((keysym == XK_Caps_Lock) || + (keysym == XK_Num_Lock) || + (keysym == XK_Scroll_Lock))) { vlog.debug("Ignoring lock key (e.g. caps lock)"); return; } @@ -587,7 +587,7 @@ void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) { if (!cp.supportsLEDState) { // Always ignore ScrollLock as we don't have a heuristic // for that - if (key == XK_Scroll_Lock) { + if (keysym == XK_Scroll_Lock) { vlog.debug("Ignoring lock key (e.g. caps lock)"); return; } @@ -596,32 +596,32 @@ void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) { // CapsLock synchronisation heuristic // (this assumes standard interaction between CapsLock the Shift // keys and normal characters) - if (((key >= XK_A) && (key <= XK_Z)) || - ((key >= XK_a) && (key <= XK_z))) { + if (((keysym >= XK_A) && (keysym <= XK_Z)) || + ((keysym >= XK_a) && (keysym <= XK_z))) { bool uppercase, shift, lock; - uppercase = (key >= XK_A) && (key <= XK_Z); + uppercase = (keysym >= XK_A) && (keysym <= XK_Z); shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() || pressedKeys.find(XK_Shift_R) != pressedKeys.end(); lock = server->ledState & ledCapsLock; if (lock == (uppercase == shift)) { vlog.debug("Inserting fake CapsLock to get in sync with client"); - server->desktop->keyEvent(XK_Caps_Lock, true); - server->desktop->keyEvent(XK_Caps_Lock, false); + server->desktop->keyEvent(XK_Caps_Lock, 0, true); + server->desktop->keyEvent(XK_Caps_Lock, 0, false); } } // NumLock synchronisation heuristic // (this is more cautious because of the differences between Unix, // Windows and macOS) - if (((key >= XK_KP_Home) && (key <= XK_KP_Delete)) || - ((key >= XK_KP_0) && (key <= XK_KP_9)) || - (key == XK_KP_Separator) || (key == XK_KP_Decimal)) { + if (((keysym >= XK_KP_Home) && (keysym <= XK_KP_Delete)) || + ((keysym >= XK_KP_0) && (keysym <= XK_KP_9)) || + (keysym == XK_KP_Separator) || (keysym == XK_KP_Decimal)) { bool number, shift, lock; - number = ((key >= XK_KP_0) && (key <= XK_KP_9)) || - (key == XK_KP_Separator) || (key == XK_KP_Decimal); + number = ((keysym >= XK_KP_0) && (keysym <= XK_KP_9)) || + (keysym == XK_KP_Separator) || (keysym == XK_KP_Decimal); shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() || pressedKeys.find(XK_Shift_R) != pressedKeys.end(); lock = server->ledState & ledNumLock; @@ -638,8 +638,8 @@ void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) { // } else if (lock == (number == shift)) { vlog.debug("Inserting fake NumLock to get in sync with client"); - server->desktop->keyEvent(XK_Num_Lock, true); - server->desktop->keyEvent(XK_Num_Lock, false); + server->desktop->keyEvent(XK_Num_Lock, 0, true); + server->desktop->keyEvent(XK_Num_Lock, 0, false); } } } @@ -647,19 +647,20 @@ void VNCSConnectionST::keyEvent(rdr::U32 key, bool down) { // Turn ISO_Left_Tab into shifted Tab. VNCSConnectionSTShiftPresser shiftPresser(server->desktop); - if (key == XK_ISO_Left_Tab) { + if (keysym == XK_ISO_Left_Tab) { if (pressedKeys.find(XK_Shift_L) == pressedKeys.end() && pressedKeys.find(XK_Shift_R) == pressedKeys.end()) shiftPresser.press(); - key = XK_Tab; + keysym = XK_Tab; } if (down) { - pressedKeys.insert(key); + pressedKeys.insert(keysym); } else { - if (!pressedKeys.erase(key)) return; + if (!pressedKeys.erase(keysym)) + return; } - server->desktop->keyEvent(key, down); + server->desktop->keyEvent(keysym, keycode, down); } void VNCSConnectionST::clientCutText(const char* str, int len) diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index 8f33962d..9c58331e 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -136,7 +136,7 @@ namespace rfb { virtual void clientInit(bool shared); virtual void setPixelFormat(const PixelFormat& pf); virtual void pointerEvent(const Point& pos, int buttonMask); - virtual void keyEvent(rdr::U32 key, bool down); + virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); virtual void clientCutText(const char* str, int len); virtual void framebufferUpdateRequest(const Rect& r, bool incremental); virtual void setDesktopSize(int fb_width, int fb_height, diff --git a/common/rfb/encodings.h b/common/rfb/encodings.h index adeecaa9..122afe7f 100644 --- a/common/rfb/encodings.h +++ b/common/rfb/encodings.h @@ -40,6 +40,7 @@ namespace rfb { const int pseudoEncodingFence = -312; const int pseudoEncodingContinuousUpdates = -313; const int pseudoEncodingCursorWithAlpha = -314; + const int pseudoEncodingQEMUKeyEvent = -258; // TightVNC-specific const int pseudoEncodingLastRect = -224; diff --git a/common/rfb/msgTypes.h b/common/rfb/msgTypes.h index a55e1c50..a17493cd 100644 --- a/common/rfb/msgTypes.h +++ b/common/rfb/msgTypes.h @@ -45,5 +45,7 @@ namespace rfb { const int msgTypeClientFence = 248; const int msgTypeSetDesktopSize = 251; + + const int msgTypeQEMUClientMessage = 255; } #endif diff --git a/common/rfb/qemuTypes.h b/common/rfb/qemuTypes.h new file mode 100644 index 00000000..6a67f781 --- /dev/null +++ b/common/rfb/qemuTypes.h @@ -0,0 +1,25 @@ +/* Copyright 2017 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +#ifndef __RFB_QEMUTYPES_H__ +#define __RFB_QEMUTYPES_H__ + +namespace rfb { + const int qemuExtendedKeyEvent = 0; + const int qemuAudio = 1; +} +#endif diff --git a/unix/x0vncserver/x0vncserver.cxx b/unix/x0vncserver/x0vncserver.cxx index 9e5da4f9..7fb197aa 100644 --- a/unix/x0vncserver/x0vncserver.cxx +++ b/unix/x0vncserver/x0vncserver.cxx @@ -295,10 +295,10 @@ public: #endif } - virtual void keyEvent(rdr::U32 key, bool down) { + virtual void keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) { #ifdef HAVE_XTEST if (!haveXtest) return; - int keycode = XKeysymToKeycode(dpy, key); + int keycode = XKeysymToKeycode(dpy, keysym); if (keycode) XTestFakeKeyEvent(dpy, keycode, down, CurrentTime); #endif diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc index 1c74bc66..031fb535 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.cc +++ b/unix/xserver/hw/vnc/XserverDesktop.cc @@ -771,7 +771,7 @@ void XserverDesktop::grabRegion(const rfb::Region& region) } } -void XserverDesktop::keyEvent(rdr::U32 keysym, bool down) +void XserverDesktop::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) { vncKeyboardEvent(keysym, down); } diff --git a/unix/xserver/hw/vnc/XserverDesktop.h b/unix/xserver/hw/vnc/XserverDesktop.h index cd85e4b0..7f7823a7 100644 --- a/unix/xserver/hw/vnc/XserverDesktop.h +++ b/unix/xserver/hw/vnc/XserverDesktop.h @@ -89,7 +89,7 @@ public: // rfb::SDesktop callbacks virtual void pointerEvent(const rfb::Point& pos, int buttonMask); - virtual void keyEvent(rdr::U32 key, bool down); + virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); virtual void clientCutText(const char* str, int len); virtual rfb::Point getFbSize() { return rfb::Point(width(), height()); } virtual unsigned int setScreenLayout(int fb_width, int fb_height, diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index bfadd4c0..1923f53a 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -709,8 +709,8 @@ void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym) if (ctrlPressed && altPressed) { vlog.debug("Faking release of AltGr (Ctrl_L+Alt_R)"); try { - cc->writer()->keyEvent(XK_Control_L, false); - cc->writer()->keyEvent(XK_Alt_R, false); + cc->writer()->keyEvent(XK_Control_L, 0, false); + cc->writer()->keyEvent(XK_Alt_R, 0, false); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); exit_vncviewer(e.str()); @@ -732,7 +732,7 @@ void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym) #endif try { - cc->writer()->keyEvent(keySym, true); + cc->writer()->keyEvent(keySym, 0, true); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); exit_vncviewer(e.str()); @@ -743,8 +743,8 @@ void Viewport::handleKeyPress(int keyCode, rdr::U32 keySym) if (ctrlPressed && altPressed) { vlog.debug("Restoring AltGr state"); try { - cc->writer()->keyEvent(XK_Control_L, true); - cc->writer()->keyEvent(XK_Alt_R, true); + cc->writer()->keyEvent(XK_Control_L, 0, true); + cc->writer()->keyEvent(XK_Alt_R, 0, true); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); exit_vncviewer(e.str()); @@ -777,7 +777,7 @@ void Viewport::handleKeyRelease(int keyCode) #endif try { - cc->writer()->keyEvent(iter->second, false); + cc->writer()->keyEvent(iter->second, 0, false); } catch (rdr::Exception& e) { vlog.error("%s", e.str()); exit_vncviewer(e.str()); diff --git a/win/rfb_win32/SDisplay.cxx b/win/rfb_win32/SDisplay.cxx index 2696f5dc..ad55d490 100644 --- a/win/rfb_win32/SDisplay.cxx +++ b/win/rfb_win32/SDisplay.cxx @@ -280,12 +280,12 @@ void SDisplay::pointerEvent(const Point& pos, int buttonmask) { } } -void SDisplay::keyEvent(rdr::U32 key, bool down) { +void SDisplay::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) { // - Check that the SDesktop doesn't need restarting if (isRestartRequired()) restartCore(); if (kbd) - kbd->keyEvent(key, down); + kbd->keyEvent(keysym, keycode, down); } bool SDisplay::checkLedState() { diff --git a/win/rfb_win32/SDisplay.h b/win/rfb_win32/SDisplay.h index e43e3021..9892ed99 100644 --- a/win/rfb_win32/SDisplay.h +++ b/win/rfb_win32/SDisplay.h @@ -66,7 +66,7 @@ namespace rfb { virtual void start(VNCServer* vs); virtual void stop(); virtual void pointerEvent(const Point& pos, int buttonmask); - virtual void keyEvent(rdr::U32 key, bool down); + virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); virtual void clientCutText(const char* str, int len); virtual Point getFbSize(); diff --git a/win/rfb_win32/SInput.cxx b/win/rfb_win32/SInput.cxx index 0923118a..15ef4b06 100644 --- a/win/rfb_win32/SInput.cxx +++ b/win/rfb_win32/SInput.cxx @@ -321,7 +321,7 @@ win32::SKeyboard::SKeyboard() } -void win32::SKeyboard::keyEvent(rdr::U32 keysym, bool down) +void win32::SKeyboard::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) { for (unsigned int i = 0; i < sizeof(keysymToAscii) / sizeof(keysymToAscii_t); i++) { if (keysymToAscii[i].keysym == keysym) { diff --git a/win/rfb_win32/SInput.h b/win/rfb_win32/SInput.h index 2a0b3e67..f7949ec4 100644 --- a/win/rfb_win32/SInput.h +++ b/win/rfb_win32/SInput.h @@ -53,7 +53,7 @@ namespace rfb { class SKeyboard { public: SKeyboard(); - void keyEvent(rdr::U32 key, bool down); + void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); static BoolParameter deadKeyAware; private: std::map vkMap; -- cgit v1.2.3 From a00a87db4f73ee82386269759c0d244dfdb395f2 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 16 May 2017 14:32:09 +0200 Subject: Fix wrong argument for CMsgWriter::clientCutText() As a result we weren't overloading properly. --- common/rfb/CMsgWriter.cxx | 2 +- common/rfb/CMsgWriter.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'common') diff --git a/common/rfb/CMsgWriter.cxx b/common/rfb/CMsgWriter.cxx index 57d1283a..84a0d1f1 100644 --- a/common/rfb/CMsgWriter.cxx +++ b/common/rfb/CMsgWriter.cxx @@ -256,7 +256,7 @@ void CMsgWriter::pointerEvent(const Point& pos, int buttonMask) } -void CMsgWriter::clientCutText(const char* str, rdr::U32 len) +void CMsgWriter::clientCutText(const char* str, int len) { startMsg(msgTypeClientCutText); os->pad(3); diff --git a/common/rfb/CMsgWriter.h b/common/rfb/CMsgWriter.h index 56e0b7b9..b1f01195 100644 --- a/common/rfb/CMsgWriter.h +++ b/common/rfb/CMsgWriter.h @@ -57,7 +57,7 @@ namespace rfb { virtual void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down); virtual void pointerEvent(const Point& pos, int buttonMask); - virtual void clientCutText(const char* str, rdr::U32 len); + virtual void clientCutText(const char* str, int len); protected: void startMsg(int type); -- cgit v1.2.3 From bdbad2aff959ae392714c18f4ce72f55c4ba6813 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 16 May 2017 14:32:58 +0200 Subject: Remove unused needsLastRect state variable --- common/rfb/SMsgWriter.h | 1 - 1 file changed, 1 deletion(-) (limited to 'common') diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h index f2adadca..7660b118 100644 --- a/common/rfb/SMsgWriter.h +++ b/common/rfb/SMsgWriter.h @@ -149,7 +149,6 @@ namespace rfb { bool needSetDesktopSize; bool needExtendedDesktopSize; bool needSetDesktopName; - bool needLastRect; bool needSetCursor; bool needSetXCursor; bool needSetCursorWithAlpha; -- cgit v1.2.3 From 16e1dcb85cbfe467e38ec053060a7e4fc908795e Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 16 May 2017 14:33:43 +0200 Subject: Track keys based on client supplied key codes This makes it easier to provide more sane events to the backend code even with crazy clients. --- common/rfb/VNCSConnectionST.cxx | 50 ++++++++++++++++++++++++++++++++++------- common/rfb/VNCSConnectionST.h | 4 ++-- 2 files changed, 44 insertions(+), 10 deletions(-) (limited to 'common') diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index be496e73..674c3922 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -100,11 +100,18 @@ VNCSConnectionST::~VNCSConnectionST() (closeReason.buf) ? closeReason.buf : ""); // Release any keys the client still had pressed - std::set::iterator i; - for (i=pressedKeys.begin(); i!=pressedKeys.end(); i++) { - vlog.debug("Releasing key 0x%x on client disconnect", *i); - server->desktop->keyEvent(*i, 0, false); + while (!pressedKeys.empty()) { + rdr::U32 keysym, keycode; + + keysym = pressedKeys.begin()->second; + keycode = pressedKeys.begin()->first; + pressedKeys.erase(pressedKeys.begin()); + + vlog.debug("Releasing key 0x%x / 0x%x on client disconnect", + keysym, keycode); + server->desktop->keyEvent(keysym, keycode, false); } + if (server->pointerClient == this) server->pointerClient = 0; @@ -553,6 +560,8 @@ public: // keyEvent() - record in the pressedKeys which keys were pressed. Allow // multiple down events (for autorepeat), but only allow a single up event. void VNCSConnectionST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) { + rdr::U32 lookup; + lastEventTime = time(0); server->lastUserInputTime = lastEventTime; if (!(accessRights & AccessKeyEvents)) return; @@ -648,18 +657,43 @@ void VNCSConnectionST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) { // Turn ISO_Left_Tab into shifted Tab. VNCSConnectionSTShiftPresser shiftPresser(server->desktop); if (keysym == XK_ISO_Left_Tab) { - if (pressedKeys.find(XK_Shift_L) == pressedKeys.end() && - pressedKeys.find(XK_Shift_R) == pressedKeys.end()) + std::map::const_iterator iter; + bool shifted; + + shifted = false; + for (iter = pressedKeys.begin(); iter != pressedKeys.end(); ++iter) { + if ((iter->second == XK_Shift_L) || + (iter->second == XK_Shift_R)) { + shifted = true; + break; + } + } + + if (!shifted) shiftPresser.press(); + keysym = XK_Tab; } + // We need to be able to track keys, so generate a fake index when we + // aren't given a keycode + if (keycode == 0) + lookup = 0x80000000 | keysym; + else + lookup = keycode; + + // We force the same keysym for an already down key for the + // sake of sanity + if (pressedKeys.find(lookup) != pressedKeys.end()) + keysym = pressedKeys[lookup]; + if (down) { - pressedKeys.insert(keysym); + pressedKeys[lookup] = keysym; } else { - if (!pressedKeys.erase(keysym)) + if (!pressedKeys.erase(lookup)) return; } + server->desktop->keyEvent(keysym, keycode, down); } diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index 9c58331e..d3bec93f 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -27,7 +27,7 @@ #ifndef __RFB_VNCSCONNECTIONST_H__ #define __RFB_VNCSCONNECTIONST_H__ -#include +#include #include #include #include @@ -210,7 +210,7 @@ namespace rfb { Region cuRegion; EncodeManager encodeManager; - std::set pressedKeys; + std::map pressedKeys; time_t lastEventTime; time_t pointerEventTime; -- cgit v1.2.3