Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

con_out.cpp 16.6 KB
Newer Older
1 2 3 4 5 6 7 8
/* Synchronet console output routines */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
9
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
10 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 38 39 40 41 42 43 44 45
 *																			*
 * 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.	*
 ****************************************************************************/


/**********************************************************************/
/* Functions that pertain to console i/o - color, strings, chars etc. */
/* Called from functions everywhere                                   */
/**********************************************************************/

#include "sbbs.h"

/****************************************************************************/
/* Outputs a NULL terminated string locally and remotely (if applicable)    */
46
/* Handles ctrl-a codes, Telnet-escaping, column & line count, auto-pausing */
47
/****************************************************************************/
48
int sbbs_t::bputs(const char *str)
49 50 51 52
{
	int i;
    ulong l=0;

53
	if(online==ON_LOCAL && console&CON_L_ECHO) 	/* script running as event */
54
		return(eprintf(LOG_INFO,"%s",str));
55

56
	while(str[l] && online) {
57 58 59 60 61
		if(str[l]==CTRL_A && str[l+1]!=0) {
			l++;
			if(toupper(str[l])=='Z')	/* EOF */
				break;
			ctrl_a(str[l++]);
62 63
			continue; 
		}
64 65 66 67
		if(str[l]=='@') {           /* '@' */
			if(str==mnestr			/* Mnemonic string or */
				|| (str>=text[0]	/* Straight out of TEXT.DAT */
					&& str<=text[TOTAL_TEXT-1])) {
68
				i=show_atcode(str+l);	/* return 0 if not valid @ code */
69 70
				l+=i;					/* i is length of code string */
				if(i)					/* if valid string, go to top */
71 72
					continue; 
			}
73 74 75 76
			for(i=0;i<TOTAL_TEXT;i++)
				if(str==text[i])
					break;
			if(i<TOTAL_TEXT) {		/* Replacement text */
77
				i=show_atcode(str+l);
78 79
				l+=i;
				if(i)
80 81 82 83 84
					continue; 
			} 
		}
		outchar(str[l++]); 
	}
85 86 87 88
	return(l);
}

/****************************************************************************/
89 90 91 92
/* Raw put string (remotely)												*/
/* Performs Telnet IAC escaping												*/
/* Performs saveline buffering (for restoreline)							*/
/* DOES NOT expand ctrl-A codes, track colunms, lines, auto-pause, etc.     */
93
/****************************************************************************/
94
int sbbs_t::rputs(const char *str, size_t len)
95
{
96 97
    size_t	l;

98 99
	if(console&CON_ECHO_OFF)
		return 0;
100 101 102 103 104 105 106
	if(len==0)
		len=strlen(str);
	for(l=0;l<len && online;l++) {
		if(str[l]==(char)TELNET_IAC && !(telnet_mode&TELNET_MODE_OFF))
			outcom(TELNET_IAC);	/* Must escape Telnet IAC char (255) */
		if(outcom(str[l])!=0)
			break;
107 108 109
		if(lbuflen<LINE_BUFSIZE)
			lbuf[lbuflen++]=str[l]; 
	}
110 111 112 113 114 115
	return(l);
}

/****************************************************************************/
/* Performs printf() using bbs bputs function								*/
/****************************************************************************/
116
int sbbs_t::bprintf(const char *fmt, ...)
117 118
{
	va_list argptr;
119
	char sbuf[4096];
120

deuce's avatar
deuce committed
121
	if(strchr(fmt,'%')==NULL)
122 123
		return(bputs(fmt));
	va_start(argptr,fmt);
124 125
	vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;	/* force termination */
126 127 128 129 130 131 132
	va_end(argptr);
	return(bputs(sbuf));
}

/****************************************************************************/
/* Performs printf() using bbs rputs function								*/
/****************************************************************************/
133
int sbbs_t::rprintf(const char *fmt, ...)
134 135
{
	va_list argptr;
136
	char sbuf[4096];
137 138

	va_start(argptr,fmt);
139 140
	vsnprintf(sbuf,sizeof(sbuf),fmt,argptr);
	sbuf[sizeof(sbuf)-1]=0;	/* force termination */
141 142 143 144
	va_end(argptr);
	return(rputs(sbuf));
}

145 146 147 148 149
/****************************************************************************/
/* Outputs destructive backspace locally and remotely (if applicable),		*/
/****************************************************************************/
void sbbs_t::backspace(void)
{
150 151 152 153 154 155 156
	if(!(console&CON_ECHO_OFF)) {
		outcom('\b');
		outcom(' ');
		outcom('\b');
		if(column)
			column--;
	}
157 158
}

159 160 161 162 163 164 165 166 167 168 169 170
/****************************************************************************/
/* 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;

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

171
/****************************************************************************/
172 173 174 175 176
/* 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)							*/
177 178 179 180 181 182 183 184 185 186
/****************************************************************************/
void sbbs_t::outchar(char ch)
{
	int		i;

	if(console&CON_ECHO_OFF)
		return;
	if(ch==ESC)
		outchar_esc=1;
	else if(outchar_esc==1) {
deuce's avatar
deuce committed
187 188 189 190 191 192
		if(ch=='[')
			outchar_esc++;
		else
			outchar_esc=0;
	}
	else if(outchar_esc==2) {
193
		if(ch>='@' && ch<='~')
194
			outchar_esc++;
195
	}
196 197
	else
		outchar_esc=0;
198
	if(term_supports(NO_EXASCII) && ch&0x80)
199
		ch=exascii_to_ascii_char(ch);  /* seven bit table */
200 201 202
	if(ch==FF && lncntr>1 && !tos) {
		lncntr=0;
		CRLF;
203 204 205 206 207 208
		if(!(sys_status&SS_PAUSEOFF)) {
			pause();
			while(lncntr && online && !(sys_status&SS_ABORT))
				pause(); 
		}
	}
209 210

	if(online==ON_REMOTE && console&CON_R_ECHO) {
deuce's avatar
deuce committed
211
		if(console&CON_R_ECHOX && (uchar)ch>=' ' && !outchar_esc) {
212 213
			ch=text[YNQP][3];
			if(text[YNQP][2]==0 || ch==0) ch='X';
214
		}
215
		if(ch==FF && term_supports(ANSI)) {
216 217 218
			putcom("\x1b[2J\x1b[H");	/* clear screen, home cursor */
		}
		else {
219
			if(ch==(char)TELNET_IAC && !(telnet_mode&TELNET_MODE_OFF))
220
				outcom(TELNET_IAC);	/* Must escape Telnet IAC char (255) */
221 222 223 224 225 226
			i=0;
			while(outcom(ch)&TXBOF && i<1440) { /* 3 minute pause delay */
				if(!online)
					break;
				i++;
				if(sys_status&SS_SYSPAGE)
227
					sbbs_beep(i,80);
228
				else
229 230
					mswait(80); 
			}
231 232
			if(i==1440) {							/* timeout - beep flush outbuf */
				i=rioctl(TXBC);
233
				lprintf(LOG_NOTICE,"timeout(outchar) %04X %04X\r\n",i,rioctl(IOFO));
234
				outcom(BEL);
235 236 237 238
				rioctl(IOCS|PAUSE); 
			} 
		} 
	}
239 240 241 242 243 244 245 246 247 248
	if(!outchar_esc) {
		if((uchar)ch>=' ')
			column++;
		else if(ch=='\r')
			column=0;
		else if(ch=='\b') {
			if(column)
				column--;
		}
	}
249
	if(ch==LF || column>=cols) {
250 251 252
		lncntr++;
		lbuflen=0;
		tos=0;
253
		column=0;
254 255 256 257
	} else if(ch==FF) {
		lncntr=0;
		lbuflen=0;
		tos=1;
258
		column=0;
259 260 261 262
	} else {
		if(!lbuflen)
			latr=curatr;
		if(lbuflen<LINE_BUFSIZE)
rswindell's avatar
rswindell committed
263 264
			lbuf[lbuflen++]=ch; 
	}
265 266
	if(outchar_esc==3)
		outchar_esc=0;
267

rswindell's avatar
rswindell committed
268 269
	if(lncntr==rows-1 && ((useron.misc&UPAUSE) || sys_status&SS_PAUSEON) 
		&& !(sys_status&SS_PAUSEOFF)) {
270
		lncntr=0;
rswindell's avatar
rswindell committed
271 272
		pause(); 
	}
273 274 275 276 277 278 279
}

void sbbs_t::center(char *instr)
{
	char str[256];
	int i,j;

280
	SAFECOPY(str,instr);
281 282
	truncsp(str);
	j=bstrlen(str);
283
	for(i=0;i<(cols-j)/2;i++)
284
		outchar(' ');
285 286 287 288
	bputs(str);
	CRLF;
}

289 290
void sbbs_t::clearline(void)
{
291 292 293
	outcom(CR);
	column=0;
	cleartoeol();
294 295 296 297
}

void sbbs_t::cursor_home(void)
{
298
	if(term_supports(ANSI))
299 300
		rputs("\x1b[H");
	else
301
		outchar(FF);	/* this will clear some terminals, do nothing with others */
302 303
	tos=1;
	column=0;
304 305 306 307
}

void sbbs_t::cursor_up(int count)
{
308 309
	if(count<1)
		return;
310
	if(!term_supports(ANSI))
311 312 313 314 315 316 317 318 319
		return;
	if(count>1)
		rprintf("\x1b[%dA",count);
	else
		rputs("\x1b[A");
}

void sbbs_t::cursor_down(int count)
{
320 321
	if(count<1)
		return;
322
	if(!term_supports(ANSI))
323 324 325 326 327 328 329 330 331
		return;
	if(count>1)
		rprintf("\x1b[%dB",count);
	else
		rputs("\x1b[B");
}

void sbbs_t::cursor_right(int count)
{
332 333
	if(count<1)
		return;
334
	if(term_supports(ANSI)) {
335 336 337 338 339 340
		if(count>1)
			rprintf("\x1b[%dC",count);
		else
			rputs("\x1b[C");
	} else {
		for(int i=0;i<count;i++)
341
			outcom(' ');
342
	}
343
	column+=count;
344 345 346 347
}

void sbbs_t::cursor_left(int count)
{
348 349
	if(count<1)
		return;
350
	if(term_supports(ANSI)) {
351 352 353 354 355 356
		if(count>1)
			rprintf("\x1b[%dD",count);
		else
			rputs("\x1b[D");
	} else {
		for(int i=0;i<count;i++)
357
			outcom('\b');
358
	}
359 360 361 362
	if(column > count)
		column-=count;
	else
		column=0;
363 364 365 366
}

void sbbs_t::cleartoeol(void)
{
367 368
	int i,j;

369
	if(term_supports(ANSI))
370 371
		rputs("\x1b[K");
	else {
372
		i=j=column;
373
		while(++i<cols)
374
			outcom(' ');
375
		while(++j<cols)
376
			outcom(BS); 
377 378
	}
}
379

rswindell's avatar
rswindell committed
380 381 382 383 384 385
void sbbs_t::cleartoeos(void)
{
	if(term_supports(ANSI))
		rputs("\x1b[J");
}

386 387 388 389 390 391
/****************************************************************************/
/* performs the correct attribute modifications for the Ctrl-A code			*/
/****************************************************************************/
void sbbs_t::ctrl_a(char x)
{
	char	tmp1[128],atr=curatr;
392
	struct	tm tm;
393

rswindell's avatar
rswindell committed
394
	if(x && (uchar)x<=CTRL_Z) {    /* Ctrl-A through Ctrl-Z for users with MF only */
395 396
		if(!(useron.flags1&FLAG(x+64)))
			console^=(CON_ECHO_OFF);
397 398 399
		return; 
	}
	if((uchar)x>0x7f) {
400
		cursor_right((uchar)x-0x7f);
401 402
		return; 
	}
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
	switch(toupper(x)) {
		case '!':   /* level 10 or higher */
			if(useron.level<10)
				console^=CON_ECHO_OFF;
			break;
		case '@':   /* level 20 or higher */
			if(useron.level<20)
				console^=CON_ECHO_OFF;
			break;
		case '#':   /* level 30 or higher */
			if(useron.level<30)
				console^=CON_ECHO_OFF;
			break;
		case '$':   /* level 40 or higher */
			if(useron.level<40)
				console^=CON_ECHO_OFF;
			break;
		case '%':   /* level 50 or higher */
			if(useron.level<50)
				console^=CON_ECHO_OFF;
			break;
		case '^':   /* level 60 or higher */
			if(useron.level<60)
				console^=CON_ECHO_OFF;
			break;
		case '&':   /* level 70 or higher */
			if(useron.level<70)
				console^=CON_ECHO_OFF;
			break;
		case '*':   /* level 80 or higher */
			if(useron.level<80)
				console^=CON_ECHO_OFF;
			break;
		case '(':   /* level 90 or higher */
			if(useron.level<90)
				console^=CON_ECHO_OFF;
			break;
		case ')':   /* turn echo back on */
			console&=~CON_ECHO_OFF;
			break;
443
		case '+':	/* push current attribte */
rswindell's avatar
rswindell committed
444
			if(attr_sp<(int)sizeof(attr_stack))
445 446 447 448 449 450 451 452
				attr_stack[attr_sp++]=curatr;
			break;
		case '-':	/* pop current attribute OR optimized "normal" */
			if(attr_sp>0)
				attr(attr_stack[--attr_sp]);
			else									/* turn off all attributes if */
				if(atr&(HIGH|BLINK|BG_LIGHTGRAY))	/* high intensity, blink or */
					attr(LIGHTGRAY);				/* background bits are set */
453 454
			break;
		case '_':								/* turn off all attributes if */
455
			if(atr&(BLINK|BG_LIGHTGRAY))		/* blink or background is set */
456 457 458 459 460 461 462 463 464 465
				attr(LIGHTGRAY);
			break;
		case 'P':	/* Pause */
			pause();
			break;
		case 'Q':   /* Pause reset */
			lncntr=0;
			break;
		case 'T':   /* Time */
			now=time(NULL);
466
			localtime_r(&now,&tm);
467 468 469 470 471 472 473 474
			if(cfg.sys_misc&SM_MILITARY)
				bprintf("%02u:%02u:%02u"
					,tm.tm_hour, tm.tm_min, tm.tm_sec);
			else
				bprintf("%02d:%02d %s"
					,tm.tm_hour==0 ? 12
					: tm.tm_hour>12 ? tm.tm_hour-12
					: tm.tm_hour, tm.tm_min, tm.tm_hour>11 ? "pm":"am");
475 476 477
			break;
		case 'D':   /* Date */
			now=time(NULL);
478
			bputs(unixtodstr(&cfg,(time32_t)now,tmp1));
479 480 481 482 483 484 485 486 487 488 489 490 491
			break;
		case ',':   /* Delay 1/10 sec */
			mswait(100);
			break;
		case ';':   /* Delay 1/2 sec */
			mswait(500);
			break;
		case '.':   /* Delay 2 secs */
			mswait(2000);
			break;
		case 'S':   /* Synchronize */
			ASYNC;
			break;
rswindell's avatar
rswindell committed
492 493 494
		case 'J':	/* clear to end-of-screen */
			cleartoeos();
			break;
495 496 497
		case 'L':	/* CLS (form feed) */
			CLS;
			break;
rswindell's avatar
rswindell committed
498 499 500
		case '`':	/* Home cursor */
			cursor_home();
			break;
501
		case '>':   /* CLREOL */
502
			cleartoeol();
503 504 505 506 507 508 509 510 511 512 513
			break;
		case '<':   /* Non-destructive backspace */
			outchar(BS);
			break;
		case '[':   /* Carriage return */
			outchar(CR);
			break;
		case ']':   /* Line feed */
			outchar(LF);
			break;
		case 'A':   /* Ctrl-A */
514
			outchar(CTRL_A);
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
			break;
		case 'H': 	/* High intensity */
			atr|=HIGH;
			attr(atr);
			break;
		case 'I':	/* Blink */
			atr|=BLINK;
			attr(atr);
			break;
		case 'N': 	/* Normal */
			attr(LIGHTGRAY);
			break;
		case 'R':
			atr=(atr&0xf8)|RED;
			attr(atr);
			break;
		case 'G':
			atr=(atr&0xf8)|GREEN;
			attr(atr);
			break;
		case 'B':
			atr=(atr&0xf8)|BLUE;
			attr(atr);
			break;
		case 'W':	/* White */
			atr=(atr&0xf8)|LIGHTGRAY;
			attr(atr);
			break;
		case 'C':
			atr=(atr&0xf8)|CYAN;
			attr(atr);
			break;
		case 'M':
			atr=(atr&0xf8)|MAGENTA;
			attr(atr);
			break;
		case 'Y':   /* Yellow */
			atr=(atr&0xf8)|BROWN;
			attr(atr);
			break;
		case 'K':	/* Black */
			atr=(atr&0xf8)|BLACK;
			attr(atr);
			break;
		case '0':	/* Black Background */
560
			atr=(atr&0x8f);
561 562 563
			attr(atr);
			break;
		case '1':	/* Red Background */
564
			atr=(atr&0x8f)|(uchar)BG_RED;
565 566 567
			attr(atr);
			break;
		case '2':	/* Green Background */
568
			atr=(atr&0x8f)|(uchar)BG_GREEN;
569 570 571
			attr(atr);
			break;
		case '3':	/* Yellow Background */
572
			atr=(atr&0x8f)|(uchar)BG_BROWN;
573 574 575
			attr(atr);
			break;
		case '4':	/* Blue Background */
576
			atr=(atr&0x8f)|(uchar)BG_BLUE;
577 578 579
			attr(atr);
			break;
		case '5':	/* Magenta Background */
580
			atr=(atr&0x8f)|(uchar)BG_MAGENTA;
581 582 583
			attr(atr);
			break;
		case '6':	/* Cyan Background */
584
			atr=(atr&0x8f)|(uchar)BG_CYAN;
585 586 587
			attr(atr);
			break;
		case '7':	/* White Background */
588
			atr=(atr&0x8f)|(uchar)BG_LIGHTGRAY;
589
			attr(atr);
590 591
			break; 
	}
592 593 594 595 596 597 598 599 600 601 602 603
}

/***************************************************************************/
/* Changes local and remote text attributes accounting for monochrome      */
/***************************************************************************/
/****************************************************************************/
/* Sends ansi codes to change remote ansi terminal's colors                 */
/* Only sends necessary codes - tracks remote terminal's current attributes */
/* through the 'curatr' variable                                            */
/****************************************************************************/
void sbbs_t::attr(int atr)
{
604
	char	str[16];
605

606
	if(!term_supports(ANSI))
607
		return;
rswindell's avatar
rswindell committed
608
	rputs(ansi(atr,curatr,str));
609 610 611 612 613 614 615 616 617 618 619 620
	curatr=atr;
}

/****************************************************************************/
/* Checks to see if user has hit Pause or Abort. Returns 1 if user aborted. */
/* If the user hit Pause, waits for a key to be hit.                        */
/* Emulates remote XON/XOFF flow control on local console                   */
/* Preserves SS_ABORT flag state, if already set.                           */
/* Called from various listing procedures that wish to check for abort      */
/****************************************************************************/
bool sbbs_t::msgabort()
{
621 622 623
	static ulong counter;

	if(sys_status&SS_SYSPAGE && !(++counter%100)) 
624
		sbbs_beep(sbbs_random(800),1);
625

626 627 628 629 630 631 632 633
	checkline();
	if(sys_status&SS_ABORT)
		return(true);
	if(!online)
		return(true);
	return(false);
}

634
int sbbs_t::backfill(const char* instr, float pct, int full_attr, int empty_attr)
635
{
636 637
	int	atr;
	int save_atr = curatr;
638
	int len;
639
	char* str = strip_ctrl(instr, NULL);
640

641
	len = strlen(str);
642 643 644 645 646 647 648 649 650 651 652 653
	if(!term_supports(ANSI))
		bputs(str);
	else {
		for(int i=0; i<len; i++) {
			if(((float)(i+1) / len)*100.0 <= pct)
				atr = full_attr;
			else
				atr = empty_attr;
			if(curatr != atr) attr(atr);
			outchar(str[i]);
		}
		attr(save_atr);
654
	}
655
	free(str);
656 657
	return len;
}
658 659 660 661 662 663 664 665 666 667

void sbbs_t::progress(const char* text, int count, int total)
{
	char str[128];

	if(text == NULL) text = "";
	float pct = ((float)count/total)*100.0F;
	SAFEPRINTF2(str, "[ %-8s  %4.1f%% ]", text, pct);
	cursor_left(backfill(str, pct, cfg.color[clr_progress_full], cfg.color[clr_progress_empty]));
}