diff options
author | Pierre Ossman <ossman@cendio.se> | 2017-09-15 11:03:48 +0200 |
---|---|---|
committer | Pierre Ossman <ossman@cendio.se> | 2017-09-15 11:03:48 +0200 |
commit | 24e83052d28d464e7f3f12aa800736bcdd16874a (patch) | |
tree | a09f3373566e759131255ac5f8648f90c37d6544 /common | |
parent | 71ca8d546baaf13cca282c840b6521a98078fb42 (diff) | |
parent | 0c15866f0c86402016e55eeb7c044eb2e6cd95f5 (diff) | |
download | tigervnc-24e83052d28d464e7f3f12aa800736bcdd16874a.tar.gz tigervnc-24e83052d28d464e7f3f12aa800736bcdd16874a.zip |
Merge branch 'qemukbd-merge' of https://github.com/CendioOssman/tigervnc
Diffstat (limited to 'common')
-rw-r--r-- | common/rfb/CMsgHandler.cxx | 10 | ||||
-rw-r--r-- | common/rfb/CMsgHandler.h | 3 | ||||
-rw-r--r-- | common/rfb/CMsgReader.cxx | 14 | ||||
-rw-r--r-- | common/rfb/CMsgReader.h | 1 | ||||
-rw-r--r-- | common/rfb/CMsgWriter.cxx | 31 | ||||
-rw-r--r-- | common/rfb/CMsgWriter.h | 4 | ||||
-rw-r--r-- | common/rfb/ConnParams.cxx | 16 | ||||
-rw-r--r-- | common/rfb/ConnParams.h | 6 | ||||
-rw-r--r-- | common/rfb/InputHandler.h | 2 | ||||
-rw-r--r-- | common/rfb/SConnection.cxx | 5 | ||||
-rw-r--r-- | common/rfb/SConnection.h | 1 | ||||
-rw-r--r-- | common/rfb/SMsgHandler.cxx | 17 | ||||
-rw-r--r-- | common/rfb/SMsgHandler.h | 11 | ||||
-rw-r--r-- | common/rfb/SMsgReader.cxx | 29 | ||||
-rw-r--r-- | common/rfb/SMsgReader.h | 3 | ||||
-rw-r--r-- | common/rfb/SMsgWriter.cxx | 75 | ||||
-rw-r--r-- | common/rfb/SMsgWriter.h | 11 | ||||
-rw-r--r-- | common/rfb/VNCSConnectionST.cxx | 178 | ||||
-rw-r--r-- | common/rfb/VNCSConnectionST.h | 9 | ||||
-rw-r--r-- | common/rfb/VNCServer.h | 4 | ||||
-rw-r--r-- | common/rfb/VNCServerST.cxx | 18 | ||||
-rw-r--r-- | common/rfb/VNCServerST.h | 2 | ||||
-rw-r--r-- | common/rfb/encodings.h | 2 | ||||
-rw-r--r-- | common/rfb/ledStates.h | 30 | ||||
-rw-r--r-- | common/rfb/msgTypes.h | 2 | ||||
-rw-r--r-- | common/rfb/qemuTypes.h | 25 |
26 files changed, 471 insertions, 38 deletions
diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx index 11c979a3..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() { } @@ -82,3 +87,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..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, @@ -69,6 +70,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..eee6d277 100644 --- a/common/rfb/CMsgReader.cxx +++ b/common/rfb/CMsgReader.cxx @@ -109,6 +109,11 @@ void CMsgReader::readMsg() case pseudoEncodingExtendedDesktopSize: readExtendedDesktopSize(x, y, w, h); break; + case pseudoEncodingLEDState: + readLEDState(); + case pseudoEncodingQEMUKeyEvent: + handler->supportsQEMUKeyEvent(); + break; default: readRect(Rect(x, y, x+w, y+h), encoding); break; @@ -382,3 +387,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..84a0d1f1 100644 --- a/common/rfb/CMsgWriter.cxx +++ b/common/rfb/CMsgWriter.cxx @@ -21,6 +21,7 @@ #include <rfb/msgTypes.h> #include <rfb/fenceTypes.h> #include <rfb/encodings.h> +#include <rfb/qemuTypes.h> #include <rfb/Exception.h> #include <rfb/PixelFormat.h> #include <rfb/Rect.h> @@ -82,10 +83,13 @@ 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; encodings[nEncodings++] = pseudoEncodingFence; + encodings[nEncodings++] = pseudoEncodingQEMUKeyEvent; if (Decoder::supported(preferredEncoding)) { encodings[nEncodings++] = preferredEncoding; @@ -213,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(); + } } @@ -239,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 06ecbe7d..b1f01195 100644 --- a/common/rfb/CMsgWriter.h +++ b/common/rfb/CMsgWriter.h @@ -55,9 +55,9 @@ 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); + virtual void clientCutText(const char* str, int len); protected: void startMsg(int type); diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx index 9ee1d9c0..23f02ed0 100644 --- a/common/rfb/ConnParams.cxx +++ b/common/rfb/ConnParams.cxx @@ -22,6 +22,7 @@ #include <rdr/OutStream.h> #include <rfb/Exception.h> #include <rfb/encodings.h> +#include <rfb/ledStates.h> #include <rfb/ConnParams.h> #include <rfb/util.h> @@ -34,10 +35,12 @@ ConnParams::ConnParams() supportsLocalCursorWithAlpha(false), supportsDesktopResize(false), supportsExtendedDesktopSize(false), supportsDesktopRename(false), supportsLastRect(false), + supportsLEDState(false), supportsQEMUKeyEvent(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); @@ -107,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; @@ -141,6 +145,11 @@ void ConnParams::setEncodings(int nEncodings, const rdr::S32* encodings) case pseudoEncodingLastRect: supportsLastRect = true; break; + case pseudoEncodingLEDState: + supportsLEDState = true; + case pseudoEncodingQEMUKeyEvent: + supportsQEMUKeyEvent = true; + break; case pseudoEncodingFence: supportsFence = true; break; @@ -183,3 +192,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..b3222936 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,8 @@ namespace rfb { bool supportsExtendedDesktopSize; bool supportsDesktopRename; bool supportsLastRect; + bool supportsLEDState; + bool supportsQEMUKeyEvent; bool supportsSetDesktopSize; bool supportsFence; @@ -111,6 +116,7 @@ namespace rfb { std::set<rdr::S32> encodings_; char verStr[13]; int verStrPos; + unsigned int ledState_; }; } #endif 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 388b21f8..c38458c3 100644 --- a/common/rfb/SMsgHandler.cxx +++ b/common/rfb/SMsgHandler.cxx @@ -41,10 +41,13 @@ void SMsgHandler::setPixelFormat(const PixelFormat& pf) void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings) { - bool firstFence, firstContinuousUpdates; + bool firstFence, firstContinuousUpdates, firstLEDState, + firstQEMUKeyEvent; firstFence = !cp.supportsFence; firstContinuousUpdates = !cp.supportsContinuousUpdates; + firstLEDState = !cp.supportsLEDState; + firstQEMUKeyEvent = !cp.supportsQEMUKeyEvent; cp.setEncodings(nEncodings, encodings); @@ -54,6 +57,10 @@ void SMsgHandler::setEncodings(int nEncodings, const rdr::S32* encodings) supportsFence(); if (cp.supportsContinuousUpdates && firstContinuousUpdates) supportsContinuousUpdates(); + if (cp.supportsLEDState && firstLEDState) + supportsLEDState(); + if (cp.supportsQEMUKeyEvent && firstQEMUKeyEvent) + supportsQEMUKeyEvent(); } void SMsgHandler::supportsLocalCursor() @@ -68,6 +75,14 @@ void SMsgHandler::supportsContinuousUpdates() { } +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 74509e0b..749f0560 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -74,6 +74,17 @@ 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(); + + // 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 <stdio.h> #include <rdr/InStream.h> #include <rfb/msgTypes.h> +#include <rfb/qemuTypes.h> #include <rfb/Exception.h> #include <rfb/util.h> #include <rfb/SMsgHandler.h> @@ -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 bc3f4398..2d4998b3 100644 --- a/common/rfb/SMsgWriter.cxx +++ b/common/rfb/SMsgWriter.cxx @@ -27,6 +27,7 @@ #include <rfb/Encoder.h> #include <rfb/SMsgWriter.h> #include <rfb/LogWriter.h> +#include <rfb/ledStates.h> 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), needQEMUKeyEvent(false) { } @@ -193,12 +195,38 @@ 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::writeQEMUKeyEvent() +{ + if (!cp->supportsQEMUKeyEvent) + return false; + + needQEMUKeyEvent = true; + + return true; +} + bool SMsgWriter::needFakeUpdate() { if (needSetDesktopName) return true; if (needSetCursor || needSetXCursor || needSetCursorWithAlpha) return true; + if (needLEDState) + return true; + if (needQEMUKeyEvent) + return true; if (needNoDataUpdate()) return true; @@ -247,6 +275,10 @@ void SMsgWriter::writeFramebufferUpdateStart(int nRects) nRects++; if (needSetCursorWithAlpha) nRects++; + if (needLEDState) + nRects++; + if (needQEMUKeyEvent) + nRects++; } os->writeU16(nRects); @@ -362,6 +394,16 @@ void SMsgWriter::writePseudoRects() writeSetDesktopNameRect(cp->name()); needSetDesktopName = false; } + + if (needLEDState) { + writeLEDStateRect(cp->ledState()); + needLEDState = false; + } + + if (needQEMUKeyEvent) { + writeQEMUKeyEventRect(); + needQEMUKeyEvent = false; + } } void SMsgWriter::writeNoDataRects() @@ -525,3 +567,34 @@ 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); +} + +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 548b8e8e..7660b118 100644 --- a/common/rfb/SMsgWriter.h +++ b/common/rfb/SMsgWriter.h @@ -82,6 +82,12 @@ namespace rfb { bool writeSetXCursor(); bool writeSetCursorWithAlpha(); + // 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(); @@ -131,6 +137,8 @@ namespace rfb { void writeSetCursorWithAlphaRect(int width, int height, int hotspotX, int hotspotY, const rdr::U8* data); + void writeLEDStateRect(rdr::U8 state); + void writeQEMUKeyEventRect(); ConnParams* cp; rdr::OutStream* os; @@ -141,10 +149,11 @@ namespace rfb { bool needSetDesktopSize; bool needExtendedDesktopSize; bool needSetDesktopName; - bool needLastRect; bool needSetCursor; 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 c92bdb46..cf37fddd 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -34,10 +34,12 @@ #include <rfb/Security.h> #include <rfb/screenTypes.h> #include <rfb/fenceTypes.h> +#include <rfb/ledStates.h> #include <rfb/ServerCore.h> #include <rfb/ComparingUpdateTracker.h> #include <rfb/KeyRemapper.h> #include <rfb/Encoder.h> +#define XK_LATIN1 #define XK_MISCELLANY #define XK_XKB_KEYS #include <rfb/keysymdef.h> @@ -100,11 +102,18 @@ VNCSConnectionST::~VNCSConnectionST() (closeReason.buf) ? closeReason.buf : ""); // Release any keys the client still had pressed - std::set<rdr::U32>::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); + 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; @@ -313,6 +322,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; @@ -421,6 +440,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()); @@ -530,12 +550,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; @@ -544,42 +564,142 @@ 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) { + rdr::U32 lookup; + 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) && + ((keysym == XK_Caps_Lock) || + (keysym == XK_Num_Lock) || + (keysym == XK_Scroll_Lock))) { + vlog.debug("Ignoring lock key (e.g. caps lock)"); + return; + } + + // 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 (keysym == 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 (((keysym >= XK_A) && (keysym <= XK_Z)) || + ((keysym >= XK_a) && (keysym <= XK_z))) { + bool uppercase, shift, lock; + + 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, 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 (((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 = ((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; + + 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, 0, true); + server->desktop->keyEvent(XK_Num_Lock, 0, false); + } + } } } // Turn ISO_Left_Tab into shifted Tab. VNCSConnectionSTShiftPresser shiftPresser(server->desktop); - if (key == XK_ISO_Left_Tab) { - if (pressedKeys.find(XK_Shift_L) == pressedKeys.end() && - pressedKeys.find(XK_Shift_R) == pressedKeys.end()) + if (keysym == XK_ISO_Left_Tab) { + std::map<rdr::U32, rdr::U32>::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(); - key = XK_Tab; + + 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(key); + pressedKeys[lookup] = keysym; } else { - if (!pressedKeys.erase(key)) return; + if (!pressedKeys.erase(lookup)) + return; } - server->desktop->keyEvent(key, down); + + server->desktop->keyEvent(keysym, keycode, down); } void VNCSConnectionST::clientCutText(const char* str, int len) @@ -754,6 +874,11 @@ void VNCSConnectionST::supportsContinuousUpdates() writer()->writeEndOfContinuousUpdates(); } +void VNCSConnectionST::supportsLEDState() +{ + writer()->writeLEDState(); +} + bool VNCSConnectionST::handleTimeout(Timer* t) { @@ -1170,6 +1295,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..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 <set> +#include <map> #include <rfb/SConnection.h> #include <rfb/SMsgWriter.h> #include <rfb/VNCServerST.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 @@ -135,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, @@ -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; @@ -207,7 +210,7 @@ namespace rfb { Region cuRegion; EncodeManager encodeManager; - std::set<rdr::U32> pressedKeys; + std::map<rdr::U32, rdr::U32> pressedKeys; time_t lastEventTime; time_t pointerEventTime; 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..43e8f3ed 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -58,6 +58,7 @@ #include <rfb/Security.h> #include <rfb/KeyRemapper.h> #include <rfb/util.h> +#include <rfb/ledStates.h> #include <rdr/types.h> @@ -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,21 @@ void VNCServerST::setCursorPos(const Point& pos) } } +void VNCServerST::setLEDState(unsigned int state) +{ + std::list<VNCSConnectionST*>::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 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/common/rfb/encodings.h b/common/rfb/encodings.h index a65d863b..122afe7f 100644 --- a/common/rfb/encodings.h +++ b/common/rfb/encodings.h @@ -34,11 +34,13 @@ 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; const int pseudoEncodingContinuousUpdates = -313; const int pseudoEncodingCursorWithAlpha = -314; + const int pseudoEncodingQEMUKeyEvent = -258; // TightVNC-specific const int pseudoEncodingLastRect = -224; 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 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 |