// https://stackoverflow.com/questions/20943322/accessing-keys-from-linux-input-device /** * TODO: Handle the keyboard being unplugged/turned off and replugged/turned on again */ #include #include #include #include #include #include #include #include struct KeyPosition { int row; int col; }; struct ConfigKey { std::string name; std::string script; }; struct ConfigRow { std::vector keys; }; struct KeyboardConfig { std::string devicePath; std::vector keyboardLayout; std::string logLevel; }; static const char *const eventValues[3] = { "RELEASED", "PRESSED ", "REPEATED"}; std::shared_ptr logger; std::pair mapKeyEventToRowColumn(int keyEventNumber, const std::unordered_map &keyMap) { auto it = keyMap.find(keyEventNumber); if (it != keyMap.end()) { return std::make_pair(it->second.row, it->second.col); } else { return std::make_pair(-1, -1); } } std::string getConfigPathFromCliArguments(int argc, char *argv[]) { std::string configPath; int opt; while ((opt = getopt(argc, argv, "c:")) != -1) { switch (opt) { case 'c': configPath = optarg; break; default: std::cerr << "Usage: " << argv[0] << " [-c config_file_path]\n"; exit(EXIT_FAILURE); } } if (configPath.empty()) { const char *home = std::getenv("HOME"); if (home != nullptr) { configPath = std::string(home) + "/.config/usbMakroBoard.yaml"; } else { std::cerr << "HOME environment variable is not set. Exiting.\n"; exit(EXIT_FAILURE); } } return configPath; } YAML::Node loadConfig(const std::string &configPath) { return YAML::LoadFile(configPath); } int openDevice(const std::string &devicePath) { int fdKeyboard = open(devicePath.c_str(), O_RDONLY); if (fdKeyboard == -1) { logger->error("Cannot open {}: {}.\n", devicePath.c_str(), strerror(errno)); exit(EXIT_FAILURE); } return fdKeyboard; } KeyboardConfig parseConfigYAML(const std::string &filename) { KeyboardConfig config; YAML::Node root = YAML::LoadFile(filename); config.devicePath = root["devicePath"].as(); config.logLevel = root["logLevel"].as(); // Initialize keyboard layout config.keyboardLayout.resize(5); for (int i = 0; i < 5; ++i) { config.keyboardLayout[i].keys.resize(4); } YAML::Node keyboardLayout = root["keyboard_layout"]; for (const auto &row : keyboardLayout) { int rowIdx = std::stoi(row.first.as().substr(3)); // Extract row index from key name YAML::Node keys = row.second["keys"]; for (const auto &key : keys) { int keyIdx = std::stoi(key.first.as().substr(3)); // Extract key index from key name config.keyboardLayout[rowIdx].keys[keyIdx].script = key.second["script"].as(); } } return config; } void initLogger(const spdlog::level::level_enum logLevel) { auto consoleSink = std::make_shared(); logger = std::make_shared("logger", consoleSink); logger->set_level(logLevel); spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%L] %v"); spdlog::register_logger(logger); } spdlog::level::level_enum convertLogLevelToSpdLog(const std::string logLevelStr) { if (logLevelStr == "trace") { return spdlog::level::trace; } else if (logLevelStr == "debug") { return spdlog::level::debug; } else if (logLevelStr == "info") { return spdlog::level::info; } else if (logLevelStr == "warn") { return spdlog::level::warn; } else if (logLevelStr == "error") { return spdlog::level::err; } else if (logLevelStr == "critical") { return spdlog::level::critical; } else { return spdlog::level::info; } } void grabReleaseInputDevice(const int fdKeyboard) { const int ioControlResult = ioctl(fdKeyboard, EVIOCGRAB, 1); if (ioControlResult == -1) { logger->error("Cannot grab input device: {}", strerror(errno)); close(fdKeyboard); exit(EXIT_FAILURE); } } int main(int argc, char *argv[]) { /** * +--+--+--+--+ * |30|48|46|32| * +--+--+--+--+ * |18|33|34|35| * +--+--+--+--+ * |23|36|37|38| * +--+--+--+--+ * |50|49|24|25| * +--+--+--+--+ * |16|19|31|20| * +--+--+--+--+ * */ const std::unordered_map keyMap = { {30, {0, 0}}, {48, {0, 1}}, {46, {0, 2}}, {32, {0, 3}}, {18, {1, 0}}, {33, {1, 1}}, {34, {1, 2}}, {35, {1, 3}}, {23, {2, 0}}, {36, {2, 1}}, {37, {2, 2}}, {38, {2, 3}}, {50, {3, 0}}, {49, {3, 1}}, {24, {3, 2}}, {25, {3, 3}}, {16, {4, 0}}, {19, {4, 1}}, {31, {4, 2}}, {20, {4, 3}}}; const KeyboardConfig config = parseConfigYAML(getConfigPathFromCliArguments(argc, argv)); initLogger(convertLogLevelToSpdLog(config.logLevel)); const int fdKeyboard = openDevice(config.devicePath); grabReleaseInputDevice(fdKeyboard); struct input_event inputEvent; ssize_t eventLength; while (1) { eventLength = read(fdKeyboard, &inputEvent, sizeof inputEvent); if (eventLength == (ssize_t)-1) { if (errno == EINTR) { logger->warn("Interrupted system call"); continue; } else if (errno == ENODEV) { logger->error("Device disconnected. Exiting"); break; } else { printf("Error reading from device: %s (%d)\n", strerror(errno), errno); break; } } /** * inputEvent.value can be * 0: RELEASED * 1: PRESSED * 2: REPEATED */ if (inputEvent.type == EV_KEY && inputEvent.value == 0) { const std::pair keyPosition = mapKeyEventToRowColumn(inputEvent.code, keyMap); if (keyPosition.first == -1 || keyPosition.second == -1) { logger->error("Key {} not found in key map", inputEvent.code); continue; } const std::string scriptToRun = config.keyboardLayout[keyPosition.first].keys[keyPosition.second].script; logger->debug("Pressed key: {}x{}", keyPosition.first, keyPosition.second); if (!scriptToRun.empty()) { logger->info("Running script: {}", scriptToRun); system(scriptToRun.c_str()); } else { logger->warn("No script found for key: {}x{}", keyPosition.first, keyPosition.second); } } } close(fdKeyboard); return EXIT_FAILURE; }