From c8008e5bd5f7f3282158b76463b496f1fffae503 Mon Sep 17 00:00:00 2001 From: Gaurav Ujjwal Date: Mon, 4 Mar 2024 11:22:21 +0530 Subject: [PATCH] Handle unknown keysyms --- unix/common/CInput.cxx | 174 ++++++++++++++++++++++++++++++++++ unix/common/CInput.h | 22 +++++ unix/common/CMakeLists.txt | 3 +- unix/x0vncserver/XDesktop.cxx | 28 ++++-- 4 files changed, 219 insertions(+), 8 deletions(-) create mode 100644 unix/common/CInput.cxx create mode 100644 unix/common/CInput.h diff --git a/unix/common/CInput.cxx b/unix/common/CInput.cxx new file mode 100644 index 0000000000..738ce7de1d --- /dev/null +++ b/unix/common/CInput.cxx @@ -0,0 +1,174 @@ + + +#include +#include + +#include "CInput.h" + + +static rfb::LogWriter vlog("CInput"); + +/************************************* + * TODO + * - Double check "changes" struct type + * - Double check all "changes" are retrieved/propagated to server (name & map) + */ + +/************************* Custom key list ****************************/ +typedef struct { + KeyCode keyCode; + KeySym keySym; +}AssignedKey; + + +/** + * Values are stored in 'most recently used' order + */ +static std::list assignedKeys; + + +static Bool isAssignmentStillValid(AssignedKey assignment, XkbDescPtr xkb) +{ + return XkbKeyNumGroups(xkb, assignment.keyCode) > 0 && + XkbKeySymsPtr(xkb, assignment.keyCode)[0] == assignment.keySym && + (xkb->names == NULL || xkb->names->keys[assignment.keyCode].name[0] == 'T'); +} + +static void addAssignment(KeyCode code, KeySym sym) +{ + vlog.info("Setting custom key assignment: [%d] -> %ld", code, sym); + assignedKeys.push_front({ code, sym }); +} + +/** + * Returns the least recently used and still valid assignment. + * If no valid assignment found, returns an asssignment with keyCode = 0. + * Returned and invalid assignments are removed from list. + */ +static AssignedKey getReusableAssignment(XkbDescPtr xkb) +{ + while (!assignedKeys.empty()) { + AssignedKey last = assignedKeys.back(); + assignedKeys.pop_back(); + if (isAssignmentStillValid(last, xkb)) + return last; + else + vlog.info("Assingnment no longer valid: %d", last.keyCode); + } + return { 0, 0 }; +} + + + +static KeyCode getUnusedKey(XkbDescPtr xkb) +{ + for (KeyCode key = xkb->max_key_code; key >= xkb->min_key_code; key--) + if (XkbKeyNumGroups(xkb, key) == 0) + return key; + return 0; +} + +static void assignKeysymToKey(KeySym keysym, KeyCode key, XkbDescPtr xkb, XkbChangesPtr changes) +{ + int types[1]; + KeySym* syms; + KeySym upper, lower; + + // Assign a custom name + if (xkb->names) { + xkb->names->keys[key].name[0] = 'T'; + xkb->names->keys[key].name[1] = '0' + (key / 100) % 10; + xkb->names->keys[key].name[2] = '0' + (key / 10) % 10; + xkb->names->keys[key].name[3] = '0' + (key / 1) % 10; + changes->names.changed |= XkbKeyNamesMask; + changes->names.first_key = key; + changes->names.num_keys = 1; + } + + // Update group + XConvertCase(keysym, &lower, &upper); + if (upper == lower) + types[XkbGroup1Index] = XkbOneLevelIndex; + else + types[XkbGroup1Index] = XkbAlphabeticIndex; + + XkbChangeTypesOfKey(xkb, key, 1, XkbGroup1Mask, types, &changes->map); + + // Assign symbols + syms = XkbKeySymsPtr(xkb, key); + if (upper == lower) + syms[0] = keysym; + else { + syms[0] = lower; + syms[1] = upper; + } + + changes->map.changed |= XkbKeySymsMask; + changes->map.first_key_sym = key; + changes->map.num_key_syms = 1; + + // Remember assignment + addAssignment(key, syms[0]); +} + +KeyCode addKeysymToMap(KeySym keysym, XkbDescPtr xkb, XkbChangesPtr changes) +{ + //vlog.info("Trying to add unknown keysym: %ld", keysym); + + KeyCode key = getUnusedKey(xkb); + if (!key) { + key = getReusableAssignment(xkb).keyCode; + vlog.info("Got reusable key: %d", key); + } + if (!key) + return 0; + + assignKeysymToKey(keysym, key, xkb, changes); + + return key; +} + +void removeAddedKeysymsFromMap(XkbDescPtr xkb, XkbMapChangesPtr changes) { + KeyCode lowestKeyCode = xkb->max_key_code; + KeyCode highestKeyCode = xkb->min_key_code; + + AssignedKey assignment = getReusableAssignment(xkb); + while (assignment.keyCode) { + KeyCode keyCode = assignment.keyCode; + + XkbChangeTypesOfKey(xkb, keyCode, 0, XkbGroup1Mask, NULL, changes); + + if (keyCode < lowestKeyCode) + lowestKeyCode = keyCode; + + if (keyCode > highestKeyCode) + highestKeyCode = keyCode; + + // Get next + assignment = getReusableAssignment(xkb); + } + + // Did we actually find something to remove? + if (highestKeyCode >= lowestKeyCode) { + changes->changed |= XkbKeySymsMask; + changes->first_key_sym = lowestKeyCode; + changes->num_key_syms = highestKeyCode - lowestKeyCode + 1; + } +} + + +void onKeyUsed(KeyCode usedKey) { + if (assignedKeys.empty()) + return; + + // If there a custom assignment for this key, move it to the front + std::list::iterator it = assignedKeys.begin(); + for (; it != assignedKeys.end(); ++it) { + AssignedKey assignment = *it; + if (assignment.keyCode == usedKey && it != assignedKeys.begin()) { + assignedKeys.erase(it); + assignedKeys.push_front(assignment); + break; + } + } +} diff --git a/unix/common/CInput.h b/unix/common/CInput.h new file mode 100644 index 0000000000..b21b155bbd --- /dev/null +++ b/unix/common/CInput.h @@ -0,0 +1,22 @@ +#ifndef INPUTCOMMON_H +#define INPUTCOMMON_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + KeyCode addKeysymToMap(KeySym keysym, XkbDescPtr xkb, XkbChangesPtr changes); + void removeAddedKeysymsFromMap(XkbDescPtr xkb, XkbMapChangesPtr changes); + + /** + * Should be called whenever client uses a key. + */ + void onKeyUsed(KeyCode key); + +#ifdef __cplusplus +} +#endif +#endif /* INPUTCOMMON_H */ diff --git a/unix/common/CMakeLists.txt b/unix/common/CMakeLists.txt index 87e2ae7985..d40bbeb425 100644 --- a/unix/common/CMakeLists.txt +++ b/unix/common/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(unixcommon STATIC - randr.cxx) + randr.cxx + CInput.cxx) target_include_directories(unixcommon PUBLIC ${CMAKE_SOURCE_DIR}/common) target_include_directories(unixcommon PUBLIC ${CMAKE_SOURCE_DIR}/unix/common) diff --git a/unix/x0vncserver/XDesktop.cxx b/unix/x0vncserver/XDesktop.cxx index b3d5e54c48..2c877aae1e 100644 --- a/unix/x0vncserver/XDesktop.cxx +++ b/unix/x0vncserver/XDesktop.cxx @@ -45,6 +45,7 @@ #ifdef HAVE_XRANDR #include #include +#include extern "C" { void vncSetGlueContext(Display *dpy, void *res); } @@ -404,19 +405,20 @@ KeyCode XDesktop::XkbKeysymToKeycode(Display* dpy, KeySym keysym) { KeyCode XDesktop::addKeysym(Display* dpy, KeySym keysym) { - int types[1]; + //int types[1]; unsigned int key; XkbDescPtr xkb; - XkbMapChangesRec changes; - KeySym *syms; - KeySym upper, lower; + XkbChangesRec changes; + //KeySym *syms; + //KeySym upper, lower; - xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd); + xkb = XkbGetMap(dpy, XkbAllComponentsMask|XkbNamesMask, XkbUseCoreKbd); if (!xkb) return 0; - for (key = xkb->max_key_code; key >= xkb->min_key_code; key--) { + memset(&changes, 0, sizeof(changes)); + /* for (key = xkb->max_key_code; key >= xkb->min_key_code; key--) { if (XkbKeyNumGroups(xkb, key) == 0) break; } @@ -446,8 +448,11 @@ KeyCode XDesktop::addKeysym(Display* dpy, KeySym keysym) changes.changed |= XkbKeySymsMask; changes.first_key_sym = key; changes.num_key_syms = 1; + */ - if (XkbChangeMap(dpy, xkb, &changes)) { + key = addKeysymToMap(keysym, xkb, &changes); + + if (key && XkbChangeMap(dpy, xkb, &changes.map)) { vlog.info("Added unknown keysym %s to keycode %d", XKeysymToString(keysym), key); addedKeysyms[keysym] = key; return key; @@ -466,6 +471,7 @@ void XDesktop::deleteAddedKeysyms(Display* dpy) { XkbMapChangesRec changes; memset(&changes, 0, sizeof(changes)); + /* KeyCode lowestKeyCode = xkb->max_key_code; KeyCode highestKeyCode = xkb->min_key_code; std::map::iterator it; @@ -493,6 +499,11 @@ void XDesktop::deleteAddedKeysyms(Display* dpy) { changes.first_key_sym = lowestKeyCode; changes.num_key_syms = highestKeyCode - lowestKeyCode + 1; XkbChangeMap(dpy, xkb, &changes); + */ + + removeAddedKeysymsFromMap(xkb, &changes); + if (changes.num_key_syms > 0) + XkbChangeMap(dpy, xkb, &changes); addedKeysyms.clear(); } @@ -550,6 +561,9 @@ void XDesktop::keyEvent(uint32_t keysym, uint32_t xtcode, bool down) { vlog.debug("%d %s", keycode, down ? "down" : "up"); + if(down) + onKeyUsed(keycode); + XTestFakeKeyEvent(dpy, keycode, down, CurrentTime); #else (void)keysym;