feat: mvp
This commit is contained in:
267
src/main.cpp
Normal file
267
src/main.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
// 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 <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/input.h>
|
||||
#include <iostream>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/sinks/stdout_sinks.h>
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
struct KeyPosition
|
||||
{
|
||||
int row;
|
||||
int col;
|
||||
};
|
||||
|
||||
struct ConfigKey
|
||||
{
|
||||
std::string name;
|
||||
std::string script;
|
||||
};
|
||||
|
||||
struct ConfigRow
|
||||
{
|
||||
std::vector<ConfigKey> keys;
|
||||
};
|
||||
|
||||
struct KeyboardConfig
|
||||
{
|
||||
std::string devicePath;
|
||||
std::vector<ConfigRow> keyboardLayout;
|
||||
std::string logLevel;
|
||||
};
|
||||
|
||||
static const char *const eventValues[3] = {
|
||||
"RELEASED",
|
||||
"PRESSED ",
|
||||
"REPEATED"};
|
||||
|
||||
std::shared_ptr<spdlog::logger> logger;
|
||||
|
||||
std::pair<int, int> mapKeyEventToRowColumn(int keyEventNumber, const std::unordered_map<int, KeyPosition> &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 = "config.yaml";
|
||||
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);
|
||||
}
|
||||
}
|
||||
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<std::string>();
|
||||
config.logLevel = root["logLevel"].as<std::string>();
|
||||
|
||||
// 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<std::string>().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<std::string>().substr(3)); // Extract key index from key name
|
||||
config.keyboardLayout[rowIdx].keys[keyIdx].script = key.second["script"].as<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
void initLogger(const spdlog::level::level_enum logLevel)
|
||||
{
|
||||
auto consoleSink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
|
||||
|
||||
logger = std::make_shared<spdlog::logger>("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<int, KeyPosition> 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<int, int> 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;
|
||||
}
|
||||
Reference in New Issue
Block a user