]> source.dussan.org Git - tigervnc.git/commitdiff
Add client support for LED state sync
authorPierre Ossman <ossman@cendio.se>
Mon, 5 Dec 2016 14:26:21 +0000 (15:26 +0100)
committerPierre Ossman <ossman@cendio.se>
Thu, 24 Aug 2017 10:33:03 +0000 (12:33 +0200)
19 files changed:
common/rfb/CMsgHandler.cxx
common/rfb/CMsgHandler.h
common/rfb/CMsgReader.cxx
common/rfb/CMsgReader.h
common/rfb/CMsgWriter.cxx
common/rfb/ConnParams.cxx
common/rfb/ConnParams.h
common/rfb/encodings.h
common/rfb/ledStates.h [new file with mode: 0644]
tests/CMakeLists.txt
vncviewer/CConn.cxx
vncviewer/CConn.h
vncviewer/CMakeLists.txt
vncviewer/DesktopWindow.cxx
vncviewer/DesktopWindow.h
vncviewer/Viewport.cxx
vncviewer/Viewport.h
vncviewer/cocoa.h
vncviewer/cocoa.mm

index 11c979a3020b654797ebaf68cc67eec8e4d4af38..74c7bf92377c84b83192c3eeb995ac3d5b20fabc 100644 (file)
@@ -82,3 +82,8 @@ void CMsgHandler::framebufferUpdateStart()
 void CMsgHandler::framebufferUpdateEnd()
 {
 }
+
+void CMsgHandler::setLEDState(unsigned int state)
+{
+  cp.setLEDState(state);
+}
index 993276ed343c4447094ee2f474a671c987a0dd15..ef2cda20d32e4d75280689005ec720375755e011 100644 (file)
@@ -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;
   };
 }
index 9abe3f244ca471cf190638eafafb7b8f5f10e6f2..0aaf71fabb3dff46aac90561973fe10bc38d0492 100644 (file)
@@ -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);
+}
index 7b52033feedeff357318b40c6ae4ceb40571cee8..99638276d9f0763100f3d62b7f1c3d2198eb5703 100644 (file)
@@ -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;
index fa784048086fef424c16c6680ac86f2913065df0..7a89a934051d4e33c467f4dcd04594551522efba 100644 (file)
@@ -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;
index 9ee1d9c0fda3739279cc2033292fa295c806457e..f0b69327f2a61da46913163e2f858b219bdfe3fc 100644 (file)
@@ -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,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;
+}
index 5e538933d52141f424d4031d78988addbc59809d..d99d142c4c8f586805bfba7fd57da71956a0f673 100644 (file)
@@ -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<rdr::S32> encodings_;
     char verStr[13];
     int verStrPos;
+    unsigned int ledState_;
   };
 }
 #endif
index a65d863ba131638d8a279beb393aa02f6e4be994..adeecaa94ab4973f371d7696267639989a331402 100644 (file)
@@ -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 (file)
index 0000000..ef14682
--- /dev/null
@@ -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
index 60edb0139531d791bbf1ff0aff6fa5e8b7fa2931..7e0068152dffa4b30f9a09eac52892a97e666d3d 100644 (file)
@@ -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()
index 2e97ec281987e06c1605b06d740eb65d94604bc0..07e7841cb9cae4aae57ff308c94b2224f83e6df2 100644 (file)
@@ -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 //////////////////////
 
index 93cc278f8a6ebc0a2758072dec51fe64f0a538ab..426bd1e2be8dd698f984ca27fff9d9e16a0c0412 100644 (file)
@@ -74,6 +74,8 @@ public:
 
   void fence(rdr::U32 flags, unsigned len, const char data[]);
 
+  void setLEDState(unsigned int state);
+
 private:
 
   void resizeFramebuffer();
index 2aecda23a1b0e20ee626b47f9f3f79c687100c1a..56313e938aeddadd043a8403197f700ff0719e74 100644 (file)
@@ -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})
index 408efd1980172a3fa354713781864516e424ec47..3973cd66334844e92b8794658f4b6e17cb9a0e2d 100644 (file)
@@ -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;
index 4224699c497fafdfd30e6d7c47786c6ad1b7c831..f1bf312ff366852b58d9c50b380f125d6429d375 100644 (file)
@@ -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);
index 6a23526a919264b0bc5e3869a6360791b593aad7..c0bbd0dd2c22d0ea076de8e4359ae7c17925ca90 100644 (file)
@@ -28,6 +28,7 @@
 #include <rfb/CMsgWriter.h>
 #include <rfb/LogWriter.h>
 #include <rfb/Exception.h>
+#include <rfb/ledStates.h>
 
 // FLTK can pull in the X11 headers on some systems
 #ifndef XK_VoidSymbol
 #include <rfb/XF86keysym.h>
 #endif
 
+#if ! (defined(WIN32) || defined(__APPLE__))
+#include <X11/XKBlib.h>
+#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)
index 6f0710d354b8473973c34eac88eb0a7c7bcfc5fd..652feb45a1181841c10a1c05f249ba5d65e1b754 100644 (file)
@@ -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);
index 0c3ac82f57225fe3552442e9ac99bd2102808d11..b5d5f763ce3e10b320aa8e253b59a5e81e2c8fb9 100644 (file)
@@ -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
index 6e464fa4902dad58abc6750da385eda916c4d4a0..6483291c6d3794c5ecc840478a55c82e60e807e0 100644 (file)
@@ -27,6 +27,9 @@
 #import <Cocoa/Cocoa.h>
 #import <Carbon/Carbon.h>
 
+#include <IOKit/hidsystem/IOHIDLib.h>
+#include <IOKit/hidsystem/IOHIDParameter.h>
+
 #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);
+}