From c78d877d45496115e90c3584b063fd2bc72303b9 Mon Sep 17 00:00:00 2001 From: piaip Date: Mon, 28 Sep 2009 17:59:20 +0000 Subject: * refine kbd stack and move to common/bbs * rename visio to vtuikit git-svn-id: http://opensvn.csie.org/pttbbs/trunk/pttbbs@4888 63ad8ddf-47c3-0310-b6dd-a9e9d9715204 --- common/sys/Makefile | 3 +- common/sys/vtkbd.c | 337 +++++++++++++++ include/bbs.h | 2 +- include/common.h | 1 - include/visio.h | 153 ------- include/vtkbd.h | 8 +- include/vtuikit.h | 154 +++++++ mbbsd/Makefile | 4 +- mbbsd/io.c | 61 +-- mbbsd/telnet.c | 71 --- mbbsd/visio.c | 1192 --------------------------------------------------- mbbsd/vtkbd.c | 279 ------------ mbbsd/vtuikit.c | 1190 ++++++++++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 1728 insertions(+), 1727 deletions(-) create mode 100644 common/sys/vtkbd.c delete mode 100644 include/visio.h create mode 100644 include/vtuikit.h delete mode 100644 mbbsd/visio.c delete mode 100644 mbbsd/vtkbd.c create mode 100644 mbbsd/vtuikit.c diff --git a/common/sys/Makefile b/common/sys/Makefile index 717ccb0f..4de3a4cf 100644 --- a/common/sys/Makefile +++ b/common/sys/Makefile @@ -3,7 +3,8 @@ SRCROOT= ../.. .include "$(SRCROOT)/pttbbs.mk" -SRCS:= daemon.c file.c lock.c log.c net.c sort.c string.c time.c crypt.c record.c vector.c telnet.c +SRCS:= daemon.c file.c lock.c log.c net.c sort.c string.c time.c \ + crypt.c record.c vector.c telnet.c vtkbd.c LIB:= cmsys all: .depend 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 + * 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( [ | O (app) + * - Home/Ins/Del/End/PgUp/PgDn: [ <1~6> ~ + * - Shift-TAB: [ Z | [ 0 Z + * - F1~F4: [ 1 <1234> ~ | O + * - F5: [ 1 <5> ~ + * - F6-F8: [ 1 <789> ~ + * - F9-F12: [ 2 <0134> ~ + * - Num 0-9 *+,-./=ENTER: O + * + * Note: we don't support some rare terms like O described + * in Dell 2650 in order to prevent confusion. + * Num pad is also always converted to digits. + */ + +#include +#include +#include "vtkbd.h" + +/* VtkbdCtx.state */ +typedef enum { + VKSTATE_NORMAL = 0, + VKSTATE_ESC, // + VKSTATE_ESC_APP, // O + VKSTATE_ESC_QUOTE, // [ + VKSTATE_ZERO, // [ 0 (wait Z) + VKSTATE_ONE, // [ <1> + VKSTATE_TWO, // [ <2> + VKSTATE_TLIDE, // [ * (wait ~, return esc_arg) +} VKSTATES; + +#define VKRAW_BS 0x08 // \b = Ctrl('H') +#define VKRAW_ERASE 0x7F // 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: // + 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: // 0 + if (c != 'Z') + break; + + ctx->state = VKSTATE_NORMAL; + return KEY_STAB; + + case VKSTATE_ESC_APP: // 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: // [ + + 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: // [ 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: // [ 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 diff --git a/include/bbs.h b/include/bbs.h index d16d5b02..7892f646 100644 --- a/include/bbs.h +++ b/include/bbs.h @@ -44,7 +44,7 @@ extern "C" { #include "ansi.h" #include "vtkbd.h" -#include "visio.h" +#include "vtuikit.h" #include "statistic.h" #include "uflags.h" #include "pttstruct.h" diff --git a/include/common.h b/include/common.h index a2829047..e584cd54 100644 --- a/include/common.h +++ b/include/common.h @@ -155,7 +155,6 @@ #define ST_REJECT (IRH | HRM) #define QCAST int (*)(const void *, const void *) -#define Ctrl(c) (c & 037) #define chartoupper(c) ((c >= 'a' && c <= 'z') ? c+'A'-'a' : c) #define LEN_AUTHOR1 5 diff --git a/include/visio.h b/include/visio.h deleted file mode 100644 index f2879ce6..00000000 --- a/include/visio.h +++ /dev/null @@ -1,153 +0,0 @@ -// $Id$ -#ifndef _VISIO_H -#define _VISIO_H - -/* - * visio.h - * piaip's new implementation of visio - * - * see visio.c for license, usage, and introduction. - */ - -#include "bbs.h" -#include "ansi.h" // we need it. -#include - -// THEME DEFINITION ---------------------------------------------------- -#define VCLR_HEADER ANSI_COLOR(1;37;46) // was: TITLE_COLOR -#define VCLR_HEADER_MID ANSI_COLOR(1;33;46) -#define VCLR_HEADER_RIGHT ANSI_COLOR(1;37;46) -#define VCLR_HDR ANSI_COLOR(1;37;46) -#define VCLR_FOOTER_CAPTION ANSI_COLOR(0;34;46) -#define VCLR_FOOTER ANSI_COLOR(0;30;47) -#define VCLR_FOOTER_QUOTE ANSI_COLOR(0;31;47) -#define VCLR_ZA_CAPTION ANSI_COLOR(1;37;42) -#define VCLR_MSG_FLOAT ANSI_COLOR(1;33;46) -#define VCLR_MSG ANSI_COLOR(1;36;44) -#define VCLR_PAUSE_PAD ANSI_COLOR(1;34;44) -#define VCLR_PAUSE ANSI_COLOR(1;37;44) - -#define VCLR_INPUT_FIELD ANSI_COLOR(0;7) - -#define VMSG_PAUSE " 請按任意鍵繼續 " -#define VMSG_PAUSE_PAD "▄" -#define VMSG_MSG_FLOAT " [按任意鍵繼續]" -#define VMSG_MSG_PREFIX " ◆ " -#define VMSG_HDR_PREFIX "【 " -#define VMSG_HDR_POSTFIX " 】" - -// CONSTANT DEFINITION ------------------------------------------------- -#define VCOL_MAXW (INT16_MAX) -#define VCOL_MAXPRI (INT16_MAX) - -#define VFILL_DEFAULT (0x00) -// #define VFILL_NO_ANSI VFILL_DEFAULT -#define VFILL_HAS_ANSI (0x01) -// #define VVILL_LEFT_ALIGN VFILL_DEFAULT -#define VFILL_RIGHT_ALIGN (0x02) -// #define VFILL_HAS_BORDER VFILL_DEFAULT -#define VFILL_NO_BORDER (0x08) - -#define VGET_DEFAULT (0x00) -// #define VGET_DOECHO (VGET_DEFAULT) -#define VGET_NOECHO (0x01) -#define VGET_LOWERCASE (0x02) -#define VGET_DIGITS (0x04) -#define VGET_TRANSPARENT (0x08) -#define VGET_ASCII_ONLY (0x10) -#define VGET_PASSWORD (VGET_NOECHO | VGET_ASCII_ONLY) - -// DATATYPE DEFINITION ------------------------------------------------- -typedef void * VREFSCR; -typedef long VREFCUR; - -typedef short VCOLW; -typedef short VCOLPRI; - -typedef struct VCOL { - char *attr; // default attribute - VCOLW minw; // minimal width - VCOLW maxw; // max width - VCOLPRI pri; // priority (higher expands first) - - struct { - char has_ansi; // field data have ANSI escapes - char right_align; // align output to right side - char usewhole; // draw entire column and prevent borders - } flags; - -} VCOL; - -#define VGETCB_NONE (0) // do nothing -#define VGETCB_NEXT (1) // skip to next event loop -#define VGETCB_END (2) // finish input -#define VGETCB_ABORT (3) // clear buffer and finish - -typedef struct { - char *buf; - const int len; // callbacks should not change this. - int icurr; // cursor position - int iend; // buffer tail (= strlen(buf)) -} VGET_RUNTIME; - -typedef int (*VGET_FCALLBACK)(int key, VGET_RUNTIME *prt, void *instance); -typedef struct { - VGET_FCALLBACK peek; // called immediately after key hit - VGET_FCALLBACK data; // called before inserting character data - VGET_FCALLBACK post; // called after every data inserted into buffer. -} VGET_CALLBACKS; - -// API DEFINITION ---------------------------------------------------- - -// curses flavor -void prints(const char *fmt, ...) GCC_CHECK_FORMAT(1,2); -void mvprints(int y, int x, const char *fmt, ...) GCC_CHECK_FORMAT(3,4); -void mvouts(int y, int x, const char *str); - -// input history -int InputHistoryAdd (const char *s); -int InputHistoryExists (const char *s); -void InputHistoryPrev (char *s, int sz); -void InputHistoryNext (char *s, int sz); - -// v*: primitive rendering -void vpad (int n, const char *pattern); /// pad n fields by pattern - int vgety (void); /// return cursor position (y) -void vfill (int n, int flags, const char *s); /// fill n-width space with s -void vfillf (int n, int flags, const char *s, ...) GCC_CHECK_FORMAT(3,4); /// formatted version of vfill -void vbarlr (const char *l, const char *r); /// draw a left-right expanded bar with (l,r) -void vbarf (const char *s, ...) GCC_CHECK_FORMAT(1,2); /// vbarlr with formatted input (\t splits (l,r) -void vshowmsg(const char *msg); /// draw standard pause/message - -// v*: input widgets -// int vans(char *prompt); // prompt at bottom and return y/n in lower case. - int vmsg (const char *msg); /// draw standard pause/message and return input - int vmsgf (const char *fmt,...) GCC_CHECK_FORMAT(1,2); /// formatted input of vmsg - int vans (const char *msg); /// prompt and return (lowercase) single byte input - int vansf (const char *fmt,...) GCC_CHECK_FORMAT(1,2); /// formatted input of vans - -// vget: (y, x, ...) -int vgets (char *buf, int len, int flags); /// input with edit box control -int vgetstr (char *buf, int len, int flags, const char *str);/// input with default value -int vget (int y, int x, const char *prompt, char *buf, int len, int mode); -int vgetstring(char *_buf, int len, int flags, const char *defstr, const VGET_CALLBACKS *pcbs, void *instance); - -// vs_*: formatted and themed virtual screen layout -// you cannot use ANSI escapes in these APIs. -void vs_header (const char *title, const char *mid, const char *right); // vs_head, showtitle -void vs_hdr (const char *title); // vs_bar, stand_title -void vs_footer (const char *caption, const char *prompt); - -void vs_rectangle_simple(int l, int t, int r, int b); // draw a single line rectangle, not filling inside interior - -// columned output -void vs_cols_layout (const VCOL* cols, VCOLW *ws, int n); /// calculate VCOL to fit current screen in ws -void vs_cols (const VCOL* cols, const VCOLW *ws, int n, ...); - -// VREF: save and storing temporary objects (restore will also free object). -VREFSCR vscr_save (void); -void vscr_restore(VREFSCR); -VREFCUR vcur_save (void); -void vcur_restore(VREFCUR); - -#endif // _VISIO_H diff --git a/include/vtkbd.h b/include/vtkbd.h index f1e998c8..609787d4 100644 --- a/include/vtkbd.h +++ b/include/vtkbd.h @@ -25,6 +25,8 @@ #ifndef _VTKBD_H #define _VTKBD_H +#include + /* context definition */ typedef struct { int state; @@ -32,7 +34,11 @@ typedef struct { } VtkbdCtx; /* vtkbd API */ -int vtkbd_process(int c, VtkbdCtx *ctx); +int vtkbd_process(int c, VtkbdCtx *ctx); +ssize_t vtkbd_ignore_dbcs_evil_repeats(const unsigned char *buf, ssize_t len); + +/* key code macro */ +#define Ctrl(c) (c & 0x1F) /* common ASCII compatible keys definition */ #define KEY_TAB 9 diff --git a/include/vtuikit.h b/include/vtuikit.h new file mode 100644 index 00000000..09257345 --- /dev/null +++ b/include/vtuikit.h @@ -0,0 +1,154 @@ +// $Id$ +#ifndef _VISIO_H +#define _VISIO_H + +/* + * vtuikit.h + * piaip's new implementation of vtuikit + * + * see vtuikit.c for license, usage, and introduction. + */ + +#include "bbs.h" +#include "ansi.h" // we need it. +#include "vtkbd.h" // we usually use it +#include + +// THEME DEFINITION ---------------------------------------------------- +#define VCLR_HEADER ANSI_COLOR(1;37;46) // was: TITLE_COLOR +#define VCLR_HEADER_MID ANSI_COLOR(1;33;46) +#define VCLR_HEADER_RIGHT ANSI_COLOR(1;37;46) +#define VCLR_HDR ANSI_COLOR(1;37;46) +#define VCLR_FOOTER_CAPTION ANSI_COLOR(0;34;46) +#define VCLR_FOOTER ANSI_COLOR(0;30;47) +#define VCLR_FOOTER_QUOTE ANSI_COLOR(0;31;47) +#define VCLR_ZA_CAPTION ANSI_COLOR(1;37;42) +#define VCLR_MSG_FLOAT ANSI_COLOR(1;33;46) +#define VCLR_MSG ANSI_COLOR(1;36;44) +#define VCLR_PAUSE_PAD ANSI_COLOR(1;34;44) +#define VCLR_PAUSE ANSI_COLOR(1;37;44) + +#define VCLR_INPUT_FIELD ANSI_COLOR(0;7) + +#define VMSG_PAUSE " 請按任意鍵繼續 " +#define VMSG_PAUSE_PAD "▄" +#define VMSG_MSG_FLOAT " [按任意鍵繼續]" +#define VMSG_MSG_PREFIX " ◆ " +#define VMSG_HDR_PREFIX "【 " +#define VMSG_HDR_POSTFIX " 】" + +// CONSTANT DEFINITION ------------------------------------------------- +#define VCOL_MAXW (INT16_MAX) +#define VCOL_MAXPRI (INT16_MAX) + +#define VFILL_DEFAULT (0x00) +// #define VFILL_NO_ANSI VFILL_DEFAULT +#define VFILL_HAS_ANSI (0x01) +// #define VVILL_LEFT_ALIGN VFILL_DEFAULT +#define VFILL_RIGHT_ALIGN (0x02) +// #define VFILL_HAS_BORDER VFILL_DEFAULT +#define VFILL_NO_BORDER (0x08) + +#define VGET_DEFAULT (0x00) +// #define VGET_DOECHO (VGET_DEFAULT) +#define VGET_NOECHO (0x01) +#define VGET_LOWERCASE (0x02) +#define VGET_DIGITS (0x04) +#define VGET_TRANSPARENT (0x08) +#define VGET_ASCII_ONLY (0x10) +#define VGET_PASSWORD (VGET_NOECHO | VGET_ASCII_ONLY) + +// DATATYPE DEFINITION ------------------------------------------------- +typedef void * VREFSCR; +typedef long VREFCUR; + +typedef short VCOLW; +typedef short VCOLPRI; + +typedef struct VCOL { + char *attr; // default attribute + VCOLW minw; // minimal width + VCOLW maxw; // max width + VCOLPRI pri; // priority (higher expands first) + + struct { + char has_ansi; // field data have ANSI escapes + char right_align; // align output to right side + char usewhole; // draw entire column and prevent borders + } flags; + +} VCOL; + +#define VGETCB_NONE (0) // do nothing +#define VGETCB_NEXT (1) // skip to next event loop +#define VGETCB_END (2) // finish input +#define VGETCB_ABORT (3) // clear buffer and finish + +typedef struct { + char *buf; + const int len; // callbacks should not change this. + int icurr; // cursor position + int iend; // buffer tail (= strlen(buf)) +} VGET_RUNTIME; + +typedef int (*VGET_FCALLBACK)(int key, VGET_RUNTIME *prt, void *instance); +typedef struct { + VGET_FCALLBACK peek; // called immediately after key hit + VGET_FCALLBACK data; // called before inserting character data + VGET_FCALLBACK post; // called after every data inserted into buffer. +} VGET_CALLBACKS; + +// API DEFINITION ---------------------------------------------------- + +// curses flavor +void prints(const char *fmt, ...) GCC_CHECK_FORMAT(1,2); +void mvprints(int y, int x, const char *fmt, ...) GCC_CHECK_FORMAT(3,4); +void mvouts(int y, int x, const char *str); + +// input history +int InputHistoryAdd (const char *s); +int InputHistoryExists (const char *s); +void InputHistoryPrev (char *s, int sz); +void InputHistoryNext (char *s, int sz); + +// v*: primitive rendering +void vpad (int n, const char *pattern); /// pad n fields by pattern + int vgety (void); /// return cursor position (y) +void vfill (int n, int flags, const char *s); /// fill n-width space with s +void vfillf (int n, int flags, const char *s, ...) GCC_CHECK_FORMAT(3,4); /// formatted version of vfill +void vbarlr (const char *l, const char *r); /// draw a left-right expanded bar with (l,r) +void vbarf (const char *s, ...) GCC_CHECK_FORMAT(1,2); /// vbarlr with formatted input (\t splits (l,r) +void vshowmsg(const char *msg); /// draw standard pause/message + +// v*: input widgets +// int vans(char *prompt); // prompt at bottom and return y/n in lower case. + int vmsg (const char *msg); /// draw standard pause/message and return input + int vmsgf (const char *fmt,...) GCC_CHECK_FORMAT(1,2); /// formatted input of vmsg + int vans (const char *msg); /// prompt and return (lowercase) single byte input + int vansf (const char *fmt,...) GCC_CHECK_FORMAT(1,2); /// formatted input of vans + +// vget: (y, x, ...) +int vgets (char *buf, int len, int flags); /// input with edit box control +int vgetstr (char *buf, int len, int flags, const char *str);/// input with default value +int vget (int y, int x, const char *prompt, char *buf, int len, int mode); +int vgetstring(char *_buf, int len, int flags, const char *defstr, const VGET_CALLBACKS *pcbs, void *instance); + +// vs_*: formatted and themed virtual screen layout +// you cannot use ANSI escapes in these APIs. +void vs_header (const char *title, const char *mid, const char *right); // vs_head, showtitle +void vs_hdr (const char *title); // vs_bar, stand_title +void vs_footer (const char *caption, const char *prompt); + +void vs_rectangle_simple(int l, int t, int r, int b); // draw a single line rectangle, not filling inside interior + +// columned output +void vs_cols_layout (const VCOL* cols, VCOLW *ws, int n); /// calculate VCOL to fit current screen in ws +void vs_cols (const VCOL* cols, const VCOLW *ws, int n, ...); + +// VREF: save and storing temporary objects (restore will also free object). +VREFSCR vscr_save (void); +void vscr_restore(VREFSCR); +VREFCUR vcur_save (void); +void vcur_restore(VREFCUR); + +#endif // _VISIO_H diff --git a/mbbsd/Makefile b/mbbsd/Makefile index 98335987..fbaeb72c 100644 --- a/mbbsd/Makefile +++ b/mbbsd/Makefile @@ -10,14 +10,14 @@ 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 vtkbd.o +NETOBJS = mbbsd.o io.o term.o telnet.o TALKOBJS = talk.o chat.o friend.o UTILOBJS = stuff.o kaede.o convert.o name.o syspost.o PAGEROBJS= more.o pmore.o PLUGOBJS = calendar.o ordersong.o gamble.o vice.o angel.o CHESSOBJS= chess.o chc.o chc_tab.o ch_go.o ch_gomo.o ch_dark.o ch_reversi.o GAMEOBJS = card.o chicken.o -OBJS:= admin.o assess.o edit.o menu.o xyz.o var.o visio.o \ +OBJS:= admin.o assess.o edit.o menu.o xyz.o var.o vtuikit.o \ vote.o voteboard.o \ $(COREOBJS) $(ACCOBJS) $(NETOBJS) $(TALKOBJS) $(UTILOBJS) \ $(PAGEROBJS) $(PLUGOBJS) \ diff --git a/mbbsd/io.c b/mbbsd/io.c index e0abbe31..3003b2af 100644 --- a/mbbsd/io.c +++ b/mbbsd/io.c @@ -158,7 +158,7 @@ static int i_newfd = 0; static struct timeval i_to, *i_top = NULL; static int (*flushf) () = NULL; -void +inline void add_io(int fd, int timeout) { i_newfd = fd; @@ -171,7 +171,7 @@ add_io(int fd, int timeout) i_top = NULL; } -int +inline int num_in_buf(void) { if (ibufsize <= icurrchar) @@ -179,12 +179,42 @@ num_in_buf(void) return ibufsize - icurrchar; } -int +inline int input_isfull(void) { return ibufsize >= IBUFSIZE; } +inline static ssize_t +wrapped_tty_read(unsigned char *buf, size_t max) +{ + /* tty_read will handle abort_bbs. + * len <= 0: read more */ + ssize_t len = tty_read(buf, max); + if (len <= 0) + return len; + + // apply additional converts +#ifdef DBCSAWARE + if (ISDBCSAWARE() && HasUserFlag(UF_DBCS_DROP_REPEAT)) + len = vtkbd_ignore_dbcs_evil_repeats(buf, len); +#endif +#ifdef CONVERT + len = input_wrapper(inbuf, len); +#endif +#ifdef DBG_OUTRPT + { + static char xbuf[128]; + sprintf(xbuf, ESC_STR "[s" ESC_STR "[2;1H [%ld] " + ESC_STR "[u", len); + write(1, xbuf, strlen(xbuf)); + fsync(1); + } +#endif // DBG_OUTRPT + return len; +} + + /* * dogetch() is not reentrant-safe. SIGUSR[12] might happen at any time, and * dogetch() might be called again, and then ibufsize/icurrchar/inbuf might @@ -246,24 +276,7 @@ dogetch(void) STATINC(STAT_SYSREADSOCKET); do { - len = tty_read(inbuf, IBUFSIZE); - /* tty_read will handle abort_bbs. - * len <= 0: read more */ -#ifdef CONVERT - if(len > 0) - len = input_wrapper(inbuf, len); -#endif -#ifdef DBG_OUTRPT - // if (0) - { - static char xbuf[128]; - sprintf(xbuf, ESC_STR "[s" ESC_STR "[2;1H [%ld] " - ESC_STR "[u", len); - write(1, xbuf, strlen(xbuf)); - fsync(1); - } -#endif // DBG_OUTRPT - + len = wrapped_tty_read(inbuf, IBUFSIZE); } while (len <= 0); ibufsize = len; @@ -677,11 +690,7 @@ peek_input(float f, int c) if (wait_input(f, 1) && (IBUFSIZE > ibufsize)) { - int len = tty_read(inbuf + ibufsize, IBUFSIZE - ibufsize); -#ifdef CONVERT - if(len > 0) - len = input_wrapper(inbuf+ibufsize, len); -#endif + int len = wrapped_tty_read(inbuf + ibufsize, IBUFSIZE - ibufsize); if (len > 0) ibufsize += len; } diff --git a/mbbsd/telnet.c b/mbbsd/telnet.c index 4e7daf53..7cf1fee5 100644 --- a/mbbsd/telnet.c +++ b/mbbsd/telnet.c @@ -43,72 +43,6 @@ telnet_init(int do_init_cmd) telnet_ctx_send_init_cmds(ctx); } -#if defined(DBCSAWARE) -ssize_t -dbcs_detect_evil_repeats(unsigned char *buf, ssize_t l) -{ - // determine DBCS repeats by evil clients (ref: io.c) - if (l == 2) - { - // XXX l=2 is dangerous. hope we are not in telnet IAC state... - // BS: \b - // BS2: \x7f - // DEL2: Ctrl('D') (KKMan3 also treats Ctrl('D') as DBCS DEL) - if (buf[0] != buf[1]) - return l; - - // Note: BS/DEL behavior on different clients: - // screen/telnet:BS=0x7F, DEL=^[3~ - // PCMan2004: BS=0x7F, DEL=^[3~ - // KKMan3: BS=0x1b, DEL=0x7F - // WinXP telnet: BS=0x1b, DEL=0x7F - if (buf[0] == '\b' || - buf[0] == '\x7f' || - buf[0] == Ctrl('D')) - return l-1; - } - else if (l == 6) - { - // RIGHT: ESC_CHR "OC" or ESC_CHR "[C" - // LEFT: ESC_CHR "OD" or ESC_CHR "[D" - if (buf[2] != 'C' && buf[2] != 'D') - return l; - - if ( buf[0] == ESC_CHR && - (buf[1] == '[' || buf[1] == 'O') && - buf[0] == buf[3] && - buf[1] == buf[4] && - buf[2] == buf[5]) - return l-3; - } - else if (l == 8) - { - // RIGHT: ESC_CHR "[OC" - // LEFT: ESC_CHR "[OD" - // DEL: ESC_STR "[3~" // vt220 - if (buf[2] != '3' && buf[2] != 'O') - return l; - - if (buf[0] != ESC_CHR || - buf[1] != '[' || - buf[4] != buf[0] || - buf[5] != buf[1] || - buf[6] != buf[2] || - buf[7] != buf[3]) - return l; - - if (buf[2] == '3' && - buf[3] == '~') - return l-4; - - if ( buf[2] == 'O' && - (buf[3] == 'C' || buf[3] == 'D') ) - return l-4; - } - return l; -} -#endif - /* tty_read * read from tty, process telnet commands if raw connection. * return: >0 = length, <=0 means read more, abort/eof is automatically processed. @@ -122,11 +56,6 @@ tty_read(unsigned char *buf, size_t max) if(l == 0 || (l < 0 && !(errno == EINTR || errno == EAGAIN))) abort_bbs(0); -#if defined(DBCSAWARE) - if (ISDBCSAWARE() && HasUserFlag(UF_DBCS_DROP_REPEAT)) - l = dbcs_detect_evil_repeats(buf, l); -#endif - if(!raw_connection || l <= 0) return l; diff --git a/mbbsd/visio.c b/mbbsd/visio.c deleted file mode 100644 index 38efcbc9..00000000 --- a/mbbsd/visio.c +++ /dev/null @@ -1,1192 +0,0 @@ -/* $Id$ */ -#include "bbs.h" - -/* - * visio.c - * piaip's new implementation of visio - * (visio: virtual screen input output, the name from Maple3) - * - * This is not the original visio.c from Maple3. - * We just borrowed its file name and few API names/prototypes - * then re-implemented everything from scratch :) - * - * We will try to keep the API behavior similiar (to help porting) - * but won't stick to it. - * Maybe at the end only 'vmsg' and 'vmsgf' will still be compatible.... - * - * m3 visio = (ptt) visio+screen/term. - * - * Author: Hung-Te Lin (piaip), April 2008. - * - * Copyright (c) 2008 Hung-Te Lin - * 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. - * - * To add API here, please... - * (1) name the API in prefix of 'v'. - * (2) use only screen.c APIs. - * (3) take care of wide screen and DBCS. - * (4) utilize the colos in visio.h, and name asa VCLR_* (visio color) - */ - -// ---- DEFINITION --------------------------------------------------- -#define MAX_COL (t_columns-1) -#define SAFE_MAX_COL (MAX_COL-1) -#define VBUFLEN (ANSILINELEN) - -// this is a special strlen to speed up processing. -// warning: x MUST be #define x "msg". -// otherwise you need to use real strlen. -#define MACROSTRLEN(x) (sizeof(x)-1) - -// ---- UTILITIES ---------------------------------------------------- -inline void -outnc(int n, unsigned char c) -{ - while (n-- > 0) - outc(c); -} - -inline void -nblank(int n) -{ - outnc(n, ' '); -} - -static inline void -fillns(int n, const char *s) -{ - while (n > 0 && *s) - outc(*s++), n--; - if (n > 0) - outnc(n, ' '); -} - -static inline void -fillns_ansi(int n, const char *s) -{ - int d = strat_ansi(n, s); - if (d < 0) { - outs(s); nblank(-d); - } else { - outns(s, d); - } -} - -// ---- VREF API -------------------------------------------------- - -/** - * vscr_save(): 傳回目前畫面的備份物件。 - */ -VREFSCR -vscr_save(void) -{ - // TODO optimize memory allocation someday. - screen_backup_t *o = (screen_backup_t*)malloc(sizeof(screen_backup_t)); - assert(o); - scr_dump(o); - return o; -} - -/** - * vscr_restore(obj): 使用並刪除畫面的備份物件。 - */ -void -vscr_restore(VREFSCR obj) -{ - screen_backup_t *o = (screen_backup_t*)obj; - if (o) - { - scr_restore(o); - memset(o, 0, sizeof(screen_backup_t)); - free(o); - } -} - -/** - * vcur_save(): 傳回目前游標的備份物件。 - */ -VREFCUR -vcur_save(void) -{ - // XXX 偷懶不 new object 了, pointer 夠大 - int y, x; - VREFCUR v; - getyx(&y, &x); - v = ((unsigned short)y << 16) | (unsigned short)x; - return v; -} - -/** - * vcur_restore(obj): 使用並刪除游標的備份物件。 - */ -void -vcur_restore(VREFCUR o) -{ - int y, x; - y = (unsigned int)(o); - x = (unsigned short)(y & 0xFFFF); - y = (unsigned short)(y >> 16); - move(y, x); -} - -// ---- LOW LEVEL API ----------------------------------------------- - -/** - * prints(fmt, ...): 使用 outs/outc 輸出並格式化字串。 - */ -void -prints(const char *fmt,...) -{ - va_list args; - char buff[VBUFLEN]; - - va_start(args, fmt); - vsnprintf(buff, sizeof(buff), fmt, args); - va_end(args); - - outs(buff); -} - -/** - * mvprints(int y, int x, fmt, ...): 使用 mvouts 輸出並格式化字串。 - */ -void -mvprints(int y, int x, const char *fmt, ...) -{ - va_list args; - char buff[VBUFLEN]; - - va_start(args, fmt); - vsnprintf(buff, sizeof(buff), fmt, args); - va_end(args); - - mvouts(x, y, buff); -} - -/** - * mvouts(y, x, str): = mvaddstr - */ -void -mvouts(int y, int x, const char *str) -{ - move(y, x); - clrtoeol(); - SOLVE_ANSI_CACHE(); - outs(str); -} - -/** - * vfill(n, flags, s): 印出並填滿 n 個字元的空間 - * - * @param n space to occupy - * @param flags VFILL_* parameters - * @param s string to display - */ -void -vfill(int n, int flags, const char *s) -{ - // warning: flag determination must take care of default values. - char has_ansi = ((flags & VFILL_HAS_ANSI) || (*s == ESC_CHR)); - char has_border = !(flags & VFILL_NO_BORDER); - - if (n < 1) - return; - - // quick return - if (!*s) - { - nblank(n); - return; - } - - // calculate border size (always draw because n > 0) - if (has_border) - n--; - - if (n > 0) - { - if (flags & VFILL_RIGHT_ALIGN) - { - // right-align - int l = has_ansi ? strlen_noansi(s) : strlen(s); - - if (l >= n) // '=' prevents blanks - l = n; - else { - nblank(n - l); - n = l; - } - // leave the task to left-align - } - - // left-align - if (has_ansi) - fillns_ansi(n, s); - else - fillns(n, s); - } - - // print border if required - if (has_border) - outc(' '); - - // close fill. - if (has_ansi) - outs(ANSI_RESET); -} - -/** - * vfill(n, flags, fmt, ...): 使用 vfill 輸出並格式化字串。 - * - * @param n space to occupy - * @param flags VFILL_* parameters - * @param fmt string to display - */ -void -vfillf(int n, int flags, const char *s, ...) -{ - va_list args; - char buff[VBUFLEN]; - - va_start(args, s); - vsnprintf(buff, sizeof(buff), s, args); - va_end(args); - - vfill(n, flags, s); -} - -/** - * vpad(n, pattern): 填滿 n 個字元 (使用的格式為 pattern) - * - * @param n 要填滿的字元數 (無法填滿時會使用空白填補) - * @param pattern 填充用的字串 - */ -inline void -vpad(int n, const char *pattern) -{ - int len = strlen(pattern); - // assert(len > 0); - - while (n >= len) - { - outs(pattern); - n -= len; - } - if (n) nblank(n); -} - -/** - * vgety(): 取得目前所在位置的行數 - * - * 考慮到 ANSI 系統,getyx() 較為少用且危險。 - * vgety() 安全而明確。 - */ -inline int -vgety(void) -{ - int y, x; - getyx(&y, &x); - return y; -} - -// ---- HIGH LEVEL API ----------------------------------------------- - -/** - * vshowmsg(s): 在底部印出指定訊息或單純的暫停訊息 - * - * @param s 指定訊息。 NULL: 任意鍵繼續。 s: 若有 \t 則後面字串靠右 (若無則顯示任意鍵) - */ -void -vshowmsg(const char *msg) -{ - int w = SAFE_MAX_COL; - move(b_lines, 0); clrtoeol(); - - if (!msg) - { - // print default message in middle - outs(VCLR_PAUSE_PAD); - outc(' '); // initial one space - - // VMSG_PAUSE MUST BE A #define STRING. - w -= MACROSTRLEN(VMSG_PAUSE); // strlen_noansi(VMSG_PAUSE); - w--; // initial space - vpad(w/2, VMSG_PAUSE_PAD); - outs(VCLR_PAUSE); - outs(VMSG_PAUSE); - outs(VCLR_PAUSE_PAD); - vpad(w - w/2, VMSG_PAUSE_PAD); - } else { - // print in left, with floating (if \t exists) - char *pfloat = strchr(msg, '\t'); - int szfloat = 0; - int nmsg = 0; - - // print prefix - w -= MACROSTRLEN(VMSG_MSG_PREFIX); // strlen_noansi(VMSG_MSG_PREFIX); - outs(VCLR_MSG); - outs(VMSG_MSG_PREFIX); - - // if have float, calculate float size - if (pfloat) { - nmsg = pfloat - msg; - w -= strlen_noansi(msg) -1; // -1 for \t - pfloat ++; // skip \t - szfloat = strlen_noansi(pfloat); - } else { - pfloat = VMSG_MSG_FLOAT; - szfloat = MACROSTRLEN(VMSG_MSG_FLOAT); // strlen_noansi() - w -= strlen_noansi(msg) + szfloat; - } - - // calculate if we can display float - if (w < 0) - { - w += szfloat; - szfloat = 0; - } - - // print msg body - if (nmsg) - outns(msg, nmsg); - else - outs(msg); - - // print padding for floats - if (w > 0) - nblank(w); - - // able to print float? - if (szfloat) - { - outs(VCLR_MSG_FLOAT); - outs(pfloat); - } - } - - // safe blank - outs(" " ANSI_RESET); -} - -/** - * vans(s): 在底部印出訊息與小輸入欄,並傳回使用者的輸入(轉為小寫)。 - * - * @param s 指定訊息,見 vshowmsg - */ -int -vans(const char *msg) -{ - char buf[3]; - - move(b_lines, 0); - clrtoeol(); SOLVE_ANSI_CACHE(); - outs(msg); - vgets(buf, sizeof(buf), VGET_LOWERCASE); - return (unsigned char)buf[0]; -} - -/** - * vansf(s, ...): 在底部印出訊息與小輸入欄,並傳回使用者的輸入(轉為小寫)。 - * - * @param s 指定訊息,見 vshowmsg - */ -int -vansf(const char *fmt, ...) -{ - char msg[VBUFLEN]; - va_list ap; - va_start(ap, fmt); - vsnprintf(msg, sizeof(msg), fmt, ap); - va_end(ap); - - return vans(msg); -} - -/** - * vmsg(s): 在底部印出指定訊息或單純的暫停訊息,並傳回使用者的按鍵。 - * - * @param s 指定訊息,見 vshowmsg - */ -int -vmsg(const char *msg) -{ - int i = 0; - - vshowmsg(msg); - - // wait for key - do { - i = igetch(); - } while( i == 0 ); - - // clear message bar - move(b_lines, 0); - clrtoeol(); - - return i; -} - - -/** - * vmsgf(s, ...): 格式化輸出暫停訊息並呼叫 vmsg)。 - * - * @param s 格式化的訊息 - */ -int -vmsgf(const char *fmt,...) -{ - char msg[VBUFLEN]; - va_list ap; - va_start(ap, fmt); - vsnprintf(msg, sizeof(msg), fmt, ap); - va_end(ap); - - return vmsg(msg); -} - -/** - * vbarf(s, ...): 格式化輸出左右對齊的字串 (MAX_COL) - * - * @param s 格式化字串 (\t 後的內容會對齊右端) - */ -void -vbarf(const char *s, ...) -{ - char msg[VBUFLEN], *s2; - va_list ap; - va_start(ap, s); - vsnprintf(msg, sizeof(msg), s, ap); - va_end(ap); - - s2 = strchr(msg, '\t'); - if (s2) *s2++ = 0; - else s2 = ""; - - return vbarlr(msg, s2); -} - -/** - * vbarlr(l, r): 左右對齊畫滿螢幕 (MAX_COL) - * 注意: 目前的實作自動認定游標已在行開頭。 - * - * @param l 靠左對齊的字串 (可含 ANSI 碼) - * @param r 靠右對齊的字串 (可含 ANSI 碼,後面不會補空白) - */ -void -vbarlr(const char *l, const char *r) -{ - // TODO strlen_noansi 跑兩次... 其實 l 可以邊 output 邊算。 - int szl = strlen_noansi(l), - szr = strlen_noansi(r); - - // assume we are already in (y, 0) - clrtoeol(); - outs(l); - szl = MAX_COL - szl; - - if (szl > szr) - { - nblank(szl - szr); - szl = szr; - } - - if (szl == szr) - outs(r); - else if (szl > 0) - nblank(szl); - - outs(ANSI_RESET); -} - -void -vs_rectangle_simple(int l, int t, int r, int b) -{ - int ol = l; - - assert( l + 4 <= r && - t + 2 <= b); - - // draw top line - move(t++, ol); l = ol+2; - outs("┌"); - while (l < r-2) { outs("─"); l+= 2; } - outs("┐"); - if (l+2 < r) outs(" "); - - while (t < b) - { - move(t++, ol); l = ol+2; - outs("│"); - // while (l < r-2) { outs(" "); l+= 2; } - l += (r-l-1)/2*2; - move_ansi(t-1, l); - outs("│"); - if (l+2 < r) outs(" "); - } - - // draw bottom line - move(t++, ol); l = ol+2; - outs("└"); - while (l < r-2) { outs("─"); l+= 2; } - outs("┘"); - if (l+2 < r) outs(" "); -} - -// ---- THEMED FORMATTING OUTPUT ------------------------------------- - -/** - * vs_header(title, mid, right): 清空螢幕並輸出完整標題 (MAX_COL) - * - * @param title: 靠左的主要標題,不會被切斷。 - * @param mid: 置中說明,可被切齊。 - * @param right: 靠右的說明,空間夠才會顯示。 - */ -void -vs_header(const char *title, const char *mid, const char *right) -{ - int w = MAX_COL; - int szmid = mid ? strlen(mid) : 0; - int szright = right ? strlen(right) : 0; - - clear(); - outs(VCLR_HEADER); - - if (title) - { - outs(VMSG_HDR_PREFIX); - outs(title); - outs(VMSG_HDR_POSTFIX); - w -= MACROSTRLEN(VMSG_HDR_PREFIX) + MACROSTRLEN(VMSG_HDR_POSTFIX); - w -= strlen(title); - } - - // determine if we can display right message, and - // if if we need to truncate mid. - if (szmid + szright > w) - szright = 0; - - if (szmid >= w) - szmid = w; - else { - int l = (MAX_COL-szmid)/2; - l -= (MAX_COL-w); - if (l > 0) - nblank(l), w -= l; - } - - if (szmid) { - outs(VCLR_HEADER_MID); - outns(mid, szmid); - outs(VCLR_HEADER); - } - nblank(w - szmid); - - if (szright) { - outs(VCLR_HEADER_RIGHT); - outs(right); - } - outs(ANSI_RESET "\n"); -} - -/** - * vs_hdr(title): 清空螢幕並輸出簡易的標題 - * - * @param title - */ -void -vs_hdr(const char *title) -{ - clear(); - outs(VCLR_HDR VMSG_HDR_PREFIX); - outs(title); - outs(VMSG_HDR_POSTFIX ANSI_RESET "\n"); -} - -/** - * vs_footer(caption, msg): 在螢幕底部印出格式化的 caption msg (不可含 ANSI 碼) - * - * @param caption 左邊的分類字串 - * @param msg 訊息字串, \t 後文字靠右、最後面會自動留一個空白。 - */ -void -vs_footer(const char *caption, const char *msg) -{ - int i = 0; - move(b_lines, 0); clrtoeol(); - - if (caption) - { - outs(VCLR_FOOTER_CAPTION); - outs(caption); - i += (*caption == ESC_CHR) ? - strlen_noansi(caption) : - strlen(caption); - } - - if (!msg) msg = ""; - outs(VCLR_FOOTER); - - while (*msg && i < SAFE_MAX_COL) - { - if (*msg == '(') - { - outs(VCLR_FOOTER_QUOTE); - } - else if (*msg == '\t') - { - // if we don't have enough space, ignore whole. - int l = strlen(++msg); - if (i + l > SAFE_MAX_COL) break; - l = SAFE_MAX_COL - l - i; - nblank(l); - i += l; - continue; - } - outc(*msg); i++; - if (*msg == ')') - outs(VCLR_FOOTER); - msg ++; - } - nblank(SAFE_MAX_COL-i); - outc(' '); - outs(ANSI_RESET); -} - -/** - * vs_cols_layout(cols, ws, n): 依據 cols (大小 n) 的定義計算適合的行寬於 ws - */ - -void -vs_cols_layout(const VCOL *cols, VCOLW *ws, int n) -{ - int i, tw; - VCOLPRI pri1 = cols[0].pri; - memset(ws, 0, sizeof(VCOLW) * n); - - // first run, calculate minimal size - for (i = 0, tw = 0; i < n; i++) - { - // drop any trailing if required - if (tw + cols[i].minw > MAX_COL) - break; - ws[i] = cols[i].minw; - tw += ws[i]; - } - - if (tw < MAX_COL) { - // calculate highest priorities - // (pri1 already set to col[0].pri) - for (i = 1; i < n; i++) - { - if (cols[i].pri > pri1) - pri1 = cols[i].pri; - } - } - - // try to iterate through all. - while (tw < MAX_COL) { - char run = 0; - - // also update pri2 here for next run. - VCOLPRI pri2 = cols[0].pri; - - for (i = 0; i < n; i++) - { - // if reach max, skip. - if (ws[i] >= cols[i].maxw) - continue; - - // lower priority, update pri2 and skip. - if (cols[i].pri < pri1) - { - if (cols[i].pri > pri2) - pri2 = cols[i].pri; - continue; - } - - // now increase fields - ws[i] ++; - if (++tw >= MAX_COL) break; - run ++; - } - - // if no more fields... - if (!run) { - if (pri1 <= pri2) // no more priorities - break; - pri1 = pri2; // try lower priority. - } - } -} - -/** - * vs_cols: 依照已經算好的欄位大小進行輸出 - */ -void -vs_cols(const VCOL *cols, const VCOLW *ws, int n, ...) -{ - int i = 0, w = 0; - char *s = NULL; - - va_list ap; - va_start(ap, n); - - for (i = 0; i < n; i++, cols++, ws++) - { - int flags = 0; - s = va_arg(ap, char*); - - // quick check input. - if (!s) - { - s = ""; - } - w = *ws; - - if (cols->attr) - outs(cols->attr); - - // build vfill flag - if (cols->flags.right_align) flags |= VFILL_RIGHT_ALIGN; - if (cols->flags.usewhole) flags |= VFILL_NO_BORDER; - - vfill(w, flags, s); - - if (cols->attr) - outs(ANSI_RESET); - } - va_end(ap); - - // end line - outs(ANSI_RESET "\n"); -} - -//////////////////////////////////////////////////////////////////////// -// DBCS Aware Helpers -//////////////////////////////////////////////////////////////////////// - -#ifdef DBCSAWARE -# define CHKDBCSTRAIL(_buf,_i) (ISDBCSAWARE() && DBCS_Status(_buf, _i) == DBCS_TRAILING) -#else // !DBCSAWARE -# define CHKDBCSTRAIL(buf,i) (0) -#endif // !DBCSAWARE - -//////////////////////////////////////////////////////////////////////// -// History Helpers -//////////////////////////////////////////////////////////////////////// -// -#define IH_MAX_ENTRIES (12) // buffer size = approx. 1k -#define IH_MIN_SIZE (2) // only keep string >= 2 bytes - -typedef struct { - int icurr; // current retrival pointer - int iappend; // new location to append - char buf[IH_MAX_ENTRIES][STRLEN]; -} InputHistory; - -static InputHistory ih; // everything intialized to zero. - -int -InputHistoryExists(const char *s) -{ - int i = 0; - - for (i = 0; i < IH_MAX_ENTRIES; i++) - if (strcmp(s, ih.buf[i]) == 0) - return i+1; - - return 0; -} - -int -InputHistoryAdd(const char *s) -{ - int i = 0; - int l = strlen(s); - - if (l < IH_MIN_SIZE) - return 0; - - i = InputHistoryExists(s); - if (i > 0) // found - { - i--; // i points to valid index - assert(i < IH_MAX_ENTRIES); - - // change order: just delete it. - ih.buf[i][0] = 0; - } - - // now append s. - strlcpy(ih.buf[ih.iappend], s, sizeof(ih.buf[ih.iappend])); - ih.iappend ++; - ih.iappend %= IH_MAX_ENTRIES; - ih.icurr = ih.iappend; - - return 1; -} - -static void -InputHistoryDelta(char *s, int sz, int d) -{ - int i, xcurr = 0; - for (i = 1; i <= IH_MAX_ENTRIES; i++) - { - xcurr = (ih.icurr+ IH_MAX_ENTRIES + d*i)%IH_MAX_ENTRIES; - if (ih.buf[xcurr][0]) - { - ih.icurr = xcurr; - - // copy buffer - strlcpy(s, ih.buf[ih.icurr], sz); - - // DBCS safe - i = strlen(s); - if (DBCS_Status(s, i) == DBCS_TRAILING) - s[i-1] = 0; - break; - } - } -} - -void -InputHistoryPrev(char *s, int sz) -{ - InputHistoryDelta(s, sz, -1); -} - -void -InputHistoryNext(char *s, int sz) -{ - InputHistoryDelta(s, sz, +1); -} - -//////////////////////////////////////////////////////////////////////// -// vget*: mini editbox -//////////////////////////////////////////////////////////////////////// - -static inline int -_vgetcbhandler(VGET_FCALLBACK cbptr, int *pabort, int c, VGET_RUNTIME *prt, void *instance) -{ - if (!cbptr) - return 0; - - switch(cbptr(c, prt, instance)) - { - case VGETCB_NONE: - assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); - return 0; - - case VGETCB_NEXT: - assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); - return 1; - - case VGETCB_END: - assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); - *pabort = 1; - return 1; - - case VGETCB_ABORT: - assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); - *pabort = 1; - prt->icurr = 0; - prt->iend = 0; - prt->buf[0] = 0; - return 1; - } - assert(0); // shall never reach here - return 0; -} - -int -vgetstring(char *_buf, int len, int flags, const char *defstr, const VGET_CALLBACKS *pcbs, void *instance) -{ - // rt.iend points to NUL address, and - // rt.icurr points to cursor. - int line, col; - int abort = 0; - int c; - char ismsgline = 0; - - // callback - VGET_CALLBACKS cb = {NULL}; - - // always use internal buffer to prevent temporary input issue. - char buf[STRLEN] = ""; // zero whole. - - // runtime structure - VGET_RUNTIME rt = { buf, len > STRLEN ? STRLEN : len }; - - // it is wrong to design input with larger buffer - // than STRLEN. Although we support large screen, - // inputting huge line will just make troubles... - if (len > STRLEN) len = STRLEN; - assert(len <= sizeof(buf) && len >= 2); - - // memset(buf, 0, len); - if (defstr && *defstr) - { - strlcpy(buf, defstr, len); - strip_ansi(buf, buf, STRIP_ALL); // safer... - rt.icurr = rt.iend = strlen(buf); - } - - // setup callbacks - if (pcbs) - cb = *pcbs; - - getyx(&line, &col); // now (line,col) is the beginning of our new fields. - - // XXX be compatible with traditional... - if (line == b_lines - msg_occupied) - ismsgline = 1; - - if (ismsgline) - msg_occupied ++; - - // main loop - while (!abort) - { - if (!(flags & VGET_NOECHO)) - { - // print current buffer - move(line, col); - clrtoeol(); - SOLVE_ANSI_CACHE(); - - if (!(flags & VGET_TRANSPARENT)) - outs(VCLR_INPUT_FIELD); // change color to prompt fields - - vfill(len, 0, buf); - - if (!(flags & VGET_TRANSPARENT)) - outs(ANSI_RESET); - - // move to cursor position - move(line, col+rt.icurr); - } else { - // to simulate the "clrtoeol" behavior... - // XXX make this call only once? or not? - clrtoeol(); - } - c = vkey(); - - // callback 1: peek - if (_vgetcbhandler(cb.peek, &abort, c, &rt, instance)) - continue; - - // standard key bindings - switch(c) { - // history navigation - case KEY_DOWN: case Ctrl('N'): - c = KEY_DOWN; - // let UP do the magic. - case KEY_UP: case Ctrl('P'): - if ((flags & VGET_NOECHO) || - (flags & VGET_DIGITS)) - { - bell(); - continue; - } - - // NOECHO is already checked... - if (!InputHistoryExists(buf)) - InputHistoryAdd(buf); - - if (c == KEY_DOWN) - InputHistoryNext(buf, len); - else - InputHistoryPrev(buf, len); - - rt.icurr = rt.iend = strlen(buf); - break; - - // exiting keys - case KEY_ENTER: - abort = 1; - break; - - case Ctrl('C'): - rt.icurr = rt.iend = 0; - buf[0] = 0; - buf[1] = c; - abort = 1; - break; - - // standard navigation - case KEY_HOME: case Ctrl('A'): - rt.icurr = 0; - break; - - case KEY_END: case Ctrl('E'): - rt.icurr = rt.iend; - break; - - case KEY_LEFT: case Ctrl('B'): - if (rt.icurr > 0) - rt.icurr--; - else - bell(); - if (rt.icurr > 0 && CHKDBCSTRAIL(buf, rt.icurr)) - rt.icurr--; - break; - - case KEY_RIGHT: case Ctrl('F'): - if (rt.icurr < rt.iend) - rt.icurr++; - else - bell(); - if (rt.icurr < rt.iend && CHKDBCSTRAIL(buf, rt.icurr)) - rt.icurr++; - break; - - // editing keys - case KEY_DEL: case Ctrl('D'): - if (rt.icurr+1 < rt.iend && CHKDBCSTRAIL(buf, rt.icurr+1)) { - // kill next one character. - memmove(buf+rt.icurr, buf+rt.icurr+1, rt.iend-rt.icurr); - rt.iend--; - } - if (rt.icurr < rt.iend) { - // kill next one character. - memmove(buf+rt.icurr, buf+rt.icurr+1, rt.iend-rt.icurr); - rt.iend--; - } - break; - - case KEY_BS: - if (rt.icurr > 0) { - // kill previous one charracter. - memmove(buf+rt.icurr-1, buf+rt.icurr, rt.iend-rt.icurr+1); - rt.icurr--; rt.iend--; - } else - bell(); - if (rt.icurr > 0 && CHKDBCSTRAIL(buf, rt.icurr)) { - // kill previous one charracter. - memmove(buf+rt.icurr-1, buf+rt.icurr, rt.iend-rt.icurr+1); - rt.icurr--; rt.iend--; - } - break; - - case Ctrl('Y'): - rt.icurr = 0; - // reuse Ctrl-K code - case Ctrl('K'): - rt.iend = rt.icurr; - buf[rt.iend] = 0; - break; - - // defaults - default: - - // content filter - if (c < ' ' || c >= 0xFF) - { - bell(); continue; - } - if ((flags & VGET_DIGITS) && - ( !isascii(c) || !isdigit(c))) - { - bell(); continue; - } - if (flags & VGET_LOWERCASE) - { - if (!isascii(c)) - { - bell(); continue; - } - c = tolower(c); - } - if (flags & VGET_ASCII_ONLY) - { - if (!isprint(c)) - { - bell(); continue; - } - } - - // size check - if(rt.iend+1 >= len) - { - bell(); continue; - } - - // prevent incomplete DBCS - if (c > 0x80 && num_in_buf() && - len - rt.iend < 3) // we need 3 for DBCS+NUL. - { - drop_input(); - bell(); - continue; - } - - // callback 2: data - if (_vgetcbhandler(cb.data, &abort, c, &rt, instance)) - continue; - - // size check again, due to data callback. - if(rt.iend+1 >= len) - { - bell(); continue; - } - - // add one character. - memmove(buf+rt.icurr+1, buf+rt.icurr, rt.iend-rt.icurr+1); - buf[rt.icurr++] = c; - rt.iend++; - - // callback 3: post - if (_vgetcbhandler(cb.post, &abort, c, &rt, instance)) - continue; - - break; - } - } - - assert(rt.iend >= 0 && rt.iend < len); - buf[rt.iend] = 0; - - // final filtering - if (rt.iend && (flags & VGET_LOWERCASE)) - buf[0] = tolower(buf[0]); - - // save the history except password mode - if (buf[0] && !(flags & VGET_NOECHO)) - InputHistoryAdd(buf); - - // copy buffer! - memcpy(_buf, buf, len); - - // XXX update screen display - if (ismsgline) - msg_occupied --; - - /* because some code then outs so change new line.*/ - move(line+1, 0); - - return rt.iend; -} - -int -vgets(char *buf, int len, int flags) -{ - return vgetstr(buf, len, flags, ""); -} - -int -vgetstr(char *buf, int len, int flags, const char *defstr) -{ - return vgetstring(buf, len, flags, defstr, NULL, NULL); -} - diff --git a/mbbsd/vtkbd.c b/mbbsd/vtkbd.c deleted file mode 100644 index 8a00671f..00000000 --- a/mbbsd/vtkbd.c +++ /dev/null @@ -1,279 +0,0 @@ -/* - * 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 - * 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( [ | O (app) - * - Home/Ins/Del/End/PgUp/PgDn: [ <1~6> ~ - * - Shift-TAB: [ Z | [ 0 Z - * - F1~F4: [ 1 <1234> ~ | O - * - F5: [ 1 <5> ~ - * - F6-F8: [ 1 <789> ~ - * - F9-F12: [ 2 <0134> ~ - * - Num 0-9 *+,-./=ENTER: O - * - * Note: we don't support some rare terms like O 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, // - VKSTATE_ESC_APP, // O - VKSTATE_ESC_QUOTE, // [ - VKSTATE_ZERO, // [ 0 (wait Z) - VKSTATE_ONE, // [ <1> - VKSTATE_TWO, // [ <2> - VKSTATE_TLIDE, // [ * (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: // - 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: // 0 - if (c != 'Z') - break; - - ctx->state = VKSTATE_NORMAL; - return KEY_STAB; - - case VKSTATE_ESC_APP: // 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: // [ - - 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: // [ 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: // [ 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 diff --git a/mbbsd/vtuikit.c b/mbbsd/vtuikit.c new file mode 100644 index 00000000..9dbcdf55 --- /dev/null +++ b/mbbsd/vtuikit.c @@ -0,0 +1,1190 @@ +/* $Id$ */ +#include "vtkbd.h" +#include "bbs.h" + +/* + * vtuikit.c (was: visio.c) + * piaip's new implementation of virtual terminal user interface toolkits + * + * This is not the original visio.c from Maple3. + * We just borrowed its file name and few API names/prototypes + * then re-implemented everything from scratch :) + * + * We will try to keep the API behavior similiar (to help porting) + * but won't stick to it. + * Maybe at the end only 'vmsg' and 'vmsgf' will still be compatible.... + * + * m3 visio = (ptt) vtuikit+vtkbd+screen/term. + * + * Author: Hung-Te Lin (piaip), April 2008. + * + * Copyright (c) 2008-2009 Hung-Te Lin + * 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. + * + * To add API here, please... + * (1) name the API in prefix of 'v'. + * (2) use only screen.c APIs. + * (3) take care of wide screen and DBCS. + * (4) utilize the colos in vtuikit.h, and name asa VCLR_* (vtuikit color) + */ + +// ---- DEFINITION --------------------------------------------------- +#define MAX_COL (t_columns-1) +#define SAFE_MAX_COL (MAX_COL-1) +#define VBUFLEN (ANSILINELEN) + +// this is a special strlen to speed up processing. +// warning: x MUST be #define x "msg". +// otherwise you need to use real strlen. +#define MACROSTRLEN(x) (sizeof(x)-1) + +// ---- UTILITIES ---------------------------------------------------- +inline void +outnc(int n, unsigned char c) +{ + while (n-- > 0) + outc(c); +} + +inline void +nblank(int n) +{ + outnc(n, ' '); +} + +static inline void +fillns(int n, const char *s) +{ + while (n > 0 && *s) + outc(*s++), n--; + if (n > 0) + outnc(n, ' '); +} + +static inline void +fillns_ansi(int n, const char *s) +{ + int d = strat_ansi(n, s); + if (d < 0) { + outs(s); nblank(-d); + } else { + outns(s, d); + } +} + +// ---- VREF API -------------------------------------------------- + +/** + * vscr_save(): 傳回目前畫面的備份物件。 + */ +VREFSCR +vscr_save(void) +{ + // TODO optimize memory allocation someday. + screen_backup_t *o = (screen_backup_t*)malloc(sizeof(screen_backup_t)); + assert(o); + scr_dump(o); + return o; +} + +/** + * vscr_restore(obj): 使用並刪除畫面的備份物件。 + */ +void +vscr_restore(VREFSCR obj) +{ + screen_backup_t *o = (screen_backup_t*)obj; + if (o) + { + scr_restore(o); + memset(o, 0, sizeof(screen_backup_t)); + free(o); + } +} + +/** + * vcur_save(): 傳回目前游標的備份物件。 + */ +VREFCUR +vcur_save(void) +{ + // XXX 偷懶不 new object 了, pointer 夠大 + int y, x; + VREFCUR v; + getyx(&y, &x); + v = ((unsigned short)y << 16) | (unsigned short)x; + return v; +} + +/** + * vcur_restore(obj): 使用並刪除游標的備份物件。 + */ +void +vcur_restore(VREFCUR o) +{ + int y, x; + y = (unsigned int)(o); + x = (unsigned short)(y & 0xFFFF); + y = (unsigned short)(y >> 16); + move(y, x); +} + +// ---- LOW LEVEL API ----------------------------------------------- + +/** + * prints(fmt, ...): 使用 outs/outc 輸出並格式化字串。 + */ +void +prints(const char *fmt,...) +{ + va_list args; + char buff[VBUFLEN]; + + va_start(args, fmt); + vsnprintf(buff, sizeof(buff), fmt, args); + va_end(args); + + outs(buff); +} + +/** + * mvprints(int y, int x, fmt, ...): 使用 mvouts 輸出並格式化字串。 + */ +void +mvprints(int y, int x, const char *fmt, ...) +{ + va_list args; + char buff[VBUFLEN]; + + va_start(args, fmt); + vsnprintf(buff, sizeof(buff), fmt, args); + va_end(args); + + mvouts(x, y, buff); +} + +/** + * mvouts(y, x, str): = mvaddstr + */ +void +mvouts(int y, int x, const char *str) +{ + move(y, x); + clrtoeol(); + SOLVE_ANSI_CACHE(); + outs(str); +} + +/** + * vfill(n, flags, s): 印出並填滿 n 個字元的空間 + * + * @param n space to occupy + * @param flags VFILL_* parameters + * @param s string to display + */ +void +vfill(int n, int flags, const char *s) +{ + // warning: flag determination must take care of default values. + char has_ansi = ((flags & VFILL_HAS_ANSI) || (*s == ESC_CHR)); + char has_border = !(flags & VFILL_NO_BORDER); + + if (n < 1) + return; + + // quick return + if (!*s) + { + nblank(n); + return; + } + + // calculate border size (always draw because n > 0) + if (has_border) + n--; + + if (n > 0) + { + if (flags & VFILL_RIGHT_ALIGN) + { + // right-align + int l = has_ansi ? strlen_noansi(s) : strlen(s); + + if (l >= n) // '=' prevents blanks + l = n; + else { + nblank(n - l); + n = l; + } + // leave the task to left-align + } + + // left-align + if (has_ansi) + fillns_ansi(n, s); + else + fillns(n, s); + } + + // print border if required + if (has_border) + outc(' '); + + // close fill. + if (has_ansi) + outs(ANSI_RESET); +} + +/** + * vfill(n, flags, fmt, ...): 使用 vfill 輸出並格式化字串。 + * + * @param n space to occupy + * @param flags VFILL_* parameters + * @param fmt string to display + */ +void +vfillf(int n, int flags, const char *s, ...) +{ + va_list args; + char buff[VBUFLEN]; + + va_start(args, s); + vsnprintf(buff, sizeof(buff), s, args); + va_end(args); + + vfill(n, flags, s); +} + +/** + * vpad(n, pattern): 填滿 n 個字元 (使用的格式為 pattern) + * + * @param n 要填滿的字元數 (無法填滿時會使用空白填補) + * @param pattern 填充用的字串 + */ +inline void +vpad(int n, const char *pattern) +{ + int len = strlen(pattern); + // assert(len > 0); + + while (n >= len) + { + outs(pattern); + n -= len; + } + if (n) nblank(n); +} + +/** + * vgety(): 取得目前所在位置的行數 + * + * 考慮到 ANSI 系統,getyx() 較為少用且危險。 + * vgety() 安全而明確。 + */ +inline int +vgety(void) +{ + int y, x; + getyx(&y, &x); + return y; +} + +// ---- HIGH LEVEL API ----------------------------------------------- + +/** + * vshowmsg(s): 在底部印出指定訊息或單純的暫停訊息 + * + * @param s 指定訊息。 NULL: 任意鍵繼續。 s: 若有 \t 則後面字串靠右 (若無則顯示任意鍵) + */ +void +vshowmsg(const char *msg) +{ + int w = SAFE_MAX_COL; + move(b_lines, 0); clrtoeol(); + + if (!msg) + { + // print default message in middle + outs(VCLR_PAUSE_PAD); + outc(' '); // initial one space + + // VMSG_PAUSE MUST BE A #define STRING. + w -= MACROSTRLEN(VMSG_PAUSE); // strlen_noansi(VMSG_PAUSE); + w--; // initial space + vpad(w/2, VMSG_PAUSE_PAD); + outs(VCLR_PAUSE); + outs(VMSG_PAUSE); + outs(VCLR_PAUSE_PAD); + vpad(w - w/2, VMSG_PAUSE_PAD); + } else { + // print in left, with floating (if \t exists) + char *pfloat = strchr(msg, '\t'); + int szfloat = 0; + int nmsg = 0; + + // print prefix + w -= MACROSTRLEN(VMSG_MSG_PREFIX); // strlen_noansi(VMSG_MSG_PREFIX); + outs(VCLR_MSG); + outs(VMSG_MSG_PREFIX); + + // if have float, calculate float size + if (pfloat) { + nmsg = pfloat - msg; + w -= strlen_noansi(msg) -1; // -1 for \t + pfloat ++; // skip \t + szfloat = strlen_noansi(pfloat); + } else { + pfloat = VMSG_MSG_FLOAT; + szfloat = MACROSTRLEN(VMSG_MSG_FLOAT); // strlen_noansi() + w -= strlen_noansi(msg) + szfloat; + } + + // calculate if we can display float + if (w < 0) + { + w += szfloat; + szfloat = 0; + } + + // print msg body + if (nmsg) + outns(msg, nmsg); + else + outs(msg); + + // print padding for floats + if (w > 0) + nblank(w); + + // able to print float? + if (szfloat) + { + outs(VCLR_MSG_FLOAT); + outs(pfloat); + } + } + + // safe blank + outs(" " ANSI_RESET); +} + +/** + * vans(s): 在底部印出訊息與小輸入欄,並傳回使用者的輸入(轉為小寫)。 + * + * @param s 指定訊息,見 vshowmsg + */ +int +vans(const char *msg) +{ + char buf[3]; + + move(b_lines, 0); + clrtoeol(); SOLVE_ANSI_CACHE(); + outs(msg); + vgets(buf, sizeof(buf), VGET_LOWERCASE); + return (unsigned char)buf[0]; +} + +/** + * vansf(s, ...): 在底部印出訊息與小輸入欄,並傳回使用者的輸入(轉為小寫)。 + * + * @param s 指定訊息,見 vshowmsg + */ +int +vansf(const char *fmt, ...) +{ + char msg[VBUFLEN]; + va_list ap; + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + return vans(msg); +} + +/** + * vmsg(s): 在底部印出指定訊息或單純的暫停訊息,並傳回使用者的按鍵。 + * + * @param s 指定訊息,見 vshowmsg + */ +int +vmsg(const char *msg) +{ + int i = 0; + + vshowmsg(msg); + + // wait for key + do { + i = igetch(); + } while( i == 0 ); + + // clear message bar + move(b_lines, 0); + clrtoeol(); + + return i; +} + + +/** + * vmsgf(s, ...): 格式化輸出暫停訊息並呼叫 vmsg)。 + * + * @param s 格式化的訊息 + */ +int +vmsgf(const char *fmt,...) +{ + char msg[VBUFLEN]; + va_list ap; + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + return vmsg(msg); +} + +/** + * vbarf(s, ...): 格式化輸出左右對齊的字串 (MAX_COL) + * + * @param s 格式化字串 (\t 後的內容會對齊右端) + */ +void +vbarf(const char *s, ...) +{ + char msg[VBUFLEN], *s2; + va_list ap; + va_start(ap, s); + vsnprintf(msg, sizeof(msg), s, ap); + va_end(ap); + + s2 = strchr(msg, '\t'); + if (s2) *s2++ = 0; + else s2 = ""; + + return vbarlr(msg, s2); +} + +/** + * vbarlr(l, r): 左右對齊畫滿螢幕 (MAX_COL) + * 注意: 目前的實作自動認定游標已在行開頭。 + * + * @param l 靠左對齊的字串 (可含 ANSI 碼) + * @param r 靠右對齊的字串 (可含 ANSI 碼,後面不會補空白) + */ +void +vbarlr(const char *l, const char *r) +{ + // TODO strlen_noansi 跑兩次... 其實 l 可以邊 output 邊算。 + int szl = strlen_noansi(l), + szr = strlen_noansi(r); + + // assume we are already in (y, 0) + clrtoeol(); + outs(l); + szl = MAX_COL - szl; + + if (szl > szr) + { + nblank(szl - szr); + szl = szr; + } + + if (szl == szr) + outs(r); + else if (szl > 0) + nblank(szl); + + outs(ANSI_RESET); +} + +void +vs_rectangle_simple(int l, int t, int r, int b) +{ + int ol = l; + + assert( l + 4 <= r && + t + 2 <= b); + + // draw top line + move(t++, ol); l = ol+2; + outs("┌"); + while (l < r-2) { outs("─"); l+= 2; } + outs("┐"); + if (l+2 < r) outs(" "); + + while (t < b) + { + move(t++, ol); l = ol+2; + outs("│"); + // while (l < r-2) { outs(" "); l+= 2; } + l += (r-l-1)/2*2; + move_ansi(t-1, l); + outs("│"); + if (l+2 < r) outs(" "); + } + + // draw bottom line + move(t++, ol); l = ol+2; + outs("└"); + while (l < r-2) { outs("─"); l+= 2; } + outs("┘"); + if (l+2 < r) outs(" "); +} + +// ---- THEMED FORMATTING OUTPUT ------------------------------------- + +/** + * vs_header(title, mid, right): 清空螢幕並輸出完整標題 (MAX_COL) + * + * @param title: 靠左的主要標題,不會被切斷。 + * @param mid: 置中說明,可被切齊。 + * @param right: 靠右的說明,空間夠才會顯示。 + */ +void +vs_header(const char *title, const char *mid, const char *right) +{ + int w = MAX_COL; + int szmid = mid ? strlen(mid) : 0; + int szright = right ? strlen(right) : 0; + + clear(); + outs(VCLR_HEADER); + + if (title) + { + outs(VMSG_HDR_PREFIX); + outs(title); + outs(VMSG_HDR_POSTFIX); + w -= MACROSTRLEN(VMSG_HDR_PREFIX) + MACROSTRLEN(VMSG_HDR_POSTFIX); + w -= strlen(title); + } + + // determine if we can display right message, and + // if if we need to truncate mid. + if (szmid + szright > w) + szright = 0; + + if (szmid >= w) + szmid = w; + else { + int l = (MAX_COL-szmid)/2; + l -= (MAX_COL-w); + if (l > 0) + nblank(l), w -= l; + } + + if (szmid) { + outs(VCLR_HEADER_MID); + outns(mid, szmid); + outs(VCLR_HEADER); + } + nblank(w - szmid); + + if (szright) { + outs(VCLR_HEADER_RIGHT); + outs(right); + } + outs(ANSI_RESET "\n"); +} + +/** + * vs_hdr(title): 清空螢幕並輸出簡易的標題 + * + * @param title + */ +void +vs_hdr(const char *title) +{ + clear(); + outs(VCLR_HDR VMSG_HDR_PREFIX); + outs(title); + outs(VMSG_HDR_POSTFIX ANSI_RESET "\n"); +} + +/** + * vs_footer(caption, msg): 在螢幕底部印出格式化的 caption msg (不可含 ANSI 碼) + * + * @param caption 左邊的分類字串 + * @param msg 訊息字串, \t 後文字靠右、最後面會自動留一個空白。 + */ +void +vs_footer(const char *caption, const char *msg) +{ + int i = 0; + move(b_lines, 0); clrtoeol(); + + if (caption) + { + outs(VCLR_FOOTER_CAPTION); + outs(caption); + i += (*caption == ESC_CHR) ? + strlen_noansi(caption) : + strlen(caption); + } + + if (!msg) msg = ""; + outs(VCLR_FOOTER); + + while (*msg && i < SAFE_MAX_COL) + { + if (*msg == '(') + { + outs(VCLR_FOOTER_QUOTE); + } + else if (*msg == '\t') + { + // if we don't have enough space, ignore whole. + int l = strlen(++msg); + if (i + l > SAFE_MAX_COL) break; + l = SAFE_MAX_COL - l - i; + nblank(l); + i += l; + continue; + } + outc(*msg); i++; + if (*msg == ')') + outs(VCLR_FOOTER); + msg ++; + } + nblank(SAFE_MAX_COL-i); + outc(' '); + outs(ANSI_RESET); +} + +/** + * vs_cols_layout(cols, ws, n): 依據 cols (大小 n) 的定義計算適合的行寬於 ws + */ + +void +vs_cols_layout(const VCOL *cols, VCOLW *ws, int n) +{ + int i, tw; + VCOLPRI pri1 = cols[0].pri; + memset(ws, 0, sizeof(VCOLW) * n); + + // first run, calculate minimal size + for (i = 0, tw = 0; i < n; i++) + { + // drop any trailing if required + if (tw + cols[i].minw > MAX_COL) + break; + ws[i] = cols[i].minw; + tw += ws[i]; + } + + if (tw < MAX_COL) { + // calculate highest priorities + // (pri1 already set to col[0].pri) + for (i = 1; i < n; i++) + { + if (cols[i].pri > pri1) + pri1 = cols[i].pri; + } + } + + // try to iterate through all. + while (tw < MAX_COL) { + char run = 0; + + // also update pri2 here for next run. + VCOLPRI pri2 = cols[0].pri; + + for (i = 0; i < n; i++) + { + // if reach max, skip. + if (ws[i] >= cols[i].maxw) + continue; + + // lower priority, update pri2 and skip. + if (cols[i].pri < pri1) + { + if (cols[i].pri > pri2) + pri2 = cols[i].pri; + continue; + } + + // now increase fields + ws[i] ++; + if (++tw >= MAX_COL) break; + run ++; + } + + // if no more fields... + if (!run) { + if (pri1 <= pri2) // no more priorities + break; + pri1 = pri2; // try lower priority. + } + } +} + +/** + * vs_cols: 依照已經算好的欄位大小進行輸出 + */ +void +vs_cols(const VCOL *cols, const VCOLW *ws, int n, ...) +{ + int i = 0, w = 0; + char *s = NULL; + + va_list ap; + va_start(ap, n); + + for (i = 0; i < n; i++, cols++, ws++) + { + int flags = 0; + s = va_arg(ap, char*); + + // quick check input. + if (!s) + { + s = ""; + } + w = *ws; + + if (cols->attr) + outs(cols->attr); + + // build vfill flag + if (cols->flags.right_align) flags |= VFILL_RIGHT_ALIGN; + if (cols->flags.usewhole) flags |= VFILL_NO_BORDER; + + vfill(w, flags, s); + + if (cols->attr) + outs(ANSI_RESET); + } + va_end(ap); + + // end line + outs(ANSI_RESET "\n"); +} + +//////////////////////////////////////////////////////////////////////// +// DBCS Aware Helpers +//////////////////////////////////////////////////////////////////////// + +#ifdef DBCSAWARE +# define CHKDBCSTRAIL(_buf,_i) (ISDBCSAWARE() && DBCS_Status(_buf, _i) == DBCS_TRAILING) +#else // !DBCSAWARE +# define CHKDBCSTRAIL(buf,i) (0) +#endif // !DBCSAWARE + +//////////////////////////////////////////////////////////////////////// +// History Helpers +//////////////////////////////////////////////////////////////////////// +// +#define IH_MAX_ENTRIES (12) // buffer size = approx. 1k +#define IH_MIN_SIZE (2) // only keep string >= 2 bytes + +typedef struct { + int icurr; // current retrival pointer + int iappend; // new location to append + char buf[IH_MAX_ENTRIES][STRLEN]; +} InputHistory; + +static InputHistory ih; // everything intialized to zero. + +int +InputHistoryExists(const char *s) +{ + int i = 0; + + for (i = 0; i < IH_MAX_ENTRIES; i++) + if (strcmp(s, ih.buf[i]) == 0) + return i+1; + + return 0; +} + +int +InputHistoryAdd(const char *s) +{ + int i = 0; + int l = strlen(s); + + if (l < IH_MIN_SIZE) + return 0; + + i = InputHistoryExists(s); + if (i > 0) // found + { + i--; // i points to valid index + assert(i < IH_MAX_ENTRIES); + + // change order: just delete it. + ih.buf[i][0] = 0; + } + + // now append s. + strlcpy(ih.buf[ih.iappend], s, sizeof(ih.buf[ih.iappend])); + ih.iappend ++; + ih.iappend %= IH_MAX_ENTRIES; + ih.icurr = ih.iappend; + + return 1; +} + +static void +InputHistoryDelta(char *s, int sz, int d) +{ + int i, xcurr = 0; + for (i = 1; i <= IH_MAX_ENTRIES; i++) + { + xcurr = (ih.icurr+ IH_MAX_ENTRIES + d*i)%IH_MAX_ENTRIES; + if (ih.buf[xcurr][0]) + { + ih.icurr = xcurr; + + // copy buffer + strlcpy(s, ih.buf[ih.icurr], sz); + + // DBCS safe + i = strlen(s); + if (DBCS_Status(s, i) == DBCS_TRAILING) + s[i-1] = 0; + break; + } + } +} + +void +InputHistoryPrev(char *s, int sz) +{ + InputHistoryDelta(s, sz, -1); +} + +void +InputHistoryNext(char *s, int sz) +{ + InputHistoryDelta(s, sz, +1); +} + +//////////////////////////////////////////////////////////////////////// +// vget*: mini editbox +//////////////////////////////////////////////////////////////////////// + +static inline int +_vgetcbhandler(VGET_FCALLBACK cbptr, int *pabort, int c, VGET_RUNTIME *prt, void *instance) +{ + if (!cbptr) + return 0; + + switch(cbptr(c, prt, instance)) + { + case VGETCB_NONE: + assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); + return 0; + + case VGETCB_NEXT: + assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); + return 1; + + case VGETCB_END: + assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); + *pabort = 1; + return 1; + + case VGETCB_ABORT: + assert( prt->icurr >= 0 && prt->icurr <= prt->iend && prt->iend <= prt->len ); + *pabort = 1; + prt->icurr = 0; + prt->iend = 0; + prt->buf[0] = 0; + return 1; + } + assert(0); // shall never reach here + return 0; +} + +int +vgetstring(char *_buf, int len, int flags, const char *defstr, const VGET_CALLBACKS *pcbs, void *instance) +{ + // rt.iend points to NUL address, and + // rt.icurr points to cursor. + int line, col; + int abort = 0; + int c; + char ismsgline = 0; + + // callback + VGET_CALLBACKS cb = {NULL}; + + // always use internal buffer to prevent temporary input issue. + char buf[STRLEN] = ""; // zero whole. + + // runtime structure + VGET_RUNTIME rt = { buf, len > STRLEN ? STRLEN : len }; + + // it is wrong to design input with larger buffer + // than STRLEN. Although we support large screen, + // inputting huge line will just make troubles... + if (len > STRLEN) len = STRLEN; + assert(len <= sizeof(buf) && len >= 2); + + // memset(buf, 0, len); + if (defstr && *defstr) + { + strlcpy(buf, defstr, len); + strip_ansi(buf, buf, STRIP_ALL); // safer... + rt.icurr = rt.iend = strlen(buf); + } + + // setup callbacks + if (pcbs) + cb = *pcbs; + + getyx(&line, &col); // now (line,col) is the beginning of our new fields. + + // XXX be compatible with traditional... + if (line == b_lines - msg_occupied) + ismsgline = 1; + + if (ismsgline) + msg_occupied ++; + + // main loop + while (!abort) + { + if (!(flags & VGET_NOECHO)) + { + // print current buffer + move(line, col); + clrtoeol(); + SOLVE_ANSI_CACHE(); + + if (!(flags & VGET_TRANSPARENT)) + outs(VCLR_INPUT_FIELD); // change color to prompt fields + + vfill(len, 0, buf); + + if (!(flags & VGET_TRANSPARENT)) + outs(ANSI_RESET); + + // move to cursor position + move(line, col+rt.icurr); + } else { + // to simulate the "clrtoeol" behavior... + // XXX make this call only once? or not? + clrtoeol(); + } + c = vkey(); + + // callback 1: peek + if (_vgetcbhandler(cb.peek, &abort, c, &rt, instance)) + continue; + + // standard key bindings + switch(c) { + // history navigation + case KEY_DOWN: case Ctrl('N'): + c = KEY_DOWN; + // let UP do the magic. + case KEY_UP: case Ctrl('P'): + if ((flags & VGET_NOECHO) || + (flags & VGET_DIGITS)) + { + bell(); + continue; + } + + // NOECHO is already checked... + if (!InputHistoryExists(buf)) + InputHistoryAdd(buf); + + if (c == KEY_DOWN) + InputHistoryNext(buf, len); + else + InputHistoryPrev(buf, len); + + rt.icurr = rt.iend = strlen(buf); + break; + + // exiting keys + case KEY_ENTER: + abort = 1; + break; + + case Ctrl('C'): + rt.icurr = rt.iend = 0; + buf[0] = 0; + buf[1] = c; + abort = 1; + break; + + // standard navigation + case KEY_HOME: case Ctrl('A'): + rt.icurr = 0; + break; + + case KEY_END: case Ctrl('E'): + rt.icurr = rt.iend; + break; + + case KEY_LEFT: case Ctrl('B'): + if (rt.icurr > 0) + rt.icurr--; + else + bell(); + if (rt.icurr > 0 && CHKDBCSTRAIL(buf, rt.icurr)) + rt.icurr--; + break; + + case KEY_RIGHT: case Ctrl('F'): + if (rt.icurr < rt.iend) + rt.icurr++; + else + bell(); + if (rt.icurr < rt.iend && CHKDBCSTRAIL(buf, rt.icurr)) + rt.icurr++; + break; + + // editing keys + case KEY_DEL: case Ctrl('D'): + if (rt.icurr+1 < rt.iend && CHKDBCSTRAIL(buf, rt.icurr+1)) { + // kill next one character. + memmove(buf+rt.icurr, buf+rt.icurr+1, rt.iend-rt.icurr); + rt.iend--; + } + if (rt.icurr < rt.iend) { + // kill next one character. + memmove(buf+rt.icurr, buf+rt.icurr+1, rt.iend-rt.icurr); + rt.iend--; + } + break; + + case KEY_BS: + if (rt.icurr > 0) { + // kill previous one charracter. + memmove(buf+rt.icurr-1, buf+rt.icurr, rt.iend-rt.icurr+1); + rt.icurr--; rt.iend--; + } else + bell(); + if (rt.icurr > 0 && CHKDBCSTRAIL(buf, rt.icurr)) { + // kill previous one charracter. + memmove(buf+rt.icurr-1, buf+rt.icurr, rt.iend-rt.icurr+1); + rt.icurr--; rt.iend--; + } + break; + + case Ctrl('Y'): + rt.icurr = 0; + // reuse Ctrl-K code + case Ctrl('K'): + rt.iend = rt.icurr; + buf[rt.iend] = 0; + break; + + // defaults + default: + + // content filter + if (c < ' ' || c >= 0xFF) + { + bell(); continue; + } + if ((flags & VGET_DIGITS) && + ( !isascii(c) || !isdigit(c))) + { + bell(); continue; + } + if (flags & VGET_LOWERCASE) + { + if (!isascii(c)) + { + bell(); continue; + } + c = tolower(c); + } + if (flags & VGET_ASCII_ONLY) + { + if (!isprint(c)) + { + bell(); continue; + } + } + + // size check + if(rt.iend+1 >= len) + { + bell(); continue; + } + + // prevent incomplete DBCS + if (c > 0x80 && num_in_buf() && + len - rt.iend < 3) // we need 3 for DBCS+NUL. + { + drop_input(); + bell(); + continue; + } + + // callback 2: data + if (_vgetcbhandler(cb.data, &abort, c, &rt, instance)) + continue; + + // size check again, due to data callback. + if(rt.iend+1 >= len) + { + bell(); continue; + } + + // add one character. + memmove(buf+rt.icurr+1, buf+rt.icurr, rt.iend-rt.icurr+1); + buf[rt.icurr++] = c; + rt.iend++; + + // callback 3: post + if (_vgetcbhandler(cb.post, &abort, c, &rt, instance)) + continue; + + break; + } + } + + assert(rt.iend >= 0 && rt.iend < len); + buf[rt.iend] = 0; + + // final filtering + if (rt.iend && (flags & VGET_LOWERCASE)) + buf[0] = tolower(buf[0]); + + // save the history except password mode + if (buf[0] && !(flags & VGET_NOECHO)) + InputHistoryAdd(buf); + + // copy buffer! + memcpy(_buf, buf, len); + + // XXX update screen display + if (ismsgline) + msg_occupied --; + + /* because some code then outs so change new line.*/ + move(line+1, 0); + + return rt.iend; +} + +int +vgets(char *buf, int len, int flags) +{ + return vgetstr(buf, len, flags, ""); +} + +int +vgetstr(char *buf, int len, int flags, const char *defstr) +{ + return vgetstring(buf, len, flags, defstr, NULL, NULL); +} + -- cgit v1.2.3