/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2025 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU Lesser General Public License, as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any
 * later version. Please see the file LICENSE-LGPL for details.
 *
 * Web Page: http://brltty.app/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

#include <string.h>

#include "log.h"
#include "report.h"
#include "cmd_queue.h"
#include "cmd_keycodes.h"
#include "kbd_keycodes.h"
#include "brl_cmds.h"
#include "alert.h"

typedef enum {
  MOD_RELEASE = 0, /* must be first */

  MOD_GUI_LEFT,
  MOD_GUI_RIGHT,
  MOD_CONTEXT,

  MOD_LOCK_CAPS,
  MOD_LOCK_SCROLL,
  MOD_LOCK_NUMBER,

  MOD_SHIFT_LEFT,
  MOD_SHIFT_RIGHT,

  MOD_CONTROL_LEFT,
  MOD_CONTROL_RIGHT,

  MOD_ALT_LEFT,
  MOD_ALT_RIGHT
} Modifier;

#define MOD_BIT(number) (1 << (number))
#define MOD_SET(number, bits) ((bits) |= MOD_BIT((number)))
#define MOD_CLR(number, bits) ((bits) &= ~MOD_BIT((number)))
#define MOD_TST(number, bits) ((bits) & MOD_BIT((number)))

typedef struct {
  int command;
  int alternate;
} KeyEntry;

static const KeyEntry keyEntry_Escape = {BRL_CMD_KEY(ESCAPE)};
static const KeyEntry keyEntry_F1 = {BRL_CMD_KFN(1)};
static const KeyEntry keyEntry_F2 = {BRL_CMD_KFN(2)};
static const KeyEntry keyEntry_F3 = {BRL_CMD_KFN(3)};
static const KeyEntry keyEntry_F4 = {BRL_CMD_KFN(4)};
static const KeyEntry keyEntry_F5 = {BRL_CMD_KFN(5)};
static const KeyEntry keyEntry_F6 = {BRL_CMD_KFN(6)};
static const KeyEntry keyEntry_F7 = {BRL_CMD_KFN(7)};
static const KeyEntry keyEntry_F8 = {BRL_CMD_KFN(8)};
static const KeyEntry keyEntry_F9 = {BRL_CMD_KFN(9)};
static const KeyEntry keyEntry_F10 = {BRL_CMD_KFN(10)};
static const KeyEntry keyEntry_F11 = {BRL_CMD_KFN(11)};
static const KeyEntry keyEntry_F12 = {BRL_CMD_KFN(12)};
static const KeyEntry keyEntry_ScrollLock = {MOD_LOCK_SCROLL};

static const KeyEntry keyEntry_F13 = {BRL_CMD_KFN(13)};
static const KeyEntry keyEntry_F14 = {BRL_CMD_KFN(14)};
static const KeyEntry keyEntry_F15 = {BRL_CMD_KFN(15)};
static const KeyEntry keyEntry_F16 = {BRL_CMD_KFN(16)};
static const KeyEntry keyEntry_F17 = {BRL_CMD_KFN(17)};
static const KeyEntry keyEntry_F18 = {BRL_CMD_KFN(18)};
static const KeyEntry keyEntry_F19 = {BRL_CMD_KFN(19)};
static const KeyEntry keyEntry_F20 = {BRL_CMD_KFN(20)};
static const KeyEntry keyEntry_F21 = {BRL_CMD_KFN(21)};
static const KeyEntry keyEntry_F22 = {BRL_CMD_KFN(22)};
static const KeyEntry keyEntry_F23 = {BRL_CMD_KFN(23)};
static const KeyEntry keyEntry_F24 = {BRL_CMD_KFN(24)};

static const KeyEntry keyEntry_Grave = {BRL_CMD_CHAR(WC_C('`')), BRL_CMD_CHAR(WC_C('~'))};
static const KeyEntry keyEntry_1 = {BRL_CMD_CHAR(WC_C('1')), BRL_CMD_CHAR(WC_C('!'))};
static const KeyEntry keyEntry_2 = {BRL_CMD_CHAR(WC_C('2')), BRL_CMD_CHAR(WC_C('@'))};
static const KeyEntry keyEntry_3 = {BRL_CMD_CHAR(WC_C('3')), BRL_CMD_CHAR(WC_C('#'))};
static const KeyEntry keyEntry_4 = {BRL_CMD_CHAR(WC_C('4')), BRL_CMD_CHAR(WC_C('$'))};
static const KeyEntry keyEntry_5 = {BRL_CMD_CHAR(WC_C('5')), BRL_CMD_CHAR(WC_C('%'))};
static const KeyEntry keyEntry_6 = {BRL_CMD_CHAR(WC_C('6')), BRL_CMD_CHAR(WC_C('^'))};
static const KeyEntry keyEntry_7 = {BRL_CMD_CHAR(WC_C('7')), BRL_CMD_CHAR(WC_C('&'))};
static const KeyEntry keyEntry_8 = {BRL_CMD_CHAR(WC_C('8')), BRL_CMD_CHAR(WC_C('*'))};
static const KeyEntry keyEntry_9 = {BRL_CMD_CHAR(WC_C('9')), BRL_CMD_CHAR(WC_C('('))};
static const KeyEntry keyEntry_0 = {BRL_CMD_CHAR(WC_C('0')), BRL_CMD_CHAR(WC_C(')'))};
static const KeyEntry keyEntry_Minus = {BRL_CMD_CHAR(WC_C('-')), BRL_CMD_CHAR(WC_C('_'))};
static const KeyEntry keyEntry_Equal = {BRL_CMD_CHAR(WC_C('=')), BRL_CMD_CHAR(WC_C('+'))};
static const KeyEntry keyEntry_Backspace = {BRL_CMD_KEY(BACKSPACE)};

static const KeyEntry keyEntry_Tab = {BRL_CMD_KEY(TAB)};
static const KeyEntry keyEntry_Q = {BRL_CMD_CHAR(WC_C('q')), BRL_CMD_CHAR(WC_C('Q'))};
static const KeyEntry keyEntry_W = {BRL_CMD_CHAR(WC_C('w')), BRL_CMD_CHAR(WC_C('W'))};
static const KeyEntry keyEntry_E = {BRL_CMD_CHAR(WC_C('e')), BRL_CMD_CHAR(WC_C('E'))};
static const KeyEntry keyEntry_R = {BRL_CMD_CHAR(WC_C('r')), BRL_CMD_CHAR(WC_C('R'))};
static const KeyEntry keyEntry_T = {BRL_CMD_CHAR(WC_C('t')), BRL_CMD_CHAR(WC_C('T'))};
static const KeyEntry keyEntry_Y = {BRL_CMD_CHAR(WC_C('y')), BRL_CMD_CHAR(WC_C('Y'))};
static const KeyEntry keyEntry_U = {BRL_CMD_CHAR(WC_C('u')), BRL_CMD_CHAR(WC_C('U'))};
static const KeyEntry keyEntry_I = {BRL_CMD_CHAR(WC_C('i')), BRL_CMD_CHAR(WC_C('I'))};
static const KeyEntry keyEntry_O = {BRL_CMD_CHAR(WC_C('o')), BRL_CMD_CHAR(WC_C('O'))};
static const KeyEntry keyEntry_P = {BRL_CMD_CHAR(WC_C('p')), BRL_CMD_CHAR(WC_C('P'))};
static const KeyEntry keyEntry_LeftBracket = {BRL_CMD_CHAR(WC_C('[')), BRL_CMD_CHAR(WC_C('{'))};
static const KeyEntry keyEntry_RightBracket = {BRL_CMD_CHAR(WC_C(']')), BRL_CMD_CHAR(WC_C('}'))};
static const KeyEntry keyEntry_Backslash = {BRL_CMD_CHAR('\\'), BRL_CMD_CHAR(WC_C('|'))};

static const KeyEntry keyEntry_CapsLock = {MOD_LOCK_CAPS};
static const KeyEntry keyEntry_A = {BRL_CMD_CHAR(WC_C('a')), BRL_CMD_CHAR(WC_C('A'))};
static const KeyEntry keyEntry_S = {BRL_CMD_CHAR(WC_C('s')), BRL_CMD_CHAR(WC_C('S'))};
static const KeyEntry keyEntry_D = {BRL_CMD_CHAR(WC_C('d')), BRL_CMD_CHAR(WC_C('D'))};
static const KeyEntry keyEntry_F = {BRL_CMD_CHAR(WC_C('f')), BRL_CMD_CHAR(WC_C('F'))};
static const KeyEntry keyEntry_G = {BRL_CMD_CHAR(WC_C('g')), BRL_CMD_CHAR(WC_C('G'))};
static const KeyEntry keyEntry_H = {BRL_CMD_CHAR(WC_C('h')), BRL_CMD_CHAR(WC_C('H'))};
static const KeyEntry keyEntry_J = {BRL_CMD_CHAR(WC_C('j')), BRL_CMD_CHAR(WC_C('J'))};
static const KeyEntry keyEntry_K = {BRL_CMD_CHAR(WC_C('k')), BRL_CMD_CHAR(WC_C('K'))};
static const KeyEntry keyEntry_L = {BRL_CMD_CHAR(WC_C('l')), BRL_CMD_CHAR(WC_C('L'))};
static const KeyEntry keyEntry_Semicolon = {BRL_CMD_CHAR(WC_C(';')), BRL_CMD_CHAR(WC_C(':'))};
static const KeyEntry keyEntry_Apostrophe = {BRL_CMD_CHAR(WC_C('\'')), BRL_CMD_CHAR(WC_C('"'))};
static const KeyEntry keyEntry_Enter = {BRL_CMD_KEY(ENTER)};

static const KeyEntry keyEntry_LeftShift = {MOD_SHIFT_LEFT};
static const KeyEntry keyEntry_Europe2 = {BRL_CMD_CHAR(WC_C('<')), BRL_CMD_CHAR(WC_C('>'))};
static const KeyEntry keyEntry_Z = {BRL_CMD_CHAR(WC_C('z')), BRL_CMD_CHAR(WC_C('Z'))};
static const KeyEntry keyEntry_X = {BRL_CMD_CHAR(WC_C('x')), BRL_CMD_CHAR(WC_C('X'))};
static const KeyEntry keyEntry_C = {BRL_CMD_CHAR(WC_C('c')), BRL_CMD_CHAR(WC_C('C'))};
static const KeyEntry keyEntry_V = {BRL_CMD_CHAR(WC_C('v')), BRL_CMD_CHAR(WC_C('V'))};
static const KeyEntry keyEntry_B = {BRL_CMD_CHAR(WC_C('b')), BRL_CMD_CHAR(WC_C('B'))};
static const KeyEntry keyEntry_N = {BRL_CMD_CHAR(WC_C('n')), BRL_CMD_CHAR(WC_C('N'))};
static const KeyEntry keyEntry_M = {BRL_CMD_CHAR(WC_C('m')), BRL_CMD_CHAR(WC_C('M'))};
static const KeyEntry keyEntry_Comma = {BRL_CMD_CHAR(WC_C(',')), BRL_CMD_CHAR(WC_C('<'))};
static const KeyEntry keyEntry_Period = {BRL_CMD_CHAR(WC_C('.')), BRL_CMD_CHAR(WC_C('>'))};
static const KeyEntry keyEntry_Slash = {BRL_CMD_CHAR(WC_C('/')), BRL_CMD_CHAR(WC_C('?'))};
static const KeyEntry keyEntry_RightShift = {MOD_SHIFT_RIGHT};

static const KeyEntry keyEntry_LeftControl = {MOD_CONTROL_LEFT};
static const KeyEntry keyEntry_LeftGUI = {MOD_GUI_LEFT};
static const KeyEntry keyEntry_LeftAlt = {MOD_ALT_LEFT};
static const KeyEntry keyEntry_Space = {BRL_CMD_CHAR(WC_C(' '))};
static const KeyEntry keyEntry_RightAlt = {MOD_ALT_RIGHT};
static const KeyEntry keyEntry_RightGUI = {MOD_GUI_RIGHT};
static const KeyEntry keyEntry_Context = {MOD_CONTEXT};
static const KeyEntry keyEntry_RightControl = {MOD_CONTROL_RIGHT};

static const KeyEntry keyEntry_Insert = {BRL_CMD_KEY(INSERT)};
static const KeyEntry keyEntry_Delete = {BRL_CMD_KEY(DELETE)};
static const KeyEntry keyEntry_Home = {BRL_CMD_KEY(HOME)};
static const KeyEntry keyEntry_End = {BRL_CMD_KEY(END)};
static const KeyEntry keyEntry_PageUp = {BRL_CMD_KEY(PAGE_UP)};
static const KeyEntry keyEntry_PageDown = {BRL_CMD_KEY(PAGE_DOWN)};

static const KeyEntry keyEntry_ArrowUp = {BRL_CMD_KEY(CURSOR_UP)};
static const KeyEntry keyEntry_ArrowLeft = {BRL_CMD_KEY(CURSOR_LEFT)};
static const KeyEntry keyEntry_ArrowDown = {BRL_CMD_KEY(CURSOR_DOWN)};
static const KeyEntry keyEntry_ArrowRight = {BRL_CMD_KEY(CURSOR_RIGHT)};

static const KeyEntry keyEntry_NumLock = {MOD_LOCK_NUMBER};
static const KeyEntry keyEntry_KPSlash = {BRL_CMD_CHAR(WC_C('/'))};
static const KeyEntry keyEntry_KPAsterisk = {BRL_CMD_CHAR(WC_C('*'))};
static const KeyEntry keyEntry_KPMinus = {BRL_CMD_CHAR(WC_C('-'))};
static const KeyEntry keyEntry_KPPlus = {BRL_CMD_CHAR(WC_C('+'))};
static const KeyEntry keyEntry_KPEnter = {BRL_CMD_KEY(ENTER)};
static const KeyEntry keyEntry_KPPeriod = {BRL_CMD_KEY(DELETE), BRL_CMD_CHAR(WC_C('.'))};
static const KeyEntry keyEntry_KP0 = {BRL_CMD_KEY(INSERT), BRL_CMD_CHAR(WC_C('0'))};
static const KeyEntry keyEntry_KP1 = {BRL_CMD_KEY(END), BRL_CMD_CHAR(WC_C('1'))};
static const KeyEntry keyEntry_KP2 = {BRL_CMD_KEY(CURSOR_DOWN), BRL_CMD_CHAR(WC_C('2'))};
static const KeyEntry keyEntry_KP3 = {BRL_CMD_KEY(PAGE_DOWN), BRL_CMD_CHAR(WC_C('3'))};
static const KeyEntry keyEntry_KP4 = {BRL_CMD_KEY(CURSOR_LEFT), BRL_CMD_CHAR(WC_C('4'))};
static const KeyEntry keyEntry_KP5 = {BRL_CMD_CHAR(WC_C('5'))};
static const KeyEntry keyEntry_KP6 = {BRL_CMD_KEY(CURSOR_RIGHT), BRL_CMD_CHAR(WC_C('6'))};
static const KeyEntry keyEntry_KP7 = {BRL_CMD_KEY(HOME), BRL_CMD_CHAR(WC_C('7'))};
static const KeyEntry keyEntry_KP8 = {BRL_CMD_KEY(CURSOR_UP), BRL_CMD_CHAR(WC_C('8'))};
static const KeyEntry keyEntry_KP9 = {BRL_CMD_KEY(PAGE_UP), BRL_CMD_CHAR(WC_C('9'))};
static const KeyEntry keyEntry_KPComma = {BRL_CMD_CHAR(WC_C(','))};

typedef struct {
  ReportListenerInstance *resetListener;

  struct {
    const KeyEntry *const *keyMap;
    size_t keyCount;
    unsigned int modifiers;
  } xt;

  struct {
    const KeyEntry *const *keyMap;
    size_t keyCount;
    unsigned int modifiers;
  } at;

  struct {
    unsigned int modifiers;
  } ps2;
} KeycodeCommandData;

#define USE_KEY_MAP(set,escape) \
  (kcd->set.keyCount = (kcd->set.keyMap = set##KeyMap##escape)? \
  (sizeof(set##KeyMap##escape) / sizeof(*kcd->set.keyMap)): 0)

static void
handleKey (const KeyEntry *key, int release, unsigned int *modifiers) {
  if (key) {
    int cmd = key->command;
    int blk = cmd & BRL_MSK_BLK;

    if (key->alternate) {
      int alternate = 0;

      if (blk == BRL_CMD_BLK(PASSCHAR)) {
        if (MOD_TST(MOD_SHIFT_LEFT, *modifiers) || MOD_TST(MOD_SHIFT_RIGHT, *modifiers)) alternate = 1;
      } else {
        if (MOD_TST(MOD_LOCK_NUMBER, *modifiers)) alternate = 1;
      }

      if (alternate) {
        cmd = key->alternate;
        blk = cmd & BRL_MSK_BLK;
      }
    }

    if (cmd) {
      if (blk) {
        if (!release) {
          if (blk == BRL_CMD_BLK(PASSCHAR)) {
            if (MOD_TST(MOD_LOCK_CAPS, *modifiers)) cmd |= BRL_FLG_INPUT_UPPER;
            if (MOD_TST(MOD_ALT_LEFT, *modifiers)) cmd |= BRL_FLG_INPUT_META;
            if (MOD_TST(MOD_ALT_RIGHT, *modifiers)) cmd |= BRL_FLG_INPUT_ALTGR;
            if (MOD_TST(MOD_GUI_LEFT, *modifiers) || MOD_TST(MOD_GUI_RIGHT, *modifiers)) cmd |= BRL_FLG_INPUT_GUI;
            if (MOD_TST(MOD_CONTROL_LEFT, *modifiers) || MOD_TST(MOD_CONTROL_RIGHT, *modifiers)) cmd |= BRL_FLG_INPUT_CONTROL;
          } else if ((blk == BRL_CMD_BLK(PASSKEY)) && MOD_TST(MOD_ALT_LEFT, *modifiers)) {
            int arg = cmd & BRL_MSK_ARG;

            switch (arg) {
              case BRL_KEY_CURSOR_LEFT:
                cmd = BRL_CMD_SWITCHVT_PREV;
                break;

              case BRL_KEY_CURSOR_RIGHT:
                cmd = BRL_CMD_SWITCHVT_NEXT;
                break;

              default:
                if (arg >= BRL_KEY_FUNCTION) {
                  cmd = BRL_CMD_BLK(SWITCHVT) + (arg - BRL_KEY_FUNCTION);
                }

                break;
            }
          }

          handleCommand(cmd);
        }
      } else {
        switch (cmd) {
          case MOD_LOCK_SCROLL:
          case MOD_LOCK_NUMBER:
          case MOD_LOCK_CAPS:
            if (!release) {
              if (MOD_TST(cmd, *modifiers)) {
                MOD_CLR(cmd, *modifiers);
              } else {
                MOD_SET(cmd, *modifiers);
              }
            }

            break;

          case MOD_SHIFT_LEFT:
          case MOD_SHIFT_RIGHT:
          case MOD_CONTROL_LEFT:
          case MOD_CONTROL_RIGHT:
          case MOD_ALT_LEFT:
          case MOD_ALT_RIGHT:
            if (release) {
              MOD_CLR(cmd, *modifiers);
            } else {
              MOD_SET(cmd, *modifiers);
            }

            break;
        }
      }
    }
  }
}

static const KeyEntry *const xtKeyMap00[] = {
  [XT_KEY_00_Escape] = &keyEntry_Escape,
  [XT_KEY_00_F1] = &keyEntry_F1,
  [XT_KEY_00_F2] = &keyEntry_F2,
  [XT_KEY_00_F3] = &keyEntry_F3,
  [XT_KEY_00_F4] = &keyEntry_F4,
  [XT_KEY_00_F5] = &keyEntry_F5,
  [XT_KEY_00_F6] = &keyEntry_F6,
  [XT_KEY_00_F7] = &keyEntry_F7,
  [XT_KEY_00_F8] = &keyEntry_F8,
  [XT_KEY_00_F9] = &keyEntry_F9,
  [XT_KEY_00_F10] = &keyEntry_F10,
  [XT_KEY_00_F11] = &keyEntry_F11,
  [XT_KEY_00_F12] = &keyEntry_F12,
  [XT_KEY_00_ScrollLock] = &keyEntry_ScrollLock,

  [XT_KEY_00_F13] = &keyEntry_F13,
  [XT_KEY_00_F14] = &keyEntry_F14,
  [XT_KEY_00_F15] = &keyEntry_F15,
  [XT_KEY_00_F16] = &keyEntry_F16,
  [XT_KEY_00_F17] = &keyEntry_F17,
  [XT_KEY_00_F18] = &keyEntry_F18,
  [XT_KEY_00_F19] = &keyEntry_F19,
  [XT_KEY_00_F20] = &keyEntry_F20,
  [XT_KEY_00_F21] = &keyEntry_F21,
  [XT_KEY_00_F22] = &keyEntry_F22,
  [XT_KEY_00_F23] = &keyEntry_F23,
  [XT_KEY_00_F24] = &keyEntry_F24,

  [XT_KEY_00_Grave] = &keyEntry_Grave,
  [XT_KEY_00_1] = &keyEntry_1,
  [XT_KEY_00_2] = &keyEntry_2,
  [XT_KEY_00_3] = &keyEntry_3,
  [XT_KEY_00_4] = &keyEntry_4,
  [XT_KEY_00_5] = &keyEntry_5,
  [XT_KEY_00_6] = &keyEntry_6,
  [XT_KEY_00_7] = &keyEntry_7,
  [XT_KEY_00_8] = &keyEntry_8,
  [XT_KEY_00_9] = &keyEntry_9,
  [XT_KEY_00_0] = &keyEntry_0,
  [XT_KEY_00_Minus] = &keyEntry_Minus,
  [XT_KEY_00_Equal] = &keyEntry_Equal,
  [XT_KEY_00_Backspace] = &keyEntry_Backspace,

  [XT_KEY_00_Tab] = &keyEntry_Tab,
  [XT_KEY_00_Q] = &keyEntry_Q,
  [XT_KEY_00_W] = &keyEntry_W,
  [XT_KEY_00_E] = &keyEntry_E,
  [XT_KEY_00_R] = &keyEntry_R,
  [XT_KEY_00_T] = &keyEntry_T,
  [XT_KEY_00_Y] = &keyEntry_Y,
  [XT_KEY_00_U] = &keyEntry_U,
  [XT_KEY_00_I] = &keyEntry_I,
  [XT_KEY_00_O] = &keyEntry_O,
  [XT_KEY_00_P] = &keyEntry_P,
  [XT_KEY_00_LeftBracket] = &keyEntry_LeftBracket,
  [XT_KEY_00_RightBracket] = &keyEntry_RightBracket,
  [XT_KEY_00_Backslash] = &keyEntry_Backslash,

  [XT_KEY_00_CapsLock] = &keyEntry_CapsLock,
  [XT_KEY_00_A] = &keyEntry_A,
  [XT_KEY_00_S] = &keyEntry_S,
  [XT_KEY_00_D] = &keyEntry_D,
  [XT_KEY_00_F] = &keyEntry_F,
  [XT_KEY_00_G] = &keyEntry_G,
  [XT_KEY_00_H] = &keyEntry_H,
  [XT_KEY_00_J] = &keyEntry_J,
  [XT_KEY_00_K] = &keyEntry_K,
  [XT_KEY_00_L] = &keyEntry_L,
  [XT_KEY_00_Semicolon] = &keyEntry_Semicolon,
  [XT_KEY_00_Apostrophe] = &keyEntry_Apostrophe,
  [XT_KEY_00_Enter] = &keyEntry_Enter,

  [XT_KEY_00_LeftShift] = &keyEntry_LeftShift,
  [XT_KEY_00_Europe2] = &keyEntry_Europe2,
  [XT_KEY_00_Z] = &keyEntry_Z,
  [XT_KEY_00_X] = &keyEntry_X,
  [XT_KEY_00_C] = &keyEntry_C,
  [XT_KEY_00_V] = &keyEntry_V,
  [XT_KEY_00_B] = &keyEntry_B,
  [XT_KEY_00_N] = &keyEntry_N,
  [XT_KEY_00_M] = &keyEntry_M,
  [XT_KEY_00_Comma] = &keyEntry_Comma,
  [XT_KEY_00_Period] = &keyEntry_Period,
  [XT_KEY_00_Slash] = &keyEntry_Slash,
  [XT_KEY_00_RightShift] = &keyEntry_RightShift,

  [XT_KEY_00_LeftControl] = &keyEntry_LeftControl,
  [XT_KEY_00_LeftAlt] = &keyEntry_LeftAlt,
  [XT_KEY_00_Space] = &keyEntry_Space,

  [XT_KEY_00_NumLock] = &keyEntry_NumLock,
  [XT_KEY_00_KPAsterisk] = &keyEntry_KPAsterisk,
  [XT_KEY_00_KPMinus] = &keyEntry_KPMinus,
  [XT_KEY_00_KPPlus] = &keyEntry_KPPlus,
  [XT_KEY_00_KPPeriod] = &keyEntry_KPPeriod,
  [XT_KEY_00_KP0] = &keyEntry_KP0,
  [XT_KEY_00_KP1] = &keyEntry_KP1,
  [XT_KEY_00_KP2] = &keyEntry_KP2,
  [XT_KEY_00_KP3] = &keyEntry_KP3,
  [XT_KEY_00_KP4] = &keyEntry_KP4,
  [XT_KEY_00_KP5] = &keyEntry_KP5,
  [XT_KEY_00_KP6] = &keyEntry_KP6,
  [XT_KEY_00_KP7] = &keyEntry_KP7,
  [XT_KEY_00_KP8] = &keyEntry_KP8,
  [XT_KEY_00_KP9] = &keyEntry_KP9,
};

static const KeyEntry *const xtKeyMapE0[] = {
  [XT_KEY_E0_LeftGUI] = &keyEntry_LeftGUI,
  [XT_KEY_E0_RightAlt] = &keyEntry_RightAlt,
  [XT_KEY_E0_RightGUI] = &keyEntry_RightGUI,
  [XT_KEY_E0_Context] = &keyEntry_Context,
  [XT_KEY_E0_RightControl] = &keyEntry_RightControl,

  [XT_KEY_E0_Insert] = &keyEntry_Insert,
  [XT_KEY_E0_Delete] = &keyEntry_Delete,
  [XT_KEY_E0_Home] = &keyEntry_Home,
  [XT_KEY_E0_End] = &keyEntry_End,
  [XT_KEY_E0_PageUp] = &keyEntry_PageUp,
  [XT_KEY_E0_PageDown] = &keyEntry_PageDown,

  [XT_KEY_E0_ArrowUp] = &keyEntry_ArrowUp,
  [XT_KEY_E0_ArrowLeft] = &keyEntry_ArrowLeft,
  [XT_KEY_E0_ArrowDown] = &keyEntry_ArrowDown,
  [XT_KEY_E0_ArrowRight] = &keyEntry_ArrowRight,

  [XT_KEY_E0_KPSlash] = &keyEntry_KPSlash,
  [XT_KEY_E0_KPEnter] = &keyEntry_KPEnter,
};

#define xtKeyMapE1 NULL

static void
xtHandleScanCode (KeycodeCommandData *kcd, unsigned char code) {
  if (code == XT_MOD_E0) {
    USE_KEY_MAP(xt, E0);
  } else if (code == XT_MOD_E1) {
    USE_KEY_MAP(xt, E1);
  } else {
    int release = (code & XT_BIT_RELEASE) != 0;
    code &= ~XT_BIT_RELEASE;

    if (code < kcd->xt.keyCount) {
      const KeyEntry *key = kcd->xt.keyMap[code];

      USE_KEY_MAP(xt, 00);
      handleKey(key, release, &kcd->xt.modifiers);
    }
  }
}

static const KeyEntry *const atKeyMap00[] = {
  [AT_KEY_00_Escape] = &keyEntry_Escape,
  [AT_KEY_00_F1] = &keyEntry_F1,
  [AT_KEY_00_F2] = &keyEntry_F2,
  [AT_KEY_00_F3] = &keyEntry_F3,
  [AT_KEY_00_F4] = &keyEntry_F4,
  [AT_KEY_00_F5] = &keyEntry_F5,
  [AT_KEY_00_F6] = &keyEntry_F6,
  [AT_KEY_00_F7] = &keyEntry_F7,
  [AT_KEY_00_F8] = &keyEntry_F8,
  [AT_KEY_00_F9] = &keyEntry_F9,
  [AT_KEY_00_F10] = &keyEntry_F10,
  [AT_KEY_00_F11] = &keyEntry_F11,
  [AT_KEY_00_F12] = &keyEntry_F12,
  [AT_KEY_00_ScrollLock] = &keyEntry_ScrollLock,

  [AT_KEY_00_F13] = &keyEntry_F13,
  [AT_KEY_00_F14] = &keyEntry_F14,
  [AT_KEY_00_F15] = &keyEntry_F15,
  [AT_KEY_00_F16] = &keyEntry_F16,
  [AT_KEY_00_F17] = &keyEntry_F17,
  [AT_KEY_00_F18] = &keyEntry_F18,
  [AT_KEY_00_F19] = &keyEntry_F19,
  [AT_KEY_00_F20] = &keyEntry_F20,
  [AT_KEY_00_F21] = &keyEntry_F21,
  [AT_KEY_00_F22] = &keyEntry_F22,
  [AT_KEY_00_F23] = &keyEntry_F23,
  [AT_KEY_00_F24] = &keyEntry_F24,

  [AT_KEY_00_Grave] = &keyEntry_Grave,
  [AT_KEY_00_1] = &keyEntry_1,
  [AT_KEY_00_2] = &keyEntry_2,
  [AT_KEY_00_3] = &keyEntry_3,
  [AT_KEY_00_4] = &keyEntry_4,
  [AT_KEY_00_5] = &keyEntry_5,
  [AT_KEY_00_6] = &keyEntry_6,
  [AT_KEY_00_7] = &keyEntry_7,
  [AT_KEY_00_8] = &keyEntry_8,
  [AT_KEY_00_9] = &keyEntry_9,
  [AT_KEY_00_0] = &keyEntry_0,
  [AT_KEY_00_Minus] = &keyEntry_Minus,
  [AT_KEY_00_Equal] = &keyEntry_Equal,
  [AT_KEY_00_Backspace] = &keyEntry_Backspace,

  [AT_KEY_00_Tab] = &keyEntry_Tab,
  [AT_KEY_00_Q] = &keyEntry_Q,
  [AT_KEY_00_W] = &keyEntry_W,
  [AT_KEY_00_E] = &keyEntry_E,
  [AT_KEY_00_R] = &keyEntry_R,
  [AT_KEY_00_T] = &keyEntry_T,
  [AT_KEY_00_Y] = &keyEntry_Y,
  [AT_KEY_00_U] = &keyEntry_U,
  [AT_KEY_00_I] = &keyEntry_I,
  [AT_KEY_00_O] = &keyEntry_O,
  [AT_KEY_00_P] = &keyEntry_P,
  [AT_KEY_00_LeftBracket] = &keyEntry_LeftBracket,
  [AT_KEY_00_RightBracket] = &keyEntry_RightBracket,
  [AT_KEY_00_Backslash] = &keyEntry_Backslash,

  [AT_KEY_00_CapsLock] = &keyEntry_CapsLock,
  [AT_KEY_00_A] = &keyEntry_A,
  [AT_KEY_00_S] = &keyEntry_S,
  [AT_KEY_00_D] = &keyEntry_D,
  [AT_KEY_00_F] = &keyEntry_F,
  [AT_KEY_00_G] = &keyEntry_G,
  [AT_KEY_00_H] = &keyEntry_H,
  [AT_KEY_00_J] = &keyEntry_J,
  [AT_KEY_00_K] = &keyEntry_K,
  [AT_KEY_00_L] = &keyEntry_L,
  [AT_KEY_00_Semicolon] = &keyEntry_Semicolon,
  [AT_KEY_00_Apostrophe] = &keyEntry_Apostrophe,
  [AT_KEY_00_Enter] = &keyEntry_Enter,

  [AT_KEY_00_LeftShift] = &keyEntry_LeftShift,
  [AT_KEY_00_Europe2] = &keyEntry_Europe2,
  [AT_KEY_00_Z] = &keyEntry_Z,
  [AT_KEY_00_X] = &keyEntry_X,
  [AT_KEY_00_C] = &keyEntry_C,
  [AT_KEY_00_V] = &keyEntry_V,
  [AT_KEY_00_B] = &keyEntry_B,
  [AT_KEY_00_N] = &keyEntry_N,
  [AT_KEY_00_M] = &keyEntry_M,
  [AT_KEY_00_Comma] = &keyEntry_Comma,
  [AT_KEY_00_Period] = &keyEntry_Period,
  [AT_KEY_00_Slash] = &keyEntry_Slash,
  [AT_KEY_00_RightShift] = &keyEntry_RightShift,

  [AT_KEY_00_LeftControl] = &keyEntry_LeftControl,
  [AT_KEY_00_LeftAlt] = &keyEntry_LeftAlt,
  [AT_KEY_00_Space] = &keyEntry_Space,

  [AT_KEY_00_NumLock] = &keyEntry_NumLock,
  [AT_KEY_00_KPAsterisk] = &keyEntry_KPAsterisk,
  [AT_KEY_00_KPMinus] = &keyEntry_KPMinus,
  [AT_KEY_00_KPPlus] = &keyEntry_KPPlus,
  [AT_KEY_00_KPPeriod] = &keyEntry_KPPeriod,
  [AT_KEY_00_KP0] = &keyEntry_KP0,
  [AT_KEY_00_KP1] = &keyEntry_KP1,
  [AT_KEY_00_KP2] = &keyEntry_KP2,
  [AT_KEY_00_KP3] = &keyEntry_KP3,
  [AT_KEY_00_KP4] = &keyEntry_KP4,
  [AT_KEY_00_KP5] = &keyEntry_KP5,
  [AT_KEY_00_KP6] = &keyEntry_KP6,
  [AT_KEY_00_KP7] = &keyEntry_KP7,
  [AT_KEY_00_KP8] = &keyEntry_KP8,
  [AT_KEY_00_KP9] = &keyEntry_KP9,
};

static const KeyEntry *const atKeyMapE0[] = {
  [AT_KEY_E0_LeftGUI] = &keyEntry_LeftGUI,
  [AT_KEY_E0_RightAlt] = &keyEntry_RightAlt,
  [AT_KEY_E0_RightGUI] = &keyEntry_RightGUI,
  [AT_KEY_E0_Context] = &keyEntry_Context,
  [AT_KEY_E0_RightControl] = &keyEntry_RightControl,

  [AT_KEY_E0_Insert] = &keyEntry_Insert,
  [AT_KEY_E0_Delete] = &keyEntry_Delete,
  [AT_KEY_E0_Home] = &keyEntry_Home,
  [AT_KEY_E0_End] = &keyEntry_End,
  [AT_KEY_E0_PageUp] = &keyEntry_PageUp,
  [AT_KEY_E0_PageDown] = &keyEntry_PageDown,

  [AT_KEY_E0_ArrowUp] = &keyEntry_ArrowUp,
  [AT_KEY_E0_ArrowLeft] = &keyEntry_ArrowLeft,
  [AT_KEY_E0_ArrowDown] = &keyEntry_ArrowDown,
  [AT_KEY_E0_ArrowRight] = &keyEntry_ArrowRight,

  [AT_KEY_E0_KPSlash] = &keyEntry_KPSlash,
  [AT_KEY_E0_KPEnter] = &keyEntry_KPEnter,
};

#define atKeyMapE1 NULL

static void
atHandleScanCode (KeycodeCommandData *kcd, unsigned char code) {
  if (code == AT_MOD_RELEASE) {
    MOD_SET(MOD_RELEASE, kcd->at.modifiers);
  } else if (code == AT_MOD_E0) {
    USE_KEY_MAP(at, E0);
  } else if (code == AT_MOD_E1) {
    USE_KEY_MAP(at, E1);
  } else if (code < kcd->at.keyCount) {
    const KeyEntry *key = kcd->at.keyMap[code];
    int release = MOD_TST(MOD_RELEASE, kcd->at.modifiers);

    MOD_CLR(MOD_RELEASE, kcd->at.modifiers);
    USE_KEY_MAP(at, 00);

    handleKey(key, release, &kcd->at.modifiers);
  }
}

static const KeyEntry *const ps2KeyMap[] = {
  [PS2_KEY_Escape] = &keyEntry_Escape,
  [PS2_KEY_F1] = &keyEntry_F1,
  [PS2_KEY_F2] = &keyEntry_F2,
  [PS2_KEY_F3] = &keyEntry_F3,
  [PS2_KEY_F4] = &keyEntry_F4,
  [PS2_KEY_F5] = &keyEntry_F5,
  [PS2_KEY_F6] = &keyEntry_F6,
  [PS2_KEY_F7] = &keyEntry_F7,
  [PS2_KEY_F8] = &keyEntry_F8,
  [PS2_KEY_F9] = &keyEntry_F9,
  [PS2_KEY_F10] = &keyEntry_F10,
  [PS2_KEY_F11] = &keyEntry_F11,
  [PS2_KEY_F12] = &keyEntry_F12,
  [PS2_KEY_ScrollLock] = &keyEntry_ScrollLock,

  [PS2_KEY_Grave] = &keyEntry_Grave,
  [PS2_KEY_1] = &keyEntry_1,
  [PS2_KEY_2] = &keyEntry_2,
  [PS2_KEY_3] = &keyEntry_3,
  [PS2_KEY_4] = &keyEntry_4,
  [PS2_KEY_5] = &keyEntry_5,
  [PS2_KEY_6] = &keyEntry_6,
  [PS2_KEY_7] = &keyEntry_7,
  [PS2_KEY_8] = &keyEntry_8,
  [PS2_KEY_9] = &keyEntry_9,
  [PS2_KEY_0] = &keyEntry_0,
  [PS2_KEY_Minus] = &keyEntry_Minus,
  [PS2_KEY_Equal] = &keyEntry_Equal,
  [PS2_KEY_Backspace] = &keyEntry_Backspace,

  [PS2_KEY_Tab] = &keyEntry_Tab,
  [PS2_KEY_Q] = &keyEntry_Q,
  [PS2_KEY_W] = &keyEntry_W,
  [PS2_KEY_E] = &keyEntry_E,
  [PS2_KEY_R] = &keyEntry_R,
  [PS2_KEY_T] = &keyEntry_T,
  [PS2_KEY_Y] = &keyEntry_Y,
  [PS2_KEY_U] = &keyEntry_U,
  [PS2_KEY_I] = &keyEntry_I,
  [PS2_KEY_O] = &keyEntry_O,
  [PS2_KEY_P] = &keyEntry_P,
  [PS2_KEY_LeftBracket] = &keyEntry_LeftBracket,
  [PS2_KEY_RightBracket] = &keyEntry_RightBracket,
  [PS2_KEY_Backslash] = &keyEntry_Backslash,

  [PS2_KEY_CapsLock] = &keyEntry_CapsLock,
  [PS2_KEY_A] = &keyEntry_A,
  [PS2_KEY_S] = &keyEntry_S,
  [PS2_KEY_D] = &keyEntry_D,
  [PS2_KEY_F] = &keyEntry_F,
  [PS2_KEY_G] = &keyEntry_G,
  [PS2_KEY_H] = &keyEntry_H,
  [PS2_KEY_J] = &keyEntry_J,
  [PS2_KEY_K] = &keyEntry_K,
  [PS2_KEY_L] = &keyEntry_L,
  [PS2_KEY_Semicolon] = &keyEntry_Semicolon,
  [PS2_KEY_Apostrophe] = &keyEntry_Apostrophe,
  [PS2_KEY_Enter] = &keyEntry_Enter,

  [PS2_KEY_LeftShift] = &keyEntry_LeftShift,
  [PS2_KEY_Europe2] = &keyEntry_Europe2,
  [PS2_KEY_Z] = &keyEntry_Z,
  [PS2_KEY_X] = &keyEntry_X,
  [PS2_KEY_C] = &keyEntry_C,
  [PS2_KEY_V] = &keyEntry_V,
  [PS2_KEY_B] = &keyEntry_B,
  [PS2_KEY_N] = &keyEntry_N,
  [PS2_KEY_M] = &keyEntry_M,
  [PS2_KEY_Comma] = &keyEntry_Comma,
  [PS2_KEY_Period] = &keyEntry_Period,
  [PS2_KEY_Slash] = &keyEntry_Slash,
  [PS2_KEY_RightShift] = &keyEntry_RightShift,

  [PS2_KEY_LeftControl] = &keyEntry_LeftControl,
  [PS2_KEY_LeftGUI] = &keyEntry_LeftGUI,
  [PS2_KEY_LeftAlt] = &keyEntry_LeftAlt,
  [PS2_KEY_Space] = &keyEntry_Space,
  [PS2_KEY_RightAlt] = &keyEntry_RightAlt,
  [PS2_KEY_RightGUI] = &keyEntry_RightGUI,
  [PS2_KEY_Context] = &keyEntry_Context,
  [PS2_KEY_RightControl] = &keyEntry_RightControl,

  [PS2_KEY_Insert] = &keyEntry_Insert,
  [PS2_KEY_Delete] = &keyEntry_Delete,
  [PS2_KEY_Home] = &keyEntry_Home,
  [PS2_KEY_End] = &keyEntry_End,
  [PS2_KEY_PageUp] = &keyEntry_PageUp,
  [PS2_KEY_PageDown] = &keyEntry_PageDown,

  [PS2_KEY_ArrowUp] = &keyEntry_ArrowUp,
  [PS2_KEY_ArrowLeft] = &keyEntry_ArrowLeft,
  [PS2_KEY_ArrowDown] = &keyEntry_ArrowDown,
  [PS2_KEY_ArrowRight] = &keyEntry_ArrowRight,

  [PS2_KEY_NumLock] = &keyEntry_NumLock,
  [PS2_KEY_KPSlash] = &keyEntry_KPSlash,
  [PS2_KEY_KPAsterisk] = &keyEntry_KPAsterisk,
  [PS2_KEY_KPMinus] = &keyEntry_KPMinus,
  [PS2_KEY_KPPlus] = &keyEntry_KPPlus,
  [PS2_KEY_KPEnter] = &keyEntry_KPEnter,
  [PS2_KEY_KPPeriod] = &keyEntry_KPPeriod,
  [PS2_KEY_KP0] = &keyEntry_KP0,
  [PS2_KEY_KP1] = &keyEntry_KP1,
  [PS2_KEY_KP2] = &keyEntry_KP2,
  [PS2_KEY_KP3] = &keyEntry_KP3,
  [PS2_KEY_KP4] = &keyEntry_KP4,
  [PS2_KEY_KP5] = &keyEntry_KP5,
  [PS2_KEY_KP6] = &keyEntry_KP6,
  [PS2_KEY_KP7] = &keyEntry_KP7,
  [PS2_KEY_KP8] = &keyEntry_KP8,
  [PS2_KEY_KP9] = &keyEntry_KP9,
  [PS2_KEY_KPComma] = &keyEntry_KPComma,
};

static void
ps2HandleScanCode (KeycodeCommandData *kcd, unsigned char code) {
  if (code == PS2_MOD_RELEASE) {
    MOD_SET(MOD_RELEASE, kcd->ps2.modifiers);
  } else if (code < ARRAY_COUNT(ps2KeyMap)) {
    const KeyEntry *key = ps2KeyMap[code];
    int release = MOD_TST(MOD_RELEASE, kcd->ps2.modifiers);

    MOD_CLR(MOD_RELEASE, kcd->ps2.modifiers);

    handleKey(key, release, &kcd->ps2.modifiers);
  }
}

static int
handleKeycodeCommands (int command, void *data) {
  KeycodeCommandData *kcd = data;
  int arg = command & BRL_MSK_ARG;

  switch (command & BRL_MSK_BLK) {
    case BRL_CMD_BLK(PASSXT):
      if (command & BRL_FLG_KBD_RELEASE) arg |= XT_BIT_RELEASE;
      if (command & BRL_FLG_KBD_EMUL0) xtHandleScanCode(kcd, XT_MOD_E0);
      if (command & BRL_FLG_KBD_EMUL1) xtHandleScanCode(kcd, XT_MOD_E1);
      xtHandleScanCode(kcd, arg);
      break;

    case BRL_CMD_BLK(PASSAT):
      if (command & BRL_FLG_KBD_RELEASE) atHandleScanCode(kcd, AT_MOD_RELEASE);
      if (command & BRL_FLG_KBD_EMUL0) atHandleScanCode(kcd, AT_MOD_E0);
      if (command & BRL_FLG_KBD_EMUL1) atHandleScanCode(kcd, AT_MOD_E1);
      atHandleScanCode(kcd, arg);
      break;

    case BRL_CMD_BLK(PASSPS2):
      if (command & BRL_FLG_KBD_RELEASE) ps2HandleScanCode(kcd, PS2_MOD_RELEASE);
      ps2HandleScanCode(kcd, arg);
      break;

    default:
      return 0;
  }

  return 1;
}

static void
resetKeycodeCommandData (void *data) {
  KeycodeCommandData *kcd = data;

  USE_KEY_MAP(xt, 00);
  kcd->xt.modifiers = 0;

  USE_KEY_MAP(at, 00);
  kcd->at.modifiers = 0;

  kcd->ps2.modifiers = 0;
}

REPORT_LISTENER(keycodeCommandDataResetListener) {
  KeycodeCommandData *kcd = parameters->listenerData;

  resetKeycodeCommandData(kcd);
}

static void
destroyKeycodeCommandData (void *data) {
  KeycodeCommandData *kcd = data;

  unregisterReportListener(kcd->resetListener);
  free(kcd);
}

int
addKeycodeCommands (void) {
  KeycodeCommandData *kcd;

  if ((kcd = malloc(sizeof(*kcd)))) {
    memset(kcd, 0, sizeof(*kcd));
    resetKeycodeCommandData(kcd);

    if ((kcd->resetListener = registerReportListener(REPORT_BRAILLE_DEVICE_ONLINE, keycodeCommandDataResetListener, kcd))) {
      if (pushCommandHandler("keycodes", KTB_CTX_DEFAULT,
                             handleKeycodeCommands, destroyKeycodeCommandData, kcd)) {
        return 1;
      }

      unregisterReportListener(kcd->resetListener);
    }

    free(kcd);
  } else {
    logMallocError();
  }

  return 0;
}
