/* $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 <piaip@csie.ntu.edu.tw> * 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(): �Ǧ^�ثe�e�����ƥ�����C */ 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): �ϥΨçR���e�����ƥ�����C */ 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(): �Ǧ^�ثe��Ъ��ƥ�����C */ VREFCUR vcur_save(void) { // XXX ���i�� new object �F�A pointer ���j int y, x; VREFCUR v; getyx(&y, &x); v = ((unsigned short)y << 16) | (unsigned short)x; return v; } /** * vcur_restore(obj): �ϥΨçR����Ъ��ƥ�����C */ 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 ��X�î榡�Ʀr��C */ 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 ��X�î榡�Ʀr��C */ 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): �L�X�ö� n �Ӧr�����Ŷ� * * @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 ��X�î榡�Ʀr��C * * @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 �Ӧr�� (�ϥΪ��榡�� pattern) * * @param n �n���r���� (�L�k�ɷ|�ϥΪťն��) * @param pattern ��R�Ϊ��r�� */ 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(): ���o�ثe�Ҧb��m����� * * �Ҽ{�� ANSI �t�ΡAgetyx() �����֥ΥB�M�I�C * vgety() �w���ө��T�C */ inline int vgety(void) { int y, x; getyx(&y, &x); return y; } // ---- HIGH LEVEL API ----------------------------------------------- /** * vshowmsg(s): �b�����L�X���w�T���γ�ª��Ȱ��T�� * * @param s ���w�T���C NULL: ���N���~��C s: �Y�� \t �h�᭱�r��a�k (�Y�L�h��ܥ��N��) */ 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): �b�����L�X�T���P�p��J��A�öǦ^�ϥΪ̪���J(�ର�p�g)�C * * @param s ���w�T���A�� 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, ...): �b�����L�X�T���P�p��J��A�öǦ^�ϥΪ̪���J(�ର�p�g)�C * * @param s ���w�T���A�� 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): �b�����L�X���w�T���γ�ª��Ȱ��T���A�öǦ^�ϥΪ̪�����C * * @param s ���w�T���A�� 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, ...): �榡�ƿ�X�Ȱ��T���éI�s vmsg)�C * * @param s �榡�ƪ��T�� */ 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, ...): �榡�ƿ�X���k������r�� (MAX_COL) * * @param s �榡�Ʀr�� (\t �᪺���e�|����k��) */ 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): ���k����e���ù� (MAX_COL) * �`�N: �ثe����@�۰ʻ{�w��Фw�b��}�Y�C * * @param l �a��������r�� (�i�t ANSI �X) * @param r �a�k������r�� (�i�t ANSI �X�A�᭱���|�ɪť�) */ void vbarlr(const char *l, const char *r) { // TODO strlen_noansi �]�⦸... ��� l �i�H�� output ���C 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); } // ---- THEMED FORMATTING OUTPUT ------------------------------------- /** * vs_header(title, mid, right): �M�ſù��ÿ�X������D (MAX_COL) * * @param title: �a�����D�n���D�A���|�Q���_�C * @param mid: �m�������A�i�Q�����C * @param right: �a�k�������A�Ŷ����~�|��ܡC */ 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): �M�ſù��ÿ�X²�������D * * @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): �b�ù������L�X�榡�ƪ� caption msg (���i�t ANSI �X) * * @param caption ���䪺�����r�� * @param msg �T���r��, \t ���r�a�k�B�̫᭱�|�۰ʯd�@�ӪťաC */ 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 (�j�p n) ���w�q�p��A�X����e�� 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: �̷Ӥw�g��n�����j�p�i���X */ 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: case KEY_BS2: 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); }