Created
September 18, 2023 19:10
-
-
Save amurzeau/c4129d6de19d6d615d9c4bee8f38638d to your computer and use it in GitHub Desktop.
Iterate all XKB keyboard layouts and test Qt with them
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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