// 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 #include #include #include #include #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(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; }