Skip to content
Snippets Groups Projects
con_out.cpp 36.1 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, long& mode)
rswindell's avatar
rswindell committed
{
	if(strncmp(str, "\xEF\xBB\xBF", 3) == 0) {
rswindell's avatar
rswindell committed
		return (char*)(str + 3);
	}
rswindell's avatar
rswindell committed
		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
 ****************************************************************************/
rswindell's avatar
rswindell committed
int sbbs_t::bputs(const char *str, long mode)
rswindell's avatar
rswindell committed
	long term = term_supports();
	if((mode & P_REMOTE) && online != ON_REMOTE)
		return 0;

	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)) {
					l++;
					continue;
				}
		}
		if(str[l]==CTRL_A && str[l+1]!=0) {
			l++;
			if(str[l] == 'Z')	/* EOF (uppercase 'Z' only) */
				break;
			if(str[l] == '~') { // Mouse hot-spot (hungry)
				if(str[l] >= ' ')
					add_hotspot(str[l], /* hungry */true);
				else
					add_hotspot('\r', /* hungry */true);
			if(str[l] == '`' && str[l + 1] >= ' ') { // Mouse hot-spot (strict)
				l++;
				add_hotspot(str[l], /* hungry */false);
		if(!(mode&P_NOATCODES) && str[l]=='@') {
			if(str==mnestr			/* Mnemonic string or */
				|| (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])
					break;
			if(i<TOTAL_TEXT) {		/* Replacement text */
				i=show_atcode(str+l);
rswindell's avatar
rswindell committed
		if(mode&P_PETSCII) {
			if(term&PETSCII)
				outcom(str[l++]);
			else
				petscii_to_ansibbs(str[l++]);
		} else if((str[l]&0x80) && (mode&P_UTF8)) {
			if(term&UTF8)
				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, long mode)
{
rswindell's avatar
rswindell committed
	size_t count = 0;
	const char* end = str + strlen(str);
	while (str < end) {
		int len = 1;
		if(*str == CTRL_A) {
			str++;
			if(*str == 0 || *str == 'Z')	// EOF
				break;
			if(*str == '[') // CR
				count = 0;
			else if(*str == '<' && count) // ND-Backspace
				count--;
		} else if(((*str) & 0x80) && (mode&P_UTF8)) {
			enum unicode_codepoint codepoint = UNICODE_UNDEFINED;
			len = utf8_getc(str, end - str, &codepoint);
			if(len < 1)
				break;
			count += unicode_width(codepoint);;
		} 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
{
rswindell's avatar
rswindell committed
		return ch ^ 0x20;	/* swap upper/lower case */
	switch(ch) {
		case '\1':		return '@';
		case '\x10':	return '>';
		case '\x11':	return '<';
		case '\x18':	
		case '\x1e':	return PETSCII_UPARROW;
		case '\x19':
		case '\x1f':	return 'V';
		case '\x1a':	return '>';
rswindell's avatar
rswindell committed
		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;
		/* Line drawing chars */
		case 186:
		case 179:		return PETSCII_VERTLINE;
		case 205:
		case 196:		return PETSCII_HORZLINE;
		case 206:
		case 215:
		case 216:
		case 197:		return PETSCII_CROSS;
		case 188:
		case 189:
		case 190:
		case 217:		return '\xBD';
		case 201:
		case 213:
		case 214:
		case 218:		return '\xB0';
		case 183:
		case 184:
		case 187:
		case 191:		return '\xAE';
		case 200:
		case 211:
		case 212:
		case 192:		return '\xAD';
		case 198:
		case 199:
		case 204:
		case 195:		return '\xAB';
		case 180:
		case 181:
		case 182:
		case 185:		return '\xB3';
		case 203:
		case 209:
		case 210:
		case 194:		return '\xB2';
		case 202:
		case 207:
		case 208:
		case 193:		return '\xB1';
	}
	if(ch&0x80)
		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);
		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_CHECKERBRD:
		case PETSCII_LIGHTHASH:		return outchar((char)176);
		case 0x7e:
		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);

		/* Line drawing chars */
		case 0x7D:
		case PETSCII_VERTLINE:		return outchar((char)179);
		case PETSCII_HORZLINE:		return outchar((char)196);
		case 0x7B:
		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);
		default:					
			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) {
		outchar(*str);
		return sizeof(char);
	}
	enum unicode_codepoint codepoint = UNICODE_UNDEFINED;
	len = utf8_getc(str, len, &codepoint);
		outchar(*str);	// Assume it's a CP437 character
		lprintf(LOG_DEBUG, "Invalid UTF-8 sequence: %02X (error = %d)", (uchar)*str, (int)len);
		return 1;
	}
	for(int i = 1; i < 0x100; i++) {
		if(cp437_unicode_tbl[i]
			&& cp437_unicode_tbl[i] == codepoint) {
			outchar(i);
			return len;
		}
	}
	char ch = unicode_to_cp437(codepoint);
	else if(unicode_width(codepoint) > 0) {
		outchar(CP437_INVERTED_QUESTION_MARK);
		char seq[32] = "";
		for(size_t i = 0; i < len; i++)
			sprintf(seq + strlen(seq), "%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);
rswindell's avatar
rswindell committed
	long term = term_supports();
	for(l=0;l<len && online;l++) {
			if(ch == PETSCII_SOLID)
				outcom(PETSCII_REVERSE_ON);
		}
		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(utf8[0])
			putcom(utf8);
			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)
				outcom(PETSCII_REVERSE_OFF);
		if(ch == '\n')
			lbuflen=0;
		else if(lbuflen<LINE_BUFSIZE) {
			if(lbuflen == 0)
				latr = curatr;
rswindell's avatar
rswindell committed
			lbuf[lbuflen++] = ch;
	return(l);
}

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

rswindell's avatar
rswindell committed
/****************************************************************************/
/* Performs printf() using bbs bputs function (with mode)					*/
/****************************************************************************/
int sbbs_t::bprintf(long mode, const char *fmt, ...)
{
	va_list argptr;
	char sbuf[4096];

	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 */
	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);
	return(rputs(sbuf));
}

/****************************************************************************/
/* Performs printf() using bbs putcom/outcom functions						*/
/****************************************************************************/
int sbbs_t::comprintf(const char *fmt, ...)
{
	va_list argptr;
	char sbuf[4096];

	va_start(argptr,fmt);
	vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;	/* force termination */
	va_end(argptr);
	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			*/
/****************************************************************************/
long sbbs_t::term_supports(long cmp_flags)
{
	long flags = ((sys_status&SS_USERON) && !(useron.misc&AUTOTERM)) ? useron.misc : autoterm;
	if((sys_status&SS_USERON) && (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));
}

/****************************************************************************/
/* Returns description of the terminal type									*/
/****************************************************************************/
const char* sbbs_t::term_type(long term)
{
	if(term == -1)
		term = term_supports();
	if(term&PETSCII)
		return "PETSCII";
	if(term&RIP)
		return "RIP";
	if(term&ANSI)
		return "ANSI";
	return "DUMB";
}

/****************************************************************************/
/* Returns description of the terminal supported character set (charset)	*/
/****************************************************************************/
const char* sbbs_t::term_charset(long term)
{
	if(term == -1)
		term = term_supports();
	if(term&PETSCII)
		return "CBM-ASCII";
	if(term&UTF8)
		return "UTF-8";
	if(term&NO_EXASCII)
		return "US-ASCII";
	return "CP437";
}

/****************************************************************************/
/* 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(cfg.mqtt.enabled) {
		char str[256];
		char topic[128];
		SAFEPRINTF(topic, "node%u/terminal", cfg.node_num);
		snprintf(str, sizeof(str), "%lu\t%lu\t%s\t%s\t%s\t%lx\t%lx\t%lx"
			,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;
			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;
rswindell's avatar
rswindell committed
	long term = term_supports();
	if(!(term&PETSCII)) {
		if((term&NO_EXASCII) && (ch&0x80))
			ch = exascii_to_ascii_char(ch);  /* seven bit table */
			enum unicode_codepoint codepoint = cp437_unicode_tbl[(uchar)ch];
			if(codepoint != 0)
				utf8_putc(utf8, sizeof(utf8) - 1, codepoint);
		}
rswindell's avatar
rswindell committed
	if(ch==FF && lncntr > 0 && row > 0) {
rswindell's avatar
rswindell committed
		newline();
		if(!(sys_status&SS_PAUSEOFF)) {
			pause();
			while(lncntr && online && !(sys_status&SS_ABORT))
	if(!(console&CON_R_ECHO))
		return 0;

	if((console&CON_R_ECHOX) && (uchar)ch>=' ' && outchar_esc == ansiState_none) {
		ch=text[YNQP][3];
		if(text[YNQP][2]==0 || ch==0) ch='X';
	}
rswindell's avatar
rswindell committed
	if(ch==FF)
		clearscreen(term);
	else if(ch == '\t') {
		outcom(' ');
		column++;
		while(column%tabstop) {
rswindell's avatar
rswindell committed
			outcom(' ');
			column++;
	else {
		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))
			cleartoeol();
		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;
			else {
				if(ch == '\n' && line_delay)
					SLEEP(line_delay);
	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)
					column--;
				if(lbuflen<LINE_BUFSIZE) {
					if(lbuflen == 0)
						latr = curatr;
					lbuf[lbuflen++] = ch;
				}
				break;
			case '\n':	// 10
rswindell's avatar
rswindell committed
				inc_row(1);
				if(lncntr || lastlinelen)
					lncntr++;
				lbuflen=0;
				break;
			case FF:	// 12
				lncntr=0;
				lbuflen=0;
rswindell's avatar
rswindell committed
				row=0;
				column=0;
			case '\r':	// 13
				column=0;
				break;
			default:
				if(!lbuflen)
					latr=curatr;
				if(lbuflen<LINE_BUFSIZE)
					lbuf[lbuflen++]=ch;
				break;
	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))) {
int sbbs_t::outchar(enum unicode_codepoint codepoint, const char* cp437_fallback)
{
	if(term_supports(UTF8)) {
		char str[UTF8_MAX_LEN];
		int len = utf8_putc(str, sizeof(str), codepoint);
		if(len < 1)
			return len;
		putcom(str, len);
		inc_column(unicode_width(codepoint));
		return 0;
	}
	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;
	if(row >= rows) {
		scroll_hotspots((row - rows) + 1);
		row = rows - 1;
void sbbs_t::center(const char *instr, bool msg, unsigned int columns)
	size_t len;
	len = bstrlen(str);
	if(len < columns)
		cursor_right((columns - len) / 2);
	if(msg)
		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)
{
	long term = term_supports();
	while(*str != '\0') {
		if((term&UTF8) && *str >= '!' && *str <= '~')
			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
{
	if(count < 1)
		return;
	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
{
	if(count < 1)
		return;
	for(int i = 0; i < count; i++) {
		if(term_supports(PETSCII))
			outcom(PETSCII_DOWN);
		else 
			outcom('\n');
	}
rswindell's avatar
rswindell committed
{
	if(count < 1)
		return;
	for(int i = 0; i < count; i++) { 
		outchar('\r');
		outchar('\n');
	}
rswindell's avatar
rswindell committed
void sbbs_t::clearscreen(long term)
{
	clear_hotspots();
	if(term&ANSI)
		putcom("\x1b[2J\x1b[H");	/* clear screen, home cursor */
	else if(term&PETSCII)
		outcom(PETSCII_CLEAR);
	else
		outcom(FF);
rswindell's avatar
rswindell committed
	carriage_return();
rswindell's avatar
rswindell committed
	long term = term_supports();
	if(term&ANSI)
rswindell's avatar
rswindell committed
	else if(term&PETSCII)
		outcom(PETSCII_HOME);
		outchar(FF);	/* this will clear some terminals, do nothing with others */
rswindell's avatar
rswindell committed
	row=0;
rswindell's avatar
rswindell committed
	long term = term_supports();
	if(term&ANSI) {
		if(count>1)
rswindell's avatar
rswindell committed
		else
rswindell's avatar
rswindell committed
	} else {
		if(term&PETSCII) {
			for(int i=0;i<count;i++)
				outcom(PETSCII_UP);
		}
	}
rswindell's avatar
rswindell committed
	if(term_supports(ANSI)) {
		if(count>1)
rswindell's avatar
rswindell committed
		else
rswindell's avatar
rswindell committed
	} else {
		for(int i=0;i<count;i++)
			line_feed();
	}
rswindell's avatar
rswindell committed
	long term = term_supports();
	if(term&ANSI) {
rswindell's avatar
rswindell committed
		for(int i=0;i<count;i++) {
			if(term&PETSCII)
				outcom(PETSCII_RIGHT);
			else
				outcom(' ');
		}
rswindell's avatar
rswindell committed
	long term = term_supports();
	if(term&ANSI) {
rswindell's avatar
rswindell committed
		for(int i=0;i<count;i++) {
			if(term&PETSCII)
				outcom(PETSCII_LEFT);
			else
				outcom('\b');
		}
bool sbbs_t::cursor_xy(int x, int y)
{
	long term = term_supports();
	if(term&ANSI)
		return ansi_gotoxy(x, y);
	if(term&PETSCII) {
		outcom(PETSCII_HOME);
		cursor_down(y - 1);
		cursor_right(x - 1);
		return true;
	}
	return false;
}

bool sbbs_t::cursor_getxy(int* x, int* y)
{