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 "utf8.h"
#include "unicode.h"
#include "petdefs.h"
char* sbbs_t::auto_utf8(const char* str, int& mode)
if (strncmp(str, "\xEF\xBB\xBF", 3) == 0) {
mode |= P_UTF8;
if (mode & P_AUTO_UTF8) {
if (!str_is_ascii(str) && utf8_str_is_valid(str))
mode |= P_UTF8;
/****************************************************************************/
/* 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
P_TRUNCATE
****************************************************************************/
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);
str = auto_utf8(str, mode);
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) {
if (str[l] == 'Z') /* EOF (uppercase 'Z' only) */
break;
if (str[l] == '~') { // Mouse hot-spot (hungry)

rswindell
committed
l++;
if (str[l] >= ' ')
add_hotspot(str[l], /* hungry */ true);
else
add_hotspot('\r', /* hungry */ true);

rswindell
committed
continue;
}
if (str[l] == '`' && str[l + 1] >= ' ') { // Mouse hot-spot (strict)

rswindell
committed
l++;
add_hotspot(str[l], /* hungry */ false);

rswindell
committed
continue;
}
ctrl_a(str[l++]);
continue;
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 */
continue;
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)
continue;
}
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)
/****************************************************************************/
/* Returns the printed columns from 'str' accounting for Ctrl-A codes */
/****************************************************************************/
size_t sbbs_t::bstrlen(const char *str, int mode)
str = auto_utf8(str, mode);
const char* end = str + strlen(str);
while (str < end) {
int len = 1;
if (*str == 0 || *str == 'Z') // EOF
if (*str == '[') // CR
else if (*str == '<' && count) // ND-Backspace
} else if (((*str) & 0x80) && (mode & P_UTF8)) {
enum unicode_codepoint codepoint = UNICODE_UNDEFINED;
len = utf8_getc(str, end - str, &codepoint);
} else
count++;
str += len;
}
return count;
}
/* Perform PETSCII terminal output translation (from ASCII/CP437) */
unsigned char cp437_to_petscii(unsigned char ch)
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 '\x19':
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;
case 179: return PETSCII_VERTLINE;
case 196: return PETSCII_HORZLINE;
case 197: return PETSCII_CROSS;
case 217: return '\xBD';
case 218: return '\xB0';
case 191: return '\xAE';
case 192: return '\xAD';
case 195: return '\xAB';
case 185: return '\xB3';
case 194: return '\xB2';
case 193: return '\xB1';
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_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:
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
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;
}
// Return length of sequence
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);
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);
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);
outchar(ch);
else if (unicode_width(codepoint, unicode_zerowidth) > 0) {
outchar(CP437_INVERTED_QUESTION_MARK);
char seq[32] = "";
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)
if (len == 0)
len = strlen(str);
int term = term_supports();
char utf8[UTF8_MAX_LEN + 1] = "";
for (l = 0; l < len && online; l++) {
uchar ch = str[l];
utf8[0] = 0;
ch = cp437_to_petscii(ch);
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];
utf8_putc(utf8, sizeof(utf8) - 1, codepoint);
}
putcom(utf8);
else {
break;
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;
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 */
/****************************************************************************/
/* Performs printf() using bbs bputs function (with mode) */
/****************************************************************************/
int sbbs_t::bprintf(int mode, const char *fmt, ...)
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 */
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 */

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

rswindell
committed
va_start(argptr, fmt);
vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
sbuf[sizeof(sbuf) - 1] = 0; /* force termination */

rswindell
committed
va_end(argptr);

rswindell
committed
}
/****************************************************************************/
/****************************************************************************/
void sbbs_t::backspace(int count)
return;
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');
}
column--;
lbuflen--;
/****************************************************************************/
/* 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);
}

Rob Swindell
committed
char* sbbs_t::term_rows(user_t* user, char* str, size_t size)
{
if (user->rows >= TERM_ROWS_MIN && user->rows <= TERM_ROWS_MAX)

Rob Swindell
committed
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)

Rob Swindell
committed
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)
{

Rob Swindell
committed
safe_snprintf(str, size, "%sCBM/PETSCII"
, (user->misc & AUTOTERM) ? text[TerminalAutoDetect] : nulstr);

Rob Swindell
committed
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);

Rob Swindell
committed
truncsp(str);
return str;
}
/****************************************************************************/

Rob Swindell
committed
/* Returns short description of the terminal type */
/****************************************************************************/
const char* sbbs_t::term_type(int term)
term = term_supports();
return "PETSCII";
return "RIP";
return "ANSI";
return "DUMB";
}
/****************************************************************************/
/* Returns description of the terminal supported character set (charset) */
/****************************************************************************/
const char* sbbs_t::term_charset(int term)
term = term_supports();
return "CBM-ASCII";
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 (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);
return result;
}
/****************************************************************************/
/* Outputs character */
/* Performs terminal translations (e.g. EXASCII-to-ASCII, FF->ESC[2J) */
/* Performs Telnet IAC escaping */
/* Performs column counting, line counting, and auto-pausing */
/* Performs saveline buffering (for restoreline) */
/****************************************************************************/
int sbbs_t::outchar(char ch)
if (console & CON_ECHO_OFF)
return 0;
if (ch == ESC && outchar_esc < ansiState_string)
else if (outchar_esc == ansiState_esc) {
if (ch == '[')
else if (ch == '_' || ch == 'P' || ch == '^' || ch == ']')
else if (ch >= '@' && ch <= '_')
else if (outchar_esc == ansiState_csi) {
if (ch >= '@' && ch <= '~')
else if (outchar_esc == ansiState_string) { // APS, DCS, PM, or OSC
if (ch == ESC)
outchar_esc = ansiState_esc;
if (!((ch >= '\b' && ch <= '\r') || (ch >= ' ' && ch <= '~')))
outchar_esc = ansiState_none;
else if (outchar_esc == ansiState_sos) { // SOS
if (ch == ESC)
else if (outchar_esc == ansiState_sos_esc) { // ESC inside SOS
if (ch == '\\')
else if (ch == 'X')
if (outchar_esc == ansiState_none && rainbow_index >= 0) {
if (rainbow[rainbow_index + 1] == 0) {
if (rainbow_repeat)
rainbow_index = 0;
} else
++rainbow_index;
}
int term = term_supports();
char utf8[UTF8_MAX_LEN + 1] = "";
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];
utf8_putc(utf8, sizeof(utf8) - 1, codepoint);
}
if (ch == FF && lncntr > 0 && row > 0) {
lncntr = 0;
if (!(sys_status & SS_PAUSEOFF)) {
while (lncntr && online && !(sys_status & SS_ABORT))
pause();
if (!(console & CON_R_ECHO))
if ((console & CON_R_ECHOX) && (uchar)ch >= ' ' && outchar_esc == ansiState_none) {

Rob Swindell
committed
ch = *text[PasswordChar];
else if (ch == '\t') {
while (column % tabstop) {
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 (ch == '\n' && line_delay)
SLEEP(line_delay);
if (pet == PETSCII_SOLID)
outcom(PETSCII_REVERSE_ON);
outcom(pet);
if (pet == PETSCII_SOLID)
if (ch == '\r' && (curatr & 0xf0) != 0) // reverse video is disabled upon CR
} else {
putcom(utf8);
outcom(ch);
}
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)
latr = curatr;
lbuf[lbuflen++] = ch;
}
if (lncntr || lastlinelen)
case FF: // 12
lncntr = 0;
lbuflen = 0;
row = 0;
column = 0;
case '\r': // 13
lastlinelen = column;
inc_column(1);
if (!lbuflen)
latr = curatr;
if (lbuflen < LINE_BUFSIZE)
lbuf[lbuflen++] = ch;
if (outchar_esc == ansiState_final)
if (lncntr == rows - 1 && ((useron.misc & (UPAUSE ^ (console & CON_PAUSEOFF))) || sys_status & SS_PAUSEON)
&& !(sys_status & (SS_PAUSEOFF | SS_ABORT))) {
lncntr = 0;
pause();
return 0;
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, unicode_zerowidth));
return 0;
}
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;
inc_row(1);
}
}
void sbbs_t::inc_row(int count)
{
row += count;
scroll_hotspots((row - rows) + 1);
row = rows - 1;
void sbbs_t::center(const char *instr, bool msg, unsigned int columns)
columns = cols;
carriage_return();
putmsg(str, P_NONE);
else
bputs(str);
while (*str != '\0') {
if ((term & UTF8) && *str >= '!' && *str <= '~')
outchar((enum unicode_codepoint)(UNICODE_FULLWIDTH_EXCLAMATION_MARK + (*str - '!')));
else {
outchar(*str);
outchar(' ');
}
str++;
}
}
// Send a bare carriage return, hopefully moving the cursor to the far left, current row
void sbbs_t::carriage_return(int count)
return;
for (int i = 0; i < count; i++) {
if (term_supports(PETSCII))
cursor_left(column);
else
outcom('\r');
column = 0;
}
}
// Send a bare line_feed, hopefully moving the cursor down one row, current column
void sbbs_t::line_feed(int count)
return;
for (int i = 0; i < count; i++) {
if (term_supports(PETSCII))
outcom(PETSCII_DOWN);
outcom('\n');
}
inc_row(count);
void sbbs_t::newline(int count)
return;
for (int i = 0; i < count; i++) {
outchar('\r');
outchar('\n');
}
void sbbs_t::clearscreen(int term)
if (term & ANSI)
putcom("\x1b[2J\x1b[H"); /* clear screen, home cursor */
else if (term & PETSCII)
row = 0;
column = 0;
lncntr = 0;
void sbbs_t::clearline(void)
{
cleartoeol();
}
void sbbs_t::cursor_home(void)
{

rswindell
committed
putcom("\x1b[H");
else if (term & PETSCII)
else
outchar(FF); /* this will clear some terminals, do nothing with others */
row = 0;
column = 0;
}
void sbbs_t::cursor_up(int count)
{
return;
if (term & ANSI) {
if (count > 1)
comprintf("\x1b[%dA", count);

rswindell
committed
putcom("\x1b[A");
if (term & PETSCII) {
for (int i = 0; i < count; i++)
}
void sbbs_t::cursor_down(int count)
{
return;
if (term_supports(ANSI)) {
if (count > 1)
comprintf("\x1b[%dB", count);

rswindell
committed
putcom("\x1b[B");
inc_row(count);
for (int i = 0; i < count; i++)
}
void sbbs_t::cursor_right(int count)
{
return;
if (term & ANSI) {
if (count > 1)
comprintf("\x1b[%dC", count);
else

rswindell
committed
putcom("\x1b[C");
} else {
for (int i = 0; i < count; i++) {
if (term & PETSCII)