Xkb: How to convert a keycode to keysym
I was finally able to figure it out after a lot of trial and error. XKeycodeToKeysym is apparently broken and the index value calculations are not defined for extended indexes.
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <X11/X.h>
#include <X11/XKBlib.h>
KeySym KeyCodeToKeySym(Display * display, KeyCode keycode, unsigned int event_mask) {
KeySym keysym = NoSymbol;
//Get the map
XkbDescPtr keyboard_map = XkbGetMap(display, XkbAllClientInfoMask, XkbUseCoreKbd);
if (keyboard_map) {
//What is diff between XkbKeyGroupInfo and XkbKeyNumGroups?
unsigned char info = XkbKeyGroupInfo(keyboard_map, keycode);
unsigned int num_groups = XkbKeyNumGroups(keyboard_map, keycode);
//Get the group
unsigned int group = 0x00;
switch (XkbOutOfRangeGroupAction(info)) {
case XkbRedirectIntoRange:
/* If the RedirectIntoRange flag is set, the four least significant
* bits of the groups wrap control specify the index of a group to
* which all illegal groups correspond. If the specified group is
* also out of range, all illegal groups map to Group1.
*/
group = XkbOutOfRangeGroupInfo(info);
if (group >= num_groups) {
group = 0;
}
break;
case XkbClampIntoRange:
/* If the ClampIntoRange flag is set, out-of-range groups correspond
* to the nearest legal group. Effective groups larger than the
* highest supported group are mapped to the highest supported group;
* effective groups less than Group1 are mapped to Group1 . For
* example, a key with two groups of symbols uses Group2 type and
* symbols if the global effective group is either Group3 or Group4.
*/
group = num_groups - 1;
break;
case XkbWrapIntoRange:
/* If neither flag is set, group is wrapped into range using integer
* modulus. For example, a key with two groups of symbols for which
* groups wrap uses Group1 symbols if the global effective group is
* Group3 or Group2 symbols if the global effective group is Group4.
*/
default:
if (num_groups != 0) {
group %= num_groups;
}
break;
}
XkbKeyTypePtr key_type = XkbKeyKeyType(keyboard_map, keycode, group);
unsigned int active_mods = event_mask & key_type->mods.mask;
int i, level = 0;
for (i = 0; i < key_type->map_count; i++) {
if (key_type->map[i].active && key_type->map[i].mods.mask == active_mods) {
level = key_type->map[i].level;
}
}
keysym = XkbKeySymEntry(keyboard_map, keycode, level, group);
XkbFreeClientMap(keyboard_map, XkbAllClientInfoMask, true);
}
return keysym;
}
int main(int argc, const char * argv[]) {
Display * display;
//Try to attach to the default X11 display.
display = XOpenDisplay(NULL);
if(display == NULL) {
printf("Error: Could not open display!\n");
return EXIT_FAILURE;
}
KeyCode keycode = 56; // b
unsigned int event_mask = ShiftMask | LockMask;
KeySym keysym = KeyCodeToKeySym(display, keycode, event_mask);
printf("KeySym: %s\n", XKeysymToString(keysym));
//Close the connection to the selected X11 display.
XCloseDisplay(display);
return EXIT_SUCCESS;
}
Replace of XKeycodeToKeysym
Provided XKB
is available then the simplest replacement for XKeycodeToKeysym
is:
#include <X11/XKBlib.h>
/* which declares:
KeySym XkbKeycodeToKeysym(Display *dpy, KeyCode kc,
unsigned int group, unsigned int level); */
... and then the original question's code could become:
mykey = XkbKeycodeToKeysym( display, event.xkey.keycode,
0, event.xkey.state & ShiftMask ? 1 : 0);
notes:
- this chromium bug report discusses both the
XGetKeyboardMapping
and theXKB
replacement solutions. - XkbKeyCodeToKeysym man page on X.org
How to map a X11 KeySym to a Unicode character?
Is there a simple function in X11/Xlib that will map a KeySym to its
Unicode equivalent?
The definitive answer is no
Because Unicode was invented years after Xlib and no one ever went
back to add such a thing? Most of the Xlib API is codeset
independent since it was written in the days when every locale used a
different character set (ISO 8859-*, Big5, JIS, etc.), so you get a
char buffer appropriate to the current locale. There were a few UTF-8
specific additions in later years, but mostly we've been trying to let
Xlib rest in peace since then, pushing new API design towards xcb
instead.
Get scancode rather than keycode on Linux using X11
I had the same problem and I've just found a solution. Let's start with the obvious first.
If you want to get specific keys such as "W" or "4", no matter where they're located, you can just convert the keycode you receive from the event into a KeySym. In this case "W" is XK_W
and XK_w
and "4" is XK_4
(and XK_dollar
on most keyboards).
However, sometimes you want to get keys such as "the nth key of the mth row". You need key names to do that. In this case "W" is AD02
and "4" is AE04
on QWERTY keyboards.
Let's say you are making a game in which the player needs to use the WASD keys to move. If you look for KeySyms it's going to work fine on QWERTY keyboards, but people using other keyboard layouts such as AZERTY, QWERTZ and DVORAK will have trouble. So in this case it's better to use key names.
Using key names is actually pretty easy, but the documentation is very messy (but I still recommend you take a look at it). I had to take a look at GLFW's source code (specifically src/x11_init.c) because I was clueless. This method requires Xkb, but you were already using it so I guess that's no problem.
First you need to get the keyboard map and obtain symbolic names. We only want key names so we use XkbKeyNamesMask
.
#include <X11/XKBlib.h>
XkbDescPtr KbDesc = XkbGetMap(XDisplay, 0, XkbUseCoreKbd);
XkbGetNames(XDisplay, XkbKeyNamesMask, KbDesc);
Then, at the event loop you can use the KbDesc->names->keys array to get the key name for a specific keycode:
XEvent Event;
XNextEvent(XDisplay, &Event);
switch (Event.type)
{
case KeyPress:
/* I'm not sure this 'if' is necessary, but better safe than sorry */
if ((Event.xkey.keycode >= KbDesc->min_key_code) && (Event.xkey.keycode <= KbDesc->max_key_code))
{
/* Copy key name into Name */
char Name[XkbKeyNameLength + 1];
memcpy(Name, KbDesc->names->keys[Event.xkey.keycode].name, XkbKeyNameLength);
Name[XkbKeyNameLength] = '\0'; /* Null terminator */
if (strcmp(Name, "AD02") == 0) /* Is it W (for QWERTY and QWERTZ) / Z (for AZERTY) / comma (for DVORAK) / ц (for Russian) etc... ? */
{
/* Do something... */
}
else if (strcmp(Name, "AE04") == 0) /* Is it 4 (for most keyboards) / whatever's in its place? */
{
/* Do something... */
}
/* ... */
}
/* ... */
}
And that's it. It seems to work pretty well so far. I'd like to mention that special keys have very different key names. For example, Left Shift is LFSH
, Left Control is LCTL
, Space is SPCE
and Escape is ESC
.
I hope it helps.
Related Topics
Why Does This Movq Instruction Work on Linux and Not Osx
Gdb Warning: Loadable Section Not Found in Added Symbol-File System-Supplied Dso at 0X7Ffff7Ffd000
Getmodulehandle(Null) on Linux
Coqide 8.5: No Syntax Highlighting on Linux
Spell Checking a File Using Command Line, Non-Interactively
How to Run Command During Docker Build Which Requires a Tty
How to Set a Dynamic Variable in Haproxy
Perl Fork() Exec() , Child Process Gone Wild
Question About File Seeking Position
Bash Script Variable Scope Issue
Get a Spectrum of Frequencies from Wav/Riff Using Linux Command Line
Rsync --Exclude Not Excluding Specific Files
How to Retrieve Advertising Payload from Ibeacon/Ble
Errors While Installing Opencv and Ffmpeg Through Rightscripts
Installing Octave Package in Ubuntu
Random Number Between Range in Shell