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
}