Files
gKeyMapper/daemon/action.cpp

161 lines
5.0 KiB
C++
Raw Permalink Normal View History

2026-10-17 03:53:51 +02:00
// gKeyMapper daemon - executes the mapping configured for a G key.
#include "action.h"
2026-10-18 16:59:51 +02:00
#include <algorithm>
2026-10-17 03:53:51 +02:00
#include <cstdio>
#include <vector>
#include <unistd.h>
namespace gkeymapper::daemon {
bool ActionRunner::init() {
keymap_.init();
if (!keyboard_.open()) {
std::fprintf(stderr,
"gkeymapper-daemon: no virtual keyboard, 'key' mappings disabled\n");
return false;
}
return true;
}
2026-10-18 16:59:51 +02:00
bool ActionRunner::resolve_shortcut(const std::string& target,
std::vector<gkeymapper::ShortcutStep>& steps) {
// Resolve key names through the active keyboard layout.
gkeymapper::KeyResolver resolve = [this](const std::string& name) {
return keymap_.key_code(name);
};
if (!gkeymapper::parse_shortcut(target, steps, resolve)) {
std::fprintf(stderr, "gkeymapper-daemon: invalid shortcut '%s'\n", target.c_str());
return false;
}
return true;
}
2026-10-17 03:53:51 +02:00
void ActionRunner::run(const gkeymapper::Mapping& mapping) {
switch (mapping.type) {
2026-10-18 16:59:51 +02:00
case gkeymapper::ActionType::Key:
case gkeymapper::ActionType::KeyHold: {
// A test (TestMapping) and any other caller that cannot wait for a
// key release fires a hold mapping as a tap. A real G-key press goes
// through begin_hold/end_hold instead.
2026-10-17 03:53:51 +02:00
std::vector<gkeymapper::ShortcutStep> steps;
2026-10-18 16:59:51 +02:00
if (!resolve_shortcut(mapping.target, steps)) {
2026-10-17 03:53:51 +02:00
return;
}
keyboard_.play(steps);
break;
}
case gkeymapper::ActionType::Command:
run_command(mapping.target);
break;
case gkeymapper::ActionType::Disabled:
default:
break;
}
}
2026-10-18 16:59:51 +02:00
bool ActionRunner::begin_hold(int index, const gkeymapper::Mapping& mapping) {
if (index < 0 || index >= static_cast<int>(held_.size())) {
return false;
}
end_hold(index); // a repeated press must not stack codes
std::vector<gkeymapper::ShortcutStep> steps;
if (!resolve_shortcut(mapping.target, steps)) {
return false;
}
if (steps.size() != 1) {
// Holding only means something for a single step (that is where the
// "same duration as the key press" idea comes from, see README). A
// sequence would have to hold several chords at once, so it is replayed
// as a tap instead - exactly like a "key" mapping.
std::fprintf(stderr,
"gkeymapper-daemon: 'keyhold:%s' is not a single step, replaying it "
"as a tap\n",
mapping.target.c_str());
keyboard_.play(steps);
return false;
}
if (!keyboard_.is_open()) {
return false; // no virtual keyboard: nothing to hold
}
const std::vector<unsigned short> codes = step_key_codes(steps.front());
if (codes.empty()) {
return false;
}
held_[static_cast<size_t>(index)] = codes;
keyboard_.press(codes);
return true;
}
void ActionRunner::end_hold(int index) {
if (index < 0 || index >= static_cast<int>(held_.size())) {
return;
}
std::vector<unsigned short>& codes = held_[static_cast<size_t>(index)];
if (codes.empty()) {
return;
}
// Release in press order; UinputKeyboard::release() turns that into the
// reverse order, so the key goes up first and the modifiers go up last. A
// code another G key also holds stays down until *its* G key is released.
std::vector<unsigned short> up;
for (unsigned short code : codes) {
bool still_held = false;
for (size_t other = 0; other < held_.size(); ++other) {
if (other == static_cast<size_t>(index)) {
continue;
}
if (std::find(held_[other].begin(), held_[other].end(), code) !=
held_[other].end()) {
still_held = true;
break;
}
}
if (!still_held) {
up.push_back(code);
}
}
codes.clear();
if (!up.empty()) {
keyboard_.release(up);
}
}
void ActionRunner::release_held() {
for (size_t i = 0; i < held_.size(); ++i) {
end_hold(static_cast<int>(i));
}
}
2026-10-18 03:50:33 +02:00
void ActionRunner::tap_key(unsigned short code) {
keyboard_.tap(code);
}
2026-10-17 03:53:51 +02:00
void ActionRunner::run_command(const std::string& command) {
if (command.empty()) {
return;
}
pid_t pid = fork();
if (pid < 0) {
std::fprintf(stderr, "gkeymapper-daemon: fork failed\n");
return;
}
if (pid == 0) {
// The field may be a bare program path (which may contain spaces) or a
// command line with arguments. Try the path directly first, then fall
// back to a shell so arguments and quoting also work.
execlp(command.c_str(), command.c_str(), static_cast<char*>(nullptr));
execl("/bin/sh", "sh", "-c", command.c_str(), static_cast<char*>(nullptr));
_exit(127);
}
// Parent: children are reaped by the SIGCHLD handler.
}
} // namespace gkeymapper::daemon