Merge branch 'cursor' of https://github.com/alanc/tigervnc
diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake
index 6db0e14..06883c6 100644
--- a/cmake/StaticBuild.cmake
+++ b/cmake/StaticBuild.cmake
@@ -91,7 +91,7 @@
       if(${CMAKE_SYSTEM_NAME} MATCHES "SunOS")
         set(FLTK_LIBRARIES "${FLTK_LIBRARIES} ${X11_Xcursor_LIB} ${X11_Xfixes_LIB} -Wl,-Bstatic -lXft -Wl,-Bdynamic -lfontconfig -lXrender -lXext -R/usr/sfw/lib -L=/usr/sfw/lib -lfreetype -lsocket -lnsl")
       else()
-        set(FLTK_LIBRARIES "${FLTK_LIBRARIES} -Wl,-Bstatic -lXcursor -lXfixes -lXft -lfontconfig -lexpat -lfreetype -lbz2 -lXrender -lXext -lXinerama -Wl,-Bdynamic")
+        set(FLTK_LIBRARIES "${FLTK_LIBRARIES} -Wl,-Bstatic -lXcursor -lXfixes -lXft -lfontconfig -lexpat -lfreetype -lpng -lbz2 -lXrender -lXext -lXinerama -Wl,-Bdynamic")
       endif()
 
       set(FLTK_LIBRARIES "${FLTK_LIBRARIES} -lX11")
diff --git a/common/network/Socket.h b/common/network/Socket.h
index 53f957e..874a59c 100644
--- a/common/network/Socket.h
+++ b/common/network/Socket.h
@@ -82,6 +82,7 @@
   class ConnectionFilter {
   public:
     virtual bool verifyConnection(Socket* s) = 0;
+    virtual ~ConnectionFilter() {}
   };
 
   class SocketListener {
diff --git a/common/rdr/FdInStream.h b/common/rdr/FdInStream.h
index 5d9598c..b4c8765 100644
--- a/common/rdr/FdInStream.h
+++ b/common/rdr/FdInStream.h
@@ -30,6 +30,7 @@
   class FdInStreamBlockCallback {
   public:
     virtual void blockCallback() = 0;
+    virtual ~FdInStreamBlockCallback() {}
   };
 
   class FdInStream : public InStream {
diff --git a/common/rdr/FileInStream.cxx b/common/rdr/FileInStream.cxx
index 8d5c22e..3acdfd4 100644
--- a/common/rdr/FileInStream.cxx
+++ b/common/rdr/FileInStream.cxx
@@ -70,12 +70,12 @@
 
   while (end < b + itemSize) {
     size_t n = fread((U8 *)end, b + sizeof(b) - end, 1, file);
-    if (n < 1) {
-      if (n < 0 || ferror(file))
+    if (n == 0) {
+      if (ferror(file))
         throw SystemException("fread", errno);
       if (feof(file))
         throw EndOfStream();
-      if (n == 0) { return 0; }
+      return 0;
     }
     end += b + sizeof(b) - end;
   }
diff --git a/common/rfb/CConnection.h b/common/rfb/CConnection.h
index e0a000f..e29c033 100644
--- a/common/rfb/CConnection.h
+++ b/common/rfb/CConnection.h
@@ -134,6 +134,8 @@
     // Identities, to determine the unique(ish) name of the server.
     const char* getServerName() const { return serverName.buf; }
 
+    bool isSecure() const { return csecurity ? csecurity->isSecure() : false; }
+
     enum stateEnum {
       RFBSTATE_UNINITIALISED,
       RFBSTATE_PROTOCOL_VERSION,
diff --git a/common/rfb/CMsgHandler.cxx b/common/rfb/CMsgHandler.cxx
index 11c979a..b89bc18 100644
--- a/common/rfb/CMsgHandler.cxx
+++ b/common/rfb/CMsgHandler.cxx
@@ -75,6 +75,11 @@
   cp.supportsContinuousUpdates = true;
 }
 
+void CMsgHandler::supportsQEMUKeyEvent()
+{
+  cp.supportsQEMUKeyEvent = true;
+}
+
 void CMsgHandler::framebufferUpdateStart()
 {
 }
@@ -82,3 +87,8 @@
 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 993276e..903ee15 100644
--- a/common/rfb/CMsgHandler.h
+++ b/common/rfb/CMsgHandler.h
@@ -55,6 +55,7 @@
     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 @@
     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 9abe3f2..eee6d27 100644
--- a/common/rfb/CMsgReader.cxx
+++ b/common/rfb/CMsgReader.cxx
@@ -109,6 +109,11 @@
     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 @@
 
   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 7b52033..9963827 100644
--- a/common/rfb/CMsgReader.h
+++ b/common/rfb/CMsgReader.h
@@ -65,6 +65,7 @@
     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 8576d8f..84a0d1f 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>
@@ -72,9 +73,9 @@
   rdr::U32 encodings[encodingMax+3];
 
   if (cp->supportsLocalCursor) {
-    encodings[nEncodings++] = pseudoEncodingXCursor;
-    encodings[nEncodings++] = pseudoEncodingCursor;
     encodings[nEncodings++] = pseudoEncodingCursorWithAlpha;
+    encodings[nEncodings++] = pseudoEncodingCursor;
+    encodings[nEncodings++] = pseudoEncodingXCursor;
   }
   if (cp->supportsDesktopResize)
     encodings[nEncodings++] = pseudoEncodingDesktopSize;
@@ -82,10 +83,13 @@
     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 @@
   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::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 06ecbe7..eb9b112 100644
--- a/common/rfb/CMsgWriter.h
+++ b/common/rfb/CMsgWriter.h
@@ -33,7 +33,7 @@
 
   class PixelFormat;
   class ConnParams;
-  class ScreenSet;
+  struct ScreenSet;
   struct Rect;
 
   class CMsgWriter : public InputHandler {
@@ -55,9 +55,9 @@
 
     // 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/CSecurity.h b/common/rfb/CSecurity.h
index 36da5c7..3fedc50 100644
--- a/common/rfb/CSecurity.h
+++ b/common/rfb/CSecurity.h
@@ -49,6 +49,7 @@
     virtual void destroy() { delete this; }
     virtual int getType() const = 0;
     virtual const char* description() const = 0;
+    virtual bool isSecure() const { return false; }
 
     /*
      * Use variable directly instead of dumb get/set methods.
diff --git a/common/rfb/CSecurityPlain.cxx b/common/rfb/CSecurityPlain.cxx
index 0320ce2..8e383c3 100644
--- a/common/rfb/CSecurityPlain.cxx
+++ b/common/rfb/CSecurityPlain.cxx
@@ -33,7 +33,7 @@
   CharArray username;
   CharArray password;
 
-  (CSecurity::upg)->getUserPasswd(&username.buf, &password.buf);
+  (CSecurity::upg)->getUserPasswd(cc->isSecure(), &username.buf, &password.buf);
 
   // Return the response to the server
   os->writeU32(strlen(username.buf));
diff --git a/common/rfb/CSecurityStack.cxx b/common/rfb/CSecurityStack.cxx
index cfc60fd..47c3f6d 100644
--- a/common/rfb/CSecurityStack.cxx
+++ b/common/rfb/CSecurityStack.cxx
@@ -63,3 +63,12 @@
 
   return res;
 }
+
+bool CSecurityStack::isSecure() const
+{
+  if (state0 && state0->isSecure())
+    return true;
+  if (state == 1 && state1 && state1->isSecure())
+    return true;
+  return false;
+}
diff --git a/common/rfb/CSecurityStack.h b/common/rfb/CSecurityStack.h
index a76b3fe..a16003f 100644
--- a/common/rfb/CSecurityStack.h
+++ b/common/rfb/CSecurityStack.h
@@ -32,6 +32,7 @@
     virtual bool processMsg(CConnection* cc);
     virtual int getType() const {return type;};
     virtual const char* description() const {return name;}
+    virtual bool isSecure() const;
   protected:
     int state;
     CSecurity* state0;
diff --git a/common/rfb/CSecurityTLS.cxx b/common/rfb/CSecurityTLS.cxx
index 8a053e3..8116e9c 100644
--- a/common/rfb/CSecurityTLS.cxx
+++ b/common/rfb/CSecurityTLS.cxx
@@ -254,6 +254,11 @@
     if (gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cert_cred) != GNUTLS_E_SUCCESS)
       throw AuthFailureException("gnutls_credentials_set failed");
 
+    if (gnutls_server_name_set(session, GNUTLS_NAME_DNS,
+                               client->getServerName(),
+                               strlen(client->getServerName())) != GNUTLS_E_SUCCESS)
+      vlog.error("Failed to configure the server name for TLS handshake");
+
     vlog.debug("X509 session has been set");
   }
 }
@@ -332,6 +337,9 @@
   if (status & GNUTLS_CERT_SIGNER_NOT_CA)
     vlog.debug("server cert signer not CA");
 
+  if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
+    throw AuthFailureException("The server certificate uses an insecure algorithm");
+
   if ((status & (~allowed_errors)) != 0) {
     /* No other errors are allowed */
     vlog.debug("GNUTLS status of certificate verification: %u", status);
diff --git a/common/rfb/CSecurityTLS.h b/common/rfb/CSecurityTLS.h
index 57d964d..e726d1e 100644
--- a/common/rfb/CSecurityTLS.h
+++ b/common/rfb/CSecurityTLS.h
@@ -48,6 +48,7 @@
     virtual int getType() const { return anon ? secTypeTLSNone : secTypeX509None; }
     virtual const char* description() const
       { return anon ? "TLS Encryption without VncAuth" : "X509 Encryption without VncAuth"; }
+    virtual bool isSecure() const { return !anon; }
     static void setDefaults();
 
     static StringParameter X509CA;
diff --git a/common/rfb/CSecurityVeNCrypt.cxx b/common/rfb/CSecurityVeNCrypt.cxx
index a15da4a..f9597cc 100644
--- a/common/rfb/CSecurityVeNCrypt.cxx
+++ b/common/rfb/CSecurityVeNCrypt.cxx
@@ -191,3 +191,16 @@
   return csecurity->processMsg(cc);
 }
 
+const char* CSecurityVeNCrypt::description() const
+{
+  if (csecurity)
+    return csecurity->description();
+  return "VeNCrypt";
+}
+
+bool CSecurityVeNCrypt::isSecure() const
+{
+  if (csecurity && csecurity->isSecure())
+    return true;
+  return false;
+}
diff --git a/common/rfb/CSecurityVeNCrypt.h b/common/rfb/CSecurityVeNCrypt.h
index 55d0744..6d978c7 100644
--- a/common/rfb/CSecurityVeNCrypt.h
+++ b/common/rfb/CSecurityVeNCrypt.h
@@ -38,7 +38,8 @@
     ~CSecurityVeNCrypt();
     virtual bool processMsg(CConnection* cc);// { return true; }
     int getType() const {return chosenType;}
-    virtual const char* description() const { return secTypeName(chosenType); }
+    virtual const char* description() const;
+    virtual bool isSecure() const;
 
   protected:
     CSecurity *csecurity;
diff --git a/common/rfb/CSecurityVncAuth.cxx b/common/rfb/CSecurityVncAuth.cxx
index f44e56e..46463e0 100644
--- a/common/rfb/CSecurityVncAuth.cxx
+++ b/common/rfb/CSecurityVncAuth.cxx
@@ -49,7 +49,7 @@
   rdr::U8 challenge[vncAuthChallengeSize];
   is->readBytes(challenge, vncAuthChallengeSize);
   PlainPasswd passwd;
-  (CSecurity::upg)->getUserPasswd(0, &passwd.buf);
+  (CSecurity::upg)->getUserPasswd(cc->isSecure(), 0, &passwd.buf);
 
   // Calculate the correct response
   rdr::U8 key[8];
diff --git a/common/rfb/Configuration.h b/common/rfb/Configuration.h
index fbf161d..d319915 100644
--- a/common/rfb/Configuration.h
+++ b/common/rfb/Configuration.h
@@ -215,6 +215,7 @@
     IntParameter(const char* name_, const char* desc_, int v,
                  int minValue=INT_MIN, int maxValue=INT_MAX,
 		 ConfigurationObject co=ConfGlobal);
+    using VoidParameter::setParam;
     virtual bool setParam(const char* value);
     virtual bool setParam(int v);
     virtual char* getDefaultStr() const;
@@ -251,6 +252,7 @@
   public:
     BinaryParameter(const char* name_, const char* desc_, const void* v, int l,
 		    ConfigurationObject co=ConfGlobal);
+    using VoidParameter::setParam;
     virtual ~BinaryParameter();
     virtual bool setParam(const char* value);
     virtual void setParam(const void* v, int l);
diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx
index 9ee1d9c..23f02ed 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 @@
     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 @@
   supportsExtendedDesktopSize = false;
   supportsLocalXCursor = false;
   supportsLastRect = false;
+  supportsQEMUKeyEvent = false;
   compressLevel = -1;
   qualityLevel = -1;
   fineQualityLevel = -1;
@@ -141,6 +145,11 @@
     case pseudoEncodingLastRect:
       supportsLastRect = true;
       break;
+    case pseudoEncodingLEDState:
+      supportsLEDState = true;
+    case pseudoEncodingQEMUKeyEvent:
+      supportsQEMUKeyEvent = true;
+      break;
     case pseudoEncodingFence:
       supportsFence = true;
       break;
@@ -183,3 +192,8 @@
       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 5e53893..b322293 100644
--- a/common/rfb/ConnParams.h
+++ b/common/rfb/ConnParams.h
@@ -84,6 +84,9 @@
 
     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 @@
     bool supportsExtendedDesktopSize;
     bool supportsDesktopRename;
     bool supportsLastRect;
+    bool supportsLEDState;
+    bool supportsQEMUKeyEvent;
 
     bool supportsSetDesktopSize;
     bool supportsFence;
@@ -111,6 +116,7 @@
     std::set<rdr::S32> encodings_;
     char verStr[13];
     int verStrPos;
+    unsigned int ledState_;
   };
 }
 #endif
diff --git a/common/rfb/DecodeManager.h b/common/rfb/DecodeManager.h
index fbb7f77..2098063 100644
--- a/common/rfb/DecodeManager.h
+++ b/common/rfb/DecodeManager.h
@@ -32,7 +32,7 @@
 }
 
 namespace rdr {
-  class Exception;
+  struct Exception;
   class MemOutStream;
 }
 
@@ -40,7 +40,7 @@
   class CConnection;
   class Decoder;
   class ModifiablePixelBuffer;
-  class Rect;
+  struct Rect;
 
   class DecodeManager {
   public:
diff --git a/common/rfb/Decoder.h b/common/rfb/Decoder.h
index 3840b3f..86ee4ef 100644
--- a/common/rfb/Decoder.h
+++ b/common/rfb/Decoder.h
@@ -27,9 +27,10 @@
 namespace rfb {
   class ConnParams;
   class ModifiablePixelBuffer;
-  class Rect;
   class Region;
 
+  struct Rect;
+
   enum DecoderFlags {
     // A constant for decoders that don't need anything special
     DecoderPlain = 0,
diff --git a/common/rfb/EncodeManager.h b/common/rfb/EncodeManager.h
index 79db950..4319b42 100644
--- a/common/rfb/EncodeManager.h
+++ b/common/rfb/EncodeManager.h
@@ -32,7 +32,7 @@
   class PixelBuffer;
   class RenderedCursor;
   class Region;
-  class Rect;
+  struct Rect;
 
   struct RectInfo;
 
diff --git a/common/rfb/InputHandler.h b/common/rfb/InputHandler.h
index b5e5e87..6c07284 100644
--- a/common/rfb/InputHandler.h
+++ b/common/rfb/InputHandler.h
@@ -25,15 +25,20 @@
 
 #include <rdr/types.h>
 #include <rfb/Rect.h>
+#include <rfb/util.h>
 
 namespace rfb {
 
   class InputHandler {
   public:
     virtual ~InputHandler() {}
-    virtual void keyEvent(rdr::U32 key, bool down) {}
-    virtual void pointerEvent(const Point& pos, int buttonMask) {}
-    virtual void clientCutText(const char* str, int len) {}
+    virtual void keyEvent(rdr::U32 __unused_attr keysym,
+                          rdr::U32 __unused_attr keycode,
+                          bool __unused_attr down) { }
+    virtual void pointerEvent(const Point& __unused_attr pos,
+                              int __unused_attr buttonMask) { }
+    virtual void clientCutText(const char* __unused_attr str,
+                               int __unused_attr len) { }
   };
 
 }
diff --git a/common/rfb/LogWriter.h b/common/rfb/LogWriter.h
index 69b530c..104a875 100644
--- a/common/rfb/LogWriter.h
+++ b/common/rfb/LogWriter.h
@@ -36,9 +36,9 @@
 // is assigned a particular log level.
 
 #define DEF_LOGFUNCTION(name, level) \
-  inline void v##name(const char* fmt, va_list ap) { \
-    if (m_log && (level <= m_level))       \
-      m_log->write(level, m_name, fmt, ap);\
+  inline void v##name(const char* fmt, va_list ap) __printf_attr(2, 0) { \
+    if (m_log && (level <= m_level))        \
+      m_log->write(level, m_name, fmt, ap); \
   } \
   inline void name(const char* fmt, ...) __printf_attr(2, 3) { \
     if (m_log && (level <= m_level)) {     \
diff --git a/common/rfb/Logger.h b/common/rfb/Logger.h
index e53764b..b75594c 100644
--- a/common/rfb/Logger.h
+++ b/common/rfb/Logger.h
@@ -28,6 +28,12 @@
 // and is attached to a particular Logger instance and
 // is assigned a particular log level.
 
+#ifdef __GNUC__
+#  define __printf_attr(a, b) __attribute__((__format__ (__printf__, a, b)))
+#else
+#  define __printf_attr(a, b)
+#endif // __GNUC__
+
 namespace rfb {
 
   class Logger {
@@ -45,7 +51,7 @@
     // -=- Write data to a log
 
     virtual void write(int level, const char *logname, const char *text) = 0;
-    void write(int level, const char *logname, const char* format, va_list ap);
+    void write(int level, const char *logname, const char* format, va_list ap) __printf_attr(4, 0);
 
     // -=- Register a logger
 
diff --git a/common/rfb/PixelBuffer.h b/common/rfb/PixelBuffer.h
index 75caa63..d89793f 100644
--- a/common/rfb/PixelBuffer.h
+++ b/common/rfb/PixelBuffer.h
@@ -28,6 +28,7 @@
 #include <rfb/PixelFormat.h>
 #include <rfb/Rect.h>
 #include <rfb/Pixel.h>
+#include <rfb/util.h>
 
 namespace rfb {
 
@@ -85,7 +86,7 @@
     // Ensure that the specified rectangle of buffer is up to date.
     //   Overridden by derived classes implementing framebuffer access
     //   to copy the required display data into place.
-    virtual void grabRegion(const Region& region) {}
+    virtual void grabRegion(const Region& __unused_attr region) {}
 
   protected:
     PixelBuffer();
diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx
index 85cc6e8..c5c9038 100644
--- a/common/rfb/SConnection.cxx
+++ b/common/rfb/SConnection.cxx
@@ -278,6 +278,11 @@
   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 63dc314..bc43583 100644
--- a/common/rfb/SConnection.h
+++ b/common/rfb/SConnection.h
@@ -73,6 +73,7 @@
 
     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/SDesktop.h b/common/rfb/SDesktop.h
index 833762e..322fe32 100644
--- a/common/rfb/SDesktop.h
+++ b/common/rfb/SDesktop.h
@@ -42,6 +42,7 @@
 #include <rfb/InputHandler.h>
 #include <rfb/Exception.h>
 #include <rfb/screenTypes.h>
+#include <rfb/util.h>
 
 namespace rfb {
 
@@ -55,7 +56,7 @@
     // set via the VNCServer's setPixelBuffer() method by the time this call
     // returns.
 
-    virtual void start(VNCServer* vs) {}
+    virtual void start(VNCServer* __unused_attr vs) {}
 
     // stop() is called by the server when there are no longer any
     // authenticated clients, and therefore the desktop can cease any
@@ -71,8 +72,9 @@
 
     // setScreenLayout() requests to reconfigure the framebuffer and/or
     // the layout of screens.
-    virtual unsigned int setScreenLayout(int fb_width, int fb_height,
-                                         const ScreenSet& layout) {
+    virtual unsigned int setScreenLayout(int __unused_attr fb_width,
+                                         int __unused_attr fb_height,
+                                         const ScreenSet& __unused_attr layout) {
       return resultProhibited;
     }
 
diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx
index 388b21f..c38458c 100644
--- a/common/rfb/SMsgHandler.cxx
+++ b/common/rfb/SMsgHandler.cxx
@@ -41,10 +41,13 @@
 
 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 @@
     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::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 74509e0..749f056 100644
--- a/common/rfb/SMsgHandler.h
+++ b/common/rfb/SMsgHandler.h
@@ -74,6 +74,17 @@
     // 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 3c08fd6..cb71ac8 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 @@
   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 @@
   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 @@
   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 00cb303..146b29f 100644
--- a/common/rfb/SMsgReader.h
+++ b/common/rfb/SMsgReader.h
@@ -55,6 +55,9 @@
     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 bc3f439..2d4998b 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 @@
     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 @@
   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 @@
       nRects++;
     if (needSetCursorWithAlpha)
       nRects++;
+    if (needLEDState)
+      nRects++;
+    if (needQEMUKeyEvent)
+      nRects++;
   }
 
   os->writeU16(nRects);
@@ -362,6 +394,16 @@
     writeSetDesktopNameRect(cp->name());
     needSetDesktopName = false;
   }
+
+  if (needLEDState) {
+    writeLEDStateRect(cp->ledState());
+    needLEDState = false;
+  }
+
+  if (needQEMUKeyEvent) {
+    writeQEMUKeyEventRect();
+    needQEMUKeyEvent = false;
+  }
 }
 
 void SMsgWriter::writeNoDataRects()
@@ -525,3 +567,34 @@
     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 548b8e8..e985941 100644
--- a/common/rfb/SMsgWriter.h
+++ b/common/rfb/SMsgWriter.h
@@ -32,7 +32,7 @@
 namespace rfb {
 
   class ConnParams;
-  class ScreenSet;
+  struct ScreenSet;
 
   class SMsgWriter {
   public:
@@ -82,6 +82,12 @@
     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 @@
     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 @@
     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/SSecurityPlain.h b/common/rfb/SSecurityPlain.h
index 2c08c24..4bf42b7 100644
--- a/common/rfb/SSecurityPlain.h
+++ b/common/rfb/SSecurityPlain.h
@@ -38,6 +38,8 @@
       { return validUser(username) ? validateInternal(sc, username, password) : false; }
     static StringParameter plainUsers;
 
+    virtual ~PasswordValidator() { }
+
   protected:
     virtual bool validateInternal(SConnection* sc, const char *username, const char *password)=0;
     static bool validUser(const char* username);
@@ -50,6 +52,8 @@
     virtual int getType() const { return secTypePlain; };
     virtual const char* getUserName() const { return username.buf; }
 
+    virtual ~SSecurityPlain() { }
+
   private:
     PasswordValidator* valid;
     unsigned int ulen, plen, state;
diff --git a/common/rfb/SSecurityVncAuth.h b/common/rfb/SSecurityVncAuth.h
index e9f379b..a1d1747 100644
--- a/common/rfb/SSecurityVncAuth.h
+++ b/common/rfb/SSecurityVncAuth.h
@@ -37,6 +37,8 @@
     // getVncAuthPasswd() fills buffer of given password and readOnlyPassword.
     // If there was no read only password in the file, readOnlyPassword buffer is null.
     virtual void getVncAuthPasswd(PlainPasswd *password, PlainPasswd *readOnlyPassword)=0;
+
+    virtual ~VncAuthPasswdGetter() { }
   };
 
   class VncAuthPasswdParameter : public VncAuthPasswdGetter, BinaryParameter {
diff --git a/common/rfb/Timer.h b/common/rfb/Timer.h
index e295b82..78687d1 100644
--- a/common/rfb/Timer.h
+++ b/common/rfb/Timer.h
@@ -49,6 +49,8 @@
       //   appropriate interval.
       //   If the handler returns false then the Timer is cancelled.
       virtual bool handleTimeout(Timer* t) = 0;
+
+      virtual ~Callback() {}
     };
 
     // checkTimeouts()
diff --git a/common/rfb/UserPasswdGetter.h b/common/rfb/UserPasswdGetter.h
index 18b0bae..27775cc 100644
--- a/common/rfb/UserPasswdGetter.h
+++ b/common/rfb/UserPasswdGetter.h
@@ -24,7 +24,9 @@
     // dialog, getpass(), etc.  The user buffer pointer can be null, in which
     // case no user name will be retrieved.  The caller MUST delete [] the
     // result(s).
-    virtual void getUserPasswd(char** user, char** password)=0;
+    virtual void getUserPasswd(bool secure, char** user, char** password)=0;
+
+    virtual ~UserPasswdGetter() {}
   };
 }
 #endif
diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index d2206f9..b183c05 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>
@@ -64,6 +66,8 @@
   unsigned inFlight;
 };
 
+static Cursor emptyCursor(0, 0, Point(0, 0), NULL);
+
 VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
                                    bool reverse)
   : sock(s), reverseConnection(reverse),
@@ -98,11 +102,18 @@
                                     (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;
 
@@ -311,6 +322,16 @@
 }
 
 
+void VNCSConnectionST::setLEDStateOrClose(unsigned int state)
+{
+  try {
+    setLEDState(state);
+  } catch(rdr::Exception& e) {
+    close(e.str());
+  }
+}
+
+
 int VNCSConnectionST::checkIdleTimeout()
 {
   int idleTimeout = rfb::Server::idleTimeout;
@@ -357,6 +378,9 @@
 void VNCSConnectionST::renderedCursorChange()
 {
   if (state() != RFBSTATE_NORMAL) return;
+  // Are we switching between client-side and server-side cursor?
+  if (damagedCursorRegion.is_empty() != needRenderedCursor())
+    setCursorOrClose();
   if (!damagedCursorRegion.is_empty())
     removeRenderedCursor = true;
   if (needRenderedCursor()) {
@@ -380,7 +404,8 @@
   if (state() != RFBSTATE_NORMAL)
     return false;
 
-  if (!cp.supportsLocalCursor && !cp.supportsLocalXCursor)
+  if (!cp.supportsLocalCursorWithAlpha &&
+      !cp.supportsLocalCursor && !cp.supportsLocalXCursor)
     return true;
   if (!server->cursorPos.equals(pointerEventPos) &&
       (time(0) - pointerEventTime) > 0)
@@ -415,6 +440,7 @@
   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());
@@ -524,12 +550,12 @@
   ~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;
@@ -538,42 +564,127 @@
 
 // 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 = isShiftPressed();
+        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 = isShiftPressed();
+        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) {
+    if (!isShiftPressed())
       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)
@@ -725,7 +836,8 @@
 
 void VNCSConnectionST::supportsLocalCursor()
 {
-  if (cp.supportsLocalCursor || cp.supportsLocalXCursor) {
+  if (cp.supportsLocalCursorWithAlpha ||
+      cp.supportsLocalCursor || cp.supportsLocalXCursor) {
     if (!damagedCursorRegion.is_empty())
       removeRenderedCursor = true;
     setCursor();
@@ -747,6 +859,11 @@
   writer()->writeEndOfContinuousUpdates();
 }
 
+void VNCSConnectionST::supportsLEDState()
+{
+  writer()->writeLEDState();
+}
+
 
 bool VNCSConnectionST::handleTimeout(Timer* t)
 {
@@ -764,6 +881,19 @@
   return false;
 }
 
+bool VNCSConnectionST::isShiftPressed()
+{
+    std::map<rdr::U32, rdr::U32>::const_iterator iter;
+
+    for (iter = pressedKeys.begin(); iter != pressedKeys.end(); ++iter) {
+      if (iter->second == XK_Shift_L)
+        return true;
+      if (iter->second == XK_Shift_R)
+        return true;
+    }
+
+  return false;
+}
 
 void VNCSConnectionST::writeRTTPing()
 {
@@ -1130,7 +1260,11 @@
   if (state() != RFBSTATE_NORMAL)
     return;
 
-  cp.setCursor(*server->cursor);
+  // We need to blank out the client's cursor or there will be two
+  if (needRenderedCursor())
+    cp.setCursor(emptyCursor);
+  else
+    cp.setCursor(*server->cursor);
 
   if (!writer()->writeSetCursorWithAlpha()) {
     if (!writer()->writeSetCursor()) {
@@ -1159,6 +1293,21 @@
   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 74a6946..9b7b14b 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 @@
     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 @@
     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 @@
     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
@@ -158,6 +160,8 @@
 
     // Internal methods
 
+    bool isShiftPressed();
+
     // Congestion control
     void writeRTTPing();
     void handleRTTPong(const struct RTTInfo &rttInfo);
@@ -174,6 +178,7 @@
     void screenLayoutChange(rdr::U16 reason);
     void setCursor();
     void setDesktopName(const char *name);
+    void setLEDState(unsigned int state);
     void setSocketTimeouts();
 
     network::Socket* sock;
@@ -207,7 +212,7 @@
     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 982a4ff..c5335ad 100644
--- a/common/rfb/VNCServer.h
+++ b/common/rfb/VNCServer.h
@@ -74,6 +74,10 @@
 
     // 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 ec5e962..43e8f3e 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 @@
 
 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::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 00f77c7..2dfdbbd 100644
--- a/common/rfb/VNCServerST.h
+++ b/common/rfb/VNCServerST.h
@@ -101,6 +101,7 @@
     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 @@
     int blockCounter;
     PixelBuffer* pb;
     ScreenSet screenLayout;
+    unsigned int ledState;
 
     CharArray name;
 
diff --git a/common/rfb/encodings.h b/common/rfb/encodings.h
index a65d863..122afe7 100644
--- a/common/rfb/encodings.h
+++ b/common/rfb/encodings.h
@@ -34,11 +34,13 @@
   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/vncviewer/FLTKPixelBuffer.h b/common/rfb/ledStates.h
similarity index 67%
copy from vncviewer/FLTKPixelBuffer.h
copy to common/rfb/ledStates.h
index 148c626..ef14682 100644
--- a/vncviewer/FLTKPixelBuffer.h
+++ b/common/rfb/ledStates.h
@@ -1,4 +1,4 @@
-/* Copyright 2011-2014 Pierre Ossman for Cendio AB
+/* 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
@@ -15,19 +15,16 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
  * USA.
  */
+#ifndef __RFB_LEDSTATES_H__
+#define __RFB_LEDSTATES_H__
 
-#ifndef __FLTKPIXELBUFFER_H__
-#define __FLTKPIXELBUFFER_H__
+namespace rfb {
 
-#include "PlatformPixelBuffer.h"
+  const unsigned int ledScrollLock = 1 << 0;
+  const unsigned int ledNumLock = 1 << 1;
+  const unsigned int ledCapsLock = 1 << 2;
 
-class FLTKPixelBuffer: public PlatformPixelBuffer {
-public:
-  FLTKPixelBuffer(int width, int height);
-  ~FLTKPixelBuffer();
-
-  virtual void draw(int src_x, int src_y, int x, int y, int w, int h);
-};
-
+  const unsigned int ledUnknown = (unsigned int)-1;
+}
 
 #endif
diff --git a/common/rfb/msgTypes.h b/common/rfb/msgTypes.h
index a55e1c5..a17493c 100644
--- a/common/rfb/msgTypes.h
+++ b/common/rfb/msgTypes.h
@@ -45,5 +45,7 @@
   const int msgTypeClientFence = 248;
 
   const int msgTypeSetDesktopSize = 251;
+
+  const int msgTypeQEMUClientMessage = 255;
 }
 #endif
diff --git a/vncviewer/FLTKPixelBuffer.h b/common/rfb/qemuTypes.h
similarity index 67%
rename from vncviewer/FLTKPixelBuffer.h
rename to common/rfb/qemuTypes.h
index 148c626..6a67f78 100644
--- a/vncviewer/FLTKPixelBuffer.h
+++ b/common/rfb/qemuTypes.h
@@ -1,4 +1,4 @@
-/* Copyright 2011-2014 Pierre Ossman for Cendio AB
+/* 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
@@ -15,19 +15,11 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
  * USA.
  */
+#ifndef __RFB_QEMUTYPES_H__
+#define __RFB_QEMUTYPES_H__
 
-#ifndef __FLTKPIXELBUFFER_H__
-#define __FLTKPIXELBUFFER_H__
-
-#include "PlatformPixelBuffer.h"
-
-class FLTKPixelBuffer: public PlatformPixelBuffer {
-public:
-  FLTKPixelBuffer(int width, int height);
-  ~FLTKPixelBuffer();
-
-  virtual void draw(int src_x, int src_y, int x, int y, int w, int h);
-};
-
-
+namespace rfb {
+  const int qemuExtendedKeyEvent = 0;
+  const int qemuAudio = 1;
+}
 #endif
diff --git a/common/rfb/util.cxx b/common/rfb/util.cxx
index 22e00ff..cfec2ef 100644
--- a/common/rfb/util.cxx
+++ b/common/rfb/util.cxx
@@ -44,7 +44,7 @@
 
   void CharArray::format(const char *fmt, ...) {
     va_list ap;
-    size_t len;
+    int len;
 
     va_start(ap, fmt);
     len = vsnprintf(NULL, 0, fmt, ap);
diff --git a/common/rfb/util.h b/common/rfb/util.h
index e9114c3..0de64c4 100644
--- a/common/rfb/util.h
+++ b/common/rfb/util.h
@@ -38,6 +38,10 @@
 #  define __printf_attr(a, b)
 #endif // __GNUC__
 
+#ifndef __unused_attr
+#  define __unused_attr __attribute((__unused__))
+#endif
+
 namespace rfb {
 
   // -=- Class to handle cleanup of arrays of characters
diff --git a/java/com/tigervnc/rfb/CMsgWriter.java b/java/com/tigervnc/rfb/CMsgWriter.java
index 7838da3..ea2b7c9 100644
--- a/java/com/tigervnc/rfb/CMsgWriter.java
+++ b/java/com/tigervnc/rfb/CMsgWriter.java
@@ -65,9 +65,10 @@
     int[] encodings = new int[Encodings.encodingMax+3];
 
     if (cp.supportsLocalCursor) {
-      encodings[nEncodings++] = Encodings.pseudoEncodingXCursor;
+      if (cp.supportsLocalCursorWithAlpha)
+        encodings[nEncodings++] = Encodings.pseudoEncodingCursorWithAlpha;
       encodings[nEncodings++] = Encodings.pseudoEncodingCursor;
-      encodings[nEncodings++] = Encodings.pseudoEncodingCursorWithAlpha;
+      encodings[nEncodings++] = Encodings.pseudoEncodingXCursor;
     }
     if (cp.supportsDesktopResize)
       encodings[nEncodings++] = Encodings.pseudoEncodingDesktopSize;
diff --git a/java/com/tigervnc/rfb/ConnParams.java b/java/com/tigervnc/rfb/ConnParams.java
index fe52770..ce3af71 100644
--- a/java/com/tigervnc/rfb/ConnParams.java
+++ b/java/com/tigervnc/rfb/ConnParams.java
@@ -39,6 +39,7 @@
     majorVersion = 0; minorVersion = 0;
     width = 0; height = 0; useCopyRect = false;
     supportsLocalCursor = false; supportsLocalXCursor = false;
+    supportsLocalCursorWithAlpha = false;
     supportsDesktopResize = false; supportsExtendedDesktopSize = false;
     supportsDesktopRename = false; supportsLastRect = false;
     supportsSetDesktopSize = false; supportsFence = false;
@@ -128,6 +129,7 @@
   {
     useCopyRect = false;
     supportsLocalCursor = false;
+    supportsLocalCursorWithAlpha = false;
     supportsDesktopResize = false;
     supportsExtendedDesktopSize = false;
     supportsLocalXCursor = false;
@@ -151,6 +153,9 @@
       case Encodings.pseudoEncodingXCursor:
         supportsLocalXCursor = true;
         break;
+      case Encodings.pseudoEncodingCursorWithAlpha:
+        supportsLocalCursorWithAlpha = true;
+        break;
       case Encodings.pseudoEncodingDesktopSize:
         supportsDesktopResize = true;
         break;
@@ -213,6 +218,7 @@
 
   public boolean supportsLocalCursor;
   public boolean supportsLocalXCursor;
+  public boolean supportsLocalCursorWithAlpha;
   public boolean supportsDesktopResize;
   public boolean supportsExtendedDesktopSize;
   public boolean supportsDesktopRename;
diff --git a/java/com/tigervnc/vncviewer/CConn.java b/java/com/tigervnc/vncviewer/CConn.java
index c53f805..85bdcb6 100644
--- a/java/com/tigervnc/vncviewer/CConn.java
+++ b/java/com/tigervnc/vncviewer/CConn.java
@@ -104,6 +104,11 @@
       currentEncoding = encNum;
 
     cp.supportsLocalCursor = true;
+    if (VncViewer.os.contains("windows"))
+      // JRE on Windows does not support alpha cursor
+      cp.supportsLocalCursorWithAlpha = false;
+    else
+      cp.supportsLocalCursorWithAlpha = true;
 
     cp.supportsDesktopResize = true;
     cp.supportsExtendedDesktopSize = true;
diff --git a/java/com/tigervnc/vncviewer/Parameters.java b/java/com/tigervnc/vncviewer/Parameters.java
index 50e26cb..2f8a585 100644
--- a/java/com/tigervnc/vncviewer/Parameters.java
+++ b/java/com/tigervnc/vncviewer/Parameters.java
@@ -295,6 +295,7 @@
     CSecurityTLS.X509CA,
     CSecurityTLS.X509CRL,
     SecurityClient.secTypes,
+    dotWhenNoCursor,
     autoSelect,
     fullColor,
     lowColorLevel,
diff --git a/java/com/tigervnc/vncviewer/VncViewer.java b/java/com/tigervnc/vncviewer/VncViewer.java
index e123499..d6a3930 100644
--- a/java/com/tigervnc/vncviewer/VncViewer.java
+++ b/java/com/tigervnc/vncviewer/VncViewer.java
@@ -81,7 +81,7 @@
   public static final InputStream timestamp =
     VncViewer.class.getResourceAsStream("timestamp");
   public static final String os = 
-    System.getProperty("os.name").toLowerCase();
+    System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
   private static VncViewer applet;
 
   private String defaultServerName;
@@ -173,7 +173,8 @@
     }
 
     // Override defaults with command-line options
-    for (int i = 0; i < argv.length; i++) {
+    int i = 0;
+    for (; i < argv.length; i++) {
       if (argv[i].length() == 0)
         continue;
 
@@ -191,9 +192,6 @@
         continue;
       }
 
-      if (Configuration.setParam(argv[i]))
-        continue;
-
       if (argv[i].charAt(0) == '-') {
         if (i+1 < argv.length) {
           if (Configuration.setParam(argv[i].substring(1), argv[i+1])) {
@@ -201,6 +199,9 @@
             continue;
           }
         }
+        if (Configuration.setParam(argv[i]))
+          continue;
+
         usage();
       }
 
diff --git a/media/insecure.svg b/media/insecure.svg
new file mode 100644
index 0000000..1c5f6fa
--- /dev/null
+++ b/media/insecure.svg
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   viewBox="0 0 4.2333332 4.2333335"
+   version="1.1"
+   id="svg8"
+   inkscape:version="0.92+devel unknown"
+   sodipodi:docname="insecure.svg">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="4.5653262"
+     inkscape:cy="11.192284"
+     inkscape:document-units="mm"
+     inkscape:current-layer="layer1"
+     inkscape:document-rotation="0"
+     showgrid="true"
+     units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid10" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-292.76665)">
+    <rect
+       style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.13229166;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.26458333, 0.79374999"
+       id="rect878"
+       width="4.2333331"
+       height="4.2333331"
+       x="0"
+       y="292.76666" />
+    <rect
+       style="fill:#ff0000;stroke-width:0.13229166;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.26458333, 0.79375"
+       id="rect926"
+       width="4.2333331"
+       height="4.2333331"
+       x="-5.5511151e-17"
+       y="292.76666" />
+    <rect
+       style="fill:#000000;fill-opacity:1;stroke-width:0.10103943;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.20207887, 0.60623662"
+       id="rect12-3"
+       width="3.175"
+       height="1.8520833"
+       x="0.52916664"
+       y="294.61874"
+       ry="0.26457277" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:#000000;stroke-width:0.13229166;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.26458333, 0.79374999"
+       d="m 2.1166667,293.29579 a 1.0583332,1.0583332 0 0 0 -1.0583334,1.05833 v 0.79375 h 0.3601848 a 1.0583332,1.0583332 0 0 0 0.6981486,0.26458 1.0583332,1.0583332 0 0 0 0.6981486,-0.26458 H 3.175 v -0.79375 a 1.0583332,1.0583332 0 0 0 -1.0583333,-1.05833 z m 0,0.52916 a 0.52916664,0.52916664 0 0 1 0.5291666,0.52917 v 0.79375 H 1.5875 v -0.79375 a 0.52916664,0.52916664 0 0 1 0.5291667,-0.52917 z"
+       id="path913" />
+    <rect
+       style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.30752403;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.61504807, 1.84514421"
+       id="rect871"
+       width="4.4979167"
+       height="0.52916664"
+       x="-209.4492"
+       y="209.48177"
+       transform="rotate(-45)" />
+    <rect
+       transform="rotate(-45)"
+       y="210.0107"
+       x="-209.26234"
+       height="0.52916664"
+       width="4.6772137"
+       id="rect875"
+       style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.30752403;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.61504807, 1.84514421"
+       ry="0" />
+  </g>
+</svg>
diff --git a/media/insecure.xpm b/media/insecure.xpm
new file mode 100644
index 0000000..f5053fe
--- /dev/null
+++ b/media/insecure.xpm
@@ -0,0 +1,71 @@
+/* XPM */
+static char *insecure[] = {
+/* columns rows colors chars-per-pixel */
+"15 15 50 1 ",
+"  c black",
+". c #020000",
+"X c #050000",
+"o c #080000",
+"O c #0A0000",
+"+ c #0C0000",
+"@ c #0D0000",
+"# c #0F0000",
+"$ c #100000",
+"% c #110000",
+"& c #120000",
+"* c #140000",
+"= c #290000",
+"- c #330000",
+"; c #370000",
+": c #430000",
+"> c #560000",
+", c #620000",
+"< c #660000",
+"1 c #6C0000",
+"2 c #7D0000",
+"3 c #800000",
+"4 c #810000",
+"5 c #840000",
+"6 c #870000",
+"7 c #950000",
+"8 c #A20000",
+"9 c #AB0000",
+"0 c #B30000",
+"q c #B40000",
+"w c #C00000",
+"e c #C40000",
+"r c #CD0000",
+"t c #DC0000",
+"y c #DD0000",
+"u c #DF0000",
+"i c #E40000",
+"p c #E50000",
+"a c #E60000",
+"s c #EA0000",
+"d c #EB0000",
+"f c #ED0000",
+"g c #F00000",
+"h c #F40000",
+"j c #F90000",
+"k c #FA0000",
+"l c #FB0000",
+"z c #FC0000",
+"x c #FD0000",
+"c c red",
+/* pixels */
+"ccccccccccccccc",
+"ccccccjpkcr:fcc",
+"ccccz6+ @1$ rcc",
+"cccc2 O-o  wccc",
+"cccdX%tr# 4cccc",
+"ccce >r& 7czccc",
+"ccq< =O 8cg60cc",
+"cs.    3cg;  ac",
+"cu    3cg;   uc",
+"cu   3cg;    uc",
+"c0  3cg;     uc",
+"r$ 3cg;      uc",
+", 5cg;      *hc",
+"l9lxiyyyyyyyhcc",
+"ccccccccccccccc"
+};
diff --git a/media/secure.svg b/media/secure.svg
new file mode 100644
index 0000000..4da1075
--- /dev/null
+++ b/media/secure.svg
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   viewBox="0 0 4.2333332 4.2333335"
+   version="1.1"
+   id="svg8"
+   inkscape:version="0.92+devel unknown"
+   sodipodi:docname="secure.svg">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="32"
+     inkscape:cx="19.34416"
+     inkscape:cy="11.532874"
+     inkscape:document-units="mm"
+     inkscape:current-layer="layer1"
+     inkscape:document-rotation="0"
+     showgrid="true"
+     units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="1136"
+     inkscape:window-x="1920"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid10" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-292.76665)">
+    <rect
+       style="fill:#00ff00;stroke-width:0.13229166;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.26458333, 0.79374999000000002"
+       id="rect926"
+       width="4.2333331"
+       height="4.2333331"
+       x="0"
+       y="292.76666" />
+    <rect
+       style="fill:#000000;fill-opacity:1;stroke-width:0.10103943;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.20207887, 0.60623661"
+       id="rect12"
+       width="3.175"
+       height="1.8520833"
+       x="0.52916664"
+       y="294.61874"
+       ry="0.26457277" />
+    <path
+       style="fill:#000000;stroke-width:0.49999997;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0.99999999, 2.99999996"
+       d="M 8 2 A 3.9999996 3.9999996 0 0 0 4 6 L 4 9 L 5.3613281 9 A 3.9999996 3.9999996 0 0 0 8 10 A 3.9999996 3.9999996 0 0 0 10.638672 9 L 12 9 L 12 6 A 3.9999996 3.9999996 0 0 0 8 2 z M 8 4 A 1.9999999 1.9999999 0 0 1 10 6 L 10 9 L 6 9 L 6 6 A 1.9999999 1.9999999 0 0 1 8 4 z "
+       transform="matrix(0.26458333,0,0,0.26458333,0,292.76665)"
+       id="path913" />
+  </g>
+</svg>
diff --git a/media/secure.xpm b/media/secure.xpm
new file mode 100644
index 0000000..49a3791
--- /dev/null
+++ b/media/secure.xpm
@@ -0,0 +1,56 @@
+/* XPM */
+static char *secure[] = {
+/* columns rows colors chars-per-pixel */
+"15 15 35 1 ",
+"  c black",
+". c #000200",
+"X c #000500",
+"o c #000A00",
+"O c #000C00",
+"+ c #000D00",
+"@ c #001000",
+"# c #001100",
+"$ c #001400",
+"% c #003300",
+"& c #005500",
+"* c #005600",
+"= c #006600",
+"- c #007D00",
+"; c #007E00",
+": c #008700",
+"> c #008800",
+", c #00B300",
+"< c #00B400",
+"1 c #00C400",
+"2 c #00DB00",
+"3 c #00DC00",
+"4 c #00DD00",
+"5 c #00DF00",
+"6 c #00E500",
+"7 c #00E600",
+"8 c #00EA00",
+"9 c #00EB00",
+"0 c #00ED00",
+"q c #00F000",
+"w c #00F400",
+"e c #00F900",
+"r c #00FA00",
+"t c #00FC00",
+"y c green",
+/* pixels */
+"yyyyyyyyyyyyyyy",
+"yyyyyye6ryyyyyy",
+"yyyyt:O +>tyyyy",
+"yyyy- o%o ;yyyy",
+"yyy9X#3y2@X0yyy",
+"yyy1 *yyy& 1yyy",
+"yy<= %>>>% =,yy",
+"y8.          7y",
+"y5           5y",
+"y5           5y",
+"y5           5y",
+"y5           5y",
+"yw#         $wy",
+"yyq444444444wyy",
+"yyyyyyyyyyyyyyy"
+};
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 60edb01..7e00681 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -40,5 +40,7 @@
   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/unix/x0vncserver/CMakeLists.txt b/unix/x0vncserver/CMakeLists.txt
index 82ea423..5831c4d 100644
--- a/unix/x0vncserver/CMakeLists.txt
+++ b/unix/x0vncserver/CMakeLists.txt
@@ -10,6 +10,8 @@
   PollingManager.cxx
   PollingScheduler.cxx
   TimeMillis.cxx
+  qnum_to_xorgevdev.c
+  qnum_to_xorgkbd.c
   x0vncserver.cxx
   XPixelBuffer.cxx
   ../vncconfig/QueryConnectDialog.cxx
diff --git a/unix/x0vncserver/qnum_to_xorgevdev.c b/unix/x0vncserver/qnum_to_xorgevdev.c
new file mode 100644
index 0000000..357c88d
--- /dev/null
+++ b/unix/x0vncserver/qnum_to_xorgevdev.c
@@ -0,0 +1,245 @@
+/*
+ * This file is auto-generated from keymaps.csv on 2017-08-28 13:03
+ * Database checksum sha256(f8aeff0c3430077a350e3d7ba2b335b381bd929ac4b193413730a402ff3f0097)
+ * To re-generate, run:
+ *   keymap-gen --lang=stdc code-map keymaps.csv qnum xorgevdev
+*/
+const unsigned short code_map_qnum_to_xorgevdev[254] = {
+  [0x1] = 0x9, /* qnum:1 -> linux:1 (KEY_ESC) -> xorgevdev:9 */
+  [0x2] = 0xa, /* qnum:2 -> linux:2 (KEY_1) -> xorgevdev:10 */
+  [0x3] = 0xb, /* qnum:3 -> linux:3 (KEY_2) -> xorgevdev:11 */
+  [0x4] = 0xc, /* qnum:4 -> linux:4 (KEY_3) -> xorgevdev:12 */
+  [0x5] = 0xd, /* qnum:5 -> linux:5 (KEY_4) -> xorgevdev:13 */
+  [0x6] = 0xe, /* qnum:6 -> linux:6 (KEY_5) -> xorgevdev:14 */
+  [0x7] = 0xf, /* qnum:7 -> linux:7 (KEY_6) -> xorgevdev:15 */
+  [0x8] = 0x10, /* qnum:8 -> linux:8 (KEY_7) -> xorgevdev:16 */
+  [0x9] = 0x11, /* qnum:9 -> linux:9 (KEY_8) -> xorgevdev:17 */
+  [0xa] = 0x12, /* qnum:10 -> linux:10 (KEY_9) -> xorgevdev:18 */
+  [0xb] = 0x13, /* qnum:11 -> linux:11 (KEY_0) -> xorgevdev:19 */
+  [0xc] = 0x14, /* qnum:12 -> linux:12 (KEY_MINUS) -> xorgevdev:20 */
+  [0xd] = 0x15, /* qnum:13 -> linux:13 (KEY_EQUAL) -> xorgevdev:21 */
+  [0xe] = 0x16, /* qnum:14 -> linux:14 (KEY_BACKSPACE) -> xorgevdev:22 */
+  [0xf] = 0x17, /* qnum:15 -> linux:15 (KEY_TAB) -> xorgevdev:23 */
+  [0x10] = 0x18, /* qnum:16 -> linux:16 (KEY_Q) -> xorgevdev:24 */
+  [0x11] = 0x19, /* qnum:17 -> linux:17 (KEY_W) -> xorgevdev:25 */
+  [0x12] = 0x1a, /* qnum:18 -> linux:18 (KEY_E) -> xorgevdev:26 */
+  [0x13] = 0x1b, /* qnum:19 -> linux:19 (KEY_R) -> xorgevdev:27 */
+  [0x14] = 0x1c, /* qnum:20 -> linux:20 (KEY_T) -> xorgevdev:28 */
+  [0x15] = 0x1d, /* qnum:21 -> linux:21 (KEY_Y) -> xorgevdev:29 */
+  [0x16] = 0x1e, /* qnum:22 -> linux:22 (KEY_U) -> xorgevdev:30 */
+  [0x17] = 0x1f, /* qnum:23 -> linux:23 (KEY_I) -> xorgevdev:31 */
+  [0x18] = 0x20, /* qnum:24 -> linux:24 (KEY_O) -> xorgevdev:32 */
+  [0x19] = 0x21, /* qnum:25 -> linux:25 (KEY_P) -> xorgevdev:33 */
+  [0x1a] = 0x22, /* qnum:26 -> linux:26 (KEY_LEFTBRACE) -> xorgevdev:34 */
+  [0x1b] = 0x23, /* qnum:27 -> linux:27 (KEY_RIGHTBRACE) -> xorgevdev:35 */
+  [0x1c] = 0x24, /* qnum:28 -> linux:28 (KEY_ENTER) -> xorgevdev:36 */
+  [0x1d] = 0x25, /* qnum:29 -> linux:29 (KEY_LEFTCTRL) -> xorgevdev:37 */
+  [0x1e] = 0x26, /* qnum:30 -> linux:30 (KEY_A) -> xorgevdev:38 */
+  [0x1f] = 0x27, /* qnum:31 -> linux:31 (KEY_S) -> xorgevdev:39 */
+  [0x20] = 0x28, /* qnum:32 -> linux:32 (KEY_D) -> xorgevdev:40 */
+  [0x21] = 0x29, /* qnum:33 -> linux:33 (KEY_F) -> xorgevdev:41 */
+  [0x22] = 0x2a, /* qnum:34 -> linux:34 (KEY_G) -> xorgevdev:42 */
+  [0x23] = 0x2b, /* qnum:35 -> linux:35 (KEY_H) -> xorgevdev:43 */
+  [0x24] = 0x2c, /* qnum:36 -> linux:36 (KEY_J) -> xorgevdev:44 */
+  [0x25] = 0x2d, /* qnum:37 -> linux:37 (KEY_K) -> xorgevdev:45 */
+  [0x26] = 0x2e, /* qnum:38 -> linux:38 (KEY_L) -> xorgevdev:46 */
+  [0x27] = 0x2f, /* qnum:39 -> linux:39 (KEY_SEMICOLON) -> xorgevdev:47 */
+  [0x28] = 0x30, /* qnum:40 -> linux:40 (KEY_APOSTROPHE) -> xorgevdev:48 */
+  [0x29] = 0x31, /* qnum:41 -> linux:41 (KEY_GRAVE) -> xorgevdev:49 */
+  [0x2a] = 0x32, /* qnum:42 -> linux:42 (KEY_LEFTSHIFT) -> xorgevdev:50 */
+  [0x2b] = 0x33, /* qnum:43 -> linux:43 (KEY_BACKSLASH) -> xorgevdev:51 */
+  [0x2c] = 0x34, /* qnum:44 -> linux:44 (KEY_Z) -> xorgevdev:52 */
+  [0x2d] = 0x35, /* qnum:45 -> linux:45 (KEY_X) -> xorgevdev:53 */
+  [0x2e] = 0x36, /* qnum:46 -> linux:46 (KEY_C) -> xorgevdev:54 */
+  [0x2f] = 0x37, /* qnum:47 -> linux:47 (KEY_V) -> xorgevdev:55 */
+  [0x30] = 0x38, /* qnum:48 -> linux:48 (KEY_B) -> xorgevdev:56 */
+  [0x31] = 0x39, /* qnum:49 -> linux:49 (KEY_N) -> xorgevdev:57 */
+  [0x32] = 0x3a, /* qnum:50 -> linux:50 (KEY_M) -> xorgevdev:58 */
+  [0x33] = 0x3b, /* qnum:51 -> linux:51 (KEY_COMMA) -> xorgevdev:59 */
+  [0x34] = 0x3c, /* qnum:52 -> linux:52 (KEY_DOT) -> xorgevdev:60 */
+  [0x35] = 0x3d, /* qnum:53 -> linux:53 (KEY_SLASH) -> xorgevdev:61 */
+  [0x36] = 0x3e, /* qnum:54 -> linux:54 (KEY_RIGHTSHIFT) -> xorgevdev:62 */
+  [0x37] = 0x3f, /* qnum:55 -> linux:55 (KEY_KPASTERISK) -> xorgevdev:63 */
+  [0x38] = 0x40, /* qnum:56 -> linux:56 (KEY_LEFTALT) -> xorgevdev:64 */
+  [0x39] = 0x41, /* qnum:57 -> linux:57 (KEY_SPACE) -> xorgevdev:65 */
+  [0x3a] = 0x42, /* qnum:58 -> linux:58 (KEY_CAPSLOCK) -> xorgevdev:66 */
+  [0x3b] = 0x43, /* qnum:59 -> linux:59 (KEY_F1) -> xorgevdev:67 */
+  [0x3c] = 0x44, /* qnum:60 -> linux:60 (KEY_F2) -> xorgevdev:68 */
+  [0x3d] = 0x45, /* qnum:61 -> linux:61 (KEY_F3) -> xorgevdev:69 */
+  [0x3e] = 0x46, /* qnum:62 -> linux:62 (KEY_F4) -> xorgevdev:70 */
+  [0x3f] = 0x47, /* qnum:63 -> linux:63 (KEY_F5) -> xorgevdev:71 */
+  [0x40] = 0x48, /* qnum:64 -> linux:64 (KEY_F6) -> xorgevdev:72 */
+  [0x41] = 0x49, /* qnum:65 -> linux:65 (KEY_F7) -> xorgevdev:73 */
+  [0x42] = 0x4a, /* qnum:66 -> linux:66 (KEY_F8) -> xorgevdev:74 */
+  [0x43] = 0x4b, /* qnum:67 -> linux:67 (KEY_F9) -> xorgevdev:75 */
+  [0x44] = 0x4c, /* qnum:68 -> linux:68 (KEY_F10) -> xorgevdev:76 */
+  [0x45] = 0x4d, /* qnum:69 -> linux:69 (KEY_NUMLOCK) -> xorgevdev:77 */
+  [0x46] = 0x4e, /* qnum:70 -> linux:70 (KEY_SCROLLLOCK) -> xorgevdev:78 */
+  [0x47] = 0x4f, /* qnum:71 -> linux:71 (KEY_KP7) -> xorgevdev:79 */
+  [0x48] = 0x50, /* qnum:72 -> linux:72 (KEY_KP8) -> xorgevdev:80 */
+  [0x49] = 0x51, /* qnum:73 -> linux:73 (KEY_KP9) -> xorgevdev:81 */
+  [0x4a] = 0x52, /* qnum:74 -> linux:74 (KEY_KPMINUS) -> xorgevdev:82 */
+  [0x4b] = 0x53, /* qnum:75 -> linux:75 (KEY_KP4) -> xorgevdev:83 */
+  [0x4c] = 0x54, /* qnum:76 -> linux:76 (KEY_KP5) -> xorgevdev:84 */
+  [0x4d] = 0x55, /* qnum:77 -> linux:77 (KEY_KP6) -> xorgevdev:85 */
+  [0x4e] = 0x56, /* qnum:78 -> linux:78 (KEY_KPPLUS) -> xorgevdev:86 */
+  [0x4f] = 0x57, /* qnum:79 -> linux:79 (KEY_KP1) -> xorgevdev:87 */
+  [0x50] = 0x58, /* qnum:80 -> linux:80 (KEY_KP2) -> xorgevdev:88 */
+  [0x51] = 0x59, /* qnum:81 -> linux:81 (KEY_KP3) -> xorgevdev:89 */
+  [0x52] = 0x5a, /* qnum:82 -> linux:82 (KEY_KP0) -> xorgevdev:90 */
+  [0x53] = 0x5b, /* qnum:83 -> linux:83 (KEY_KPDOT) -> xorgevdev:91 */
+  [0x54] = 0x6b, /* qnum:84 -> linux:99 (KEY_SYSRQ) -> xorgevdev:107 */
+  [0x55] = 0xc2, /* qnum:85 -> linux:186 (KEY_F16) -> xorgevdev:194 */
+  [0x56] = 0x5e, /* qnum:86 -> linux:86 (KEY_102ND) -> xorgevdev:94 */
+  [0x57] = 0x5f, /* qnum:87 -> linux:87 (KEY_F11) -> xorgevdev:95 */
+  [0x58] = 0x60, /* qnum:88 -> linux:88 (KEY_F12) -> xorgevdev:96 */
+  [0x59] = 0x7d, /* qnum:89 -> linux:117 (KEY_KPEQUAL) -> xorgevdev:125 */
+  [0x5a] = 0xc6, /* qnum:90 -> linux:190 (KEY_F20) -> xorgevdev:198 */
+  [0x5b] = 0x6d, /* qnum:91 -> linux:101 (KEY_LINEFEED) -> xorgevdev:109 */
+  [0x5c] = 0x67, /* qnum:92 -> linux:95 (KEY_KPJPCOMMA) -> xorgevdev:103 */
+  [0x5d] = 0xbf, /* qnum:93 -> linux:183 (KEY_F13) -> xorgevdev:191 */
+  [0x5e] = 0xc0, /* qnum:94 -> linux:184 (KEY_F14) -> xorgevdev:192 */
+  [0x5f] = 0xc1, /* qnum:95 -> linux:185 (KEY_F15) -> xorgevdev:193 */
+  [0x63] = 0xb1, /* qnum:99 -> linux:169 (KEY_PHONE) -> xorgevdev:177 */
+  [0x64] = 0x8e, /* qnum:100 -> linux:134 (KEY_OPEN) -> xorgevdev:142 */
+  [0x65] = 0x8f, /* qnum:101 -> linux:135 (KEY_PASTE) -> xorgevdev:143 */
+  [0x66] = 0x95, /* qnum:102 -> linux:141 (KEY_SETUP) -> xorgevdev:149 */
+  [0x67] = 0x98, /* qnum:103 -> linux:144 (KEY_FILE) -> xorgevdev:152 */
+  [0x68] = 0x99, /* qnum:104 -> linux:145 (KEY_SENDFILE) -> xorgevdev:153 */
+  [0x69] = 0x9a, /* qnum:105 -> linux:146 (KEY_DELETEFILE) -> xorgevdev:154 */
+  [0x6a] = 0x9f, /* qnum:106 -> linux:151 (KEY_MSDOS) -> xorgevdev:159 */
+  [0x6b] = 0xa1, /* qnum:107 -> linux:153 (KEY_DIRECTION) -> xorgevdev:161 */
+  [0x6c] = 0xa9, /* qnum:108 -> linux:161 (KEY_EJECTCD) -> xorgevdev:169 */
+  [0x6d] = 0xc9, /* qnum:109 -> linux:193 (KEY_F23) -> xorgevdev:201 */
+  [0x6f] = 0xca, /* qnum:111 -> linux:194 (KEY_F24) -> xorgevdev:202 */
+  [0x70] = 0xb2, /* qnum:112 -> linux:170 (KEY_ISO) -> xorgevdev:178 */
+  [0x71] = 0xb6, /* qnum:113 -> linux:174 (KEY_EXIT) -> xorgevdev:182 */
+  [0x72] = 0xb7, /* qnum:114 -> linux:175 (KEY_MOVE) -> xorgevdev:183 */
+  [0x73] = 0x61, /* qnum:115 -> linux:89 (KEY_RO) -> xorgevdev:97 */
+  [0x74] = 0xc7, /* qnum:116 -> linux:191 (KEY_F21) -> xorgevdev:199 */
+  [0x75] = 0xb9, /* qnum:117 -> linux:177 (KEY_SCROLLUP) -> xorgevdev:185 */
+  [0x76] = 0x5d, /* qnum:118 -> linux:85 (KEY_ZENKAKUHANKAKU) -> xorgevdev:93 */
+  [0x77] = 0x63, /* qnum:119 -> linux:91 (KEY_HIRAGANA) -> xorgevdev:99 */
+  [0x78] = 0x62, /* qnum:120 -> linux:90 (KEY_KATAKANA) -> xorgevdev:98 */
+  [0x79] = 0x64, /* qnum:121 -> linux:92 (KEY_HENKAN) -> xorgevdev:100 */
+  [0x7b] = 0x66, /* qnum:123 -> linux:94 (KEY_MUHENKAN) -> xorgevdev:102 */
+  [0x7d] = 0x84, /* qnum:125 -> linux:124 (KEY_YEN) -> xorgevdev:132 */
+  [0x7e] = 0x81, /* qnum:126 -> linux:121 (KEY_KPCOMMA) -> xorgevdev:129 */
+  [0x81] = 0xb3, /* qnum:129 -> linux:171 (KEY_CONFIG) -> xorgevdev:179 */
+  [0x82] = 0x9e, /* qnum:130 -> linux:150 (KEY_WWW) -> xorgevdev:158 */
+  [0x83] = 0xc3, /* qnum:131 -> linux:187 (KEY_F17) -> xorgevdev:195 */
+  [0x84] = 0xc5, /* qnum:132 -> linux:189 (KEY_F19) -> xorgevdev:197 */
+  [0x85] = 0x89, /* qnum:133 -> linux:129 (KEY_AGAIN) -> xorgevdev:137 */
+  [0x86] = 0x8a, /* qnum:134 -> linux:130 (KEY_PROPS) -> xorgevdev:138 */
+  [0x87] = 0x8b, /* qnum:135 -> linux:131 (KEY_UNDO) -> xorgevdev:139 */
+  [0x88] = 0xb8, /* qnum:136 -> linux:176 (KEY_EDIT) -> xorgevdev:184 */
+  [0x89] = 0xbd, /* qnum:137 -> linux:181 (KEY_NEW) -> xorgevdev:189 */
+  [0x8a] = 0xbe, /* qnum:138 -> linux:182 (KEY_REDO) -> xorgevdev:190 */
+  [0x8b] = 0x80, /* qnum:139 -> linux:120 (KEY_SCALE) -> xorgevdev:128 */
+  [0x8c] = 0x8c, /* qnum:140 -> linux:132 (KEY_FRONT) -> xorgevdev:140 */
+  [0x8d] = 0x83, /* qnum:141 -> linux:123 (KEY_HANJA) -> xorgevdev:131 */
+  [0x8e] = 0xf1, /* qnum:142 -> linux:233 (KEY_FORWARDMAIL) -> xorgevdev:241 */
+  [0x8f] = 0xba, /* qnum:143 -> linux:178 (KEY_SCROLLDOWN) -> xorgevdev:186 */
+  [0x90] = 0xad, /* qnum:144 -> linux:165 (KEY_PREVIOUSSONG) -> xorgevdev:173 */
+  [0x92] = 0xa0, /* qnum:146 -> linux:152 (KEY_SCREENLOCK) -> xorgevdev:160 */
+  [0x93] = 0x9b, /* qnum:147 -> linux:147 (KEY_XFER) -> xorgevdev:155 */
+  [0x94] = 0xe6, /* qnum:148 -> linux:222 (KEY_ALTERASE) -> xorgevdev:230 */
+  [0x95] = 0xcb, /* qnum:149 -> linux:195 (unnamed) -> xorgevdev:203 */
+  [0x96] = 0xcc, /* qnum:150 -> linux:196 (unnamed) -> xorgevdev:204 */
+  [0x97] = 0x9d, /* qnum:151 -> linux:149 (KEY_PROG2) -> xorgevdev:157 */
+  [0x98] = 0xb0, /* qnum:152 -> linux:168 (KEY_REWIND) -> xorgevdev:176 */
+  [0x99] = 0xab, /* qnum:153 -> linux:163 (KEY_NEXTSONG) -> xorgevdev:171 */
+  [0x9a] = 0xcd, /* qnum:154 -> linux:197 (unnamed) -> xorgevdev:205 */
+  [0x9b] = 0xce, /* qnum:155 -> linux:198 (unnamed) -> xorgevdev:206 */
+  [0x9c] = 0x68, /* qnum:156 -> linux:96 (KEY_KPENTER) -> xorgevdev:104 */
+  [0x9d] = 0x69, /* qnum:157 -> linux:97 (KEY_RIGHTCTRL) -> xorgevdev:105 */
+  [0x9e] = 0x93, /* qnum:158 -> linux:139 (KEY_MENU) -> xorgevdev:147 */
+  [0x9f] = 0x9c, /* qnum:159 -> linux:148 (KEY_PROG1) -> xorgevdev:156 */
+  [0xa0] = 0x79, /* qnum:160 -> linux:113 (KEY_MUTE) -> xorgevdev:121 */
+  [0xa1] = 0x94, /* qnum:161 -> linux:140 (KEY_CALC) -> xorgevdev:148 */
+  [0xa2] = 0xac, /* qnum:162 -> linux:164 (KEY_PLAYPAUSE) -> xorgevdev:172 */
+  [0xa3] = 0xa8, /* qnum:163 -> linux:160 (KEY_CLOSECD) -> xorgevdev:168 */
+  [0xa4] = 0xae, /* qnum:164 -> linux:166 (KEY_STOPCD) -> xorgevdev:174 */
+  [0xa5] = 0xd5, /* qnum:165 -> linux:205 (KEY_SUSPEND) -> xorgevdev:213 */
+  [0xa6] = 0xa2, /* qnum:166 -> linux:154 (KEY_CYCLEWINDOWS) -> xorgevdev:162 */
+  [0xa7] = 0xcf, /* qnum:167 -> linux:199 (unnamed) -> xorgevdev:207 */
+  [0xa8] = 0xd0, /* qnum:168 -> linux:200 (KEY_PLAYCD) -> xorgevdev:208 */
+  [0xa9] = 0xd1, /* qnum:169 -> linux:201 (KEY_PAUSECD) -> xorgevdev:209 */
+  [0xab] = 0xd2, /* qnum:171 -> linux:202 (KEY_PROG3) -> xorgevdev:210 */
+  [0xac] = 0xd3, /* qnum:172 -> linux:203 (KEY_PROG4) -> xorgevdev:211 */
+  [0xad] = 0xd4, /* qnum:173 -> linux:204 (KEY_DASHBOARD) -> xorgevdev:212 */
+  [0xae] = 0x7a, /* qnum:174 -> linux:114 (KEY_VOLUMEDOWN) -> xorgevdev:122 */
+  [0xaf] = 0xd6, /* qnum:175 -> linux:206 (KEY_CLOSE) -> xorgevdev:214 */
+  [0xb0] = 0x7b, /* qnum:176 -> linux:115 (KEY_VOLUMEUP) -> xorgevdev:123 */
+  [0xb1] = 0xaf, /* qnum:177 -> linux:167 (KEY_RECORD) -> xorgevdev:175 */
+  [0xb2] = 0xb4, /* qnum:178 -> linux:172 (KEY_HOMEPAGE) -> xorgevdev:180 */
+  [0xb3] = 0xd7, /* qnum:179 -> linux:207 (KEY_PLAY) -> xorgevdev:215 */
+  [0xb4] = 0xd8, /* qnum:180 -> linux:208 (KEY_FASTFORWARD) -> xorgevdev:216 */
+  [0xb5] = 0x6a, /* qnum:181 -> linux:98 (KEY_KPSLASH) -> xorgevdev:106 */
+  [0xb6] = 0xd9, /* qnum:182 -> linux:209 (KEY_BASSBOOST) -> xorgevdev:217 */
+  [0xb8] = 0x6c, /* qnum:184 -> linux:100 (KEY_RIGHTALT) -> xorgevdev:108 */
+  [0xb9] = 0xda, /* qnum:185 -> linux:210 (KEY_PRINT) -> xorgevdev:218 */
+  [0xba] = 0xdb, /* qnum:186 -> linux:211 (KEY_HP) -> xorgevdev:219 */
+  [0xbb] = 0xdc, /* qnum:187 -> linux:212 (KEY_CAMERA) -> xorgevdev:220 */
+  [0xbc] = 0x91, /* qnum:188 -> linux:137 (KEY_CUT) -> xorgevdev:145 */
+  [0xbd] = 0xdd, /* qnum:189 -> linux:213 (KEY_SOUND) -> xorgevdev:221 */
+  [0xbe] = 0xde, /* qnum:190 -> linux:214 (KEY_QUESTION) -> xorgevdev:222 */
+  [0xbf] = 0xdf, /* qnum:191 -> linux:215 (KEY_EMAIL) -> xorgevdev:223 */
+  [0xc0] = 0xe0, /* qnum:192 -> linux:216 (KEY_CHAT) -> xorgevdev:224 */
+  [0xc1] = 0x90, /* qnum:193 -> linux:136 (KEY_FIND) -> xorgevdev:144 */
+  [0xc2] = 0xe2, /* qnum:194 -> linux:218 (KEY_CONNECT) -> xorgevdev:226 */
+  [0xc3] = 0xe3, /* qnum:195 -> linux:219 (KEY_FINANCE) -> xorgevdev:227 */
+  [0xc4] = 0xe4, /* qnum:196 -> linux:220 (KEY_SPORT) -> xorgevdev:228 */
+  [0xc5] = 0xe5, /* qnum:197 -> linux:221 (KEY_SHOP) -> xorgevdev:229 */
+  [0xc6] = 0x7f, /* qnum:198 -> linux:119 (KEY_PAUSE) -> xorgevdev:127 */
+  [0xc7] = 0x6e, /* qnum:199 -> linux:102 (KEY_HOME) -> xorgevdev:110 */
+  [0xc8] = 0x6f, /* qnum:200 -> linux:103 (KEY_UP) -> xorgevdev:111 */
+  [0xc9] = 0x70, /* qnum:201 -> linux:104 (KEY_PAGEUP) -> xorgevdev:112 */
+  [0xca] = 0xe7, /* qnum:202 -> linux:223 (KEY_CANCEL) -> xorgevdev:231 */
+  [0xcb] = 0x71, /* qnum:203 -> linux:105 (KEY_LEFT) -> xorgevdev:113 */
+  [0xcc] = 0xe8, /* qnum:204 -> linux:224 (KEY_BRIGHTNESSDOWN) -> xorgevdev:232 */
+  [0xcd] = 0x72, /* qnum:205 -> linux:106 (KEY_RIGHT) -> xorgevdev:114 */
+  [0xce] = 0x7e, /* qnum:206 -> linux:118 (KEY_KPPLUSMINUS) -> xorgevdev:126 */
+  [0xcf] = 0x73, /* qnum:207 -> linux:107 (KEY_END) -> xorgevdev:115 */
+  [0xd0] = 0x74, /* qnum:208 -> linux:108 (KEY_DOWN) -> xorgevdev:116 */
+  [0xd1] = 0x75, /* qnum:209 -> linux:109 (KEY_PAGEDOWN) -> xorgevdev:117 */
+  [0xd2] = 0x76, /* qnum:210 -> linux:110 (KEY_INSERT) -> xorgevdev:118 */
+  [0xd3] = 0x77, /* qnum:211 -> linux:111 (KEY_DELETE) -> xorgevdev:119 */
+  [0xd4] = 0xe9, /* qnum:212 -> linux:225 (KEY_BRIGHTNESSUP) -> xorgevdev:233 */
+  [0xd5] = 0xf2, /* qnum:213 -> linux:234 (KEY_SAVE) -> xorgevdev:242 */
+  [0xd6] = 0xeb, /* qnum:214 -> linux:227 (KEY_SWITCHVIDEOMODE) -> xorgevdev:235 */
+  [0xd7] = 0xec, /* qnum:215 -> linux:228 (KEY_KBDILLUMTOGGLE) -> xorgevdev:236 */
+  [0xd8] = 0xed, /* qnum:216 -> linux:229 (KEY_KBDILLUMDOWN) -> xorgevdev:237 */
+  [0xd9] = 0xee, /* qnum:217 -> linux:230 (KEY_KBDILLUMUP) -> xorgevdev:238 */
+  [0xda] = 0xef, /* qnum:218 -> linux:231 (KEY_SEND) -> xorgevdev:239 */
+  [0xdb] = 0x85, /* qnum:219 -> linux:125 (KEY_LEFTMETA) -> xorgevdev:133 */
+  [0xdc] = 0x86, /* qnum:220 -> linux:126 (KEY_RIGHTMETA) -> xorgevdev:134 */
+  [0xdd] = 0x87, /* qnum:221 -> linux:127 (KEY_COMPOSE) -> xorgevdev:135 */
+  [0xde] = 0x7c, /* qnum:222 -> linux:116 (KEY_POWER) -> xorgevdev:124 */
+  [0xdf] = 0x96, /* qnum:223 -> linux:142 (KEY_SLEEP) -> xorgevdev:150 */
+  [0xe3] = 0x97, /* qnum:227 -> linux:143 (KEY_WAKEUP) -> xorgevdev:151 */
+  [0xe4] = 0xf0, /* qnum:228 -> linux:232 (KEY_REPLY) -> xorgevdev:240 */
+  [0xe5] = 0xe1, /* qnum:229 -> linux:217 (KEY_SEARCH) -> xorgevdev:225 */
+  [0xe6] = 0xa4, /* qnum:230 -> linux:156 (KEY_BOOKMARKS) -> xorgevdev:164 */
+  [0xe7] = 0xb5, /* qnum:231 -> linux:173 (KEY_REFRESH) -> xorgevdev:181 */
+  [0xe8] = 0x88, /* qnum:232 -> linux:128 (KEY_STOP) -> xorgevdev:136 */
+  [0xe9] = 0xa7, /* qnum:233 -> linux:159 (KEY_FORWARD) -> xorgevdev:167 */
+  [0xea] = 0xa6, /* qnum:234 -> linux:158 (KEY_BACK) -> xorgevdev:166 */
+  [0xeb] = 0xa5, /* qnum:235 -> linux:157 (KEY_COMPUTER) -> xorgevdev:165 */
+  [0xec] = 0xa3, /* qnum:236 -> linux:155 (KEY_MAIL) -> xorgevdev:163 */
+  [0xed] = 0xea, /* qnum:237 -> linux:226 (KEY_MEDIA) -> xorgevdev:234 */
+  [0xef] = 0x78, /* qnum:239 -> linux:112 (KEY_MACRO) -> xorgevdev:120 */
+  [0xf0] = 0xf3, /* qnum:240 -> linux:235 (KEY_DOCUMENTS) -> xorgevdev:243 */
+  [0xf1] = 0xf4, /* qnum:241 -> linux:236 (KEY_BATTERY) -> xorgevdev:244 */
+  [0xf2] = 0xf5, /* qnum:242 -> linux:237 (KEY_BLUETOOTH) -> xorgevdev:245 */
+  [0xf3] = 0xf6, /* qnum:243 -> linux:238 (KEY_WLAN) -> xorgevdev:246 */
+  [0xf4] = 0xf7, /* qnum:244 -> linux:239 (KEY_UWB) -> xorgevdev:247 */
+  [0xf5] = 0x92, /* qnum:245 -> linux:138 (KEY_HELP) -> xorgevdev:146 */
+  [0xf6] = 0xbb, /* qnum:246 -> linux:179 (KEY_KPLEFTPAREN) -> xorgevdev:187 */
+  [0xf7] = 0xc4, /* qnum:247 -> linux:188 (KEY_F18) -> xorgevdev:196 */
+  [0xf8] = 0x8d, /* qnum:248 -> linux:133 (KEY_COPY) -> xorgevdev:141 */
+  [0xf9] = 0xc8, /* qnum:249 -> linux:192 (KEY_F22) -> xorgevdev:200 */
+  [0xfb] = 0xbc, /* qnum:251 -> linux:180 (KEY_KPRIGHTPAREN) -> xorgevdev:188 */
+  [0xfd] = 0xaa, /* qnum:253 -> linux:162 (KEY_EJECTCLOSECD) -> xorgevdev:170 */
+};
+const unsigned int code_map_qnum_to_xorgevdev_len = sizeof(code_map_qnum_to_xorgevdev)/sizeof(code_map_qnum_to_xorgevdev[0]);
diff --git a/unix/x0vncserver/qnum_to_xorgkbd.c b/unix/x0vncserver/qnum_to_xorgkbd.c
new file mode 100644
index 0000000..57c2047
--- /dev/null
+++ b/unix/x0vncserver/qnum_to_xorgkbd.c
@@ -0,0 +1,121 @@
+/*
+ * This file is auto-generated from keymaps.csv on 2017-08-28 13:04
+ * Database checksum sha256(f8aeff0c3430077a350e3d7ba2b335b381bd929ac4b193413730a402ff3f0097)
+ * To re-generate, run:
+ *   keymap-gen --lang=stdc code-map keymaps.csv qnum xorgkbd
+*/
+const unsigned short code_map_qnum_to_xorgkbd[254] = {
+  [0x1] = 0x9, /* qnum:1 -> linux:1 (KEY_ESC) -> xorgkbd:9 */
+  [0x2] = 0xa, /* qnum:2 -> linux:2 (KEY_1) -> xorgkbd:10 */
+  [0x3] = 0xb, /* qnum:3 -> linux:3 (KEY_2) -> xorgkbd:11 */
+  [0x4] = 0xc, /* qnum:4 -> linux:4 (KEY_3) -> xorgkbd:12 */
+  [0x5] = 0xd, /* qnum:5 -> linux:5 (KEY_4) -> xorgkbd:13 */
+  [0x6] = 0xe, /* qnum:6 -> linux:6 (KEY_5) -> xorgkbd:14 */
+  [0x7] = 0xf, /* qnum:7 -> linux:7 (KEY_6) -> xorgkbd:15 */
+  [0x8] = 0x10, /* qnum:8 -> linux:8 (KEY_7) -> xorgkbd:16 */
+  [0x9] = 0x11, /* qnum:9 -> linux:9 (KEY_8) -> xorgkbd:17 */
+  [0xa] = 0x12, /* qnum:10 -> linux:10 (KEY_9) -> xorgkbd:18 */
+  [0xb] = 0x13, /* qnum:11 -> linux:11 (KEY_0) -> xorgkbd:19 */
+  [0xc] = 0x14, /* qnum:12 -> linux:12 (KEY_MINUS) -> xorgkbd:20 */
+  [0xd] = 0x15, /* qnum:13 -> linux:13 (KEY_EQUAL) -> xorgkbd:21 */
+  [0xe] = 0x16, /* qnum:14 -> linux:14 (KEY_BACKSPACE) -> xorgkbd:22 */
+  [0xf] = 0x17, /* qnum:15 -> linux:15 (KEY_TAB) -> xorgkbd:23 */
+  [0x10] = 0x18, /* qnum:16 -> linux:16 (KEY_Q) -> xorgkbd:24 */
+  [0x11] = 0x19, /* qnum:17 -> linux:17 (KEY_W) -> xorgkbd:25 */
+  [0x12] = 0x1a, /* qnum:18 -> linux:18 (KEY_E) -> xorgkbd:26 */
+  [0x13] = 0x1b, /* qnum:19 -> linux:19 (KEY_R) -> xorgkbd:27 */
+  [0x14] = 0x1c, /* qnum:20 -> linux:20 (KEY_T) -> xorgkbd:28 */
+  [0x15] = 0x1d, /* qnum:21 -> linux:21 (KEY_Y) -> xorgkbd:29 */
+  [0x16] = 0x1e, /* qnum:22 -> linux:22 (KEY_U) -> xorgkbd:30 */
+  [0x17] = 0x1f, /* qnum:23 -> linux:23 (KEY_I) -> xorgkbd:31 */
+  [0x18] = 0x20, /* qnum:24 -> linux:24 (KEY_O) -> xorgkbd:32 */
+  [0x19] = 0x21, /* qnum:25 -> linux:25 (KEY_P) -> xorgkbd:33 */
+  [0x1a] = 0x22, /* qnum:26 -> linux:26 (KEY_LEFTBRACE) -> xorgkbd:34 */
+  [0x1b] = 0x23, /* qnum:27 -> linux:27 (KEY_RIGHTBRACE) -> xorgkbd:35 */
+  [0x1c] = 0x24, /* qnum:28 -> linux:28 (KEY_ENTER) -> xorgkbd:36 */
+  [0x1d] = 0x25, /* qnum:29 -> linux:29 (KEY_LEFTCTRL) -> xorgkbd:37 */
+  [0x1e] = 0x26, /* qnum:30 -> linux:30 (KEY_A) -> xorgkbd:38 */
+  [0x1f] = 0x27, /* qnum:31 -> linux:31 (KEY_S) -> xorgkbd:39 */
+  [0x20] = 0x28, /* qnum:32 -> linux:32 (KEY_D) -> xorgkbd:40 */
+  [0x21] = 0x29, /* qnum:33 -> linux:33 (KEY_F) -> xorgkbd:41 */
+  [0x22] = 0x2a, /* qnum:34 -> linux:34 (KEY_G) -> xorgkbd:42 */
+  [0x23] = 0x2b, /* qnum:35 -> linux:35 (KEY_H) -> xorgkbd:43 */
+  [0x24] = 0x2c, /* qnum:36 -> linux:36 (KEY_J) -> xorgkbd:44 */
+  [0x25] = 0x2d, /* qnum:37 -> linux:37 (KEY_K) -> xorgkbd:45 */
+  [0x26] = 0x2e, /* qnum:38 -> linux:38 (KEY_L) -> xorgkbd:46 */
+  [0x27] = 0x2f, /* qnum:39 -> linux:39 (KEY_SEMICOLON) -> xorgkbd:47 */
+  [0x28] = 0x30, /* qnum:40 -> linux:40 (KEY_APOSTROPHE) -> xorgkbd:48 */
+  [0x29] = 0x31, /* qnum:41 -> linux:41 (KEY_GRAVE) -> xorgkbd:49 */
+  [0x2a] = 0x32, /* qnum:42 -> linux:42 (KEY_LEFTSHIFT) -> xorgkbd:50 */
+  [0x2b] = 0x33, /* qnum:43 -> linux:43 (KEY_BACKSLASH) -> xorgkbd:51 */
+  [0x2c] = 0x34, /* qnum:44 -> linux:44 (KEY_Z) -> xorgkbd:52 */
+  [0x2d] = 0x35, /* qnum:45 -> linux:45 (KEY_X) -> xorgkbd:53 */
+  [0x2e] = 0x36, /* qnum:46 -> linux:46 (KEY_C) -> xorgkbd:54 */
+  [0x2f] = 0x37, /* qnum:47 -> linux:47 (KEY_V) -> xorgkbd:55 */
+  [0x30] = 0x38, /* qnum:48 -> linux:48 (KEY_B) -> xorgkbd:56 */
+  [0x31] = 0x39, /* qnum:49 -> linux:49 (KEY_N) -> xorgkbd:57 */
+  [0x32] = 0x3a, /* qnum:50 -> linux:50 (KEY_M) -> xorgkbd:58 */
+  [0x33] = 0x3b, /* qnum:51 -> linux:51 (KEY_COMMA) -> xorgkbd:59 */
+  [0x34] = 0x3c, /* qnum:52 -> linux:52 (KEY_DOT) -> xorgkbd:60 */
+  [0x35] = 0x3d, /* qnum:53 -> linux:53 (KEY_SLASH) -> xorgkbd:61 */
+  [0x36] = 0x3e, /* qnum:54 -> linux:54 (KEY_RIGHTSHIFT) -> xorgkbd:62 */
+  [0x37] = 0x3f, /* qnum:55 -> linux:55 (KEY_KPASTERISK) -> xorgkbd:63 */
+  [0x38] = 0x40, /* qnum:56 -> linux:56 (KEY_LEFTALT) -> xorgkbd:64 */
+  [0x39] = 0x41, /* qnum:57 -> linux:57 (KEY_SPACE) -> xorgkbd:65 */
+  [0x3a] = 0x42, /* qnum:58 -> linux:58 (KEY_CAPSLOCK) -> xorgkbd:66 */
+  [0x3b] = 0x43, /* qnum:59 -> linux:59 (KEY_F1) -> xorgkbd:67 */
+  [0x3c] = 0x44, /* qnum:60 -> linux:60 (KEY_F2) -> xorgkbd:68 */
+  [0x3d] = 0x45, /* qnum:61 -> linux:61 (KEY_F3) -> xorgkbd:69 */
+  [0x3e] = 0x46, /* qnum:62 -> linux:62 (KEY_F4) -> xorgkbd:70 */
+  [0x3f] = 0x47, /* qnum:63 -> linux:63 (KEY_F5) -> xorgkbd:71 */
+  [0x40] = 0x48, /* qnum:64 -> linux:64 (KEY_F6) -> xorgkbd:72 */
+  [0x41] = 0x49, /* qnum:65 -> linux:65 (KEY_F7) -> xorgkbd:73 */
+  [0x42] = 0x4a, /* qnum:66 -> linux:66 (KEY_F8) -> xorgkbd:74 */
+  [0x43] = 0x4b, /* qnum:67 -> linux:67 (KEY_F9) -> xorgkbd:75 */
+  [0x44] = 0x4c, /* qnum:68 -> linux:68 (KEY_F10) -> xorgkbd:76 */
+  [0x45] = 0x4d, /* qnum:69 -> linux:69 (KEY_NUMLOCK) -> xorgkbd:77 */
+  [0x46] = 0x4e, /* qnum:70 -> linux:70 (KEY_SCROLLLOCK) -> xorgkbd:78 */
+  [0x47] = 0x4f, /* qnum:71 -> linux:71 (KEY_KP7) -> xorgkbd:79 */
+  [0x48] = 0x50, /* qnum:72 -> linux:72 (KEY_KP8) -> xorgkbd:80 */
+  [0x49] = 0x51, /* qnum:73 -> linux:73 (KEY_KP9) -> xorgkbd:81 */
+  [0x4a] = 0x52, /* qnum:74 -> linux:74 (KEY_KPMINUS) -> xorgkbd:82 */
+  [0x4b] = 0x53, /* qnum:75 -> linux:75 (KEY_KP4) -> xorgkbd:83 */
+  [0x4c] = 0x54, /* qnum:76 -> linux:76 (KEY_KP5) -> xorgkbd:84 */
+  [0x4d] = 0x55, /* qnum:77 -> linux:77 (KEY_KP6) -> xorgkbd:85 */
+  [0x4e] = 0x56, /* qnum:78 -> linux:78 (KEY_KPPLUS) -> xorgkbd:86 */
+  [0x4f] = 0x57, /* qnum:79 -> linux:79 (KEY_KP1) -> xorgkbd:87 */
+  [0x50] = 0x58, /* qnum:80 -> linux:80 (KEY_KP2) -> xorgkbd:88 */
+  [0x51] = 0x59, /* qnum:81 -> linux:81 (KEY_KP3) -> xorgkbd:89 */
+  [0x52] = 0x5a, /* qnum:82 -> linux:82 (KEY_KP0) -> xorgkbd:90 */
+  [0x53] = 0x5b, /* qnum:83 -> linux:83 (KEY_KPDOT) -> xorgkbd:91 */
+  [0x54] = 0x6f, /* qnum:84 -> linux:99 (KEY_SYSRQ) -> xorgkbd:111 */
+  [0x55] = 0x79, /* qnum:85 -> linux:186 (KEY_F16) -> xorgkbd:121 */
+  [0x56] = 0x5e, /* qnum:86 -> linux:86 (KEY_102ND) -> xorgkbd:94 */
+  [0x57] = 0x5f, /* qnum:87 -> linux:87 (KEY_F11) -> xorgkbd:95 */
+  [0x58] = 0x60, /* qnum:88 -> linux:88 (KEY_F12) -> xorgkbd:96 */
+  [0x59] = 0x7e, /* qnum:89 -> linux:117 (KEY_KPEQUAL) -> xorgkbd:126 */
+  [0x5d] = 0x76, /* qnum:93 -> linux:183 (KEY_F13) -> xorgkbd:118 */
+  [0x5e] = 0x77, /* qnum:94 -> linux:184 (KEY_F14) -> xorgkbd:119 */
+  [0x5f] = 0x78, /* qnum:95 -> linux:185 (KEY_F15) -> xorgkbd:120 */
+  [0x7d] = 0x85, /* qnum:125 -> linux:124 (KEY_YEN) -> xorgkbd:133 */
+  [0x83] = 0x7a, /* qnum:131 -> linux:187 (KEY_F17) -> xorgkbd:122 */
+  [0x9c] = 0x6c, /* qnum:156 -> linux:96 (KEY_KPENTER) -> xorgkbd:108 */
+  [0x9d] = 0x6d, /* qnum:157 -> linux:97 (KEY_RIGHTCTRL) -> xorgkbd:109 */
+  [0xb5] = 0x70, /* qnum:181 -> linux:98 (KEY_KPSLASH) -> xorgkbd:112 */
+  [0xb8] = 0x71, /* qnum:184 -> linux:100 (KEY_RIGHTALT) -> xorgkbd:113 */
+  [0xc6] = 0x6e, /* qnum:198 -> linux:119 (KEY_PAUSE) -> xorgkbd:110 */
+  [0xc7] = 0x61, /* qnum:199 -> linux:102 (KEY_HOME) -> xorgkbd:97 */
+  [0xc8] = 0x62, /* qnum:200 -> linux:103 (KEY_UP) -> xorgkbd:98 */
+  [0xc9] = 0x63, /* qnum:201 -> linux:104 (KEY_PAGEUP) -> xorgkbd:99 */
+  [0xcb] = 0x64, /* qnum:203 -> linux:105 (KEY_LEFT) -> xorgkbd:100 */
+  [0xcd] = 0x66, /* qnum:205 -> linux:106 (KEY_RIGHT) -> xorgkbd:102 */
+  [0xcf] = 0x67, /* qnum:207 -> linux:107 (KEY_END) -> xorgkbd:103 */
+  [0xd0] = 0x68, /* qnum:208 -> linux:108 (KEY_DOWN) -> xorgkbd:104 */
+  [0xd1] = 0x69, /* qnum:209 -> linux:109 (KEY_PAGEDOWN) -> xorgkbd:105 */
+  [0xd2] = 0x6a, /* qnum:210 -> linux:110 (KEY_INSERT) -> xorgkbd:106 */
+  [0xd3] = 0x6b, /* qnum:211 -> linux:111 (KEY_DELETE) -> xorgkbd:107 */
+  [0xdb] = 0x73, /* qnum:219 -> linux:125 (KEY_LEFTMETA) -> xorgkbd:115 */
+  [0xdc] = 0x74, /* qnum:220 -> linux:126 (KEY_RIGHTMETA) -> xorgkbd:116 */
+  [0xdd] = 0x75, /* qnum:221 -> linux:127 (KEY_COMPOSE) -> xorgkbd:117 */
+};
+const unsigned int code_map_qnum_to_xorgkbd_len = sizeof(code_map_qnum_to_xorgkbd)/sizeof(code_map_qnum_to_xorgkbd[0]);
diff --git a/unix/x0vncserver/x0vncserver.cxx b/unix/x0vncserver/x0vncserver.cxx
index 315d30c..d3042db 100644
--- a/unix/x0vncserver/x0vncserver.cxx
+++ b/unix/x0vncserver/x0vncserver.cxx
@@ -39,6 +39,7 @@
 #include <X11/X.h>
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
+#include <X11/XKBlib.h>
 #ifdef HAVE_XTEST
 #include <X11/extensions/XTest.h>
 #endif
@@ -54,6 +55,12 @@
 #include <x0vncserver/XPixelBuffer.h>
 #include <x0vncserver/PollingScheduler.h>
 
+extern const unsigned short code_map_qnum_to_xorgevdev[];
+extern const unsigned int code_map_qnum_to_xorgevdev_len;
+
+extern const unsigned short code_map_qnum_to_xorgkbd[];
+extern const unsigned int code_map_qnum_to_xorgkbd_len;
+
 // XXX Lynx/OS 2.3: protos for select(), bzero()
 #ifdef Lynx
 #include <sys/proto.h>
@@ -64,6 +71,14 @@
 using namespace rfb;
 using namespace network;
 
+// number of XKb indicator leds to handle
+static const int N_LEDS = 3;
+
+// order is important as it must match RFB extension
+static const char * ledNames[N_LEDS] = {
+  "Scroll Lock", "Num Lock", "Caps Lock"
+};
+
 static LogWriter vlog("Main");
 
 IntParameter pollingCycle("PollingCycle", "Milliseconds per one polling "
@@ -81,6 +96,10 @@
                                  "rejecting the connection",
                                  10);
 StringParameter hostsFile("HostsFile", "File with IP access control rules", "");
+BoolParameter rawKeyboard("RawKeyboard",
+                          "Send keyboard events straight through and "
+                          "avoid mapping them to the current keyboard "
+                          "layout", false);
 
 //
 // Allow the main loop terminate itself gracefully on receiving a signal.
@@ -144,12 +163,69 @@
   XDesktop(Display* dpy_, Geometry *geometry_)
     : dpy(dpy_), geometry(geometry_), pb(0), server(0),
       oldButtonMask(0), haveXtest(false), haveDamage(false),
-      haveXfixes(false), maxButtons(0), running(false)
+      haveXfixes(false),  maxButtons(0), running(false),
+      ledMasks(), ledState(0), codeMap(0), codeMapLen(0)
   {
+    int major, minor;
+
+    int xkbOpcode, xkbErrorBase;
+
+    major = XkbMajorVersion;
+    minor = XkbMinorVersion;
+    if (!XkbQueryExtension(dpy, &xkbOpcode, &xkbEventBase,
+                           &xkbErrorBase, &major, &minor)) {
+      vlog.error("XKEYBOARD extension not present");
+      throw Exception();
+    }
+
+    XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask,
+                    XkbIndicatorStateNotifyMask);
+
+    // figure out bit masks for the indicators we are interested in
+    for (int i = 0; i < N_LEDS; i++) {
+      Atom a;
+      int shift;
+      Bool on;
+
+      a = XInternAtom(dpy, ledNames[i], True);
+      if (!a || !XkbGetNamedIndicator(dpy, a, &shift, &on, NULL, NULL))
+        continue;
+
+      ledMasks[i] = 1u << shift;
+      vlog.debug("Mask for '%s' is 0x%x", ledNames[i], ledMasks[i]);
+      if (on)
+        ledState |= 1u << i;
+    }
+
+    // X11 unfortunately uses keyboard driver specific keycodes and provides no
+    // direct way to query this, so guess based on the keyboard mapping
+    XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
+    if (desc && desc->names) {
+      char *keycodes = XGetAtomName(dpy, desc->names->keycodes);
+
+      if (keycodes) {
+        if (strncmp("evdev", keycodes, strlen("evdev")) == 0) {
+          codeMap = code_map_qnum_to_xorgevdev;
+          codeMapLen = code_map_qnum_to_xorgevdev_len;
+          vlog.info("Using evdev codemap\n");
+        } else if (strncmp("xfree86", keycodes, strlen("xfree86")) == 0) {
+          codeMap = code_map_qnum_to_xorgkbd;
+          codeMapLen = code_map_qnum_to_xorgkbd_len;
+          vlog.info("Using xorgkbd codemap\n");
+        } else {
+          vlog.info("Unknown keycode '%s', no codemap\n", keycodes);
+        }
+        XFree(keycodes);
+      } else {
+        vlog.debug("Unable to get keycode map\n");
+      }
+
+      XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
+    }
+
 #ifdef HAVE_XTEST
     int xtestEventBase;
     int xtestErrorBase;
-    int major, minor;
 
     if (XTestQueryExtension(dpy, &xtestEventBase,
                             &xtestErrorBase, &major, &minor)) {
@@ -168,7 +244,6 @@
     int xdamageErrorBase;
 
     if (XDamageQueryExtension(dpy, &xdamageEventBase, &xdamageErrorBase)) {
-      TXWindow::setGlobalEventHandler(this);
       haveDamage = true;
     } else {
 #endif
@@ -182,8 +257,6 @@
     int xfixesErrorBase;
 
     if (XFixesQueryExtension(dpy, &xfixesEventBase, &xfixesErrorBase)) {
-      if (not haveDamage)
-        TXWindow::setGlobalEventHandler(this);
       haveXfixes = true;
     } else {
 #endif
@@ -192,6 +265,8 @@
 #ifdef HAVE_XFIXES
     }
 #endif
+
+    TXWindow::setGlobalEventHandler(this);
   }
   virtual ~XDesktop() {
     stop();
@@ -237,6 +312,8 @@
     }
 #endif
 
+    server->setLEDState(ledState);
+
     running = true;
   }
 
@@ -278,12 +355,67 @@
 #endif
   }
 
-  virtual void keyEvent(rdr::U32 key, bool down) {
 #ifdef HAVE_XTEST
-    if (!haveXtest) return;
-    int keycode = XKeysymToKeycode(dpy, key);
-    if (keycode)
-      XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
+  KeyCode XkbKeysymToKeycode(Display* dpy, KeySym keysym) {
+    XkbDescPtr xkb;
+    XkbStateRec state;
+    unsigned keycode;
+
+    xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
+    if (!xkb)
+      return 0;
+
+    XkbGetState(dpy, XkbUseCoreKbd, &state);
+
+    for (keycode = xkb->min_key_code;
+         keycode <= xkb->max_key_code;
+         keycode++) {
+      KeySym cursym;
+      unsigned int mods;
+      XkbTranslateKeyCode(xkb, keycode, state.compat_state, &mods, &cursym);
+      if (cursym == keysym)
+        break;
+    }
+
+    if (keycode > xkb->max_key_code)
+      keycode = 0;
+
+    XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
+
+    return keycode;
+  }
+#endif
+
+  virtual void keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
+#ifdef HAVE_XTEST
+    int keycode = 0;
+
+    if (!haveXtest)
+      return;
+
+    // Use scan code if provided and mapping exists
+    if (codeMap && rawKeyboard && xtcode < codeMapLen)
+        keycode = codeMap[xtcode];
+
+    if (!keycode) {
+      if (!down || (pressedKeys.find(keysym) != pressedKeys.end()))
+        keycode = pressedKeys[keysym];
+      else {
+        // XKeysymToKeycode() doesn't respect state, so we have to use
+        // something slightly more complex
+        keycode = XkbKeysymToKeycode(dpy, keysym);
+      }
+    }
+
+    if (!keycode)
+      return;
+
+    if (down)
+      pressedKeys[keysym] = keycode;
+    else
+      pressedKeys.erase(keysym);
+
+    XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
 #endif
   }
 
@@ -297,8 +429,26 @@
   // -=- TXGlobalEventHandler interface
 
   virtual bool handleGlobalEvent(XEvent* ev) {
+    if (ev->type == xkbEventBase + XkbEventCode) {
+      XkbEvent *kb = (XkbEvent *)ev;
+
+      if (kb->any.xkb_type != XkbIndicatorStateNotify)
+        return false;
+
+      vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
+
+      ledState = 0;
+      for (int i = 0; i < N_LEDS; i++) {
+        if (kb->indicators.state & ledMasks[i])
+          ledState |= 1u << i;
+      }
+
+      if (running)
+        server->setLEDState(ledState);
+
+      return true;
 #ifdef HAVE_XDAMAGE
-    if (ev->type == xdamageEventBase) {
+    } else if (ev->type == xdamageEventBase) {
       XDamageNotifyEvent* dev;
       Rect rect;
 
@@ -310,11 +460,9 @@
       server->add_changed(rect);
 
       return true;
-    }
 #endif
-
 #ifdef HAVE_XFIXES
-    if (ev->type == xfixesEventBase + XFixesCursorNotify) {
+    } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
       XFixesCursorNotifyEvent* cev;
       XFixesCursorImage *cim;
 
@@ -369,8 +517,8 @@
       delete [] cursorData;
       XFree(cim);
       return true;
-    }
 #endif
+    }
 
     return false;
   }
@@ -385,14 +533,20 @@
   bool haveDamage;
   bool haveXfixes;
   int maxButtons;
+  std::map<KeySym, KeyCode> pressedKeys;
   bool running;
 #ifdef HAVE_XDAMAGE
   Damage damage;
   int xdamageEventBase;
 #endif
+  int xkbEventBase;
 #ifdef HAVE_XFIXES
   int xfixesEventBase;
 #endif
+  int ledMasks[N_LEDS];
+  unsigned ledState;
+  const unsigned short *codeMap;
+  unsigned codeMapLen;
 };
 
 
diff --git a/unix/x0vncserver/x0vncserver.man b/unix/x0vncserver/x0vncserver.man
index 095f6db..804a70f 100644
--- a/unix/x0vncserver/x0vncserver.man
+++ b/unix/x0vncserver/x0vncserver.man
@@ -168,6 +168,13 @@
 `greater than' characters).
 .
 .TP
+.B RawKeyboard
+Send keyboard events straight through and avoid mapping them to the current
+keyboard layout. This effectively makes the keyboard behave according to the
+layout configured on the server instead of the layout configured on the
+client. Default is off.
+.
+.TP
 .B Protocol3.3
 Always use RFB protocol version 3.3 for backwards compatibility with
 badly-behaved clients.  Default is off.
diff --git a/unix/xserver/hw/vnc/Input.c b/unix/xserver/hw/vnc/Input.c
index 33e8604..a50f33a 100644
--- a/unix/xserver/hw/vnc/Input.c
+++ b/unix/xserver/hw/vnc/Input.c
@@ -44,6 +44,11 @@
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
 
+extern const unsigned short code_map_qnum_to_xorgevdev[];
+extern const unsigned int code_map_qnum_to_xorgevdev_len;
+extern const unsigned short code_map_qnum_to_xorgkbd[];
+extern const unsigned int code_map_qnum_to_xorgkbd_len;
+
 #define BUTTONS 7
 
 /* Event queue is shared between all devices. */
@@ -57,6 +62,9 @@
 static int oldButtonMask;
 static int cursorPosX, cursorPosY;
 
+static const unsigned short *codeMap;
+static unsigned int codeMapLen;
+
 static KeySym pressedKeys[256];
 
 static int vncPointerProc(DeviceIntPtr pDevice, int onoff);
@@ -64,6 +72,8 @@
                             void * ctrl, int class);
 static int vncKeyboardProc(DeviceIntPtr pDevice, int onoff);
 
+static void vncKeysymKeyboardEvent(KeySym keysym, int down);
+
 #define LOG_NAME "Input"
 
 #define LOG_ERROR(...) vncLogError(LOG_NAME, __VA_ARGS__)
@@ -88,6 +98,19 @@
 	if ((vncPointerDev != NULL) || (vncKeyboardDev != NULL))
 		return;
 
+	/*
+	 * On Linux we try to provide the same key codes as Xorg with
+	 * the evdev driver. On other platforms we mimic the older
+	 * Xorg KBD driver.
+	 */
+#ifdef __linux__
+	codeMap = code_map_qnum_to_xorgevdev;
+	codeMapLen = code_map_qnum_to_xorgevdev_len;
+#else
+	codeMap = code_map_qnum_to_xorgkbd;
+	codeMapLen = code_map_qnum_to_xorgkbd_len;
+#endif
+
 	for (i = 0;i < 256;i++)
 		pressedKeys[i] = NoSymbol;
 
@@ -273,6 +296,11 @@
 		vncBell();
 }
 
+static void vncKeyboardCtrl(DeviceIntPtr pDevice, KeybdCtrl *ctrl)
+{
+	vncSetLEDState(ctrl->leds);
+}
+
 static int vncKeyboardProc(DeviceIntPtr pDevice, int onoff)
 {
 	DevicePtr pDev = (DevicePtr)pDevice;
@@ -280,7 +308,7 @@
 	switch (onoff) {
 	case DEVICE_INIT:
 		InitKeyboardDeviceStruct(pDevice, NULL, vncKeyboardBell,
-					 (KbdCtrlProcPtr)NoopDDA);
+					 vncKeyboardCtrl);
 		break;
 	case DEVICE_ON:
 		pDev->on = TRUE;
@@ -317,6 +345,48 @@
 #endif
 }
 
+/*
+ * vncKeyboardEvent() - add X11 events for the given RFB key event
+ */
+void vncKeyboardEvent(KeySym keysym, unsigned xtcode, int down)
+{
+	/* Simple case: the client has specified the key */
+	if (xtcode && xtcode < codeMapLen) {
+		int keycode;
+
+		keycode = codeMap[xtcode];
+		if (!keycode) {
+			/*
+			 * Figure something out based on keysym if we
+			 * cannot find a mapping.
+			 */
+			if (keysym)
+				vncKeysymKeyboardEvent(keysym, down);
+			return;
+		}
+
+		/*
+		 * We update the state table in case we get a mix of
+		 * events with and without key codes.
+		 */
+		if (down)
+			pressedKeys[keycode] = keysym;
+		else
+			pressedKeys[keycode] = NoSymbol;
+
+		pressKey(vncKeyboardDev, keycode, down, "raw keycode");
+		mieqProcessInputEvents();
+		return;
+	}
+
+	/*
+	 * Advanced case: We have to figure out a sequence of keys that
+	 *                result in the given keysym
+	 */
+	if (keysym)
+		vncKeysymKeyboardEvent(keysym, down);
+}
+
 /* altKeysym is a table of alternative keysyms which have the same meaning. */
 
 static struct altKeysym_t {
@@ -367,14 +437,14 @@
 };
 
 /*
- * vncKeyboardEvent() - work out the best keycode corresponding to the keysym
- * sent by the viewer. This is basically impossible in the general case, but
- * we make a best effort by assuming that all useful keysyms can be reached
- * using just the Shift and Level 3 (AltGr) modifiers. For core keyboards this
- * is basically always true, and should be true for most sane, western XKB
- * layouts.
+ * vncKeysymKeyboardEvent() - work out the best keycode corresponding
+ * to the keysym sent by the viewer. This is basically impossible in
+ * the general case, but we make a best effort by assuming that all
+ * useful keysyms can be reached using just the Shift and
+ * Level 3 (AltGr) modifiers. For core keyboards this is basically
+ * always true, and should be true for most sane, western XKB layouts.
  */
-void vncKeyboardEvent(KeySym keysym, int down)
+static void vncKeysymKeyboardEvent(KeySym keysym, int down)
 {
 	int i;
 	unsigned state, new_state;
@@ -437,12 +507,6 @@
 		}
 	}
 
-	/* 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 11e8871..a9d067f 100644
--- a/unix/xserver/hw/vnc/Input.h
+++ b/unix/xserver/hw/vnc/Input.h
@@ -36,7 +36,7 @@
 void vncPointerMove(int x, int y);
 void vncGetPointerPos(int *x, int *y);
 
-void vncKeyboardEvent(KeySym keysym, int down);
+void vncKeyboardEvent(KeySym keysym, unsigned xtcode, int down);
 
 /* Backend dependent functions below here */
 
@@ -53,8 +53,6 @@
 
 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 2a3f7af..a9bd11d 100644
--- a/unix/xserver/hw/vnc/InputXKB.c
+++ b/unix/xserver/hw/vnc/InputXKB.c
@@ -485,23 +485,6 @@
 	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;
diff --git a/unix/xserver/hw/vnc/Makefile.am b/unix/xserver/hw/vnc/Makefile.am
index 0d6a4ac..24a40b3 100644
--- a/unix/xserver/hw/vnc/Makefile.am
+++ b/unix/xserver/hw/vnc/Makefile.am
@@ -18,7 +18,7 @@
 libvnccommon_la_SOURCES = $(HDRS) \
 	vncExt.c vncExtInit.cc vncHooks.c vncSelection.c \
 	vncBlockHandler.c XorgGlue.c RFBGlue.cc XserverDesktop.cc \
-	Input.c InputXKB.c
+	Input.c InputXKB.c qnum_to_xorgevdev.c qnum_to_xorgkbd.c
 
 libvnccommon_la_CPPFLAGS = -DVENDOR_RELEASE="$(VENDOR_RELEASE)" \
 	-DVENDOR_STRING="\"$(VENDOR_STRING)\"" -I$(TIGERVNC_SRCDIR)/common -UHAVE_CONFIG_H \
diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc
index 4836782..2131db6 100644
--- a/unix/xserver/hw/vnc/XserverDesktop.cc
+++ b/unix/xserver/hw/vnc/XserverDesktop.cc
@@ -53,6 +53,11 @@
 
 static LogWriter vlog("XserverDesktop");
 
+BoolParameter rawKeyboard("RawKeyboard",
+                          "Send keyboard events straight through and "
+                          "avoid mapping them to the current keyboard "
+                          "layout", false);
+
 class FileHTTPServer : public rfb::HTTPServer {
 public:
   FileHTTPServer(XserverDesktop* d) : desktop(d) {}
@@ -322,6 +327,11 @@
   server->bell();
 }
 
+void XserverDesktop::setLEDState(unsigned int state)
+{
+  server->setLEDState(state);
+}
+
 void XserverDesktop::serverCutText(const char* str, int len)
 {
   try {
@@ -766,7 +776,10 @@
   }
 }
 
-void XserverDesktop::keyEvent(rdr::U32 keysym, bool down)
+void XserverDesktop::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down)
 {
-  vncKeyboardEvent(keysym, down);
+  if (!rawKeyboard)
+    keycode = 0;
+
+  vncKeyboardEvent(keysym, keycode, down);
 }
diff --git a/unix/xserver/hw/vnc/XserverDesktop.h b/unix/xserver/hw/vnc/XserverDesktop.h
index c766c26..7f7823a 100644
--- a/unix/xserver/hw/vnc/XserverDesktop.h
+++ b/unix/xserver/hw/vnc/XserverDesktop.h
@@ -63,6 +63,7 @@
   void setFramebuffer(int w, int h, void* fbptr, int stride);
   void refreshScreenLayout();
   void bell();
+  void setLEDState(unsigned int state);
   void serverCutText(const char* str, int len);
   void setDesktopName(const char* name);
   void setCursor(int width, int height, int hotX, int hotY,
@@ -88,7 +89,7 @@
 
   // 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/unix/xserver/hw/vnc/Xvnc.man b/unix/xserver/hw/vnc/Xvnc.man
index 04e8f94..36777c2 100644
--- a/unix/xserver/hw/vnc/Xvnc.man
+++ b/unix/xserver/hw/vnc/Xvnc.man
@@ -294,8 +294,10 @@
 where
 .I char
 is a hexadecimal keysym. For example, to exchange the " and @ symbols you would specify the following:
-.IP "" 10
+
+.RS 10
 RemapKeys=0x22<>0x40
+.RE
 .
 .TP
 .B \-AvoidShiftNumLock
@@ -305,6 +307,13 @@
 (e.g. a Return instead of a keypad Enter).
 .
 .TP
+.B \-RawKeyboard
+Send keyboard events straight through and avoid mapping them to the current
+keyboard layout. This effectively makes the keyboard behave according to the
+layout configured on the server instead of the layout configured on the
+client. Default is off.
+.
+.TP
 .B \-AllowOverride
 Comma separated list of parameters that can be modified using VNC extension.
 Parameters can be modified for example using \fBvncconfig\fP(1) program from
diff --git a/unix/xserver/hw/vnc/qnum_to_xorgevdev.c b/unix/xserver/hw/vnc/qnum_to_xorgevdev.c
new file mode 100644
index 0000000..357c88d
--- /dev/null
+++ b/unix/xserver/hw/vnc/qnum_to_xorgevdev.c
@@ -0,0 +1,245 @@
+/*
+ * This file is auto-generated from keymaps.csv on 2017-08-28 13:03
+ * Database checksum sha256(f8aeff0c3430077a350e3d7ba2b335b381bd929ac4b193413730a402ff3f0097)
+ * To re-generate, run:
+ *   keymap-gen --lang=stdc code-map keymaps.csv qnum xorgevdev
+*/
+const unsigned short code_map_qnum_to_xorgevdev[254] = {
+  [0x1] = 0x9, /* qnum:1 -> linux:1 (KEY_ESC) -> xorgevdev:9 */
+  [0x2] = 0xa, /* qnum:2 -> linux:2 (KEY_1) -> xorgevdev:10 */
+  [0x3] = 0xb, /* qnum:3 -> linux:3 (KEY_2) -> xorgevdev:11 */
+  [0x4] = 0xc, /* qnum:4 -> linux:4 (KEY_3) -> xorgevdev:12 */
+  [0x5] = 0xd, /* qnum:5 -> linux:5 (KEY_4) -> xorgevdev:13 */
+  [0x6] = 0xe, /* qnum:6 -> linux:6 (KEY_5) -> xorgevdev:14 */
+  [0x7] = 0xf, /* qnum:7 -> linux:7 (KEY_6) -> xorgevdev:15 */
+  [0x8] = 0x10, /* qnum:8 -> linux:8 (KEY_7) -> xorgevdev:16 */
+  [0x9] = 0x11, /* qnum:9 -> linux:9 (KEY_8) -> xorgevdev:17 */
+  [0xa] = 0x12, /* qnum:10 -> linux:10 (KEY_9) -> xorgevdev:18 */
+  [0xb] = 0x13, /* qnum:11 -> linux:11 (KEY_0) -> xorgevdev:19 */
+  [0xc] = 0x14, /* qnum:12 -> linux:12 (KEY_MINUS) -> xorgevdev:20 */
+  [0xd] = 0x15, /* qnum:13 -> linux:13 (KEY_EQUAL) -> xorgevdev:21 */
+  [0xe] = 0x16, /* qnum:14 -> linux:14 (KEY_BACKSPACE) -> xorgevdev:22 */
+  [0xf] = 0x17, /* qnum:15 -> linux:15 (KEY_TAB) -> xorgevdev:23 */
+  [0x10] = 0x18, /* qnum:16 -> linux:16 (KEY_Q) -> xorgevdev:24 */
+  [0x11] = 0x19, /* qnum:17 -> linux:17 (KEY_W) -> xorgevdev:25 */
+  [0x12] = 0x1a, /* qnum:18 -> linux:18 (KEY_E) -> xorgevdev:26 */
+  [0x13] = 0x1b, /* qnum:19 -> linux:19 (KEY_R) -> xorgevdev:27 */
+  [0x14] = 0x1c, /* qnum:20 -> linux:20 (KEY_T) -> xorgevdev:28 */
+  [0x15] = 0x1d, /* qnum:21 -> linux:21 (KEY_Y) -> xorgevdev:29 */
+  [0x16] = 0x1e, /* qnum:22 -> linux:22 (KEY_U) -> xorgevdev:30 */
+  [0x17] = 0x1f, /* qnum:23 -> linux:23 (KEY_I) -> xorgevdev:31 */
+  [0x18] = 0x20, /* qnum:24 -> linux:24 (KEY_O) -> xorgevdev:32 */
+  [0x19] = 0x21, /* qnum:25 -> linux:25 (KEY_P) -> xorgevdev:33 */
+  [0x1a] = 0x22, /* qnum:26 -> linux:26 (KEY_LEFTBRACE) -> xorgevdev:34 */
+  [0x1b] = 0x23, /* qnum:27 -> linux:27 (KEY_RIGHTBRACE) -> xorgevdev:35 */
+  [0x1c] = 0x24, /* qnum:28 -> linux:28 (KEY_ENTER) -> xorgevdev:36 */
+  [0x1d] = 0x25, /* qnum:29 -> linux:29 (KEY_LEFTCTRL) -> xorgevdev:37 */
+  [0x1e] = 0x26, /* qnum:30 -> linux:30 (KEY_A) -> xorgevdev:38 */
+  [0x1f] = 0x27, /* qnum:31 -> linux:31 (KEY_S) -> xorgevdev:39 */
+  [0x20] = 0x28, /* qnum:32 -> linux:32 (KEY_D) -> xorgevdev:40 */
+  [0x21] = 0x29, /* qnum:33 -> linux:33 (KEY_F) -> xorgevdev:41 */
+  [0x22] = 0x2a, /* qnum:34 -> linux:34 (KEY_G) -> xorgevdev:42 */
+  [0x23] = 0x2b, /* qnum:35 -> linux:35 (KEY_H) -> xorgevdev:43 */
+  [0x24] = 0x2c, /* qnum:36 -> linux:36 (KEY_J) -> xorgevdev:44 */
+  [0x25] = 0x2d, /* qnum:37 -> linux:37 (KEY_K) -> xorgevdev:45 */
+  [0x26] = 0x2e, /* qnum:38 -> linux:38 (KEY_L) -> xorgevdev:46 */
+  [0x27] = 0x2f, /* qnum:39 -> linux:39 (KEY_SEMICOLON) -> xorgevdev:47 */
+  [0x28] = 0x30, /* qnum:40 -> linux:40 (KEY_APOSTROPHE) -> xorgevdev:48 */
+  [0x29] = 0x31, /* qnum:41 -> linux:41 (KEY_GRAVE) -> xorgevdev:49 */
+  [0x2a] = 0x32, /* qnum:42 -> linux:42 (KEY_LEFTSHIFT) -> xorgevdev:50 */
+  [0x2b] = 0x33, /* qnum:43 -> linux:43 (KEY_BACKSLASH) -> xorgevdev:51 */
+  [0x2c] = 0x34, /* qnum:44 -> linux:44 (KEY_Z) -> xorgevdev:52 */
+  [0x2d] = 0x35, /* qnum:45 -> linux:45 (KEY_X) -> xorgevdev:53 */
+  [0x2e] = 0x36, /* qnum:46 -> linux:46 (KEY_C) -> xorgevdev:54 */
+  [0x2f] = 0x37, /* qnum:47 -> linux:47 (KEY_V) -> xorgevdev:55 */
+  [0x30] = 0x38, /* qnum:48 -> linux:48 (KEY_B) -> xorgevdev:56 */
+  [0x31] = 0x39, /* qnum:49 -> linux:49 (KEY_N) -> xorgevdev:57 */
+  [0x32] = 0x3a, /* qnum:50 -> linux:50 (KEY_M) -> xorgevdev:58 */
+  [0x33] = 0x3b, /* qnum:51 -> linux:51 (KEY_COMMA) -> xorgevdev:59 */
+  [0x34] = 0x3c, /* qnum:52 -> linux:52 (KEY_DOT) -> xorgevdev:60 */
+  [0x35] = 0x3d, /* qnum:53 -> linux:53 (KEY_SLASH) -> xorgevdev:61 */
+  [0x36] = 0x3e, /* qnum:54 -> linux:54 (KEY_RIGHTSHIFT) -> xorgevdev:62 */
+  [0x37] = 0x3f, /* qnum:55 -> linux:55 (KEY_KPASTERISK) -> xorgevdev:63 */
+  [0x38] = 0x40, /* qnum:56 -> linux:56 (KEY_LEFTALT) -> xorgevdev:64 */
+  [0x39] = 0x41, /* qnum:57 -> linux:57 (KEY_SPACE) -> xorgevdev:65 */
+  [0x3a] = 0x42, /* qnum:58 -> linux:58 (KEY_CAPSLOCK) -> xorgevdev:66 */
+  [0x3b] = 0x43, /* qnum:59 -> linux:59 (KEY_F1) -> xorgevdev:67 */
+  [0x3c] = 0x44, /* qnum:60 -> linux:60 (KEY_F2) -> xorgevdev:68 */
+  [0x3d] = 0x45, /* qnum:61 -> linux:61 (KEY_F3) -> xorgevdev:69 */
+  [0x3e] = 0x46, /* qnum:62 -> linux:62 (KEY_F4) -> xorgevdev:70 */
+  [0x3f] = 0x47, /* qnum:63 -> linux:63 (KEY_F5) -> xorgevdev:71 */
+  [0x40] = 0x48, /* qnum:64 -> linux:64 (KEY_F6) -> xorgevdev:72 */
+  [0x41] = 0x49, /* qnum:65 -> linux:65 (KEY_F7) -> xorgevdev:73 */
+  [0x42] = 0x4a, /* qnum:66 -> linux:66 (KEY_F8) -> xorgevdev:74 */
+  [0x43] = 0x4b, /* qnum:67 -> linux:67 (KEY_F9) -> xorgevdev:75 */
+  [0x44] = 0x4c, /* qnum:68 -> linux:68 (KEY_F10) -> xorgevdev:76 */
+  [0x45] = 0x4d, /* qnum:69 -> linux:69 (KEY_NUMLOCK) -> xorgevdev:77 */
+  [0x46] = 0x4e, /* qnum:70 -> linux:70 (KEY_SCROLLLOCK) -> xorgevdev:78 */
+  [0x47] = 0x4f, /* qnum:71 -> linux:71 (KEY_KP7) -> xorgevdev:79 */
+  [0x48] = 0x50, /* qnum:72 -> linux:72 (KEY_KP8) -> xorgevdev:80 */
+  [0x49] = 0x51, /* qnum:73 -> linux:73 (KEY_KP9) -> xorgevdev:81 */
+  [0x4a] = 0x52, /* qnum:74 -> linux:74 (KEY_KPMINUS) -> xorgevdev:82 */
+  [0x4b] = 0x53, /* qnum:75 -> linux:75 (KEY_KP4) -> xorgevdev:83 */
+  [0x4c] = 0x54, /* qnum:76 -> linux:76 (KEY_KP5) -> xorgevdev:84 */
+  [0x4d] = 0x55, /* qnum:77 -> linux:77 (KEY_KP6) -> xorgevdev:85 */
+  [0x4e] = 0x56, /* qnum:78 -> linux:78 (KEY_KPPLUS) -> xorgevdev:86 */
+  [0x4f] = 0x57, /* qnum:79 -> linux:79 (KEY_KP1) -> xorgevdev:87 */
+  [0x50] = 0x58, /* qnum:80 -> linux:80 (KEY_KP2) -> xorgevdev:88 */
+  [0x51] = 0x59, /* qnum:81 -> linux:81 (KEY_KP3) -> xorgevdev:89 */
+  [0x52] = 0x5a, /* qnum:82 -> linux:82 (KEY_KP0) -> xorgevdev:90 */
+  [0x53] = 0x5b, /* qnum:83 -> linux:83 (KEY_KPDOT) -> xorgevdev:91 */
+  [0x54] = 0x6b, /* qnum:84 -> linux:99 (KEY_SYSRQ) -> xorgevdev:107 */
+  [0x55] = 0xc2, /* qnum:85 -> linux:186 (KEY_F16) -> xorgevdev:194 */
+  [0x56] = 0x5e, /* qnum:86 -> linux:86 (KEY_102ND) -> xorgevdev:94 */
+  [0x57] = 0x5f, /* qnum:87 -> linux:87 (KEY_F11) -> xorgevdev:95 */
+  [0x58] = 0x60, /* qnum:88 -> linux:88 (KEY_F12) -> xorgevdev:96 */
+  [0x59] = 0x7d, /* qnum:89 -> linux:117 (KEY_KPEQUAL) -> xorgevdev:125 */
+  [0x5a] = 0xc6, /* qnum:90 -> linux:190 (KEY_F20) -> xorgevdev:198 */
+  [0x5b] = 0x6d, /* qnum:91 -> linux:101 (KEY_LINEFEED) -> xorgevdev:109 */
+  [0x5c] = 0x67, /* qnum:92 -> linux:95 (KEY_KPJPCOMMA) -> xorgevdev:103 */
+  [0x5d] = 0xbf, /* qnum:93 -> linux:183 (KEY_F13) -> xorgevdev:191 */
+  [0x5e] = 0xc0, /* qnum:94 -> linux:184 (KEY_F14) -> xorgevdev:192 */
+  [0x5f] = 0xc1, /* qnum:95 -> linux:185 (KEY_F15) -> xorgevdev:193 */
+  [0x63] = 0xb1, /* qnum:99 -> linux:169 (KEY_PHONE) -> xorgevdev:177 */
+  [0x64] = 0x8e, /* qnum:100 -> linux:134 (KEY_OPEN) -> xorgevdev:142 */
+  [0x65] = 0x8f, /* qnum:101 -> linux:135 (KEY_PASTE) -> xorgevdev:143 */
+  [0x66] = 0x95, /* qnum:102 -> linux:141 (KEY_SETUP) -> xorgevdev:149 */
+  [0x67] = 0x98, /* qnum:103 -> linux:144 (KEY_FILE) -> xorgevdev:152 */
+  [0x68] = 0x99, /* qnum:104 -> linux:145 (KEY_SENDFILE) -> xorgevdev:153 */
+  [0x69] = 0x9a, /* qnum:105 -> linux:146 (KEY_DELETEFILE) -> xorgevdev:154 */
+  [0x6a] = 0x9f, /* qnum:106 -> linux:151 (KEY_MSDOS) -> xorgevdev:159 */
+  [0x6b] = 0xa1, /* qnum:107 -> linux:153 (KEY_DIRECTION) -> xorgevdev:161 */
+  [0x6c] = 0xa9, /* qnum:108 -> linux:161 (KEY_EJECTCD) -> xorgevdev:169 */
+  [0x6d] = 0xc9, /* qnum:109 -> linux:193 (KEY_F23) -> xorgevdev:201 */
+  [0x6f] = 0xca, /* qnum:111 -> linux:194 (KEY_F24) -> xorgevdev:202 */
+  [0x70] = 0xb2, /* qnum:112 -> linux:170 (KEY_ISO) -> xorgevdev:178 */
+  [0x71] = 0xb6, /* qnum:113 -> linux:174 (KEY_EXIT) -> xorgevdev:182 */
+  [0x72] = 0xb7, /* qnum:114 -> linux:175 (KEY_MOVE) -> xorgevdev:183 */
+  [0x73] = 0x61, /* qnum:115 -> linux:89 (KEY_RO) -> xorgevdev:97 */
+  [0x74] = 0xc7, /* qnum:116 -> linux:191 (KEY_F21) -> xorgevdev:199 */
+  [0x75] = 0xb9, /* qnum:117 -> linux:177 (KEY_SCROLLUP) -> xorgevdev:185 */
+  [0x76] = 0x5d, /* qnum:118 -> linux:85 (KEY_ZENKAKUHANKAKU) -> xorgevdev:93 */
+  [0x77] = 0x63, /* qnum:119 -> linux:91 (KEY_HIRAGANA) -> xorgevdev:99 */
+  [0x78] = 0x62, /* qnum:120 -> linux:90 (KEY_KATAKANA) -> xorgevdev:98 */
+  [0x79] = 0x64, /* qnum:121 -> linux:92 (KEY_HENKAN) -> xorgevdev:100 */
+  [0x7b] = 0x66, /* qnum:123 -> linux:94 (KEY_MUHENKAN) -> xorgevdev:102 */
+  [0x7d] = 0x84, /* qnum:125 -> linux:124 (KEY_YEN) -> xorgevdev:132 */
+  [0x7e] = 0x81, /* qnum:126 -> linux:121 (KEY_KPCOMMA) -> xorgevdev:129 */
+  [0x81] = 0xb3, /* qnum:129 -> linux:171 (KEY_CONFIG) -> xorgevdev:179 */
+  [0x82] = 0x9e, /* qnum:130 -> linux:150 (KEY_WWW) -> xorgevdev:158 */
+  [0x83] = 0xc3, /* qnum:131 -> linux:187 (KEY_F17) -> xorgevdev:195 */
+  [0x84] = 0xc5, /* qnum:132 -> linux:189 (KEY_F19) -> xorgevdev:197 */
+  [0x85] = 0x89, /* qnum:133 -> linux:129 (KEY_AGAIN) -> xorgevdev:137 */
+  [0x86] = 0x8a, /* qnum:134 -> linux:130 (KEY_PROPS) -> xorgevdev:138 */
+  [0x87] = 0x8b, /* qnum:135 -> linux:131 (KEY_UNDO) -> xorgevdev:139 */
+  [0x88] = 0xb8, /* qnum:136 -> linux:176 (KEY_EDIT) -> xorgevdev:184 */
+  [0x89] = 0xbd, /* qnum:137 -> linux:181 (KEY_NEW) -> xorgevdev:189 */
+  [0x8a] = 0xbe, /* qnum:138 -> linux:182 (KEY_REDO) -> xorgevdev:190 */
+  [0x8b] = 0x80, /* qnum:139 -> linux:120 (KEY_SCALE) -> xorgevdev:128 */
+  [0x8c] = 0x8c, /* qnum:140 -> linux:132 (KEY_FRONT) -> xorgevdev:140 */
+  [0x8d] = 0x83, /* qnum:141 -> linux:123 (KEY_HANJA) -> xorgevdev:131 */
+  [0x8e] = 0xf1, /* qnum:142 -> linux:233 (KEY_FORWARDMAIL) -> xorgevdev:241 */
+  [0x8f] = 0xba, /* qnum:143 -> linux:178 (KEY_SCROLLDOWN) -> xorgevdev:186 */
+  [0x90] = 0xad, /* qnum:144 -> linux:165 (KEY_PREVIOUSSONG) -> xorgevdev:173 */
+  [0x92] = 0xa0, /* qnum:146 -> linux:152 (KEY_SCREENLOCK) -> xorgevdev:160 */
+  [0x93] = 0x9b, /* qnum:147 -> linux:147 (KEY_XFER) -> xorgevdev:155 */
+  [0x94] = 0xe6, /* qnum:148 -> linux:222 (KEY_ALTERASE) -> xorgevdev:230 */
+  [0x95] = 0xcb, /* qnum:149 -> linux:195 (unnamed) -> xorgevdev:203 */
+  [0x96] = 0xcc, /* qnum:150 -> linux:196 (unnamed) -> xorgevdev:204 */
+  [0x97] = 0x9d, /* qnum:151 -> linux:149 (KEY_PROG2) -> xorgevdev:157 */
+  [0x98] = 0xb0, /* qnum:152 -> linux:168 (KEY_REWIND) -> xorgevdev:176 */
+  [0x99] = 0xab, /* qnum:153 -> linux:163 (KEY_NEXTSONG) -> xorgevdev:171 */
+  [0x9a] = 0xcd, /* qnum:154 -> linux:197 (unnamed) -> xorgevdev:205 */
+  [0x9b] = 0xce, /* qnum:155 -> linux:198 (unnamed) -> xorgevdev:206 */
+  [0x9c] = 0x68, /* qnum:156 -> linux:96 (KEY_KPENTER) -> xorgevdev:104 */
+  [0x9d] = 0x69, /* qnum:157 -> linux:97 (KEY_RIGHTCTRL) -> xorgevdev:105 */
+  [0x9e] = 0x93, /* qnum:158 -> linux:139 (KEY_MENU) -> xorgevdev:147 */
+  [0x9f] = 0x9c, /* qnum:159 -> linux:148 (KEY_PROG1) -> xorgevdev:156 */
+  [0xa0] = 0x79, /* qnum:160 -> linux:113 (KEY_MUTE) -> xorgevdev:121 */
+  [0xa1] = 0x94, /* qnum:161 -> linux:140 (KEY_CALC) -> xorgevdev:148 */
+  [0xa2] = 0xac, /* qnum:162 -> linux:164 (KEY_PLAYPAUSE) -> xorgevdev:172 */
+  [0xa3] = 0xa8, /* qnum:163 -> linux:160 (KEY_CLOSECD) -> xorgevdev:168 */
+  [0xa4] = 0xae, /* qnum:164 -> linux:166 (KEY_STOPCD) -> xorgevdev:174 */
+  [0xa5] = 0xd5, /* qnum:165 -> linux:205 (KEY_SUSPEND) -> xorgevdev:213 */
+  [0xa6] = 0xa2, /* qnum:166 -> linux:154 (KEY_CYCLEWINDOWS) -> xorgevdev:162 */
+  [0xa7] = 0xcf, /* qnum:167 -> linux:199 (unnamed) -> xorgevdev:207 */
+  [0xa8] = 0xd0, /* qnum:168 -> linux:200 (KEY_PLAYCD) -> xorgevdev:208 */
+  [0xa9] = 0xd1, /* qnum:169 -> linux:201 (KEY_PAUSECD) -> xorgevdev:209 */
+  [0xab] = 0xd2, /* qnum:171 -> linux:202 (KEY_PROG3) -> xorgevdev:210 */
+  [0xac] = 0xd3, /* qnum:172 -> linux:203 (KEY_PROG4) -> xorgevdev:211 */
+  [0xad] = 0xd4, /* qnum:173 -> linux:204 (KEY_DASHBOARD) -> xorgevdev:212 */
+  [0xae] = 0x7a, /* qnum:174 -> linux:114 (KEY_VOLUMEDOWN) -> xorgevdev:122 */
+  [0xaf] = 0xd6, /* qnum:175 -> linux:206 (KEY_CLOSE) -> xorgevdev:214 */
+  [0xb0] = 0x7b, /* qnum:176 -> linux:115 (KEY_VOLUMEUP) -> xorgevdev:123 */
+  [0xb1] = 0xaf, /* qnum:177 -> linux:167 (KEY_RECORD) -> xorgevdev:175 */
+  [0xb2] = 0xb4, /* qnum:178 -> linux:172 (KEY_HOMEPAGE) -> xorgevdev:180 */
+  [0xb3] = 0xd7, /* qnum:179 -> linux:207 (KEY_PLAY) -> xorgevdev:215 */
+  [0xb4] = 0xd8, /* qnum:180 -> linux:208 (KEY_FASTFORWARD) -> xorgevdev:216 */
+  [0xb5] = 0x6a, /* qnum:181 -> linux:98 (KEY_KPSLASH) -> xorgevdev:106 */
+  [0xb6] = 0xd9, /* qnum:182 -> linux:209 (KEY_BASSBOOST) -> xorgevdev:217 */
+  [0xb8] = 0x6c, /* qnum:184 -> linux:100 (KEY_RIGHTALT) -> xorgevdev:108 */
+  [0xb9] = 0xda, /* qnum:185 -> linux:210 (KEY_PRINT) -> xorgevdev:218 */
+  [0xba] = 0xdb, /* qnum:186 -> linux:211 (KEY_HP) -> xorgevdev:219 */
+  [0xbb] = 0xdc, /* qnum:187 -> linux:212 (KEY_CAMERA) -> xorgevdev:220 */
+  [0xbc] = 0x91, /* qnum:188 -> linux:137 (KEY_CUT) -> xorgevdev:145 */
+  [0xbd] = 0xdd, /* qnum:189 -> linux:213 (KEY_SOUND) -> xorgevdev:221 */
+  [0xbe] = 0xde, /* qnum:190 -> linux:214 (KEY_QUESTION) -> xorgevdev:222 */
+  [0xbf] = 0xdf, /* qnum:191 -> linux:215 (KEY_EMAIL) -> xorgevdev:223 */
+  [0xc0] = 0xe0, /* qnum:192 -> linux:216 (KEY_CHAT) -> xorgevdev:224 */
+  [0xc1] = 0x90, /* qnum:193 -> linux:136 (KEY_FIND) -> xorgevdev:144 */
+  [0xc2] = 0xe2, /* qnum:194 -> linux:218 (KEY_CONNECT) -> xorgevdev:226 */
+  [0xc3] = 0xe3, /* qnum:195 -> linux:219 (KEY_FINANCE) -> xorgevdev:227 */
+  [0xc4] = 0xe4, /* qnum:196 -> linux:220 (KEY_SPORT) -> xorgevdev:228 */
+  [0xc5] = 0xe5, /* qnum:197 -> linux:221 (KEY_SHOP) -> xorgevdev:229 */
+  [0xc6] = 0x7f, /* qnum:198 -> linux:119 (KEY_PAUSE) -> xorgevdev:127 */
+  [0xc7] = 0x6e, /* qnum:199 -> linux:102 (KEY_HOME) -> xorgevdev:110 */
+  [0xc8] = 0x6f, /* qnum:200 -> linux:103 (KEY_UP) -> xorgevdev:111 */
+  [0xc9] = 0x70, /* qnum:201 -> linux:104 (KEY_PAGEUP) -> xorgevdev:112 */
+  [0xca] = 0xe7, /* qnum:202 -> linux:223 (KEY_CANCEL) -> xorgevdev:231 */
+  [0xcb] = 0x71, /* qnum:203 -> linux:105 (KEY_LEFT) -> xorgevdev:113 */
+  [0xcc] = 0xe8, /* qnum:204 -> linux:224 (KEY_BRIGHTNESSDOWN) -> xorgevdev:232 */
+  [0xcd] = 0x72, /* qnum:205 -> linux:106 (KEY_RIGHT) -> xorgevdev:114 */
+  [0xce] = 0x7e, /* qnum:206 -> linux:118 (KEY_KPPLUSMINUS) -> xorgevdev:126 */
+  [0xcf] = 0x73, /* qnum:207 -> linux:107 (KEY_END) -> xorgevdev:115 */
+  [0xd0] = 0x74, /* qnum:208 -> linux:108 (KEY_DOWN) -> xorgevdev:116 */
+  [0xd1] = 0x75, /* qnum:209 -> linux:109 (KEY_PAGEDOWN) -> xorgevdev:117 */
+  [0xd2] = 0x76, /* qnum:210 -> linux:110 (KEY_INSERT) -> xorgevdev:118 */
+  [0xd3] = 0x77, /* qnum:211 -> linux:111 (KEY_DELETE) -> xorgevdev:119 */
+  [0xd4] = 0xe9, /* qnum:212 -> linux:225 (KEY_BRIGHTNESSUP) -> xorgevdev:233 */
+  [0xd5] = 0xf2, /* qnum:213 -> linux:234 (KEY_SAVE) -> xorgevdev:242 */
+  [0xd6] = 0xeb, /* qnum:214 -> linux:227 (KEY_SWITCHVIDEOMODE) -> xorgevdev:235 */
+  [0xd7] = 0xec, /* qnum:215 -> linux:228 (KEY_KBDILLUMTOGGLE) -> xorgevdev:236 */
+  [0xd8] = 0xed, /* qnum:216 -> linux:229 (KEY_KBDILLUMDOWN) -> xorgevdev:237 */
+  [0xd9] = 0xee, /* qnum:217 -> linux:230 (KEY_KBDILLUMUP) -> xorgevdev:238 */
+  [0xda] = 0xef, /* qnum:218 -> linux:231 (KEY_SEND) -> xorgevdev:239 */
+  [0xdb] = 0x85, /* qnum:219 -> linux:125 (KEY_LEFTMETA) -> xorgevdev:133 */
+  [0xdc] = 0x86, /* qnum:220 -> linux:126 (KEY_RIGHTMETA) -> xorgevdev:134 */
+  [0xdd] = 0x87, /* qnum:221 -> linux:127 (KEY_COMPOSE) -> xorgevdev:135 */
+  [0xde] = 0x7c, /* qnum:222 -> linux:116 (KEY_POWER) -> xorgevdev:124 */
+  [0xdf] = 0x96, /* qnum:223 -> linux:142 (KEY_SLEEP) -> xorgevdev:150 */
+  [0xe3] = 0x97, /* qnum:227 -> linux:143 (KEY_WAKEUP) -> xorgevdev:151 */
+  [0xe4] = 0xf0, /* qnum:228 -> linux:232 (KEY_REPLY) -> xorgevdev:240 */
+  [0xe5] = 0xe1, /* qnum:229 -> linux:217 (KEY_SEARCH) -> xorgevdev:225 */
+  [0xe6] = 0xa4, /* qnum:230 -> linux:156 (KEY_BOOKMARKS) -> xorgevdev:164 */
+  [0xe7] = 0xb5, /* qnum:231 -> linux:173 (KEY_REFRESH) -> xorgevdev:181 */
+  [0xe8] = 0x88, /* qnum:232 -> linux:128 (KEY_STOP) -> xorgevdev:136 */
+  [0xe9] = 0xa7, /* qnum:233 -> linux:159 (KEY_FORWARD) -> xorgevdev:167 */
+  [0xea] = 0xa6, /* qnum:234 -> linux:158 (KEY_BACK) -> xorgevdev:166 */
+  [0xeb] = 0xa5, /* qnum:235 -> linux:157 (KEY_COMPUTER) -> xorgevdev:165 */
+  [0xec] = 0xa3, /* qnum:236 -> linux:155 (KEY_MAIL) -> xorgevdev:163 */
+  [0xed] = 0xea, /* qnum:237 -> linux:226 (KEY_MEDIA) -> xorgevdev:234 */
+  [0xef] = 0x78, /* qnum:239 -> linux:112 (KEY_MACRO) -> xorgevdev:120 */
+  [0xf0] = 0xf3, /* qnum:240 -> linux:235 (KEY_DOCUMENTS) -> xorgevdev:243 */
+  [0xf1] = 0xf4, /* qnum:241 -> linux:236 (KEY_BATTERY) -> xorgevdev:244 */
+  [0xf2] = 0xf5, /* qnum:242 -> linux:237 (KEY_BLUETOOTH) -> xorgevdev:245 */
+  [0xf3] = 0xf6, /* qnum:243 -> linux:238 (KEY_WLAN) -> xorgevdev:246 */
+  [0xf4] = 0xf7, /* qnum:244 -> linux:239 (KEY_UWB) -> xorgevdev:247 */
+  [0xf5] = 0x92, /* qnum:245 -> linux:138 (KEY_HELP) -> xorgevdev:146 */
+  [0xf6] = 0xbb, /* qnum:246 -> linux:179 (KEY_KPLEFTPAREN) -> xorgevdev:187 */
+  [0xf7] = 0xc4, /* qnum:247 -> linux:188 (KEY_F18) -> xorgevdev:196 */
+  [0xf8] = 0x8d, /* qnum:248 -> linux:133 (KEY_COPY) -> xorgevdev:141 */
+  [0xf9] = 0xc8, /* qnum:249 -> linux:192 (KEY_F22) -> xorgevdev:200 */
+  [0xfb] = 0xbc, /* qnum:251 -> linux:180 (KEY_KPRIGHTPAREN) -> xorgevdev:188 */
+  [0xfd] = 0xaa, /* qnum:253 -> linux:162 (KEY_EJECTCLOSECD) -> xorgevdev:170 */
+};
+const unsigned int code_map_qnum_to_xorgevdev_len = sizeof(code_map_qnum_to_xorgevdev)/sizeof(code_map_qnum_to_xorgevdev[0]);
diff --git a/unix/xserver/hw/vnc/qnum_to_xorgkbd.c b/unix/xserver/hw/vnc/qnum_to_xorgkbd.c
new file mode 100644
index 0000000..57c2047
--- /dev/null
+++ b/unix/xserver/hw/vnc/qnum_to_xorgkbd.c
@@ -0,0 +1,121 @@
+/*
+ * This file is auto-generated from keymaps.csv on 2017-08-28 13:04
+ * Database checksum sha256(f8aeff0c3430077a350e3d7ba2b335b381bd929ac4b193413730a402ff3f0097)
+ * To re-generate, run:
+ *   keymap-gen --lang=stdc code-map keymaps.csv qnum xorgkbd
+*/
+const unsigned short code_map_qnum_to_xorgkbd[254] = {
+  [0x1] = 0x9, /* qnum:1 -> linux:1 (KEY_ESC) -> xorgkbd:9 */
+  [0x2] = 0xa, /* qnum:2 -> linux:2 (KEY_1) -> xorgkbd:10 */
+  [0x3] = 0xb, /* qnum:3 -> linux:3 (KEY_2) -> xorgkbd:11 */
+  [0x4] = 0xc, /* qnum:4 -> linux:4 (KEY_3) -> xorgkbd:12 */
+  [0x5] = 0xd, /* qnum:5 -> linux:5 (KEY_4) -> xorgkbd:13 */
+  [0x6] = 0xe, /* qnum:6 -> linux:6 (KEY_5) -> xorgkbd:14 */
+  [0x7] = 0xf, /* qnum:7 -> linux:7 (KEY_6) -> xorgkbd:15 */
+  [0x8] = 0x10, /* qnum:8 -> linux:8 (KEY_7) -> xorgkbd:16 */
+  [0x9] = 0x11, /* qnum:9 -> linux:9 (KEY_8) -> xorgkbd:17 */
+  [0xa] = 0x12, /* qnum:10 -> linux:10 (KEY_9) -> xorgkbd:18 */
+  [0xb] = 0x13, /* qnum:11 -> linux:11 (KEY_0) -> xorgkbd:19 */
+  [0xc] = 0x14, /* qnum:12 -> linux:12 (KEY_MINUS) -> xorgkbd:20 */
+  [0xd] = 0x15, /* qnum:13 -> linux:13 (KEY_EQUAL) -> xorgkbd:21 */
+  [0xe] = 0x16, /* qnum:14 -> linux:14 (KEY_BACKSPACE) -> xorgkbd:22 */
+  [0xf] = 0x17, /* qnum:15 -> linux:15 (KEY_TAB) -> xorgkbd:23 */
+  [0x10] = 0x18, /* qnum:16 -> linux:16 (KEY_Q) -> xorgkbd:24 */
+  [0x11] = 0x19, /* qnum:17 -> linux:17 (KEY_W) -> xorgkbd:25 */
+  [0x12] = 0x1a, /* qnum:18 -> linux:18 (KEY_E) -> xorgkbd:26 */
+  [0x13] = 0x1b, /* qnum:19 -> linux:19 (KEY_R) -> xorgkbd:27 */
+  [0x14] = 0x1c, /* qnum:20 -> linux:20 (KEY_T) -> xorgkbd:28 */
+  [0x15] = 0x1d, /* qnum:21 -> linux:21 (KEY_Y) -> xorgkbd:29 */
+  [0x16] = 0x1e, /* qnum:22 -> linux:22 (KEY_U) -> xorgkbd:30 */
+  [0x17] = 0x1f, /* qnum:23 -> linux:23 (KEY_I) -> xorgkbd:31 */
+  [0x18] = 0x20, /* qnum:24 -> linux:24 (KEY_O) -> xorgkbd:32 */
+  [0x19] = 0x21, /* qnum:25 -> linux:25 (KEY_P) -> xorgkbd:33 */
+  [0x1a] = 0x22, /* qnum:26 -> linux:26 (KEY_LEFTBRACE) -> xorgkbd:34 */
+  [0x1b] = 0x23, /* qnum:27 -> linux:27 (KEY_RIGHTBRACE) -> xorgkbd:35 */
+  [0x1c] = 0x24, /* qnum:28 -> linux:28 (KEY_ENTER) -> xorgkbd:36 */
+  [0x1d] = 0x25, /* qnum:29 -> linux:29 (KEY_LEFTCTRL) -> xorgkbd:37 */
+  [0x1e] = 0x26, /* qnum:30 -> linux:30 (KEY_A) -> xorgkbd:38 */
+  [0x1f] = 0x27, /* qnum:31 -> linux:31 (KEY_S) -> xorgkbd:39 */
+  [0x20] = 0x28, /* qnum:32 -> linux:32 (KEY_D) -> xorgkbd:40 */
+  [0x21] = 0x29, /* qnum:33 -> linux:33 (KEY_F) -> xorgkbd:41 */
+  [0x22] = 0x2a, /* qnum:34 -> linux:34 (KEY_G) -> xorgkbd:42 */
+  [0x23] = 0x2b, /* qnum:35 -> linux:35 (KEY_H) -> xorgkbd:43 */
+  [0x24] = 0x2c, /* qnum:36 -> linux:36 (KEY_J) -> xorgkbd:44 */
+  [0x25] = 0x2d, /* qnum:37 -> linux:37 (KEY_K) -> xorgkbd:45 */
+  [0x26] = 0x2e, /* qnum:38 -> linux:38 (KEY_L) -> xorgkbd:46 */
+  [0x27] = 0x2f, /* qnum:39 -> linux:39 (KEY_SEMICOLON) -> xorgkbd:47 */
+  [0x28] = 0x30, /* qnum:40 -> linux:40 (KEY_APOSTROPHE) -> xorgkbd:48 */
+  [0x29] = 0x31, /* qnum:41 -> linux:41 (KEY_GRAVE) -> xorgkbd:49 */
+  [0x2a] = 0x32, /* qnum:42 -> linux:42 (KEY_LEFTSHIFT) -> xorgkbd:50 */
+  [0x2b] = 0x33, /* qnum:43 -> linux:43 (KEY_BACKSLASH) -> xorgkbd:51 */
+  [0x2c] = 0x34, /* qnum:44 -> linux:44 (KEY_Z) -> xorgkbd:52 */
+  [0x2d] = 0x35, /* qnum:45 -> linux:45 (KEY_X) -> xorgkbd:53 */
+  [0x2e] = 0x36, /* qnum:46 -> linux:46 (KEY_C) -> xorgkbd:54 */
+  [0x2f] = 0x37, /* qnum:47 -> linux:47 (KEY_V) -> xorgkbd:55 */
+  [0x30] = 0x38, /* qnum:48 -> linux:48 (KEY_B) -> xorgkbd:56 */
+  [0x31] = 0x39, /* qnum:49 -> linux:49 (KEY_N) -> xorgkbd:57 */
+  [0x32] = 0x3a, /* qnum:50 -> linux:50 (KEY_M) -> xorgkbd:58 */
+  [0x33] = 0x3b, /* qnum:51 -> linux:51 (KEY_COMMA) -> xorgkbd:59 */
+  [0x34] = 0x3c, /* qnum:52 -> linux:52 (KEY_DOT) -> xorgkbd:60 */
+  [0x35] = 0x3d, /* qnum:53 -> linux:53 (KEY_SLASH) -> xorgkbd:61 */
+  [0x36] = 0x3e, /* qnum:54 -> linux:54 (KEY_RIGHTSHIFT) -> xorgkbd:62 */
+  [0x37] = 0x3f, /* qnum:55 -> linux:55 (KEY_KPASTERISK) -> xorgkbd:63 */
+  [0x38] = 0x40, /* qnum:56 -> linux:56 (KEY_LEFTALT) -> xorgkbd:64 */
+  [0x39] = 0x41, /* qnum:57 -> linux:57 (KEY_SPACE) -> xorgkbd:65 */
+  [0x3a] = 0x42, /* qnum:58 -> linux:58 (KEY_CAPSLOCK) -> xorgkbd:66 */
+  [0x3b] = 0x43, /* qnum:59 -> linux:59 (KEY_F1) -> xorgkbd:67 */
+  [0x3c] = 0x44, /* qnum:60 -> linux:60 (KEY_F2) -> xorgkbd:68 */
+  [0x3d] = 0x45, /* qnum:61 -> linux:61 (KEY_F3) -> xorgkbd:69 */
+  [0x3e] = 0x46, /* qnum:62 -> linux:62 (KEY_F4) -> xorgkbd:70 */
+  [0x3f] = 0x47, /* qnum:63 -> linux:63 (KEY_F5) -> xorgkbd:71 */
+  [0x40] = 0x48, /* qnum:64 -> linux:64 (KEY_F6) -> xorgkbd:72 */
+  [0x41] = 0x49, /* qnum:65 -> linux:65 (KEY_F7) -> xorgkbd:73 */
+  [0x42] = 0x4a, /* qnum:66 -> linux:66 (KEY_F8) -> xorgkbd:74 */
+  [0x43] = 0x4b, /* qnum:67 -> linux:67 (KEY_F9) -> xorgkbd:75 */
+  [0x44] = 0x4c, /* qnum:68 -> linux:68 (KEY_F10) -> xorgkbd:76 */
+  [0x45] = 0x4d, /* qnum:69 -> linux:69 (KEY_NUMLOCK) -> xorgkbd:77 */
+  [0x46] = 0x4e, /* qnum:70 -> linux:70 (KEY_SCROLLLOCK) -> xorgkbd:78 */
+  [0x47] = 0x4f, /* qnum:71 -> linux:71 (KEY_KP7) -> xorgkbd:79 */
+  [0x48] = 0x50, /* qnum:72 -> linux:72 (KEY_KP8) -> xorgkbd:80 */
+  [0x49] = 0x51, /* qnum:73 -> linux:73 (KEY_KP9) -> xorgkbd:81 */
+  [0x4a] = 0x52, /* qnum:74 -> linux:74 (KEY_KPMINUS) -> xorgkbd:82 */
+  [0x4b] = 0x53, /* qnum:75 -> linux:75 (KEY_KP4) -> xorgkbd:83 */
+  [0x4c] = 0x54, /* qnum:76 -> linux:76 (KEY_KP5) -> xorgkbd:84 */
+  [0x4d] = 0x55, /* qnum:77 -> linux:77 (KEY_KP6) -> xorgkbd:85 */
+  [0x4e] = 0x56, /* qnum:78 -> linux:78 (KEY_KPPLUS) -> xorgkbd:86 */
+  [0x4f] = 0x57, /* qnum:79 -> linux:79 (KEY_KP1) -> xorgkbd:87 */
+  [0x50] = 0x58, /* qnum:80 -> linux:80 (KEY_KP2) -> xorgkbd:88 */
+  [0x51] = 0x59, /* qnum:81 -> linux:81 (KEY_KP3) -> xorgkbd:89 */
+  [0x52] = 0x5a, /* qnum:82 -> linux:82 (KEY_KP0) -> xorgkbd:90 */
+  [0x53] = 0x5b, /* qnum:83 -> linux:83 (KEY_KPDOT) -> xorgkbd:91 */
+  [0x54] = 0x6f, /* qnum:84 -> linux:99 (KEY_SYSRQ) -> xorgkbd:111 */
+  [0x55] = 0x79, /* qnum:85 -> linux:186 (KEY_F16) -> xorgkbd:121 */
+  [0x56] = 0x5e, /* qnum:86 -> linux:86 (KEY_102ND) -> xorgkbd:94 */
+  [0x57] = 0x5f, /* qnum:87 -> linux:87 (KEY_F11) -> xorgkbd:95 */
+  [0x58] = 0x60, /* qnum:88 -> linux:88 (KEY_F12) -> xorgkbd:96 */
+  [0x59] = 0x7e, /* qnum:89 -> linux:117 (KEY_KPEQUAL) -> xorgkbd:126 */
+  [0x5d] = 0x76, /* qnum:93 -> linux:183 (KEY_F13) -> xorgkbd:118 */
+  [0x5e] = 0x77, /* qnum:94 -> linux:184 (KEY_F14) -> xorgkbd:119 */
+  [0x5f] = 0x78, /* qnum:95 -> linux:185 (KEY_F15) -> xorgkbd:120 */
+  [0x7d] = 0x85, /* qnum:125 -> linux:124 (KEY_YEN) -> xorgkbd:133 */
+  [0x83] = 0x7a, /* qnum:131 -> linux:187 (KEY_F17) -> xorgkbd:122 */
+  [0x9c] = 0x6c, /* qnum:156 -> linux:96 (KEY_KPENTER) -> xorgkbd:108 */
+  [0x9d] = 0x6d, /* qnum:157 -> linux:97 (KEY_RIGHTCTRL) -> xorgkbd:109 */
+  [0xb5] = 0x70, /* qnum:181 -> linux:98 (KEY_KPSLASH) -> xorgkbd:112 */
+  [0xb8] = 0x71, /* qnum:184 -> linux:100 (KEY_RIGHTALT) -> xorgkbd:113 */
+  [0xc6] = 0x6e, /* qnum:198 -> linux:119 (KEY_PAUSE) -> xorgkbd:110 */
+  [0xc7] = 0x61, /* qnum:199 -> linux:102 (KEY_HOME) -> xorgkbd:97 */
+  [0xc8] = 0x62, /* qnum:200 -> linux:103 (KEY_UP) -> xorgkbd:98 */
+  [0xc9] = 0x63, /* qnum:201 -> linux:104 (KEY_PAGEUP) -> xorgkbd:99 */
+  [0xcb] = 0x64, /* qnum:203 -> linux:105 (KEY_LEFT) -> xorgkbd:100 */
+  [0xcd] = 0x66, /* qnum:205 -> linux:106 (KEY_RIGHT) -> xorgkbd:102 */
+  [0xcf] = 0x67, /* qnum:207 -> linux:107 (KEY_END) -> xorgkbd:103 */
+  [0xd0] = 0x68, /* qnum:208 -> linux:108 (KEY_DOWN) -> xorgkbd:104 */
+  [0xd1] = 0x69, /* qnum:209 -> linux:109 (KEY_PAGEDOWN) -> xorgkbd:105 */
+  [0xd2] = 0x6a, /* qnum:210 -> linux:110 (KEY_INSERT) -> xorgkbd:106 */
+  [0xd3] = 0x6b, /* qnum:211 -> linux:111 (KEY_DELETE) -> xorgkbd:107 */
+  [0xdb] = 0x73, /* qnum:219 -> linux:125 (KEY_LEFTMETA) -> xorgkbd:115 */
+  [0xdc] = 0x74, /* qnum:220 -> linux:126 (KEY_RIGHTMETA) -> xorgkbd:116 */
+  [0xdd] = 0x75, /* qnum:221 -> linux:127 (KEY_COMPOSE) -> xorgkbd:117 */
+};
+const unsigned int code_map_qnum_to_xorgkbd_len = sizeof(code_map_qnum_to_xorgkbd)/sizeof(code_map_qnum_to_xorgkbd[0]);
diff --git a/unix/xserver/hw/vnc/vncExtInit.cc b/unix/xserver/hw/vnc/vncExtInit.cc
index 57bf6d8..13248f9 100644
--- a/unix/xserver/hw/vnc/vncExtInit.cc
+++ b/unix/xserver/hw/vnc/vncExtInit.cc
@@ -32,6 +32,7 @@
 #include <rfb/LogWriter.h>
 #include <rfb/Hostname.h>
 #include <rfb/Region.h>
+#include <rfb/ledStates.h>
 #include <network/TcpSocket.h>
 
 #include "XserverDesktop.h"
@@ -358,6 +359,25 @@
   }
 }
 
+void vncSetLEDState(unsigned long leds)
+{
+  unsigned int state;
+
+  state = 0;
+  if (leds & (1 << 0))
+    state |= ledCapsLock;
+  if (leds & (1 << 1))
+    state |= ledNumLock;
+  if (leds & (1 << 2))
+    state |= ledScrollLock;
+
+  for (int scr = 0; scr < vncGetScreenCount(); scr++) {
+    if (desktop[scr] == NULL)
+      continue;
+    desktop[scr]->setLEDState(state);
+  }
+}
+
 void vncAddChanged(int scrIdx, const struct UpdateRect *extents,
                    int nRects, const struct UpdateRect *rects)
 {
diff --git a/unix/xserver/hw/vnc/vncExtInit.h b/unix/xserver/hw/vnc/vncExtInit.h
index 9f8d9e7..99fee27 100644
--- a/unix/xserver/hw/vnc/vncExtInit.h
+++ b/unix/xserver/hw/vnc/vncExtInit.h
@@ -70,6 +70,8 @@
 
 void vncBell(void);
 
+void vncSetLEDState(unsigned long leds);
+
 // Must match rfb::ShortRect in common/rfb/Region.h, and BoxRec in the
 // Xorg source.
 struct UpdateRect {
diff --git a/vncviewer/CConn.cxx b/vncviewer/CConn.cxx
index 2e97ec2..07e7841 100644
--- a/vncviewer/CConn.cxx
+++ b/vncviewer/CConn.cxx
@@ -92,6 +92,8 @@
   cp.supportsExtendedDesktopSize = true;
   cp.supportsDesktopRename = true;
 
+  cp.supportsLEDState = true;
+
   if (customCompressLevel)
     cp.compressLevel = compressLevel;
   else
@@ -503,6 +505,13 @@
   }
 }
 
+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 93cc278..426bd1e 100644
--- a/vncviewer/CConn.h
+++ b/vncviewer/CConn.h
@@ -74,6 +74,8 @@
 
   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 e04e335..3c18646 100644
--- a/vncviewer/CMakeLists.txt
+++ b/vncviewer/CMakeLists.txt
@@ -29,7 +29,9 @@
 if(WIN32)
   set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} win32.c)
 elseif(APPLE)
-  set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} cocoa.mm)
+  set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} cocoa.mm osx_to_qnum.c)
+else()
+  set(VNCVIEWER_SOURCES ${VNCVIEWER_SOURCES} xkb_to_qnum.c)
 endif()
 
 if(WIN32)
@@ -53,7 +55,9 @@
 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})
@@ -62,7 +66,7 @@
 
   configure_file(vncviewer.desktop.in.in vncviewer.desktop.in)
   find_program(INTLTOOL_MERGE_EXECUTABLE intltool-merge)
-  if("${GETTEXT_VERSION_STRING}" VERSION_GREATER 0.17.99)
+  if("${GETTEXT_VERSION_STRING}" VERSION_GREATER 0.18.99)
     add_custom_command(OUTPUT vncviewer.desktop
       COMMAND ${GETTEXT_MSGFMT_EXECUTABLE}
                 --desktop --template vncviewer.desktop.in
diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx
index 408efd1..897f955 100644
--- a/vncviewer/DesktopWindow.cxx
+++ b/vncviewer/DesktopWindow.cxx
@@ -395,6 +395,12 @@
 }
 
 
+void DesktopWindow::setLEDState(unsigned int state)
+{
+  viewport->setLEDState(state);
+}
+
+
 void DesktopWindow::resize(int x, int y, int w, int h)
 {
   bool resizing;
@@ -660,23 +666,31 @@
 
   DesktopWindow *dw = dynamic_cast<DesktopWindow*>(win);
 
-  if (dw && fullscreenSystemKeys) {
+  if (dw) {
     switch (event) {
     case FL_FOCUS:
-      // FIXME: We reassert the keyboard grabbing on focus as FLTK there are
-      //        some issues we need to work around:
-      //        a) Fl::grab(0) on X11 will release the keyboard grab for us.
-      //        b) Gaining focus on the system level causes FLTK to switch
-      //           window level on OS X.
-      if (dw->fullscreen_active())
-        dw->grabKeyboard();
+      if (fullscreenSystemKeys) {
+        // FIXME: We reassert the keyboard grabbing on focus as FLTK there are
+        //        some issues we need to work around:
+        //        a) Fl::grab(0) on X11 will release the keyboard grab for us.
+        //        b) Gaining focus on the system level causes FLTK to switch
+        //           window level on OS X.
+        if (dw->fullscreen_active())
+          dw->grabKeyboard();
+      }
+
+      // We may have gotten our lock keys out of sync with the server
+      // whilst we didn't have focus. Try to sort this out.
+      dw->viewport->pushLEDState();
       break;
 
     case FL_UNFOCUS:
-      // FIXME: We need to relinquish control when the entire window loses
-      //        focus as it is very tied to this specific window on some
-      //        platforms and we want to be able to open subwindows.
-      dw->ungrabKeyboard();
+      if (fullscreenSystemKeys) {
+        // FIXME: We need to relinquish control when the entire window loses
+        //        focus as it is very tied to this specific window on some
+        //        platforms and we want to be able to open subwindows.
+        dw->ungrabKeyboard();
+      }
       break;
     }
   }
diff --git a/vncviewer/DesktopWindow.h b/vncviewer/DesktopWindow.h
index 4224699..f1bf312 100644
--- a/vncviewer/DesktopWindow.h
+++ b/vncviewer/DesktopWindow.h
@@ -66,6 +66,9 @@
   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/FLTKPixelBuffer.cxx b/vncviewer/FLTKPixelBuffer.cxx
deleted file mode 100644
index ab116d1..0000000
--- a/vncviewer/FLTKPixelBuffer.cxx
+++ /dev/null
@@ -1,53 +0,0 @@
-/* Copyright 2011-2014 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.
- */
-
-#include <FL/fl_draw.H>
-
-#include <rfb/Exception.h>
-
-#include "i18n.h"
-#include "FLTKPixelBuffer.h"
-
-FLTKPixelBuffer::FLTKPixelBuffer(int width, int height) :
-  PlatformPixelBuffer(rfb::PixelFormat(32, 24, false, true,
-                                       255, 255, 255, 0, 8, 16),
-                      width, height, NULL, width)
-{
-  data = new rdr::U8[width * height * format.bpp/8];
-  if (data == NULL)
-    throw rfb::Exception(_("Not enough memory for framebuffer"));
-}
-
-FLTKPixelBuffer::~FLTKPixelBuffer()
-{
-  delete [] data;
-}
-
-void FLTKPixelBuffer::draw(int src_x, int src_y, int x, int y, int w, int h)
-{
-  int pixel_bytes, stride_bytes;
-  const uchar *buf_start;
-
-  pixel_bytes = format.bpp/8;
-  stride_bytes = pixel_bytes * stride;
-  buf_start = data +
-              pixel_bytes * src_x +
-              stride_bytes * src_y;
-
-  fl_draw_image(buf_start, x, y, w, h, pixel_bytes, stride_bytes);
-}
diff --git a/vncviewer/UserDialog.cxx b/vncviewer/UserDialog.cxx
index b57588d..640f2a9 100644
--- a/vncviewer/UserDialog.cxx
+++ b/vncviewer/UserDialog.cxx
@@ -32,6 +32,7 @@
 #include <FL/Fl_Secret_Input.H>
 #include <FL/Fl_Button.H>
 #include <FL/Fl_Return_Button.H>
+#include <FL/Fl_Pixmap.H>
 
 #include <rfb/util.h>
 #include <rfb/Password.h>
@@ -42,8 +43,18 @@
 #include "parameters.h"
 #include "UserDialog.h"
 
+/* xpm:s predate const, so they have invalid definitions */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wwrite-strings"
+#include "../media/secure.xpm"
+#include "../media/insecure.xpm"
+#pragma GCC diagnostic pop
+
 using namespace rfb;
 
+static Fl_Pixmap secure_icon(secure);
+static Fl_Pixmap insecure_icon(insecure);
+
 static int ret_val = 0;
 
 static void button_cb(Fl_Widget *widget, void *val) {
@@ -59,7 +70,7 @@
 {
 }
 
-void UserDialog::getUserPasswd(char** user, char** password)
+void UserDialog::getUserPasswd(bool secure, char** user, char** password)
 {
   CharArray passwordFileStr(passwordFile.getData());
 
@@ -82,46 +93,73 @@
     return;
   }
 
-  if (!user) {
-    fl_message_title(_("VNC authentication"));
-    *password = strDup(fl_password(_("Password:"), ""));
-    if (!*password)
-      throw rfb::Exception(_("Authentication cancelled"));
+  Fl_Window *win;
+  Fl_Box *banner;
+  Fl_Input *username;
+  Fl_Secret_Input *passwd;
+  Fl_Box *icon;
+  Fl_Button *button;
 
-    return;
-  }
+  int y;
 
-  // Largely copied from FLTK so that we get the same look and feel
-  // as the simpler password input.
-  Fl_Window *win = new Fl_Window(410, 145, _("VNC authentication"));
+  win = new Fl_Window(410, 145, _("VNC authentication"));
   win->callback(button_cb,(void *)0);
 
-  Fl_Input *username = new Fl_Input(70, 25, 300, 25, _("Username:"));
-  username->align(FL_ALIGN_TOP_LEFT);
+  banner = new Fl_Box(0, 0, win->w(), 20);
+  banner->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE|FL_ALIGN_IMAGE_NEXT_TO_TEXT);
+  banner->box(FL_FLAT_BOX);
+  if (secure) {
+    banner->label(_("This connection is secure"));
+    banner->color(FL_GREEN);
+    banner->image(secure_icon);
+  } else {
+    banner->label(_("This connection is not secure"));
+    banner->color(FL_RED);
+    banner->image(insecure_icon);
+  }
 
-  Fl_Secret_Input *passwd = new Fl_Secret_Input(70, 70, 300, 25, _("Password:"));
-  passwd->align(FL_ALIGN_TOP_LEFT);
+  y = 20 + 10;
 
-  Fl_Box *icon = new Fl_Box(10, 10, 50, 50, "?");
+  icon = new Fl_Box(10, y, 50, 50, "?");
   icon->box(FL_UP_BOX);
   icon->labelfont(FL_TIMES_BOLD);
   icon->labelsize(34);
   icon->color(FL_WHITE);
   icon->labelcolor(FL_BLUE);
 
-  Fl_Button *button;
+  y += 5;
 
-  button = new Fl_Return_Button(310, 110, 90, 25, fl_ok);
+  if (user) {
+    (new Fl_Box(70, y, win->w()-70-10, 20, _("Username:")))
+      ->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE);
+    y += 20 + 5;
+    username = new Fl_Input(70, y, win->w()-70-10, 25);
+    y += 25 + 5;
+  }
+
+  (new Fl_Box(70, y, win->w()-70-10, 20, _("Password:")))
+    ->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE);
+  y += 20 + 5;
+  passwd = new Fl_Secret_Input(70, y, win->w()-70-10, 25);
+  y += 25 + 5;
+
+  y += 5;
+
+  button = new Fl_Return_Button(310, y, 90, 25, fl_ok);
   button->align(FL_ALIGN_INSIDE|FL_ALIGN_WRAP);
   button->callback(button_cb, (void*)0);
 
-  button = new Fl_Button(210, 110, 90, 25, fl_cancel);
+  button = new Fl_Button(210, y, 90, 25, fl_cancel);
   button->align(FL_ALIGN_INSIDE|FL_ALIGN_WRAP);
   button->callback(button_cb, (void*)1);
   button->shortcut(FL_Escape);
 
+  y += 25 + 10;
+
   win->end();
 
+  win->size(win->w(), y);
+
   win->set_modal();
 
   ret_val = -1;
@@ -130,14 +168,15 @@
   while (win->shown()) Fl::wait();
 
   if (ret_val == 0) {
-    *user = strDup(username->value());
+    if (user)
+      *user = strDup(username->value());
     *password = strDup(passwd->value());
-  } else {
-    *user = strDup("");
-    *password = strDup("");
   }
 
   delete win;
+
+  if (ret_val != 0)
+    throw rfb::Exception(_("Authentication cancelled"));
 }
 
 bool UserDialog::showMsgBox(int flags, const char* title, const char* text)
diff --git a/vncviewer/UserDialog.h b/vncviewer/UserDialog.h
index c6756a8..b62ba7f 100644
--- a/vncviewer/UserDialog.h
+++ b/vncviewer/UserDialog.h
@@ -31,7 +31,7 @@
 
   // UserPasswdGetter callbacks
 
-  void getUserPasswd(char** user, char** password);
+  void getUserPasswd(bool secure, char** user, char** password);
 
   // UserMsgBox callbacks
 
diff --git a/vncviewer/Viewport.cxx b/vncviewer/Viewport.cxx
index 6a23526..331efbc 100644
--- a/vncviewer/Viewport.cxx
+++ b/vncviewer/Viewport.cxx
@@ -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
@@ -41,6 +42,10 @@
 #include <rfb/XF86keysym.h>
 #endif
 
+#if ! (defined(WIN32) || defined(__APPLE__))
+#include <X11/XKBlib.h>
+#endif
+
 #ifndef NoSymbol
 #define NoSymbol 0
 #endif
@@ -69,8 +74,21 @@
 #include <FL/Fl_Menu.H>
 #include <FL/Fl_Menu_Button.H>
 
+#if !defined(WIN32) && !defined(__APPLE__)
+#include <X11/XKBlib.h>
+extern const struct _code_map_xkb_to_qnum {
+  const char * from;
+  const unsigned short to;
+} code_map_xkb_to_qnum[];
+extern const unsigned int code_map_xkb_to_qnum_len;
+
+static int code_map_keycode_to_qnum[256];
+#endif
+
 #ifdef __APPLE__
 #include "cocoa.h"
+extern const unsigned short code_map_osx_to_qnum[];
+extern const unsigned int code_map_osx_to_qnum_len;
 #endif
 
 #ifdef WIN32
@@ -89,14 +107,55 @@
        ID_CTRL, ID_ALT, ID_MENUKEY, ID_CTRLALTDEL,
        ID_REFRESH, ID_OPTIONS, ID_INFO, ID_ABOUT, ID_DISMISS };
 
-// 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),
     menuCtrlKey(false), menuAltKey(false), cursor(NULL)
 {
+#if !defined(WIN32) && !defined(__APPLE__)
+  XkbDescPtr xkb;
+  Status status;
+
+  xkb = XkbGetMap(fl_display, 0, XkbUseCoreKbd);
+  if (!xkb)
+    throw Exception("XkbGetMap");
+
+  status = XkbGetNames(fl_display, XkbKeyNamesMask, xkb);
+  if (status != Success)
+    throw Exception("XkbGetNames");
+
+  memset(code_map_keycode_to_qnum, 0, sizeof(code_map_keycode_to_qnum));
+  for (KeyCode keycode = xkb->min_key_code;
+       keycode < xkb->max_key_code;
+       keycode++) {
+    const char *keyname = xkb->names->keys[keycode].name;
+    unsigned short rfbcode;
+
+    if (keyname[0] == '\0')
+      continue;
+
+    rfbcode = 0;
+    for (unsigned i = 0;i < code_map_xkb_to_qnum_len;i++) {
+        if (strncmp(code_map_xkb_to_qnum[i].from,
+                    keyname, XkbKeyNameLength) == 0) {
+            rfbcode = code_map_xkb_to_qnum[i].to;
+            break;
+        }
+    }
+    if (rfbcode != 0)
+        code_map_keycode_to_qnum[keycode] = rfbcode;
+    else
+        vlog.debug("No key mapping for key %.4s", keyname);
+  }
+
+  XkbFreeKeyboard(xkb, 0, True);
+#endif
+
   Fl::add_clipboard_notify(handleClipboardChange, this);
 
   // We need to intercept keyboard events early
@@ -218,6 +277,189 @@
 }
 
 
+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::pushLEDState()
+{
+  unsigned int state;
+
+  // Server support?
+  if (cc->cp.ledState() == ledUnknown)
+    return;
+
+  state = 0;
+
+#if defined(WIN32)
+  if (GetKeyState(VK_CAPITAL) & 0x1)
+    state |= ledCapsLock;
+  if (GetKeyState(VK_NUMLOCK) & 0x1)
+    state |= ledNumLock;
+  if (GetKeyState(VK_SCROLL) & 0x1)
+    state |= ledScrollLock;
+#elif defined(__APPLE__)
+  int ret;
+  bool on;
+
+  ret = cocoa_get_caps_lock_state(&on);
+  if (ret != 0) {
+    vlog.error(_("Failed to get keyboard LED state: %d"), ret);
+    return;
+  }
+  if (on)
+    state |= ledCapsLock;
+
+  ret = cocoa_get_num_lock_state(&on);
+  if (ret != 0) {
+    vlog.error(_("Failed to get keyboard LED state: %d"), ret);
+    return;
+  }
+  if (on)
+    state |= ledNumLock;
+
+  // No support for Scroll Lock //
+  state |= (cc->cp.ledState() & ledScrollLock);
+
+#else
+  unsigned int mask;
+
+  Status status;
+  XkbStateRec xkbState;
+
+  status = XkbGetState(fl_display, XkbUseCoreKbd, &xkbState);
+  if (status != Success) {
+    vlog.error(_("Failed to get keyboard LED state: %d"), status);
+    return;
+  }
+
+  if (xkbState.locked_mods & LockMask)
+    state |= ledCapsLock;
+
+  mask = getModifierMask(XK_Num_Lock);
+  if (xkbState.locked_mods & mask)
+    state |= ledNumLock;
+
+  mask = getModifierMask(XK_Scroll_Lock);
+  if (xkbState.locked_mods & mask)
+    state |= ledScrollLock;
+#endif
+
+  if ((state & ledCapsLock) != (cc->cp.ledState() & ledCapsLock)) {
+    vlog.debug("Inserting fake CapsLock to get in sync with server");
+    handleKeyPress(0x3a, XK_Caps_Lock);
+    handleKeyRelease(0x3a);
+  }
+  if ((state & ledNumLock) != (cc->cp.ledState() & ledNumLock)) {
+    vlog.debug("Inserting fake NumLock to get in sync with server");
+    handleKeyPress(0x45, XK_Num_Lock);
+    handleKeyRelease(0x45);
+  }
+  if ((state & ledScrollLock) != (cc->cp.ledState() & ledScrollLock)) {
+    vlog.debug("Inserting fake ScrollLock to get in sync with server");
+    handleKeyPress(0x46, XK_Scroll_Lock);
+    handleKeyRelease(0x46);
+  }
+}
+
+
 void Viewport::draw(Surface* dst)
 {
   int X, Y, W, H;
@@ -352,6 +594,55 @@
   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;
@@ -422,6 +713,11 @@
   if (viewOnly)
     return;
 
+  if (keyCode == 0) {
+    vlog.error(_("No key code specified on key press"));
+    return;
+  }
+
 #ifdef __APPLE__
   // Alt on OS X behaves more like AltGr on other systems, and to get
   // sane behaviour we should translate things in that manner for the
@@ -452,23 +748,11 @@
   // Ctrl+Alt+AltGr, which we usually end up with when Xvnc tries to
   // get everything in the correct state. Cheat and temporarily release
   // Ctrl and Alt when we send some other symbol.
-  bool ctrlPressed, altPressed;
-  DownMap::iterator iter;
-
-  ctrlPressed = false;
-  altPressed = false;
-  for (iter = downKeySym.begin();iter != downKeySym.end();++iter) {
-    if (iter->second == XK_Control_L)
-      ctrlPressed = true;
-    else if (iter->second == XK_Alt_R)
-      altPressed = true;
-  }
-
-  if (ctrlPressed && altPressed) {
+  if (downKeySym.count(0x1d) && downKeySym.count(0xb8)) {
     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(downKeySym[0x1d], 0x1d, false);
+      cc->writer()->keyEvent(downKeySym[0xb8], 0xb8, false);
     } catch (rdr::Exception& e) {
       vlog.error("%s", e.str());
       exit_vncviewer(e.str());
@@ -490,7 +774,11 @@
 #endif
 
   try {
-    cc->writer()->keyEvent(keySym, true);
+    // Fake keycode?
+    if (keyCode > 0xff)
+      cc->writer()->keyEvent(keySym, 0, true);
+    else
+      cc->writer()->keyEvent(keySym, keyCode, true);
   } catch (rdr::Exception& e) {
     vlog.error("%s", e.str());
     exit_vncviewer(e.str());
@@ -498,11 +786,11 @@
 
 #ifdef WIN32
   // Ugly hack continued...
-  if (ctrlPressed && altPressed) {
+  if (downKeySym.count(0x1d) && downKeySym.count(0xb8)) {
     vlog.debug("Restoring AltGr state");
     try {
-      cc->writer()->keyEvent(XK_Control_L, true);
-      cc->writer()->keyEvent(XK_Alt_R, true);
+      cc->writer()->keyEvent(downKeySym[0x1d], 0x1d, true);
+      cc->writer()->keyEvent(downKeySym[0xb8], 0xb8, true);
     } catch (rdr::Exception& e) {
       vlog.error("%s", e.str());
       exit_vncviewer(e.str());
@@ -535,7 +823,10 @@
 #endif
 
   try {
-    cc->writer()->keyEvent(iter->second, false);
+    if (keyCode > 0xff)
+      cc->writer()->keyEvent(iter->second, 0, false);
+    else
+      cc->writer()->keyEvent(iter->second, keyCode, false);
   } catch (rdr::Exception& e) {
     vlog.error("%s", e.str());
     exit_vncviewer(e.str());
@@ -577,6 +868,11 @@
 
     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) {
@@ -590,14 +886,19 @@
       }
     }
 
+    if (keyCode & ~0x7f) {
+      vlog.error(_("Invalid scan code 0x%02x"), (int)keyCode);
+      return 1;
+    }
+
     if (isExtended)
-      keyCode |= 0x100;
+      keyCode |= 0x80;
 
     // VK_SNAPSHOT sends different scan codes depending on the state of
     // Alt. This means that we can get different scan codes on press and
     // release. Force it to be something standard.
     if (vKey == VK_SNAPSHOT)
-      keyCode = 0x137;
+      keyCode = 0x54;
 
     keySym = win32_vkey_to_keysym(vKey, isExtended);
     if (keySym == NoSymbol) {
@@ -605,9 +906,12 @@
         vlog.error(_("No symbol for extended virtual key 0x%02x"), (int)vKey);
       else
         vlog.error(_("No symbol for virtual key 0x%02x"), (int)vKey);
-      return 1;
     }
 
+    // Fortunately RFB and Windows use the same scan code set,
+    // so there is no conversion needed
+    // (as long as we encode the extended keys with the high bit)
+
     self->handleKeyPress(keyCode, keySym);
 
     return 1;
@@ -620,12 +924,18 @@
     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)
-      keyCode |= 0x100;
+      keyCode |= 0x80;
     if (vKey == VK_SNAPSHOT)
-      keyCode = 0x137;
+      keyCode = 0x54;
 
     self->handleKeyRelease(keyCode);
 
@@ -636,6 +946,10 @@
     int keyCode;
 
     keyCode = cocoa_event_keycode(event);
+    if ((unsigned)keyCode >= code_map_osx_to_qnum_len)
+      keyCode = 0;
+    else
+      keyCode = code_map_osx_to_qnum[keyCode];
 
     if (cocoa_is_key_press(event)) {
       rdr::U32 keySym;
@@ -644,7 +958,6 @@
       if (keySym == NoSymbol) {
         vlog.error(_("No symbol for key code 0x%02x (in the current state)"),
                    (int)keyCode);
-        return 1;
       }
 
       self->handleKeyPress(keyCode, keySym);
@@ -663,14 +976,21 @@
   XEvent *xevent = (XEvent*)event;
 
   if (xevent->type == KeyPress) {
+    int keycode;
     char str;
     KeySym keysym;
 
+    keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
+
+    // Generate a fake keycode just for tracking if we can't figure
+    // out the proper one
+    if (keycode == 0)
+        keycode = 0x100 | xevent->xkey.keycode;
+
     XLookupString(&xevent->xkey, &str, 1, &keysym, NULL);
     if (keysym == NoSymbol) {
       vlog.error(_("No symbol for key code %d (in the current state)"),
                  (int)xevent->xkey.keycode);
-      return 1;
     }
 
     switch (keysym) {
@@ -690,10 +1010,13 @@
       break;
     }
 
-    self->handleKeyPress(xevent->xkey.keycode, keysym);
+    self->handleKeyPress(keycode, keysym);
     return 1;
   } else if (xevent->type == KeyRelease) {
-    self->handleKeyRelease(xevent->xkey.keycode);
+    int keycode = code_map_keycode_to_qnum[xevent->xkey.keycode];
+    if (keycode == 0)
+        keycode = 0x100 | xevent->xkey.keycode;
+    self->handleKeyRelease(keycode);
     return 1;
   }
 #endif
@@ -729,7 +1052,7 @@
     char sendMenuKey[64];
     snprintf(sendMenuKey, 64, p_("ContextMenu|", "Send %s"), (const char *)menuKey);
     fltk_menu_add(contextMenu, sendMenuKey, 0, NULL, (void*)ID_MENUKEY, 0);
-    fltk_menu_add(contextMenu, "Secret shortcut menu key", menuKeyCode, NULL,
+    fltk_menu_add(contextMenu, "Secret shortcut menu key", menuKeyFLTK, NULL,
                   (void*)ID_MENUKEY, FL_MENU_INVISIBLE);
   }
 
@@ -797,30 +1120,30 @@
     break;
   case ID_CTRL:
     if (m->value())
-      handleKeyPress(fakeKeyBase + 0, XK_Control_L);
+      handleKeyPress(0x1d, XK_Control_L);
     else
-      handleKeyRelease(fakeKeyBase + 0);
+      handleKeyRelease(0x1d);
     menuCtrlKey = !menuCtrlKey;
     break;
   case ID_ALT:
     if (m->value())
-      handleKeyPress(fakeKeyBase + 1, XK_Alt_L);
+      handleKeyPress(0x38, XK_Alt_L);
     else
-      handleKeyRelease(fakeKeyBase + 1);
+      handleKeyRelease(0x38);
     menuAltKey = !menuAltKey;
     break;
   case ID_MENUKEY:
-    handleKeyPress(fakeKeyBase + 2, menuKeySym);
-    handleKeyRelease(fakeKeyBase + 2);
+    handleKeyPress(menuKeyCode, menuKeySym);
+    handleKeyRelease(menuKeyCode);
     break;
   case ID_CTRLALTDEL:
-    handleKeyPress(fakeKeyBase + 3, XK_Control_L);
-    handleKeyPress(fakeKeyBase + 4, XK_Alt_L);
-    handleKeyPress(fakeKeyBase + 5, XK_Delete);
+    handleKeyPress(0x1d, XK_Control_L);
+    handleKeyPress(0x38, XK_Alt_L);
+    handleKeyPress(0xd3, XK_Delete);
 
-    handleKeyRelease(fakeKeyBase + 5);
-    handleKeyRelease(fakeKeyBase + 4);
-    handleKeyRelease(fakeKeyBase + 3);
+    handleKeyRelease(0xd3);
+    handleKeyRelease(0x38);
+    handleKeyRelease(0x1d);
     break;
   case ID_REFRESH:
     cc->refreshFramebuffer();
@@ -846,7 +1169,7 @@
 
 void Viewport::setMenuKey()
 {
-  getMenuKey(&menuKeyCode, &menuKeySym);
+  getMenuKey(&menuKeyFLTK, &menuKeyCode, &menuKeySym);
 }
 
 
diff --git a/vncviewer/Viewport.h b/vncviewer/Viewport.h
index 6f0710d..a4b7d8b 100644
--- a/vncviewer/Viewport.h
+++ b/vncviewer/Viewport.h
@@ -47,6 +47,11 @@
   void setCursor(int width, int height, const rfb::Point& hotspot,
                  const rdr::U8* data);
 
+  // Change client LED state
+  void setLEDState(unsigned int state);
+  // Change server LED state
+  void pushLEDState();
+
   void draw(Surface* dst);
 
   // Fl_Widget callback methods
@@ -59,6 +64,8 @@
 
 private:
 
+  unsigned int getModifierMask(unsigned int keysym);
+
   static void handleClipboardChange(int source, void *data);
 
   void handlePointerEvent(const rfb::Point& pos, int buttonMask);
@@ -88,7 +95,7 @@
   DownMap downKeySym;
 
   rdr::U32 menuKeySym;
-  int menuKeyCode;
+  int menuKeyCode, menuKeyFLTK;
   Fl_Menu_Button *contextMenu;
 
   bool menuCtrlKey;
diff --git a/vncviewer/cocoa.h b/vncviewer/cocoa.h
index 0c3ac82..a713802 100644
--- a/vncviewer/cocoa.h
+++ b/vncviewer/cocoa.h
@@ -33,4 +33,10 @@
 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);
+
+int cocoa_get_caps_lock_state(bool *on);
+int cocoa_get_num_lock_state(bool *on);
+
 #endif
diff --git a/vncviewer/cocoa.mm b/vncviewer/cocoa.mm
index 6e464fa..e26851d 100644
--- a/vncviewer/cocoa.mm
+++ b/vncviewer/cocoa.mm
@@ -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,77 @@
 
   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;
+}
+
+static int cocoa_get_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 = IOHIDGetModifierLockState(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);
+}
+
+int cocoa_get_caps_lock_state(bool *on)
+{
+  return cocoa_get_modifier_lock_state(kIOHIDCapsLockState, on);
+}
+
+int cocoa_get_num_lock_state(bool *on)
+{
+  return cocoa_get_modifier_lock_state(kIOHIDNumLockState, on);
+}
diff --git a/vncviewer/menukey.cxx b/vncviewer/menukey.cxx
index 04e52d0..25f9f56 100644
--- a/vncviewer/menukey.cxx
+++ b/vncviewer/menukey.cxx
@@ -30,26 +30,26 @@
 #include "parameters.h"
 
 static const MenuKeySymbol menuSymbols[] = {
-  {"F1", FL_F + 1, XK_F1},
-  {"F2", FL_F + 2, XK_F2},
-  {"F3", FL_F + 3, XK_F3},
-  {"F4", FL_F + 4, XK_F4},
-  {"F5", FL_F + 5, XK_F5},
-  {"F6", FL_F + 6, XK_F6},
-  {"F7", FL_F + 7, XK_F7},
-  {"F8", FL_F + 8, XK_F8},
-  {"F9", FL_F + 9, XK_F9},
-  {"F10", FL_F + 10, XK_F10},
-  {"F11", FL_F + 11, XK_F11},
-  {"F12", FL_F + 12, XK_F12},
-  {"Pause", FL_Pause, XK_Pause},
-  {"Scroll_Lock", FL_Scroll_Lock, XK_Scroll_Lock},
-  {"Escape", FL_Escape, XK_Escape},
-  {"Insert", FL_Insert, XK_Insert},
-  {"Delete", FL_Delete, XK_Delete},
-  {"Home", FL_Home, XK_Home},
-  {"Page_Up", FL_Page_Up, XK_Page_Up},
-  {"Page_Down", FL_Page_Down, XK_Page_Down},
+  {"F1",          FL_F + 1,       0x3b, XK_F1},
+  {"F2",          FL_F + 2,       0x3c, XK_F2},
+  {"F3",          FL_F + 3,       0x3d, XK_F3},
+  {"F4",          FL_F + 4,       0x3e, XK_F4},
+  {"F5",          FL_F + 5,       0x3f, XK_F5},
+  {"F6",          FL_F + 6,       0x40, XK_F6},
+  {"F7",          FL_F + 7,       0x41, XK_F7},
+  {"F8",          FL_F + 8,       0x42, XK_F8},
+  {"F9",          FL_F + 9,       0x43, XK_F9},
+  {"F10",         FL_F + 10,      0x44, XK_F10},
+  {"F11",         FL_F + 11,      0x57, XK_F11},
+  {"F12",         FL_F + 12,      0x58, XK_F12},
+  {"Pause",       FL_Pause,       0xc6, XK_Pause},
+  {"Scroll_Lock", FL_Scroll_Lock, 0x46, XK_Scroll_Lock},
+  {"Escape",      FL_Escape,      0x01, XK_Escape},
+  {"Insert",      FL_Insert,      0xd2, XK_Insert},
+  {"Delete",      FL_Delete,      0xd3, XK_Delete},
+  {"Home",        FL_Home,        0xc7, XK_Home},
+  {"Page_Up",     FL_Page_Up,     0xc9, XK_Page_Up},
+  {"Page_Down",   FL_Page_Down,   0xd1, XK_Page_Down},
 };
 
 int getMenuKeySymbolCount()
@@ -62,19 +62,21 @@
   return menuSymbols;
 }
 
-void getMenuKey(int *keycode, rdr::U32 *keysym)
+void getMenuKey(int *fltkcode, int *keycode, rdr::U32 *keysym)
 {
   const char *menuKeyStr;
 
   menuKeyStr = menuKey;
   for(int i = 0; i < getMenuKeySymbolCount(); i++) {
     if (!strcmp(menuSymbols[i].name, menuKeyStr)) {
+      *fltkcode = menuSymbols[i].fltkcode;
       *keycode = menuSymbols[i].keycode;
       *keysym = menuSymbols[i].keysym;
       return;
     }
   }
 
+  *fltkcode = 0;
   *keycode = 0;
   *keysym = 0;
 }
diff --git a/vncviewer/menukey.h b/vncviewer/menukey.h
index fcc5135..7935428 100644
--- a/vncviewer/menukey.h
+++ b/vncviewer/menukey.h
@@ -22,11 +22,12 @@
 
 typedef struct {
   const char* name;
+  int fltkcode;
   int keycode;
   rdr::U32 keysym;
 } MenuKeySymbol;
 
-void getMenuKey(int *keycode, rdr::U32 *keysym);
+void getMenuKey(int *fltkcode, int *keycode, rdr::U32 *keysym);
 int getMenuKeySymbolCount();
 const MenuKeySymbol* getMenuKeySymbols();
 
diff --git a/vncviewer/osx_to_qnum.c b/vncviewer/osx_to_qnum.c
new file mode 100644
index 0000000..511b7d5
--- /dev/null
+++ b/vncviewer/osx_to_qnum.c
@@ -0,0 +1,126 @@
+/*
+ * This file is auto-generated from keymaps.csv on 2017-08-28 13:04
+ * Database checksum sha256(f8aeff0c3430077a350e3d7ba2b335b381bd929ac4b193413730a402ff3f0097)
+ * To re-generate, run:
+ *   keymap-gen --lang=stdc code-map keymaps.csv osx qnum
+*/
+const unsigned short code_map_osx_to_qnum[256] = {
+  [0x0] = 0x1e, /* osx:0 (ANSI_A) -> linux:30 (KEY_A) -> qnum:30 */
+  [0x1] = 0x1f, /* osx:1 (ANSI_S) -> linux:31 (KEY_S) -> qnum:31 */
+  [0x2] = 0x20, /* osx:2 (ANSI_D) -> linux:32 (KEY_D) -> qnum:32 */
+  [0x3] = 0x21, /* osx:3 (ANSI_F) -> linux:33 (KEY_F) -> qnum:33 */
+  [0x4] = 0x23, /* osx:4 (ANSI_H) -> linux:35 (KEY_H) -> qnum:35 */
+  [0x5] = 0x22, /* osx:5 (ANSI_G) -> linux:34 (KEY_G) -> qnum:34 */
+  [0x6] = 0x2c, /* osx:6 (ANSI_Z) -> linux:44 (KEY_Z) -> qnum:44 */
+  [0x7] = 0x2d, /* osx:7 (ANSI_X) -> linux:45 (KEY_X) -> qnum:45 */
+  [0x8] = 0x2e, /* osx:8 (ANSI_C) -> linux:46 (KEY_C) -> qnum:46 */
+  [0x9] = 0x2f, /* osx:9 (ANSI_V) -> linux:47 (KEY_V) -> qnum:47 */
+  [0xa] = 0x70, /* osx:10 (ISO_Section) -> linux:170 (KEY_ISO) -> qnum:112 */
+  [0xb] = 0x30, /* osx:11 (ANSI_B) -> linux:48 (KEY_B) -> qnum:48 */
+  [0xc] = 0x10, /* osx:12 (ANSI_Q) -> linux:16 (KEY_Q) -> qnum:16 */
+  [0xd] = 0x11, /* osx:13 (ANSI_W) -> linux:17 (KEY_W) -> qnum:17 */
+  [0xe] = 0x12, /* osx:14 (ANSI_E) -> linux:18 (KEY_E) -> qnum:18 */
+  [0xf] = 0x13, /* osx:15 (ANSI_R) -> linux:19 (KEY_R) -> qnum:19 */
+  [0x10] = 0x15, /* osx:16 (ANSI_Y) -> linux:21 (KEY_Y) -> qnum:21 */
+  [0x11] = 0x14, /* osx:17 (ANSI_T) -> linux:20 (KEY_T) -> qnum:20 */
+  [0x12] = 0x2, /* osx:18 (ANSI_1) -> linux:2 (KEY_1) -> qnum:2 */
+  [0x13] = 0x3, /* osx:19 (ANSI_2) -> linux:3 (KEY_2) -> qnum:3 */
+  [0x14] = 0x4, /* osx:20 (ANSI_3) -> linux:4 (KEY_3) -> qnum:4 */
+  [0x15] = 0x5, /* osx:21 (ANSI_4) -> linux:5 (KEY_4) -> qnum:5 */
+  [0x16] = 0x7, /* osx:22 (ANSI_6) -> linux:7 (KEY_6) -> qnum:7 */
+  [0x17] = 0x6, /* osx:23 (ANSI_5) -> linux:6 (KEY_5) -> qnum:6 */
+  [0x18] = 0xd, /* osx:24 (ANSI_Equal) -> linux:13 (KEY_EQUAL) -> qnum:13 */
+  [0x19] = 0xa, /* osx:25 (ANSI_9) -> linux:10 (KEY_9) -> qnum:10 */
+  [0x1a] = 0x8, /* osx:26 (ANSI_7) -> linux:8 (KEY_7) -> qnum:8 */
+  [0x1b] = 0xc, /* osx:27 (ANSI_Minus) -> linux:12 (KEY_MINUS) -> qnum:12 */
+  [0x1c] = 0x9, /* osx:28 (ANSI_8) -> linux:9 (KEY_8) -> qnum:9 */
+  [0x1d] = 0xb, /* osx:29 (ANSI_0) -> linux:11 (KEY_0) -> qnum:11 */
+  [0x1e] = 0x1b, /* osx:30 (ANSI_RightBracket) -> linux:27 (KEY_RIGHTBRACE) -> qnum:27 */
+  [0x1f] = 0x18, /* osx:31 (ANSI_O) -> linux:24 (KEY_O) -> qnum:24 */
+  [0x20] = 0x16, /* osx:32 (ANSI_U) -> linux:22 (KEY_U) -> qnum:22 */
+  [0x21] = 0x1a, /* osx:33 (ANSI_LeftBracket) -> linux:26 (KEY_LEFTBRACE) -> qnum:26 */
+  [0x22] = 0x17, /* osx:34 (ANSI_I) -> linux:23 (KEY_I) -> qnum:23 */
+  [0x23] = 0x19, /* osx:35 (ANSI_P) -> linux:25 (KEY_P) -> qnum:25 */
+  [0x24] = 0x1c, /* osx:36 (Return) -> linux:28 (KEY_ENTER) -> qnum:28 */
+  [0x25] = 0x26, /* osx:37 (ANSI_L) -> linux:38 (KEY_L) -> qnum:38 */
+  [0x26] = 0x24, /* osx:38 (ANSI_J) -> linux:36 (KEY_J) -> qnum:36 */
+  [0x27] = 0x28, /* osx:39 (ANSI_Quote) -> linux:40 (KEY_APOSTROPHE) -> qnum:40 */
+  [0x28] = 0x25, /* osx:40 (ANSI_K) -> linux:37 (KEY_K) -> qnum:37 */
+  [0x29] = 0x27, /* osx:41 (ANSI_Semicolon) -> linux:39 (KEY_SEMICOLON) -> qnum:39 */
+  [0x2a] = 0x2b, /* osx:42 (ANSI_Backslash) -> linux:43 (KEY_BACKSLASH) -> qnum:43 */
+  [0x2b] = 0x33, /* osx:43 (ANSI_Comma) -> linux:51 (KEY_COMMA) -> qnum:51 */
+  [0x2c] = 0x35, /* osx:44 (ANSI_Slash) -> linux:53 (KEY_SLASH) -> qnum:53 */
+  [0x2d] = 0x31, /* osx:45 (ANSI_N) -> linux:49 (KEY_N) -> qnum:49 */
+  [0x2e] = 0x32, /* osx:46 (ANSI_M) -> linux:50 (KEY_M) -> qnum:50 */
+  [0x2f] = 0x34, /* osx:47 (ANSI_Period) -> linux:52 (KEY_DOT) -> qnum:52 */
+  [0x30] = 0xf, /* osx:48 (Tab) -> linux:15 (KEY_TAB) -> qnum:15 */
+  [0x31] = 0x39, /* osx:49 (Space) -> linux:57 (KEY_SPACE) -> qnum:57 */
+  [0x32] = 0x29, /* osx:50 (ANSI_Grave) -> linux:41 (KEY_GRAVE) -> qnum:41 */
+  [0x33] = 0xe, /* osx:51 (Delete) -> linux:14 (KEY_BACKSPACE) -> qnum:14 */
+  [0x35] = 0x1, /* osx:53 (Escape) -> linux:1 (KEY_ESC) -> qnum:1 */
+  [0x36] = 0xdc, /* osx:54 (RightCommand) -> linux:126 (KEY_RIGHTMETA) -> qnum:220 */
+  [0x37] = 0xdb, /* osx:55 (Command) -> linux:125 (KEY_LEFTMETA) -> qnum:219 */
+  [0x38] = 0x2a, /* osx:56 (Shift) -> linux:42 (KEY_LEFTSHIFT) -> qnum:42 */
+  [0x39] = 0x3a, /* osx:57 (CapsLock) -> linux:58 (KEY_CAPSLOCK) -> qnum:58 */
+  [0x3a] = 0x38, /* osx:58 (Option) -> linux:56 (KEY_LEFTALT) -> qnum:56 */
+  [0x3b] = 0x1d, /* osx:59 (Control) -> linux:29 (KEY_LEFTCTRL) -> qnum:29 */
+  [0x3c] = 0x36, /* osx:60 (RightShift) -> linux:54 (KEY_RIGHTSHIFT) -> qnum:54 */
+  [0x3d] = 0xb8, /* osx:61 (RightOption) -> linux:100 (KEY_RIGHTALT) -> qnum:184 */
+  [0x3e] = 0x9d, /* osx:62 (RightControl) -> linux:97 (KEY_RIGHTCTRL) -> qnum:157 */
+  [0x40] = 0x83, /* osx:64 (F17) -> linux:187 (KEY_F17) -> qnum:131 */
+  [0x41] = 0x53, /* osx:65 (ANSI_KeypadDecimal) -> linux:83 (KEY_KPDOT) -> qnum:83 */
+  [0x43] = 0x37, /* osx:67 (ANSI_KeypadMultiply) -> linux:55 (KEY_KPASTERISK) -> qnum:55 */
+  [0x45] = 0x4e, /* osx:69 (ANSI_KeypadPlus) -> linux:78 (KEY_KPPLUS) -> qnum:78 */
+  [0x47] = 0x45, /* osx:71 (ANSI_KeypadClear) -> linux:69 (KEY_NUMLOCK) -> qnum:69 */
+  [0x48] = 0xb0, /* osx:72 (VolumeUp) -> linux:115 (KEY_VOLUMEUP) -> qnum:176 */
+  [0x49] = 0xae, /* osx:73 (VolumeDown) -> linux:114 (KEY_VOLUMEDOWN) -> qnum:174 */
+  [0x4a] = 0xa0, /* osx:74 (Mute) -> linux:113 (KEY_MUTE) -> qnum:160 */
+  [0x4b] = 0xb5, /* osx:75 (ANSI_KeypadDivide) -> linux:98 (KEY_KPSLASH) -> qnum:181 */
+  [0x4c] = 0x9c, /* osx:76 (ANSI_KeypadEnter) -> linux:96 (KEY_KPENTER) -> qnum:156 */
+  [0x4e] = 0x4a, /* osx:78 (ANSI_KeypadMinus) -> linux:74 (KEY_KPMINUS) -> qnum:74 */
+  [0x4f] = 0xf7, /* osx:79 (F18) -> linux:188 (KEY_F18) -> qnum:247 */
+  [0x50] = 0x84, /* osx:80 (F19) -> linux:189 (KEY_F19) -> qnum:132 */
+  [0x51] = 0x59, /* osx:81 (ANSI_KeypadEquals) -> linux:117 (KEY_KPEQUAL) -> qnum:89 */
+  [0x52] = 0x52, /* osx:82 (ANSI_Keypad0) -> linux:82 (KEY_KP0) -> qnum:82 */
+  [0x53] = 0x4f, /* osx:83 (ANSI_Keypad1) -> linux:79 (KEY_KP1) -> qnum:79 */
+  [0x54] = 0x50, /* osx:84 (ANSI_Keypad2) -> linux:80 (KEY_KP2) -> qnum:80 */
+  [0x55] = 0x51, /* osx:85 (ANSI_Keypad3) -> linux:81 (KEY_KP3) -> qnum:81 */
+  [0x56] = 0x4b, /* osx:86 (ANSI_Keypad4) -> linux:75 (KEY_KP4) -> qnum:75 */
+  [0x57] = 0x4c, /* osx:87 (ANSI_Keypad5) -> linux:76 (KEY_KP5) -> qnum:76 */
+  [0x58] = 0x4d, /* osx:88 (ANSI_Keypad6) -> linux:77 (KEY_KP6) -> qnum:77 */
+  [0x59] = 0x47, /* osx:89 (ANSI_Keypad7) -> linux:71 (KEY_KP7) -> qnum:71 */
+  [0x5a] = 0x5a, /* osx:90 (F20) -> linux:190 (KEY_F20) -> qnum:90 */
+  [0x5b] = 0x48, /* osx:91 (ANSI_Keypad8) -> linux:72 (KEY_KP8) -> qnum:72 */
+  [0x5c] = 0x49, /* osx:92 (ANSI_Keypad9) -> linux:73 (KEY_KP9) -> qnum:73 */
+  [0x5d] = 0x7d, /* osx:93 (JIS_Yen) -> linux:124 (KEY_YEN) -> qnum:125 */
+  [0x5f] = 0x5c, /* osx:95 (JIS_KeypadComma) -> linux:95 (KEY_KPJPCOMMA) -> qnum:92 */
+  [0x60] = 0x3f, /* osx:96 (F5) -> linux:63 (KEY_F5) -> qnum:63 */
+  [0x61] = 0x40, /* osx:97 (F6) -> linux:64 (KEY_F6) -> qnum:64 */
+  [0x62] = 0x41, /* osx:98 (F7) -> linux:65 (KEY_F7) -> qnum:65 */
+  [0x63] = 0x3d, /* osx:99 (F3) -> linux:61 (KEY_F3) -> qnum:61 */
+  [0x64] = 0x42, /* osx:100 (F8) -> linux:66 (KEY_F8) -> qnum:66 */
+  [0x65] = 0x43, /* osx:101 (F9) -> linux:67 (KEY_F9) -> qnum:67 */
+  [0x67] = 0x57, /* osx:103 (F11) -> linux:87 (KEY_F11) -> qnum:87 */
+  [0x68] = 0x78, /* osx:104 (JIS_Kana) -> linux:90 (KEY_KATAKANA) -> qnum:120 */
+  [0x69] = 0x5d, /* osx:105 (F13) -> linux:183 (KEY_F13) -> qnum:93 */
+  [0x6a] = 0x55, /* osx:106 (F16) -> linux:186 (KEY_F16) -> qnum:85 */
+  [0x6b] = 0x5e, /* osx:107 (F14) -> linux:184 (KEY_F14) -> qnum:94 */
+  [0x6d] = 0x44, /* osx:109 (F10) -> linux:68 (KEY_F10) -> qnum:68 */
+  [0x6e] = 0xdd, /* osx:110 (unnamed) -> linux:127 (KEY_COMPOSE) -> qnum:221 */
+  [0x6f] = 0x58, /* osx:111 (F12) -> linux:88 (KEY_F12) -> qnum:88 */
+  [0x71] = 0x5f, /* osx:113 (F15) -> linux:185 (KEY_F15) -> qnum:95 */
+  [0x72] = 0xf5, /* osx:114 (Help) -> linux:138 (KEY_HELP) -> qnum:245 */
+  [0x73] = 0xc7, /* osx:115 (Home) -> linux:102 (KEY_HOME) -> qnum:199 */
+  [0x74] = 0xc9, /* osx:116 (PageUp) -> linux:104 (KEY_PAGEUP) -> qnum:201 */
+  [0x75] = 0xd3, /* osx:117 (ForwardDelete) -> linux:111 (KEY_DELETE) -> qnum:211 */
+  [0x76] = 0x3e, /* osx:118 (F4) -> linux:62 (KEY_F4) -> qnum:62 */
+  [0x77] = 0xcf, /* osx:119 (End) -> linux:107 (KEY_END) -> qnum:207 */
+  [0x78] = 0x3c, /* osx:120 (F2) -> linux:60 (KEY_F2) -> qnum:60 */
+  [0x79] = 0xd1, /* osx:121 (PageDown) -> linux:109 (KEY_PAGEDOWN) -> qnum:209 */
+  [0x7a] = 0x3b, /* osx:122 (F1) -> linux:59 (KEY_F1) -> qnum:59 */
+  [0x7b] = 0xcb, /* osx:123 (LeftArrow) -> linux:105 (KEY_LEFT) -> qnum:203 */
+  [0x7c] = 0xcd, /* osx:124 (RightArrow) -> linux:106 (KEY_RIGHT) -> qnum:205 */
+  [0x7d] = 0xd0, /* osx:125 (DownArrow) -> linux:108 (KEY_DOWN) -> qnum:208 */
+  [0x7e] = 0xc8, /* osx:126 (UpArrow) -> linux:103 (KEY_UP) -> qnum:200 */
+};
+const unsigned int code_map_osx_to_qnum_len = sizeof(code_map_osx_to_qnum)/sizeof(code_map_osx_to_qnum[0]);
diff --git a/vncviewer/xkb_to_qnum.c b/vncviewer/xkb_to_qnum.c
new file mode 100644
index 0000000..759c3d4
--- /dev/null
+++ b/vncviewer/xkb_to_qnum.c
@@ -0,0 +1,257 @@
+/*
+ * This file is auto-generated from keymaps.csv on 2017-08-28 13:04
+ * Database checksum sha256(f8aeff0c3430077a350e3d7ba2b335b381bd929ac4b193413730a402ff3f0097)
+ * To re-generate, run:
+ *   keymap-gen --lang=stdc code-map keymaps.csv xkb qnum
+*/
+const struct _code_map_xkb_to_qnum {
+  const char * from;
+  const unsigned short to;
+} code_map_xkb_to_qnum[] = {
+  {"AB00", 0x29}, /* xkb:AB00 (AB00) -> linux:41 (KEY_GRAVE) -> qnum:41 */
+  {"AB01", 0x2c}, /* xkb:AB01 (AB01) -> linux:44 (KEY_Z) -> qnum:44 */
+  {"AB02", 0x2d}, /* xkb:AB02 (AB02) -> linux:45 (KEY_X) -> qnum:45 */
+  {"AB03", 0x2e}, /* xkb:AB03 (AB03) -> linux:46 (KEY_C) -> qnum:46 */
+  {"AB04", 0x2f}, /* xkb:AB04 (AB04) -> linux:47 (KEY_V) -> qnum:47 */
+  {"AB05", 0x30}, /* xkb:AB05 (AB05) -> linux:48 (KEY_B) -> qnum:48 */
+  {"AB06", 0x31}, /* xkb:AB06 (AB06) -> linux:49 (KEY_N) -> qnum:49 */
+  {"AB07", 0x32}, /* xkb:AB07 (AB07) -> linux:50 (KEY_M) -> qnum:50 */
+  {"AB08", 0x33}, /* xkb:AB08 (AB08) -> linux:51 (KEY_COMMA) -> qnum:51 */
+  {"AB09", 0x34}, /* xkb:AB09 (AB09) -> linux:52 (KEY_DOT) -> qnum:52 */
+  {"AB10", 0x35}, /* xkb:AB10 (AB10) -> linux:53 (KEY_SLASH) -> qnum:53 */
+  {"AB11", 0x73}, /* xkb:AB11 (AB11) -> linux:89 (KEY_RO) -> qnum:115 */
+  {"AC01", 0x1e}, /* xkb:AC01 (AC01) -> linux:30 (KEY_A) -> qnum:30 */
+  {"AC02", 0x1f}, /* xkb:AC02 (AC02) -> linux:31 (KEY_S) -> qnum:31 */
+  {"AC03", 0x20}, /* xkb:AC03 (AC03) -> linux:32 (KEY_D) -> qnum:32 */
+  {"AC04", 0x21}, /* xkb:AC04 (AC04) -> linux:33 (KEY_F) -> qnum:33 */
+  {"AC05", 0x22}, /* xkb:AC05 (AC05) -> linux:34 (KEY_G) -> qnum:34 */
+  {"AC06", 0x23}, /* xkb:AC06 (AC06) -> linux:35 (KEY_H) -> qnum:35 */
+  {"AC07", 0x24}, /* xkb:AC07 (AC07) -> linux:36 (KEY_J) -> qnum:36 */
+  {"AC08", 0x25}, /* xkb:AC08 (AC08) -> linux:37 (KEY_K) -> qnum:37 */
+  {"AC09", 0x26}, /* xkb:AC09 (AC09) -> linux:38 (KEY_L) -> qnum:38 */
+  {"AC10", 0x27}, /* xkb:AC10 (AC10) -> linux:39 (KEY_SEMICOLON) -> qnum:39 */
+  {"AC11", 0x28}, /* xkb:AC11 (AC11) -> linux:40 (KEY_APOSTROPHE) -> qnum:40 */
+  {"AC12", 0x2b}, /* xkb:AC12 (AC12) -> linux:43 (KEY_BACKSLASH) -> qnum:43 */
+  {"AD01", 0x10}, /* xkb:AD01 (AD01) -> linux:16 (KEY_Q) -> qnum:16 */
+  {"AD02", 0x11}, /* xkb:AD02 (AD02) -> linux:17 (KEY_W) -> qnum:17 */
+  {"AD03", 0x12}, /* xkb:AD03 (AD03) -> linux:18 (KEY_E) -> qnum:18 */
+  {"AD04", 0x13}, /* xkb:AD04 (AD04) -> linux:19 (KEY_R) -> qnum:19 */
+  {"AD05", 0x14}, /* xkb:AD05 (AD05) -> linux:20 (KEY_T) -> qnum:20 */
+  {"AD06", 0x15}, /* xkb:AD06 (AD06) -> linux:21 (KEY_Y) -> qnum:21 */
+  {"AD07", 0x16}, /* xkb:AD07 (AD07) -> linux:22 (KEY_U) -> qnum:22 */
+  {"AD08", 0x17}, /* xkb:AD08 (AD08) -> linux:23 (KEY_I) -> qnum:23 */
+  {"AD09", 0x18}, /* xkb:AD09 (AD09) -> linux:24 (KEY_O) -> qnum:24 */
+  {"AD10", 0x19}, /* xkb:AD10 (AD10) -> linux:25 (KEY_P) -> qnum:25 */
+  {"AD11", 0x1a}, /* xkb:AD11 (AD11) -> linux:26 (KEY_LEFTBRACE) -> qnum:26 */
+  {"AD12", 0x1b}, /* xkb:AD12 (AD12) -> linux:27 (KEY_RIGHTBRACE) -> qnum:27 */
+  {"AE01", 0x2}, /* xkb:AE01 (AE01) -> linux:2 (KEY_1) -> qnum:2 */
+  {"AE02", 0x3}, /* xkb:AE02 (AE02) -> linux:3 (KEY_2) -> qnum:3 */
+  {"AE03", 0x4}, /* xkb:AE03 (AE03) -> linux:4 (KEY_3) -> qnum:4 */
+  {"AE04", 0x5}, /* xkb:AE04 (AE04) -> linux:5 (KEY_4) -> qnum:5 */
+  {"AE05", 0x6}, /* xkb:AE05 (AE05) -> linux:6 (KEY_5) -> qnum:6 */
+  {"AE06", 0x7}, /* xkb:AE06 (AE06) -> linux:7 (KEY_6) -> qnum:7 */
+  {"AE07", 0x8}, /* xkb:AE07 (AE07) -> linux:8 (KEY_7) -> qnum:8 */
+  {"AE08", 0x9}, /* xkb:AE08 (AE08) -> linux:9 (KEY_8) -> qnum:9 */
+  {"AE09", 0xa}, /* xkb:AE09 (AE09) -> linux:10 (KEY_9) -> qnum:10 */
+  {"AE10", 0xb}, /* xkb:AE10 (AE10) -> linux:11 (KEY_0) -> qnum:11 */
+  {"AE11", 0xc}, /* xkb:AE11 (AE11) -> linux:12 (KEY_MINUS) -> qnum:12 */
+  {"AE12", 0xd}, /* xkb:AE12 (AE12) -> linux:13 (KEY_EQUAL) -> qnum:13 */
+  {"AE13", 0x7d}, /* xkb:AE13 (AE13) -> linux:124 (KEY_YEN) -> qnum:125 */
+  {"AGAI", 0x85}, /* xkb:AGAI (AGAI) -> linux:129 (KEY_AGAIN) -> qnum:133 */
+  {"ALGR", 0xb8}, /* xkb:ALGR (RALT) -> linux:100 (KEY_RIGHTALT) -> qnum:184 */
+  {"BKSL", 0x2b}, /* xkb:BKSL (AC12) -> linux:43 (KEY_BACKSLASH) -> qnum:43 */
+  {"BKSP", 0xe}, /* xkb:BKSP (BKSP) -> linux:14 (KEY_BACKSPACE) -> qnum:14 */
+  {"CAPS", 0x3a}, /* xkb:CAPS (CAPS) -> linux:58 (KEY_CAPSLOCK) -> qnum:58 */
+  {"COMP", 0xdd}, /* xkb:COMP (COMP) -> linux:127 (KEY_COMPOSE) -> qnum:221 */
+  {"COPY", 0xf8}, /* xkb:COPY (COPY) -> linux:133 (KEY_COPY) -> qnum:248 */
+  {"CUT", 0xbc}, /* xkb:CUT (CUT) -> linux:137 (KEY_CUT) -> qnum:188 */
+  {"DEL", 0xd3}, /* xkb:DEL (DELE) -> linux:111 (KEY_DELETE) -> qnum:211 */
+  {"DELE", 0xd3}, /* xkb:DELE (DELE) -> linux:111 (KEY_DELETE) -> qnum:211 */
+  {"DOWN", 0xd0}, /* xkb:DOWN (DOWN) -> linux:108 (KEY_DOWN) -> qnum:208 */
+  {"END", 0xcf}, /* xkb:END (END) -> linux:107 (KEY_END) -> qnum:207 */
+  {"ESC", 0x1}, /* xkb:ESC (ESC) -> linux:1 (KEY_ESC) -> qnum:1 */
+  {"FIND", 0xc1}, /* xkb:FIND (FIND) -> linux:136 (KEY_FIND) -> qnum:193 */
+  {"FK01", 0x3b}, /* xkb:FK01 (FK01) -> linux:59 (KEY_F1) -> qnum:59 */
+  {"FK02", 0x3c}, /* xkb:FK02 (FK02) -> linux:60 (KEY_F2) -> qnum:60 */
+  {"FK03", 0x3d}, /* xkb:FK03 (FK03) -> linux:61 (KEY_F3) -> qnum:61 */
+  {"FK04", 0x3e}, /* xkb:FK04 (FK04) -> linux:62 (KEY_F4) -> qnum:62 */
+  {"FK05", 0x3f}, /* xkb:FK05 (FK05) -> linux:63 (KEY_F5) -> qnum:63 */
+  {"FK06", 0x40}, /* xkb:FK06 (FK06) -> linux:64 (KEY_F6) -> qnum:64 */
+  {"FK07", 0x41}, /* xkb:FK07 (FK07) -> linux:65 (KEY_F7) -> qnum:65 */
+  {"FK08", 0x42}, /* xkb:FK08 (FK08) -> linux:66 (KEY_F8) -> qnum:66 */
+  {"FK09", 0x43}, /* xkb:FK09 (FK09) -> linux:67 (KEY_F9) -> qnum:67 */
+  {"FK10", 0x44}, /* xkb:FK10 (FK10) -> linux:68 (KEY_F10) -> qnum:68 */
+  {"FK11", 0x57}, /* xkb:FK11 (FK11) -> linux:87 (KEY_F11) -> qnum:87 */
+  {"FK12", 0x58}, /* xkb:FK12 (FK12) -> linux:88 (KEY_F12) -> qnum:88 */
+  {"FK13", 0x5d}, /* xkb:FK13 (FK13) -> linux:183 (KEY_F13) -> qnum:93 */
+  {"FK14", 0x5e}, /* xkb:FK14 (FK14) -> linux:184 (KEY_F14) -> qnum:94 */
+  {"FK15", 0x5f}, /* xkb:FK15 (FK15) -> linux:185 (KEY_F15) -> qnum:95 */
+  {"FK16", 0x55}, /* xkb:FK16 (FK16) -> linux:186 (KEY_F16) -> qnum:85 */
+  {"FK17", 0x83}, /* xkb:FK17 (FK17) -> linux:187 (KEY_F17) -> qnum:131 */
+  {"FK18", 0xf7}, /* xkb:FK18 (FK18) -> linux:188 (KEY_F18) -> qnum:247 */
+  {"FK19", 0x84}, /* xkb:FK19 (FK19) -> linux:189 (KEY_F19) -> qnum:132 */
+  {"FK20", 0x5a}, /* xkb:FK20 (FK20) -> linux:190 (KEY_F20) -> qnum:90 */
+  {"FK21", 0x74}, /* xkb:FK21 (FK21) -> linux:191 (KEY_F21) -> qnum:116 */
+  {"FK22", 0xf9}, /* xkb:FK22 (FK22) -> linux:192 (KEY_F22) -> qnum:249 */
+  {"FK23", 0x6d}, /* xkb:FK23 (FK23) -> linux:193 (KEY_F23) -> qnum:109 */
+  {"FK24", 0x6f}, /* xkb:FK24 (FK24) -> linux:194 (KEY_F24) -> qnum:111 */
+  {"FRNT", 0x8c}, /* xkb:FRNT (FRNT) -> linux:132 (KEY_FRONT) -> qnum:140 */
+  {"HELP", 0xf5}, /* xkb:HELP (HELP) -> linux:138 (KEY_HELP) -> qnum:245 */
+  {"HENK", 0x79}, /* xkb:HENK (HENK) -> linux:92 (KEY_HENKAN) -> qnum:121 */
+  {"HIRA", 0x77}, /* xkb:HIRA (HIRA) -> linux:91 (KEY_HIRAGANA) -> qnum:119 */
+  {"HJCV", 0x8d}, /* xkb:HJCV (HJCV) -> linux:123 (KEY_HANJA) -> qnum:141 */
+  {"HKTG", 0x70}, /* xkb:HKTG (HKTG) -> linux:93 (KEY_KATAKANAHIRAGANA) -> qnum:112 */
+  {"HOME", 0xc7}, /* xkb:HOME (HOME) -> linux:102 (KEY_HOME) -> qnum:199 */
+  {"HZTG", 0x76}, /* xkb:HZTG (HZTG) -> linux:85 (KEY_ZENKAKUHANKAKU) -> qnum:118 */
+  {"I120", 0xef}, /* xkb:I120 (I120) -> linux:112 (KEY_MACRO) -> qnum:239 */
+  {"I126", 0xce}, /* xkb:I126 (I126) -> linux:118 (KEY_KPPLUSMINUS) -> qnum:206 */
+  {"I128", 0x8b}, /* xkb:I128 (I128) -> linux:120 (KEY_SCALE) -> qnum:139 */
+  {"I129", 0x7e}, /* xkb:I129 (I129) -> linux:121 (KEY_KPCOMMA) -> qnum:126 */
+  {"I147", 0x9e}, /* xkb:I147 (I147) -> linux:139 (KEY_MENU) -> qnum:158 */
+  {"I148", 0xa1}, /* xkb:I148 (I148) -> linux:140 (KEY_CALC) -> qnum:161 */
+  {"I149", 0x66}, /* xkb:I149 (I149) -> linux:141 (KEY_SETUP) -> qnum:102 */
+  {"I150", 0xdf}, /* xkb:I150 (I150) -> linux:142 (KEY_SLEEP) -> qnum:223 */
+  {"I151", 0xe3}, /* xkb:I151 (I151) -> linux:143 (KEY_WAKEUP) -> qnum:227 */
+  {"I152", 0x67}, /* xkb:I152 (I152) -> linux:144 (KEY_FILE) -> qnum:103 */
+  {"I153", 0x68}, /* xkb:I153 (I153) -> linux:145 (KEY_SENDFILE) -> qnum:104 */
+  {"I154", 0x69}, /* xkb:I154 (I154) -> linux:146 (KEY_DELETEFILE) -> qnum:105 */
+  {"I155", 0x93}, /* xkb:I155 (I155) -> linux:147 (KEY_XFER) -> qnum:147 */
+  {"I156", 0x9f}, /* xkb:I156 (I156) -> linux:148 (KEY_PROG1) -> qnum:159 */
+  {"I157", 0x97}, /* xkb:I157 (I157) -> linux:149 (KEY_PROG2) -> qnum:151 */
+  {"I158", 0x82}, /* xkb:I158 (I158) -> linux:150 (KEY_WWW) -> qnum:130 */
+  {"I159", 0x6a}, /* xkb:I159 (I159) -> linux:151 (KEY_MSDOS) -> qnum:106 */
+  {"I160", 0x92}, /* xkb:I160 (I160) -> linux:152 (KEY_SCREENLOCK) -> qnum:146 */
+  {"I161", 0x6b}, /* xkb:I161 (I161) -> linux:153 (KEY_DIRECTION) -> qnum:107 */
+  {"I162", 0xa6}, /* xkb:I162 (I162) -> linux:154 (KEY_CYCLEWINDOWS) -> qnum:166 */
+  {"I163", 0xec}, /* xkb:I163 (I163) -> linux:155 (KEY_MAIL) -> qnum:236 */
+  {"I164", 0xe6}, /* xkb:I164 (I164) -> linux:156 (KEY_BOOKMARKS) -> qnum:230 */
+  {"I165", 0xeb}, /* xkb:I165 (I165) -> linux:157 (KEY_COMPUTER) -> qnum:235 */
+  {"I166", 0xea}, /* xkb:I166 (I166) -> linux:158 (KEY_BACK) -> qnum:234 */
+  {"I167", 0xe9}, /* xkb:I167 (I167) -> linux:159 (KEY_FORWARD) -> qnum:233 */
+  {"I168", 0xa3}, /* xkb:I168 (I168) -> linux:160 (KEY_CLOSECD) -> qnum:163 */
+  {"I169", 0x6c}, /* xkb:I169 (I169) -> linux:161 (KEY_EJECTCD) -> qnum:108 */
+  {"I170", 0xfd}, /* xkb:I170 (I170) -> linux:162 (KEY_EJECTCLOSECD) -> qnum:253 */
+  {"I171", 0x99}, /* xkb:I171 (I171) -> linux:163 (KEY_NEXTSONG) -> qnum:153 */
+  {"I172", 0xa2}, /* xkb:I172 (I172) -> linux:164 (KEY_PLAYPAUSE) -> qnum:162 */
+  {"I173", 0x90}, /* xkb:I173 (I173) -> linux:165 (KEY_PREVIOUSSONG) -> qnum:144 */
+  {"I174", 0xa4}, /* xkb:I174 (I174) -> linux:166 (KEY_STOPCD) -> qnum:164 */
+  {"I175", 0xb1}, /* xkb:I175 (I175) -> linux:167 (KEY_RECORD) -> qnum:177 */
+  {"I176", 0x98}, /* xkb:I176 (I176) -> linux:168 (KEY_REWIND) -> qnum:152 */
+  {"I177", 0x63}, /* xkb:I177 (I177) -> linux:169 (KEY_PHONE) -> qnum:99 */
+  {"I178", 0x70}, /* xkb:I178 (I178) -> linux:170 (KEY_ISO) -> qnum:112 */
+  {"I179", 0x81}, /* xkb:I179 (I179) -> linux:171 (KEY_CONFIG) -> qnum:129 */
+  {"I180", 0xb2}, /* xkb:I180 (I180) -> linux:172 (KEY_HOMEPAGE) -> qnum:178 */
+  {"I181", 0xe7}, /* xkb:I181 (I181) -> linux:173 (KEY_REFRESH) -> qnum:231 */
+  {"I182", 0x71}, /* xkb:I182 (I182) -> linux:174 (KEY_EXIT) -> qnum:113 */
+  {"I183", 0x72}, /* xkb:I183 (I183) -> linux:175 (KEY_MOVE) -> qnum:114 */
+  {"I184", 0x88}, /* xkb:I184 (I184) -> linux:176 (KEY_EDIT) -> qnum:136 */
+  {"I185", 0x75}, /* xkb:I185 (I185) -> linux:177 (KEY_SCROLLUP) -> qnum:117 */
+  {"I186", 0x8f}, /* xkb:I186 (I186) -> linux:178 (KEY_SCROLLDOWN) -> qnum:143 */
+  {"I187", 0xf6}, /* xkb:I187 (I187) -> linux:179 (KEY_KPLEFTPAREN) -> qnum:246 */
+  {"I188", 0xfb}, /* xkb:I188 (I188) -> linux:180 (KEY_KPRIGHTPAREN) -> qnum:251 */
+  {"I189", 0x89}, /* xkb:I189 (I189) -> linux:181 (KEY_NEW) -> qnum:137 */
+  {"I190", 0x8a}, /* xkb:I190 (I190) -> linux:182 (KEY_REDO) -> qnum:138 */
+  {"I208", 0xa8}, /* xkb:I208 (I208) -> linux:200 (KEY_PLAYCD) -> qnum:168 */
+  {"I209", 0xa9}, /* xkb:I209 (I209) -> linux:201 (KEY_PAUSECD) -> qnum:169 */
+  {"I210", 0xab}, /* xkb:I210 (I210) -> linux:202 (KEY_PROG3) -> qnum:171 */
+  {"I211", 0xac}, /* xkb:I211 (I211) -> linux:203 (KEY_PROG4) -> qnum:172 */
+  {"I212", 0xad}, /* xkb:I212 (I212) -> linux:204 (KEY_DASHBOARD) -> qnum:173 */
+  {"I213", 0xa5}, /* xkb:I213 (I213) -> linux:205 (KEY_SUSPEND) -> qnum:165 */
+  {"I214", 0xaf}, /* xkb:I214 (I214) -> linux:206 (KEY_CLOSE) -> qnum:175 */
+  {"I215", 0xb3}, /* xkb:I215 (I215) -> linux:207 (KEY_PLAY) -> qnum:179 */
+  {"I216", 0xb4}, /* xkb:I216 (I216) -> linux:208 (KEY_FASTFORWARD) -> qnum:180 */
+  {"I217", 0xb6}, /* xkb:I217 (I217) -> linux:209 (KEY_BASSBOOST) -> qnum:182 */
+  {"I218", 0xb9}, /* xkb:I218 (I218) -> linux:210 (KEY_PRINT) -> qnum:185 */
+  {"I219", 0xba}, /* xkb:I219 (I219) -> linux:211 (KEY_HP) -> qnum:186 */
+  {"I220", 0xbb}, /* xkb:I220 (I220) -> linux:212 (KEY_CAMERA) -> qnum:187 */
+  {"I221", 0xbd}, /* xkb:I221 (I221) -> linux:213 (KEY_SOUND) -> qnum:189 */
+  {"I222", 0xbe}, /* xkb:I222 (I222) -> linux:214 (KEY_QUESTION) -> qnum:190 */
+  {"I223", 0xbf}, /* xkb:I223 (I223) -> linux:215 (KEY_EMAIL) -> qnum:191 */
+  {"I224", 0xc0}, /* xkb:I224 (I224) -> linux:216 (KEY_CHAT) -> qnum:192 */
+  {"I225", 0xe5}, /* xkb:I225 (I225) -> linux:217 (KEY_SEARCH) -> qnum:229 */
+  {"I226", 0xc2}, /* xkb:I226 (I226) -> linux:218 (KEY_CONNECT) -> qnum:194 */
+  {"I227", 0xc3}, /* xkb:I227 (I227) -> linux:219 (KEY_FINANCE) -> qnum:195 */
+  {"I228", 0xc4}, /* xkb:I228 (I228) -> linux:220 (KEY_SPORT) -> qnum:196 */
+  {"I229", 0xc5}, /* xkb:I229 (I229) -> linux:221 (KEY_SHOP) -> qnum:197 */
+  {"I230", 0x94}, /* xkb:I230 (I230) -> linux:222 (KEY_ALTERASE) -> qnum:148 */
+  {"I231", 0xca}, /* xkb:I231 (I231) -> linux:223 (KEY_CANCEL) -> qnum:202 */
+  {"I232", 0xcc}, /* xkb:I232 (I232) -> linux:224 (KEY_BRIGHTNESSDOWN) -> qnum:204 */
+  {"I233", 0xd4}, /* xkb:I233 (I233) -> linux:225 (KEY_BRIGHTNESSUP) -> qnum:212 */
+  {"I234", 0xed}, /* xkb:I234 (I234) -> linux:226 (KEY_MEDIA) -> qnum:237 */
+  {"I235", 0xd6}, /* xkb:I235 (I235) -> linux:227 (KEY_SWITCHVIDEOMODE) -> qnum:214 */
+  {"I236", 0xd7}, /* xkb:I236 (I236) -> linux:228 (KEY_KBDILLUMTOGGLE) -> qnum:215 */
+  {"I237", 0xd8}, /* xkb:I237 (I237) -> linux:229 (KEY_KBDILLUMDOWN) -> qnum:216 */
+  {"I238", 0xd9}, /* xkb:I238 (I238) -> linux:230 (KEY_KBDILLUMUP) -> qnum:217 */
+  {"I239", 0xda}, /* xkb:I239 (I239) -> linux:231 (KEY_SEND) -> qnum:218 */
+  {"I240", 0xe4}, /* xkb:I240 (I240) -> linux:232 (KEY_REPLY) -> qnum:228 */
+  {"I241", 0x8e}, /* xkb:I241 (I241) -> linux:233 (KEY_FORWARDMAIL) -> qnum:142 */
+  {"I242", 0xd5}, /* xkb:I242 (I242) -> linux:234 (KEY_SAVE) -> qnum:213 */
+  {"I243", 0xf0}, /* xkb:I243 (I243) -> linux:235 (KEY_DOCUMENTS) -> qnum:240 */
+  {"I244", 0xf1}, /* xkb:I244 (I244) -> linux:236 (KEY_BATTERY) -> qnum:241 */
+  {"I245", 0xf2}, /* xkb:I245 (I245) -> linux:237 (KEY_BLUETOOTH) -> qnum:242 */
+  {"I246", 0xf3}, /* xkb:I246 (I246) -> linux:238 (KEY_WLAN) -> qnum:243 */
+  {"I247", 0xf4}, /* xkb:I247 (I247) -> linux:239 (KEY_UWB) -> qnum:244 */
+  {"INS", 0xd2}, /* xkb:INS (INS) -> linux:110 (KEY_INSERT) -> qnum:210 */
+  {"JPCM", 0x5c}, /* xkb:JPCM (JPCM) -> linux:95 (KEY_KPJPCOMMA) -> qnum:92 */
+  {"KATA", 0x78}, /* xkb:KATA (KATA) -> linux:90 (KEY_KATAKANA) -> qnum:120 */
+  {"KP0", 0x52}, /* xkb:KP0 (KP0) -> linux:82 (KEY_KP0) -> qnum:82 */
+  {"KP1", 0x4f}, /* xkb:KP1 (KP1) -> linux:79 (KEY_KP1) -> qnum:79 */
+  {"KP2", 0x50}, /* xkb:KP2 (KP2) -> linux:80 (KEY_KP2) -> qnum:80 */
+  {"KP3", 0x51}, /* xkb:KP3 (KP3) -> linux:81 (KEY_KP3) -> qnum:81 */
+  {"KP4", 0x4b}, /* xkb:KP4 (KP4) -> linux:75 (KEY_KP4) -> qnum:75 */
+  {"KP5", 0x4c}, /* xkb:KP5 (KP5) -> linux:76 (KEY_KP5) -> qnum:76 */
+  {"KP6", 0x4d}, /* xkb:KP6 (KP6) -> linux:77 (KEY_KP6) -> qnum:77 */
+  {"KP7", 0x47}, /* xkb:KP7 (KP7) -> linux:71 (KEY_KP7) -> qnum:71 */
+  {"KP8", 0x48}, /* xkb:KP8 (KP8) -> linux:72 (KEY_KP8) -> qnum:72 */
+  {"KP9", 0x49}, /* xkb:KP9 (KP9) -> linux:73 (KEY_KP9) -> qnum:73 */
+  {"KPAD", 0x4e}, /* xkb:KPAD (KPAD) -> linux:78 (KEY_KPPLUS) -> qnum:78 */
+  {"KPCO", 0x7e}, /* xkb:KPCO (I129) -> linux:121 (KEY_KPCOMMA) -> qnum:126 */
+  {"KPDC", 0x53}, /* xkb:KPDC (KPDC) -> linux:83 (KEY_KPDOT) -> qnum:83 */
+  {"KPDL", 0x53}, /* xkb:KPDL (KPDC) -> linux:83 (KEY_KPDOT) -> qnum:83 */
+  {"KPDV", 0xb5}, /* xkb:KPDV (KPDV) -> linux:98 (KEY_KPSLASH) -> qnum:181 */
+  {"KPEN", 0x9c}, /* xkb:KPEN (KPEN) -> linux:96 (KEY_KPENTER) -> qnum:156 */
+  {"KPEQ", 0x59}, /* xkb:KPEQ (KPEQ) -> linux:117 (KEY_KPEQUAL) -> qnum:89 */
+  {"KPMU", 0x37}, /* xkb:KPMU (KPMU) -> linux:55 (KEY_KPASTERISK) -> qnum:55 */
+  {"KPSP", 0x5c}, /* xkb:KPSP (JPCM) -> linux:95 (KEY_KPJPCOMMA) -> qnum:92 */
+  {"KPSU", 0x4a}, /* xkb:KPSU (KPSU) -> linux:74 (KEY_KPMINUS) -> qnum:74 */
+  {"LALT", 0x38}, /* xkb:LALT (LALT) -> linux:56 (KEY_LEFTALT) -> qnum:56 */
+  {"LCTL", 0x1d}, /* xkb:LCTL (LCTL) -> linux:29 (KEY_LEFTCTRL) -> qnum:29 */
+  {"LEFT", 0xcb}, /* xkb:LEFT (LEFT) -> linux:105 (KEY_LEFT) -> qnum:203 */
+  {"LFSH", 0x2a}, /* xkb:LFSH (LFSH) -> linux:42 (KEY_LEFTSHIFT) -> qnum:42 */
+  {"LMTA", 0xdb}, /* xkb:LMTA (LWIN) -> linux:125 (KEY_LEFTMETA) -> qnum:219 */
+  {"LNFD", 0x5b}, /* xkb:LNFD (LNFD) -> linux:101 (KEY_LINEFEED) -> qnum:91 */
+  {"LSGT", 0x56}, /* xkb:LSGT (LSGT) -> linux:86 (KEY_102ND) -> qnum:86 */
+  {"LWIN", 0xdb}, /* xkb:LWIN (LWIN) -> linux:125 (KEY_LEFTMETA) -> qnum:219 */
+  {"MENU", 0xdd}, /* xkb:MENU (COMP) -> linux:127 (KEY_COMPOSE) -> qnum:221 */
+  {"MUHE", 0x7b}, /* xkb:MUHE (MUHE) -> linux:94 (KEY_MUHENKAN) -> qnum:123 */
+  {"MUTE", 0xa0}, /* xkb:MUTE (MUTE) -> linux:113 (KEY_MUTE) -> qnum:160 */
+  {"NFER", 0x7b}, /* xkb:NFER (MUHE) -> linux:94 (KEY_MUHENKAN) -> qnum:123 */
+  {"NMLK", 0x45}, /* xkb:NMLK (NMLK) -> linux:69 (KEY_NUMLOCK) -> qnum:69 */
+  {"OPEN", 0x64}, /* xkb:OPEN (OPEN) -> linux:134 (KEY_OPEN) -> qnum:100 */
+  {"PAST", 0x65}, /* xkb:PAST (PAST) -> linux:135 (KEY_PASTE) -> qnum:101 */
+  {"PAUS", 0xc6}, /* xkb:PAUS (PAUS) -> linux:119 (KEY_PAUSE) -> qnum:198 */
+  {"PGDN", 0xd1}, /* xkb:PGDN (PGDN) -> linux:109 (KEY_PAGEDOWN) -> qnum:209 */
+  {"PGUP", 0xc9}, /* xkb:PGUP (PGUP) -> linux:104 (KEY_PAGEUP) -> qnum:201 */
+  {"POWR", 0xde}, /* xkb:POWR (POWR) -> linux:116 (KEY_POWER) -> qnum:222 */
+  {"PROP", 0x86}, /* xkb:PROP (PROP) -> linux:130 (KEY_PROPS) -> qnum:134 */
+  {"PRSC", 0x54}, /* xkb:PRSC (SYRQ) -> linux:99 (KEY_SYSRQ) -> qnum:84 */
+  {"RALT", 0xb8}, /* xkb:RALT (RALT) -> linux:100 (KEY_RIGHTALT) -> qnum:184 */
+  {"RCTL", 0x9d}, /* xkb:RCTL (RCTL) -> linux:97 (KEY_RIGHTCTRL) -> qnum:157 */
+  {"RGHT", 0xcd}, /* xkb:RGHT (RGHT) -> linux:106 (KEY_RIGHT) -> qnum:205 */
+  {"RMTA", 0xdc}, /* xkb:RMTA (RWIN) -> linux:126 (KEY_RIGHTMETA) -> qnum:220 */
+  {"RTRN", 0x1c}, /* xkb:RTRN (RTRN) -> linux:28 (KEY_ENTER) -> qnum:28 */
+  {"RTSH", 0x36}, /* xkb:RTSH (RTSH) -> linux:54 (KEY_RIGHTSHIFT) -> qnum:54 */
+  {"RWIN", 0xdc}, /* xkb:RWIN (RWIN) -> linux:126 (KEY_RIGHTMETA) -> qnum:220 */
+  {"SCLK", 0x46}, /* xkb:SCLK (SCLK) -> linux:70 (KEY_SCROLLLOCK) -> qnum:70 */
+  {"SPCE", 0x39}, /* xkb:SPCE (SPCE) -> linux:57 (KEY_SPACE) -> qnum:57 */
+  {"STOP", 0xe8}, /* xkb:STOP (STOP) -> linux:128 (KEY_STOP) -> qnum:232 */
+  {"SYRQ", 0x54}, /* xkb:SYRQ (SYRQ) -> linux:99 (KEY_SYSRQ) -> qnum:84 */
+  {"TAB", 0xf}, /* xkb:TAB (TAB) -> linux:15 (KEY_TAB) -> qnum:15 */
+  {"TLDE", 0x29}, /* xkb:TLDE (AB00) -> linux:41 (KEY_GRAVE) -> qnum:41 */
+  {"UNDO", 0x87}, /* xkb:UNDO (UNDO) -> linux:131 (KEY_UNDO) -> qnum:135 */
+  {"UP", 0xc8}, /* xkb:UP (UP) -> linux:103 (KEY_UP) -> qnum:200 */
+  {"VOL+", 0xb0}, /* xkb:VOL+ (VOL+) -> linux:115 (KEY_VOLUMEUP) -> qnum:176 */
+  {"VOL-", 0xae}, /* xkb:VOL- (VOL-) -> linux:114 (KEY_VOLUMEDOWN) -> qnum:174 */
+  {"XFER", 0x93}, /* xkb:XFER (I155) -> linux:147 (KEY_XFER) -> qnum:147 */
+};
+const unsigned int code_map_xkb_to_qnum_len = sizeof(code_map_xkb_to_qnum)/sizeof(code_map_xkb_to_qnum[0]);
diff --git a/win/rfb_win32/SDisplay.cxx b/win/rfb_win32/SDisplay.cxx
index ac64e3e..ad55d49 100644
--- a/win/rfb_win32/SDisplay.cxx
+++ b/win/rfb_win32/SDisplay.cxx
@@ -30,6 +30,7 @@
 #include <rfb_win32/SDisplayCoreWMHooks.h>
 #include <rfb/Exception.h>
 #include <rfb/LogWriter.h>
+#include <rfb/ledStates.h>
 
 
 using namespace rdr;
@@ -65,7 +66,7 @@
   : server(0), pb(0), device(0),
     core(0), ptr(0), kbd(0), clipboard(0),
     inputs(0), monitor(0), cleanDesktop(0), cursor(0),
-    statusLocation(0)
+    statusLocation(0), ledState(0)
 {
   updateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
 }
@@ -196,6 +197,10 @@
     cleanDesktop->disableEffects();
   isWallpaperRemoved = removeWallpaper;
   areEffectsDisabled = disableEffects;
+
+  checkLedState();
+  if (server)
+    server->setLEDState(ledState);
 }
 
 void SDisplay::stopCore() {
@@ -275,12 +280,30 @@
   }
 }
 
-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() {
+  unsigned state = 0;
+
+  if (GetKeyState(VK_SCROLL) & 0x0001)
+    state |= ledScrollLock;
+  if (GetKeyState(VK_NUMLOCK) & 0x0001)
+    state |= ledNumLock;
+  if (GetKeyState(VK_CAPITAL) & 0x0001)
+    state |= ledCapsLock;
+
+  if (ledState != state) {
+    ledState = state;
+    return true;
+  }
+
+  return false;
 }
 
 void SDisplay::clientCutText(const char* text, int len) {
@@ -384,6 +407,10 @@
 
       // Flush any changes to the server
       flushChangeTracker();
+
+      // Forward current LED state to the server
+      if (checkLedState())
+        server->setLEDState(ledState);
     }
     return;
   }
diff --git a/win/rfb_win32/SDisplay.h b/win/rfb_win32/SDisplay.h
index b021782..9892ed9 100644
--- a/win/rfb_win32/SDisplay.h
+++ b/win/rfb_win32/SDisplay.h
@@ -66,7 +66,7 @@
       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();
 
@@ -106,6 +106,7 @@
       void restartCore();
       void recreatePixelBuffer(bool force=false);
       bool flushChangeTracker();  // true if flushed, false if empty
+      bool checkLedState();
 
       VNCServer* server;
 
@@ -151,6 +152,8 @@
 
       // -=- Where to write the active/inactive indicator to
       bool* statusLocation;
+
+      unsigned ledState;
     };
 
   }
diff --git a/win/rfb_win32/SInput.cxx b/win/rfb_win32/SInput.cxx
index 0923118..f6075c1 100644
--- a/win/rfb_win32/SInput.cxx
+++ b/win/rfb_win32/SInput.cxx
@@ -134,6 +134,9 @@
 BoolParameter rfb::win32::SKeyboard::deadKeyAware("DeadKeyAware",
   "Whether to assume the viewer has already interpreted dead key sequences "
   "into latin-1 characters", true);
+BoolParameter rfb::win32::SKeyboard::rawKeyboard("RawKeyboard",
+  "Send keyboard events straight through and avoid mapping them to the "
+  "current keyboard layout", false);
 
 // The keysymToAscii table transforms a couple of awkward keysyms into their
 // ASCII equivalents.
@@ -229,6 +232,31 @@
   keybd_event(vkCode, MapVirtualKey(vkCode, 0), flags, 0);
 }
 
+inline void doScanCodeEvent(BYTE scancode, bool down) {
+  INPUT evt;
+
+  evt.type = INPUT_KEYBOARD;
+  evt.ki.wVk = 0;
+  evt.ki.dwFlags = KEYEVENTF_SCANCODE;
+
+  if (!down)
+    evt.ki.dwFlags |= KEYEVENTF_KEYUP;
+
+  if (scancode & 0x80) {
+    evt.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
+    scancode &= ~0x80;
+  }
+
+  evt.ki.wScan = scancode;
+  evt.ki.dwExtraInfo = 0;
+  evt.ki.time = 0;
+  vlog.debug("SendInput ScanCode: 0x%x Flags: 0x%lx %s", scancode,
+             evt.ki.dwFlags, down ? "Down" : "Up");
+
+  if (SendInput(1, &evt, sizeof(evt)) != 1)
+    vlog.error("SendInput %lu", GetLastError());
+}
+
 // KeyStateModifier is a class which helps simplify generating a "fake" press
 // or release of shift, ctrl, alt, etc.  An instance of the class is created
 // for every key which may need to be pressed or released.  Then either press()
@@ -321,8 +349,15 @@
 }
 
 
-void win32::SKeyboard::keyEvent(rdr::U32 keysym, bool down)
+void win32::SKeyboard::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down)
 {
+  // If scan code is available use that directly as windows uses
+  // compatible scancodes
+  if (keycode && rawKeyboard) {
+    doScanCodeEvent(keycode, down);
+    return;
+  }
+
   for (unsigned int i = 0; i < sizeof(keysymToAscii) / sizeof(keysymToAscii_t); i++) {
     if (keysymToAscii[i].keysym == keysym) {
       keysym = keysymToAscii[i].ascii;
diff --git a/win/rfb_win32/SInput.h b/win/rfb_win32/SInput.h
index 2a0b3e6..c927c65 100644
--- a/win/rfb_win32/SInput.h
+++ b/win/rfb_win32/SInput.h
@@ -53,8 +53,9 @@
     class SKeyboard {
     public:
       SKeyboard();
-      void keyEvent(rdr::U32 key, bool down);
+      void keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down);
       static BoolParameter deadKeyAware;
+      static BoolParameter rawKeyboard;
     private:
       std::map<rdr::U32,rdr::U8> vkMap;
       std::map<rdr::U32,bool> extendedMap;
diff --git a/win/vncconfig/Inputs.h b/win/vncconfig/Inputs.h
index 2ad3b42..f290831 100644
--- a/win/vncconfig/Inputs.h
+++ b/win/vncconfig/Inputs.h
@@ -37,6 +37,7 @@
           regKey(rk), enableAffectSSaver(true) {}
       void initDialog() {
         setItemChecked(IDC_ACCEPT_KEYS, rfb::Server::acceptKeyEvents);
+        setItemChecked(IDC_RAW_KEYBOARD, SKeyboard::rawKeyboard);
         setItemChecked(IDC_ACCEPT_PTR, rfb::Server::acceptPointerEvents);
         setItemChecked(IDC_ACCEPT_CUTTEXT, rfb::Server::acceptCutText);
         setItemChecked(IDC_SEND_CUTTEXT, rfb::Server::sendCutText);
@@ -52,6 +53,7 @@
         BOOL inputResetsBlocked;
         SystemParametersInfo(SPI_GETBLOCKSENDINPUTRESETS, 0, &inputResetsBlocked, 0);
         setChanged((rfb::Server::acceptKeyEvents != isItemChecked(IDC_ACCEPT_KEYS)) ||
+          (SKeyboard::rawKeyboard != isItemChecked(IDC_RAW_KEYBOARD)) ||
           (rfb::Server::acceptPointerEvents != isItemChecked(IDC_ACCEPT_PTR)) ||
           (rfb::Server::acceptCutText != isItemChecked(IDC_ACCEPT_CUTTEXT)) ||
           (rfb::Server::sendCutText != isItemChecked(IDC_SEND_CUTTEXT)) ||
@@ -61,6 +63,7 @@
       }
       bool onOk() {
         regKey.setBool(_T("AcceptKeyEvents"), isItemChecked(IDC_ACCEPT_KEYS));
+        regKey.setBool(_T("RawKeyboard"), isItemChecked(IDC_RAW_KEYBOARD));
         regKey.setBool(_T("AcceptPointerEvents"), isItemChecked(IDC_ACCEPT_PTR));
         regKey.setBool(_T("AcceptCutText"), isItemChecked(IDC_ACCEPT_CUTTEXT));
         regKey.setBool(_T("SendCutText"), isItemChecked(IDC_SEND_CUTTEXT));
diff --git a/win/vncconfig/resource.h b/win/vncconfig/resource.h
index 243f824..16bbc15 100644
--- a/win/vncconfig/resource.h
+++ b/win/vncconfig/resource.h
@@ -77,6 +77,7 @@
 #define IDC_RFB_ENABLE                  1082
 #define IDC_LOAD_CERT                   1087
 #define IDC_LOAD_CERTKEY                1088
+#define IDC_RAW_KEYBOARD                1089
 #define ID_OPTIONS                      40001
 #define ID_CLOSE                        40002
 #define ID_ABOUT                        40003
diff --git a/win/vncconfig/vncconfig.rc b/win/vncconfig/vncconfig.rc
index 4da1a3b..f23bbdb 100644
--- a/win/vncconfig/vncconfig.rc
+++ b/win/vncconfig/vncconfig.rc
@@ -206,17 +206,19 @@
                     "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,10,172,15
     CONTROL         "Accept keyboard events from clients",IDC_ACCEPT_KEYS,
                     "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,25,172,15
+    CONTROL         "Send raw keyboard events to applications",IDC_RAW_KEYBOARD,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,40,172,15
     CONTROL         "Accept clipboard updates from clients",
                     IDC_ACCEPT_CUTTEXT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,
-                    7,40,172,15
+                    7,55,172,15
     CONTROL         "Send clipboard updates to clients",IDC_SEND_CUTTEXT,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,55,172,15
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,70,172,15
     CONTROL         "Allow input events to affect the screen-saver",
                     IDC_AFFECT_SCREENSAVER,"Button",BS_AUTOCHECKBOX | 
-                    WS_TABSTOP,7,70,172,15
+                    WS_TABSTOP,7,85,172,15
     CONTROL         "Disable local inputs while server is in use",
                     IDC_DISABLE_LOCAL_INPUTS,"Button",BS_AUTOCHECKBOX | 
-                    WS_TABSTOP,7,95,172,15
+                    WS_TABSTOP,7,110,172,15
 END
 
 IDD_ABOUT DIALOG DISCARDABLE  0, 0, 300, 92