From 2fa63f8576e5d1c632efeeb2c185f11e943899d8 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 5 Dec 2016 15:26:21 +0100 Subject: [PATCH] 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 +++++++ tests/CMakeLists.txt | 4 +- vncviewer/CConn.cxx | 9 ++ vncviewer/CConn.h | 2 + vncviewer/CMakeLists.txt | 4 +- vncviewer/DesktopWindow.cxx | 6 ++ vncviewer/DesktopWindow.h | 3 + vncviewer/Viewport.cxx | 172 ++++++++++++++++++++++++++++++++++++ vncviewer/Viewport.h | 5 ++ vncviewer/cocoa.h | 3 + vncviewer/cocoa.mm | 50 +++++++++++ 19 files changed, 327 insertions(+), 5 deletions(-) create mode 100644 common/rfb/ledStates.h 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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 60edb013..7e006815 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,5 +40,7 @@ if(WIN32) target_link_libraries(fbperf msimg32) endif() if(APPLE) - target_link_libraries(fbperf "-framework Cocoa" "-framework Carbon") + target_link_libraries(fbperf "-framework Cocoa") + target_link_libraries(fbperf "-framework Carbon") + target_link_libraries(fbperf "-framework IOKit") endif() diff --git a/vncviewer/CConn.cxx b/vncviewer/CConn.cxx index 2e97ec28..07e7841c 100644 --- a/vncviewer/CConn.cxx +++ b/vncviewer/CConn.cxx @@ -92,6 +92,8 @@ CConn::CConn(const char* vncServerName, network::Socket* socket=NULL) cp.supportsExtendedDesktopSize = true; cp.supportsDesktopRename = true; + cp.supportsLEDState = true; + if (customCompressLevel) cp.compressLevel = compressLevel; else @@ -503,6 +505,13 @@ void CConn::fence(rdr::U32 flags, unsigned len, const char data[]) } } +void CConn::setLEDState(unsigned int state) +{ + CConnection::setLEDState(state); + + desktop->setLEDState(state); +} + ////////////////////// Internal methods ////////////////////// diff --git a/vncviewer/CConn.h b/vncviewer/CConn.h index 93cc278f..426bd1e2 100644 --- a/vncviewer/CConn.h +++ b/vncviewer/CConn.h @@ -74,6 +74,8 @@ public: void fence(rdr::U32 flags, unsigned len, const char data[]); + void setLEDState(unsigned int state); + private: void resizeFramebuffer(); diff --git a/vncviewer/CMakeLists.txt b/vncviewer/CMakeLists.txt index 2aecda23..56313e93 100644 --- a/vncviewer/CMakeLists.txt +++ b/vncviewer/CMakeLists.txt @@ -53,7 +53,9 @@ if(WIN32) endif() if(APPLE) - target_link_libraries(vncviewer "-framework Cocoa" "-framework Carbon") + target_link_libraries(vncviewer "-framework Cocoa") + target_link_libraries(vncviewer "-framework Carbon") + target_link_libraries(vncviewer "-framework IOKit") endif() install(TARGETS vncviewer DESTINATION ${BIN_DIR}) diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx index 408efd19..3973cd66 100644 --- a/vncviewer/DesktopWindow.cxx +++ b/vncviewer/DesktopWindow.cxx @@ -395,6 +395,12 @@ void DesktopWindow::draw() } +void DesktopWindow::setLEDState(unsigned int state) +{ + viewport->setLEDState(state); +} + + void DesktopWindow::resize(int x, int y, int w, int h) { bool resizing; diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h index 4224699c..f1bf312f 100644 --- a/vncviewer/DesktopWindow.h +++ b/vncviewer/DesktopWindow.h @@ -66,6 +66,9 @@ public: void setCursor(int width, int height, const rfb::Point& hotspot, const rdr::U8* data); + // Change client LED state + void setLEDState(unsigned int state); + // Fl_Window callback methods void draw(); void resize(int x, int y, int w, int h); diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx index 6a23526a..c0bbd0dd 100644 --- a/vncviewer/Viewport.cxx +++ b/vncviewer/Viewport.cxx @@ -28,6 +28,7 @@ #include #include #include +#include // FLTK can pull in the X11 headers on some systems #ifndef XK_VoidSymbol @@ -41,6 +42,10 @@ #include #endif +#if ! (defined(WIN32) || defined(__APPLE__)) +#include +#endif + #ifndef NoSymbol #define NoSymbol 0 #endif @@ -92,6 +97,11 @@ enum { ID_EXIT, ID_FULLSCREEN, ID_MINIMIZE, ID_RESIZE, // Fake key presses use this value and above static const int fakeKeyBase = 0x200; +// Used to detect fake input (0xaa is not a real key) +#ifdef WIN32 +static const WORD SCAN_FAKE = 0xaa; +#endif + Viewport::Viewport(int w, int h, const rfb::PixelFormat& serverPF, CConn* cc_) : Fl_Widget(0, 0, w, h), cc(cc_), frameBuffer(NULL), lastPointerPos(0, 0), lastButtonMask(0), @@ -218,6 +228,108 @@ void Viewport::setCursor(int width, int height, const Point& hotspot, } +void Viewport::setLEDState(unsigned int state) +{ + Fl_Widget *focus; + + vlog.debug("Got server LED state: 0x%08x", state); + + focus = Fl::grab(); + if (!focus) + focus = Fl::focus(); + if (!focus) + return; + + if (focus != this) + return; + +#if defined(WIN32) + INPUT input[6]; + UINT count; + UINT ret; + + memset(input, 0, sizeof(input)); + count = 0; + + if (!!(state & ledCapsLock) != !!(GetKeyState(VK_CAPITAL) & 0x1)) { + input[count].type = input[count+1].type = INPUT_KEYBOARD; + input[count].ki.wVk = input[count+1].ki.wVk = VK_CAPITAL; + input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE; + input[count].ki.dwFlags = 0; + input[count+1].ki.dwFlags = KEYEVENTF_KEYUP; + count += 2; + } + + if (!!(state & ledNumLock) != !!(GetKeyState(VK_NUMLOCK) & 0x1)) { + input[count].type = input[count+1].type = INPUT_KEYBOARD; + input[count].ki.wVk = input[count+1].ki.wVk = VK_NUMLOCK; + input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE; + input[count].ki.dwFlags = KEYEVENTF_EXTENDEDKEY; + input[count+1].ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_EXTENDEDKEY; + count += 2; + } + + if (!!(state & ledScrollLock) != !!(GetKeyState(VK_SCROLL) & 0x1)) { + input[count].type = input[count+1].type = INPUT_KEYBOARD; + input[count].ki.wVk = input[count+1].ki.wVk = VK_SCROLL; + input[count].ki.wScan = input[count+1].ki.wScan = SCAN_FAKE; + input[count].ki.dwFlags = 0; + input[count+1].ki.dwFlags = KEYEVENTF_KEYUP; + count += 2; + } + + if (count == 0) + return; + + ret = SendInput(count, input, sizeof(*input)); + if (ret < count) + vlog.error(_("Failed to update keyboard LED state: %lu"), GetLastError()); +#elif defined(__APPLE__) + int ret; + + ret = cocoa_set_caps_lock_state(state & ledCapsLock); + if (ret != 0) { + vlog.error(_("Failed to update keyboard LED state: %d"), ret); + return; + } + + ret = cocoa_set_num_lock_state(state & ledNumLock); + if (ret != 0) { + vlog.error(_("Failed to update keyboard LED state: %d"), ret); + return; + } + + // No support for Scroll Lock // + +#else + unsigned int affect, values; + unsigned int mask; + + Bool ret; + + affect = values = 0; + + affect |= LockMask; + if (state & ledCapsLock) + values |= LockMask; + + mask = getModifierMask(XK_Num_Lock); + affect |= mask; + if (state & ledNumLock) + values |= mask; + + mask = getModifierMask(XK_Scroll_Lock); + affect |= mask; + if (state & ledScrollLock) + values |= mask; + + ret = XkbLockModifiers(fl_display, XkbUseCoreKbd, affect, values); + if (!ret) + vlog.error(_("Failed to update keyboard LED state")); +#endif +} + + void Viewport::draw(Surface* dst) { int X, Y, W, H; @@ -352,6 +464,55 @@ int Viewport::handle(int event) return Fl_Widget::handle(event); } + +#if ! (defined(WIN32) || defined(__APPLE__)) +unsigned int Viewport::getModifierMask(unsigned int keysym) +{ + XkbDescPtr xkb; + unsigned int mask, keycode; + XkbAction *act; + + mask = 0; + + xkb = XkbGetMap(fl_display, XkbAllComponentsMask, XkbUseCoreKbd); + if (xkb == NULL) + return 0; + + for (keycode = xkb->min_key_code; keycode <= xkb->max_key_code; keycode++) { + unsigned int state_out; + KeySym ks; + + XkbTranslateKeyCode(xkb, keycode, 0, &state_out, &ks); + if (ks == NoSymbol) + continue; + + if (ks == keysym) + break; + } + + // KeySym not mapped? + if (keycode > xkb->max_key_code) + goto out; + + act = XkbKeyAction(xkb, keycode, 0); + if (act == NULL) + goto out; + if (act->type != XkbSA_LockMods) + goto out; + + if (act->mods.flags & XkbSA_UseModMapMods) + mask = xkb->map->modmap[keycode]; + else + mask = act->mods.mask; + +out: + XkbFreeKeyboard(xkb, XkbAllComponentsMask, True); + + return mask; +} +#endif + + void Viewport::handleClipboardChange(int source, void *data) { Viewport *self = (Viewport *)data; @@ -577,6 +738,11 @@ int Viewport::handleSystemEvent(void *event, void *data) keyCode = ((msg->lParam >> 16) & 0xff); + if (keyCode == SCAN_FAKE) { + vlog.debug("Ignoring fake key press (virtual key 0x%02x)", vKey); + return 1; + } + // Windows sets the scan code to 0x00 for multimedia keys, so we // have to do a reverse lookup based on the vKey. if (keyCode == 0x00) { @@ -620,6 +786,12 @@ int Viewport::handleSystemEvent(void *event, void *data) isExtended = (msg->lParam & (1 << 24)) != 0; keyCode = ((msg->lParam >> 16) & 0xff); + + if (keyCode == SCAN_FAKE) { + vlog.debug("Ignoring fake key release (virtual key 0x%02x)", vKey); + return 1; + } + if (keyCode == 0x00) keyCode = MapVirtualKey(vKey, MAPVK_VK_TO_VSC); if (isExtended) diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h index 6f0710d3..652feb45 100644 --- a/vncviewer/Viewport.h +++ b/vncviewer/Viewport.h @@ -47,6 +47,9 @@ public: void setCursor(int width, int height, const rfb::Point& hotspot, const rdr::U8* data); + // Change client LED state + void setLEDState(unsigned int state); + void draw(Surface* dst); // Fl_Widget callback methods @@ -59,6 +62,8 @@ public: private: + unsigned int getModifierMask(unsigned int keysym); + static void handleClipboardChange(int source, void *data); void handlePointerEvent(const rfb::Point& pos, int buttonMask); diff --git a/vncviewer/cocoa.h b/vncviewer/cocoa.h index 0c3ac82f..b5d5f763 100644 --- a/vncviewer/cocoa.h +++ b/vncviewer/cocoa.h @@ -33,4 +33,7 @@ int cocoa_is_key_press(const void *event); int cocoa_event_keycode(const void *event); int cocoa_event_keysym(const void *event); +int cocoa_set_caps_lock_state(bool on); +int cocoa_set_num_lock_state(bool on); + #endif diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm index 6e464fa4..6483291c 100644 --- a/vncviewer/cocoa.mm +++ b/vncviewer/cocoa.mm @@ -27,6 +27,9 @@ #import #import +#include +#include + #define XK_LATIN1 #define XK_MISCELLANY #define XK_XKB_KEYS @@ -406,3 +409,50 @@ int cocoa_event_keysym(const void *event) return ucs2keysym([chars characterAtIndex:0]); } + +static int cocoa_open_hid(io_connect_t *ioc) +{ + kern_return_t ret; + io_service_t ios; + CFMutableDictionaryRef mdict; + + mdict = IOServiceMatching(kIOHIDSystemClass); + ios = IOServiceGetMatchingService(kIOMasterPortDefault, + (CFDictionaryRef) mdict); + if (!ios) + return KERN_FAILURE; + + ret = IOServiceOpen(ios, mach_task_self(), kIOHIDParamConnectType, ioc); + IOObjectRelease(ios); + if (ret != KERN_SUCCESS) + return ret; + + return KERN_SUCCESS; +} + +static int cocoa_set_modifier_lock_state(int modifier, bool on) +{ + kern_return_t ret; + io_connect_t ioc; + + ret = cocoa_open_hid(&ioc); + if (ret != KERN_SUCCESS) + return ret; + + ret = IOHIDSetModifierLockState(ioc, modifier, on); + IOServiceClose(ioc); + if (ret != KERN_SUCCESS) + return ret; + + return KERN_SUCCESS; +} + +int cocoa_set_caps_lock_state(bool on) +{ + return cocoa_set_modifier_lock_state(kIOHIDCapsLockState, on); +} + +int cocoa_set_num_lock_state(bool on) +{ + return cocoa_set_modifier_lock_state(kIOHIDNumLockState, on); +} -- 2.39.5