I'd just pick a nonconflicting set of control keys, myself.
I've always preferred the cursor keys over WASD, anyway.
If you do have to use those conflicting keys, then the setxkbmap -print option is definitely acceptable. (You can do the equivalent via X11 XKB extensions, but it requires a lot more code than using setxkbmap.) Here's how I'd do it (note, untested!):
Code:
typedef enum {
LAYOUT_UNKNOWN = 0,
LAYOUT_QWERTY = 1,
LAYOUT_AZERTY = 2,
} keyboard_layout;
static keyboard_layout detect_keyboard_layout(void)
{
char *line = NULL;
size_t size = 0;
ssize_t len;
keyboard_layout result = KEYBOARD_UNKNOWN;
FILE *in;
in = popen("LANG=C LC_ALL=C setxkbmap -print", "rb");
if (!in)
return LAYOUT_UNKNOWN;
while (1) {
len = getline(&line, &size, in);
if (strstr(line, "aliases(qwerty)"))
result = LAYOUT_QWERTY;
if (strstr(line, "aliases(azerty)"))
result = LAYOUT_AZERTY;
}
free(line);
pclose(in);
return result;
}
When you package your program for distribution, include setxkbmap in the required packages. For Ubuntu, that is the x11-xkb-utils package. Normally it is absolutely required by x.org (the X server), so it should be installed anyway, but there's no harm in specifically listing it.
Making the program configurable is not that hard, really. For example, if you save the following as example.c,
Code:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <wordexp.h>
/* Paths where the keyboard configuration is located,
* later ones overriding previous ones.
*/
static const char *const config_paths[] = {
"/etc/yourapp/config",
"~/.yourapp/config",
NULL
};
/* Keyboard controls are defined using
* key <config> <string>
*/
static struct {
const char *const config;
KeySym keysym;
KeyCode keycode;
} control[] = {
/* control[0] */ { "up", XK_W, 0 },
/* control[1] */ { "down", XK_S, 0 },
/* control[2] */ { "left", XK_A, 0 },
/* control[3] */ { "right", XK_S, 0 },
/* control[4] */ { "foo", XK_Q, 0 },
/* control[5] */ { "bar", XK_E, 0 },
/* control[6] */ { "space", XK_space, 0 },
};
#define CONTROL_COUNT (sizeof control / sizeof control[0])
static char *rtrim(char *const s)
{
if (s) {
char *end = NULL;
char *cur = s;
while (*cur)
if (*cur == '\t' || *cur == '\n' || *cur == '\v' ||
*cur == '\f' || *cur == '\r' || *cur == ' ')
++cur;
else
end = ++cur;
if (end)
*end = '\0';
}
return s;
}
static char *spaces(char *const s, char *const none)
{
if (s) {
char *c = s;
while (*c == '\t' || *c == '\n' || *c == '\v' ||
*c == '\f' || *c == '\r' || *c == ' ')
c++;
return (c > s) ? c : none;
} else
return none;
}
void load_controls(Display *const display)
{
char *line = NULL;
size_t size = 0;
size_t p, i, k;
ssize_t n;
for (p = 0; config_paths[p] != NULL; p++) {
wordexp_t alternatives;
if (wordexp(config_paths[p], &alternatives, WRDE_NOCMD))
continue;
for (i = 0; i < alternatives.we_wordc; i++) {
FILE *in;
char *name, *value, *ends;
in = fopen(alternatives.we_wordv[i], "rb");
if (!in)
continue;
while (1) {
n = getline(&line, &size, in);
if (n <= 0)
break;
/* Skip leading whitespace. */
name = spaces(line, line);
/* Require "key". */
if (!(name[0] == 'K' || name[0] == 'k')) continue;
if (!(name[1] == 'E' || name[1] == 'e')) continue;
if (!(name[2] == 'Y' || name[2] == 'y')) continue;
/* Require whitespace. */
name = spaces(name + 3, NULL);
if (!name) continue;
/* Find end of name. */
ends = name + strcspn(name, "\t\n\v\f\r ");
if (ends == name) continue;
/* Find start of value. */
value = spaces(ends, NULL);
if (!value) continue;
/* Terminate name. */
*ends = '\0';
/* Value continues till the end of the line. */
value = rtrim(value);
/* Find if this is a control we know of. */
for (k = 0; k < CONTROL_COUNT; k++)
if (!strcmp(name, control[k].config)) {
KeySym ks;
ks = XStringToKeysym(value);
if (ks == NoSymbol)
continue;
control[k].keysym = ks;
if (display)
control[k].keycode = XKeysymToKeycode(display, ks);
}
}
if (ferror(in)) {
fprintf(stderr, "Warning: Error reading file '%s'.\n", alternatives.we_wordv[i]);
fflush(stderr);
}
if (fclose(in)) {
fprintf(stderr, "Warning: Error closing file '%s'.\n", alternatives.we_wordv[i]);
fflush(stderr);
}
}
wordfree(&alternatives);
}
if (display)
for (k = 0; k < CONTROL_COUNT; k++)
if (!control[k].keycode)
control[k].keycode = XKeysymToKeycode(display, control[k].keysym);
free(line);
}
int main(int argc, char *argv[])
{
Display *display;
size_t k;
display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "No X support.\n");
return EXIT_FAILURE;
}
load_controls(display);
XCloseDisplay(display);
for (k = 0; k < CONTROL_COUNT; k++)
printf("'%s' is symbol 0x%lx, keycode 0x%lx\n", control[k].config, (unsigned long)control[k].keysym, (unsigned long)control[k].keycode);
return EXIT_SUCCESS;
}
and compile it using e.g.
Code:
gcc -Wall -Wextra -O2 $(pkg-config --cflags x11) example.c $(pkg-config --libs x11) -o example
you'll see how to read the keyboard configuration easily from a configuration file (.yourapp/config in the user's home directory). For example, to change the 'foo' key to the left shift, create the directory .yourapp in your home directory, and create there file config with contents
key foo Shift_L
The configuration files are read whenever the program starts, you don't need to recompile it.