cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

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}