Files

534 lines
23 KiB
C

#define _DEFAULT_SOURCE
#include "native.h"
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <linux/input-event-codes.h>
#include <poll.h>
#include <pty.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
int terminal_pty(int *master, int *slave, int rows, int cols) {
struct winsize size = {.ws_row = rows, .ws_col = cols};
if (openpty(master, slave, NULL, NULL, &size) < 0) return -errno;
fcntl(*master, F_SETFD, FD_CLOEXEC);
fcntl(*slave, F_SETFD, FD_CLOEXEC);
return 0;
}
int terminal_raw(int fd, struct termios *saved, int *flags) {
if (tcgetattr(fd, saved) < 0) return -errno;
*flags = fcntl(fd, F_GETFL);
if (*flags < 0) return -errno;
struct termios raw = *saved;
cfmakeraw(&raw);
if (tcsetattr(fd, TCSANOW, &raw) < 0) return -errno;
if (fcntl(fd, F_SETFL, *flags | O_NONBLOCK) < 0) {
int error = errno;
tcsetattr(fd, TCSANOW, saved);
return -error;
}
return 0;
}
int terminal_restore(int fd, struct termios *saved, int flags) {
int result = 0;
if (tcsetattr(fd, TCSANOW, saved) < 0) result = -errno;
if (fcntl(fd, F_SETFL, flags) < 0 && !result) result = -errno;
return result;
}
int terminal_size(int fd, int *rows, int *cols) {
struct winsize size;
if (ioctl(fd, TIOCGWINSZ, &size) < 0) return -errno;
*rows = size.ws_row;
*cols = size.ws_col;
return 0;
}
int terminal_resize(int fd, int rows, int cols) {
struct winsize size = {.ws_row = rows, .ws_col = cols};
return ioctl(fd, TIOCSWINSZ, &size) < 0 ? -errno : 0;
}
int terminal_wait(int master, int input, int output, int xfd, int wake, int writing, int output_pending) {
struct pollfd fds[] = {
{.fd=output_pending && !writing ? -1 : master,
.events=(output_pending ? 0 : POLLIN) | (writing ? POLLOUT : 0)},
{.fd=writing ? -1 : input, .events=POLLIN}, {.fd=xfd, .events=POLLIN},
{.fd=wake, .events=POLLIN},
{.fd=output_pending ? output : -1, .events=POLLOUT}
};
int result;
do { result = poll(fds, 5, -1); } while (result < 0 && errno == EINTR);
if (result < 0) return -errno;
if (fds[3].revents) return 1;
if (fds[2].revents & (POLLERR | POLLHUP | POLLNVAL)) return -EPIPE;
return (fds[0].revents || fds[4].revents ? 2 : 0) |
(fds[1].revents || fds[2].revents || (fds[0].revents & POLLOUT) ? 4 : 0);
}
static int terminal_property(VTermProp prop, VTermValue *value, void *user) {
(void)value;
if (prop == VTERM_PROP_ALTSCREEN) (*(uint64_t *)user)++;
return 1;
}
static const VTermScreenCallbacks screen_callbacks = {.settermprop=terminal_property};
VTerm *terminal_vterm(int rows, int cols) {
VTerm *vt = vterm_new(rows, cols);
if (!vt) return NULL;
vterm_set_utf8(vt, 1);
VTermScreen *screen = vterm_obtain_screen(vt);
vterm_screen_enable_altscreen(screen, 1);
vterm_screen_reset(screen, 1);
uint64_t *generation = calloc(1, sizeof(*generation));
if (!generation) { vterm_free(vt); return NULL; }
vterm_screen_set_callbacks(screen, &screen_callbacks, generation);
return vt;
}
void terminal_vterm_free(VTerm *vt) {
free(vterm_screen_get_cbdata(vterm_obtain_screen(vt)));
vterm_free(vt);
}
uint64_t terminal_generation(VTerm *vt) {
return *(uint64_t *)vterm_screen_get_cbdata(vterm_obtain_screen(vt));
}
static uint32_t cell_color(VTermScreen *screen, VTermColor color) {
uint32_t defaults = color.type & (VTERM_COLOR_DEFAULT_FG | VTERM_COLOR_DEFAULT_BG);
// Match screen.ColorDefault/ColorIndexed; preserve palette identity rather
// than guessing a named color from its current RGB value.
if (defaults) return UINT32_C(1) << 31;
if (VTERM_COLOR_IS_INDEXED(&color)) return (UINT32_C(1) << 30) | color.indexed.idx;
vterm_screen_convert_color_to_rgb(screen, &color);
return (color.rgb.red << 16) | (color.rgb.green << 8) | color.rgb.blue;
}
void terminal_cells(VTerm *vt, uint32_t *cells, TerminalAttribute *attributes, int count, int *x, int *y) {
int rows, cols;
vterm_get_size(vt, &rows, &cols);
VTermScreen *screen = vterm_obtain_screen(vt);
vterm_screen_flush_damage(screen);
for (int i = 0; i < count && i < rows * cols; i++) {
VTermScreenCell cell;
memset(&cell, 0, sizeof(cell));
vterm_screen_get_cell(screen, (VTermPos){.row=i/cols, .col=i%cols}, &cell);
// A wide glyph's continuation occupies one cell, without repeating it.
cells[i] = cell.chars[0] && cell.chars[0] != (uint32_t)-1 ? cell.chars[0] : ' ';
attributes[i] = (TerminalAttribute){
.foreground = cell_color(screen, cell.fg),
.background = cell_color(screen, cell.bg),
.style = cell.attrs.bold | (cell.attrs.underline << 1) |
(cell.attrs.italic << 3) | (cell.attrs.blink << 4) |
(cell.attrs.reverse << 5) | (cell.attrs.strike << 6)
};
}
VTermPos cursor;
vterm_state_get_cursorpos(vterm_obtain_state(vt), &cursor);
*x = cursor.col;
*y = cursor.row;
}
int terminal_key(VTerm *vt, const TerminalEvent *event, char *bytes, int size) {
VTermKey key = VTERM_KEY_NONE;
switch (event->symbol) {
case XK_Return: key=VTERM_KEY_ENTER; break;
case XK_Tab: case XK_ISO_Left_Tab: key=VTERM_KEY_TAB; break;
case XK_BackSpace: key=VTERM_KEY_BACKSPACE; break;
case XK_Escape: key=VTERM_KEY_ESCAPE; break;
case XK_Up: key=VTERM_KEY_UP; break;
case XK_Down: key=VTERM_KEY_DOWN; break;
case XK_Left: key=VTERM_KEY_LEFT; break;
case XK_Right: key=VTERM_KEY_RIGHT; break;
case XK_Insert: key=VTERM_KEY_INS; break;
case XK_Delete: key=VTERM_KEY_DEL; break;
case XK_Home: key=VTERM_KEY_HOME; break;
case XK_End: key=VTERM_KEY_END; break;
case XK_Page_Up: key=VTERM_KEY_PAGEUP; break;
case XK_Page_Down: key=VTERM_KEY_PAGEDOWN; break;
case XK_KP_Enter: key=VTERM_KEY_KP_ENTER; break;
case XK_KP_Insert: key=VTERM_KEY_INS; break;
case XK_KP_Delete: key=VTERM_KEY_DEL; break;
case XK_KP_Home: key=VTERM_KEY_HOME; break;
case XK_KP_End: key=VTERM_KEY_END; break;
case XK_KP_Up: key=VTERM_KEY_UP; break;
case XK_KP_Down: key=VTERM_KEY_DOWN; break;
case XK_KP_Left: key=VTERM_KEY_LEFT; break;
case XK_KP_Right: key=VTERM_KEY_RIGHT; break;
case XK_KP_Page_Up: key=VTERM_KEY_PAGEUP; break;
case XK_KP_Page_Down: key=VTERM_KEY_PAGEDOWN; break;
default:
if (event->symbol >= XK_F1 && event->symbol <= XK_F35)
key = VTERM_KEY_FUNCTION(event->symbol - XK_F1 + 1);
}
// Output queries are answered by the outer terminal. Never send a second
// response from the reading emulator to the child.
while (vterm_output_read(vt, bytes, size)) {}
if (key != VTERM_KEY_NONE) {
VTermModifier modifiers = VTERM_MOD_NONE;
if (event->state & ShiftMask) modifiers |= VTERM_MOD_SHIFT;
if (event->state & ControlMask) modifiers |= VTERM_MOD_CTRL;
if (event->state & Mod1Mask) modifiers |= VTERM_MOD_ALT;
vterm_keyboard_key(vt, key, modifiers);
return vterm_output_read(vt, bytes, size);
}
int length = event->length < size ? event->length : size;
memcpy(bytes, event->text, length);
return length;
}
struct TerminalX { Display *display; Window window; unsigned int numlock; int grabbed; XIM im; XIC ic; int scriptKeys[KEY_MAX+1]; int scriptKeyCount; unsigned char grabbedKeys[256]; };
static int x_error;
static int trap_error(Display *display, XErrorEvent *event) {
(void)display;
x_error = event->error_code;
return 0;
}
static XErrorHandler begin_trap(Display *display) {
XSync(display, False);
x_error = 0;
return XSetErrorHandler(trap_error);
}
static int end_trap(Display *display, XErrorHandler previous) {
XSync(display, False);
XSetErrorHandler(previous);
return x_error;
}
static void observe_windows(TerminalX *x, Window window, int depth, int *remaining) {
if (depth > 16 || --*remaining < 0) { x_error = BadAlloc; return; }
// Xterm's WINDOWID can be its outer window while ordinary keys are
// delivered to the VT widget below it. Observe the whole target subtree.
XSelectInput(x->display, window, KeyPressMask | KeyReleaseMask |
FocusChangeMask | StructureNotifyMask | SubstructureNotifyMask);
Window root, parent, *children=NULL;
unsigned int count;
if (XQueryTree(x->display, window, &root, &parent, &children, &count)) {
for (unsigned int i=0; i<count && *remaining>0; i++)
observe_windows(x, children[i], depth+1, remaining);
}
if (children) XFree(children);
}
static void collect_grabs(TerminalX *x, const int *keys, int count, unsigned char wanted[256]) {
x->numlock = 0;
XModifierKeymap *modifiers = XGetModifierMapping(x->display);
KeyCode numlock = XKeysymToKeycode(x->display, XK_Num_Lock);
if (modifiers) {
for (int mask=0; mask<8; mask++)
for (int k=0; k<modifiers->max_keypermod; k++)
if (numlock && modifiers->modifiermap[mask*modifiers->max_keypermod+k] == numlock)
x->numlock |= 1u << mask;
XFreeModifiermap(modifiers);
}
KeySym fixedKeys[] = {XK_Insert,
XK_KP_Insert, XK_KP_End, XK_KP_Down, XK_KP_Page_Down, XK_KP_Left,
XK_KP_Begin, XK_KP_Right, XK_KP_Home, XK_KP_Up, XK_KP_Page_Up,
XK_KP_Delete, XK_KP_Enter, XK_KP_Add, XK_KP_Subtract, XK_KP_Divide,
XK_Control_L, XK_Control_R};
for (unsigned int i=0; i<sizeof(fixedKeys)/sizeof(fixedKeys[0]); i++) {
KeyCode code = XKeysymToKeycode(x->display, fixedKeys[i]);
if (code) wanted[code] = 1;
}
// Script modifiers may be any Linux key, not just Compose. Resolve through
// the same symbol translation as event routing instead of assuming that
// the X server always uses evdev keycodes plus eight.
int first, last;
XDisplayKeycodes(x->display, &first, &last);
for (int code=first; code<=last; code++) {
KeySym symbol = XkbKeycodeToKeysym(x->display, code, 0, 0);
int key = terminal_key_code(symbol, code);
for (int i=0; i<count; i++) {
if (key == keys[i]) {
wanted[code] = 1;
break;
}
}
}
}
static int replace_grabs(TerminalX *x, const int *keys, int count, int idleOnly) {
if (count < 0 || count > KEY_MAX+1) return -EINVAL;
// Freeze other clients only for the bounded grab transaction. This closes
// the gap between the idle check, adding grabs and switching key sets.
XGrabServer(x->display);
XSync(x->display, False);
if (idleOnly) {
char down[32];
XQueryKeymap(x->display, down);
int busy = XPending(x->display) != 0;
for (int i=0; i<32; i++) busy |= down[i] != 0;
if (busy) { XUngrabServer(x->display); XFlush(x->display); return 1; }
}
unsigned char wanted[256] = {0};
XErrorHandler previous = begin_trap(x->display);
collect_grabs(x, keys, count, wanted);
for (int code=0; code<256; code++) {
if (wanted[code] && !x->grabbedKeys[code])
XGrabKey(x->display, code, AnyModifier, x->window, False, GrabModeAsync, GrabModeSync);
}
int error = end_trap(x->display, previous);
if (error) {
previous = begin_trap(x->display);
for (int code=0; code<256; code++) {
if (wanted[code] && !x->grabbedKeys[code])
XUngrabKey(x->display, code, AnyModifier, x->window);
}
end_trap(x->display, previous);
} else {
for (int code=0; code<256; code++) {
if (x->grabbedKeys[code] && !wanted[code])
XUngrabKey(x->display, code, AnyModifier, x->window);
}
memcpy(x->grabbedKeys, wanted, sizeof(wanted));
x->scriptKeyCount = count;
for (int i=0; i<count; i++) x->scriptKeys[i] = keys[i];
}
XUngrabServer(x->display);
XSync(x->display, False);
return error ? -error : 0;
}
static void install_grabs(TerminalX *x) {
unsigned char wanted[256] = {0};
collect_grabs(x, x->scriptKeys, x->scriptKeyCount, wanted);
for (int code=0; code<256; code++) {
if (wanted[code]) XGrabKey(x->display, code, AnyModifier, x->window, False, GrabModeAsync, GrabModeSync);
}
memcpy(x->grabbedKeys, wanted, sizeof(wanted));
}
int terminal_x_caps_lock(TerminalX *x, int enabled) {
if (!XkbLockModifiers(x->display, XkbUseCoreKbd, LockMask, enabled ? LockMask : 0)) return -EIO;
XSync(x->display, False);
return 0;
}
int terminal_x_toggle_caps(TerminalX *x) {
XkbStateRec state;
if (XkbGetState(x->display, XkbUseCoreKbd, &state) != Success) return -EIO;
return terminal_x_caps_lock(x, !(state.locked_mods & LockMask));
}
int terminal_x_script_keys(TerminalX *x, const int *keys, int count) {
return replace_grabs(x, keys, count, 0);
}
int terminal_x_update_keys(TerminalX *x, const int *keys, int count) {
return replace_grabs(x, keys, count, 1);
}
TerminalX *terminal_x_open(unsigned long window, int *error) {
TerminalX *x = calloc(1, sizeof(*x));
if (!x) { *error = ENOMEM; return NULL; }
x->display = XOpenDisplay(NULL);
if (!x->display) { *error = ENXIO; free(x); return NULL; }
x->window = window;
XErrorHandler previous = begin_trap(x->display);
XWindowAttributes attributes;
if (!XGetWindowAttributes(x->display, window, &attributes) ||
attributes.map_state != IsViewable || window == attributes.root) {
end_trap(x->display, previous);
*error = BadWindow;
terminal_x_close(x);
return NULL;
}
// Observing ordinary keys does not consume them. Only passive grabs and
// an active reader chord withhold events from xterm.
int remaining = 1024;
observe_windows(x, window, 0, &remaining);
install_grabs(x);
Bool supported;
XkbSetDetectableAutoRepeat(x->display, True, &supported);
if (!supported) x_error = BadImplementation;
int failure = end_trap(x->display, previous);
if (failure) { *error=failure; terminal_x_close(x); return NULL; }
setlocale(LC_CTYPE, "");
XSetLocaleModifiers("");
x->im = XOpenIM(x->display, NULL, NULL, NULL);
if (x->im) x->ic = XCreateIC(x->im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, window, XNFocusWindow, window, NULL);
return x;
}
void terminal_x_release(TerminalX *x) {
XUngrabKeyboard(x->display, CurrentTime);
XAllowEvents(x->display, AsyncKeyboard, CurrentTime);
XFlush(x->display);
x->grabbed = 0;
}
void terminal_x_close(TerminalX *x) {
if (!x) return;
terminal_x_release(x);
XErrorHandler previous = begin_trap(x->display);
XUngrabKey(x->display, AnyKey, AnyModifier, x->window);
end_trap(x->display, previous);
if (x->ic) XDestroyIC(x->ic);
if (x->im) XCloseIM(x->im);
XCloseDisplay(x->display);
free(x);
}
int terminal_x_fd(TerminalX *x) { return ConnectionNumber(x->display); }
int terminal_x_pending(TerminalX *x) { return XPending(x->display); }
int terminal_x_down(TerminalX *x, int code) {
char keys[32];
XQueryKeymap(x->display, keys);
return (unsigned char)keys[code/8] & (1u << (code%8));
}
int terminal_x_keys_down(TerminalX *x) {
char keys[32];
XQueryKeymap(x->display, keys);
for (int i=0; i<32; i++) if (keys[i]) return 1;
return 0;
}
int terminal_x_state(TerminalX *x, int *active, int *numlock, int *capslock) {
XErrorHandler previous = begin_trap(x->display);
Window focus;
int revert;
XGetInputFocus(x->display, &focus, &revert);
*active = 0;
for (int depth=0; focus != None && focus != PointerRoot && depth<64; depth++) {
if (focus == x->window) { *active=1; break; }
Window root, parent, *children=NULL;
unsigned int count;
if (!XQueryTree(x->display, focus, &root, &parent, &children, &count)) break;
if (children) XFree(children);
if (focus == parent || focus == root) break;
focus = parent;
}
XkbStateRec state;
int haveState = XkbGetState(x->display, XkbUseCoreKbd, &state) == Success;
*numlock = haveState && (state.mods & x->numlock);
*capslock = haveState && (state.locked_mods & LockMask);
int error = end_trap(x->display, previous);
// The focused window belongs to another client. It can disappear between
// XGetInputFocus and XQueryTree while a dialog closes or focus changes.
// Treat that race as inactive; the next query will find the new focus.
if (error == BadWindow) { *active = 0; return 0; }
return error ? -EIO : 0;
}
int terminal_x_next(TerminalX *x, TerminalEvent *event) {
if (!XPending(x->display)) return 0;
XEvent native;
XNextEvent(x->display, &native);
memset(event, 0, sizeof(*event));
if (native.type == DestroyNotify) return native.xdestroywindow.window == x->window ? -EPIPE : 1;
if (native.type == CreateNotify) {
XErrorHandler previous = begin_trap(x->display);
int remaining = 1024;
observe_windows(x, native.xcreatewindow.window, 0, &remaining);
return end_trap(x->display, previous) ? -EIO : 1;
}
if (native.type == MappingNotify) {
XRefreshKeyboardMapping(&native.xmapping);
XErrorHandler previous = begin_trap(x->display);
XUngrabKey(x->display, AnyKey, AnyModifier, x->window);
install_grabs(x);
return end_trap(x->display, previous) ? -EBUSY : 1;
}
if (native.type != KeyPress && native.type != KeyRelease) return 1;
event->type = 1;
event->keycode = native.xkey.keycode;
event->value = native.type == KeyPress;
event->state = native.xkey.state;
event->time = native.xkey.time;
// The unshifted symbol identifies bindings independently of CapsLock.
event->symbol = XLookupKeysym(&native.xkey, 0);
if (native.type == KeyPress) {
KeySym translated;
if (x->ic) {
Status status;
event->length = Xutf8LookupString(x->ic, &native.xkey, event->text, sizeof(event->text), &translated, &status);
if (status == XBufferOverflow) event->length = 0;
} else event->length = XLookupString(&native.xkey, event->text, sizeof(event->text), &translated, NULL);
// Keypad navigation/numeric meaning depends on NumLock and Shift.
if (event->symbol >= XK_KP_Space && event->symbol <= XK_KP_Equal) event->symbol = translated;
}
return 1;
}
int terminal_x_consume(TerminalX *x) {
if (!x->grabbed) {
int active, numlock, capslock;
int error = terminal_x_state(x, &active, &numlock, &capslock);
if (error < 0) return error;
if (!active) return TERMINAL_CAPTURE_INACTIVE;
XErrorHandler previous = begin_trap(x->display);
// A queued key can predate a desktop grab or our own release. Its
// timestamp then fails with GrabInvalidTime even after focus returns.
// Recheck focus above and acquire at the current server time.
int result = XGrabKeyboard(x->display, x->window, False, GrabModeAsync, GrabModeAsync, CurrentTime);
error = end_trap(x->display, previous);
if (error == BadWindow) return GrabNotViewable;
if (error) return -EIO;
if (result != GrabSuccess) return result;
x->grabbed = 1;
}
XAllowEvents(x->display, AsyncKeyboard, CurrentTime);
XFlush(x->display);
return 0;
}
void terminal_x_replay(TerminalX *x, unsigned long time) {
XAllowEvents(x->display, ReplayKeyboard, time);
XFlush(x->display);
}
int terminal_key_code(unsigned long symbol, int keycode) {
// Translate every letter/function key, so adding a shared binding (such
// as Fenrir+F) never requires a second command map in this adapter.
static const int letters[] = {KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F,
KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P,
KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z};
if (symbol >= XK_a && symbol <= XK_z) return letters[symbol-XK_a];
if (symbol >= XK_A && symbol <= XK_Z) return letters[symbol-XK_A];
if (symbol >= XK_1 && symbol <= XK_9) return KEY_1 + symbol-XK_1;
if (symbol >= XK_F1 && symbol <= XK_F10) return KEY_F1 + symbol-XK_F1;
if (symbol == XK_F11) return KEY_F11;
if (symbol == XK_F12) return KEY_F12;
switch (symbol) {
case XK_0: return KEY_0;
case XK_Escape: return KEY_ESC;
case XK_Return: return KEY_ENTER;
case XK_Tab: return KEY_TAB;
case XK_space: return KEY_SPACE;
case XK_minus: return KEY_MINUS;
case XK_equal: return KEY_EQUAL;
case XK_bracketleft: return KEY_LEFTBRACE;
case XK_bracketright: return KEY_RIGHTBRACE;
case XK_backslash: return KEY_BACKSLASH;
case XK_semicolon: return KEY_SEMICOLON;
case XK_apostrophe: return KEY_APOSTROPHE;
case XK_grave: return KEY_GRAVE;
case XK_comma: return KEY_COMMA;
case XK_period: return KEY_DOT;
case XK_slash: return KEY_SLASH;
case XK_Up: return KEY_UP;
case XK_Down: return KEY_DOWN;
case XK_Left: return KEY_LEFT;
case XK_Right: return KEY_RIGHT;
case XK_Home: return KEY_HOME;
case XK_End: return KEY_END;
case XK_Delete: return KEY_DELETE;
case XK_Page_Up: return KEY_PAGEUP;
case XK_Page_Down: return KEY_PAGEDOWN;
case XK_Insert: return KEY_INSERT;
case XK_Caps_Lock: return KEY_CAPSLOCK;
case XK_Multi_key: return KEY_COMPOSE;
case XK_Menu: return KEY_MENU;
case XK_Super_L: case XK_Meta_L: return KEY_LEFTMETA;
case XK_Super_R: case XK_Meta_R: return KEY_RIGHTMETA;
case XK_Control_L: return KEY_LEFTCTRL;
case XK_Control_R: return KEY_RIGHTCTRL;
case XK_Shift_L: return KEY_LEFTSHIFT;
case XK_Shift_R: return KEY_RIGHTSHIFT;
case XK_Alt_L: return KEY_LEFTALT;
case XK_Alt_R: case XK_ISO_Level3_Shift: return KEY_RIGHTALT;
case XK_BackSpace: return KEY_BACKSPACE;
case XK_KP_Insert: case XK_KP_0: return KEY_KP0;
case XK_KP_End: case XK_KP_1: return KEY_KP1;
case XK_KP_Down: case XK_KP_2: return KEY_KP2;
case XK_KP_Page_Down: case XK_KP_3: return KEY_KP3;
case XK_KP_Left: case XK_KP_4: return KEY_KP4;
case XK_KP_Begin: case XK_KP_5: return KEY_KP5;
case XK_KP_Right: case XK_KP_6: return KEY_KP6;
case XK_KP_Home: case XK_KP_7: return KEY_KP7;
case XK_KP_Up: case XK_KP_8: return KEY_KP8;
case XK_KP_Page_Up: case XK_KP_9: return KEY_KP9;
case XK_KP_Delete: case XK_KP_Decimal: return KEY_KPDOT;
case XK_KP_Enter: return KEY_KPENTER;
case XK_KP_Add: return KEY_KPPLUS;
case XK_KP_Subtract: return KEY_KPMINUS;
case XK_KP_Multiply: return KEY_KPASTERISK;
case XK_KP_Divide: return KEY_KPSLASH;
// Xorg's evdev keymap uses Linux codes plus eight for otherwise unknown
// symbols (multimedia/international keys). Known symbols above take priority.
default: return keycode >= 8 && keycode <= KEY_MAX+8 ? keycode-8 : KEY_MAX+1+keycode;
}
}