diff --git a/src/protocols/rdp/keyboard.c b/src/protocols/rdp/keyboard.c index d7d2d518..d7e9a37c 100644 --- a/src/protocols/rdp/keyboard.c +++ b/src/protocols/rdp/keyboard.c @@ -31,6 +31,46 @@ #include #include +/** + * Translates the given keysym into the corresponding lock flag, as would be + * required by the RDP synchronize event. If the given keysym does not + * represent a lock key, zero is returned. + * + * @param keysym + * The keysym to translate into a RDP lock flag. + * + * @return + * The RDP lock flag which corresponds to the given keysym, or zero if the + * given keysym does not represent a lock key. + */ +static int guac_rdp_keyboard_lock_flag(int keysym) { + + /* Translate keysym into corresponding lock flag */ + switch (keysym) { + + /* Scroll lock */ + case 0xFF14: + return KBD_SYNC_SCROLL_LOCK; + + /* Kana lock */ + case 0xFF2D: + return KBD_SYNC_KANA_LOCK; + + /* Num lock */ + case 0xFF7F: + return KBD_SYNC_NUM_LOCK; + + /* Caps lock */ + case 0xFFE5: + return KBD_SYNC_CAPS_LOCK; + + } + + /* Not a lock key */ + return 0; + +} + /** * Immediately sends an RDP key event having the given scancode and flags. * @@ -113,6 +153,39 @@ static void guac_rdp_send_unicode_event(guac_rdp_client* rdp_client, } +/** + * Immediately sends an RDP synchonize event having the given flags. An RDP + * synchronize event sets the state of remote lock keys absolutely, where a + * lock key will be active only if its corresponding flag is set in the event. + * + * @param rdp_client + * The RDP client instance associated with the RDP session along which the + * synchronize event should be sent. + * + * @param flags + * Bitwise OR of the flags representing the lock keys which should be set, + * if any, as dictated by the RDP protocol. If no flags are set, then no + * lock keys will be active. + */ +static void guac_rdp_send_synchronize_event(guac_rdp_client* rdp_client, + int flags) { + + pthread_mutex_lock(&(rdp_client->rdp_lock)); + + /* Skip if not yet connected */ + freerdp* rdp_inst = rdp_client->rdp_inst; + if (rdp_inst == NULL) { + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + return; + } + + /* Synchronize lock key states */ + rdp_inst->input->SynchronizeEvent(rdp_inst->input, flags); + + pthread_mutex_unlock(&(rdp_client->rdp_lock)); + +} + /** * Loads all keysym/scancode mappings declared within the given keymap and its * parent keymap, if any. These mappings are stored within the given @@ -187,6 +260,11 @@ int guac_rdp_keyboard_send_event(guac_rdp_keyboard* keyboard, /* If defined, send event */ if (keysym_desc->scancode != 0) { + /* Update remote lock state as necessary */ + guac_rdp_keyboard_update_locks(keyboard, + keysym_desc->set_locks, + keysym_desc->clear_locks); + /* If defined, send any prerequesite keys that must be set */ if (keysym_desc->set_keysyms != NULL) guac_rdp_keyboard_send_events(keyboard, @@ -270,9 +348,42 @@ void guac_rdp_keyboard_send_events(guac_rdp_keyboard* keyboard, } +void guac_rdp_keyboard_update_locks(guac_rdp_keyboard* keyboard, + int set_flags, int clear_flags) { + + guac_client* client = keyboard->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* Calculate updated lock flags */ + int lock_flags = (keyboard->lock_flags | set_flags) & ~clear_flags; + + /* Synchronize remote side only if lock flags have changed */ + if (lock_flags != keyboard->lock_flags) { + guac_rdp_send_synchronize_event(rdp_client, lock_flags); + keyboard->lock_flags = lock_flags; + } + +} + int guac_rdp_keyboard_update_keysym(guac_rdp_keyboard* keyboard, int keysym, int pressed) { + /* Synchronize lock keys states, if this has not yet been done */ + if (!keyboard->synchronized) { + + guac_client* client = keyboard->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + /* Synchronize remote lock key states with local state */ + guac_rdp_send_synchronize_event(rdp_client, keyboard->lock_flags); + keyboard->synchronized = 1; + + } + + /* Toggle lock flag, if any */ + if (pressed) + keyboard->lock_flags ^= guac_rdp_keyboard_lock_flag(keysym); + /* Update keysym state */ if (GUAC_RDP_KEYSYM_STORABLE(keysym)) GUAC_RDP_KEYSYM_LOOKUP(keyboard->keysym_state, keysym) = pressed; diff --git a/src/protocols/rdp/keyboard.h b/src/protocols/rdp/keyboard.h index aebc58e7..8282c18c 100644 --- a/src/protocols/rdp/keyboard.h +++ b/src/protocols/rdp/keyboard.h @@ -35,6 +35,19 @@ typedef struct guac_rdp_keyboard { */ guac_client* client; + /** + * The local state of all known lock keys, as a bitwise OR of all RDP lock + * key flags. Legal flags are KBD_SYNC_SCROLL_LOCK, KBD_SYNC_NUM_LOCK, + * KBD_SYNC_CAPS_LOCK, and KBD_SYNC_KANA_LOCK. + */ + int lock_flags; + + /** + * Whether the states of remote lock keys (Caps lock, Num lock, etc.) have + * been synchronized with local lock key states. + */ + int synchronized; + /** * The keymap to use when translating keysyms into scancodes or sequences * of scancodes for RDP. @@ -127,6 +140,26 @@ int guac_rdp_keyboard_send_event(guac_rdp_keyboard* keyboard, void guac_rdp_keyboard_send_events(guac_rdp_keyboard* keyboard, const int* keysym_string, int from, int to); +/** + * Updates the local state of the lock keys (such as Caps lock or Num lock), + * synchronizing the remote state of those keys if it is expected to differ. + * + * @param keyboard + * The guac_rdp_keyboard associated with the current RDP session. + * + * @param set_flags + * The lock key flags which should be set. Legal flags are + * KBD_SYNC_SCROLL_LOCK, KBD_SYNC_NUM_LOCK, KBD_SYNC_CAPS_LOCK, and + * KBD_SYNC_KANA_LOCK. + * + * @param clear_flags + * The lock key flags which should be cleared. Legal flags are + * KBD_SYNC_SCROLL_LOCK, KBD_SYNC_NUM_LOCK, KBD_SYNC_CAPS_LOCK, and + * KBD_SYNC_KANA_LOCK. + */ +void guac_rdp_keyboard_update_locks(guac_rdp_keyboard* keyboard, + int set_flags, int clear_flags); + /** * Updates the local state of the given keysym, sending the key events required * to replicate that state remotely (on the RDP server). The key events sent diff --git a/src/protocols/rdp/keymaps/base.keymap b/src/protocols/rdp/keymaps/base.keymap index b5e5963a..1aaaf001 100644 --- a/src/protocols/rdp/keymaps/base.keymap +++ b/src/protocols/rdp/keymaps/base.keymap @@ -44,16 +44,16 @@ map 0x46 ~ 0xff14 # Scroll_Lock map +ext 0x3A ~ 0xffe5 # Caps_Lock # Keypad numerals -map 0x52 ~ 0xffb0 # KP_0 -map 0x4F ~ 0xffb1 # KP_1 -map 0x50 ~ 0xffb2 # KP_2 -map 0x51 ~ 0xffb3 # KP_3 -map 0x4B ~ 0xffb4 # KP_4 -map 0x4C ~ 0xffb5 # KP_5 -map 0x4D ~ 0xffb6 # KP_6 -map 0x47 ~ 0xffb7 # KP_7 -map 0x48 ~ 0xffb8 # KP_8 -map 0x49 ~ 0xffb9 # KP_9 +map -shift +num 0x52 ~ 0xffb0 # KP_0 +map -shift +num 0x4F ~ 0xffb1 # KP_1 +map -shift +num 0x50 ~ 0xffb2 # KP_2 +map -shift +num 0x51 ~ 0xffb3 # KP_3 +map -shift +num 0x4B ~ 0xffb4 # KP_4 +map -shift +num 0x4C ~ 0xffb5 # KP_5 +map -shift +num 0x4D ~ 0xffb6 # KP_6 +map -shift +num 0x47 ~ 0xffb7 # KP_7 +map -shift +num 0x48 ~ 0xffb8 # KP_8 +map -shift +num 0x49 ~ 0xffb9 # KP_9 # Keypad operators map 0x37 ~ 0xffaa # KP_multiply diff --git a/src/protocols/rdp/keymaps/generate.pl b/src/protocols/rdp/keymaps/generate.pl index 255e7b10..d3764d6e 100755 --- a/src/protocols/rdp/keymaps/generate.pl +++ b/src/protocols/rdp/keymaps/generate.pl @@ -97,9 +97,11 @@ for my $filename (@ARGV) { my $ext_flags = 0; my $set_shift = 0; my $set_altgr = 0; + my $set_num = 0; my $clear_shift = 0; my $clear_altgr = 0; + my $clear_num = 0; # Parse ranges and options foreach $_ (split(/\s+/, $range)) { @@ -108,6 +110,7 @@ for my $filename (@ARGV) { if ((my $opt) = m/^\+([a-z]+)$/) { if ($opt eq "shift") { $set_shift = 1; } elsif ($opt eq "altgr") { $set_altgr = 1; } + elsif ($opt eq "num") { $set_num = 1; } elsif ($opt eq "ext") { $ext_flags = 1; } else { die "$filename: $.: ERROR: " @@ -119,6 +122,7 @@ for my $filename (@ARGV) { elsif ((my $opt) = m/^-([a-z]+)$/) { if ($opt eq "shift") { $clear_shift = 1; } elsif ($opt eq "altgr") { $clear_altgr = 1; } + elsif ($opt eq "num") { $clear_num = 1; } else { die "$filename: $.: ERROR: " . "Invalid clear option\n"; @@ -198,6 +202,16 @@ for my $filename (@ARGV) { $content .= ", .clear_keysyms = GUAC_KEYSYMS_ALL_SHIFT_ALTGR"; } + # Set locks + if ($set_num) { + $content .= ", .set_locks = KBD_SYNC_NUM_LOCK"; + } + + # Clear locks + if ($clear_num) { + $content .= ", .clear_locks = KBD_SYNC_NUM_LOCK"; + } + # Flags if ($ext_flags) { $content .= ", .flags = KBD_FLAGS_EXTENDED"; diff --git a/src/protocols/rdp/rdp_keymap.h b/src/protocols/rdp/rdp_keymap.h index 01e11efe..b299bdc1 100644 --- a/src/protocols/rdp/rdp_keymap.h +++ b/src/protocols/rdp/rdp_keymap.h @@ -62,6 +62,22 @@ typedef struct guac_rdp_keysym_desc { */ const int* clear_keysyms; + /** + * Bitwise OR of the flags of all lock keys (ie: Caps lock, Num lock, etc.) + * which must be active for this keysym to be properly typed. Legal flags + * are KBD_SYNC_SCROLL_LOCK, KBD_SYNC_NUM_LOCK, KBD_SYNC_CAPS_LOCK, and + * KBD_SYNC_KANA_LOCK. + */ + int set_locks; + + /** + * Bitwise OR of the flags of all lock keys (ie: Caps lock, Num lock, etc.) + * which must be inactive for this keysym to be properly typed. Legal flags + * are KBD_SYNC_SCROLL_LOCK, KBD_SYNC_NUM_LOCK, KBD_SYNC_CAPS_LOCK, and + * KBD_SYNC_KANA_LOCK. + */ + int clear_locks; + } guac_rdp_keysym_desc; /**