161 lines
5.0 KiB
C++
161 lines
5.0 KiB
C++
// gKeyMapper daemon - executes the mapping configured for a G key.
|
|
|
|
#include "action.h"
|
|
|
|
#include <algorithm>
|
|
#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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void ActionRunner::run(const gkeymapper::Mapping& mapping) {
|
|
switch (mapping.type) {
|
|
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.
|
|
std::vector<gkeymapper::ShortcutStep> steps;
|
|
if (!resolve_shortcut(mapping.target, steps)) {
|
|
return;
|
|
}
|
|
keyboard_.play(steps);
|
|
break;
|
|
}
|
|
case gkeymapper::ActionType::Command:
|
|
run_command(mapping.target);
|
|
break;
|
|
case gkeymapper::ActionType::Disabled:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
void ActionRunner::tap_key(unsigned short code) {
|
|
keyboard_.tap(code);
|
|
}
|
|
|
|
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
|