| |
| #include "app.h" |
| |
| #include <unistd.h> |
| |
| #include <X11/Xutil.h> |
| #include <X11/XKBlib.h> |
| #include <X11/extensions/XTest.h> |
| |
| |
| |
| |
| |
| |
| void inputPrepareKeysyms(unsigned int *ks0, unsigned int *ks1, int *isLetter, int *isKeypad) { |
| if (!*ks0) *ks0 = *ks1; |
| |
| *isLetter = 0; |
| if (*ks0) { |
| KeySym kl = 0, ku = 0; |
| XConvertCase(*ks0, &kl, &ku); |
| if (*ks0 == kl && ku && kl != ku) { |
| if (!*ks1) *ks1 = ku; |
| *isLetter = 1; |
| } |
| } |
| |
| if (!*ks1) *ks1 = *ks0; |
| *isKeypad = (0xFF80 <= *ks1 && *ks1 <= 0xFFBD) || (0x11000000 <= *ks1 && *ks1 <= 0x1100FFFF); |
| } |
| |
| |
| int inputChooseKeysym(unsigned int modifiers, unsigned int ks0, unsigned int ks1, int isLetter, int isKeypad) { |
| if (!ks1) return 0; |
| if (!ks0) return 1; |
| |
| |
| isLetter = !!isLetter; |
| isKeypad = !!isKeypad; |
| int shift = !!(modifiers & IM_SHIFT_BIT); |
| int capsLock = !!(modifiers & IM_CAPSLOCK_BIT); |
| int shiftLock = !!(modifiers & IM_SHIFTLOCK_BIT); |
| int numLock = !!(modifiers & IM_NUMLOCK_BIT); |
| |
| if (capsLock) |
| shiftLock = 0; |
| if (isKeypad) |
| return numLock; |
| if (!shift && !capsLock && !shiftLock) |
| return 0; |
| if (!shift && capsLock) |
| return !!isLetter; |
| if (shift && capsLock) |
| return !isLetter; |
| return shift || shiftLock; |
| } |
| |
| |
| |
| |
| |
| int inputInit(Input *in, App *app) { |
| LOGDBG("input: init"); |
| |
| CLEARFROM(in, app); |
| in->app = app; |
| |
| LOGDBG("input: init: query keycode range"); |
| XDisplayKeycodes(in->app->dpy, &in->key0, &in->key1); |
| ++in->key1; |
| if (in->key0 < 0) in->key0 = 0; |
| if (in->key1 > IN_MAXKEYS) in->key1 = IN_MAXKEYS; |
| LOGDBG("input: init: keycode range [%d, %d)", in->key0, in->key1); |
| |
| inputUpdateLayout(in, in->key0, in->key1); |
| |
| return 1; |
| } |
| |
| |
| void inputDeinit(Input *in) { |
| LOGDBG("input: deinit"); |
| if (in->mapKey) { |
| LOGDBG("input: deinit: unmap temporary key %d", in->mapKey); |
| if (in->mapDown) |
| inputEvent(in, in->mapKey, 0); |
| KeySym ks[4] = {}; |
| XChangeKeyboardMapping(in->app->dpy, in->mapKey, 4, ks, 1); |
| } |
| } |
| |
| |
| void inputUpdateLayout(Input* in, int key0, int key1) { |
| if (key0 < in->key0) key0 = in->key0; |
| if (key1 > in->key1) key1 = in->key1; |
| if (key1 <= key0) |
| return; |
| if (in->mapKey && key0 == in->mapKey && key1 - key0 == 1) |
| return; |
| |
| LOGDBG("input: update layout"); |
| |
| static const KeySym imKeys[IM_COUNT] = { |
| XK_Shift_L, |
| XK_Caps_Lock, |
| XK_Shift_Lock, |
| XK_Num_Lock, |
| XK_Mode_switch, |
| XK_Scroll_Lock }; |
| |
| memset(in->masks, 0, sizeof(in->masks)); |
| |
| LOGDBG("input: update layout: read modifiers"); |
| XModifierKeymap *mods = XGetModifierMapping(in->app->dpy); |
| |
| LOGDBG("input: update layout: read keyboard mapping"); |
| int mcnt = 0; |
| KeySym *ks = XGetKeyboardMapping(in->app->dpy, in->key0, in->key1 - in->key0, &mcnt); |
| for(int i = in->key0; i < in->key1; ++i, ks += mcnt) { |
| |
| if (!in->mapKey) { |
| int found = 1; |
| for(int i = 0; i < mcnt; ++i) |
| if (ks[i]) { found = 0; break; } |
| if (found) { |
| in->mapKey = i; |
| LOGDBG("input: update layout: choose keycode for temporary map: %d", in->mapKey); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| for(int imask = 0; imask < IM_COUNT; ++imask) |
| for(int iks = 0; iks < mcnt; ++iks) |
| if (ks[iks] == imKeys[imask]) { |
| LOGDBG("input: update layout: keycode for mask %d is %d", imask, i); |
| for(int imod = 0; imod < XkbNumModifiers; ++imod) |
| for(int ik = 0; ik < mods->max_keypermod; ++ik) |
| if (mods->modifiermap[imod*mods->max_keypermod + ik] == i) |
| in->masks[imask] |= 1 << imod; |
| } |
| |
| |
| unsigned int k[] = { |
| mcnt > 0 ? ks[0] : 0, |
| mcnt > 1 ? ks[1] : 0, |
| mcnt > 2 ? ks[2] : 0, |
| mcnt > 3 ? ks[3] : 0 }; |
| if (!k[2] && !k[3]) |
| k[2] = k[0], k[3] = k[1]; |
| |
| int isLetter[2] = {}; |
| int isKeypad[2] = {}; |
| inputPrepareKeysyms(&k[0], &k[1], &isLetter[0], &isKeypad[0]); |
| |
| |
| for(unsigned int modifiers = 0; modifiers < (1 << IM_COUNT); ++modifiers) { |
| int g = !!(modifiers & IM_GROUP_BIT); |
| int ki = inputChooseKeysym(modifiers, k[g*2], k[g*2 + 1], isLetter[g], isKeypad[g]); |
| in->keys[i][modifiers] = k[g*2 + ki]; |
| } |
| } |
| |
| |
| in->masks[IM_SHIFT] = 1 << 0; |
| |
| in->masks[IM_SHIFTLOCK] &= ~in->masks[IM_CAPSLOCK]; |
| |
| in->masks[IM_CAPSLOCK] &= 1 << 1; |
| in->masks[IM_SHIFTLOCK] &= 1 << 1; |
| |
| XFreeModifiermap(mods); |
| |
| for(int i = 0; i < IM_COUNT; ++i) |
| LOGDBG("input: update layout: masks[%d] = %08u", i, in->masks[i]); |
| |
| inputUpdateModifiers(in); |
| } |
| |
| |
| void inputUpdateModifiers(Input *in) { |
| LOGDBG("input: update modifiers"); |
| XkbStateRec state = {}; |
| XkbGetState(in->app->dpy, XkbUseCoreKbd, &state); |
| in->modifiers = 0; |
| for(int i = 0; i < IM_COUNT; ++i) |
| if (in->masks[i] & state.mods) |
| in->modifiers |= (1 << i); |
| LOGDBG("input: update modifiers: current state %08x, chosen mod %02x", state.mods, in->modifiers); |
| } |
| |
| |
| int inputKeycode(Input *in, unsigned int keySym) { |
| LOGDBG("input: keycode: keySym=%u", keySym); |
| |
| if (!keySym) |
| return 0; |
| |
| for(int i = in->key0; i < in->key1; ++i) { |
| if (in->keys[i][in->modifiers] == keySym) { |
| LOGDBG("input: keycode: found keycode %d", i); |
| return i; |
| } |
| } |
| |
| if (!in->mapKey) { |
| LOGWRN("input: keycode: no keycode mapped to keySym[%u], and no free keycodes for temporary mapping", keySym); |
| return 0; |
| } |
| |
| LOGDBG("input: keycode: temporary remap keySym[%u] to %d", keySym, in->mapKey); |
| |
| if (in->mapDown) { |
| LOGWRN("input: event: temporary remap: mapped keys collision, release previously mapped key"); |
| inputEvent(in, in->mapKey, 0); |
| } |
| |
| for(int i = 0; i < IM_COUNT; ++i) |
| in->keys[in->mapKey][i] = keySym; |
| KeySym ks[4] = { keySym, keySym, keySym, keySym }; |
| XChangeKeyboardMapping(in->app->dpy, in->mapKey, 4, ks, 1); |
| XSync(in->app->dpy, False); |
| |
| return in->mapKey; |
| } |
| |
| |
| void inputEvent(Input *in, int keycode, int press) { |
| LOGDBG("input: event: keycode=%d press=%d", keycode, press); |
| press = !!press; |
| if (keycode < in->key0 || keycode >= in->key1) { |
| LOGERR("input: event: bad keycode=%d", keycode); |
| return; |
| } |
| XTestFakeKeyEvent(in->app->dpy, keycode, press, CurrentTime); |
| XSync(in->app->dpy, False); |
| inputUpdateModifiers(in); |
| } |
| |