Skip to content
Snippets Groups Projects
getkey.cpp 14.3 KiB
Newer Older
/* Synchronet single-key console functions */

/****************************************************************************
 * @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 "telnet.h" // TELNET_GA

/****************************************************************************/
/* Waits for remote or local user to hit a key. Inactivity timer is checked */
/* and hangs up if inactive for 4 minutes. Returns key hit, or uppercase of */
/* key hit if mode&K_UPPER or key out of KEY BUFFER. Does not print key.    */
/* Called from functions all over the place.                                */
/****************************************************************************/
char sbbs_t::getkey(int mode)
	uchar  ch, coldkey;
	uint   c = sbbs_random(5);
	time_t last_telnet_cmd = 0;
	char*  cursor = text[SpinningCursor0  + sbbs_random(10)];
	size_t cursors = strlen(cursor);
	if (online == ON_REMOTE && !input_thread_running)
		online = FALSE;
	if (!online) {
		YIELD();    // just in case someone is looping on getkey() when they shouldn't
	sys_status &= ~SS_ABORT;
	if ((sys_status & SS_USERON || action == NODE_DFLT) && !(mode & (K_GETSTR | K_NOSPIN)))
		mode |= (useron.misc & SPIN);
	lncntr = 0;
	getkey_last_activity = time(NULL);
#if !defined SPINNING_CURSOR_OVER_HARDWARE_CURSOR
		outchar(' ');
		if (sys_status & SS_ABORT) {
			if (mode & K_SPIN) {
#if defined SPINNING_CURSOR_OVER_HARDWARE_CURSOR
				bputs(" \b");
#else
		if (mode & K_SPIN) {
#if !defined SPINNING_CURSOR_OVER_HARDWARE_CURSOR
			outchar(cursor[(c++) % cursors]);
#if defined SPINNING_CURSOR_OVER_HARDWARE_CURSOR
			outchar('\b');
#endif
		ch = inkey(mode, mode & K_SPIN ? 200:1000);
		if (sys_status & SS_ABORT)
		now = time(NULL);
		if (ch) {
			if (mode & K_NUMBER && IS_PRINTABLE(ch) && !IS_DIGIT(ch))
			if (mode & K_ALPHA && IS_PRINTABLE(ch) && !IS_ALPHA(ch))
			if (mode & K_NOEXASC && ch & 0x80)
			if (mode & K_SPIN) {
#if defined SPINNING_CURSOR_OVER_HARDWARE_CURSOR
				bputs(" \b");
#else
			if (mode & K_COLD && ch > ' ' && useron.misc & COLDKEYS) {
				if (mode & K_UPPER)
					outchar(toupper(ch));
				else
					outchar(ch);
				while ((coldkey = inkey(mode, 1000)) == 0 && online && !(sys_status & SS_ABORT))
				if (coldkey == BS || coldkey == DEL)
				if (coldkey > ' ')
					ungetkey(coldkey);
			if (mode & K_UPPER)
				return toupper(ch);
			return ch;
		if (sys_status & SS_USERON && !(sys_status & SS_LCHAT))
			gettimeleft();
		else if (online && now - answertime > SEC_LOGON && !(sys_status & SS_LCHAT)) {
			console &= ~(CON_R_ECHOX | CON_L_ECHOX);
			console |= (CON_R_ECHO | CON_L_ECHO);
			bputs(text[TakenTooLongToLogon]);
		if (sys_status & SS_USERON && online && (timeleft / 60) < (5 - timeleft_warn)
		    && !SYSOP && !(sys_status & SS_LCHAT)) {
			timeleft_warn = 5 - (timeleft / 60);
			attr(LIGHTGRAY);
			bprintf(text[OnlyXminutesLeft]
			        , ((ushort)timeleft / 60) + 1, (timeleft / 60) ? "s" : nulstr);
		if (!(startup->options & BBS_OPT_NO_TELNET_GA)
		    && now != last_telnet_cmd && now - getkey_last_activity >= 60 && !((now - getkey_last_activity) % 60)) {
			// Let's make sure the socket is up
			// Sending will trigger a socket d/c detection
			send_telnet_cmd(TELNET_GA, 0);
			last_telnet_cmd = now;

		time_t inactive = now - getkey_last_activity;
		if (online == ON_REMOTE && !(console & CON_NO_INACT)
		    && ((cfg.inactivity_warn && inactive >= cfg.max_getkey_inactivity * (cfg.inactivity_warn / 100.0))
		        || inactive >= cfg.max_getkey_inactivity)) {
			if ((sys_status & SS_USERON) && inactive < cfg.max_getkey_inactivity && *text[AreYouThere] != '\0') {
				bputs(text[AreYouThere]);
				bputs(text[InactivityAlert]);
			while (!inkey(K_NONE, 100) && online && now - getkey_last_activity < cfg.max_getkey_inactivity) {
				now = time(NULL);
			if (now - getkey_last_activity >= cfg.max_getkey_inactivity) {
				if (online == ON_REMOTE) {
					console |= CON_R_ECHO;
					console &= ~CON_R_ECHOX;
				bputs(text[CallBackWhenYoureThere]);
				logline(LOG_NOTICE, nulstr, "Maximum user input inactivity exceeded");
			if ((sys_status & SS_USERON) && *text[AreYouThere] != '\0') {
				attr(LIGHTGRAY);
				carriage_return();
				cleartoeol();
			getkey_last_activity = now;
}


/****************************************************************************/
/* Outputs a string highlighting characters preceded by a tilde             */
/****************************************************************************/
void sbbs_t::mnemonics(const char *instr)
	if (!strchr(instr, '~')) {
		mnestr = instr;
	bool ctrl_a_codes = contains_ctrl_a_attr(instr);
	if (!ctrl_a_codes) {
		if (instr[0] == '@' && *last == '@' && strchr(instr + 1, '@') == last && strchr(instr, ' ') == NULL) {
			mnestr = instr;

	mneattr_low = cfg.color[clr_mnelow];
	mneattr_high = cfg.color[clr_mnehigh];
	mneattr_cmd = cfg.color[clr_mnecmd];

	char str[256];
	expand_atcodes(instr, str, sizeof str);
	l = 0L;
	int  term = term_supports();
	while (str[l]) {
		if (str[l] == '~' && str[l + 1] < ' ') {
			add_hotspot('\r', /* hungry: */ true);
			l += 2;
		else if (str[l] == '~') {
			if (!(term & (ANSI | PETSCII)))
				outchar('(');
			l++;
			add_hotspot(str[l], /* hungry: */ true);
			outchar(str[l]);
			l++;
			if (!(term & (ANSI | PETSCII)))
		else if (str[l] == '`' && str[l + 1] != 0) {
			if (!(term & (ANSI | PETSCII)))
			add_hotspot(str[l], /* hungry: */ false);
			if (!(term & (ANSI | PETSCII)))
			if (str[l] == CTRL_A && str[l + 1] != 0) {
				if (str[l] == 'Z')   /* EOF (uppercase 'Z') */
				ctrl_a(str[l++]);
}

/****************************************************************************/
/* Prompts user for Y or N (yes or no) and CR is interpreted as a Y         */
rswindell's avatar
rswindell committed
/* Returns true for Yes or false for No                                     */
/* Called from quite a few places                                           */
/****************************************************************************/
bool sbbs_t::yesno(const char *str, int mode)
rswindell's avatar
rswindell committed
		return true;
	SAFECOPY(question, str);
	bprintf(mode, text[YesNoQuestion], str);
	while (online) {
		if (sys_status & SS_ABORT)
			ch = no_key();
			ch = getkey(K_UPPER | K_COLD);
		if (ch == yes_key() || ch == CR) {
			if (bputs(text[Yes], mode) && !(mode & P_NOCRLF))
			if (!(mode & P_SAVEATR))
		if (ch == no_key()) {
			if (bputs(text[No], mode) && !(mode & P_NOCRLF))
			if (!(mode & P_SAVEATR))
}

/****************************************************************************/
/* Prompts user for N or Y (no or yes) and CR is interpreted as a N         */
rswindell's avatar
rswindell committed
/* Returns true for No or false for Yes                                     */
/****************************************************************************/
bool sbbs_t::noyes(const char *str, int mode)
	SAFECOPY(question, str);
	bprintf(mode, text[NoYesQuestion], str);
	while (online) {
		if (sys_status & SS_ABORT)
			ch = no_key();
			ch = getkey(K_UPPER | K_COLD);
		if (ch == no_key() || ch == CR) {
			if (bputs(text[No], mode) && !(mode & P_NOCRLF))
			if (!(mode & P_SAVEATR))
		if (ch == yes_key()) {
			if (bputs(text[Yes], mode) && !(mode & P_NOCRLF))
			if (!(mode & P_SAVEATR))
}

/****************************************************************************/
/* Waits for remote or local user to hit a key among 'keys'.				*/
/* If 'keys' is NULL, *any* non-numeric key is valid input.					*/
/* 'max' is non-zero, allow that a decimal number input up to that size		*/
/* and return the value OR'd with 0x80000000.								*/
/****************************************************************************/
int sbbs_t::getkeys(const char *keys, uint max, int mode)
	char  str[81]{};
	uchar ch, n = 0, c = 0;
	uint  i = 0;
	if (keys != NULL) {
		SAFECOPY(str, keys);
	while (online) {
		ch = getkey(mode);
		if (max && ch > 0x7f)  /* extended ascii chars are digits to isdigit() */
		if (sys_status & SS_ABORT) {   /* return -1 if Ctrl-C hit */
			if (!(mode & (K_NOECHO | K_NOCRLF))) {
		if (ch && !n && ((keys == NULL && !IS_DIGIT(ch)) || (strchr(str, ch)))) {  /* return character if in string */
			if (ch > ' ') {
				if (!(mode & K_NOECHO))
				if (useron.misc & COLDKEYS) {
					while (online && !(sys_status & SS_ABORT)) {
						c = getkey(0);
						if (c == CR || c == BS || c == DEL)
							break;
					if (sys_status & SS_ABORT) {
						if (!(mode & (K_NOECHO | K_NOCRLF))) {
					if (c == BS || c == DEL) {
						if (!(mode & K_NOECHO))
				if (!(mode & (K_NOECHO | K_NOCRLF))) {
		if (ch == CR && max) {             /* return 0 if no number */
			if (!(mode & (K_NOECHO | K_NOCRLF))) {
				return i | 0x80000000L;    /* return number plus high bit */
			return 0;
		if ((ch == BS || ch == DEL) && n) {
			if (!(mode & K_NOECHO))
		else if (max && IS_DIGIT(ch) && (i * 10) + (ch & 0xf) <= max && (ch != '0' || n)) {
			i *= 10;
			i += ch & 0xf;
			if (!(mode & K_NOECHO))
			if (i * 10 > max && !(useron.misc & COLDKEYS) && keybuf_level() < 1) {
				if (!(mode & (K_NOECHO | K_NOCRLF))) {
				return i | 0x80000000L;
}

/****************************************************************************/
/* Prints PAUSE message and waits for a key stoke                           */
/* Returns false if aborted by user											*/
/****************************************************************************/
bool sbbs_t::pause(bool set_abort)
	char   ch;
	uint   tempattrs = curatr; /* was lclatr(-1) */
	int    l = K_UPPER;
	size_t len;
	if ((sys_status & SS_ABORT) || pause_inside)
rswindell's avatar
rswindell committed
	pause_inside = true;
	lncntr = 0;
	if (online == ON_REMOTE)
	if (mouse_hotspots.first == NULL)
	bputs(text[Pause]);
	if (sys_status & SS_USERON && !(useron.misc & (NOPAUSESPIN))
	    && cfg.spinning_pause_prompt)
		l |= K_SPIN;
	ch = getkey(l);
	if (pause_hotspot) {
rswindell's avatar
rswindell committed
		clear_hotspots();
	bool aborted = (ch == no_key() || ch == quit_key() || (sys_status & SS_ABORT));
	if (set_abort && aborted)
		sys_status |= SS_ABORT;
	if (text[Pause][0] != '@')
	getnodedat(cfg.node_num, &thisnode);
	nodesync();
	attr(tempattrs);
	if (ch == TERM_KEY_DOWN) // down arrow == display one more line
		lncntr = rows - 2;
rswindell's avatar
rswindell committed
	pause_inside = false;
}

/****************************************************************************/
/* Puts a character into the input buffer                                   */
/****************************************************************************/
bool sbbs_t::ungetkey(char ch, bool insert)
#if 0   /* this way breaks ansi_getxy() */
	RingBufWrite(&inbuf, (uchar*)&ch, sizeof(uchar));
	if (keybuf_space()) {
		char* p = c_escape_char(ch);
			p = dbg;
		}
		lprintf(LOG_DEBUG, "%s key into keybuf: %02X (%s)", insert ? "insert" : "append", ch, p);
		if (insert) {
			if (keybufbot == 0)
				keybufbot = KEY_BUFSIZE - 1;
			else
				keybufbot--;
			keybuf[keybufbot] = ch;
		} else {
			keybuf[keybuftop++] = ch;
			if (keybuftop == KEY_BUFSIZE)
				keybuftop = 0;
		return true;
	}
	lprintf(LOG_WARNING, "No space in keyboard input buffer");
	return false;
rswindell's avatar
rswindell committed

/****************************************************************************/
/* Puts a string into the input buffer										*/
/****************************************************************************/
bool sbbs_t::ungetkeys(const char* str, bool insert)
	for (i = 0; str[i] != '\0'; i++) {
		if (!ungetkey(str[i], insert))

size_t sbbs_t::keybuf_space(void)
{
	return sizeof(keybuf) - (keybuf_level() + 1);
}

size_t sbbs_t::keybuf_level(void)
{
	if (keybufbot > keybuftop)
		return (sizeof(keybuf) - keybufbot) + keybuftop;
	return keybuftop - keybufbot;
}