keyboard.c (25226B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "decompose.h" 21#include "keyboard.h" 22#include "keymap.h" 23#include "rdp.h" 24 25#include <freerdp/freerdp.h> 26#include <freerdp/input.h> 27#include <guacamole/client.h> 28#include <guacamole/mem.h> 29 30#include <stdlib.h> 31 32/** 33 * Translates the given keysym into the corresponding lock flag, as would be 34 * required by the RDP synchronize event. If the given keysym does not 35 * represent a lock key, zero is returned. 36 * 37 * @param keysym 38 * The keysym to translate into a RDP lock flag. 39 * 40 * @return 41 * The RDP lock flag which corresponds to the given keysym, or zero if the 42 * given keysym does not represent a lock key. 43 */ 44static int guac_rdp_keyboard_lock_flag(int keysym) { 45 46 /* Translate keysym into corresponding lock flag */ 47 switch (keysym) { 48 49 /* Scroll lock */ 50 case GUAC_RDP_KEYSYM_SCROLL_LOCK: 51 return KBD_SYNC_SCROLL_LOCK; 52 53 /* Kana lock */ 54 case GUAC_RDP_KEYSYM_KANA_LOCK: 55 return KBD_SYNC_KANA_LOCK; 56 57 /* Num lock */ 58 case GUAC_RDP_KEYSYM_NUM_LOCK: 59 return KBD_SYNC_NUM_LOCK; 60 61 /* Caps lock */ 62 case GUAC_RDP_KEYSYM_CAPS_LOCK: 63 return KBD_SYNC_CAPS_LOCK; 64 65 } 66 67 /* Not a lock key */ 68 return 0; 69 70} 71 72/** 73 * Immediately sends an RDP key event having the given scancode and flags. 74 * 75 * @param rdp_client 76 * The RDP client instance associated with the RDP session along which the 77 * key event should be sent. 78 * 79 * @param scancode 80 * The scancode of the key to press or release via the RDP key event. 81 * 82 * @param flags 83 * Any RDP-specific flags required for the provided scancode to have the 84 * intended meaning, such as KBD_FLAGS_EXTENDED. The possible flags and 85 * their meanings are dictated by RDP. KBD_FLAGS_DOWN and KBD_FLAGS_UP 86 * need not be specified here - they will automatically be added depending 87 * on the value specified for the pressed parameter. 88 * 89 * @param pressed 90 * Non-zero if the key is being pressed, zero if the key is being released. 91 */ 92static void guac_rdp_send_key_event(guac_rdp_client* rdp_client, 93 int scancode, int flags, int pressed) { 94 95 /* Determine proper event flag for pressed state */ 96 int pressed_flags; 97 if (pressed) 98 pressed_flags = KBD_FLAGS_DOWN; 99 else 100 pressed_flags = KBD_FLAGS_RELEASE; 101 102 /* Skip if not yet connected */ 103 freerdp* rdp_inst = rdp_client->rdp_inst; 104 if (rdp_inst == NULL) 105 return; 106 107 /* Send actual key */ 108 pthread_mutex_lock(&(rdp_client->message_lock)); 109 rdp_inst->input->KeyboardEvent(rdp_inst->input, flags | pressed_flags, scancode); 110 pthread_mutex_unlock(&(rdp_client->message_lock)); 111 112} 113 114/** 115 * Immediately sends an RDP Unicode event having the given Unicode codepoint. 116 * Unlike key events, RDP Unicode events do have not a pressed or released 117 * state. They represent strictly the input of a single character, and are 118 * technically independent of the keyboard. 119 * 120 * @param rdp_client 121 * The RDP client instance associated with the RDP session along which the 122 * Unicode event should be sent. 123 * 124 * @param codepoint 125 * The Unicode codepoint of the character being input via the Unicode 126 * event. 127 */ 128static void guac_rdp_send_unicode_event(guac_rdp_client* rdp_client, 129 int codepoint) { 130 131 /* Skip if not yet connected */ 132 freerdp* rdp_inst = rdp_client->rdp_inst; 133 if (rdp_inst == NULL) 134 return; 135 136 /* Send Unicode event */ 137 pthread_mutex_lock(&(rdp_client->message_lock)); 138 rdp_inst->input->UnicodeKeyboardEvent(rdp_inst->input, 0, codepoint); 139 pthread_mutex_unlock(&(rdp_client->message_lock)); 140 141} 142 143/** 144 * Immediately sends an RDP synchronize event having the given flags. An RDP 145 * synchronize event sets the state of remote lock keys absolutely, where a 146 * lock key will be active only if its corresponding flag is set in the event. 147 * 148 * @param rdp_client 149 * The RDP client instance associated with the RDP session along which the 150 * synchronize event should be sent. 151 * 152 * @param flags 153 * Bitwise OR of the flags representing the lock keys which should be set, 154 * if any, as dictated by the RDP protocol. If no flags are set, then no 155 * lock keys will be active. 156 */ 157static void guac_rdp_send_synchronize_event(guac_rdp_client* rdp_client, 158 UINT32 flags) { 159 160 /* Skip if not yet connected */ 161 freerdp* rdp_inst = rdp_client->rdp_inst; 162 if (rdp_inst == NULL) 163 return; 164 165 /* Synchronize lock key states */ 166 pthread_mutex_lock(&(rdp_client->message_lock)); 167 rdp_inst->input->SynchronizeEvent(rdp_inst->input, flags); 168 pthread_mutex_unlock(&(rdp_client->message_lock)); 169 170} 171 172/** 173 * Given a keyboard instance and X11 keysym, returns a pointer to the 174 * keys_by_keysym entry that represents the key having that keysym within the 175 * keyboard, regardless of whether the key is currently defined. If no such key 176 * can exist (the keysym cannot be mapped or is out of range), NULL is 177 * returned. 178 * 179 * @param keyboard 180 * The guac_rdp_keyboard associated with the current RDP session. 181 * 182 * @param keysym 183 * The keysym of the key to lookup within the given keyboard. 184 * 185 * @return 186 * A pointer to the keys_by_keysym entry which represents or can represent 187 * the key having the given keysym, or NULL if no such keysym can be 188 * defined within a guac_rdp_keyboard structure. 189 */ 190static guac_rdp_key** guac_rdp_keyboard_map_key(guac_rdp_keyboard* keyboard, 191 int keysym) { 192 193 int index; 194 195 /* Map keysyms between 0x0000 and 0xFFFF directly */ 196 if (keysym >= 0x0000 && keysym <= 0xFFFF) 197 index = keysym; 198 199 /* Map all Unicode keysyms from U+0000 to U+FFFF */ 200 else if (keysym >= 0x1000000 && keysym <= 0x100FFFF) 201 index = 0x10000 + (keysym & 0xFFFF); 202 203 /* All other keysyms are unmapped */ 204 else 205 return NULL; 206 207 /* Corresponding key mapping (defined or not) has been located */ 208 return &(keyboard->keys_by_keysym[index]); 209 210} 211 212/** 213 * Returns the number of bits that are set within the given integer (the number 214 * of 1s in the binary expansion of the given integer). 215 * 216 * @param value 217 * The integer to read. 218 * 219 * @return 220 * The number of bits that are set within the given integer. 221 */ 222static int guac_rdp_count_bits(unsigned int value) { 223 224 int bits = 0; 225 226 while (value) { 227 bits += value & 1; 228 value >>= 1; 229 } 230 231 return bits; 232 233} 234 235/** 236 * Returns an estimated cost for sending the necessary RDP events to type the 237 * key described by the given guac_rdp_keysym_desc, given the current lock and 238 * modifier state of the keyboard. A higher cost value indicates that a greater 239 * number of events are expected to be required. 240 * 241 * Lower-cost approaches should be preferred when multiple alternatives exist 242 * for typing a particular key, as the lower cost implies fewer additional key 243 * events required to produce the expected behavior. For example, if Caps Lock 244 * is enabled, typing an uppercase "A" by pressing the "A" key has a lower cost 245 * than disabling Caps Lock and pressing Shift+A. 246 * 247 * @param keyboard 248 * The guac_rdp_keyboard associated with the current RDP session. 249 * 250 * @param def 251 * The guac_rdp_keysym_desc that describes the key being pressed, as well 252 * as any requirements that must be satisfied for the key to be interpreted 253 * as expected. 254 * 255 * @return 256 * An arbitrary integer value which indicates the overall estimated 257 * complexity of typing the given key. 258 */ 259static int guac_rdp_keyboard_get_cost(guac_rdp_keyboard* keyboard, 260 const guac_rdp_keysym_desc* def) { 261 262 unsigned int modifier_flags = guac_rdp_keyboard_get_modifier_flags(keyboard); 263 264 /* Each change to any key requires one event, by definition */ 265 int cost = 1; 266 267 /* Each change to a lock requires roughly two key events */ 268 unsigned int update_locks = (def->set_locks & ~keyboard->lock_flags) | (def->clear_locks & keyboard->lock_flags); 269 cost += guac_rdp_count_bits(update_locks) * 2; 270 271 /* Each change to a modifier requires one key event */ 272 unsigned int update_modifiers = (def->clear_modifiers & modifier_flags) | (def->set_modifiers & ~modifier_flags); 273 cost += guac_rdp_count_bits(update_modifiers); 274 275 return cost; 276 277} 278 279/** 280 * Returns a pointer to the guac_rdp_key structure representing the 281 * definition(s) and state of the key having the given keysym. If no such key 282 * is defined within the keyboard layout of the RDP server, NULL is returned. 283 * 284 * @param keyboard 285 * The guac_rdp_keyboard associated with the current RDP session. 286 * 287 * @param keysym 288 * The keysym of the key to lookup within the given keyboard. 289 * 290 * @return 291 * A pointer to the guac_rdp_key structure representing the definition(s) 292 * and state of the key having the given keysym, or NULL if no such key is 293 * defined within the keyboard layout of the RDP server. 294 */ 295static guac_rdp_key* guac_rdp_keyboard_get_key(guac_rdp_keyboard* keyboard, 296 int keysym) { 297 298 /* Verify that the key is actually defined */ 299 guac_rdp_key** key_by_keysym = guac_rdp_keyboard_map_key(keyboard, keysym); 300 if (key_by_keysym == NULL) 301 return NULL; 302 303 return *key_by_keysym; 304 305} 306 307/** 308 * Given a key which may have multiple possible definitions, returns the 309 * definition that currently has the lowest cost, taking into account the 310 * current keyboard lock and modifier states. 311 * 312 * @param keyboard 313 * The guac_rdp_keyboard associated with the current RDP session. 314 * 315 * @param key 316 * The key whose lowest-cost possible definition should be retrieved. 317 * 318 * @return 319 * A pointer to the guac_rdp_keysym_desc which defines the current 320 * lowest-cost method of typing the given key. 321 */ 322static const guac_rdp_keysym_desc* guac_rdp_keyboard_get_definition(guac_rdp_keyboard* keyboard, 323 guac_rdp_key* key) { 324 325 /* Consistently map the same entry so long as the key is held */ 326 if (key->pressed != NULL) 327 return key->pressed; 328 329 /* Calculate cost of first definition of key (there must always be at least 330 * one definition) */ 331 const guac_rdp_keysym_desc* best_def = key->definitions[0]; 332 int best_cost = guac_rdp_keyboard_get_cost(keyboard, best_def); 333 334 /* If further definitions exist, choose the definition with the lowest 335 * overall cost */ 336 for (int i = 1; i < key->num_definitions; i++) { 337 338 const guac_rdp_keysym_desc* def = key->definitions[i]; 339 int cost = guac_rdp_keyboard_get_cost(keyboard, def); 340 341 if (cost < best_cost) { 342 best_def = def; 343 best_cost = cost; 344 } 345 346 } 347 348 return best_def; 349 350} 351 352/** 353 * Adds the keysym/scancode mapping described by the given guac_rdp_keysym_desc 354 * to the internal mapping of the keyboard. If insufficient space remains for 355 * additional keysyms, or the given keysym has already reached the maximum 356 * number of possible definitions, the mapping is ignored and the failure is 357 * logged. 358 * 359 * @param keyboard 360 * The guac_rdp_keyboard associated with the current RDP session. 361 * 362 * @param mapping 363 * The keysym/scancode mapping that should be added to the given keyboard. 364 */ 365static void guac_rdp_keyboard_add_mapping(guac_rdp_keyboard* keyboard, 366 const guac_rdp_keysym_desc* mapping) { 367 368 /* Locate corresponding keysym-to-key translation entry within keyboard 369 * structure */ 370 guac_rdp_key** key_by_keysym = guac_rdp_keyboard_map_key(keyboard, mapping->keysym); 371 if (key_by_keysym == NULL) { 372 guac_client_log(keyboard->client, GUAC_LOG_DEBUG, "Ignoring unmappable keysym 0x%X", mapping->keysym); 373 return; 374 } 375 376 /* If not yet pointing to a key, point keysym-to-key translation entry at 377 * next available storage */ 378 if (*key_by_keysym == NULL) { 379 380 if (keyboard->num_keys == GUAC_RDP_KEYBOARD_MAX_KEYSYMS) { 381 guac_client_log(keyboard->client, GUAC_LOG_DEBUG, "Key definition " 382 "for keysym 0x%X dropped: Keymap exceeds maximum " 383 "supported number of keysyms", 384 mapping->keysym); 385 return; 386 } 387 388 *key_by_keysym = &keyboard->keys[keyboard->num_keys++]; 389 390 } 391 392 guac_rdp_key* key = *key_by_keysym; 393 394 /* Add new definition only if sufficient space remains */ 395 if (key->num_definitions == GUAC_RDP_KEY_MAX_DEFINITIONS) { 396 guac_client_log(keyboard->client, GUAC_LOG_DEBUG, "Key definition " 397 "for keysym 0x%X dropped: Maximum number of possible " 398 "definitions has been reached for this keysym", 399 mapping->keysym); 400 return; 401 } 402 403 /* Store new possible definition of key */ 404 key->definitions[key->num_definitions++] = mapping; 405 406} 407 408/** 409 * Loads all keysym/scancode mappings declared within the given keymap and its 410 * parent keymap, if any. These mappings are stored within the given 411 * guac_rdp_keyboard structure for future use in translating keysyms to the 412 * scancodes required by RDP key events. 413 * 414 * @param keyboard 415 * The guac_rdp_keyboard which should be initialized with the 416 * keysym/scancode mapping defined in the given keymap. 417 * 418 * @param keymap 419 * The keymap to use to populate the given client's keysym/scancode 420 * mapping. 421 */ 422static void guac_rdp_keyboard_load_keymap(guac_rdp_keyboard* keyboard, 423 const guac_rdp_keymap* keymap) { 424 425 /* If parent exists, load parent first */ 426 if (keymap->parent != NULL) 427 guac_rdp_keyboard_load_keymap(keyboard, keymap->parent); 428 429 /* Log load */ 430 guac_client_log(keyboard->client, GUAC_LOG_INFO, 431 "Loading keymap \"%s\"", keymap->name); 432 433 /* Copy mapping into keymap */ 434 const guac_rdp_keysym_desc* mapping = keymap->mapping; 435 while (mapping->keysym != 0) { 436 guac_rdp_keyboard_add_mapping(keyboard, mapping++); 437 } 438 439} 440 441guac_rdp_keyboard* guac_rdp_keyboard_alloc(guac_client* client, 442 const guac_rdp_keymap* keymap) { 443 444 guac_rdp_keyboard* keyboard = guac_mem_zalloc(sizeof(guac_rdp_keyboard)); 445 keyboard->client = client; 446 447 /* Load keymap into keyboard */ 448 guac_rdp_keyboard_load_keymap(keyboard, keymap); 449 450 return keyboard; 451 452} 453 454void guac_rdp_keyboard_free(guac_rdp_keyboard* keyboard) { 455 guac_mem_free(keyboard); 456} 457 458int guac_rdp_keyboard_is_defined(guac_rdp_keyboard* keyboard, int keysym) { 459 460 /* Return whether the mapping actually exists */ 461 return guac_rdp_keyboard_get_key(keyboard, keysym) != NULL; 462 463} 464 465int guac_rdp_keyboard_is_pressed(guac_rdp_keyboard* keyboard, int keysym) { 466 467 guac_rdp_key* key = guac_rdp_keyboard_get_key(keyboard, keysym); 468 return key != NULL && key->pressed != NULL; 469 470} 471 472unsigned int guac_rdp_keyboard_get_modifier_flags(guac_rdp_keyboard* keyboard) { 473 474 unsigned int modifier_flags = 0; 475 476 /* Shift */ 477 if (guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_LSHIFT) 478 || guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_RSHIFT)) 479 modifier_flags |= GUAC_RDP_KEYMAP_MODIFIER_SHIFT; 480 481 /* Dedicated AltGr key */ 482 if (guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_RALT) 483 || guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_ALTGR)) 484 modifier_flags |= GUAC_RDP_KEYMAP_MODIFIER_ALTGR; 485 486 /* AltGr via Ctrl+Alt */ 487 if (guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_LALT) 488 && (guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_RCTRL) 489 || guac_rdp_keyboard_is_pressed(keyboard, GUAC_RDP_KEYSYM_LCTRL))) 490 modifier_flags |= GUAC_RDP_KEYMAP_MODIFIER_ALTGR; 491 492 return modifier_flags; 493 494} 495 496/** 497 * Presses/releases the requested key by sending one or more RDP key events, as 498 * defined within the keymap defining that key. 499 * 500 * @param keyboard 501 * The guac_rdp_keyboard associated with the current RDP session. 502 * 503 * @param key 504 * The guac_rdp_keysym_desc of the key being pressed or released, as 505 * retrieved from the relevant keymap. 506 * 507 * @param pressed 508 * Zero if the key is being released, non-zero otherwise. 509 * 510 * @return 511 * Zero if the key was successfully pressed/released, non-zero if the key 512 * cannot be sent using RDP key events. 513 */ 514static const guac_rdp_keysym_desc* guac_rdp_keyboard_send_defined_key(guac_rdp_keyboard* keyboard, 515 guac_rdp_key* key, int pressed) { 516 517 guac_client* client = keyboard->client; 518 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 519 520 const guac_rdp_keysym_desc* keysym_desc = guac_rdp_keyboard_get_definition(keyboard, key); 521 if (keysym_desc->scancode == 0) 522 return NULL; 523 524 /* Update state of required locks and modifiers only when key is just 525 * now being pressed */ 526 if (pressed) { 527 guac_rdp_keyboard_update_locks(keyboard, 528 keysym_desc->set_locks, 529 keysym_desc->clear_locks); 530 531 guac_rdp_keyboard_update_modifiers(keyboard, 532 keysym_desc->set_modifiers, 533 keysym_desc->clear_modifiers); 534 } 535 536 /* Fire actual key event for target key */ 537 guac_rdp_send_key_event(rdp_client, keysym_desc->scancode, 538 keysym_desc->flags, pressed); 539 540 return keysym_desc; 541 542} 543 544/** 545 * Presses and releases the requested key by sending one or more RDP events, 546 * without relying on a keymap for that key. This will typically involve either 547 * sending the key using a Unicode event or decomposing the key into a series 548 * of keypresses involving deadkeys. 549 * 550 * @param keyboard 551 * The guac_rdp_keyboard associated with the current RDP session. 552 * 553 * @param keysym 554 * The keysym of the key to press and release. 555 */ 556static void guac_rdp_keyboard_send_missing_key(guac_rdp_keyboard* keyboard, 557 int keysym) { 558 559 guac_client* client = keyboard->client; 560 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 561 562 /* Attempt to type using dead keys */ 563 if (!guac_rdp_decompose_keysym(keyboard, keysym)) 564 return; 565 566 guac_client_log(client, GUAC_LOG_DEBUG, "Sending keysym 0x%x as " 567 "Unicode", keysym); 568 569 /* Translate keysym into codepoint */ 570 int codepoint; 571 if (keysym <= 0xFF) 572 codepoint = keysym; 573 else if (keysym >= 0x1000000) 574 codepoint = keysym & 0xFFFFFF; 575 else { 576 guac_client_log(client, GUAC_LOG_DEBUG, "Unmapped keysym has no " 577 "equivalent unicode value: 0x%x", keysym); 578 return; 579 } 580 581 /* Send as Unicode event */ 582 guac_rdp_send_unicode_event(rdp_client, codepoint); 583 584} 585 586void guac_rdp_keyboard_update_locks(guac_rdp_keyboard* keyboard, 587 unsigned int set_flags, unsigned int clear_flags) { 588 589 guac_client* client = keyboard->client; 590 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 591 592 /* Calculate updated lock flags */ 593 unsigned int lock_flags = (keyboard->lock_flags | set_flags) & ~clear_flags; 594 595 /* Synchronize remote side only if lock flags have changed */ 596 if (lock_flags != keyboard->lock_flags) { 597 guac_rdp_send_synchronize_event(rdp_client, lock_flags); 598 keyboard->lock_flags = lock_flags; 599 } 600 601} 602 603void guac_rdp_keyboard_update_modifiers(guac_rdp_keyboard* keyboard, 604 unsigned int set_flags, unsigned int clear_flags) { 605 606 unsigned int modifier_flags = guac_rdp_keyboard_get_modifier_flags(keyboard); 607 608 /* Only clear modifiers that are set */ 609 clear_flags &= modifier_flags; 610 611 /* Only set modifiers that are currently cleared */ 612 set_flags &= ~modifier_flags; 613 614 /* Press/release Shift as needed */ 615 if (set_flags & GUAC_RDP_KEYMAP_MODIFIER_SHIFT) { 616 guac_rdp_keyboard_update_keysym(keyboard, GUAC_RDP_KEYSYM_LSHIFT, 1, GUAC_RDP_KEY_SOURCE_SYNTHETIC); 617 } 618 else if (clear_flags & GUAC_RDP_KEYMAP_MODIFIER_SHIFT) { 619 guac_rdp_keyboard_update_keysym(keyboard, GUAC_RDP_KEYSYM_LSHIFT, 0, GUAC_RDP_KEY_SOURCE_SYNTHETIC); 620 guac_rdp_keyboard_update_keysym(keyboard, GUAC_RDP_KEYSYM_RSHIFT, 0, GUAC_RDP_KEY_SOURCE_SYNTHETIC); 621 } 622 623 /* Press/release AltGr as needed */ 624 if (set_flags & GUAC_RDP_KEYMAP_MODIFIER_ALTGR) { 625 guac_rdp_keyboard_update_keysym(keyboard, GUAC_RDP_KEYSYM_ALTGR, 1, GUAC_RDP_KEY_SOURCE_SYNTHETIC); 626 } 627 else if (clear_flags & GUAC_RDP_KEYMAP_MODIFIER_ALTGR) { 628 guac_rdp_keyboard_update_keysym(keyboard, GUAC_RDP_KEYSYM_ALTGR, 0, GUAC_RDP_KEY_SOURCE_SYNTHETIC); 629 guac_rdp_keyboard_update_keysym(keyboard, GUAC_RDP_KEYSYM_LALT, 0, GUAC_RDP_KEY_SOURCE_SYNTHETIC); 630 guac_rdp_keyboard_update_keysym(keyboard, GUAC_RDP_KEYSYM_RALT, 0, GUAC_RDP_KEY_SOURCE_SYNTHETIC); 631 guac_rdp_keyboard_update_keysym(keyboard, GUAC_RDP_KEYSYM_LCTRL, 0, GUAC_RDP_KEY_SOURCE_SYNTHETIC); 632 guac_rdp_keyboard_update_keysym(keyboard, GUAC_RDP_KEYSYM_RCTRL, 0, GUAC_RDP_KEY_SOURCE_SYNTHETIC); 633 } 634 635} 636 637int guac_rdp_keyboard_update_keysym(guac_rdp_keyboard* keyboard, 638 int keysym, int pressed, guac_rdp_key_source source) { 639 640 /* Synchronize lock keys states, if this has not yet been done */ 641 if (!keyboard->synchronized) { 642 643 guac_client* client = keyboard->client; 644 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 645 646 /* Synchronize remote lock key states with local state */ 647 guac_rdp_send_synchronize_event(rdp_client, keyboard->lock_flags); 648 keyboard->synchronized = 1; 649 650 } 651 652 guac_rdp_key* key = guac_rdp_keyboard_get_key(keyboard, keysym); 653 654 /* Update tracking of client-side keyboard state but only for keys which 655 * are tracked server-side, as well (to ensure that the key count remains 656 * correct, even if a user sends extra unbalanced or excessive press and 657 * release events) */ 658 if (source == GUAC_RDP_KEY_SOURCE_CLIENT && key != NULL) { 659 if (pressed && !key->user_pressed) { 660 keyboard->user_pressed_keys++; 661 key->user_pressed = 1; 662 } 663 else if (!pressed && key->user_pressed) { 664 keyboard->user_pressed_keys--; 665 key->user_pressed = 0; 666 } 667 } 668 669 /* Send events and update server-side lock state only if server-side key 670 * state is changing (or if server-side state of this key is untracked) */ 671 if (key == NULL || (pressed && key->pressed == NULL) || (!pressed && key->pressed != NULL)) { 672 673 /* Toggle locks on keydown */ 674 if (pressed) 675 keyboard->lock_flags ^= guac_rdp_keyboard_lock_flag(keysym); 676 677 /* If key is known, update state and attempt to send using normal RDP key 678 * events */ 679 const guac_rdp_keysym_desc* definition = NULL; 680 if (key != NULL) { 681 definition = guac_rdp_keyboard_send_defined_key(keyboard, key, pressed); 682 key->pressed = pressed ? definition : NULL; 683 } 684 685 /* Fall back to dead keys or Unicode events if otherwise undefined inside 686 * current keymap (note that we only handle "pressed" here, as neither 687 * Unicode events nor dead keys can have a pressed/released state) */ 688 if (definition == NULL && pressed) { 689 guac_rdp_keyboard_send_missing_key(keyboard, keysym); 690 } 691 692 } 693 694 /* Reset RDP server keyboard state (releasing any automatically 695 * pressed keys) once all keys have been released on the client 696 * side */ 697 if (source == GUAC_RDP_KEY_SOURCE_CLIENT && keyboard->user_pressed_keys == 0) 698 guac_rdp_keyboard_reset(keyboard); 699 700 return 0; 701 702} 703 704void guac_rdp_keyboard_reset(guac_rdp_keyboard* keyboard) { 705 706 /* Release all pressed keys */ 707 for (int i = 0; i < keyboard->num_keys; i++) { 708 guac_rdp_key* key = &keyboard->keys[i]; 709 if (key->pressed != NULL) 710 guac_rdp_keyboard_update_keysym(keyboard, key->pressed->keysym, 0, 711 GUAC_RDP_KEY_SOURCE_SYNTHETIC); 712 } 713 714} 715 716BOOL guac_rdp_keyboard_set_indicators(rdpContext* context, UINT16 flags) { 717 718 guac_client* client = ((rdp_freerdp_context*) context)->client; 719 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 720 721 pthread_rwlock_rdlock(&(rdp_client->lock)); 722 723 /* Skip if keyboard not yet ready */ 724 guac_rdp_keyboard* keyboard = rdp_client->keyboard; 725 if (keyboard == NULL) 726 goto complete; 727 728 /* Update with received locks */ 729 guac_client_log(client, GUAC_LOG_DEBUG, "Received updated keyboard lock flags from RDP server: 0x%X", flags); 730 keyboard->lock_flags = flags; 731 732complete: 733 pthread_rwlock_unlock(&(rdp_client->lock)); 734 return TRUE; 735 736}