Skip to content

Commit

Permalink
refactor(linux): Implement ordered output sentinel in keyman-system-s…
Browse files Browse the repository at this point in the history
…ervice

This change allows to press F24 as ordered output sentinel from
keyman-system-service.

This is part of implementing serialized output with keyman-system-service
instead of requiring a patched ibus.

The problem both approaches try to solve is that with non-compliant apps
it is not possible to directly delete characters from the context. Instead
we have to emit a backspace key before we can commit the new
characters. However, the backspace key press goes through a different
code path in ibus and so it can happen that the commit gets processed
before the backspace which then deletes from the characters we just
added instead of from the old content.

The previous implementation solved this by forwarding a F24 ordered output
sentinel key to ibus and relying on the patched ibus to send that back to
us. When we received the F24 key we committed the characters that we
queued when we forwarded the F24 key (implemented in #7079).

The new approach implemented in this change instead sends the F24 ordered
output sentinel key through keyman-system-service and so follows the
regular key processing without requiring a patched ibus to send the key
back to us. The rest of the algorithm stays the same: when we receive the
F24 key we commit the characters previously queued.

Part of #10799.
  • Loading branch information
ermshiperete committed May 24, 2024
1 parent 071b178 commit 1212710
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 11 deletions.
15 changes: 11 additions & 4 deletions linux/keyman-system-service/src/KeyboardDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,26 @@ using namespace std;

KeyboardDevice::KeyboardDevice()
{
dev = nullptr;
fd = -1;
dev = nullptr;
fd = -1;
hasCapsLockLed = -1;
debug = false;
}

KeyboardDevice::~KeyboardDevice()
{
Close();
}

void KeyboardDevice::Close()
{
if (dev) {
libevdev_free(dev);
dev = nullptr;
}
if (fd != -1) {
close(fd);
fd = -1;
}
}

Expand All @@ -34,14 +41,14 @@ bool KeyboardDevice::Initialize(const char* name)
fd = open(path.c_str(), O_RDWR);
if (fd < 0) {
std::cerr << "Failed to open device " << path << ": " << strerror(errno) << std::endl;
Close();
return false;
}

int rc = libevdev_new_from_fd(fd, &dev);
if (rc < 0) {
std::cerr << "Failed to init libevdev for " << path << ": " << strerror(-rc) << std::endl;
close(fd);
fd = -1;
Close();
return false;
}

Expand Down
3 changes: 3 additions & 0 deletions linux/keyman-system-service/src/KeyboardDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <libevdev/libevdev.h>

// Any keyboard device available on the system
class KeyboardDevice
{
public:
Expand All @@ -20,6 +21,8 @@ class KeyboardDevice
int fd;
int hasCapsLockLed;
bool debug;

void Close();
};

#endif // __KEYBOARDDEVICE_H__
55 changes: 52 additions & 3 deletions linux/keyman-system-service/src/KeymanSystemService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,41 @@ on_get_caps_lock_indicator(
return sd_bus_reply_method_return(msg, "b", state);
}

static int32_t
on_call_ordered_output_sentinel(
sd_bus_message *msg,
void *user_data,
sd_bus_error *ret_error
) {
int32_t ret;
uint32_t isKeyDown;

*ret_error = SD_BUS_ERROR_NULL;

ret = sd_bus_message_read_basic(msg, 'b', &isKeyDown);
if (ret < 0) {
syslog(LOG_USER | LOG_NOTICE, "Failed to parse parameter: %s", strerror(-ret));
return ret;
}

KeymanSystemService *service = static_cast<KeymanSystemService *>(user_data);
service->CallOrderedOutputSentinel(isKeyDown);
return sd_bus_reply_method_return(msg, "");
}

static const sd_bus_vtable system_service_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("SetCapsLockIndicator", "b", "", on_set_caps_lock_indicator, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetCapsLockIndicator", "", "b", on_get_caps_lock_indicator, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END
};
SD_BUS_METHOD("CallOrderedOutputSentinel", "b", "", on_call_ordered_output_sentinel, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END};

KeymanSystemService::KeymanSystemService()
{
int ret;

GetKbdDevices();
CreateOrderOutputDevice();

#ifdef KEYMAN_TESTING
ret = sd_bus_open_user(&bus);
Expand Down Expand Up @@ -115,6 +138,12 @@ KeymanSystemService::~KeymanSystemService()
delete device;
}
delete kbd_devices;
kbd_ordered_output = nullptr;
}

if (kbd_ordered_output) {
delete kbd_ordered_output;
kbd_ordered_output = nullptr;
}
}

Expand Down Expand Up @@ -166,6 +195,16 @@ void KeymanSystemService::GetKbdDevices() {
}
}

void KeymanSystemService::CreateOrderOutputDevice() {
if (!kbd_ordered_output) {
kbd_ordered_output = new OrderOutputDevice();
if (!kbd_ordered_output->Initialize()) {
delete kbd_ordered_output;
kbd_ordered_output = nullptr;
}
}
}

// Set the CapsLock indicator on all keyboard devices.
void
KeymanSystemService::SetCapsLockIndicatorOnDevices(uint32_t state) {
Expand Down Expand Up @@ -193,3 +232,13 @@ KeymanSystemService::GetCapsLockIndicatorOnDevices() {
}
return state;
}

// Emit a ordered output sentinel key event
void
KeymanSystemService::CallOrderedOutputSentinel(uint32_t isKeyDown) {
if (!kbd_ordered_output) {
syslog(LOG_USER | LOG_ERR, "%s: No keyboard initialized", __FUNCTION__);
return;
}
kbd_ordered_output->PressSentinelKey(isKeyDown);
}
12 changes: 8 additions & 4 deletions linux/keyman-system-service/src/KeymanSystemService.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@

#include <list>
#include <systemd/sd-bus.h>
#include "OrderOutputDevice.h"
#include "KeyboardDevice.h"

using namespace std;

class KeymanSystemService {
private:
std::list<KeyboardDevice *>* kbd_devices = NULL;
sd_bus_slot *slot = NULL;
sd_bus *bus = NULL;
bool failed = false;
std::list<KeyboardDevice *>* kbd_devices = nullptr;
OrderOutputDevice *kbd_ordered_output = nullptr;
sd_bus_slot *slot = nullptr;
sd_bus *bus = nullptr;
bool failed = false;

void GetKbdDevices();
void CreateOrderOutputDevice();

public:
KeymanSystemService();
Expand All @@ -28,6 +31,7 @@ class KeymanSystemService {
int Loop();
void SetCapsLockIndicatorOnDevices(uint32_t state);
uint32_t GetCapsLockIndicatorOnDevices();
void CallOrderedOutputSentinel(uint32_t isKeyDown);
};

#endif // __KEYMANSYSTEMSERVICE_H__
74 changes: 74 additions & 0 deletions linux/keyman-system-service/src/OrderOutputDevice.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include <dirent.h>
#include <fcntl.h>
#include <iostream>
#include <string.h>
#include <string>
#include <syslog.h>
#include <unistd.h>
#include "OrderOutputDevice.h"

using namespace std;

#define KEYMAN_F24_KEYCODE_OUTPUT_SENTINEL 194 // 0xC2

OrderOutputDevice::OrderOutputDevice() {
uinput_dev = nullptr;
}

OrderOutputDevice::~OrderOutputDevice() {
Close();
}

void
OrderOutputDevice::Close() {
if (uinput_dev) {
libevdev_uinput_destroy(uinput_dev);
uinput_dev = nullptr;
}
}

bool
OrderOutputDevice::Initialize() {
struct libevdev* dev;

syslog(LOG_USER | LOG_ALERT, "%s: creating fake device", __FUNCTION__);

dev = libevdev_new();
libevdev_set_name(dev, "Ordered Output Keyman Keyboard Device");

libevdev_enable_event_type(dev, EV_KEY);

// F24 is the only key we support.
libevdev_enable_event_code(dev, EV_KEY, KEYMAN_F24_KEYCODE_OUTPUT_SENTINEL, NULL);

int rc = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uinput_dev);
if (rc < 0) {
syslog(LOG_USER | LOG_ERR, "%s: Failed to create Ordered Output keyman keyboard device: %s",
__FUNCTION__, strerror(-rc));
libevdev_free(dev);
Close();
return false;
}

return true;
}

bool
OrderOutputDevice::PressSentinelKey(bool isKeyDown) {
int error = libevdev_uinput_write_event(uinput_dev, EV_KEY, KEYMAN_F24_KEYCODE_OUTPUT_SENTINEL,
isKeyDown ? 1 : 0);
if (error < 0) {
syslog(LOG_USER | LOG_ERR,
"%s: Error writing send key event for sentinel key (down: %d): %s",
__FUNCTION__, isKeyDown, strerror(-error));
return false;
}
error = libevdev_uinput_write_event(uinput_dev, EV_SYN, SYN_REPORT, 0);
if (error < 0) {
syslog(LOG_USER | LOG_ERR,
"%s: Error writing syn event for sentinel key (down: %d): %s",
__FUNCTION__, isKeyDown, strerror(-error));
return false;
}
return true;
}
24 changes: 24 additions & 0 deletions linux/keyman-system-service/src/OrderOutputDevice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef __ORDEROUTPUTDEVICE_H__
#define __ORDEROUTPUTDEVICE_H__

#include <libevdev/libevdev-uinput.h>

// The fake keyboard we use to force the serializing of the output (#10799/#7079)
// Generate a fake key event to get the correct order of events so that any backspace key we
// generated will be processed before the character we're adding. We need to send a
// valid keycode so that it doesn't get swallowed by GTK but which isn't very likely used
// in real keyboards. F24 seems to work for that.
class OrderOutputDevice {
public:
OrderOutputDevice();
virtual ~OrderOutputDevice();

bool Initialize();
bool PressSentinelKey(bool isKeyDown);

private:
void Close();
struct libevdev_uinput *uinput_dev;
};

#endif // __ORDEROUTPUTDEVICE_H__
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,16 @@
<method name="GetCapsLockIndicator">
<arg name="state" type="b" direction="out" />
</method>

<!--
CallOrderedOutputSentinel:
@isKeyDown: Whether or not we're dealing with a keydown or keyup
Press the orderd output sentinel key to serialize the output.
-->
<method name="CallOrderedOutputSentinel">
<arg name="isKeyDown" type="b" direction="in"/>
</method>

</interface>
</node>
1 change: 1 addition & 0 deletions linux/keyman-system-service/src/meson.build
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
service_files = files(
'KeyboardDevice.cpp',
'KeymanSystemService.cpp',
'OrderOutputDevice.cpp',
'main.cpp',
)

Expand Down
3 changes: 3 additions & 0 deletions linux/keyman-system-service/tests/KeyboardDeviceMock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ KeyboardDeviceMock::KeyboardDeviceMock() {
KeyboardDeviceMock::~KeyboardDeviceMock() {
}

void KeyboardDeviceMock::Close() {
}

bool
KeyboardDeviceMock::Initialize(const char* name) {
return true;
Expand Down
1 change: 1 addition & 0 deletions linux/keyman-system-service/tests/meson.build
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
test_service_files = files(
'../src/main.cpp',
'../src/KeymanSystemService.cpp',
'../src/OrderOutputDevice.cpp',
'KeyboardDeviceMock.cpp',
)

Expand Down

0 comments on commit 1212710

Please sign in to comment.