spiegel-keyman/linux/ibus-keyman/tests/KeyHandling.cpp
Eberhard Beilharz 0c2d8f0312
refactor(linux): changes to get integration tests working
This change contains various changes and refactorings which together
allows the integration tests to pass again.

One of the important changs is `DbusTestHelper` which listens to a DBus
signal for a sentinel message and then simulates the sentinel key press.
Getting this to work also requires processing sd-bus messages which hooks
into the glib event loop and is implemented in `EventSource`.

Fixes: #13204
2025-02-14 17:30:42 +01:00

167 lines
5.8 KiB
C++

#include <kmx/kmx_processevent.h>
#include <linux/input-event-codes.h>
#include <list>
#include <stack>
#include "keycodes.h"
#include "KeymanSystemServiceClient.h"
#include "testfixture.h"
#include "KeyHandling.h"
#define KEYMAN_LCTRL 29 // 0x1D
#define KEYMAN_LALT 56 // 0x38
#define KEYMAN_RCTRL 97 // 0x61
#define KEYMAN_RALT 100 // 0x64
extern GdkWindow* window;
typedef struct {
guint modifiers_keydown;
guint modifiers_keyup;
guint keyval;
guint16 keycode;
guint is_modifier;
} gdk_key_event;
static gdk_key_event get_key_event_for_modifier(guint modifier, guint& prev_modifiers, guint keyval, guint keycode) {
gdk_key_event event;
event.modifiers_keydown = prev_modifiers;
event.modifiers_keyup = prev_modifiers | modifier | IBUS_RELEASE_MASK;
prev_modifiers |= modifier;
event.keyval = keyval;
event.keycode = keycode;
event.is_modifier = 1;
return event;
}
static unsigned short vk_to_keycode(unsigned short vk) {
for (int i = 0; i < (int)sizeof(keycode_to_vk); i++) {
if (keycode_to_vk[i] == vk)
return i;
}
return -1;
}
static bool is_modifier(guint16 keycode) {
switch (keycode) {
case KEY_LEFTSHIFT:
case KEY_RIGHTSHIFT:
case KEY_LEFTALT:
case KEY_RIGHTALT:
case KEY_LEFTCTRL:
case KEY_RIGHTCTRL:
case KEY_CAPSLOCK:
return true;
}
return false;
}
static std::list<gdk_key_event> get_involved_keys(km::tests::key_event test_event) {
guint modifiers = 0;
if (get_capslock_indicator()) {
modifiers = IBUS_LOCK_MASK;
}
std::list<gdk_key_event> result;
if (test_event.vk == KEYMAN_F24_KEYCODE_OUTPUT_SENTINEL) {
gdk_key_event event;
event.modifiers_keydown = modifiers;
event.modifiers_keyup = modifiers | IBUS_RELEASE_MASK;
event.keyval = 0;
event.keycode = KEYMAN_F24_KEYCODE_OUTPUT_SENTINEL;
event.is_modifier = is_modifier(event.keycode);
result.push_back(event);
return result;
}
// process all modifiers
if (test_event.modifier_state & KM_CORE_MODIFIER_SHIFT) {
// we don't distinguish between R and L Shift
result.push_back(get_key_event_for_modifier(IBUS_SHIFT_MASK, modifiers, GDK_KEY_Shift_L, KEY_LEFTSHIFT));
}
if (test_event.modifier_state & KM_CORE_MODIFIER_ALT || test_event.modifier_state & KM_CORE_MODIFIER_LALT) {
result.push_back(get_key_event_for_modifier(IBUS_MOD1_MASK, modifiers, GDK_KEY_Alt_L, KEY_LEFTALT));
}
if (test_event.modifier_state & KM_CORE_MODIFIER_RALT) {
if (test_event.vk == KEYMAN_RALT)
result.push_back(get_key_event_for_modifier(IBUS_MOD1_MASK, modifiers, GDK_KEY_Alt_R, KEY_RIGHTALT));
else
result.push_back(get_key_event_for_modifier(IBUS_MOD5_MASK, modifiers, GDK_KEY_Alt_R, KEY_RIGHTALT));
}
if (test_event.modifier_state & KM_CORE_MODIFIER_CTRL || test_event.modifier_state & KM_CORE_MODIFIER_LCTRL) {
result.push_back(get_key_event_for_modifier(IBUS_CONTROL_MASK, modifiers, GDK_KEY_Control_L, KEY_LEFTCTRL));
}
if (test_event.modifier_state & KM_CORE_MODIFIER_RCTRL) {
result.push_back(get_key_event_for_modifier(IBUS_CONTROL_MASK, modifiers, GDK_KEY_Control_R, KEY_RIGHTCTRL));
}
// process key
guint keyval = (test_event.modifier_state & KM_CORE_MODIFIER_SHIFT) ? gdk_keyval_to_upper(test_event.vk)
: gdk_keyval_to_lower(test_event.vk);
// REVIEW: the keyval we use here are not correct for < 0x20 and >= 0x7f
// See /usr/include/X11/keysymdef.h and /usr/include/ibus-1.0/ibuskeysyms.h
// Currently this is not a problem because we don't use keyval, but if that ever changes we
// should be aware that our test behaves different than the real client.
gdk_key_event event;
event.modifiers_keydown = modifiers;
event.modifiers_keyup = modifiers | IBUS_RELEASE_MASK;
event.keyval = keyval;
event.keycode = vk_to_keycode(test_event.vk);
event.is_modifier = is_modifier(event.keycode);
result.push_back(event);
return result;
}
void press_key(IBusKeymanTestsFixture* fixture, km::tests::key_event key_event) {
auto involved_keys = get_involved_keys(key_event);
// press and hold the individual modifier keys, finally the actual key
std::stack<GdkEventKey> keys_to_release;
for (auto key = involved_keys.begin(); key != involved_keys.end(); key++) {
bool old_caps_lock = get_capslock_indicator();
guint lock_modifier = 0;
// KEY_CAPSLOCK behaves a bit strange:
// If capslock is off:
// - on keypress we turn on capslock, but the modifier is still not set.
// - The LOCK_MASK modifier gets set on release.
// When capslock is already on:
// - the LOCK_MASK modifier is set on both keypress and release
// - capslock gets turned off on release
if (key->keycode == KEY_CAPSLOCK && !old_caps_lock) {
set_capslock_indicator((guint32) true);
lock_modifier = IBUS_LOCK_MASK;
}
GdkEventKey keyEvent = {
.type = GDK_KEY_PRESS,
.window = window,
.send_event = 0,
.time = 0,
.state = key->modifiers_keydown,
.keyval = key->keyval,
.length = 0,
.string = NULL,
.hardware_keycode = key->keycode,
.group = 0,
.is_modifier = key->is_modifier};
gtk_im_context_filter_keypress(fixture->context, &keyEvent);
keyEvent.type = GDK_KEY_RELEASE;
keyEvent.state = key->modifiers_keyup | lock_modifier;
keys_to_release.push(keyEvent);
if (key->keycode == KEY_CAPSLOCK && old_caps_lock) {
set_capslock_indicator((guint32) false);
}
}
// then release the keys in reverse order
while (!keys_to_release.empty()) {
auto keyEvent = keys_to_release.top();
gtk_im_context_filter_keypress(fixture->context, &keyEvent);
keys_to_release.pop();
}
}