inkey.cpp 19 KB
Newer Older
1 2
/* Synchronet single key input function (no wait) */

3
/* $Id: inkey.cpp,v 1.80 2020/08/04 04:56:37 rswindell Exp $ */
4 5 6 7 8

/****************************************************************************
 * @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
 *																			*
 * 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"
37
#include "petdefs.h"
38

39 40 41 42 43 44 45
int kbincom(sbbs_t* sbbs, unsigned long timeout)
{
	int	ch;

	if(sbbs->keybuftop!=sbbs->keybufbot) { 
		ch=sbbs->keybuf[sbbs->keybufbot++]; 
		if(sbbs->keybufbot==KEY_BUFSIZE) 
46 47 48 49 50 51 52
			sbbs->keybufbot=0;
#if 0
		char* p = c_escape_char(ch);
		if(p == NULL)
			p = (char*)&ch;
		lprintf(LOG_DEBUG, "kbincom read %02X '%s'", ch, p);
#endif
53
	} else {
54
		ch=sbbs->incom(timeout);
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
		long term = sbbs->term_supports();
		if(term&PETSCII) {
			switch(ch) {
				case PETSCII_HOME:
					return TERM_KEY_HOME;
				case PETSCII_CLEAR:
					return TERM_KEY_END;
				case PETSCII_INSERT:
					return TERM_KEY_INSERT;
				case PETSCII_DELETE:
					return '\b';
				case PETSCII_LEFT:
					return TERM_KEY_LEFT;
				case PETSCII_RIGHT:
					return TERM_KEY_RIGHT;
				case PETSCII_UP:
					return TERM_KEY_UP;
				case PETSCII_DOWN:
					return TERM_KEY_DOWN;
			}
			if((ch&0xe0) == 0xc0)	/* "Codes $60-$7F are, actually, copies of codes $C0-$DF" */
				ch = 0x60 | (ch&0x1f);
			if(isalpha((unsigned char)ch))
				ch ^= 0x20;	/* Swap upper/lower case */
		}

		if(term&SWAP_DELETE) {
			switch(ch) {
				case TERM_KEY_DELETE:
					ch = '\b';
					break;
				case '\b':
					ch = TERM_KEY_DELETE;
					break;
			}
		}
	}
92 93

	return ch;
94 95 96 97 98 99 100 101 102 103
}

/****************************************************************************/
/* Returns character if a key has been hit remotely and responds			*/
/* Called from functions getkey, msgabort and main_sec						*/
/****************************************************************************/
char sbbs_t::inkey(long mode, unsigned long timeout)
{
	uchar	ch=0;

104
	ch=kbincom(this,timeout); 
105

106
	if(ch==0) {
107 108
		if(sys_status&SS_SYSPAGE) 
			sbbs_beep(sbbs_random(800),100);
109 110 111 112
		return(0);
	}

	if(cfg.node_misc&NM_7BITONLY
113
		&& (!(sys_status&SS_USERON) || term_supports(NO_EXASCII)))
114 115
		ch&=0x7f; 

116
	this->timeout=time(NULL);
117

118
	/* Is this a control key */
119
	if(!(mode & K_CTRLKEYS) && ch < ' ') {
120 121 122 123
		if(cfg.ctrlkey_passthru&(1<<ch))	/*  flagged as passthru? */
			return(ch);						/* do not handle here */
		return(handle_ctrlkey(ch,mode));
	}
124

rswindell's avatar
rswindell committed
125 126 127 128 129 130 131 132 133 134 135 136
	if(mode&K_UPPER)
		ch=toupper(ch);

	return(ch);
}

char sbbs_t::handle_ctrlkey(char ch, long mode)
{
	char	str[512];
	char 	tmp[512];
	uint	i,j;

137
	if(ch==TERM_KEY_ABORT) {  /* Ctrl-C Abort */
138 139
		sys_status|=SS_ABORT;
		if(mode&K_SPIN) /* back space once if on spinning cursor */
140
			backspace();
141 142
		return(0); 
	}
143
	if(ch==CTRL_Z && !(mode&(K_MSG|K_GETSTR))
144
		&& action!=NODE_PCHT) {	 /* Ctrl-Z toggle raw input mode */
145
		if(hotkey_inside&(1<<ch))
146
			return(0);
147
		hotkey_inside |= (1<<ch);
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
		if(mode&K_SPIN)
			bputs("\b ");
		SAVELINE;
		attr(LIGHTGRAY);
		CRLF;
		bputs(text[RawMsgInputModeIsNow]);
		if(console&CON_RAW_IN)
			bputs(text[OFF]);
		else
			bputs(text[ON]);
		console^=CON_RAW_IN;
		CRLF;
		CRLF;
		RESTORELINE;
		lncntr=0;
163
		hotkey_inside &= ~(1<<ch);
164
		if(action!=NODE_MAIN && action!=NODE_XFER)
165 166 167
			return(CTRL_Z);
		return(0); 
	}
168

169 170 171
	if(console&CON_RAW_IN)	 /* ignore ctrl-key commands if in raw mode */
		return(ch);

172
#if 0	/* experimental removal to fix Tracker1's pause module problem with down-arrow */
rswindell's avatar
rswindell committed
173 174
	if(ch==LF)				/* ignore LF's if not in raw mode */
		return(0);
175
#endif
rswindell's avatar
rswindell committed
176 177

	/* Global hot key event */
178 179 180 181 182
	if(sys_status&SS_USERON) {
		for(i=0;i<cfg.total_hotkeys;i++)
			if(cfg.hotkey[i]->key==ch)
				break;
		if(i<cfg.total_hotkeys) {
183
			if(hotkey_inside&(1<<ch))
184
				return(0);
185
			hotkey_inside |= (1<<ch);
186 187 188 189 190 191 192
			if(mode&K_SPIN)
				bputs("\b ");
			if(!(sys_status&SS_SPLITP)) {
				SAVELINE;
				attr(LIGHTGRAY);
				CRLF; 
			}
193 194 195 196 197
			if(cfg.hotkey[i]->cmd[0]=='?') {
				if(js_hotkey_cx == NULL) {
					js_hotkey_cx = js_init(&js_hotkey_runtime, &js_hotkey_glob, "HotKey");
					js_create_user_objects(js_hotkey_cx, js_hotkey_glob);
				}
198
				js_execfile(cmdstr(cfg.hotkey[i]->cmd+1,nulstr,nulstr,tmp), /* startup_dir: */NULL, /* scope: */js_hotkey_glob, js_hotkey_cx, js_hotkey_glob);
199 200
			} else
				external(cmdstr(cfg.hotkey[i]->cmd,nulstr,nulstr,tmp),0);
201 202 203 204 205
			if(!(sys_status&SS_SPLITP)) {
				CRLF;
				RESTORELINE; 
			}
			lncntr=0;
206
			hotkey_inside &= ~(1<<ch);
207
			return(0);
rswindell's avatar
rswindell committed
208 209 210 211 212
		}
	}

	switch(ch) {
		case CTRL_O:	/* Ctrl-O toggles pause temporarily */
213
			console^=CON_PAUSEOFF;
214
			return(0); 
rswindell's avatar
rswindell committed
215
		case CTRL_P:	/* Ctrl-P Private node-node comm */
216
			if(!(sys_status&SS_USERON))
217
				break;;
218
			if(hotkey_inside&(1<<ch))
219
				return(0);
220
			hotkey_inside |= (1<<ch);
221 222
			if(mode&K_SPIN)
				bputs("\b ");
223
			if(!(sys_status&SS_SPLITP)) {
224 225
				SAVELINE;
				attr(LIGHTGRAY);
226 227
				CRLF; 
			}
228 229 230
			nodesync(); 	/* read any waiting messages */
			nodemsg();		/* send a message */
			SYNC;
231
			if(!(sys_status&SS_SPLITP)) {
232
				CRLF;
233 234
				RESTORELINE; 
			}
235
			lncntr=0;
236
			hotkey_inside &= ~(1<<ch);
237
			return(0); 
238

rswindell's avatar
rswindell committed
239
		case CTRL_U:	/* Ctrl-U Users online */
240
			if(!(sys_status&SS_USERON))
241
				break;
242
			if(hotkey_inside&(1<<ch))
243
				return(0);
244
			hotkey_inside |= (1<<ch);
245 246
			if(mode&K_SPIN)
				bputs("\b ");
247
			if(!(sys_status&SS_SPLITP)) {
248 249
				SAVELINE;
				attr(LIGHTGRAY);
250 251
				CRLF; 
			}
252 253
			whos_online(true); 	/* list users */
			ASYNC;
254
			if(!(sys_status&SS_SPLITP)) {
255
				CRLF;
256
				RESTORELINE; 
257 258
			}
			lncntr=0;
259
			hotkey_inside &= ~(1<<ch);
260
			return(0); 
rswindell's avatar
rswindell committed
261 262 263
		case CTRL_T: /* Ctrl-T Time information */
			if(sys_status&SS_SPLITP)
				return(ch);
264
			if(!(sys_status&SS_USERON))
265
				break;
266
			if(hotkey_inside&(1<<ch))
267
				return(0);
268
			hotkey_inside |= (1<<ch);
269 270 271 272 273
			if(mode&K_SPIN)
				bputs("\b ");
			SAVELINE;
			attr(LIGHTGRAY);
			now=time(NULL);
274
			bprintf(text[TiLogon],timestr(logontime));
275
			bprintf(text[TiNow],timestr(now),smb_zonestr(sys_timezone(&cfg),NULL));
276
			bprintf(text[TiTimeon]
277
				,sectostr((uint)(now-logontime),tmp));
278 279
			bprintf(text[TiTimeLeft]
				,sectostr(timeleft,tmp));
280 281
			if(sys_status&SS_EVENT)
				bprintf(text[ReducedTime],timestr(event_time));
282 283 284
			SYNC;
			RESTORELINE;
			lncntr=0;
285
			hotkey_inside &= ~(1<<ch);
286
			return(0); 
287
		case CTRL_K:  /*  Ctrl-K Control key menu */
rswindell's avatar
rswindell committed
288 289
			if(sys_status&SS_SPLITP)
				return(ch);
290
			if(!(sys_status&SS_USERON))
291
				break;
292
			if(hotkey_inside&(1<<ch))
293
				return(0);
294
			hotkey_inside |= (1<<ch);
295 296 297 298 299
			if(mode&K_SPIN)
				bputs("\b ");
			SAVELINE;
			attr(LIGHTGRAY);
			lncntr=0;
300 301 302 303
			if(mode&K_GETSTR)
				bputs(text[GetStrMenu]);
			else
				bputs(text[ControlKeyMenu]);
304 305 306
			ASYNC;
			RESTORELINE;
			lncntr=0;
307
			hotkey_inside &= ~(1<<ch);
308
			return(0); 
rswindell's avatar
rswindell committed
309
		case ESC:
310
			i=kbincom(this, (mode&K_GETSTR) ? 3000:1000);
311
			if(i==NOINP)		// timed-out waiting for '['
312
				return(ESC);
313
			ch=i;
314
			if(ch!='[') {
315
				ungetkey(ch, /* insert: */true);
316
				return(ESC); 
317
			}
318 319
			i=j=0;
			autoterm|=ANSI; 			/* <ESC>[x means they have ANSI */
320
#if 0 // this seems like a "bad idea" {tm}
321
			if(sys_status&SS_USERON && useron.misc&AUTOTERM && !(useron.misc&ANSI)
322 323
				&& useron.number) {
				useron.misc|=ANSI;
324 325
				putuserrec(&cfg,useron.number,U_MISC,8,ultoa(useron.misc,str,16)); 
			}
326
#endif
327
			while(i<10 && j<30) {		/* up to 3 seconds */
328
				ch=kbincom(this, 100);
329 330 331 332
				if(ch==(NOINP&0xff)) {
					j++;
					continue;
				}
rswindell's avatar
rswindell committed
333
				if(i == 0 && ch == 'M' && mouse_mode != MOUSE_MODE_OFF) {
334
					str[i++] = ch;
rswindell's avatar
rswindell committed
335
					int button = kbincom(this, 100);
336 337 338 339 340
					if(button == NOINP) {
						lprintf(LOG_DEBUG, "Timeout waiting for mouse button value");
						continue;
					}
					str[i++] = button;
rswindell's avatar
rswindell committed
341 342 343 344 345 346
					ch = kbincom(this, 100);
					if(ch < '!') {
						lprintf(LOG_DEBUG, "Unexpected mouse-button (0x%02X) tracking char: 0x%02X < '!'"
							, button, ch);
						continue;
					}
347
					str[i++] = ch;
rswindell's avatar
rswindell committed
348 349 350 351 352 353 354
					int x = ch - '!';
					ch = kbincom(this, 100);
					if(ch < '!') {
						lprintf(LOG_DEBUG, "Unexpected mouse-button (0x%02X) tracking char: 0x%02X < '!'"
							, button, ch);
						continue;
					}
355
					str[i++] = ch;
rswindell's avatar
rswindell committed
356
					int y = ch - '!';
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
					lprintf(LOG_DEBUG, "X10 Mouse button-click (0x%02X) reported at: %u x %u", button, x, y);
					if(button == 0x20) { // Left-click
						list_node_t* node;
						for(node = mouse_hotspots.first; node != NULL; node = node->next) {
							struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
							if(spot->y == y && x >= spot->minx && x <= spot->maxx)
								break;
						}
						if(node == NULL) {
							for(node = mouse_hotspots.first; node != NULL; node = node->next) {
								struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
								if(spot->hungry && spot->y == y && x >= spot->minx)
									break;
							}
						}
						if(node == NULL) {
							for(node = mouse_hotspots.last; node != NULL; node = node->prev) {
								struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
								if(spot->hungry && spot->y == y && x <= spot->minx)
									break;
							}
						}
						if(node != NULL) {
							struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
	#ifdef _DEBUG
							{
								char dbg[128];
								c_escape_str(spot->cmd, dbg, sizeof(dbg), /* Ctrl-only? */true);
								lprintf(LOG_DEBUG, "Stuffing hot spot command into keybuf: '%s'", dbg);
							}
	#endif
							ungetstr(spot->cmd);
							if(pause_inside && pause_hotspot == NULL)
								return handle_ctrlkey(TERM_KEY_ABORT, mode);
							return 0;
						}
393
						if(pause_inside && y == rows - 1)
394
							return '\r';
395
					} else if(button == '`' && console&CON_MOUSE_SCROLL) {
396
						return TERM_KEY_UP;
397
					} else if(button == 'a' && console&CON_MOUSE_SCROLL) {
398
						return TERM_KEY_DOWN;
399
					}
400 401
					if((button != 0x23 && console&CON_MOUSE_CLK_PASSTHRU)
						|| (button == 0x23 && console&CON_MOUSE_REL_PASSTHRU)) {
402 403 404 405 406
						for(j = i; j > 0; j--)
							ungetkey(str[j - 1], /* insert: */true);
						ungetkey('[', /* insert: */true);
						return(ESC); 
					}
rswindell's avatar
rswindell committed
407 408
					if(button == 0x22)  // Right-click
						return handle_ctrlkey(TERM_KEY_ABORT, mode);
409 410 411 412 413 414 415 416 417 418 419
					return 0;
				}
				if(i == 0 && ch == '<' && mouse_mode != MOUSE_MODE_OFF) {
					while(i < sizeof(str) - 1) {
						int byte = kbincom(this, 100);
						if(byte == NOINP) {
							lprintf(LOG_DEBUG, "Timeout waiting for mouse report character (%d)", i);
							return 0;
						}
						str[i++] = byte;
						if(isalpha(byte))
rswindell's avatar
rswindell committed
420 421
							break;
					}
422 423 424 425 426 427 428 429 430 431 432 433
					str[i] = 0;
					int button = -1, x = 0, y = 0;
					if(sscanf(str, "%d;%d;%d%c", &button, &x, &y, &ch) != 4
						|| button < 0 || x < 1 || y < 1 || toupper(ch) != 'M') {
						lprintf(LOG_DEBUG, "Invalid SGR mouse report sequence: '%s'", str);
						return 0;
					}
					--x;
					--y;
					lprintf(LOG_DEBUG, "SGR Mouse button-click (0x%02X) reported at: %u x %u", button, x, y);
					if(button == 0 && ch == 'M') { // Left-button press
						list_node_t* node;
rswindell's avatar
rswindell committed
434 435
						for(node = mouse_hotspots.first; node != NULL; node = node->next) {
							struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
436
							if(spot->y == y && x >= spot->minx && x <= spot->maxx)
rswindell's avatar
rswindell committed
437 438
								break;
						}
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
						if(node == NULL) {
							for(node = mouse_hotspots.first; node != NULL; node = node->next) {
								struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
								if(spot->hungry && spot->y == y && x >= spot->minx)
									break;
							}
						}
						if(node == NULL) {
							for(node = mouse_hotspots.last; node != NULL; node = node->prev) {
								struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
								if(spot->hungry && spot->y == y && x <= spot->minx)
									break;
							}
						}
						if(node != NULL) {
rswindell's avatar
rswindell committed
454
							struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
455 456 457 458 459 460 461 462 463 464 465
	#ifdef _DEBUG
							{
								char dbg[128];
								c_escape_str(spot->cmd, dbg, sizeof(dbg), /* Ctrl-only? */true);
								lprintf(LOG_DEBUG, "Stuffing hot spot command into keybuf: '%s'", dbg);
							}
	#endif
							ungetstr(spot->cmd);
							if(pause_inside && pause_hotspot == NULL)
								return handle_ctrlkey(TERM_KEY_ABORT, mode);
							return 0;
rswindell's avatar
rswindell committed
466
						}
467
						if(pause_inside && y == rows - 1)
468
							return '\r';
469
					} else if(button == 0x40 && console&CON_MOUSE_SCROLL) {
470
						return TERM_KEY_UP;
471
					} else if(button == 0x41 && console&CON_MOUSE_SCROLL) {
472
						return TERM_KEY_DOWN;
rswindell's avatar
rswindell committed
473
					}
474 475
					if((ch == 'M' && console&CON_MOUSE_CLK_PASSTHRU)
						|| (ch == 'm' && console&CON_MOUSE_REL_PASSTHRU)) {
476
						lprintf(LOG_DEBUG, "Passing-through SGR mouse report: 'ESC[<%s'", str);
477 478 479 480 481
						for(j = i; j > 0; j--)
							ungetkey(str[j - 1], /* insert: */true);
						ungetkey('<', /* insert: */true);
						ungetkey('[', /* insert: */true);
						return(ESC); 
rswindell's avatar
rswindell committed
482
					}
483
					if(ch == 'M' && button == 2)  // Right-click
484
						return handle_ctrlkey(TERM_KEY_ABORT, mode);
485 486 487
	#ifdef _DEBUG
					lprintf(LOG_DEBUG, "Eating SGR mouse report: 'ESC[<%s'", str);
	#endif
rswindell's avatar
rswindell committed
488 489
					return 0;
				}
490
				if(ch!=';' && !isdigit((uchar)ch) && ch!='R') {    /* other ANSI */
491
					str[i]=0;
492 493
					switch(ch) {
						case 'A':
494
							return(TERM_KEY_UP);
495
						case 'B':
496
							return(TERM_KEY_DOWN);
497
						case 'C':
498
							return(TERM_KEY_RIGHT);
499
						case 'D':
500
							return(TERM_KEY_LEFT);
501
						case 'H':	/* ANSI:  home cursor */
502
							return(TERM_KEY_HOME);
503 504 505 506
						case 'V':
							return TERM_KEY_PAGEUP;
						case 'U':
							return TERM_KEY_PAGEDN;
507 508
						case 'F':	/* Xterm: cursor preceding line */
						case 'K':	/* ANSI:  clear-to-end-of-line */
509
							return(TERM_KEY_END);
510
						case '@':	/* ANSI/ECMA-048 INSERT */
511
							return(TERM_KEY_INSERT);
512 513 514
						case '~':	/* VT-220 (XP telnet.exe) */
							switch(atoi(str)) {
								case 1:
515
									return(TERM_KEY_HOME);
516
								case 2:
517
									return(TERM_KEY_INSERT);
518
								case 3:
519
									return(TERM_KEY_DELETE);
520
								case 4:
521
									return(TERM_KEY_END);
522 523 524 525
								case 5:
									return TERM_KEY_PAGEUP;
								case 6:
									return TERM_KEY_PAGEDN;
526 527
							}
							break;
528
					}
529 530 531 532
					ungetkey(ch, /* insert: */true);
					for(j = i; j > 0; j--)
						ungetkey(str[j - 1], /* insert: */true);
					ungetkey('[', /* insert: */true);
533
					return(ESC); 
534 535
				}
				if(ch=='R') {       /* cursor position report */
536
					if(mode&K_ANSI_CPR && i) {	/* auto-detect rows */
537
						int	x,y;
538
						str[i]=0;
539
						if(sscanf(str,"%u;%u",&y,&x)==2) {
540 541
							lprintf(LOG_DEBUG,"received ANSI cursor position report: %ux%u"
								,x, y);
542
							/* Sanity check the coordinates in the response: */
543 544
							if(useron.cols == TERM_COLS_AUTO && x >= TERM_COLS_MIN && x <= TERM_COLS_MAX) cols=x;
							if(useron.rows == TERM_ROWS_AUTO && y >= TERM_ROWS_MIN && y <= TERM_ROWS_MAX) rows=y;
545
						}
546
					}
547 548 549
					return(0); 
				}
				str[i++]=ch; 
550
			}
551

552 553 554
			for(j = i; j > 0; j--)
				ungetkey(str[j - 1], /* insert: */true);
			ungetkey('[', /* insert: */true);
555
			return(ESC); 
rswindell's avatar
rswindell committed
556
	}
557
	return(ch);
558
}
rswindell's avatar
rswindell committed
559 560 561

void sbbs_t::set_mouse(long flags)
{
562 563
	long term = term_supports();
	if((term&ANSI) && ((term&MOUSE) || flags == MOUSE_MODE_OFF)) {
rswindell's avatar
rswindell committed
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
		long mode = mouse_mode & ~flags;
		if(mode & MOUSE_MODE_X10)	ansi_mouse(ANSI_MOUSE_X10, false);
		if(mode & MOUSE_MODE_NORM)	ansi_mouse(ANSI_MOUSE_NORM, false);
		if(mode & MOUSE_MODE_BTN)	ansi_mouse(ANSI_MOUSE_BTN, false);
		if(mode & MOUSE_MODE_ANY)	ansi_mouse(ANSI_MOUSE_ANY, false);
		if(mode & MOUSE_MODE_EXT)	ansi_mouse(ANSI_MOUSE_EXT, false);

		mode = flags & ~mouse_mode;
		if(mode & MOUSE_MODE_X10)	ansi_mouse(ANSI_MOUSE_X10, true);
		if(mode & MOUSE_MODE_NORM)	ansi_mouse(ANSI_MOUSE_NORM, true);
		if(mode & MOUSE_MODE_BTN)	ansi_mouse(ANSI_MOUSE_BTN, true);
		if(mode & MOUSE_MODE_ANY)	ansi_mouse(ANSI_MOUSE_ANY, true);
		if(mode & MOUSE_MODE_EXT)	ansi_mouse(ANSI_MOUSE_EXT, true);

		mouse_mode = flags;
	}
}

rswindell's avatar
rswindell committed
582
struct mouse_hotspot* sbbs_t::add_hotspot(struct mouse_hotspot* spot)
rswindell's avatar
rswindell committed
583 584 585 586
{
	if(spot->y < 0)
		spot->y = row;
	if(spot->minx < 0)
rswindell's avatar
rswindell committed
587
		spot->minx = column;
rswindell's avatar
rswindell committed
588 589 590
	if(spot->maxx < 0)
		spot->maxx = cols - 1;
#ifdef _DEBUG
591
	char dbg[128];
592
	lprintf(LOG_DEBUG, "Adding mouse hot spot %ld-%ld x %ld = '%s'"
593
		,spot->minx, spot->maxx, spot->y, c_escape_str(spot->cmd, dbg, sizeof(dbg), /* Ctrl-only? */true));
rswindell's avatar
rswindell committed
594
#endif
rswindell's avatar
rswindell committed
595 596 597
	list_node_t* node = listInsertNodeData(&mouse_hotspots, spot, sizeof(*spot));
	if(node == NULL)
		return NULL;
598
	set_mouse(MOUSE_MODE_NORM | MOUSE_MODE_EXT);
rswindell's avatar
rswindell committed
599
	return (struct mouse_hotspot*)node->data;
rswindell's avatar
rswindell committed
600 601 602 603 604
}

void sbbs_t::clear_hotspots(void)
{
	long spots = listCountNodes(&mouse_hotspots);
605 606
	if(spots) {
#ifdef _DEBUG
rswindell's avatar
rswindell committed
607 608
		lprintf(LOG_DEBUG, "Clearing %ld mouse hot spots", spots);
#endif
609
		listFreeNodes(&mouse_hotspots);
610
		if(!(console&CON_MOUSE_SCROLL))
611 612
			set_mouse(MOUSE_MODE_OFF);
	}
rswindell's avatar
rswindell committed
613 614 615 616 617
}

void sbbs_t::scroll_hotspots(long count)
{
	long spots = 0;
rswindell's avatar
rswindell committed
618
	long remain = 0;
rswindell's avatar
rswindell committed
619 620 621 622
	for(list_node_t* node = mouse_hotspots.first; node != NULL; node = node->next) {
		struct mouse_hotspot* spot = (struct mouse_hotspot*)node->data;
		spot->y -= count;
		spots++;
rswindell's avatar
rswindell committed
623 624
		if(spot->y >= 0)
			remain++;
rswindell's avatar
rswindell committed
625 626 627
	}
#ifdef _DEBUG
	if(spots)
rswindell's avatar
rswindell committed
628
		lprintf(LOG_DEBUG, "Scrolled %ld mouse hot-spots %ld rows (%ld remain)", spots, count, remain);
rswindell's avatar
rswindell committed
629
#endif
rswindell's avatar
rswindell committed
630 631
	if(remain < 1)
		clear_hotspots();
rswindell's avatar
rswindell committed
632 633
}

634
struct mouse_hotspot* sbbs_t::add_hotspot(char cmd, bool hungry, long minx, long maxx, long y)
rswindell's avatar
rswindell committed
635 636 637
{
	struct mouse_hotspot spot = {0};
	spot.cmd[0] = cmd;
638 639
	spot.minx = minx < 0 ? column : minx;
	spot.maxx = maxx < 0 ? column : maxx;
rswindell's avatar
rswindell committed
640
	spot.y = y;
641
	spot.hungry = hungry;
rswindell's avatar
rswindell committed
642
	return add_hotspot(&spot);
rswindell's avatar
rswindell committed
643 644
}

645
struct mouse_hotspot* sbbs_t::add_hotspot(ulong num, bool hungry, long minx, long maxx, long y)
rswindell's avatar
rswindell committed
646 647
{
	struct mouse_hotspot spot = {0};
rswindell's avatar
rswindell committed
648
	SAFEPRINTF(spot.cmd, "%lu\r", num);
rswindell's avatar
rswindell committed
649 650 651
	spot.minx = minx;
	spot.maxx = maxx;
	spot.y = y;
652
	spot.hungry = hungry;
rswindell's avatar
rswindell committed
653
	return add_hotspot(&spot);
rswindell's avatar
rswindell committed
654 655
}

656
struct mouse_hotspot* sbbs_t::add_hotspot(const char* cmd, bool hungry, long minx, long maxx, long y)
rswindell's avatar
rswindell committed
657 658 659 660 661 662
{
	struct mouse_hotspot spot = {0};
	SAFECOPY(spot.cmd, cmd);
	spot.minx = minx;
	spot.maxx = maxx;
	spot.y = y;
663
	spot.hungry = hungry;
rswindell's avatar
rswindell committed
664
	return add_hotspot(&spot);
rswindell's avatar
rswindell committed
665
}