199 lines
5.0 KiB
C++
199 lines
5.0 KiB
C++
// gKeyMapper - mappings file parsing/formatting.
|
|
|
|
#include "mapping.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
#include "keys.h"
|
|
|
|
namespace gkeymapper {
|
|
|
|
const char* action_type_token(ActionType type) {
|
|
switch (type) {
|
|
case ActionType::Key:
|
|
return "key";
|
|
case ActionType::Command:
|
|
return "command";
|
|
case ActionType::KeyHold:
|
|
return "keyhold";
|
|
case ActionType::Disabled:
|
|
default:
|
|
return "disabled";
|
|
}
|
|
}
|
|
|
|
ActionType action_type_from_token(const std::string& token) {
|
|
if (token == "key") {
|
|
return ActionType::Key;
|
|
}
|
|
if (token == "command") {
|
|
return ActionType::Command;
|
|
}
|
|
if (token == "keyhold") {
|
|
return ActionType::KeyHold;
|
|
}
|
|
return ActionType::Disabled;
|
|
}
|
|
|
|
std::string format_mappings(const MappingSet& set) {
|
|
std::string out = "# gKeyMapper mappings (G1..G9)\n";
|
|
for (int i = 0; i < kNumKeys; ++i) {
|
|
Mapping mapping;
|
|
if (i < static_cast<int>(set.keys.size())) {
|
|
mapping = set.keys[i];
|
|
}
|
|
out += "G";
|
|
out += std::to_string(i + 1);
|
|
out += "=";
|
|
out += action_type_token(mapping.type);
|
|
out += ":";
|
|
out += mapping.target;
|
|
out += "\n";
|
|
}
|
|
return out;
|
|
}
|
|
|
|
MappingSet parse_mappings(const std::string& text) {
|
|
MappingSet set;
|
|
set.keys.assign(kNumKeys, Mapping{});
|
|
|
|
std::istringstream in(text);
|
|
std::string line;
|
|
while (std::getline(in, line)) {
|
|
if (line.empty() || line[0] == '#') {
|
|
continue;
|
|
}
|
|
auto eq = line.find('=');
|
|
if (eq == std::string::npos) {
|
|
continue;
|
|
}
|
|
std::string key = line.substr(0, eq);
|
|
if (key.size() < 2 || key[0] != 'G') {
|
|
continue;
|
|
}
|
|
int n = 0;
|
|
try {
|
|
n = std::stoi(key.substr(1)) - 1;
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
if (n < 0 || n >= kNumKeys) {
|
|
continue;
|
|
}
|
|
|
|
std::string rest = line.substr(eq + 1);
|
|
auto col = rest.find(':');
|
|
std::string typeToken = (col == std::string::npos) ? rest : rest.substr(0, col);
|
|
std::string target = (col == std::string::npos) ? std::string() : rest.substr(col + 1);
|
|
|
|
set.keys[n].type = action_type_from_token(typeToken);
|
|
set.keys[n].target = target;
|
|
}
|
|
return set;
|
|
}
|
|
|
|
bool load_mappings_file(const std::string& path, MappingSet& out) {
|
|
std::ifstream in(path);
|
|
if (!in) {
|
|
return false;
|
|
}
|
|
std::stringstream buffer;
|
|
buffer << in.rdbuf();
|
|
out = parse_mappings(buffer.str());
|
|
return true;
|
|
}
|
|
|
|
bool save_mappings_file(const std::string& path, const MappingSet& set) {
|
|
// Write a temporary file first and rename it into place: a reader (the
|
|
// daemon) either sees the old or the new file, never a half-written one.
|
|
const std::string tempPath = path + ".tmp";
|
|
{
|
|
std::ofstream out(tempPath);
|
|
if (!out) {
|
|
return false;
|
|
}
|
|
out << format_mappings(set);
|
|
out.flush();
|
|
if (!out) {
|
|
return false;
|
|
}
|
|
}
|
|
return std::rename(tempPath.c_str(), path.c_str()) == 0;
|
|
}
|
|
|
|
std::string default_config_path() {
|
|
const char* home = std::getenv("HOME");
|
|
return std::string(home ? home : "") + "/.config/gKeyMapper/mappings.conf";
|
|
}
|
|
|
|
std::string format_shortcut_step(unsigned modifiers, const std::string& key_name) {
|
|
std::string step;
|
|
if ((modifiers & kModCtrl) != 0) {
|
|
step += "ctrl+";
|
|
}
|
|
if ((modifiers & kModAlt) != 0) {
|
|
step += "alt+";
|
|
}
|
|
if ((modifiers & kModShift) != 0) {
|
|
step += "shift+";
|
|
}
|
|
if ((modifiers & kModSuper) != 0) {
|
|
step += "super+";
|
|
}
|
|
if ((modifiers & kModAltGr) != 0) {
|
|
step += "altgr+";
|
|
}
|
|
step += key_name;
|
|
return step;
|
|
}
|
|
|
|
bool parse_shortcut(const std::string& text, std::vector<ShortcutStep>& out,
|
|
const KeyResolver& resolve) {
|
|
out.clear();
|
|
if (text.empty()) {
|
|
return false;
|
|
}
|
|
|
|
std::istringstream combos(text);
|
|
std::string combo;
|
|
while (std::getline(combos, combo, ',')) {
|
|
if (combo.empty()) {
|
|
return false;
|
|
}
|
|
|
|
ShortcutStep step;
|
|
bool haveKey = false;
|
|
|
|
std::istringstream parts(combo);
|
|
std::string part;
|
|
while (std::getline(parts, part, '+')) {
|
|
if (part.empty()) {
|
|
return false;
|
|
}
|
|
unsigned mod = modifier_bit(part);
|
|
if (mod != kModNone) {
|
|
step.modifiers |= mod;
|
|
continue;
|
|
}
|
|
int code = resolve ? resolve(part) : linux_key_code(part);
|
|
if (code < 0) {
|
|
return false; // unknown key name
|
|
}
|
|
step.key_name = part;
|
|
step.linux_key = code;
|
|
haveKey = true;
|
|
}
|
|
|
|
if (!haveKey) {
|
|
return false; // combo without an actual key
|
|
}
|
|
out.push_back(step);
|
|
}
|
|
return !out.empty();
|
|
}
|
|
|
|
} // namespace gkeymapper
|