Newer
Older
/* Synchronet single key input function */
/****************************************************************************
* @format.tab-size 4 (Plain Text/Source Code File Header) *
* @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) *
* *
* Copyright Rob Swindell - http://www.synchro.net/copyright.html *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* See the GNU General Public License for more details: gpl.txt or *
* http://www.fsf.org/copyleft/gpl.html *
* *
* For Synchronet coding style and modification guidelines, see *
* http://www.synchro.net/source.html *
* *
* Note: If this box doesn't appear square, then you need to fix your tabs. *
****************************************************************************/
#include "sbbs.h"
#include "petdefs.h"
#include "unicode.h"
#include "utf8.h"
int sbbs_t::kbincom(unsigned int timeout)
{
int ch;
if(keybuftop!=keybufbot) {
ch=keybuf[keybufbot++];
if(keybufbot==KEY_BUFSIZE)
keybufbot=0;
#if 0
char* p = c_escape_char(ch);
if(p == NULL)
p = (char*)&ch;
lprintf(LOG_DEBUG, "kbincom read %02X '%s'", ch, p);
#endif
return ch;
}
ch = incom(timeout);
return ch;
}
int sbbs_t::translate_input(int ch)
{
if(term&PETSCII) {
switch(ch) {
case PETSCII_HOME:
return TERM_KEY_HOME;
case PETSCII_CLEAR:
return TERM_KEY_END;
case PETSCII_INSERT:
return TERM_KEY_INSERT;
case PETSCII_DELETE:
return '\b';
case PETSCII_LEFT:
return TERM_KEY_LEFT;
case PETSCII_RIGHT:
return TERM_KEY_RIGHT;
case PETSCII_UP:
return TERM_KEY_UP;
case PETSCII_DOWN:
return TERM_KEY_DOWN;

rswindell
committed
}
if((ch&0xe0) == 0xc0) /* "Codes $60-$7F are, actually, copies of codes $C0-$DF" */
ch = 0x60 | (ch&0x1f);
if(IS_ALPHA(ch))
ch ^= 0x20; /* Swap upper/lower case */
}
else if(term&SWAP_DELETE) {
switch(ch) {
case TERM_KEY_DELETE:
ch = '\b';
break;
case '\b':
ch = TERM_KEY_DELETE;
break;

rswindell
committed
}
}
return ch;

Rob Swindell
committed
void sbbs_t::translate_input(uchar* buf, size_t len)
{
for(size_t i =0; i < len; i++)
buf[i] = translate_input(buf[i]);
}
/****************************************************************************/
/* Returns character if a key has been hit remotely and responds */
/* May return NOINP on timeout instead of '\0' when K_NUL mode is used. */
/****************************************************************************/
int sbbs_t::inkey(int mode, unsigned int timeout)
const int no_input = (mode & K_NUL) ? NOINP : 0; // distinguish between timeout and '\0'
ch=kbincom(timeout);
if(sys_status&SS_SYSPAGE)
sbbs_beep(400 + sbbs_random(800), ch == NOINP ? 100 : 10);
if(ch == NOINP)
return no_input;
if(cfg.node_misc&NM_7BITONLY
&& (!(sys_status&SS_USERON) || term_supports(NO_EXASCII)))
getkey_last_activity = time(NULL);
/* Is this a control key */
if(!(mode & K_CTRLKEYS) && ch < ' ') {
if(cfg.ctrlkey_passthru&(1<<ch)) /* flagged as passthru? */
return(ch); /* do not handle here */
return(handle_ctrlkey(ch,mode));
}
/* Translate (not control character) input into CP437 */
if (mode & K_CP437) {
if ((ch & 0x80) && term_supports(UTF8)) {
char utf8[UTF8_MAX_LEN] = { (char)ch };
int len = utf8_decode_firstbyte(ch);
if (len < 2 || len > sizeof(utf8))
return no_input;
for (int i = 1; i < len; ++i) {
ch = kbincom(timeout);
if (!(ch & 0x80) || (ch == NOINP))
break;
utf8[i] = ch;
}
if (ch & 0x80) {
enum unicode_codepoint codepoint;
len = utf8_getc(utf8, len, &codepoint);
if (len < 1)
return no_input;
ch = unicode_to_cp437(codepoint);
if (ch == 0)
return no_input;
}
}
}
if(mode&K_UPPER)
ch=toupper(ch);
return(ch);
}
char sbbs_t::handle_ctrlkey(char ch, int mode)

Rob Swindell
committed
int i,j;
if(ch==TERM_KEY_ABORT) { /* Ctrl-C Abort */
sys_status|=SS_ABORT;
if(mode&K_SPIN) /* back space once if on spinning cursor */
}
if(ch==CTRL_Z && !(mode&(K_MSG|K_GETSTR))
&& action!=NODE_PCHT) { /* Ctrl-Z toggle raw input mode */
if(hotkey_inside&(1<<ch))
return(0);
hotkey_inside |= (1<<ch);
if(mode&K_SPIN)
bputs("\b ");
attr(LIGHTGRAY);
CRLF;
bputs(text[RawMsgInputModeIsNow]);
if(console&CON_RAW_IN)

Rob Swindell
committed
bputs(text[Off]);

Rob Swindell
committed
bputs(text[On]);
console^=CON_RAW_IN;
CRLF;
CRLF;
restoreline();
hotkey_inside &= ~(1<<ch);
if(action!=NODE_MAIN && action!=NODE_XFER)
return(CTRL_Z);
}
if(console&CON_RAW_IN) /* ignore ctrl-key commands if in raw mode */
return(ch);
#if 0 /* experimental removal to fix Tracker1's pause module problem with down-arrow */
if(ch==LF) /* ignore LF's if not in raw mode */
return(0);
#endif
if(sys_status&SS_USERON) {
for(i=0;i<cfg.total_hotkeys;i++)
if(cfg.hotkey[i]->key==ch)
break;
if(i<cfg.total_hotkeys) {
if(hotkey_inside&(1<<ch))
hotkey_inside |= (1<<ch);
if(mode&K_SPIN)
bputs("\b ");
if(!(sys_status&SS_SPLITP)) {
if(cfg.hotkey[i]->cmd[0]=='?') {
if(js_hotkey_cx == NULL) {
js_hotkey_cx = js_init(&js_hotkey_runtime, &js_hotkey_glob, "HotKey");
js_create_user_objects(js_hotkey_cx, js_hotkey_glob);
}
js_execfile(cmdstr(cfg.hotkey[i]->cmd+1,nulstr,nulstr,tmp), /* startup_dir: */NULL, /* scope: */js_hotkey_glob, js_hotkey_cx, js_hotkey_glob);
} else
external(cmdstr(cfg.hotkey[i]->cmd,nulstr,nulstr,tmp),0);
if(!(sys_status&SS_SPLITP)) {
CRLF;
hotkey_inside &= ~(1<<ch);
}
}
switch(ch) {
case CTRL_O: /* Ctrl-O toggles pause temporarily */
console^=CON_PAUSEOFF;
case CTRL_P: /* Ctrl-P Private node-node comm */
break;;
if(hotkey_inside&(1<<ch))
return(0);
hotkey_inside |= (1<<ch);
if(mode&K_SPIN)
bputs("\b ");
nodesync(); /* read any waiting messages */
nodemsg(); /* send a message */
hotkey_inside &= ~(1<<ch);
break;
if(hotkey_inside&(1<<ch))
return(0);
hotkey_inside |= (1<<ch);
if(mode&K_SPIN)
bputs("\b ");
whos_online(true); /* list users */
hotkey_inside &= ~(1<<ch);
case CTRL_T: /* Ctrl-T Time information */
if(sys_status&SS_SPLITP)
return(ch);
break;
if(hotkey_inside&(1<<ch))
return(0);
hotkey_inside |= (1<<ch);
if(mode&K_SPIN)
bputs("\b ");
attr(LIGHTGRAY);
now=time(NULL);
bprintf(text[TiLogon],timestr(logontime));
bprintf(text[TiNow],timestr(now),smb_zonestr(sys_timezone(&cfg),NULL));
,sectostr((uint)(now-logontime),tmp));
bprintf(text[TiTimeLeft]
,sectostr(timeleft,tmp));
if(sys_status&SS_EVENT)
bprintf(text[ReducedTime],timestr(event_time));
restoreline();
hotkey_inside &= ~(1<<ch);
case CTRL_K: /* Ctrl-K Control key menu */
break;
if(hotkey_inside&(1<<ch))
return(0);
hotkey_inside |= (1<<ch);
if(mode&K_SPIN)
bputs("\b ");
attr(LIGHTGRAY);
lncntr=0;
if(mode&K_GETSTR)
bputs(text[GetStrMenu]);
else
bputs(text[ControlKeyMenu]);
restoreline();
hotkey_inside &= ~(1<<ch);
i=kbincom((mode&K_GETSTR) ? 3000:1000);
if(i==NOINP) // timed-out waiting for '['

rswindell
committed
ungetkey(ch, /* insert: */true);
}
i=j=0;
autoterm|=ANSI; /* <ESC>[x means they have ANSI */

rswindell
committed
#if 0 // this seems like a "bad idea" {tm}
if(sys_status&SS_USERON && useron.misc&AUTOTERM && !(useron.misc&ANSI)
&& useron.number) {
useron.misc|=ANSI;
putuserrec(&cfg,useron.number,U_MISC,8,ultoa(useron.misc,str,16));
}

rswindell
committed
#endif
while(i<10 && j<30) { /* up to 3 seconds */
ch=kbincom(100);
if(ch==(NOINP&0xff)) {
j++;
continue;
}
if(i == 0 && ch == 'M' && mouse_mode != MOUSE_MODE_OFF) {
int button = kbincom(100);
if(button == NOINP) {
lprintf(LOG_DEBUG, "Timeout waiting for mouse button value");
continue;
}
str[i++] = button;
ch = kbincom(100);
if(ch < '!') {
lprintf(LOG_DEBUG, "Unexpected mouse-button (0x%02X) tracking char: 0x%02X < '!'"
, button, ch);
continue;
}
ch = kbincom(100);
if(ch < '!') {
lprintf(LOG_DEBUG, "Unexpected mouse-button (0x%02X) tracking char: 0x%02X < '!'"
, button, ch);
continue;
}
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
lprintf(LOG_DEBUG, "X10 Mouse button-click (0x%02X) reported at: %u x %u", button, x, y);
if(button == 0x20) { // Left-click
list_node_t* node;
for(node = mouse_hotspots.first; node != NULL; node = node->next) {
struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
if(spot->y == y && x >= spot->minx && x <= spot->maxx)
break;
}
if(node == NULL) {
for(node = mouse_hotspots.first; node != NULL; node = node->next) {
struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
if(spot->hungry && spot->y == y && x >= spot->minx)
break;
}
}
if(node == NULL) {
for(node = mouse_hotspots.last; node != NULL; node = node->prev) {
struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
if(spot->hungry && spot->y == y && x <= spot->minx)
break;
}
}
if(node != NULL) {
struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
#ifdef _DEBUG
{
char dbg[128];
c_escape_str(spot->cmd, dbg, sizeof(dbg), /* Ctrl-only? */true);
lprintf(LOG_DEBUG, "Stuffing hot spot command into keybuf: '%s'", dbg);
}
#endif
ungetstr(spot->cmd);
if(pause_inside && pause_hotspot == NULL)
return handle_ctrlkey(TERM_KEY_ABORT, mode);
return 0;
}
if(pause_inside && y == rows - 1)
} else if(button == '`' && console&CON_MOUSE_SCROLL) {
return TERM_KEY_UP;
} else if(button == 'a' && console&CON_MOUSE_SCROLL) {
return TERM_KEY_DOWN;
if((button != 0x23 && console&CON_MOUSE_CLK_PASSTHRU)
|| (button == 0x23 && console&CON_MOUSE_REL_PASSTHRU)) {
for(j = i; j > 0; j--)
ungetkey(str[j - 1], /* insert: */true);
ungetkey('[', /* insert: */true);
if(button == 0x22) // Right-click
return handle_ctrlkey(TERM_KEY_ABORT, mode);
return 0;
}
if(i == 0 && ch == '<' && mouse_mode != MOUSE_MODE_OFF) {
while(i < (int)sizeof(str) - 1) {
int byte = kbincom(100);
if(byte == NOINP) {
lprintf(LOG_DEBUG, "Timeout waiting for mouse report character (%d)", i);
return 0;
}
str[i++] = byte;
if(IS_ALPHA(byte))
str[i] = 0;
int button = -1, x = 0, y = 0;
if(sscanf(str, "%d;%d;%d%c", &button, &x, &y, &ch) != 4
|| button < 0 || x < 1 || y < 1 || toupper(ch) != 'M') {
lprintf(LOG_DEBUG, "Invalid SGR mouse report sequence: '%s'", str);
return 0;
}
--x;
--y;
lprintf(LOG_DEBUG, "SGR Mouse button (0x%02X) %s reported at: %u x %u"
,button, ch == 'M' ? "PRESS" : "RELEASE", x, y);
if(button == 0 && ch == 'm') { // Left-button release
for(node = mouse_hotspots.first; node != NULL; node = node->next) {
struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
if(spot->y == y && x >= spot->minx && x <= spot->maxx)
if(node == NULL) {
for(node = mouse_hotspots.first; node != NULL; node = node->next) {
struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
if(spot->hungry && spot->y == y && x >= spot->minx)
break;
}
}
if(node == NULL) {
for(node = mouse_hotspots.last; node != NULL; node = node->prev) {
struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
if(spot->hungry && spot->y == y && x <= spot->minx)
break;
}
}
if(node != NULL) {
struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
#ifdef _DEBUG
{
char dbg[128];
c_escape_str(spot->cmd, dbg, sizeof(dbg), /* Ctrl-only? */true);
lprintf(LOG_DEBUG, "Stuffing hot spot command into keybuf: '%s'", dbg);
}
#endif
ungetstr(spot->cmd);
if(pause_inside && pause_hotspot == NULL)
return handle_ctrlkey(TERM_KEY_ABORT, mode);
return 0;
if(pause_inside && y == rows - 1)
} else if(button == 0x40 && console&CON_MOUSE_SCROLL) {
return TERM_KEY_UP;
} else if(button == 0x41 && console&CON_MOUSE_SCROLL) {
return TERM_KEY_DOWN;
if((ch == 'M' && console&CON_MOUSE_CLK_PASSTHRU)
|| (ch == 'm' && console&CON_MOUSE_REL_PASSTHRU)) {
lprintf(LOG_DEBUG, "Passing-through SGR mouse report: 'ESC[<%s'", str);
for(j = i; j > 0; j--)
ungetkey(str[j - 1], /* insert: */true);
ungetkey('<', /* insert: */true);
ungetkey('[', /* insert: */true);
if(ch == 'M' && button == 2) // Right-click
return handle_ctrlkey(TERM_KEY_ABORT, mode);
#ifdef _DEBUG
lprintf(LOG_DEBUG, "Eating SGR mouse report: 'ESC[<%s'", str);
#endif
if(ch!=';' && !IS_DIGIT(ch) && ch!='R') { /* other ANSI */
str[i]=0;
return(TERM_KEY_UP);
return(TERM_KEY_DOWN);
return(TERM_KEY_RIGHT);
return(TERM_KEY_LEFT);
case 'H': /* ANSI: home cursor */
return(TERM_KEY_HOME);
case 'V':
return TERM_KEY_PAGEUP;
case 'U':
return TERM_KEY_PAGEDN;
case 'F': /* Xterm: cursor preceding line */
case 'K': /* ANSI: clear-to-end-of-line */
return(TERM_KEY_END);
case '@': /* ANSI/ECMA-048 INSERT */
return(TERM_KEY_INSERT);
case '~': /* VT-220 (XP telnet.exe) */
switch(atoi(str)) {
case 1:
return(TERM_KEY_HOME);
case 2:
return(TERM_KEY_INSERT);
case 3:
return(TERM_KEY_DELETE);
case 4:
return(TERM_KEY_END);
case 5:
return TERM_KEY_PAGEUP;
case 6:
return TERM_KEY_PAGEDN;
}
break;
}

rswindell
committed
ungetkey(ch, /* insert: */true);
for(j = i; j > 0; j--)
ungetkey(str[j - 1], /* insert: */true);
ungetkey('[', /* insert: */true);
}
if(ch=='R') { /* cursor position report */
if(mode&K_ANSI_CPR && i) { /* auto-detect rows */
int x,y;
if(sscanf(str,"%u;%u",&y,&x)==2) {
lprintf(LOG_DEBUG,"received ANSI cursor position report: %ux%u"
,x, y);
/* Sanity check the coordinates in the response: */
if(useron.cols == TERM_COLS_AUTO && x >= TERM_COLS_MIN && x <= TERM_COLS_MAX) cols=x;
if(useron.rows == TERM_ROWS_AUTO && y >= TERM_ROWS_MIN && y <= TERM_ROWS_MAX) rows=y;
if(useron.cols == TERM_COLS_AUTO || useron.rows == TERM_ROWS_AUTO)
update_nodeterm();
}
}
}

rswindell
committed
for(j = i; j > 0; j--)
ungetkey(str[j - 1], /* insert: */true);
ungetkey('[', /* insert: */true);
}
void sbbs_t::set_mouse(int flags)
if((term&ANSI) && ((term&MOUSE) || flags == MOUSE_MODE_OFF)) {
int mode = mouse_mode & ~flags;
if(mode & MOUSE_MODE_X10) ansi_mouse(ANSI_MOUSE_X10, false);
if(mode & MOUSE_MODE_NORM) ansi_mouse(ANSI_MOUSE_NORM, false);
if(mode & MOUSE_MODE_BTN) ansi_mouse(ANSI_MOUSE_BTN, false);
if(mode & MOUSE_MODE_ANY) ansi_mouse(ANSI_MOUSE_ANY, false);
if(mode & MOUSE_MODE_EXT) ansi_mouse(ANSI_MOUSE_EXT, false);
mode = flags & ~mouse_mode;
if(mode & MOUSE_MODE_X10) ansi_mouse(ANSI_MOUSE_X10, true);
if(mode & MOUSE_MODE_NORM) ansi_mouse(ANSI_MOUSE_NORM, true);
if(mode & MOUSE_MODE_BTN) ansi_mouse(ANSI_MOUSE_BTN, true);
if(mode & MOUSE_MODE_ANY) ansi_mouse(ANSI_MOUSE_ANY, true);
if(mode & MOUSE_MODE_EXT) ansi_mouse(ANSI_MOUSE_EXT, true);
if(mouse_mode != flags) {
#if 0
lprintf(LOG_DEBUG, "New mouse mode: %X (was: %X)", flags, mouse_mode);
#endif
mouse_mode = flags;
}
struct mouse_hotspot* sbbs_t::add_hotspot(struct mouse_hotspot* spot)
if(!term_supports(MOUSE))
return NULL;
if(spot->y < 0)
spot->y = row;
if(spot->minx < 0)
#if 0 //def _DEBUG
char dbg[128];

rswindell
committed
lprintf(LOG_DEBUG, "Adding mouse hot spot %ld-%ld x %ld = '%s'"
,spot->minx, spot->maxx, spot->y, c_escape_str(spot->cmd, dbg, sizeof(dbg), /* Ctrl-only? */true));
list_node_t* node = listInsertNodeData(&mouse_hotspots, spot, sizeof(*spot));
if(node == NULL)
return NULL;
set_mouse(MOUSE_MODE_ON);
}
void sbbs_t::clear_hotspots(void)
{
int spots = listCountNodes(&mouse_hotspots);
if(spots) {
#if 0 //def _DEBUG
lprintf(LOG_DEBUG, "Clearing %ld mouse hot spots", spots);
#endif
listFreeNodes(&mouse_hotspots);
if(!(console&CON_MOUSE_SCROLL))
set_mouse(MOUSE_MODE_OFF);
}
void sbbs_t::scroll_hotspots(int count)
int spots = 0;
int remain = 0;
for(list_node_t* node = mouse_hotspots.first; node != NULL; node = node->next) {
struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
spot->y -= count;
spots++;
lprintf(LOG_DEBUG, "Scrolled %d mouse hot-spots %d rows (%d remain)", spots, count, remain);
struct mouse_hotspot* sbbs_t::add_hotspot(char cmd, bool hungry, int minx, int maxx, int y)
struct mouse_hotspot spot = {};

rswindell
committed
spot.minx = minx < 0 ? column : minx;
spot.maxx = maxx < 0 ? column : maxx;

rswindell
committed
spot.hungry = hungry;

Rob Swindell
committed
struct mouse_hotspot* sbbs_t::add_hotspot(int num, bool hungry, int minx, int maxx, int y)
{
struct mouse_hotspot spot = {};
SAFEPRINTF(spot.cmd, "%d\r", num);
spot.minx = minx;
spot.maxx = maxx;
spot.y = y;
spot.hungry = hungry;
return add_hotspot(&spot);
}
struct mouse_hotspot* sbbs_t::add_hotspot(uint num, bool hungry, int minx, int maxx, int y)
struct mouse_hotspot spot = {};
SAFEPRINTF(spot.cmd, "%u\r", num);
spot.minx = minx;
spot.maxx = maxx;
spot.y = y;

rswindell
committed
spot.hungry = hungry;
struct mouse_hotspot* sbbs_t::add_hotspot(const char* cmd, bool hungry, int minx, int maxx, int y)
struct mouse_hotspot spot = {};
SAFECOPY(spot.cmd, cmd);
spot.minx = minx;
spot.maxx = maxx;
spot.y = y;

rswindell
committed
spot.hungry = hungry;