Commit 738ebcf8 for guacamole.apache.org
commit 738ebcf8a84e2d88bc874d38690ffeb7bbb5b688
Author: Michael Jumper <mjumper@apache.org>
Date: Mon Aug 3 01:48:47 2026 -0700
GUACAMOLE-2313: Synchronize VNC lock key state when supported.
diff --git a/src/protocols/vnc/input.c b/src/protocols/vnc/input.c
index f22d0e53..37d42bf9 100644
--- a/src/protocols/vnc/input.c
+++ b/src/protocols/vnc/input.c
@@ -18,6 +18,7 @@
*/
#include "display.h"
+#include "input.h"
#include "vnc.h"
#include <guacamole/display.h>
@@ -74,6 +75,47 @@ static int guac_vnc_translate_keysym(int keysym) {
}
+/**
+ * Toggles the lock controlled by the given lock keysym within the VNC session
+ * by pressing and releasing that key. The key will be pressed/released only if
+ * the given lock flag is set within the reported lock key state.
+ *
+ * @param rfb_client
+ * The VNC client to send key events through.
+ *
+ * @param led_state
+ * The lock key state reported by the VNC server, as a bitmask of
+ * rfbKeyboardMask* flags.
+ *
+ * @param led_mask
+ * The single rfbKeyboardMask* flag to test.
+ *
+ * @param keysym
+ * The keysym of the lock key that toggles the tested lock.
+ */
+static void guac_vnc_release_lock(rfbClient* rfb_client, int led_state,
+ int led_mask, int keysym) {
+ if (led_state & led_mask) {
+ SendKeyEvent(rfb_client, keysym, TRUE);
+ SendKeyEvent(rfb_client, keysym, FALSE);
+ }
+}
+
+void guac_vnc_keyboard_led_state(rfbClient* rfb_client, int state, int pad) {
+
+ guac_client* client = rfbClientGetClientData(rfb_client, GUAC_VNC_CLIENT_KEY);
+ guac_vnc_client* vnc_client = (guac_vnc_client*) client->data;
+
+ /* Clear lock states once we know which locks need to be cleared */
+ if (!vnc_client->lock_state_synced) {
+ guac_vnc_release_lock(rfb_client, state, rfbKeyboardMaskCapsLock, 0xFFE5 /* Caps_Lock */);
+ guac_vnc_release_lock(rfb_client, state, rfbKeyboardMaskNumLock, 0xFF7F /* Num_Lock */);
+ guac_vnc_release_lock(rfb_client, state, rfbKeyboardMaskScrollLock, 0xFF14 /* Scroll_Lock */);
+ vnc_client->lock_state_synced = 1;
+ }
+
+}
+
int guac_vnc_user_key_handler(guac_user* user, int keysym, int pressed) {
guac_vnc_client* vnc_client = (guac_vnc_client*) user->client->data;
@@ -85,9 +127,22 @@ int guac_vnc_user_key_handler(guac_user* user, int keysym, int pressed) {
keysym, pressed);
/* Send VNC event only if finished connecting */
- if (rfb_client != NULL)
+ if (rfb_client != NULL) {
+
+ /* Ensure the lock key state of the VNC session matches the
+ * all-released state assumed by connecting clients before any key
+ * events are forwarded */
+ if (!vnc_client->lock_state_synced) {
+ vnc_client->lock_state_synced = 1;
+ guac_client_log(user->client, GUAC_LOG_WARNING, "VNC server did "
+ "not report which lock keys are active. Client-side use of "
+ "Caps Lock, etc. may not match server-side lock state.");
+ }
+
SendKeyEvent(rfb_client, guac_vnc_translate_keysym(keysym), pressed);
+ }
+
return 0;
}
diff --git a/src/protocols/vnc/input.h b/src/protocols/vnc/input.h
index cce07168..63f81bc6 100644
--- a/src/protocols/vnc/input.h
+++ b/src/protocols/vnc/input.h
@@ -21,6 +21,7 @@
#define GUAC_VNC_INPUT_H
#include <guacamole/user.h>
+#include <rfb/rfbclient.h>
/**
* Handler for Guacamole user mouse events.
@@ -32,6 +33,22 @@ guac_user_mouse_handler guac_vnc_user_mouse_handler;
*/
guac_user_key_handler guac_vnc_user_key_handler;
+/**
+ * Handler for lock key state (KeyboardLedState) updates received from the VNC
+ * server. Where supported, this is used to help ensure the client can assume all
+ * locks are inactive at connection start.
+ *
+ * @param client
+ * The VNC client associated with the update.
+ *
+ * @param state
+ * The reported lock key state, as a bitmask of rfbKeyboardMask flags.
+ *
+ * @param pad
+ * Unused (reserved by libvncclient for future use).
+ */
+void guac_vnc_keyboard_led_state(rfbClient* client, int state, int pad);
+
/**
* Handler for Guacamole user resize events.
*/
diff --git a/src/protocols/vnc/vnc.c b/src/protocols/vnc/vnc.c
index 26af94c6..47102c44 100644
--- a/src/protocols/vnc/vnc.c
+++ b/src/protocols/vnc/vnc.c
@@ -23,6 +23,7 @@
#include "common/clipboard.h"
#include "cursor.h"
#include "display.h"
+#include "input.h"
#include "log.h"
#include "settings.h"
#include "vnc.h"
@@ -242,6 +243,9 @@ rfbClient* guac_vnc_get_client(guac_client* client) {
vnc_client->rfb_GotCopyRect = rfb_client->GotCopyRect;
rfb_client->GotCopyRect = guac_vnc_copyrect;
+ /* Lock key state (KeyboardLedState) update handler */
+ rfb_client->HandleKeyboardLedState = guac_vnc_keyboard_led_state;
+
#ifdef ENABLE_VNC_TLS_LOCKING
/* TLS Locking and Unlocking */
rfb_client->LockWriteToTLS = guac_vnc_lock_write_to_tls;
diff --git a/src/protocols/vnc/vnc.h b/src/protocols/vnc/vnc.h
index 3a348ecc..fb0ec6cc 100644
--- a/src/protocols/vnc/vnc.h
+++ b/src/protocols/vnc/vnc.h
@@ -100,6 +100,13 @@ typedef struct guac_vnc_client {
*/
int finished_frame_logged;
+ /**
+ * Whether the initial lock key state of the VNC session has been received
+ * and synchronized with the all-released state that connecting Guacamole
+ * clients assume.
+ */
+ int lock_state_synced;
+
/**
* Client settings, parsed from args.
*/