Skip to content

Instantly share code, notes, and snippets.

@amurzeau
Created September 18, 2023 19:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amurzeau/c4129d6de19d6d615d9c4bee8f38638d to your computer and use it in GitHub Desktop.
Save amurzeau/c4129d6de19d6d615d9c4bee8f38638d to your computer and use it in GitHub Desktop.
Iterate all XKB keyboard layouts and test Qt with them
cmake_minimum_required(VERSION 3.16)
project(xlibdump LANGUAGES CXX C)
add_executable(xkbdump main.cpp)
target_link_libraries(xkbdump xkbcommon xkbregistry xkbfile X11 Qt::Core Qt::XcbQpaPrivate)
// Required packages on Debian:
// libxkbcommon-tools libx11-dev x11proto-dev libxkbcommon-dev libxkbregistry-dev libxkbfile-dev
// Compile that file inside the Qt source tree to get private headers and to test modified Qt code
// or outside Qt using this package (but this will need adjustments to CMakeLists.txt):
// qt6-base-private-dev
#include <stdio.h>
#include <QChar>
#include <QString>
#include <private/qxkbcommon_p.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <X11/keysym.h>
#include <X11/extensions/XKBgeom.h>
#include <X11/extensions/XKBrules.h>
#include <X11/extensions/XKBstr.h>
#include <string>
#include <set>
#include <unordered_map>
#include "xkbcommon/xkbregistry.h"
#include "xkbcommon/xkbcommon.h"
void printAssociatedKeySymWithKeyCode(XkbDescPtr descPtr, unsigned int keycode) {
XkbSymMapPtr symMapRec = &descPtr->map->key_sym_map[keycode];
printf("keysym[0x%x]: ", keycode);
for(size_t keysymIndex = 0; keysymIndex < symMapRec->width; keysymIndex++) {
KeySym keysym = descPtr->map->syms[symMapRec->offset + keysymIndex];
const char* keysymStr = XKeysymToString(keysym);
char unicodeBuffer[32];
if(xkb_keysym_to_utf8(keysym, unicodeBuffer, sizeof(unicodeBuffer)) <= 0) {
unicodeBuffer[0] = 0;
}
bool isCombining = false;
bool isRTL = false;
if(unicodeBuffer[0] != 0) {
QChar c = QString::fromUtf8(unicodeBuffer).at(0);
if(c.category() <= QChar::Mark_Enclosing)
isCombining = true;
if(c.direction() == QChar::DirR || c.direction() == QChar::DirAL || c.direction() == QChar::DirAN)
isRTL = true;
}
printf("%s (\"%s%s%s\", 0x%x), ",
keysymStr,
isCombining ? "◌" : "",
unicodeBuffer,
isRTL ? "\u200E" : "", // Reset to LTR for proper ordering of mixed directions characters
keysym);
}
printf("\n");
}
void printKeyboardLayoutIfNeeded(bool* layoutShown, const std::string& layoutIdentifier, const std::string& description) {
if(!*layoutShown) {
*layoutShown = true;
printf("Keyboard layout %s (%s)\n", layoutIdentifier.c_str(), description.c_str());
}
}
int main() {
struct rxkb_context *ctx = NULL;
struct rxkb_model *m;
struct rxkb_layout *l;
struct rxkb_option_group *g;
ctx = rxkb_context_new(RXKB_CONTEXT_NO_FLAGS);
if (!rxkb_context_parse(ctx, "evdev")) {
fprintf(stderr, "Failed to parse XKB descriptions.\n");
return 1;
}
Display* dpy = XOpenDisplay(getenv("DISPLAY"));
char c[] = "C";
char evdev[] = "/usr/share/X11/xkb/rules/evdev";
XkbRF_RulesPtr rules = XkbRF_Load (evdev, c, True, True);
std::string model = "pc105";
std::set<std::string> processedLayouts;
std::unordered_map<std::string, size_t> unicodeToKeyCode;
std::unordered_map<int, size_t> qtKeyToKeyCode;
XkbDescPtr descEnglishPtr = nullptr;
l = rxkb_layout_first(ctx);
// Iterate keyboard layouts
while (l) {
std::string variant = rxkb_layout_get_variant(l) ?: "";
std::string layout = rxkb_layout_get_name(l) ?: "";
std::string description = rxkb_layout_get_description(l) ?: "";
std::string layoutIdentifier = layout + "+" + variant;
if(processedLayouts.count(layoutIdentifier) > 0) {
l = rxkb_layout_next(l);
continue;
}
processedLayouts.insert(layoutIdentifier);
unicodeToKeyCode.clear();
qtKeyToKeyCode.clear();
model = "pc105";
XkbComponentNamesRec rnames = {0};
XkbRF_VarDefsRec rdefs = {0};
rdefs.model = &model[0];
rdefs.layout = &layout[0];
rdefs.variant = &variant[0];
rdefs.options = NULL;
XkbRF_GetComponents (rules, &rdefs, &rnames);
XkbDescPtr descPtr = XkbGetKeyboardByName(dpy, XkbUseCoreKbd, &rnames, XkbGBN_AllComponentsMask, XkbGBN_KeyNamesMask, 0);
bool layoutShown = false;
if(!descPtr) {
continue;
}
if(descEnglishPtr == nullptr) {
descEnglishPtr = descPtr;
}
// Iterate physical keys (keycode)
for(size_t keycode = descPtr->min_key_code+1; keycode < descPtr->max_key_code+1; keycode++) {
XkbSymMapPtr symMapRec = &descPtr->map->key_sym_map[keycode];
//printf("keysym[0x%x]: ", keycode);
// Iterate keysyms associated with the physical key
for(size_t keysymIndex = 0; keysymIndex < symMapRec->width; keysymIndex++) {
KeySym keysym = descPtr->map->syms[symMapRec->offset + keysymIndex];
const char* keysymStr = XKeysymToString(keysym);
// Get the unicode string for the keysym
char unicodeBuffer[32];
if(xkb_keysym_to_utf8(keysym, unicodeBuffer, sizeof(unicodeBuffer)) <= 0) {
unicodeBuffer[0] = 0;
}
//printf("%s (\"%s\", 0x%x), ", keysymStr, unicodeBuffer, keysym);
// Do check only for the first keysym of a key (this is the keysym when pressing the key without modifiers)
// and only when the associated unicode value is not empty (it is empty for non-printable keys)
if(keysymIndex == 0 && unicodeBuffer[0] != 0) {
bool isAlreadyMappedLetter = false;
QChar c = QString::fromUtf8(unicodeBuffer).at(0);
// We do a uniqueness check, so skip numbers which are also on the keypad
if(c.isLetter()) {
unsigned int qtcode = QXkbCommon::keysymToQtKey(keysym, Qt::NoModifier);
if(qtcode == Qt::Key_unknown) {
printKeyboardLayoutIfNeeded(&layoutShown, layoutIdentifier, description);
printf("Qt::Key_unknown for keysym 0x%x (%s)\n", keysym, keysymStr);
} else {
// Check in the map that we didn't encountered that Qt::Key already
auto it = qtKeyToKeyCode.find(qtcode);
if(it != qtKeyToKeyCode.end()) {
if(keycode != it->second) {
printKeyboardLayoutIfNeeded(&layoutShown, layoutIdentifier, description);
printf("Got Qt::Key 0x%x for keysym %s (0x%x), keycode 0x%x but already got the same Qt::Key for keycode 0x%x\n",
(unsigned int)qtcode,
keysymStr,
keysym,
(unsigned int)keycode,
(unsigned int)it->second);
printAssociatedKeySymWithKeyCode(descPtr, keycode);
printAssociatedKeySymWithKeyCode(descPtr, it->second);
printf("English mappings:\n");
printAssociatedKeySymWithKeyCode(descEnglishPtr, keycode);
printAssociatedKeySymWithKeyCode(descEnglishPtr, it->second);
printf("\n");
}
}
qtKeyToKeyCode.emplace(qtcode, keycode);
}
}
}
}
//printf("\n");
}
l = rxkb_layout_next(l);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment