Skip to content
Snippets Groups Projects
con_out.cpp 40.2 KiB
Newer Older
/* Synchronet console output routines */

/****************************************************************************
 * @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 "cp437defs.h"
char* sbbs_t::auto_utf8(const char* str, int& mode)
rswindell's avatar
rswindell committed
{
	if (strncmp(str, "\xEF\xBB\xBF", 3) == 0) {
Rob Swindell's avatar
Rob Swindell committed
		return (char*)str;
rswindell's avatar
rswindell committed
	}
	if (mode & P_AUTO_UTF8) {
		if (!str_is_ascii(str) && utf8_str_is_valid(str))
rswindell's avatar
rswindell committed
	}
	return (char*)str;
}

/****************************************************************************/
/* Outputs a NULL terminated string locally and remotely (if applicable)    */
/* Handles ctrl-a codes, Telnet-escaping, column & line count, auto-pausing */
/* Supported P_* mode flags:
   P_PETSCII
   P_UTF8
   P_AUTO_UTF8
   P_NOATCODES
 ****************************************************************************/
int sbbs_t::bputs(const char *str, int mode)
	int    i;
	size_t l = 0;
	int    term = term_supports();
	if ((mode & P_REMOTE) && online != ON_REMOTE)
	if (online == ON_LOCAL && console & CON_L_ECHO)  /* script running as event */
		return lputs(LOG_INFO, str);
rswindell's avatar
rswindell committed
	size_t len = strlen(str);
	while (l < len && online) {
		switch (str[l]) {
			case '\b':
			case '\r':
			case '\n':
			case FF:
			case CTRL_A:
				break;
			default: // printing char
				if ((mode & P_TRUNCATE) && column >= (cols - 1)) {
		if (str[l] == CTRL_A && str[l + 1] != 0) {
			if (str[l] == 'Z')   /* EOF (uppercase 'Z' only) */
			if (str[l] == '~') { // Mouse hot-spot (hungry)
				if (str[l] >= ' ')
					add_hotspot(str[l], /* hungry */ true);
					add_hotspot('\r', /* hungry */ true);
			if (str[l] == '`' && str[l + 1] >= ' ') { // Mouse hot-spot (strict)
				add_hotspot(str[l], /* hungry */ false);
		if (!(mode & P_NOATCODES) && str[l] == '@') {
			if (str == mnestr          /* Mnemonic string or */
			    || (mode & P_ATCODES) /* trusted parameters to formatted string */
			    || (str >= text[0]    /* Straight out of TEXT.DAT */
			        && str <= text[TOTAL_TEXT - 1])) {
				i = show_atcode(str + l);   /* return 0 if not valid @ code */
				l += i;                   /* i is length of code string */
				if (i)                   /* if valid string, go to top */
			for (i = 0; i < TOTAL_TEXT; i++)
				if (str == text[i])
			if (i < TOTAL_TEXT) {      /* Replacement text */
				i = show_atcode(str + l);
				l += i;
				if (i)
		if (mode & P_PETSCII) {
			if (term & PETSCII)
rswindell's avatar
rswindell committed
				outcom(str[l++]);
			else
				petscii_to_ansibbs(str[l++]);
		} else if ((str[l] & 0x80) && (mode & P_UTF8)) {
			if (term & UTF8)
rswindell's avatar
rswindell committed
				outcom(str[l++]);
			else
rswindell's avatar
rswindell committed
				l += print_utf8_as_cp437(str + l, len - l);
rswindell's avatar
rswindell committed
		} else
			outchar(str[l++]);
rswindell's avatar
rswindell committed
/****************************************************************************/
/* Returns the printed columns from 'str' accounting for Ctrl-A codes		*/
/****************************************************************************/
size_t sbbs_t::bstrlen(const char *str, int mode)
rswindell's avatar
rswindell committed
{
rswindell's avatar
rswindell committed
	const char* end = str + strlen(str);
	while (str < end) {
		int len = 1;
		if (*str == CTRL_A) {
rswindell's avatar
rswindell committed
			str++;
			if (*str == 0 || *str == 'Z')    // EOF
rswindell's avatar
rswindell committed
				break;
			if (*str == '[') // CR
rswindell's avatar
rswindell committed
				count = 0;
			else if (*str == '<' && count) // ND-Backspace
rswindell's avatar
rswindell committed
				count--;
		} else if (((*str) & 0x80) && (mode & P_UTF8)) {
rswindell's avatar
rswindell committed
			enum unicode_codepoint codepoint = UNICODE_UNDEFINED;
			len = utf8_getc(str, end - str, &codepoint);
rswindell's avatar
rswindell committed
				break;
Rob Swindell's avatar
Rob Swindell committed
			count += unicode_width(codepoint, unicode_zerowidth);
rswindell's avatar
rswindell committed
		} else
			count++;
		str += len;
	}
	return count;
}


/* Perform PETSCII terminal output translation (from ASCII/CP437) */
unsigned char cp437_to_petscii(unsigned char ch)
rswindell's avatar
rswindell committed
{
	if (IS_ALPHA(ch))
		return ch ^ 0x20;   /* swap upper/lower case */
	switch (ch) {
		case '\1':      return '@';
		case '\x10':    return '>';
		case '\x11':    return '<';
		case '\x1e':    return PETSCII_UPARROW;
		case '\x1f':    return 'V';
		case '\x1a':    return '>';
		case '|':       return PETSCII_VERTLINE;
		case '\\':      return PETSCII_BACKSLASH;
		case '`':       return PETSCII_BACKTICK;
		case '~':       return PETSCII_TILDE;
		case '_':       return PETSCII_UNDERSCORE;
		case '{':       return '(';
		case '}':       return ')';
		case '\b':      return PETSCII_LEFT;
		case 156:       return PETSCII_BRITPOUND;
		case 251:       return PETSCII_CHECKMARK;
		case 176:       return PETSCII_LIGHTHASH;
		case 177:       return PETSCII_MEDIUMHASH;
		case 178:       return PETSCII_HEAVYHASH;
		case 219:       return PETSCII_SOLID;
		case 220:       return PETSCII_BOTTOMHALF;
		case 221:       return PETSCII_LEFTHALF;
		case 222:       return PETSCII_RIGHTHALF;
		case 223:       return PETSCII_TOPHALF;
		case 254:       return PETSCII_UPRLFTBOX;
rswindell's avatar
rswindell committed
		/* Line drawing chars */
		case 186:
		case 179:       return PETSCII_VERTLINE;
rswindell's avatar
rswindell committed
		case 205:
		case 196:       return PETSCII_HORZLINE;
rswindell's avatar
rswindell committed
		case 206:
		case 215:
		case 216:
		case 197:       return PETSCII_CROSS;
rswindell's avatar
rswindell committed
		case 188:
		case 189:
		case 190:
		case 217:       return '\xBD';
rswindell's avatar
rswindell committed
		case 201:
		case 213:
		case 214:
		case 218:       return '\xB0';
rswindell's avatar
rswindell committed
		case 183:
		case 184:
		case 187:
		case 191:       return '\xAE';
rswindell's avatar
rswindell committed
		case 200:
		case 211:
		case 212:
		case 192:       return '\xAD';
rswindell's avatar
rswindell committed
		case 198:
		case 199:
		case 204:
		case 195:       return '\xAB';
rswindell's avatar
rswindell committed
		case 180:
		case 181:
		case 182:
		case 185:       return '\xB3';
rswindell's avatar
rswindell committed
		case 203:
		case 209:
		case 210:
		case 194:       return '\xB2';
rswindell's avatar
rswindell committed
		case 202:
		case 207:
		case 208:
		case 193:       return '\xB1';
rswindell's avatar
rswindell committed
	}
rswindell's avatar
rswindell committed
		return exascii_to_ascii_char(ch);
	return ch;
}

/* Perform PETSCII conversion to ANSI-BBS/CP437 */
int sbbs_t::petscii_to_ansibbs(unsigned char ch)
{
	if ((ch & 0xe0) == 0xc0)   /* "Codes $60-$7F are, actually, copies of codes $C0-$DF" */
		ch = 0x60 | (ch & 0x1f);
	if (IS_ALPHA(ch))
		return outchar(ch ^ 0x20);  /* swap upper/lower case */
	switch (ch) {
		case '\r':                  newline();      break;
		case PETSCII_HOME:          cursor_home();  break;
		case PETSCII_CLEAR:         return CLS;
		case PETSCII_DELETE:        backspace();    break;
		case PETSCII_LEFT:          cursor_left();  break;
		case PETSCII_RIGHT:         cursor_right(); break;
		case PETSCII_UP:            cursor_up();    break;
		case PETSCII_DOWN:          cursor_down();  break;

		case PETSCII_BRITPOUND:     return outchar((char)156);
		case PETSCII_CHECKMARK:     return outchar((char)251);
		case PETSCII_LIGHTHASH:     return outchar((char)176);
		case PETSCII_MEDIUMHASH:    return outchar((char)177);
		case PETSCII_HEAVYHASH:     return outchar((char)178);
		case PETSCII_SOLID:         return outchar((char)219);
		case PETSCII_BOTTOMHALF:    return outchar((char)220);
		case PETSCII_LEFTHALF:      return outchar((char)221);
		case PETSCII_RIGHTHALF:     return outchar((char)222);
		case PETSCII_TOPHALF:       return outchar((char)223);
		case PETSCII_LWRLFTBOX:
		case PETSCII_LWRRHTBOX:
		case PETSCII_UPRRHTBOX:
		case PETSCII_UPRLFTBOX:     return outchar((char)254);
		case PETSCII_VERTLINE:      return outchar((char)179);
		case PETSCII_HORZLINE:      return outchar((char)196);
		case PETSCII_CROSS:         return outchar((char)197);
		case (uchar)'\xBD':         return outchar((char)217);
		case (uchar)'\xB0':         return outchar((char)218);
		case (uchar)'\xAE':         return outchar((char)191);
		case (uchar)'\xAD':         return outchar((char)192);
		case (uchar)'\xAB':         return outchar((char)195);
		case (uchar)'\xB3':         return outchar((char)180);
		case (uchar)'\xB2':         return outchar((char)194);
		case (uchar)'\xB1':         return outchar((char)193);
		case PETSCII_BLACK:         return attr(BLACK);
		case PETSCII_WHITE:         return attr(WHITE);
		case PETSCII_RED:           return attr(RED);
		case PETSCII_GREEN:         return attr(GREEN);
		case PETSCII_BLUE:          return attr(BLUE);
		case PETSCII_ORANGE:        return attr(MAGENTA);
		case PETSCII_BROWN:         return attr(BROWN);
		case PETSCII_YELLOW:        return attr(YELLOW);
		case PETSCII_CYAN:          return attr(LIGHTCYAN);
		case PETSCII_LIGHTRED:      return attr(LIGHTRED);
		case PETSCII_DARKGRAY:      return attr(DARKGRAY);
		case PETSCII_MEDIUMGRAY:    return attr(CYAN);
		case PETSCII_LIGHTGREEN:    return attr(LIGHTGREEN);
		case PETSCII_LIGHTBLUE:     return attr(LIGHTBLUE);
		case PETSCII_LIGHTGRAY:     return attr(LIGHTGRAY);
		case PETSCII_PURPLE:        return attr(LIGHTMAGENTA);
		case PETSCII_REVERSE_ON:    return attr((curatr & 0x07) << 4);
		case PETSCII_REVERSE_OFF:   return attr(curatr >> 4);
		case PETSCII_FLASH_ON:      return attr(curatr | BLINK);
		case PETSCII_FLASH_OFF:     return attr(curatr & ~BLINK);
			if (ch & 0x80)
				return bprintf("#%3d", ch);
			return outchar(ch);
		case PETSCII_UPPERLOWER:
		case PETSCII_UPPERGRFX:
			/* Do nothing */
			return 0;
	}
	return 0;
}

rswindell's avatar
rswindell committed
size_t sbbs_t::print_utf8_as_cp437(const char* str, size_t len)
	if (((*str) & 0x80) == 0) {
	enum unicode_codepoint codepoint = UNICODE_UNDEFINED;
	len = utf8_getc(str, len, &codepoint);
	if ((int)len < 2) {
		outchar(*str);  // Assume it's a CP437 character
		lprintf(LOG_DEBUG, "Invalid UTF-8 sequence: %02X (error = %d)", (uchar) * str, (int)len);
	for (int i = 1; i < 0x100; i++) {
		if (cp437_unicode_tbl[i]
		    && cp437_unicode_tbl[i] == codepoint) {
	char ch = unicode_to_cp437(codepoint);
	else if (unicode_width(codepoint, unicode_zerowidth) > 0) {
		outchar(CP437_INVERTED_QUESTION_MARK);
		for (size_t i = 0; i < len; i++)
			snprintf(seq + strlen(seq), 4, "%02X ", (uchar) * (str + i));
		lprintf(LOG_DEBUG, "Unsupported UTF-8 sequence: %s (U+%X)", seq, codepoint);
	}
	return len;
}
/****************************************************************************/
/* Raw put string (remotely)												*/
/* Performs Telnet IAC escaping												*/
/* Performs charset translations											*/
/* Performs saveline buffering (for restoreline)							*/
/* DOES NOT expand ctrl-A codes, track columns, lines, auto-pause, etc.     */
/****************************************************************************/
int sbbs_t::rputs(const char *str, size_t len)
	if (console & CON_ECHO_OFF)
		return 0;
	if (len == 0)
		len = strlen(str);
	int  term = term_supports();
	for (l = 0; l < len && online; l++) {
		if (term & PETSCII) {
			if (ch == PETSCII_SOLID)
		else if ((term & NO_EXASCII) && (ch & 0x80))
			ch = exascii_to_ascii_char(ch);  /* seven bit table */
		else if (term & UTF8) {
			enum unicode_codepoint codepoint = cp437_unicode_tbl[(uchar)ch];
			if (codepoint != 0)
				utf8_putc(utf8, sizeof(utf8) - 1, codepoint);
		}
			if (outcom(ch) != 0)
			if ((char)ch == (char)TELNET_IAC && !(telnet_mode & TELNET_MODE_OFF))
				outcom(TELNET_IAC); /* Must escape Telnet IAC char (255) */
			if ((term & PETSCII) && ch == PETSCII_SOLID)
		if (ch == '\n')
			lbuflen = 0;
		else if (lbuflen < LINE_BUFSIZE) {
			if (lbuflen == 0)
			lbuf[lbuflen++] = str[l]; // save non-translated char to line buffer
}

/****************************************************************************/
/* Performs printf() using bbs bputs function								*/
/****************************************************************************/
int sbbs_t::bprintf(const char *fmt, ...)
	if (strchr(fmt, '%') == NULL)
	va_start(argptr, fmt);
	vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
	sbuf[sizeof(sbuf) - 1] = 0; /* force termination */
	va_end(argptr);
rswindell's avatar
rswindell committed
/****************************************************************************/
/* Performs printf() using bbs bputs function (with mode)					*/
/****************************************************************************/
int sbbs_t::bprintf(int mode, const char *fmt, ...)
rswindell's avatar
rswindell committed
{
	va_list argptr;
rswindell's avatar
rswindell committed

	if (strchr(fmt, '%') == NULL)
		return bputs(fmt, mode);
	va_start(argptr, fmt);
	vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
	sbuf[sizeof(sbuf) - 1] = 0; /* force termination */
rswindell's avatar
rswindell committed
	va_end(argptr);
	return bputs(sbuf, mode);
/****************************************************************************/
/* Performs printf() using bbs rputs function								*/
/****************************************************************************/
int sbbs_t::rprintf(const char *fmt, ...)
	va_start(argptr, fmt);
	vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
	sbuf[sizeof(sbuf) - 1] = 0; /* force termination */
	va_end(argptr);
/****************************************************************************/
/* Performs printf() using bbs putcom/outcom functions						*/
/****************************************************************************/
int sbbs_t::comprintf(const char *fmt, ...)
{
	va_list argptr;
	va_start(argptr, fmt);
	vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
	sbuf[sizeof(sbuf) - 1] = 0; /* force termination */
	return putcom(sbuf);
/****************************************************************************/
rswindell's avatar
rswindell committed
/* Outputs destructive backspace 											*/
/****************************************************************************/
	if (!(console & CON_ECHO_OFF)) {
		for (int i = 0; i < count; i++) {
			if (term_supports(PETSCII))
				outcom(PETSCII_DELETE);
			else {
				outcom('\b');
				outcom(' ');
				outcom('\b');
			}
rswindell's avatar
rswindell committed
		}
/****************************************************************************/
/* Returns true if the user (or the yet-to-be-logged-in client) supports	*/
/* all of the specified terminal 'cmp_flags' (e.g. ANSI, COLOR, RIP).		*/
/* If no flags specified, returns all terminal flag bits supported			*/
/****************************************************************************/
int sbbs_t::term_supports(int cmp_flags)
	int flags = ((sys_status & (SS_USERON | SS_NEWUSER)) && !(useron.misc & AUTOTERM)) ? useron.misc : autoterm;
	if ((sys_status & (SS_USERON | SS_NEWUSER)) && (useron.misc & AUTOTERM))
		flags |= useron.misc & (NO_EXASCII | SWAP_DELETE | COLOR | ICE_COLOR | MOUSE);
	return cmp_flags ? ((flags & cmp_flags) == cmp_flags) : (flags & TERM_FLAGS);
char* sbbs_t::term_rows(user_t* user, char* str, size_t size)
{
	if (user->rows >= TERM_ROWS_MIN && user->rows <= TERM_ROWS_MAX)
		rows = user->rows;
	safe_snprintf(str, size, "%s%d %s", user->rows ? nulstr:text[TerminalAutoDetect], rows, text[TerminalRows]);
	return str;
}

char* sbbs_t::term_cols(user_t* user, char* str, size_t size)
{
	if (user->cols >= TERM_COLS_MIN && user->cols <= TERM_COLS_MAX)
		cols = user->cols;
	safe_snprintf(str, size, "%s%d %s", user->cols ? nulstr:text[TerminalAutoDetect], cols, text[TerminalColumns]);
	return str;
}

/****************************************************************************/
/* Returns verbose description of the terminal type	configuration for user	*/
/****************************************************************************/
char* sbbs_t::term_type(user_t* user, int term, char* str, size_t size)
{
	if (term & PETSCII)
		safe_snprintf(str, size, "%sCBM/PETSCII"
		              , (user->misc & AUTOTERM) ? text[TerminalAutoDetect] : nulstr);
	else
		safe_snprintf(str, size, "%s%s / %s %s%s%s"
		              , (user->misc & AUTOTERM) ? text[TerminalAutoDetect] : nulstr
		              , term_charset(term)
		              , term_type(term)
		              , (term & COLOR) ? (term & ICE_COLOR ? text[TerminalIceColor] : text[TerminalColor]) : text[TerminalMonochrome]
		              , (term & MOUSE) ? text[TerminalMouse] : ""
		              , (term & SWAP_DELETE) ? "DEL=BS" : nulstr);
/****************************************************************************/
/* Returns short description of the terminal type							*/
/****************************************************************************/
const char* sbbs_t::term_type(int term)
	if (term & PETSCII)
		return "ANSI";
	return "DUMB";
}

/****************************************************************************/
/* Returns description of the terminal supported character set (charset)	*/
/****************************************************************************/
const char* sbbs_t::term_charset(int term)
	if (term & PETSCII)
	if (term & NO_EXASCII)
/****************************************************************************/
/* For node spying purposes													*/
/****************************************************************************/
bool sbbs_t::update_nodeterm(void)
{
	str_list_t ini = strListInit();
	iniSetInteger(&ini, ROOT_SECTION, "cols", cols, NULL);
	iniSetInteger(&ini, ROOT_SECTION, "rows", rows, NULL);
	iniSetString(&ini, ROOT_SECTION, "desc", terminal, NULL);
	iniSetString(&ini, ROOT_SECTION, "type", term_type(), NULL);
	iniSetString(&ini, ROOT_SECTION, "chars", term_charset(), NULL);
	iniSetHexInt(&ini, ROOT_SECTION, "flags", term_supports(), NULL);
	iniSetHexInt(&ini, ROOT_SECTION, "mouse", mouse_mode, NULL);
	iniSetHexInt(&ini, ROOT_SECTION, "console", console, NULL);

	char  path[MAX_PATH + 1];
	SAFEPRINTF(path, "%sterminal.ini", cfg.node_dir);
	FILE* fp = iniOpenFile(path, /* for_modify: */ TRUE);
	bool  result = false;
	if (fp != NULL) {
		result = iniWriteFile(fp, ini);
		iniCloseFile(fp);
	}
	strListFree(&ini);
	if (mqtt->connected) {
		char str[256];
		char topic[128];
		SAFEPRINTF(topic, "node/%u/terminal", cfg.node_num);
		snprintf(str, sizeof(str), "%u\t%u\t%s\t%s\t%s\t%x\t%x\t%x"
		         , cols
		         , rows
		         , terminal
		         , term_type()
		         , term_charset()
		         , term_supports()
		         , mouse_mode
		         , console
		         );
		mqtt_pub_strval(mqtt, TOPIC_BBS, topic, str);
/****************************************************************************/
/* Outputs character														*/
/* Performs terminal translations (e.g. EXASCII-to-ASCII, FF->ESC[2J)		*/
/* Performs Telnet IAC escaping												*/
rswindell's avatar
rswindell committed
/* Performs tab expansion													*/
/* Performs column counting, line counting, and auto-pausing				*/
/* Performs saveline buffering (for restoreline)							*/
/****************************************************************************/
	if (console & CON_ECHO_OFF)
	if (ch == ESC && outchar_esc < ansiState_string)
		outchar_esc = ansiState_esc;
	else if (outchar_esc == ansiState_esc) {
		if (ch == '[')
			outchar_esc = ansiState_csi;
		else if (ch == '_' || ch == 'P' || ch == '^' || ch == ']')
			outchar_esc = ansiState_string;
		else if (ch == 'X')
			outchar_esc = ansiState_sos;
		else if (ch >= '@' && ch <= '_')
			outchar_esc = ansiState_final;
deuce's avatar
deuce committed
		else
			outchar_esc = ansiState_none;
deuce's avatar
deuce committed
	}
	else if (outchar_esc == ansiState_csi) {
		if (ch >= '@' && ch <= '~')
			outchar_esc = ansiState_final;
	else if (outchar_esc == ansiState_string) {  // APS, DCS, PM, or OSC
			outchar_esc = ansiState_esc;
		if (!((ch >= '\b' && ch <= '\r') || (ch >= ' ' && ch <= '~')))
			outchar_esc = ansiState_none;
	else if (outchar_esc == ansiState_sos) { // SOS
			outchar_esc = ansiState_sos_esc;
	else if (outchar_esc == ansiState_sos_esc) { // ESC inside SOS
			outchar_esc = ansiState_esc;
			outchar_esc = ansiState_none;
			outchar_esc = ansiState_sos;
		outchar_esc = ansiState_none;
	if (outchar_esc == ansiState_none && rainbow_index >= 0) {
		attr(rainbow[rainbow_index]);
		if (rainbow[rainbow_index + 1] == 0) {
			if (rainbow_repeat)
				rainbow_index = 0;
		} else
			++rainbow_index;
	}
	int  term = term_supports();
	if (!(term & PETSCII)) {
		if ((term & NO_EXASCII) && (ch & 0x80))
			ch = exascii_to_ascii_char(ch);  /* seven bit table */
		else if (term & UTF8) {
			enum unicode_codepoint codepoint = cp437_unicode_tbl[(uchar)ch];
			if (codepoint != 0)
				utf8_putc(utf8, sizeof(utf8) - 1, codepoint);
		}
	if (ch == FF && lncntr > 0 && row > 0) {
		lncntr = 0;
rswindell's avatar
rswindell committed
		newline();
		if (!(sys_status & SS_PAUSEOFF)) {
			while (lncntr && online && !(sys_status & SS_ABORT))
	if (!(console & CON_R_ECHO))
	if ((console & CON_R_ECHOX) && (uchar)ch >= ' ' && outchar_esc == ansiState_none) {
rswindell's avatar
rswindell committed
		clearscreen(term);
	else if (ch == '\t') {
		outcom(' ');
		column++;
		while (column % tabstop) {
rswindell's avatar
rswindell committed
			outcom(' ');
			column++;
		if (ch == (char)TELNET_IAC && !(telnet_mode & TELNET_MODE_OFF))
			outcom(TELNET_IAC); /* Must escape Telnet IAC char (255) */
		if (ch == '\r' && (console & CON_CR_CLREOL))
		if (ch == '\n' && line_delay)
		if (term & PETSCII) {
			uchar pet = cp437_to_petscii(ch);
			if (pet == PETSCII_SOLID)
				outcom(PETSCII_REVERSE_ON);
			outcom(pet);
			if (pet == PETSCII_SOLID)
				outcom(PETSCII_REVERSE_OFF);
			if (ch == '\r' && (curatr & 0xf0) != 0) // reverse video is disabled upon CR
				curatr >>= 4;
	if (outchar_esc == ansiState_none) {
		/* Track cursor position locally */
		switch (ch) {
			case '\a':  // 7
			case '\t':  // 9
				/* Non-printing or handled elsewhere */
				break;
			case '\b':  // 8
				if (column > 0)
				if (lbuflen < LINE_BUFSIZE) {
					if (lbuflen == 0)
rswindell's avatar
rswindell committed
				inc_row(1);
				if (lncntr || lastlinelen)
			case FF:    // 12
				lncntr = 0;
				lbuflen = 0;
				row = 0;
				column = 0;
			case '\r':  // 13
				if (!lbuflen)
					latr = curatr;
				if (lbuflen < LINE_BUFSIZE)
					lbuf[lbuflen++] = ch;
	if (outchar_esc == ansiState_final)
		outchar_esc = ansiState_none;
	if (lncntr == rows - 1 && ((useron.misc & (UPAUSE ^ (console & CON_PAUSEOFF))) || sys_status & SS_PAUSEON)
	    && !(sys_status & (SS_PAUSEOFF | SS_ABORT))) {
		lncntr = 0;
int sbbs_t::outchar(enum unicode_codepoint codepoint, const char* cp437_fallback)
	if (term_supports(UTF8)) {
		int  len = utf8_putc(str, sizeof(str), codepoint);
		if (len < 1)
Rob Swindell's avatar
Rob Swindell committed
		inc_column(unicode_width(codepoint, unicode_zerowidth));
	if (cp437_fallback == NULL)
	return bputs(cp437_fallback);
}

int sbbs_t::outchar(enum unicode_codepoint codepoint, char cp437_fallback)
{
	char str[2] = { cp437_fallback, '\0' };
	return outchar(codepoint, str);
}

void sbbs_t::inc_column(int count)
{
	column += count;
	if (column >= cols) {    // assume terminal has/will auto-line-wrap
		lncntr++;
		lbuflen = 0;
		lastlinelen = column;
		column = 0;
rswindell's avatar
rswindell committed
		inc_row(1);
	}
}

void sbbs_t::inc_row(int count)
{
	row += count;
rswindell's avatar
rswindell committed
		scroll_hotspots((row - rows) + 1);
		row = rows - 1;
void sbbs_t::center(const char *instr, bool msg, unsigned int columns)
	size_t len;
	SAFECOPY(str, instr);
	len = bstrlen(str);
		cursor_right((columns - len) / 2);
		putmsg(str, P_NONE);
	else
		bputs(str);
rswindell's avatar
rswindell committed
	newline();
}

rswindell's avatar
rswindell committed
void sbbs_t::wide(const char* str)
{
	int term = term_supports();
	while (*str != '\0') {
		if ((term & UTF8) && *str >= '!' && *str <= '~')
rswindell's avatar
rswindell committed
			outchar((enum unicode_codepoint)(UNICODE_FULLWIDTH_EXCLAMATION_MARK + (*str - '!')));
		else {
			outchar(*str);
			outchar(' ');
		}
		str++;
	}
}


rswindell's avatar
rswindell committed
// Send a bare carriage return, hopefully moving the cursor to the far left, current row
void sbbs_t::carriage_return(int count)
rswindell's avatar
rswindell committed
{
	for (int i = 0; i < count; i++) {
		if (term_supports(PETSCII))
			cursor_left(column);
		else
			outcom('\r');
		column = 0;
	}
rswindell's avatar
rswindell committed
}

// Send a bare line_feed, hopefully moving the cursor down one row, current column
rswindell's avatar
rswindell committed
{
	for (int i = 0; i < count; i++) {
		if (term_supports(PETSCII))
rswindell's avatar
rswindell committed
{
	for (int i = 0; i < count; i++) {
void sbbs_t::clearscreen(int term)
rswindell's avatar
rswindell committed
{
	clear_hotspots();
	if (term & ANSI)
		putcom("\x1b[2J\x1b[H");    /* clear screen, home cursor */
	else if (term & PETSCII)
rswindell's avatar
rswindell committed
		outcom(PETSCII_CLEAR);
	else
		outcom(FF);
	row = 0;
	column = 0;
	lncntr = 0;
rswindell's avatar
rswindell committed
	carriage_return();
	int term = term_supports();
	else if (term & PETSCII)
rswindell's avatar
rswindell committed
		outcom(PETSCII_HOME);
		outchar(FF);    /* this will clear some terminals, do nothing with others */
	row = 0;
	column = 0;
	int term = term_supports();
	if (term & ANSI) {
		if (count > 1)
			comprintf("\x1b[%dA", count);
rswindell's avatar
rswindell committed
		else
rswindell's avatar
rswindell committed
	} else {
		if (term & PETSCII) {
			for (int i = 0; i < count; i++)
rswindell's avatar
rswindell committed
				outcom(PETSCII_UP);
		}
	}
	if (term_supports(ANSI)) {
		if (count > 1)
			comprintf("\x1b[%dB", count);
rswindell's avatar
rswindell committed
		else
rswindell's avatar
rswindell committed
	} else {
		for (int i = 0; i < count; i++)
rswindell's avatar
rswindell committed
			line_feed();
	}
	int term = term_supports();
	if (term & ANSI) {
		if (count > 1)
			comprintf("\x1b[%dC", count);
		for (int i = 0; i < count; i++) {
			if (term & PETSCII)
rswindell's avatar
rswindell committed
				outcom(PETSCII_RIGHT);
			else
				outcom(' ');