Send lock LED state from server to client
diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index de41e8f..232776f 100644
--- a/common/rfb/VNCSConnectionST.cxx
+++ b/common/rfb/VNCSConnectionST.cxx
@@ -313,6 +313,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;
@@ -418,6 +428,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());
@@ -570,61 +581,66 @@
     vlog.debug("Ignoring lock key (e.g. caps lock)");
     return;
   }
-  // Always ignore ScrollLock though as we don't have a heuristic
-  // for that
-  if (key == XK_Scroll_Lock) {
-    vlog.debug("Ignoring lock key (e.g. caps lock)");
-    return;
-  }
 
-  if (down && (server->ledState != ledUnknown)) {
-    // CapsLock synchronisation heuristic
-    // (this assumes standard interaction between CapsLock the Shift
-    // keys and normal characters)
-    if (((key >= XK_A) && (key <= XK_Z)) ||
-        ((key >= XK_a) && (key <= XK_z))) {
-      bool uppercase, shift, lock;
-
-      uppercase = (key >= XK_A) && (key <= XK_Z);
-      shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() ||
-              pressedKeys.find(XK_Shift_R) != pressedKeys.end();
-      lock = server->ledState & ledCapsLock;
-
-      if (lock == (uppercase == shift)) {
-        vlog.debug("Inserting fake CapsLock to get in sync with client");
-        server->desktop->keyEvent(XK_Caps_Lock, true);
-        server->desktop->keyEvent(XK_Caps_Lock, false);
-      }
+  // Lock key heuristics
+  // (only for clients that do not support the LED state extension)
+  if (!cp.supportsLEDState) {
+    // Always ignore ScrollLock as we don't have a heuristic
+    // for that
+    if (key == XK_Scroll_Lock) {
+      vlog.debug("Ignoring lock key (e.g. caps lock)");
+      return;
     }
 
-    // NumLock synchronisation heuristic
-    // (this is more cautious because of the differences between Unix,
-    // Windows and macOS)
-    if (((key >= XK_KP_Home) && (key <= XK_KP_Delete)) ||
-        ((key >= XK_KP_0) && (key <= XK_KP_9)) ||
-        (key == XK_KP_Separator) || (key == XK_KP_Decimal)) {
-      bool number, shift, lock;
+    if (down && (server->ledState != ledUnknown)) {
+      // CapsLock synchronisation heuristic
+      // (this assumes standard interaction between CapsLock the Shift
+      // keys and normal characters)
+      if (((key >= XK_A) && (key <= XK_Z)) ||
+          ((key >= XK_a) && (key <= XK_z))) {
+        bool uppercase, shift, lock;
 
-      number = ((key >= XK_KP_0) && (key <= XK_KP_9)) ||
-                (key == XK_KP_Separator) || (key == XK_KP_Decimal);
-      shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() ||
-              pressedKeys.find(XK_Shift_R) != pressedKeys.end();
-      lock = server->ledState & ledNumLock;
+        uppercase = (key >= XK_A) && (key <= XK_Z);
+        shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() ||
+                pressedKeys.find(XK_Shift_R) != pressedKeys.end();
+        lock = server->ledState & ledCapsLock;
 
-      if (shift) {
-        // We don't know the appropriate NumLock state for when Shift
-        // is pressed as it could be one of:
-        //
-        // a) A Unix client where Shift negates NumLock
-        //
-        // b) A Windows client where Shift only cancels NumLock
-        //
-        // c) A macOS client where Shift doesn't have any effect
-        //
-      } else if (lock == (number == shift)) {
-        vlog.debug("Inserting fake NumLock to get in sync with client");
-        server->desktop->keyEvent(XK_Num_Lock, true);
-        server->desktop->keyEvent(XK_Num_Lock, false);
+        if (lock == (uppercase == shift)) {
+          vlog.debug("Inserting fake CapsLock to get in sync with client");
+          server->desktop->keyEvent(XK_Caps_Lock, true);
+          server->desktop->keyEvent(XK_Caps_Lock, false);
+        }
+      }
+
+      // NumLock synchronisation heuristic
+      // (this is more cautious because of the differences between Unix,
+      // Windows and macOS)
+      if (((key >= XK_KP_Home) && (key <= XK_KP_Delete)) ||
+          ((key >= XK_KP_0) && (key <= XK_KP_9)) ||
+          (key == XK_KP_Separator) || (key == XK_KP_Decimal)) {
+        bool number, shift, lock;
+
+        number = ((key >= XK_KP_0) && (key <= XK_KP_9)) ||
+                  (key == XK_KP_Separator) || (key == XK_KP_Decimal);
+        shift = pressedKeys.find(XK_Shift_L) != pressedKeys.end() ||
+                pressedKeys.find(XK_Shift_R) != pressedKeys.end();
+        lock = server->ledState & ledNumLock;
+
+        if (shift) {
+          // We don't know the appropriate NumLock state for when Shift
+          // is pressed as it could be one of:
+          //
+          // a) A Unix client where Shift negates NumLock
+          //
+          // b) A Windows client where Shift only cancels NumLock
+          //
+          // c) A macOS client where Shift doesn't have any effect
+          //
+        } else if (lock == (number == shift)) {
+          vlog.debug("Inserting fake NumLock to get in sync with client");
+          server->desktop->keyEvent(XK_Num_Lock, true);
+          server->desktop->keyEvent(XK_Num_Lock, false);
+        }
       }
     }
   }
@@ -818,6 +834,11 @@
   writer()->writeEndOfContinuousUpdates();
 }
 
+void VNCSConnectionST::supportsLEDState()
+{
+  writer()->writeLEDState();
+}
+
 
 bool VNCSConnectionST::handleTimeout(Timer* t)
 {
@@ -1230,6 +1251,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;