Files
gKeyMapper/daemon/uinput_keyboard.cpp
2026-10-18 16:59:51 +02:00

172 lines
4.4 KiB
C++

// gKeyMapper daemon - virtual keyboard for replaying "key" mappings.
#include "uinput_keyboard.h"
#include <algorithm>
#include <cstdio>
#include <fcntl.h>
#include <initializer_list>
#include <sys/ioctl.h>
#include <unistd.h>
#include <linux/input-event-codes.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include "keys.h"
namespace gkeymapper::daemon {
unsigned short modifier_key_code(unsigned bit) {
switch (bit) {
case gkeymapper::kModCtrl: return KEY_LEFTCTRL;
case gkeymapper::kModAlt: return KEY_LEFTALT;
case gkeymapper::kModShift: return KEY_LEFTSHIFT;
case gkeymapper::kModSuper: return KEY_LEFTMETA;
case gkeymapper::kModAltGr: return KEY_RIGHTALT;
default: return 0;
}
}
std::vector<unsigned short> step_key_codes(const gkeymapper::ShortcutStep& step) {
std::vector<unsigned short> codes;
for (unsigned bit : {gkeymapper::kModCtrl, gkeymapper::kModAlt,
gkeymapper::kModShift, gkeymapper::kModSuper,
gkeymapper::kModAltGr}) {
if ((step.modifiers & bit) == 0) {
continue;
}
unsigned short code = modifier_key_code(bit);
if (code != 0) {
codes.push_back(code);
}
}
if (step.linux_key >= 0) {
// "ctrl+leftctrl" and the like would press the same code twice; keep
// the list unique so press()/release() stay balanced.
unsigned short code = static_cast<unsigned short>(step.linux_key);
if (std::find(codes.begin(), codes.end(), code) == codes.end()) {
codes.push_back(code);
}
}
return codes;
}
UinputKeyboard::~UinputKeyboard() {
close();
}
bool UinputKeyboard::open() {
if (fd_ >= 0) {
return true;
}
fd_ = ::open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (fd_ < 0) {
std::fprintf(stderr, "gkeymapper-daemon: cannot open /dev/uinput (permissions?)\n");
fd_ = -1;
return false;
}
ioctl(fd_, UI_SET_EVBIT, EV_KEY);
ioctl(fd_, UI_SET_EVBIT, EV_SYN);
for (int code = 0; code <= KEY_MAX; ++code) {
ioctl(fd_, UI_SET_KEYBIT, code);
}
uinput_user_dev device{};
std::snprintf(device.name, UINPUT_MAX_NAME_SIZE, "gKeyMapper virtual keyboard");
device.id.bustype = BUS_USB;
device.id.vendor = 0x046d;
device.id.product = 0xc32b;
device.id.version = 1;
if (::write(fd_, &device, sizeof(device)) < 0) {
std::fprintf(stderr, "gkeymapper-daemon: uinput setup failed\n");
close();
return false;
}
if (ioctl(fd_, UI_DEV_CREATE) < 0) {
std::fprintf(stderr, "gkeymapper-daemon: UI_DEV_CREATE failed\n");
close();
return false;
}
return true;
}
void UinputKeyboard::close() {
if (fd_ >= 0) {
ioctl(fd_, UI_DEV_DESTROY);
::close(fd_);
fd_ = -1;
}
}
void UinputKeyboard::emit(unsigned short type, unsigned short code, int value) {
input_event event{};
event.type = type;
event.code = code;
event.value = value;
// A short write is not recoverable here; drop the event.
ssize_t written = ::write(fd_, &event, sizeof(event));
(void)written;
}
void UinputKeyboard::sync() {
emit(EV_SYN, SYN_REPORT, 0);
}
void UinputKeyboard::key_event(unsigned short code, bool down) {
emit(EV_KEY, code, down ? 1 : 0);
}
void UinputKeyboard::tap(unsigned short code) {
if (fd_ < 0) {
return;
}
key_event(code, true);
sync();
usleep(2000);
key_event(code, false);
sync();
}
void UinputKeyboard::press(const std::vector<unsigned short>& codes) {
if (fd_ < 0) {
return;
}
for (unsigned short code : codes) {
key_event(code, true);
}
sync();
}
void UinputKeyboard::release(const std::vector<unsigned short>& codes) {
if (fd_ < 0) {
return;
}
// Reverse order: the modifiers go up last.
for (auto it = codes.rbegin(); it != codes.rend(); ++it) {
key_event(*it, false);
}
sync();
}
void UinputKeyboard::play(const std::vector<gkeymapper::ShortcutStep>& steps) {
if (fd_ < 0) {
return;
}
for (const gkeymapper::ShortcutStep& step : steps) {
const std::vector<unsigned short> codes = step_key_codes(step);
if (codes.empty()) {
continue;
}
press(codes);
usleep(2000);
release(codes);
usleep(2000);
}
}
} // namespace gkeymapper::daemon