From 5d5698547961f68b5b089ed1b3aeed63209e2260 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Jan 2018 00:56:24 -0800 Subject: [PATCH 1/2] GUACAMOLE-352: Add utility function for checking whether a keysym exists within the current RDP keyboard layout. --- src/protocols/rdp/keyboard.c | 15 +++++++++++++++ src/protocols/rdp/keyboard.h | 17 +++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/protocols/rdp/keyboard.c b/src/protocols/rdp/keyboard.c index d7e9a37c..2184e02c 100644 --- a/src/protocols/rdp/keyboard.c +++ b/src/protocols/rdp/keyboard.c @@ -244,6 +244,21 @@ void guac_rdp_keyboard_free(guac_rdp_keyboard* keyboard) { free(keyboard); } +int guac_rdp_keyboard_is_defined(guac_rdp_keyboard* keyboard, int keysym) { + + /* Verify keysym can actually be stored within keymap */ + if (!GUAC_RDP_KEYSYM_STORABLE(keysym)) + return 0; + + /* Look up scancode mapping */ + const guac_rdp_keysym_desc* keysym_desc = + &GUAC_RDP_KEYSYM_LOOKUP(keyboard->keymap, keysym); + + /* Return whether the mapping actually exists */ + return keysym_desc->scancode != 0; + +} + int guac_rdp_keyboard_send_event(guac_rdp_keyboard* keyboard, int keysym, int pressed) { diff --git a/src/protocols/rdp/keyboard.h b/src/protocols/rdp/keyboard.h index 8282c18c..d81b47a7 100644 --- a/src/protocols/rdp/keyboard.h +++ b/src/protocols/rdp/keyboard.h @@ -95,6 +95,23 @@ guac_rdp_keyboard* guac_rdp_keyboard_alloc(guac_client* client, */ void guac_rdp_keyboard_free(guac_rdp_keyboard* keyboard); +/** + * Returns whether the given keysym is defined for the keyboard layout + * associated with the given keyboard. + * + * @param keyboard + * The guac_rdp_keyboard instance to check. + * + * @param keysym + * The keysym of the key being checked against the keyboard layout of the + * given keyboard. + * + * @return + * Non-zero if the key is explicitly defined within the keyboard layout of + * the given keyboard, zero otherwise. + */ +int guac_rdp_keyboard_is_defined(guac_rdp_keyboard* keyboard, int keysym); + /** * Sends one or more RDP key events, effectively pressing or releasing the * given keysym on the remote side. The key events sent will depend on the From 9a5b5574a833d95e1f2828bd028eddab4ecb2acb Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 28 Jan 2018 01:00:02 -0800 Subject: [PATCH 2/2] GUACAMOLE-352: Type using dead keys when necessary and possible. --- src/protocols/rdp/Makefile.am | 2 + src/protocols/rdp/decompose.c | 177 ++++++++++++++++++++++++++++++++++ src/protocols/rdp/decompose.h | 50 ++++++++++ src/protocols/rdp/keyboard.c | 12 ++- 4 files changed, 237 insertions(+), 4 deletions(-) create mode 100644 src/protocols/rdp/decompose.c create mode 100644 src/protocols/rdp/decompose.h diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 7de6df51..cffabb2c 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -28,6 +28,7 @@ nodist_libguac_client_rdp_la_SOURCES = \ libguac_client_rdp_la_SOURCES = \ audio_input.c \ client.c \ + decompose.c \ dvc.c \ error.c \ input.c \ @@ -98,6 +99,7 @@ noinst_HEADERS = \ guac_svc/svc_service.h \ audio_input.h \ client.h \ + decompose.h \ dvc.h \ error.h \ input.h \ diff --git a/src/protocols/rdp/decompose.c b/src/protocols/rdp/decompose.c new file mode 100644 index 00000000..5f559d99 --- /dev/null +++ b/src/protocols/rdp/decompose.c @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "keyboard.h" + +/** + * The X11 keysym for the dead key which types a grave (`). + */ +#define DEAD_GRAVE 0xFE50 + +/** + * The X11 keysym for the dead key which types an acute (´). Note that this is + * NOT equivalent to an apostrophe or single quote. + */ +#define DEAD_ACUTE 0xFE51 + +/** + * The X11 keysym for the dead key which types a circumflex/caret (^). + */ +#define DEAD_CIRCUMFLEX 0xFE52 + +/** + * The X11 keysym for the dead key which types a tilde (~). + */ +#define DEAD_TILDE 0xFE53 + +/** + * The X11 keysym for the dead key which types a dieresis/umlaut (¨). + */ +#define DEAD_DIERESIS 0xFE57 + +/** + * The X11 keysym for the dead key which types an abovering (˚). Note that this + * is NOT equivalent to the degree symbol. + */ +#define DEAD_ABOVERING 0xFE58 + +/** + * The decomposed form of a key that can be typed using two keypresses: a dead + * key followed by a base key. For example, on a keyboard which lacks a single + * dedicated key for doing the same, "ó" would be typed using the dead acute + * key followed by the "o" key. The dead key and base key are pressed and + * released in sequence; they are not held down. + */ +typedef struct guac_rdp_decomposed_key { + + /** + * The keysym of the dead key which must first be pressed and released to + * begin typing the desired character. The dead key defines the diacritic + * which will be applied to the character typed by the base key. + */ + int dead_keysym; + + /** + * The keysym of the base key which must be pressed and released to finish + * typing the desired character. The base key defines the normal form of + * the character (the form which lacks any diacritic) to which the + * diacritic defined by the previously-pressed dead key will be applied. + */ + int base_keysym; + +} guac_rdp_decomposed_key; + +/** + * A lookup table of all known decomposed forms of various keysyms. Keysyms map + * directly to entries within this table. A keysym which has no entry within + * this table does not have a defined decomposed form (or at least does not + * have a decomposed form relevant to RDP). + */ +guac_rdp_decomposed_key guac_rdp_decomposed_keys[256] = { + + /* ^ */ [0x005E] = { DEAD_CIRCUMFLEX, ' ' }, + /* ` */ [0x0060] = { DEAD_GRAVE, ' ' }, + /* ~ */ [0x007E] = { DEAD_TILDE, ' ' }, + /* ¨ */ [0x00A8] = { DEAD_DIERESIS, ' ' }, + /* ´ */ [0x00B4] = { DEAD_ACUTE, ' ' }, + /* À */ [0x00C0] = { DEAD_GRAVE, 'A' }, + /* Á */ [0x00C1] = { DEAD_ACUTE, 'A' }, + /* Â */ [0x00C2] = { DEAD_CIRCUMFLEX, 'A' }, + /* Ã */ [0x00C3] = { DEAD_TILDE, 'A' }, + /* Ä */ [0x00C4] = { DEAD_DIERESIS, 'A' }, + /* Å */ [0x00C5] = { DEAD_ABOVERING, 'A' }, + /* È */ [0x00C8] = { DEAD_GRAVE, 'E' }, + /* É */ [0x00C9] = { DEAD_ACUTE, 'E' }, + /* Ê */ [0x00CA] = { DEAD_CIRCUMFLEX, 'E' }, + /* Ë */ [0x00CB] = { DEAD_DIERESIS, 'E' }, + /* Ì */ [0x00CC] = { DEAD_GRAVE, 'I' }, + /* Í */ [0x00CD] = { DEAD_ACUTE, 'I' }, + /* Î */ [0x00CE] = { DEAD_CIRCUMFLEX, 'I' }, + /* Ï */ [0x00CF] = { DEAD_DIERESIS, 'I' }, + /* Ñ */ [0x00D1] = { DEAD_TILDE, 'N' }, + /* Ò */ [0x00D2] = { DEAD_GRAVE, 'O' }, + /* Ó */ [0x00D3] = { DEAD_ACUTE, 'O' }, + /* Ô */ [0x00D4] = { DEAD_CIRCUMFLEX, 'O' }, + /* Õ */ [0x00D5] = { DEAD_TILDE, 'O' }, + /* Ö */ [0x00D6] = { DEAD_DIERESIS, 'O' }, + /* Ù */ [0x00D9] = { DEAD_GRAVE, 'U' }, + /* Ú */ [0x00DA] = { DEAD_ACUTE, 'U' }, + /* Û */ [0x00DB] = { DEAD_CIRCUMFLEX, 'U' }, + /* Ü */ [0x00DC] = { DEAD_DIERESIS, 'U' }, + /* Ý */ [0x00DD] = { DEAD_ACUTE, 'Y' }, + /* à */ [0x00E0] = { DEAD_GRAVE, 'a' }, + /* á */ [0x00E1] = { DEAD_ACUTE, 'a' }, + /* â */ [0x00E2] = { DEAD_CIRCUMFLEX, 'a' }, + /* ã */ [0x00E3] = { DEAD_TILDE, 'a' }, + /* ä */ [0x00E4] = { DEAD_DIERESIS, 'a' }, + /* å */ [0x00E5] = { DEAD_ABOVERING, 'a' }, + /* è */ [0x00E8] = { DEAD_GRAVE, 'e' }, + /* é */ [0x00E9] = { DEAD_ACUTE, 'e' }, + /* ê */ [0x00EA] = { DEAD_CIRCUMFLEX, 'e' }, + /* ë */ [0x00EB] = { DEAD_DIERESIS, 'e' }, + /* ì */ [0x00EC] = { DEAD_GRAVE, 'i' }, + /* í */ [0x00ED] = { DEAD_ACUTE, 'i' }, + /* î */ [0x00EE] = { DEAD_CIRCUMFLEX, 'i' }, + /* ï */ [0x00EF] = { DEAD_DIERESIS, 'i' }, + /* ñ */ [0x00F1] = { DEAD_TILDE, 'n' }, + /* ò */ [0x00F2] = { DEAD_GRAVE, 'o' }, + /* ó */ [0x00F3] = { DEAD_ACUTE, 'o' }, + /* ô */ [0x00F4] = { DEAD_CIRCUMFLEX, 'o' }, + /* õ */ [0x00F5] = { DEAD_TILDE, 'o' }, + /* ö */ [0x00F6] = { DEAD_DIERESIS, 'o' }, + /* ù */ [0x00F9] = { DEAD_GRAVE, 'u' }, + /* ú */ [0x00FA] = { DEAD_ACUTE, 'u' }, + /* û */ [0x00FB] = { DEAD_CIRCUMFLEX, 'u' }, + /* ü */ [0x00FC] = { DEAD_DIERESIS, 'u' }, + /* ý */ [0x00FD] = { DEAD_ACUTE, 'y' }, + /* ÿ */ [0x00FF] = { DEAD_DIERESIS, 'y' } + +}; + +int guac_rdp_decompose_keysym(guac_rdp_keyboard* keyboard, int keysym) { + + /* Verify keysym is within range of lookup table */ + if (keysym < 0x00 || keysym > 0xFF) + return 1; + + /* Verify keysym is actually defined within lookup table */ + guac_rdp_decomposed_key* key = &guac_rdp_decomposed_keys[keysym]; + if (!key->dead_keysym) + return 1; + + /* Cannot type using decomposed keys if those keys are not defined within + * the current layout */ + if (!guac_rdp_keyboard_is_defined(keyboard, key->dead_keysym) + || !guac_rdp_keyboard_is_defined(keyboard, key->base_keysym)) + return 1; + + /* Press dead key */ + guac_rdp_keyboard_send_event(keyboard, key->dead_keysym, 1); + guac_rdp_keyboard_send_event(keyboard, key->dead_keysym, 0); + + /* Press base key */ + guac_rdp_keyboard_send_event(keyboard, key->base_keysym, 1); + guac_rdp_keyboard_send_event(keyboard, key->base_keysym, 0); + + /* Decomposed key successfully typed */ + return 0; + +} + diff --git a/src/protocols/rdp/decompose.h b/src/protocols/rdp/decompose.h new file mode 100644 index 00000000..a7ab1465 --- /dev/null +++ b/src/protocols/rdp/decompose.h @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_RDP_DECOMPOSE_H +#define GUAC_RDP_DECOMPOSE_H + +#include "config.h" +#include "keyboard.h" + +/** + * Attempts to type the given keysym by decomposing the associated character + * into the dead key and base key pair which would be used to type that + * character on a keyboard which lacks the necessary dedicated key. The key + * events for the dead key and base key are sent only if the keyboard layout of + * the given keyboard defines those keys. + * + * For example, the keysym for "ò" (0x00F2) would decompose into a dead grave + * (`) and the base key "o". May it rest in peace. + * + * @param keyboard + * The guac_rdp_keyboard associated with the current RDP session. + * + * @param keysym + * The keysym being pressed. + * + * @return + * Zero if the keysym was successfully decomposed and sent to the RDP + * server as a pair of key events (the dead key and base key), non-zero + * otherwise. + */ +int guac_rdp_decompose_keysym(guac_rdp_keyboard* keyboard, int keysym); + +#endif + diff --git a/src/protocols/rdp/keyboard.c b/src/protocols/rdp/keyboard.c index 2184e02c..94329ec6 100644 --- a/src/protocols/rdp/keyboard.c +++ b/src/protocols/rdp/keyboard.c @@ -20,6 +20,7 @@ #include "config.h" #include "client.h" +#include "decompose.h" #include "keyboard.h" #include "rdp.h" #include "rdp_keymap.h" @@ -309,12 +310,15 @@ int guac_rdp_keyboard_send_event(guac_rdp_keyboard* keyboard, } } - /* Fall back to unicode events if undefined inside current keymap */ - - /* Only send when key pressed - Unicode events do not have - * DOWN/RELEASE flags */ + /* Fall back to dead keys or Unicode events if otherwise undefined inside + * current keymap (note that we only handle "pressed" here, as neither + * Unicode events nor dead keys can have a pressed/released state) */ if (pressed) { + /* Attempt to type using dead keys */ + if (!guac_rdp_decompose_keysym(keyboard, keysym)) + return 0; + guac_client_log(client, GUAC_LOG_DEBUG, "Sending keysym 0x%x as Unicode", keysym);