135 lines
3.4 KiB
C++
135 lines
3.4 KiB
C++
// 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
|