Blob Blame Raw

#include "app.h"

#include <unistd.h>

#include <X11/Xutil.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XTest.h>


int inputInit(Input *in, App *app) {
  LOGDBG("input: init");
  
  in->app = app;
  in->key0 = in->key1 = in->mapKey = 0;
  in->mapDown = 0;
  in->updated = 0;
  memset(in->keys, 0, sizeof(in->keys));
  memset(in->masks, 0, sizeof(in->masks));
  
  LOGDBG("input: init: connect to X11");
  in->dpy = XOpenDisplay(NULL);
  if (!in->dpy)
    return LOGERR("input: init: cannot connect to X11");
  
  LOGDBG("input: init: query keycode range");
  XDisplayKeycodes(in->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);
  
  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->dpy, in->mapKey, 4, ks, 1);
  }
  XCloseDisplay(in->dpy);
}


void inputUpdateLayout(Input *in) {
  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 };
  
  in->updated = 0;
  memset(in->masks, 0, sizeof(in->masks));
    
  LOGDBG("input: update layout: read modifiers");
  XModifierKeymap *mods = XGetModifierMapping(in->dpy);
  
  LOGDBG("input: update layout: read keyboard mapping");
  int mcnt = 0;
  KeySym *ks = XGetKeyboardMapping(in->dpy, in->key0, in->key1 - in->key0, &mcnt);
  for(int i = in->key0; i < in->key1; ++i, ks += mcnt) {
    // search key for temporary maps
    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);
      }
    }
    
    //if (i == 50 || i == 62 || i == 66) {
    //  printf("keysyms for key #%d (%d):\n", i, mcnt);
    //  for(int i = 0; i < mcnt; ++i) {
    //    printf("  %lu\n", ks[i]);
    //  }
    //  printf("\n");
    //}
    
    // update state masks
    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;
        }
    
    // get base keys
    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 isKeypad[2] = {
      (0xFF80 <= k[1] && k[1] <= 0xFFBD) || (0x11000000 <= k[1] && k[1] <= 0x1100FFFF),
      (0xFF80 <= k[3] && k[3] <= 0xFFBD) || (0x11000000 <= k[3] && k[3] <= 0x1100FFFF) };
      
    int isLetter[2] = {};
    for(int i = 0; i < 2; ++i) {
      if (k[i*2]) {
        KeySym kl = 0, ku = 0;
        XConvertCase(k[i*2], &kl, &ku);
        if (k[i*2] == kl && kl != ku && ku)
          k[i*3+1] = ku, isLetter[i] = 1;
      }
    }
    
    if (!k[1]) k[1] = k[0];
    if (!k[3]) k[3] = k[2];
    
    // fill keys by algorithm from here:
    //   https://www.x.org/releases/current/doc/libX11/libX11/libX11.html#Manipulating_the_Keyboard_Encoding
    for(int j = 0; j < (1 << IM_COUNT); ++j) {
      int shift     = !!(j & (1 << IM_SHIFT    ));
      int capsLock  = !!(j & (1 << IM_CAPSLOCK ));
      int shiftLock = !!(j & (1 << IM_SHIFTLOCK));
      int numLock   = !!(j & (1 << IM_NUMLOCK  ));
      int group     = !!(j & (1 << IM_GROUP    ));
      
      if (capsLock) shiftLock = 0;
      
      unsigned int k0 = k[group*2];
      unsigned int k1 = k[group*2 + 1];
      
      if (numLock && isKeypad[group]) {
        in->keys[i][j] = shift || shiftLock ? k1 : k0;
        continue;
      }
      
      if (!shift && !capsLock && !shiftLock) {
        in->keys[i][j] = k0;
        continue;
      }
      
      if (!shift && capsLock) {
        in->keys[i][j] = isLetter[group] ? k1 : k0;
        continue;
      }
      
      if (shift && capsLock) {
        in->keys[i][j] = isLetter[group] ? k0 : k1;
        continue;
      }
      
      if (shift || shiftLock) {
        in->keys[i][j] = k1;
        continue;
      }
      
      in->keys[i][j] = k0;
    }
  }
  
  // force shift mask to first modifier
  in->masks[IM_SHIFT] = 1;
  XFreeModifiermap(mods);
  
  for(int i = 0; i < IM_COUNT; ++i)
    LOGDBG("input: update layout: masks[%d] = %08u", i, in->masks[i]);
}


int inputKeycode(Input *in, unsigned int keySym) {
  LOGDBG("input: keycode: keySym=%u", keySym);

  if (!keySym)
    return 0;
  
  XkbStateRec state = {};
  XkbGetState(in->dpy, XkbUseCoreKbd, &state);
  int mod = 0;
  for(int i = 0; i < IM_COUNT; ++i)
    if (in->masks[i] & state.mods)
      mod |= (1 << i);
    
  LOGDBG("input: keycode: current state %08x, chosen mod %04x", (unsigned int)state.mods, (unsigned int)mod);
  
  for(int i = in->key0; i < in->key1; ++i) {
    if (in->keys[i][mod] == 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->dpy, in->mapKey, 4, ks, 1);
  XSync(in->dpy, False);
  in->updated = 1;
  
  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->dpy, keycode, press, CurrentTime);
  XSync(in->dpy, False);
}