// gKeyMapper daemon - virtual keyboard for replaying "key" mappings. #include "uinput_keyboard.h" #include #include #include #include #include #include #include #include #include #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 step_key_codes(const gkeymapper::ShortcutStep& step) { std::vector 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(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& codes) { if (fd_ < 0) { return; } for (unsigned short code : codes) { key_event(code, true); } sync(); } void UinputKeyboard::release(const std::vector& 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& steps) { if (fd_ < 0) { return; } for (const gkeymapper::ShortcutStep& step : steps) { const std::vector codes = step_key_codes(step); if (codes.empty()) { continue; } press(codes); usleep(2000); release(codes); usleep(2000); } } } // namespace gkeymapper::daemon