Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve unknown keysym handling by reusing keycodes #1734

Merged
merged 1 commit into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 52 additions & 15 deletions unix/x0vncserver/XDesktop.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,43 @@ KeyCode XDesktop::XkbKeysymToKeycode(KeySym keysym) {
return keycode;
}

/*
* Keeps the list in LRU order by moving the used key to front of the list.
*/
static void onKeyUsed(std::list<AddedKeySym> &list, KeyCode usedKeycode) {
if (list.empty() || list.front().keycode == usedKeycode)
return;

std::list<AddedKeySym>::iterator it = list.begin();
++it;
for (; it != list.end(); ++it) {
AddedKeySym item = *it;
if (item.keycode == usedKeycode) {
list.erase(it);
list.push_front(item);
break;
}
}
}

/*
* Returns keycode of oldest item from list of manually added keysyms.
* The item is removed from the list.
* Returns 0 if no usable keycode is found.
*/
KeyCode XDesktop::getReusableKeycode(XkbDescPtr xkb) {
while (!addedKeysyms.empty()) {
AddedKeySym last = addedKeysyms.back();
addedKeysyms.pop_back();

// Make sure someone else hasn't modified the key
if (XkbKeyNumGroups(xkb, last.keycode) > 0 &&
XkbKeySymsPtr(xkb, last.keycode)[0] == last.keysym)
return last.keycode;
}
return 0;
}

KeyCode XDesktop::addKeysym(KeySym keysym)
{
int types[1];
Expand All @@ -426,6 +463,9 @@ KeyCode XDesktop::addKeysym(KeySym keysym)
}

if (key < xkb->min_key_code)
key = getReusableKeycode(xkb);

if (!key)
return 0;

memset(&changes, 0, sizeof(changes));
Expand Down Expand Up @@ -453,7 +493,7 @@ KeyCode XDesktop::addKeysym(KeySym keysym)

if (XkbChangeMap(dpy, xkb, &changes)) {
vlog.info("Added unknown keysym %s to keycode %d", XKeysymToString(keysym), key);
addedKeysyms[keysym] = key;
addedKeysyms.push_front({ syms[0], (KeyCode)key });
return key;
}

Expand All @@ -472,21 +512,17 @@ void XDesktop::deleteAddedKeysyms() {

KeyCode lowestKeyCode = xkb->max_key_code;
KeyCode highestKeyCode = xkb->min_key_code;
std::map<KeySym, KeyCode>::iterator it;
for (it = addedKeysyms.begin(); it != addedKeysyms.end(); it++) {
if (XkbKeyNumGroups(xkb, it->second) != 0) {
// Check if we are removing keysym we added ourself
if (XkbKeysymToKeycode(it->first) != it->second)
continue;
KeyCode keyCode = getReusableKeycode(xkb);
while (keyCode != 0) {
XkbChangeTypesOfKey(xkb, keyCode, 0, XkbGroup1Mask, nullptr, &changes);

XkbChangeTypesOfKey(xkb, it->second, 0, XkbGroup1Mask, nullptr, &changes);
if (keyCode < lowestKeyCode)
lowestKeyCode = keyCode;

if (it->second < lowestKeyCode)
lowestKeyCode = it->second;
if (keyCode > highestKeyCode)
highestKeyCode = keyCode;

if (it->second > highestKeyCode)
highestKeyCode = it->second;
}
keyCode = getReusableKeycode(xkb);
}

// Did we actually find something to remove?
Expand All @@ -497,8 +533,6 @@ void XDesktop::deleteAddedKeysyms() {
changes.first_key_sym = lowestKeyCode;
changes.num_key_syms = highestKeyCode - lowestKeyCode + 1;
XkbChangeMap(dpy, xkb, &changes);

addedKeysyms.clear();
}

KeyCode XDesktop::keysymToKeycode(KeySym keysym) {
Expand Down Expand Up @@ -552,6 +586,9 @@ void XDesktop::keyEvent(uint32_t keysym, uint32_t xtcode, bool down) {
else
pressedKeys.erase(keysym);

if (down)
onKeyUsed(addedKeysyms, keycode);

vlog.debug("%d %s", keycode, down ? "down" : "up");

XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
Expand Down
9 changes: 8 additions & 1 deletion unix/x0vncserver/XDesktop.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
class Geometry;
class XPixelBuffer;

struct AddedKeySym
{
KeySym keysym;
KeyCode keycode;
};

// number of XKb indicator leds to handle
#define XDESKTOP_N_LEDS 3

Expand Down Expand Up @@ -78,7 +84,7 @@ class XDesktop : public rfb::SDesktop,
bool haveXtest;
bool haveDamage;
int maxButtons;
std::map<KeySym, KeyCode> addedKeysyms;
std::list<AddedKeySym> addedKeysyms;
std::map<KeySym, KeyCode> pressedKeys;
bool running;
#ifdef HAVE_XDAMAGE
Expand All @@ -102,6 +108,7 @@ class XDesktop : public rfb::SDesktop,
protected:
#ifdef HAVE_XTEST
KeyCode XkbKeysymToKeycode(KeySym keysym);
KeyCode getReusableKeycode(XkbDescPtr xkb);
KeyCode addKeysym(KeySym keysym);
void deleteAddedKeysyms();
KeyCode keysymToKeycode(KeySym keysym);
Expand Down
3 changes: 3 additions & 0 deletions unix/xserver/hw/vnc/vncInput.c
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,9 @@ static void vncKeysymKeyboardEvent(KeySym keysym, int down)
/* Now press the actual key */
pressKey(vncKeyboardDev, keycode, TRUE, "keycode");

if(down)
vncOnKeyUsed(keycode);

/* And store the mapping so that we can do a proper release later */
for (i = 0;i < 256;i++) {
if (i == keycode)
Expand Down
1 change: 1 addition & 0 deletions unix/xserver/hw/vnc/vncInput.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ KeyCode vncKeysymToKeycode(KeySym keysym, unsigned state, unsigned *new_state);
int vncIsAffectedByNumLock(KeyCode keycode);

KeyCode vncAddKeysym(KeySym keysym, unsigned state);
void vncOnKeyUsed(KeyCode usedKeycode);

#ifdef __cplusplus
}
Expand Down
94 changes: 91 additions & 3 deletions unix/xserver/hw/vnc/vncInputXKB.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "list.h"
#include "xkbsrv.h"
#include "xkbstr.h"
#include "eventstr.h"
Expand All @@ -56,6 +57,25 @@ static const KeyCode fakeKeys[] = {
#endif
};

typedef struct
{
KeySym keysym;
KeyCode keycode;
struct xorg_list entry;
} AddedKeySym;

/*
* If a KeySym recieved from client is not mapped to any KeyCode, it needs to be
* mapped to an unused KeyCode to generate required key events.
*
* This list tracks such assignments. A KeyCode from this list can be reused if
* we run out of unused KeyCodes.
*
* Items in this list are maintained in LRU order, with most recently used key
* in front.
*/
static struct xorg_list addedKeysyms;

static void vncXkbProcessDeviceEvent(int screenNum,
InternalEvent *event,
DeviceIntPtr dev);
Expand Down Expand Up @@ -218,6 +238,8 @@ void vncPrepareInputDevices(void)
*/
mieqSetHandler(ET_KeyPress, vncXkbProcessDeviceEvent);
mieqSetHandler(ET_KeyRelease, vncXkbProcessDeviceEvent);

xorg_list_init(&addedKeysyms);
}

unsigned vncGetKeyboardState(void)
Expand Down Expand Up @@ -568,6 +590,68 @@ int vncIsAffectedByNumLock(KeyCode keycode)
return 1;
}

static void saveAddedKeysym(KeyCode code, KeySym sym)
{
AddedKeySym* item;

item = malloc(sizeof(AddedKeySym));
if (!item)
return;

item->keycode = code;
item->keysym = sym;
xorg_list_add(&item->entry, &addedKeysyms);
}

/*
* Keeps the list in LRU order by moving the used key to front of the list.
*/
void vncOnKeyUsed(KeyCode usedKeycode)
{
AddedKeySym* it;

if (xorg_list_is_empty(&addedKeysyms))
return;

it = xorg_list_first_entry(&addedKeysyms, AddedKeySym, entry);
if (it->keycode == usedKeycode)
return;

xorg_list_for_each_entry(it, &addedKeysyms, entry) {
if (it->keycode == usedKeycode) {
xorg_list_del(&it->entry);
xorg_list_add(&it->entry, &addedKeysyms);
break;
}
}
}

/*
* Returns keycode of oldest item from list of manually added keysyms.
* The item is removed from the list.
* Returns 0 if no usable keycode is found.
*/
static KeyCode getReusableKeycode(XkbDescPtr xkb)
{
AddedKeySym* last;
KeyCode result;

result = 0;
while (result == 0 && !xorg_list_is_empty(&addedKeysyms)) {
last = xorg_list_last_entry(&addedKeysyms, AddedKeySym, entry);

// Make sure someone else hasn't modified the key
if (XkbKeyNumGroups(xkb, last->keycode) > 0 &&
XkbKeySymsPtr(xkb, last->keycode)[0] == last->keysym &&
(xkb->names == NULL || xkb->names->keys[last->keycode].name[0] == 'T'))
result = last->keycode;

xorg_list_del(&last->entry);
free(last);
}
return result;
}

KeyCode vncAddKeysym(KeySym keysym, unsigned state)
{
DeviceIntPtr master;
Expand All @@ -589,6 +673,9 @@ KeyCode vncAddKeysym(KeySym keysym, unsigned state)
}

if (key < xkb->min_key_code)
key = getReusableKeycode(xkb);

if (!key)
return 0;

memset(&changes, 0, sizeof(changes));
Expand All @@ -600,9 +687,8 @@ KeyCode vncAddKeysym(KeySym keysym, unsigned state)
* Tools like xkbcomp get confused if there isn't a name
* assigned to the keycode we're trying to use.
*/
if (xkb->names && xkb->names->keys &&
(xkb->names->keys[key].name[0] == '\0')) {
xkb->names->keys[key].name[0] = 'I';
if (xkb->names && xkb->names->keys) {
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;
Expand Down Expand Up @@ -641,6 +727,8 @@ KeyCode vncAddKeysym(KeySym keysym, unsigned state)

XkbSendNotification(master, &changes, &cause);

saveAddedKeysym(key, syms[0]);

return key;
}

Expand Down