534 lines
23 KiB
C
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;
|
|
}
|
|
}
|