// gKeyMapper - keyboard layout lookups (xkbcommon). #include "keymap.h" #include #include #include #include #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(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(std::tolower(static_cast(c))); } return name; } } // namespace gkeymapper