// gKeyMapper daemon - executes the mapping configured for a G key. #include "action.h" #include #include #include #include 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& 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 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(held_.size())) { return false; } end_hold(index); // a repeated press must not stack codes std::vector 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 codes = step_key_codes(steps.front()); if (codes.empty()) { return false; } held_[static_cast(index)] = codes; keyboard_.press(codes); return true; } void ActionRunner::end_hold(int index) { if (index < 0 || index >= static_cast(held_.size())) { return; } std::vector& codes = held_[static_cast(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 up; for (unsigned short code : codes) { bool still_held = false; for (size_t other = 0; other < held_.size(); ++other) { if (other == static_cast(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(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(nullptr)); execl("/bin/sh", "sh", "-c", command.c_str(), static_cast(nullptr)); _exit(127); } // Parent: children are reaped by the SIGCHLD handler. } } // namespace gkeymapper::daemon