Compare commits
23 Commits
05fba43e87
...
renovate/h
| Author | SHA1 | Date | |
|---|---|---|---|
| be83698259 | |||
| eddaee52a1 | |||
| c235b44f71 | |||
| d72b2f6d51 | |||
| 6ca5f5bd96 | |||
| 07952b7071 | |||
| 19138aaf81 | |||
| 33593fcf81 | |||
| 4854d8ef68 | |||
| 13aac3a963 | |||
| d1f1510d96 | |||
| a21193d9fa | |||
| 583eeb5355 | |||
| fe59687b76 | |||
| ff8bc15bc5 | |||
| 0d0509b8f1 | |||
| 36e37c46ba | |||
| 83f4a683cd | |||
| c839c1ff49 | |||
| a13048665f | |||
| e522001182 | |||
| 1902c111ef | |||
|
|
5501d05aa4 |
49
.gitea/workflows/build.yaml
Normal file
49
.gitea/workflows/build.yaml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
runner:
|
||||||
|
type: string
|
||||||
|
default: "linux_amd64"
|
||||||
|
arch:
|
||||||
|
type: string
|
||||||
|
default: "amd64"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on:
|
||||||
|
- ubuntu-latest
|
||||||
|
- ${{ inputs.runner }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y build-essential cmake zip
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
cmake --build .
|
||||||
|
- name: Verify binary
|
||||||
|
run: |
|
||||||
|
test -f build/usbmakroboard && echo "Binary built successfully" || exit 1
|
||||||
|
- name: Create zip archive
|
||||||
|
run: |
|
||||||
|
cd build
|
||||||
|
zip usbmakroboard-linux-${{ inputs.arch }}.zip usbmakroboard
|
||||||
|
- name: Generate checksum
|
||||||
|
run: |
|
||||||
|
cd build
|
||||||
|
sha256sum usbmakroboard-linux-${{ inputs.arch }}.zip > usbmakroboard-linux-${{ inputs.arch }}.zip.sha256
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: ChristopherHX/gitea-upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: usbmakroboard-${{ inputs.arch }}
|
||||||
|
path: |
|
||||||
|
build/usbmakroboard-linux-${{ inputs.arch }}.zip
|
||||||
|
build/usbmakroboard-linux-${{ inputs.arch }}.zip.sha256
|
||||||
|
retention-days: 1
|
||||||
58
.gitea/workflows/cd.yaml
Normal file
58
.gitea/workflows/cd.yaml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
name: CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "src/**"
|
||||||
|
- "CMakeLists.txt"
|
||||||
|
- "schemas/**"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- runner: linux_amd64
|
||||||
|
arch: amd64
|
||||||
|
artifact_name: usbmakroboard-amd64
|
||||||
|
- runner: linux_arm64
|
||||||
|
arch: arm64
|
||||||
|
artifact_name: usbmakroboard-arm64
|
||||||
|
uses: ./.gitea/workflows/build.yaml
|
||||||
|
with:
|
||||||
|
runner: ${{ matrix.runner }}
|
||||||
|
arch: ${{ matrix.arch }}
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: https://gitea.t000-n.de/t.behrendt/conventional-semantic-git-tag-increment@41b7e04221df8a033bec841d40a097b76e5f67ff # 0.1.29
|
||||||
|
id: tag
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
prerelease: ${{ github.event_name == 'workflow_dispatch' }}
|
||||||
|
- uses: https://gitea.t000-n.de/t.behrendt/actions/release-git-tag@f386e2570df6a796ba0a69865c89ea0c1a7109ab # 0.2.2
|
||||||
|
with:
|
||||||
|
tag: ${{ steps.tag.outputs.new-tag }}
|
||||||
|
- uses: ChristopherHX/gitea-download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
- name: Create release
|
||||||
|
uses: akkuman/gitea-release-action@fe8e0322804b48e34e3bddbbf6335bd2b1046eb7 # v1
|
||||||
|
with:
|
||||||
|
tag_name: ${{ steps.tag.outputs.new-tag }}
|
||||||
|
name: ${{ steps.tag.outputs.new-tag }}
|
||||||
|
files: |
|
||||||
|
artifacts/usbmakroboard-amd64/usbmakroboard-linux-amd64.zip
|
||||||
|
artifacts/usbmakroboard-amd64/usbmakroboard-linux-amd64.zip.sha256
|
||||||
|
artifacts/usbmakroboard-arm64/usbmakroboard-linux-arm64.zip
|
||||||
|
artifacts/usbmakroboard-arm64/usbmakroboard-linux-arm64.zip.sha256
|
||||||
|
prerelease: ${{ github.event_name == 'workflow_dispatch' }}
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
20
.gitea/workflows/ci.yaml
Normal file
20
.gitea/workflows/ci.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- runner: linux_amd64
|
||||||
|
arch: amd64
|
||||||
|
artifact_name: usbmakroboard-amd64
|
||||||
|
- runner: linux_arm64
|
||||||
|
arch: arm64
|
||||||
|
artifact_name: usbmakroboard-arm64
|
||||||
|
uses: ./.gitea/workflows/build.yaml
|
||||||
|
with:
|
||||||
|
runner: ${{ matrix.runner }}
|
||||||
|
arch: ${{ matrix.arch }}
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -45,3 +45,4 @@ _deps
|
|||||||
*.out
|
*.out
|
||||||
*.app
|
*.app
|
||||||
|
|
||||||
|
build/
|
||||||
7
.vscode/launch.json
vendored
Normal file
7
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": []
|
||||||
|
}
|
||||||
77
.vscode/settings.json
vendored
Normal file
77
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"C_Cpp.errorSquiggles": "Disabled",
|
||||||
|
"yaml.schemas": {
|
||||||
|
"schemas/config.schema.json": "config.yaml",
|
||||||
|
},
|
||||||
|
"files.associations": {
|
||||||
|
"array": "cpp",
|
||||||
|
"atomic": "cpp",
|
||||||
|
"bit": "cpp",
|
||||||
|
"*.tcc": "cpp",
|
||||||
|
"bitset": "cpp",
|
||||||
|
"cctype": "cpp",
|
||||||
|
"charconv": "cpp",
|
||||||
|
"chrono": "cpp",
|
||||||
|
"clocale": "cpp",
|
||||||
|
"cmath": "cpp",
|
||||||
|
"compare": "cpp",
|
||||||
|
"concepts": "cpp",
|
||||||
|
"cstdarg": "cpp",
|
||||||
|
"cstddef": "cpp",
|
||||||
|
"cstdint": "cpp",
|
||||||
|
"cstdio": "cpp",
|
||||||
|
"cstdlib": "cpp",
|
||||||
|
"cstring": "cpp",
|
||||||
|
"ctime": "cpp",
|
||||||
|
"cwchar": "cpp",
|
||||||
|
"cwctype": "cpp",
|
||||||
|
"list": "cpp",
|
||||||
|
"map": "cpp",
|
||||||
|
"set": "cpp",
|
||||||
|
"string": "cpp",
|
||||||
|
"unordered_map": "cpp",
|
||||||
|
"vector": "cpp",
|
||||||
|
"exception": "cpp",
|
||||||
|
"algorithm": "cpp",
|
||||||
|
"functional": "cpp",
|
||||||
|
"iterator": "cpp",
|
||||||
|
"memory": "cpp",
|
||||||
|
"memory_resource": "cpp",
|
||||||
|
"optional": "cpp",
|
||||||
|
"random": "cpp",
|
||||||
|
"ratio": "cpp",
|
||||||
|
"string_view": "cpp",
|
||||||
|
"system_error": "cpp",
|
||||||
|
"tuple": "cpp",
|
||||||
|
"type_traits": "cpp",
|
||||||
|
"utility": "cpp",
|
||||||
|
"format": "cpp",
|
||||||
|
"fstream": "cpp",
|
||||||
|
"initializer_list": "cpp",
|
||||||
|
"iomanip": "cpp",
|
||||||
|
"iosfwd": "cpp",
|
||||||
|
"istream": "cpp",
|
||||||
|
"limits": "cpp",
|
||||||
|
"new": "cpp",
|
||||||
|
"numbers": "cpp",
|
||||||
|
"ostream": "cpp",
|
||||||
|
"sstream": "cpp",
|
||||||
|
"stdexcept": "cpp",
|
||||||
|
"streambuf": "cpp",
|
||||||
|
"cinttypes": "cpp",
|
||||||
|
"typeinfo": "cpp",
|
||||||
|
"variant": "cpp",
|
||||||
|
"__bit_reference": "cpp",
|
||||||
|
"__config": "cpp",
|
||||||
|
"__hash_table": "cpp",
|
||||||
|
"__locale": "cpp",
|
||||||
|
"__node_handle": "cpp",
|
||||||
|
"__split_buffer": "cpp",
|
||||||
|
"__threading_support": "cpp",
|
||||||
|
"__tree": "cpp",
|
||||||
|
"__verbose_abort": "cpp",
|
||||||
|
"execution": "cpp",
|
||||||
|
"ios": "cpp",
|
||||||
|
"locale": "cpp"
|
||||||
|
}
|
||||||
|
}
|
||||||
48
CMakeLists.txt
Normal file
48
CMakeLists.txt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
project(UsbMakroBoard VERSION 1.0)
|
||||||
|
|
||||||
|
# Add the executable target
|
||||||
|
add_executable(usbmakroboard
|
||||||
|
src/main.cpp # Add your source files here
|
||||||
|
)
|
||||||
|
|
||||||
|
# Include yaml-cpp as a dependency
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
yaml-cpp
|
||||||
|
GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
|
||||||
|
GIT_TAG master
|
||||||
|
)
|
||||||
|
FetchContent_GetProperties(yaml-cpp)
|
||||||
|
|
||||||
|
if(NOT yaml-cpp_POPULATED)
|
||||||
|
message(STATUS "Fetching yaml-cpp...")
|
||||||
|
FetchContent_Populate(yaml-cpp)
|
||||||
|
add_subdirectory(${yaml-cpp_SOURCE_DIR} ${yaml-cpp_BINARY_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Link yaml-cpp with your executable
|
||||||
|
target_link_libraries(usbmakroboard PUBLIC yaml-cpp::yaml-cpp)
|
||||||
|
|
||||||
|
# Include spdlog as a dependency
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
spdlog
|
||||||
|
GIT_REPOSITORY https://github.com/gabime/spdlog.git
|
||||||
|
GIT_TAG v1.14.1
|
||||||
|
)
|
||||||
|
FetchContent_GetProperties(spdlog)
|
||||||
|
|
||||||
|
if(NOT spdlog_POPULATED)
|
||||||
|
message(STATUS "Fetching spdlog...")
|
||||||
|
FetchContent_Populate(spdlog)
|
||||||
|
add_subdirectory(${spdlog_SOURCE_DIR} ${spdlog_BINARY_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Link spdlog with your executable
|
||||||
|
target_link_libraries(usbmakroboard PRIVATE spdlog::spdlog)
|
||||||
|
|
||||||
|
install(TARGETS usbmakroboard DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
|
||||||
44
README.md
44
README.md
@@ -1,2 +1,46 @@
|
|||||||
# UsbMakroBoard
|
# UsbMakroBoard
|
||||||
|
|
||||||
|
## Kudos
|
||||||
|
|
||||||
|
For info that with "EVIOCGRAB" the keyboard events can be consumed exclusively by one application: https://stackoverflow.com/questions/29942421/read-barcodes-from-input-event-linux-c/29956584#29956584
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
```
|
||||||
|
-c <path to config>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The configuration can be provided by either specifying a config file path via the `-c` parameter. If no parameter is provided, the default config path under `$HOME/.config/UsbMakroBoard.yaml` is used.
|
||||||
|
|
||||||
|
Find the config schema in [schemas/config.schema.json](./schemas/config.schema.json) as well as a default config in [config.yaml](./config.yaml).
|
||||||
|
|
||||||
|
## Allowing non-root access to the device
|
||||||
|
|
||||||
|
By default, accessing input devices requires root privileges. However, it's possible to allow a regular user to access a specific input device by creating a udev rule:
|
||||||
|
|
||||||
|
1. Identify the device's vendor and product IDs. You can do this by running `lsusb` in the terminal and looking for your device.
|
||||||
|
This may looke like this where `195d` is the `vendor_id` and `6008` is the `product_id`:
|
||||||
|
```bash
|
||||||
|
# lsusb
|
||||||
|
Bus 003 Device 005: ID 195d:6008 Itron Technology iONE Falcon 20 RGB
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Create a new udev rule. Open a new file in the `/etc/udev/rules.d/` directory. The filename should end with `.rules`, for example `90-input.rules` (the number determins the order in which the rules are loaded. It is generally recommended to chose higher numbers for changes that are relevant for the user-space).
|
||||||
|
|
||||||
|
In the new file, write a rule that matches your device and sets the mode to `0666` (read and write permissions for everyone). Replace `vendor_id` and `product_id` with your device's IDs from the first step.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SUBSYSTEM=="input", ATTRS{idVendor}=="vendor_id", ATTRS{idProduct}=="product_id", MODE="0666"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Reload the udev rules with the command `sudo udevadm control --reload-rules && sudo udevadm trigger`.
|
||||||
|
|
||||||
|
Now, every time the device is connected, it will be accessible by all users. The rule can be made more specific to only make the device accessible to a certain user or user group. You can use the `OWNER`, `GROUP`, and `MODE` parameters in the udev rule. For example, to restrict access to the user `username` and the group `usergroup`, you can use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SUBSYSTEM=="input", ATTRS{idVendor}=="vendor_id", ATTRS{idProduct}=="product_id", OWNER="username", GROUP="usergroup", MODE="0660"
|
||||||
|
```
|
||||||
|
|
||||||
|
This will give read and write permissions to the user and/or the group, and no permissions to others.
|
||||||
|
|||||||
7
config.yaml
Normal file
7
config.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
logLevel: "trace"
|
||||||
|
devicePath: "/dev/input/by-id/usb-MAX_Falcon_20_RGB-if02-event-kbd"
|
||||||
|
keyboard_layout:
|
||||||
|
row0:
|
||||||
|
keys:
|
||||||
|
key0:
|
||||||
|
script: "/home/tbehrendt/.screenlayout/default.sh"
|
||||||
7
renovate.json
Normal file
7
renovate.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"local>t.behrendt/renovate-configs:common",
|
||||||
|
"local>t.behrendt/renovate-configs:action"
|
||||||
|
]
|
||||||
|
}
|
||||||
43
schemas/config.schema.json
Normal file
43
schemas/config.schema.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"devicePath": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"logLevel": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["trace", "debug", "info", "warn", "error", "critical"],
|
||||||
|
"default": "info"
|
||||||
|
},
|
||||||
|
"keyboard_layout": {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^row[0-4]+": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"keys": {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^key[0-3]+$": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"script": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["script"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["keys"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["keyboard_layout", "devicePath"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
280
src/main.cpp
Normal file
280
src/main.cpp
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
// 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 <cstdlib>
|
||||||
|
#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;
|
||||||
|
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<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