Files
gKeyMapper/shared/keymap.cpp

135 lines
3.4 KiB
C++
Raw Normal View History

2026-10-17 03:53:51 +02:00
// gKeyMapper - keyboard layout lookups (xkbcommon).
#include "keymap.h"
#include <cctype>
#include <cstdio>
#include <fstream>
#include <xkbcommon/xkbcommon.h>
#include "keys.h"
namespace gkeymapper {
namespace {
// xkbcommon uses X11 keycodes, which are the evdev codes offset by 8.
constexpr int kXkbKeycodeOffset = 8;
// Reads XKBLAYOUT from /etc/default/keyboard, e.g. "de".
std::string read_layout_name() {
std::ifstream in("/etc/default/keyboard");
std::string line;
while (std::getline(in, line)) {
const std::string key = "XKBLAYOUT=";
auto pos = line.find(key);
if (pos == std::string::npos) {
continue;
}
std::string value = line.substr(pos + key.size());
if (!value.empty() && value.front() == '"') {
value.erase(0, 1);
}
if (!value.empty() && value.back() == '"') {
value.pop_back();
}
return value;
}
return std::string();
}
} // namespace
Keymap::~Keymap() {
if (state_) {
xkb_state_unref(state_);
}
if (keymap_) {
xkb_keymap_unref(keymap_);
}
}
bool Keymap::init() {
layoutName_ = read_layout_name();
if (layoutName_.empty()) {
std::fprintf(stderr, "gkeymapper: no XKBLAYOUT, using US key positions\n");
return false;
}
xkb_context* context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if (!context) {
return false;
}
xkb_rule_names names{};
names.rules = "evdev";
names.model = "pc105";
names.layout = layoutName_.c_str();
names.variant = "";
names.options = "";
keymap_ = xkb_keymap_new_from_names(context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS);
xkb_context_unref(context);
if (!keymap_) {
std::fprintf(stderr, "gkeymapper: unknown layout '%s', using US key positions\n",
layoutName_.c_str());
return false;
}
state_ = xkb_state_new(keymap_);
if (!state_) {
return false;
}
return true;
}
int Keymap::key_code(const std::string& name) const {
if (!state_) {
return linux_key_code(name);
}
xkb_keysym_t keysym =
xkb_keysym_from_name(name.c_str(), XKB_KEYSYM_CASE_INSENSITIVE);
if (keysym != XKB_KEY_NoSymbol) {
for (xkb_keycode_t code = xkb_keymap_min_keycode(keymap_);
code <= xkb_keymap_max_keycode(keymap_); ++code) {
if (xkb_state_key_get_one_sym(state_, code) == keysym) {
int evdev = static_cast<int>(code) - kXkbKeycodeOffset;
if (evdev > 0) {
return evdev;
}
}
}
}
// Not present on this layout (e.g. "semicolon" on a German keyboard) or
// unknown: fall back to the US position.
return linux_key_code(name);
}
std::string Keymap::base_key_name(unsigned int keycode) const {
if (!state_) {
return std::string();
}
xkb_keysym_t keysym = xkb_state_key_get_one_sym(state_, keycode);
if (keysym == XKB_KEY_NoSymbol) {
return std::string();
}
char buffer[64];
int written = xkb_keysym_get_name(keysym, buffer, sizeof(buffer));
if (written <= 0) {
return std::string();
}
std::string name(buffer);
for (char& c : name) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
return name;
}
} // namespace gkeymapper