summaryrefslogtreecommitdiffstats
path: root/common/sys/vtkbd.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/sys/vtkbd.c')
-rw-r--r--common/sys/vtkbd.c337
1 files changed, 337 insertions, 0 deletions
diff --git a/common/sys/vtkbd.c b/common/sys/vtkbd.c
new file mode 100644
index 00000000..a5f4cf9e
--- /dev/null
+++ b/common/sys/vtkbd.c
@@ -0,0 +1,337 @@
+/*
+ * vtkbd.c
+ * Virtual Terminal Keyboard
+ *
+ * piaip's new re-implementation of xterm/VT100/220/ANSI key input
+ * escape sequence parser for BBS
+ *
+ * Author: Hung-Te Lin (piaip)
+ * Create: Wed Sep 23 15:06:43 CST 2009
+ * ---------------------------------------------------------------------------
+ * Copyright (c) 2009 Hung-Te Lin <piaip@csie.org>
+ * All rights reserved.
+ * Distributed under BSD license (GPL compatible).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * ---------------------------------------------------------------------------
+ * References:
+ * http://support.dell.com/support/edocs/systems/pe2650/en/ug/5g387ad0.htm
+ * http://aperiodic.net/phil/archives/Geekery/term-function-keys.html
+ * http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/rzaiw/rzaiwvt220opmode.htm
+ * http://www.rebol.com/docs/core23/rebolcore-18.html
+ * http://ascii-table.com/ansi-escape-sequences-vt-100.php
+ * http://web.mit.edu/gnu/doc/html/screen_10.html
+ * http://vt100.net/docs/vt220-rm/chapter3.html
+ * http://inwap.com/pdp10/ansicode.txt
+ * http://www.connectrf.com/Documents/vt220.html
+ * http://www.ibb.net/~anne/keyboard.html
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ * PuTTY Source < terminal.c, term_key() >
+ * Termcap
+ * ---------------------------------------------------------------------------
+ * * BS/DEL Rules
+ * The BackSpace, Erase(<X]), and Delete keys are special due to history...
+ * - on vt220, BS=0x7F, Delete=ESC[3~ (screen/xterm/PuTTY/PCMan)
+ * - on vt100, BS=0x7F
+ * - on vt100/xterm, BS=0x08, Delete=0x7F (VMX/Windows/DOS telnet/KKMan)
+ * So we define
+ * KEY_BS = BACKSPACE/ERASE = 0x08, 0x7F
+ * KEY_DEL = DELETE = ESC[3~
+ *
+ * * CR/LF Rules
+ * The traditional newline maps to following combination...
+ * - UNIX, LF
+ * - Win, CR+LF
+ * - Mac, CR
+ * When it comes to terminal,CR LF should be treated as one.
+ * There were reports that some users getting double ENTERs if we take LF
+ * as ENTER, so we decided reject LF. We are not sure if there is any
+ * clients sending LF only, but according the the compatibility test,
+ * most modern clients send only CR (or CR+LF).
+ * So we define
+ * KEY_CR = CR, CR+LF
+ * KEY_LF = ignored.
+ * ---------------------------------------------------------------------------
+ * * The complete list to support:
+ * - Up/Down/Right/Left: <Esc> [ <A-D> | <Esc> O <A-D> (app)
+ * - Home/Ins/Del/End/PgUp/PgDn: <Esc> [ <1~6> ~
+ * - Shift-TAB: <Esc> [ Z | <Esc> [ 0 Z
+ * - F1~F4: <Esc> [ 1 <1234> ~ | <Esc> O <PQRS>
+ * - F5: <Esc> [ 1 <5> ~
+ * - F6-F8: <Esc> [ 1 <789> ~
+ * - F9-F12: <Esc> [ 2 <0134> ~
+ * - Num 0-9 *+,-./=ENTER: <Esc> O <pqrstuvwxyjklmnoXM>
+ *
+ * Note: we don't support some rare terms like <Esc> O <TUVWXYZA> described
+ * in Dell 2650 in order to prevent confusion.
+ * Num pad is also always converted to digits.
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "vtkbd.h"
+
+/* VtkbdCtx.state */
+typedef enum {
+ VKSTATE_NORMAL = 0,
+ VKSTATE_ESC, // <Esc>
+ VKSTATE_ESC_APP, // <Esc> O
+ VKSTATE_ESC_QUOTE, // <Esc> [
+ VKSTATE_ZERO, // <Esc> [ 0 (wait Z)
+ VKSTATE_ONE, // <Esc> [ <1>
+ VKSTATE_TWO, // <Esc> [ <2>
+ VKSTATE_TLIDE, // <Esc> [ * (wait ~, return esc_arg)
+} VKSTATES;
+
+#define VKRAW_BS 0x08 // \b = Ctrl('H')
+#define VKRAW_ERASE 0x7F // <X]
+
+/* the processor API */
+int
+vtkbd_process(int c, VtkbdCtx *ctx)
+{
+ switch (ctx->state)
+ {
+ case VKSTATE_NORMAL: // original state
+ if (c == KEY_ESC)
+ {
+ ctx->state = VKSTATE_ESC;
+ return KEY_INCOMPLETE;
+ }
+
+ // simple mappings
+ switch (c) {
+ // BS/ERASE/DEL Rules
+ case VKRAW_BS:
+ case VKRAW_ERASE:
+ return KEY_BS;
+ }
+ return c;
+
+ case VKSTATE_ESC: // <Esc>
+ switch (c) {
+ case '[':
+ ctx->state = VKSTATE_ESC_QUOTE;
+ return KEY_INCOMPLETE;
+
+ case 'O':
+ ctx->state = VKSTATE_ESC_APP;
+ return KEY_INCOMPLETE;
+
+ case '0':
+ ctx->state = VKSTATE_ZERO;
+ return KEY_INCOMPLETE;
+ }
+
+ // XXX should we map this into another section of KEY_ESC_* ?
+ ctx->esc_arg = c;
+ ctx->state = VKSTATE_NORMAL;
+ return KEY_ESC;
+
+ case VKSTATE_ZERO: // <Esc> 0
+ if (c != 'Z')
+ break;
+
+ ctx->state = VKSTATE_NORMAL;
+ return KEY_STAB;
+
+ case VKSTATE_ESC_APP: // <Esc> O
+
+ switch (c) {
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ ctx->state = VKSTATE_NORMAL;
+ return KEY_UP + (c - 'A');
+
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ ctx->state = VKSTATE_NORMAL;
+ return KEY_F1 + (c - 'P');
+
+ // Num pads: always convert to NumLock=ON
+ case 'p': case 'q': case 'r': case 's':
+ case 't': case 'u': case 'v': case 'w':
+ case 'x': case 'y':
+ ctx->state = VKSTATE_NORMAL;
+ return '0' + (c - 'p');
+
+ case 'M':
+ ctx->state = VKSTATE_NORMAL;
+ return KEY_ENTER;
+
+ case 'X':
+ ctx->state = VKSTATE_NORMAL;
+ return '=';
+
+ case 'j': case 'k': case 'l': case 'm':
+ case 'n': case 'o':
+ {
+ static const char *numx = "*+,-./";
+ assert( c >= 'j' && (c-'j') < strlen(numx));
+ ctx->state = VKSTATE_NORMAL;
+ return numx[c-'j'];
+ }
+ }
+ break;
+
+ case VKSTATE_ESC_QUOTE: // <Esc> [
+
+ switch(c) {
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ ctx->state = VKSTATE_NORMAL;
+ return KEY_UP + (c - 'A');
+
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ ctx->state = VKSTATE_TLIDE;
+ ctx->esc_arg = KEY_DEL + (c - '3');
+ return KEY_INCOMPLETE;
+
+ case 'Z':
+ ctx->state = VKSTATE_NORMAL;
+ return KEY_STAB;
+
+ case '1':
+ ctx->state = VKSTATE_ONE;
+ return KEY_INCOMPLETE;
+ case '2':
+ ctx->state = VKSTATE_TWO;
+ return KEY_INCOMPLETE;
+ }
+ break;
+
+ case VKSTATE_ONE: // <Esc> [ 1
+ if (c == '~')
+ {
+ ctx->state = VKSTATE_NORMAL;
+ return KEY_HOME;
+ }
+
+ switch(c) {
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ ctx->state = VKSTATE_TLIDE;
+ ctx->esc_arg = KEY_F1 + c - '1'; // F1 .. F5
+ return KEY_INCOMPLETE;
+
+ case '7':
+ case '8':
+ case '9':
+ ctx->state = VKSTATE_TLIDE;
+ ctx->esc_arg = KEY_F6 + c - '7'; // F6 .. F8
+ return KEY_INCOMPLETE;
+ }
+ break;
+
+ case VKSTATE_TWO: // <Esc> [ 2
+ if (c == '~')
+ {
+ ctx->state = VKSTATE_NORMAL;
+ return KEY_INS; // HOME+1
+ }
+
+ switch(c) {
+ case '0':
+ case '1':
+ ctx->state = VKSTATE_TLIDE;
+ ctx->esc_arg = KEY_F9 + c - '0'; // F9 .. F10
+ return KEY_INCOMPLETE;
+
+ case '3':
+ case '4':
+ ctx->state = VKSTATE_TLIDE;
+ ctx->esc_arg = KEY_F11 + c - '3'; // F11 .. F12
+ return KEY_INCOMPLETE;
+ }
+ break;
+
+ case VKSTATE_TLIDE: // Esc [ <12> <0-9> ~
+ if (c != '~')
+ break;
+
+ ctx->state = VKSTATE_NORMAL;
+ return ctx->esc_arg;
+
+ default:
+ assert(!"unknown vkstate");
+ break;
+ }
+
+ // what to do now?
+ ctx->state = VKSTATE_NORMAL;
+ return KEY_UNKNOWN;
+}
+
+ssize_t
+vtkbd_ignore_dbcs_evil_repeats(const unsigned char *buf, ssize_t len)
+{
+ // determine DBCS repeats by evil clients
+ // NOTE: this is usually invoked before vtkbd_process,
+ // so we have to deal with the raw sequence.
+ if (len == 2)
+ {
+ // XXX len==2 is dangerous. hope we are not in telnet IAC state...
+ if (buf[0] != buf[1])
+ return len;
+
+ // targest here:
+ // - VKRAW_BS
+ // - VKRAW_ERASE
+ // - Ctrl('D') (KKMan3 also treats Ctrl('D') as DBCS DEL)
+ if (buf[0] == VKRAW_BS ||
+ buf[0] == VKRAW_ERASE ||
+ buf[0] == Ctrl('D'))
+ return len/2;
+ }
+ else if (len == 6)
+ {
+ // RIGHT: KEY_ESC "OC" or KEY_ESC "[C"
+ // LEFT: KEY_ESC "OD" or KEY_ESC "[D"
+ if (buf[2] != 'C' && buf[2] != 'D')
+ return len;
+
+ if ( buf[0] == KEY_ESC &&
+ (buf[1] == '[' || buf[1] == 'O') &&
+ buf[0] == buf[3] &&
+ buf[1] == buf[4] &&
+ buf[2] == buf[5])
+ return len/2;
+ }
+ else if (len == 8)
+ {
+ // DEL: ESC_STR "[3~" // vt220
+ if (buf[0] != KEY_ESC ||
+ buf[2] != '3' ||
+ buf[1] != '[' ||
+ buf[3] != '~')
+ return len;
+
+ if( buf[4] == buf[0] &&
+ buf[5] == buf[1] &&
+ buf[6] == buf[2] &&
+ buf[7] == buf[3])
+ return len/2;
+ }
+ return len;
+}
+
+// vim:sw=4:sw=4:et