summaryrefslogtreecommitdiffstats
path: root/mbbsd
diff options
context:
space:
mode:
authorpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2009-09-28 23:06:16 +0800
committerpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2009-09-28 23:06:16 +0800
commitc0dd6835cc9f376ad6f195bad2daacff12b5efd9 (patch)
tree424ea7c2335aa863f8fdc634a0f6b17da8b2641a /mbbsd
parent4621028ffd6a8acbc2bc76c528f11315812c7049 (diff)
downloadpttbbs-c0dd6835cc9f376ad6f195bad2daacff12b5efd9.tar
pttbbs-c0dd6835cc9f376ad6f195bad2daacff12b5efd9.tar.gz
pttbbs-c0dd6835cc9f376ad6f195bad2daacff12b5efd9.tar.bz2
pttbbs-c0dd6835cc9f376ad6f195bad2daacff12b5efd9.tar.lz
pttbbs-c0dd6835cc9f376ad6f195bad2daacff12b5efd9.tar.xz
pttbbs-c0dd6835cc9f376ad6f195bad2daacff12b5efd9.tar.zst
pttbbs-c0dd6835cc9f376ad6f195bad2daacff12b5efd9.zip
* new keyboard input framework: vtkbd
* BS/BS2 is now merged to BS. * DEL/Ctrl-D is not mreged. * pager code (waterball) is a little refined but still far from good. git-svn-id: http://opensvn.csie.org/pttbbs/trunk/pttbbs@4887 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
Diffstat (limited to 'mbbsd')
-rw-r--r--mbbsd/Makefile2
-rw-r--r--mbbsd/edit.c3
-rw-r--r--mbbsd/io.c472
-rw-r--r--mbbsd/name.c4
-rw-r--r--mbbsd/register.c5
-rw-r--r--mbbsd/talk.c2
-rw-r--r--mbbsd/visio.c2
-rw-r--r--mbbsd/vtkbd.c279
8 files changed, 483 insertions, 286 deletions
diff --git a/mbbsd/Makefile b/mbbsd/Makefile
index 6b15e1dc..98335987 100644
--- a/mbbsd/Makefile
+++ b/mbbsd/Makefile
@@ -10,7 +10,7 @@ SRCROOT= ..
PROG= mbbsd
COREOBJS = bbs.o announce.o read.o board.o cache.o cal.o brc.o mail.o record.o fav.o
ACCOBJS = user.o register.o passwd.o emaildb.o
-NETOBJS = mbbsd.o io.o term.o telnet.o
+NETOBJS = mbbsd.o io.o term.o telnet.o vtkbd.o
TALKOBJS = talk.o chat.o friend.o
UTILOBJS = stuff.o kaede.o convert.o name.o syspost.o
PAGEROBJS= more.o pmore.o
diff --git a/mbbsd/edit.c b/mbbsd/edit.c
index f39280d4..676fc7de 100644
--- a/mbbsd/edit.c
+++ b/mbbsd/edit.c
@@ -3931,8 +3931,7 @@ vedit2(const char *fpath, int saveheader, int *islocal, char title[STRLEN], int
case KEY_INS: /* Toggle insert/overwrite */
curr_buf->insert_mode ^= 1;
break;
- case KEY_BS:
- case KEY_BS2: /* backspace */
+ case KEY_BS: /* backspace */
block_cancel();
if (curr_buf->ansimode) {
curr_buf->ansimode = 0;
diff --git a/mbbsd/io.c b/mbbsd/io.c
index 68a2a62a..e0abbe31 100644
--- a/mbbsd/io.c
+++ b/mbbsd/io.c
@@ -279,15 +279,7 @@ dogetch(void)
lastact = now;
}
- // CRLF Handle:
- //
- // (UNIX) LF
- // (WIN) CRLF
- // (MAC) CR
- //
- // to work in a compatible way, (see KEY_ENTER definition)
- // let KEY_ENTER = CR
-
+ // see vtkbd.c for CR/LF Rules
{
unsigned char c = (unsigned char) inbuf[icurrchar++];
@@ -301,24 +293,9 @@ dogetch(void)
}
else if (c == KEY_LF)
{
- // XXX it is reported that still some users
- // experience double ENTERs. We are not sure if there
- // are still any stupid clients. But if any, you
- // can reject the LFs. According the the compatibility
- // test, most clients send CR only so it should be fine.
-#ifdef ACCEPT_LF
- return KEY_ENTER;
-#else
return KEY_UNKNOWN;
-#endif
}
- // XXX also treat ^H and 127 (KEY_BS2) the same one?
- // else if (c == KEY_BS2)
- // {
- // return KEY_BS;
- // }
-
return c;
}
}
@@ -400,148 +377,18 @@ _debug_check_keyinput()
static int water_which_flag = 0;
-#ifndef vkey
-int
-vkey(void)
-{
- return igetch();
-}
-#endif
-
-int
-igetch(void)
+static int
+process_pager_keys(int ch)
{
- register int ch, mode = 0, last = 0;
- while (1) {
- ch = dogetch();
-
- /* for escape codes, check
- * http://support.dell.com/support/edocs/systems/pe2650/en/ug/5g387ad0.htm
- */
- if (mode == 0 && ch == KEY_ESC)
- mode = 1;
- else if (mode == 1) {
-
- /* Escape sequence */
-
- if (ch == '[' || ch == 'O')
- { mode = 2; last = ch; }
- else {
- KEY_ESC_arg = ch;
- return KEY_ESC;
- }
-
- }
- else if (mode == 2)
- {
- /* ^[ or ^O,
- * ordered by frequency */
-
- if(ch >= 'A' && ch <= 'D') /* Cursor key */
- {
- return KEY_UP + (ch - 'A');
- }
- else if (ch >= '1' && ch <= '6') /* Ins Del Home End PgUp PgDn */
- {
- mode = 3; last = ch;
- continue;
- }
- else if(ch == 'O')
- {
- mode = 4;
- continue;
- }
- else if(ch == 'Z')
- {
- return KEY_STAB;
- }
- else if (ch == '0')
- {
- if (dogetch() == 'Z')
- return KEY_STAB;
- else
- return KEY_UNKNOWN;
- }
- else if (last == 'O') {
- /* ^[O ... */
- if (ch >= 'A' && ch <= 'D')
- return KEY_UP + (ch - 'A');
- if (ch >= 'P' && ch <= 'S') // vt100 k1-4
- return KEY_F1 + (ch - 'P');
- if (ch >= 'T' && ch <= '[') // putty vt100+ F5-F12
- return KEY_F5 + (ch - 'T');
- if (ch >= 't' && ch <= 'z') // vt100 F5-F11
- return KEY_F5 + (ch - 't');
- if (ch >= 'p' && ch <= 's') // Old (num or fn)kbd 4 keys
- return KEY_F1 + (ch - 'p');
- else if (ch == 'a') // DELL spec
- return KEY_F12;
- }
- else return KEY_UNKNOWN;
- }
- else if (mode == 3)
- {
- /* ^[[1-6] */
-
- /* ~: Ins Del Home End PgUp PgDn */
- if(ch == '~')
- {
- // vt220 style
- if (last >= '1' && last <= '6')
- return KEY_HOME + (last - '1');
- // else, unknown.
- return KEY_UNKNOWN;
- }
- else if (last == '1')
- {
- if (ch >= '1' && ch <= '6')
- {
- // use num_in_buf() to prevent waiting keys
- if (num_in_buf() && dogetch() == '~') /* must be '~' */
- return KEY_F1 + ch - '1';
- }
- else if (ch >= '7' && ch <= '9')
- {
- // use num_in_buf() to prevent waiting keys
- if (num_in_buf() && dogetch() == '~') /* must be '~' */
- return KEY_F6 + ch - '7';
- }
- return KEY_UNKNOWN;
- }
- else if (last == '2')
- {
- if (ch >= '0' && ch <= '4')
- {
- // use inbuf() to prevent waiting keys
- if (num_in_buf() && dogetch() == '~') /* hope you are '~' */
- return KEY_F9 + ch - '0';
- }
- return KEY_UNKNOWN;
- }
- // if fall here, then escape sequence is broken.
- return KEY_UNKNOWN;
- }
- else // here is switch for default keys
- switch (ch) { // XXX: indent error
-#ifdef DEBUG
- case Ctrl('Q'):{
- struct rusage ru;
- getrusage(RUSAGE_SELF, &ru);
- vmsgf("sbrk: %d KB, idrss: %d KB, isrss: %d KB",
- ((int)sbrk(0) - 0x8048000) / 1024,
- (int)ru.ru_idrss, (int)ru.ru_isrss);
- }
- continue;
-#endif
- case Ctrl('L'):
- redrawwin();
- refresh();
- continue;
-
- case Ctrl('U'):
- if (currutmp != NULL && currutmp->mode != EDITING
- && currutmp->mode != LUSERS && currutmp->mode) {
-
+ assert(currutmp);
+ switch (ch)
+ {
+ case Ctrl('U') :
+ if ( currutmp->mode == EDITING ||
+ currutmp->mode == LUSERS ||
+ !currutmp->mode) {
+ return ch;
+ } else {
screen_backup_t old_screen;
int oldroll = roll;
int my_newfd;
@@ -555,147 +402,218 @@ igetch(void)
i_newfd = my_newfd;
roll = oldroll;
scr_restore(&old_screen);
- continue;
- }
- return ch;
- case KEY_TAB:
- if (PAGER_UI_IS(PAGER_UI_ORIG) || PAGER_UI_IS(PAGER_UI_NEW))
- if (currutmp != NULL && watermode > 0) {
- check_water_init();
- watermode = (watermode + water_which->count)
- % water_which->count + 1;
- t_display_new();
- continue;
- }
- return ch;
+ }
+ return KEY_INCOMPLETE;
+ // TODO 醜死了的 code ,等好心人 refine
case Ctrl('R'):
- if (currutmp == NULL)
- return (ch);
- if (currutmp->msgs[0].pid &&
- PAGER_UI_IS(PAGER_UI_OFO) && wmofo == NOTREPLYING) {
+ if (PAGER_UI_IS(PAGER_UI_OFO))
+ {
int my_newfd;
screen_backup_t old_screen;
+ if (!currutmp->msgs[0].pid ||
+ wmofo != NOTREPLYING)
+ break;
+
scr_dump(&old_screen);
my_newfd = i_newfd;
i_newfd = 0;
my_write2();
- scr_restore(&old_screen);
+ scr_restore(&old_screen);
i_newfd = my_newfd;
- continue;
- } else if (!PAGER_UI_IS(PAGER_UI_OFO)) {
- check_water_init();
- if (watermode > 0) {
- watermode = (watermode + water_which->count)
- % water_which->count + 1;
- t_display_new();
- continue;
- } else if (currutmp->mode == 0 &&
+ return KEY_INCOMPLETE;
+
+ }
+
+ // non-UFO
+ check_water_init();
+
+ if (watermode > 0)
+ {
+ watermode = (watermode + water_which->count)
+ % water_which->count + 1;
+ t_display_new();
+ return KEY_INCOMPLETE;
+ }
+ else if (watermode == 0 &&
+ currutmp->mode == 0 &&
(currutmp->chatid[0] == 2 || currutmp->chatid[0] == 3) &&
- water_which->count != 0 && watermode == 0) {
- /* 第二次按 Ctrl-R */
- watermode = 1;
- t_display_new();
- continue;
- } else if (watermode == -1 && currutmp->msgs[0].pid) {
- /* 第一次按 Ctrl-R (必須先被丟過水球) */
- screen_backup_t old_screen;
- int my_newfd;
- scr_dump(&old_screen);
-
- /* 如果正在talk的話先不處理對方送過來的封包 (不去select) */
- my_newfd = i_newfd;
- i_newfd = 0;
- show_call_in(0, 0);
- watermode = 0;
+ water_which->count != 0)
+ {
+ /* 第二次按 Ctrl-R */
+ watermode = 1;
+ t_display_new();
+ return KEY_INCOMPLETE;
+ }
+ else if (watermode == -1 &&
+ currutmp->msgs[0].pid)
+ {
+ /* 第一次按 Ctrl-R (必須先被丟過水球) */
+ screen_backup_t old_screen;
+ int my_newfd;
+ scr_dump(&old_screen);
+
+ /* 如果正在talk的話先不處理對方送過來的封包 (不去select) */
+ my_newfd = i_newfd;
+ i_newfd = 0;
+ show_call_in(0, 0);
+ watermode = 0;
#ifndef PLAY_ANGEL
- my_write(currutmp->msgs[0].pid, "水球丟過去: ",
- currutmp->msgs[0].userid, WATERBALL_GENERAL, NULL);
+ my_write(currutmp->msgs[0].pid, "水球丟過去: ",
+ currutmp->msgs[0].userid, WATERBALL_GENERAL, NULL);
#else
- switch (currutmp->msgs[0].msgmode) {
- case MSGMODE_TALK:
- case MSGMODE_WRITE:
- my_write(currutmp->msgs[0].pid, "水球丟過去: ",
- currutmp->msgs[0].userid, WATERBALL_GENERAL, NULL);
- break;
- case MSGMODE_FROMANGEL:
- my_write(currutmp->msgs[0].pid, "再問他一次: ",
- currutmp->msgs[0].userid, WATERBALL_ANGEL, NULL);
- break;
- case MSGMODE_TOANGEL:
- my_write(currutmp->msgs[0].pid, "回答小主人: ",
- currutmp->msgs[0].userid, WATERBALL_ANSWER, NULL);
- break;
- }
+ switch (currutmp->msgs[0].msgmode) {
+ case MSGMODE_TALK:
+ case MSGMODE_WRITE:
+ my_write(currutmp->msgs[0].pid, "水球丟過去: ",
+ currutmp->msgs[0].userid, WATERBALL_GENERAL, NULL);
+ break;
+ case MSGMODE_FROMANGEL:
+ my_write(currutmp->msgs[0].pid, "再問他一次: ",
+ currutmp->msgs[0].userid, WATERBALL_ANGEL, NULL);
+ break;
+ case MSGMODE_TOANGEL:
+ my_write(currutmp->msgs[0].pid, "回答小主人: ",
+ currutmp->msgs[0].userid, WATERBALL_ANSWER, NULL);
+ break;
+ }
#endif
- i_newfd = my_newfd;
+ i_newfd = my_newfd;
- /* 還原螢幕 */
- scr_restore(&old_screen);
- continue;
- }
+ /* 還原螢幕 */
+ scr_restore(&old_screen);
+ return KEY_INCOMPLETE;
}
- return ch;
+ break;
+
+ case KEY_TAB:
+ if (watermode <= 0 ||
+ (!PAGER_UI_IS(PAGER_UI_ORIG) || PAGER_UI_IS(PAGER_UI_NEW)))
+ break;
+
+ check_water_init();
+ watermode = (watermode + water_which->count)
+ % water_which->count + 1;
+ t_display_new();
+ return KEY_INCOMPLETE;
case Ctrl('T'):
- if (PAGER_UI_IS(PAGER_UI_ORIG) || PAGER_UI_IS(PAGER_UI_NEW)) {
- if (watermode > 0) {
- check_water_init();
- if (watermode > 1)
- watermode--;
- else
- watermode = water_which->count;
- t_display_new();
- continue;
- }
- }
- return ch;
+ if (watermode <= 0 ||
+ !(PAGER_UI_IS(PAGER_UI_ORIG) || PAGER_UI_IS(PAGER_UI_NEW)))
+ break;
+
+ check_water_init();
+ if (watermode > 1)
+ watermode--;
+ else
+ watermode = water_which->count;
+ t_display_new();
+ return KEY_INCOMPLETE;
case Ctrl('F'):
- if (PAGER_UI_IS(PAGER_UI_NEW)) {
- if (watermode > 0) {
- check_water_init();
- if (water_which_flag == (int)water_usies)
- water_which_flag = 0;
- else
- water_which_flag =
- (water_which_flag + 1) % (int)(water_usies + 1);
- if (water_which_flag == 0)
- water_which = &water[0];
- else
- water_which = swater[water_which_flag - 1];
- watermode = 1;
- t_display_new();
- continue;
- }
- }
- return ch;
+ if (watermode <= 0 || !PAGER_UI_IS(PAGER_UI_NEW))
+ break;
+
+ check_water_init();
+ if (water_which_flag == (int)water_usies)
+ water_which_flag = 0;
+ else
+ water_which_flag =
+ (water_which_flag + 1) % (int)(water_usies + 1);
+ if (water_which_flag == 0)
+ water_which = &water[0];
+ else
+ water_which = swater[water_which_flag - 1];
+ watermode = 1;
+ t_display_new();
+ return KEY_INCOMPLETE;
case Ctrl('G'):
- if (PAGER_UI_IS(PAGER_UI_NEW)) {
- if (watermode > 0) {
- check_water_init();
- water_which_flag = (water_which_flag + water_usies) % (water_usies + 1);
- if (water_which_flag == 0)
- water_which = &water[0];
- else
- water_which = swater[water_which_flag - 1];
- watermode = 1;
- t_display_new();
- continue;
+ if (watermode <= 0 || !PAGER_UI_IS(PAGER_UI_NEW))
+ break;
+
+ check_water_init();
+ water_which_flag = (water_which_flag + water_usies) % (water_usies + 1);
+ if (water_which_flag == 0)
+ water_which = &water[0];
+ else
+ water_which = swater[water_which_flag - 1];
+
+ watermode = 1;
+ t_display_new();
+ return KEY_INCOMPLETE;
+ }
+ return ch;
+}
+
+#ifndef vkey
+int
+vkey(void)
+{
+ return igetch();
+}
+#endif
+
+// virtual terminal keyboard context
+static VtkbdCtx vtkbd_ctx;
+
+int
+igetch(void)
+{
+ register int ch;
+
+ while (1)
+ {
+ ch = dogetch();
+
+ // convert virtual terminal keys
+ ch = vtkbd_process(ch, &vtkbd_ctx);
+ switch(ch)
+ {
+ case KEY_INCOMPLETE:
+ // XXX what if endless?
+ continue;
+
+ case KEY_ESC:
+ KEY_ESC_arg = vtkbd_ctx.esc_arg;
+ return ch;
+
+ case KEY_UNKNOWN:
+ return ch;
+
+ // common global hot keys...
+ case Ctrl('L'):
+ redrawwin();
+ refresh();
+ continue;
+#ifdef DEBUG
+ case Ctrl('Q'):
+ {
+ struct rusage ru;
+ getrusage(RUSAGE_SELF, &ru);
+ vmsgf("sbrk: %d KB, idrss: %d KB, isrss: %d KB",
+ ((int)sbrk(0) - 0x8048000) / 1024,
+ (int)ru.ru_idrss, (int)ru.ru_isrss);
}
- }
- return ch;
+ continue;
+#endif
+ }
- default:
- return ch;
+ // complex pager hot keys
+ if (currutmp)
+ {
+ ch = process_pager_keys(ch);
+ if (ch == KEY_INCOMPLETE)
+ continue;
}
+
+ return ch;
}
// should not reach here. just to make compiler happy.
- return 0;
+ return ch;
}
/*
diff --git a/mbbsd/name.c b/mbbsd/name.c
index 67381b00..803434b8 100644
--- a/mbbsd/name.c
+++ b/mbbsd/name.c
@@ -108,7 +108,7 @@ nc_cb_peek(int key, VGET_RUNTIME *prt, void *instance)
return VGETCB_NEXT;
break;
- case KEY_BS2: case KEY_BS: /* backspace */
+ case KEY_BS: /* backspace */
nc_int->dirty = -1;
break;
@@ -336,7 +336,7 @@ generalnamecomplete(const char *prompt, char *data, int len, size_t nmemb,
}
continue;
- } else if (ch == KEY_BS2 || ch == KEY_BS) { /* backspace */
+ } else if (ch == KEY_BS) { /* backspace */
if (ptr == 0)
continue;
morelist = -1;
diff --git a/mbbsd/register.c b/mbbsd/register.c
index 81d13cd0..5123f63b 100644
--- a/mbbsd/register.c
+++ b/mbbsd/register.c
@@ -2508,8 +2508,9 @@ regform2_validate_page(int dryrun)
#endif
case 's': // skip
case 'd': // delete
- case KEY_DEL: //delete
- if (ch == KEY_DEL) ch = 'd';
+ case Ctrl('D'): // delete
+ case KEY_DEL: // delete
+ if (ch == KEY_DEL || ch == Ctrl('D')) ch = 'd';
grayout(ci*2, ci*2+1, GRAYOUT_DARK);
move_ansi(ci*2, 4); outc(ch);
diff --git a/mbbsd/talk.c b/mbbsd/talk.c
index 16be0d89..1e27257a 100644
--- a/mbbsd/talk.c
+++ b/mbbsd/talk.c
@@ -1328,7 +1328,6 @@ do_talk_char(talkwin_t * twin, int ch, FILE *flog)
// complex data change
case KEY_BS:
- case KEY_BS2:
if (twin->curcol > 0)
{
int delta = 1;
@@ -1347,6 +1346,7 @@ do_talk_char(talkwin_t * twin, int ch, FILE *flog)
}
return;
+ case KEY_DEL:
case Ctrl('D'):
if (twin->curcol < line->len)
{
diff --git a/mbbsd/visio.c b/mbbsd/visio.c
index 4f6909d8..38efcbc9 100644
--- a/mbbsd/visio.c
+++ b/mbbsd/visio.c
@@ -1065,7 +1065,7 @@ vgetstring(char *_buf, int len, int flags, const char *defstr, const VGET_CALLBA
}
break;
- case KEY_BS: case KEY_BS2:
+ case KEY_BS:
if (rt.icurr > 0) {
// kill previous one charracter.
memmove(buf+rt.icurr-1, buf+rt.icurr, rt.iend-rt.icurr+1);
diff --git a/mbbsd/vtkbd.c b/mbbsd/vtkbd.c
new file mode 100644
index 00000000..8a00671f
--- /dev/null
+++ b/mbbsd/vtkbd.c
@@ -0,0 +1,279 @@
+/*
+ * 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)
+ * - on vt100, BS=0x7F
+ * - on vt100/xterm, BS=0x08, Delete=0x7F (Windows/VMX/DOS telnet)
+ * 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 "bbs.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;
+
+/* 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 0x7F:
+ case 0x08:
+ 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;
+}
+
+// vim:sw=4:sw=4:et