Skip to content

Commit

Permalink
Auto-Type: Support multiple Xkb layouts
Browse files Browse the repository at this point in the history
Completely rewritten XCB Auto-Type keymap system.

 - supports multiple simultaneous layouts
 - prefers current layout if it has all keysyms available
 - removed hardcoded KeySymMap
 - removed clunky custom KeySym emulation

Biggest breaking change is removing KeySym emulation for keys that
do not exist in any of the layouts currently in use. It would be
possible to make it work but if you are trying to type syms that
are not available in any of your layouts you are abusing it. It
also adds unnecessary complexity and opens up timing issues when
the keymap is modified on-the-fly. Now we are just reading it.

This also workarounds a Qt related issue where QX11Info::display()
returns a connection to X server that fails to receive updated
keymap data when client settings change. We use our own connection
now to get it working.
  • Loading branch information
hifi committed Mar 13, 2021
1 parent 8b8fb95 commit 63a81c5
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 356 deletions.
1 change: 1 addition & 0 deletions src/autotype/AutoType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin
const int maxRepetition = 100;

QList<QSharedPointer<AutoTypeAction>> actions;
actions << QSharedPointer<AutoTypeBegin>::create();
actions << QSharedPointer<AutoTypeDelay>::create(qMax(0, config()->get(Config::AutoTypeDelay).toInt()), true);

// Replace escaped braces with a template for easier regex
Expand Down
5 changes: 5 additions & 0 deletions src/autotype/AutoTypeAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,8 @@ void AutoTypeClearField::exec(AutoTypeExecutor* executor) const
{
executor->execClearField(this);
}

void AutoTypeBegin::exec(AutoTypeExecutor* executor) const
{
executor->execBegin(this);
}
7 changes: 7 additions & 0 deletions src/autotype/AutoTypeAction.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,17 @@ class KEEPASSXC_EXPORT AutoTypeClearField : public AutoTypeAction
void exec(AutoTypeExecutor* executor) const override;
};

class KEEPASSXC_EXPORT AutoTypeBegin : public AutoTypeAction
{
public:
void exec(AutoTypeExecutor* executor) const override;
};

class KEEPASSXC_EXPORT AutoTypeExecutor
{
public:
virtual ~AutoTypeExecutor() = default;
virtual void execBegin(const AutoTypeBegin* action) = 0;
virtual void execType(const AutoTypeKey* action) = 0;
virtual void execClearField(const AutoTypeClearField* action) = 0;

Expand Down
206 changes: 75 additions & 131 deletions src/autotype/xcb/AutoTypeXCB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

AutoTypePlatformX11::AutoTypePlatformX11()
{
m_dpy = QX11Info::display();
// Qt handles XCB slightly differently so we open our own connection
m_dpy = XOpenDisplay(DisplayString(QX11Info::display()));
m_rootWindow = QX11Info::appRootWindow();

m_atomWmState = XInternAtom(m_dpy, "WM_STATE", True);
Expand All @@ -42,15 +43,9 @@ AutoTypePlatformX11::AutoTypePlatformX11()
m_classBlacklist << "xfdesktop"
<< "xfce4-panel"; // Xfce 4

m_keysymTable = nullptr;
m_xkb = nullptr;
m_remapKeycode = 0;
m_currentRemapKeysym = NoSymbol;

m_loaded = true;

connect(nixUtils(), &NixUtils::keymapChanged, this, [this] { updateKeymap(); });
updateKeymap();
}

bool AutoTypePlatformX11::isAvailable()
Expand All @@ -65,34 +60,21 @@ bool AutoTypePlatformX11::isAvailable()
return false;
}

if (!m_xkb) {
XkbDescPtr kbd = getKeyboard();

if (!kbd) {
return false;
}

XkbFreeKeyboard(kbd, XkbAllComponentsMask, True);
}

return true;
}

void AutoTypePlatformX11::unload()
{
// Restore the KeyboardMapping to its original state.
if (m_currentRemapKeysym != NoSymbol) {
AddKeysym(NoSymbol);
}

if (m_keysymTable) {
XFree(m_keysymTable);
}
m_keymap.clear();

if (m_xkb) {
XkbFreeKeyboard(m_xkb, XkbAllComponentsMask, True);
m_xkb = nullptr;
}

XCloseDisplay(m_dpy);
m_dpy = nullptr;

m_loaded = false;
}

Expand Down Expand Up @@ -304,22 +286,30 @@ void AutoTypePlatformX11::updateKeymap()
if (m_xkb) {
XkbFreeKeyboard(m_xkb, XkbAllComponentsMask, True);
}
m_xkb = getKeyboard();
m_xkb = XkbGetMap(m_dpy, XkbAllClientInfoMask, XkbUseCoreKbd);

XDisplayKeycodes(m_dpy, &m_minKeycode, &m_maxKeycode);
if (m_keysymTable != nullptr) {
XFree(m_keysymTable);
}
m_keysymTable = XGetKeyboardMapping(m_dpy, m_minKeycode, m_maxKeycode - m_minKeycode + 1, &m_keysymPerKeycode);

/* determine the keycode to use for remapped keys */
if (m_remapKeycode == 0 || !isRemapKeycodeValid()) {
for (int keycode = m_minKeycode; keycode <= m_maxKeycode; keycode++) {
int inx = (keycode - m_minKeycode) * m_keysymPerKeycode;
if (m_keysymTable[inx] == NoSymbol) {
m_remapKeycode = keycode;
m_currentRemapKeysym = NoSymbol;
break;
/* Build updated keymap */
m_keymap.clear();

for (int ckeycode = m_xkb->min_key_code; ckeycode < m_xkb->max_key_code; ckeycode++) {
int groups = XkbKeyNumGroups(m_xkb, ckeycode);

for (int cgroup = 0; cgroup < groups; cgroup++) {
XkbKeyTypePtr type = XkbKeyKeyType(m_xkb, ckeycode, cgroup);

for (int clevel = 0; clevel < type->num_levels; clevel++) {
KeySym sym = XkbKeycodeToKeysym(m_dpy, ckeycode, cgroup, clevel);

int mask = 0;
for (int nmap = 0; nmap < type->map_count; nmap++) {
XkbKTMapEntryRec map = type->map[nmap];
if (map.active && map.level == clevel) {
mask = map.mods.mask;
break;
}
}

m_keymap.append(AutoTypePlatformX11::KeyDesc{sym, ckeycode, cgroup, mask});
}
}
}
Expand All @@ -337,70 +327,12 @@ void AutoTypePlatformX11::updateKeymap()
}
}
XFreeModifiermap(modifiers);

/* Xlib needs some time until the mapping is distributed to
all clients */
Tools::sleep(30);
}

bool AutoTypePlatformX11::isRemapKeycodeValid()
{
int baseKeycode = (m_remapKeycode - m_minKeycode) * m_keysymPerKeycode;
for (int i = 0; i < m_keysymPerKeycode; i++) {
if (m_keysymTable[baseKeycode + i] == m_currentRemapKeysym) {
return true;
}
}

return false;
}

XkbDescPtr AutoTypePlatformX11::getKeyboard()
{
int num_devices;
XID keyboard_id = XkbUseCoreKbd;
XDeviceInfo* devices = XListInputDevices(m_dpy, &num_devices);
if (!devices) {
return nullptr;
}

for (int i = 0; i < num_devices; i++) {
if (QString(devices[i].name) == "Virtual core XTEST keyboard") {
keyboard_id = devices[i].id;
break;
}
}

XFreeDeviceList(devices);

return XkbGetKeyboard(m_dpy, XkbCompatMapMask | XkbGeometryMask, keyboard_id);
}

// --------------------------------------------------------------------------
// The following code is taken from xvkbd 3.0 and has been slightly modified.
// --------------------------------------------------------------------------

/*
* Insert a specified keysym on the dedicated position in the keymap
* table.
*/
int AutoTypePlatformX11::AddKeysym(KeySym keysym)
{
if (m_remapKeycode == 0) {
return 0;
}

int inx = (m_remapKeycode - m_minKeycode) * m_keysymPerKeycode;
m_keysymTable[inx] = keysym;
m_currentRemapKeysym = keysym;

XChangeKeyboardMapping(m_dpy, m_remapKeycode, m_keysymPerKeycode, &m_keysymTable[inx], 1);
XFlush(m_dpy);
updateKeymap();

return m_remapKeycode;
}

/*
* Send event to the focused window.
* If input focus is specified explicitly, select the window
Expand Down Expand Up @@ -435,42 +367,26 @@ void AutoTypePlatformX11::SendModifiers(unsigned int mask, bool press)
* Determines the keycode and modifier mask for the given
* keysym.
*/
int AutoTypePlatformX11::GetKeycode(KeySym keysym, unsigned int* mask)
bool AutoTypePlatformX11::GetKeycode(KeySym keysym, int* keycode, int* group, unsigned int* mask)
{
int keycode = XKeysymToKeycode(m_dpy, keysym);

if (keycode && keysymModifiers(keysym, keycode, mask)) {
return keycode;
}

/* no modifier matches => resort to remapping */
keycode = AddKeysym(keysym);
if (keycode && keysymModifiers(keysym, keycode, mask)) {
return keycode;
}
const KeyDesc* desc = nullptr;

*mask = 0;
return 0;
}

bool AutoTypePlatformX11::keysymModifiers(KeySym keysym, int keycode, unsigned int* mask)
{
int shift, mod;
unsigned int mods_rtrn;

/* determine whether there is a combination of the modifiers
(Mod1-Mod5) with or without shift which returns keysym */
for (shift = 0; shift < 2; shift++) {
for (mod = ControlMapIndex; mod <= Mod5MapIndex; mod++) {
KeySym keysym_rtrn;
*mask = (mod == ControlMapIndex) ? shift : shift | (1 << mod);
XkbTranslateKeyCode(m_xkb, keycode, *mask, &mods_rtrn, &keysym_rtrn);
if (keysym_rtrn == keysym) {
return true;
for (const auto& key : m_keymap) {
if (key.sym == keysym) {
// pick this description if we don't have any for this sym or this matches the current group
if (desc == nullptr || key.group == *group) {
desc = &key;
}
}
}

if (desc) {
*keycode = desc->code;
*group = desc->group;
*mask = desc->mask;
return true;
}

return false;
}

Expand All @@ -487,14 +403,24 @@ void AutoTypePlatformX11::sendKey(KeySym keysym, unsigned int modifiers)
}

int keycode;
int group;
int group_active;
unsigned int wanted_mask;

/* determine keycode and mask for the given keysym */
keycode = GetKeycode(keysym, &wanted_mask);
if (keycode < 8 || keycode > 255) {
/* pull current active layout group */
XkbStateRec state;
XkbGetState(m_dpy, XkbUseCoreKbd, &state);
group_active = state.group;

/* tell GeyKeycode we would prefer a key from active group */
group = group_active;

/* determine keycode, group and mask for the given keysym */
if (!GetKeycode(keysym, &keycode, &group, &wanted_mask)) {
qWarning("Unable to get valid keycode for key: keysym=0x%lX", keysym);
return;
}

wanted_mask |= modifiers;

Window root, child;
Expand Down Expand Up @@ -541,6 +467,12 @@ void AutoTypePlatformX11::sendKey(KeySym keysym, unsigned int modifiers)
release_mask = release_check_mask;
}

/* change layout group if necessary */
if (group_active != group) {
XkbLockGroup(m_dpy, XkbUseCoreKbd, group);
XFlush(m_dpy);
}

/* set modifiers mask */
if ((release_mask | press_mask) & LockMask) {
SendModifiers(LockMask, true);
Expand All @@ -560,6 +492,12 @@ void AutoTypePlatformX11::sendKey(KeySym keysym, unsigned int modifiers)
SendModifiers(LockMask, true);
SendModifiers(LockMask, false);
}

/* reset layout group if necessary */
if (group_active != group) {
XkbLockGroup(m_dpy, XkbUseCoreKbd, group_active);
XFlush(m_dpy);
}
}

int AutoTypePlatformX11::MyErrorHandler(Display* my_dpy, XErrorEvent* event)
Expand All @@ -579,6 +517,12 @@ AutoTypeExecutorX11::AutoTypeExecutorX11(AutoTypePlatformX11* platform)
{
}

void AutoTypeExecutorX11::execBegin(const AutoTypeBegin* action)
{
Q_UNUSED(action);
m_platform->updateKeymap();
}

void AutoTypeExecutorX11::execType(const AutoTypeKey* action)
{
if (action->key != Qt::Key_unknown) {
Expand Down
22 changes: 11 additions & 11 deletions src/autotype/xcb/AutoTypeXCB.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class AutoTypePlatformX11 : public QObject, public AutoTypePlatformInterface
QString activeWindowTitle() override;
bool raiseWindow(WId window) override;
AutoTypeExecutor* createExecutor() override;
void updateKeymap();

void sendKey(KeySym keysym, unsigned int modifiers = 0);

Expand All @@ -65,13 +66,10 @@ class AutoTypePlatformX11 : public QObject, public AutoTypePlatformInterface
bool isTopLevelWindow(Window window);

XkbDescPtr getKeyboard();
void updateKeymap();
bool isRemapKeycodeValid();
int AddKeysym(KeySym keysym);
void SendKeyEvent(unsigned keycode, bool press);
void SendModifiers(unsigned int mask, bool press);
int GetKeycode(KeySym keysym, unsigned int* mask);
bool keysymModifiers(KeySym keysym, int keycode, unsigned int* mask);
bool GetKeycode(KeySym keysym, int* keycode, int* group, unsigned int* mask);

static int MyErrorHandler(Display* my_dpy, XErrorEvent* event);

Expand All @@ -87,14 +85,15 @@ class AutoTypePlatformX11 : public QObject, public AutoTypePlatformInterface
Atom m_atomWindow;
QSet<QString> m_classBlacklist;

typedef struct {
KeySym sym;
int code;
int group;
int mask;
} KeyDesc;

XkbDescPtr m_xkb;
KeySym* m_keysymTable;
int m_minKeycode;
int m_maxKeycode;
int m_keysymPerKeycode;
/* dedicated keycode for remapped keys */
unsigned int m_remapKeycode;
KeySym m_currentRemapKeysym;
QList<KeyDesc> m_keymap;
KeyCode m_modifier_keycode[N_MOD_INDICES];
bool m_loaded;
};
Expand All @@ -104,6 +103,7 @@ class AutoTypeExecutorX11 : public AutoTypeExecutor
public:
explicit AutoTypeExecutorX11(AutoTypePlatformX11* platform);

void execBegin(const AutoTypeBegin* action) override;
void execType(const AutoTypeKey* action) override;
void execClearField(const AutoTypeClearField* action) override;

Expand Down
Loading

0 comments on commit 63a81c5

Please sign in to comment.