Skip to content

Instantly share code, notes, and snippets.

@baines
Last active September 19, 2023 20:53
Show Gist options
  • Save baines/5a49f1334281b2685af5dcae81a6fa8a to your computer and use it in GitHub Desktop.
Save baines/5a49f1334281b2685af5dcae81a6fa8a to your computer and use it in GitHub Desktop.
simple xlib key example
#include <stdio.h>
#include <locale.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
int main(void){
setlocale(LC_ALL, "");
Display* dpy = XOpenDisplay(NULL);
Window win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 100, 100, 0, 0, 0);
XMapRaised(dpy, win);
XSync(dpy, False);
// X input method setup, only strictly necessary for intl key text
// loads the XMODIFIERS environment variable to see what IME to use
XSetLocaleModifiers("");
XIM xim = XOpenIM(dpy, 0, 0, 0);
if(!xim){
// fallback to internal input method
XSetLocaleModifiers("@im=none");
xim = XOpenIM(dpy, 0, 0, 0);
}
// X input context, you can have multiple for text boxes etc, but having a
// single one is the easiest.
XIC xic = XCreateIC(xim,
XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, win,
XNFocusWindow, win,
NULL);
XSetICFocus(xic);
// we want key presses
XSelectInput(dpy, win, KeyPressMask | KeyReleaseMask);
// main loop
XEvent prev_ev = {}, ev = {};
// you probably want XPending(dpy) here instead of 1, and an outer main loop
// where you do things other than just getting X events
while(1){
XNextEvent(dpy, &ev);
// this is needed for IMEs to hook keypresses is some cases,
// if you need keys even when they're filtered things get a bit more complex
if(XFilterEvent(&ev, None) == True) continue;
switch(ev.type){
case KeyPress: {
// note: if you just want the XK_ keysym, then you can just use
// XLookupKeysym(&ev.xkey, 0);
// and ignore all the XIM / XIC / status etc stuff
Status status;
KeySym keysym = NoSymbol;
char text[32] = {};
// if you want to tell if this was a repeated key, this trick seems reliable.
int is_repeat = prev_ev.type == KeyRelease &&
prev_ev.xkey.time == ev.xkey.time &&
prev_ev.xkey.keycode == ev.xkey.keycode;
// you might want to remove the control modifier, since it makes stuff return control codes
ev.xkey.state &= ~ControlMask;
// get text from the key.
// it could be multiple characters in the case an IME is used.
// if you only care about latin-1 input, you can use XLookupString instead
// and skip all the XIM / XIC setup stuff
Xutf8LookupString(xic, &ev.xkey, text, sizeof(text) - 1, &keysym, &status);
if(status == XBufferOverflow){
// an IME was probably used, and wants to commit more than 32 chars.
// ignore this fairly unlikely case for now
}
if(status == XLookupChars){
// some characters were returned without an associated key,
// again probably the result of an IME
printf("Got chars: (%s)\n", text);
}
if(status == XLookupBoth){
// we got one or more characters with an associated keysym
// (all the keysyms are listed in /usr/include/X11/keysymdef.h)
char* sym_name = XKeysymToString(keysym);
printf("Got both: (%s), (%s)\n", text, sym_name);
}
if(status == XLookupKeySym){
// a key without text on it
char* sym_name = XKeysymToString(keysym);
printf("Got keysym: (%s)\n", sym_name);
}
// example of responding to a key
if(keysym == XK_Escape){
puts("Exiting because escape was pressed.");
return 0;
}
} break;
}
prev_ev = ev;
}
return 0;
}
@Neko-Box-Coder
Copy link

Neko-Box-Coder commented Feb 1, 2023

Xutf8LookupString returns a length, which is more reliable than null termination if you are trying to capture entered characters.
Xutf8LookupString doesn't guarantee to add a null character (at least right after the character entered) for you if it is marked as XLookupBoth.
Meaning it is possible that it could contain garbage characters from this line (Although ultimately depends on the compiler)
char text[32] = {};

This example works because it is just outputting to console (along with the garbage character),
but could potentially create unexpected or undefined results if you are processing the garbage character since it can go below 0.

This happens to me when entering keys like TAB or ESC because it is marked as XLookupBoth
and it epically crashes when I try to convert it to wstring.
The better approach would be something like this:

int len = Xutf8LookupString(xic, &ev.xkey, text, sizeof(text) - 1, &keysym, &status);;
//...
if(status == XLookupBoth){
    //Manually add null terminating char
    text[len] = '\0';
    //Process the text...
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment