Files
gKeyMapper/daemon/main.cpp
2026-10-17 03:53:51 +02:00

119 lines
3.7 KiB
C++

// gKeyMapper daemon - remaps G1..G9 on the Logitech G910.
//
// It reads the raw HID reports of the keyboard's vendor interface, looks up the
// configured action and replays it ("key" via uinput, "command" via exec).
//
// Milestones: M0 (shared core), M1 (device bring-up/decoding), M2 (action
// execution) and M3 (config watching + lifecycle) are done; the GUI channel
// (D-Bus) is still open (M4).
#include <csignal>
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
#include "action.h"
#include "config_watch.h"
#include "device_tables.h"
#include "g910_device.h"
#include "mapping.h"
namespace {
volatile std::sig_atomic_t g_running = 1;
volatile std::sig_atomic_t g_reload = 0;
void handle_stop(int /*signum*/) {
g_running = 0;
}
void handle_reload(int /*signum*/) {
g_reload = 1;
}
} // namespace
int main(int argc, char** argv) {
std::signal(SIGINT, handle_stop);
std::signal(SIGTERM, handle_stop);
std::signal(SIGHUP, handle_reload);
std::signal(SIGUSR1, handle_reload);
std::signal(SIGCHLD, SIG_IGN); // auto-reap launched commands
std::string configPath = gkeymapper::default_config_path();
if (argc > 1) {
configPath = argv[1];
}
gkeymapper::MappingSet mappings = gkeymapper::parse_mappings("");
// Reads the config; on failure the previous mappings stay in place, so a
// transient read error never silently disables every key.
auto reload = [&]() {
if (gkeymapper::load_mappings_file(configPath, mappings)) {
std::fprintf(stderr, "gkeymapper-daemon: loaded %s\n", configPath.c_str());
} else {
std::fprintf(stderr, "gkeymapper-daemon: cannot read %s\n", configPath.c_str());
}
};
reload();
// The GUI is the writer; a save is picked up through inotify, SIGHUP/SIGUSR1
// stay as the fallback for scripts and for systems without inotify.
gkeymapper::daemon::ConfigWatcher watcher;
watcher.init(configPath);
gkeymapper::daemon::ActionRunner actions;
actions.init();
gkeymapper::daemon::GKeyDevice device;
if (!device.open()) {
return 1;
}
device.disable_binding();
const gkeymapper::daemon::DeviceProfile* profile = device.profile();
uint8_t buffer[64];
while (g_running) {
// The loop wakes up at least every read timeout, which also bounds how
// long a config change takes to be applied (well under a second).
watcher.poll_events();
if (g_reload || watcher.take_change()) {
g_reload = 0;
reload();
}
int n = device.read_report(buffer, sizeof(buffer), 500);
if (n == 0) {
continue; // no report within the timeout
}
if (n < 0) {
std::fprintf(stderr, "gkeymapper-daemon: read error (%d), reopening\n", n);
device.close();
if (!device.open()) {
return 1;
}
device.disable_binding();
profile = device.profile();
continue;
}
int index = gkeymapper::daemon::match_gkey(*profile, buffer, n);
if (index >= 0 && index < static_cast<int>(mappings.keys.size())) {
const gkeymapper::Mapping& mapping = mappings.keys[index];
std::printf("G%d -> %s:%s\n", index + 1,
gkeymapper::action_type_token(mapping.type),
mapping.target.c_str());
std::fflush(stdout);
actions.run(mapping);
} else if (gkeymapper::daemon::is_release_report(*profile, buffer, n)) {
// Key released; nothing to do.
}
// Media keys and LED acknowledgements are ignored.
}
device.close();
return 0;
}