con_out.cpp 33.8 KB
Newer Older
1
/* Synchronet console output routines */
2
// vi: tabstop=4
3
4
5
6
7
8
9

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
10
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 *																			*
 * 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										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
38
#include "utf8.h"
39
#include "unicode.h"
40
#include "petdefs.h"
rswindell's avatar
rswindell committed
41
#include "cp437defs.h"
42

rswindell's avatar
rswindell committed
43
44
45
46
47
48
49
50
51
52
53
54
55
char* sbbs_t::auto_utf8(const char* str, long* mode)
{
	if(strncmp(str, "\xEF\xBB\xBF", 3) == 0) {
		*mode |= P_UTF8;
		return (char*)(str + 3);
	}
	if((*mode)&P_AUTO_UTF8) {
		if(!str_is_ascii(str) && utf8_str_is_valid(str))
			*mode |= P_UTF8;
	}
	return (char*)str;
}

56
57
/****************************************************************************/
/* Outputs a NULL terminated string locally and remotely (if applicable)    */
58
/* Handles ctrl-a codes, Telnet-escaping, column & line count, auto-pausing */
59
60
61
62
63
/* Supported P_* mode flags:
   P_PETSCII
   P_UTF8
   P_AUTO_UTF8
   P_NOATCODES
64
   P_TRUNCATE
65
 ****************************************************************************/
rswindell's avatar
rswindell committed
66
int sbbs_t::bputs(const char *str, long mode)
67
68
69
{
	int i;
    ulong l=0;
rswindell's avatar
rswindell committed
70
	long term = term_supports();
71

72
	if(online==ON_LOCAL && console&CON_L_ECHO) 	/* script running as event */
73
		return(lputs(LOG_INFO, str));
74

rswindell's avatar
rswindell committed
75
76
77
	str = auto_utf8(str, &mode);
	size_t len = strlen(str);
	while(l < len && online) {
78
79
80
81
82
83
84
85
86
87
88
89
90
		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;
				}
		}
91
92
		if(str[l]==CTRL_A && str[l+1]!=0) {
			l++;
93
94
			if(str[l] == 'Z')	/* EOF (uppercase 'Z' only) */
				break;
95
			ctrl_a(str[l++]);
96
			continue;
97
		}
98
		if(!(mode&P_NOATCODES) && str[l]=='@') {
99
100
101
			if(str==mnestr			/* Mnemonic string or */
				|| (str>=text[0]	/* Straight out of TEXT.DAT */
					&& str<=text[TOTAL_TEXT-1])) {
102
				i=show_atcode(str+l);	/* return 0 if not valid @ code */
103
104
				l+=i;					/* i is length of code string */
				if(i)					/* if valid string, go to top */
105
					continue;
106
			}
107
108
109
110
			for(i=0;i<TOTAL_TEXT;i++)
				if(str==text[i])
					break;
			if(i<TOTAL_TEXT) {		/* Replacement text */
111
				i=show_atcode(str+l);
112
113
				l+=i;
				if(i)
114
115
					continue;
			}
116
		}
rswindell's avatar
rswindell committed
117
118
119
120
121
122
123
124
125
		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
126
				l += print_utf8_as_cp437(str + l, len - l);
rswindell's avatar
rswindell committed
127
128
		} else
			outchar(str[l++]);
129
	}
130
131
132
	return(l);
}

rswindell's avatar
rswindell committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/****************************************************************************/
/* Returns the printed columns from 'str' accounting for Ctrl-A codes		*/
/****************************************************************************/
size_t sbbs_t::bstrlen(const char *str, long mode)
{
	str = auto_utf8(str, &mode);
	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;
}


165
166
/* Perform PETSCII terminal output translation (from ASCII/CP437) */
unsigned char cp437_to_petscii(unsigned char ch)
rswindell's avatar
rswindell committed
167
168
169
170
171
{
	if(isalpha(ch))
		return ch ^ 0x20;	/* swap upper/lower case */
	switch(ch) {
		case '\1':		return '@';
172
173
174
175
176
177
178
		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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
		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;
}

245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
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
316
317
318
319
320
321
322
323
/* 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(isalpha(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:
		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;
}

324
// Return length of sequence
rswindell's avatar
rswindell committed
325
size_t sbbs_t::print_utf8_as_cp437(const char* str, size_t len)
326
327
328
329
330
{
	if(((*str)&0x80) == 0) {
		outchar(*str);
		return sizeof(char);
	}
331
	enum unicode_codepoint codepoint = UNICODE_UNDEFINED;
332
	len = utf8_getc(str, len, &codepoint);
333
	if((int)len < 2) {
334
335
		outchar(*str);	// Assume it's a CP437 character
		lprintf(LOG_DEBUG, "Invalid UTF-8 sequence: %02X (error = %d)", (uchar)*str, (int)len);
336
337
338
339
340
341
342
343
344
		return 1;
	}
	for(int i = 1; i < 0x100; i++) {
		if(cp437_unicode_tbl[i]
			&& cp437_unicode_tbl[i] == codepoint) {
			outchar(i);
			return len;
		}
	}
345
	char ch = unicode_to_cp437(codepoint);
346
347
	if(ch)
		outchar(ch);
348
	else if(unicode_width(codepoint) > 0) {
349
		outchar(CP437_INVERTED_QUESTION_MARK);
350
351
352
353
354
355
356
		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;
}
357

358
/****************************************************************************/
359
360
/* Raw put string (remotely)												*/
/* Performs Telnet IAC escaping												*/
361
/* Performs charset translations											*/
362
/* Performs saveline buffering (for restoreline)							*/
363
/* DOES NOT expand ctrl-A codes, track columns, lines, auto-pause, etc.     */
364
/****************************************************************************/
365
int sbbs_t::rputs(const char *str, size_t len)
366
{
367
368
    size_t	l;

369
370
	if(console&CON_ECHO_OFF)
		return 0;
371
372
	if(len==0)
		len=strlen(str);
rswindell's avatar
rswindell committed
373
	long term = term_supports();
374
	char utf8[UTF8_MAX_LEN + 1] = "";
375
	for(l=0;l<len && online;l++) {
376
		uchar ch = str[l];
377
		utf8[0] = 0;
rswindell's avatar
rswindell committed
378
		if(term&PETSCII)
379
			ch = cp437_to_petscii(ch);
380
381
382
		else if((term&NO_EXASCII) && (ch&0x80))
			ch = exascii_to_ascii_char(ch);  /* seven bit table */
		else if(term&UTF8) {
383
			enum unicode_codepoint codepoint = cp437_unicode_tbl[(uchar)ch];
384
385
386
387
388
			if(codepoint != 0)
				utf8_putc(utf8, sizeof(utf8) - 1, codepoint);
		}
		if(utf8[0])
			putcom(utf8);
389
		else {
390
391
			if(outcom(ch)!=0)
				break;
392
			if((char)ch == (char)TELNET_IAC && !(telnet_mode&TELNET_MODE_OFF))
393
394
				outcom(TELNET_IAC);	/* Must escape Telnet IAC char (255) */
		}
395
396
397
398
399
		if(ch == '\n')
			lbuflen=0;
		else if(lbuflen<LINE_BUFSIZE) {
			if(lbuflen == 0)
				latr = curatr;
rswindell's avatar
rswindell committed
400
			lbuf[lbuflen++] = ch;
401
		}
402
	}
403
404
405
406
407
408
	return(l);
}

/****************************************************************************/
/* Performs printf() using bbs bputs function								*/
/****************************************************************************/
409
int sbbs_t::bprintf(const char *fmt, ...)
410
411
{
	va_list argptr;
412
	char sbuf[4096];
413

deuce's avatar
deuce committed
414
	if(strchr(fmt,'%')==NULL)
415
416
		return(bputs(fmt));
	va_start(argptr,fmt);
417
418
	vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;	/* force termination */
419
420
421
422
	va_end(argptr);
	return(bputs(sbuf));
}

rswindell's avatar
rswindell committed
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
/****************************************************************************/
/* 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));
}

440
441
442
/****************************************************************************/
/* Performs printf() using bbs rputs function								*/
/****************************************************************************/
443
int sbbs_t::rprintf(const char *fmt, ...)
444
445
{
	va_list argptr;
446
	char sbuf[4096];
447
448

	va_start(argptr,fmt);
449
450
	vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;	/* force termination */
451
452
453
454
	va_end(argptr);
	return(rputs(sbuf));
}

455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
/****************************************************************************/
/* 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));
}

470
/****************************************************************************/
rswindell's avatar
rswindell committed
471
/* Outputs destructive backspace 											*/
472
/****************************************************************************/
473
void sbbs_t::backspace(int count)
474
{
475
476
	if(count < 1)
		return;
477
	if(!(console&CON_ECHO_OFF)) {
478
479
480
481
482
483
484
485
486
487
		for(int i = 0; i < count; i++) {
			if(term_supports(PETSCII))
				outcom(PETSCII_DELETE);
			else {
				outcom('\b');
				outcom(' ');
				outcom('\b');
			}
			if(column)
				column--;
rswindell's avatar
rswindell committed
488
		}
489
	}
490
491
}

492
493
494
495
496
497
498
/****************************************************************************/
/* 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)
{
499
	long flags = ((sys_status&SS_USERON) && !(useron.misc&AUTOTERM)) ? useron.misc : autoterm;
500

501
	if((sys_status&SS_USERON) && (useron.misc&AUTOTERM))
rswindell's avatar
rswindell committed
502
		flags |= useron.misc & (NO_EXASCII | SWAP_DELETE | COLOR | ICE_COLOR);
503

504
505
506
	return(cmp_flags ? ((flags&cmp_flags)==cmp_flags) : (flags&TERM_FLAGS));
}

507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
/****************************************************************************/
/* 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";
}

539
/****************************************************************************/
540
541
542
/* Outputs character														*/
/* Performs terminal translations (e.g. EXASCII-to-ASCII, FF->ESC[2J)		*/
/* Performs Telnet IAC escaping												*/
rswindell's avatar
rswindell committed
543
/* Performs tab expansion													*/
544
545
/* Performs column counting, line counting, and auto-pausing				*/
/* Performs saveline buffering (for restoreline)							*/
546
/****************************************************************************/
547
int sbbs_t::outchar(char ch)
548
{
549
550
551
552
553
554
555
556
557
558
559
	/*
	 * outchar_esc values:
	 * 0: No sequence
	 * 1: ESC
	 * 2: CSI
	 * 3: Final byte
     * 4: APS, DCS, PM, or OSC
     * 5: SOS
     * 6: ESC inside of SOS
     */

560
	if(console&CON_ECHO_OFF)
561
		return 0;
562
	if(ch==ESC && outchar_esc < 4)
563
564
		outchar_esc=1;
	else if(outchar_esc==1) {
deuce's avatar
deuce committed
565
566
		if(ch=='[')
			outchar_esc++;
567
568
569
570
		else if(ch=='_' || ch=='P' || ch == '^' || ch == ']')
			outchar_esc=4;
		else if(ch=='X')
			outchar_esc=5;
571
572
		else if(ch >= 0x40 && ch <= 0x5f)
			outchar_esc=3;
deuce's avatar
deuce committed
573
574
575
576
		else
			outchar_esc=0;
	}
	else if(outchar_esc==2) {
577
		if(ch>='@' && ch<='~')
578
			outchar_esc++;
579
	}
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
	else if(outchar_esc==4) {	// APS, DCS, PM, or OSC
		if (ch == ESC)
			outchar_esc = 1;
		if (!((ch >= 0x08 && ch <= 0x0d) || (ch >= 0x20 && ch <= 0x7e)))
			outchar_esc = 0;
	}
	else if(outchar_esc==5) {	// SOS
		if (ch == ESC)
			outchar_esc++;
	}
	else if(outchar_esc==6) {	// ESC inside SOS
		if (ch == '\\')
			outchar_esc = 1;
		else if (ch == 'X')
			outchar_esc = 0;
		else
			outchar_esc = 5;
	}
598
599
	else
		outchar_esc=0;
rswindell's avatar
rswindell committed
600
	long term = term_supports();
601
	char utf8[UTF8_MAX_LEN + 1] = "";
602
603
604
	if(!(term&PETSCII)) {
		if((term&NO_EXASCII) && (ch&0x80))
			ch = exascii_to_ascii_char(ch);  /* seven bit table */
605
		else if(term&UTF8) {
606
			enum unicode_codepoint codepoint = cp437_unicode_tbl[(uchar)ch];
607
608
609
			if(codepoint != 0)
				utf8_putc(utf8, sizeof(utf8) - 1, codepoint);
		}
610
	}
rswindell's avatar
rswindell committed
611

612
	if(ch==FF && lncntr > 0 && !tos) {
613
		lncntr=0;
rswindell's avatar
rswindell committed
614
		newline();
615
616
617
		if(!(sys_status&SS_PAUSEOFF)) {
			pause();
			while(lncntr && online && !(sys_status&SS_ABORT))
618
				pause();
619
620
		}
	}
621

622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
	if(!(console&CON_R_ECHO))
		return 0;

	if((console&CON_R_ECHOX) && (uchar)ch>=' ' && !outchar_esc) {
		ch=text[YNQP][3];
		if(text[YNQP][2]==0 || ch==0) ch='X';
	}
	if(ch==FF) {
		if(term&ANSI)
			putcom("\x1b[2J\x1b[H");	/* clear screen, home cursor */
		else if(term&PETSCII)
			outcom(PETSCII_CLEAR);
		else
			outcom(FF);
	}
	else if(ch == '\t') {
		outcom(' ');
		column++;
		while(column%tabstop) {
rswindell's avatar
rswindell committed
641
642
			outcom(' ');
			column++;
643
		}
644
	}
645
646
647
	else {
		if(ch==(char)TELNET_IAC && !(telnet_mode&TELNET_MODE_OFF))
			outcom(TELNET_IAC);	/* Must escape Telnet IAC char (255) */
648
649
		if(ch == '\r' && (console&CON_CR_CLREOL))
			cleartoeol();
650
651
652
653
654
655
656
		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);
rswindell's avatar
rswindell committed
657
658
			if(ch == '\r' && (curatr&0xf0) != 0) // reverse video is disabled upon CR
				curatr >>= 4;
659
		} else {
660
			if(utf8[0] != 0)
661
662
663
664
				putcom(utf8);
			else
				outcom(ch);
		}
665
	}
666
	if(!outchar_esc) {
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
		/* 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 > 0)
					lbuflen--;
				break;
			case '\n':	// 10
				if(lncntr || lastlinelen)
					lncntr++;
				lbuflen=0;
				tos=0;
				column=0;
				break;
			case FF:	// 12
				lncntr=0;
				lbuflen=0;
				tos=1;
				column=0;
			case '\r':	// 13
692
				lastlinelen = column;
693
694
695
				column=0;
				break;
			default:
696
				inc_column(1);
697
698
699
700
701
				if(!lbuflen)
					latr=curatr;
				if(lbuflen<LINE_BUFSIZE)
					lbuf[lbuflen++]=ch;
				break;
702
703
		}
	}
704
705
	if(outchar_esc==3)
		outchar_esc=0;
706

707
	if(lncntr==rows-1 && ((useron.misc&UPAUSE) || sys_status&SS_PAUSEON)
708
		&& !(sys_status&(SS_PAUSEOFF|SS_ABORT))) {
709
		lncntr=0;
710
		pause();
rswindell's avatar
rswindell committed
711
	}
712
	return 0;
713
714
}

715
int sbbs_t::outchar(enum unicode_codepoint codepoint, const char* cp437_fallback)
716
717
718
719
720
721
722
723
724
725
{
	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;
	}
726
	if(cp437_fallback == NULL)
727
		return 0;
728
729
730
731
732
733
734
	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);
735
736
737
738
739
740
741
742
743
744
745
746
747
748
}

void sbbs_t::inc_column(int count)
{
	column += count;
	if(column >= cols) {	// assume terminal has/will auto-line-wrap
		lncntr++;
		lbuflen = 0;
		tos = 0;
		lastlinelen = column;
		column = 0;
	}
}

749
void sbbs_t::center(char *instr, unsigned int columns)
750
751
{
	char str[256];
rswindell's avatar
rswindell committed
752
	size_t len;
753

754
755
756
	if(columns < 1)
		columns = cols;

757
	SAFECOPY(str,instr);
758
	truncsp(str);
rswindell's avatar
rswindell committed
759
760
761
	len = bstrlen(str);
	if(len < columns)
		cursor_right((columns - len) / 2);
762
	bputs(str);
rswindell's avatar
rswindell committed
763
764
765
	newline();
}

rswindell's avatar
rswindell committed
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
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
781
// Send a bare carriage return, hopefully moving the cursor to the far left, current row
782
void sbbs_t::carriage_return(int count)
rswindell's avatar
rswindell committed
783
{
784
785
786
787
788
789
790
791
792
	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
793
794
795
}

// Send a bare line_feed, hopefully moving the cursor down one row, current column
796
void sbbs_t::line_feed(int count)
rswindell's avatar
rswindell committed
797
{
798
799
800
801
802
803
804
805
	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
806
807
}

808
void sbbs_t::newline(int count)
rswindell's avatar
rswindell committed
809
{
810
811
812
813
814
815
	if(count < 1)
		return;
	for(int i = 0; i < count; i++) { 
		outchar('\r');
		outchar('\n');
	}
816
817
}

818
819
void sbbs_t::clearline(void)
{
rswindell's avatar
rswindell committed
820
	carriage_return();
821
	cleartoeol();
822
823
824
825
}

void sbbs_t::cursor_home(void)
{
rswindell's avatar
rswindell committed
826
827
	long term = term_supports();
	if(term&ANSI)
828
		putcom("\x1b[H");
rswindell's avatar
rswindell committed
829
830
	else if(term&PETSCII)
		outcom(PETSCII_HOME);
831
	else
832
		outchar(FF);	/* this will clear some terminals, do nothing with others */
833
834
	tos=1;
	column=0;
835
836
837
838
}

void sbbs_t::cursor_up(int count)
{
839
840
	if(count<1)
		return;
rswindell's avatar
rswindell committed
841
842
843
	long term = term_supports();
	if(term&ANSI) {
		if(count>1)
844
			comprintf("\x1b[%dA",count);
rswindell's avatar
rswindell committed
845
		else
846
			putcom("\x1b[A");
rswindell's avatar
rswindell committed
847
848
849
850
851
852
	} else {
		if(term&PETSCII) {
			for(int i=0;i<count;i++)
				outcom(PETSCII_UP);
		}
	}
853
854
855
856
}

void sbbs_t::cursor_down(int count)
{
857
858
	if(count<1)
		return;
rswindell's avatar
rswindell committed
859
860
	if(term_supports(ANSI)) {
		if(count>1)
861
			comprintf("\x1b[%dB",count);
rswindell's avatar
rswindell committed
862
		else
863
			putcom("\x1b[B");
rswindell's avatar
rswindell committed
864
865
866
867
	} else {
		for(int i=0;i<count;i++)
			line_feed();
	}
868
869
870
871
}

void sbbs_t::cursor_right(int count)
{
872
873
	if(count<1)
		return;
rswindell's avatar
rswindell committed
874
875
	long term = term_supports();
	if(term&ANSI) {
876
		if(count>1)
877
			comprintf("\x1b[%dC",count);
878
		else
879
			putcom("\x1b[C");
880
	} else {
rswindell's avatar
rswindell committed
881
882
883
884
885
886
		for(int i=0;i<count;i++) {
			if(term&PETSCII)
				outcom(PETSCII_RIGHT);
			else
				outcom(' ');
		}
887
	}
888
	column+=count;
889
890
891
892
}

void sbbs_t::cursor_left(int count)
{
893
894
	if(count<1)
		return;
rswindell's avatar
rswindell committed
895
896
	long term = term_supports();
	if(term&ANSI) {
897
		if(count>1)
898
			comprintf("\x1b[%dD",count);
899
		else
900
			putcom("\x1b[D");
901
	} else {
rswindell's avatar
rswindell committed
902
903
904
905
906
907
		for(int i=0;i<count;i++) {
			if(term&PETSCII)
				outcom(PETSCII_LEFT);
			else
				outcom('\b');
		}
908
	}
909
910
911
912
	if(column > count)
		column-=count;
	else
		column=0;
913
914
}

915
916
917
918
919
920
921
922
923
924
925
926
927
928
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;
}

929
930
void sbbs_t::cleartoeol(void)
{
931
932
	int i,j;

rswindell's avatar
rswindell committed
933
934
	long term = term_supports();
	if(term&ANSI)
935
		putcom("\x1b[K");
936
	else {
937
		i=j=column;
938
		while(++i<cols)
939
			outcom(' ');
rswindell's avatar
rswindell committed
940
941
942
943
944
945
		while(++j<cols) {
			if(term&PETSCII)
				outcom(PETSCII_LEFT);
			else
				outcom('\b');
		}
946
947
	}
}
948

rswindell's avatar
rswindell committed
949
950
951
void sbbs_t::cleartoeos(void)
{
	if(term_supports(ANSI))
952
		putcom("\x1b[J");
rswindell's avatar
rswindell committed
953
954
}

955
956
957
958
void sbbs_t::set_output_rate(enum output_rate speed)
{
	if(term_supports(ANSI)) {
		unsigned int val = speed;
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
		switch(val) {
			case 0:		val = 0; break;
			case 600:	val = 2; break;
			case 1200:	val = 3; break;
			case 2400:	val = 4; break;
			case 4800:	val = 5; break;
			case 9600:	val = 6; break;
			case 19200:	val = 7; break;
			case 38400: val = 8; break;
			case 57600: val = 9; break;
			case 76800: val = 10; break;
			default:
				if(val <= 300)
					val = 1;
				else if(val > 76800)
					val = 11;
				break;
976
		}
977
		comprintf("\x1b[;%u*r", val);
978
979
980
981
		cur_output_rate = speed;
	}
}

982
983
984
985
986
/****************************************************************************/
/* performs the correct attribute modifications for the Ctrl-A code			*/
/****************************************************************************/
void sbbs_t::ctrl_a(char x)
{
rswindell's avatar
rswindell committed
987
988
	char	tmp1[128];
	uint	atr = curatr;
989
	struct	tm tm;
990

rswindell's avatar
rswindell committed
991
	if(x && (uchar)x<=CTRL_Z) {    /* Ctrl-A through Ctrl-Z for users with MF only */
992
993
		if(!(useron.flags1&FLAG(x+64)))
			console^=(CON_ECHO_OFF);
994
		return;
995
996
	}
	if((uchar)x>0x7f) {
997
		cursor_right((uchar)x-0x7f);
998
		return;
999
	}
rswindell's avatar
rswindell committed
1000
	if(isdigit(x)) {	/* background color */
For faster browsing, not all history is shown. View entire blame