inkey.cpp 18.4 KB
Newer Older
1
/* Synchronet single key input function */
2
3
4
5
6

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
23
#include "petdefs.h"
24

25
26
27
28
29
30
31
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) 
32
33
34
35
36
37
38
			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
39
	} else {
40
		ch=sbbs->incom(timeout);
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
		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);
63
			if(IS_ALPHA(ch))
64
65
66
67
68
69
70
71
72
73
74
75
76
77
				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;
			}
		}
	}
78
79

	return ch;
80
81
82
83
}

/****************************************************************************/
/* Returns character if a key has been hit remotely and responds			*/
84
/* May return NOINP on timeout instead of '\0' when K_NUL mode is used.		*/
85
/****************************************************************************/
86
int sbbs_t::inkey(long mode, unsigned long timeout)
87
{
88
	int	ch=0;
89

90
91
92
93
	ch=kbincom(this,timeout);

	if(sys_status&SS_SYSPAGE) 
		sbbs_beep(400 + sbbs_random(800), ch == NOINP ? 100 : 10);
94

95
96
97
98
	if(ch == NOINP) {
		if(mode & K_NUL)	// distinguish between timeout and '\0'
			return NOINP;
		return 0;
99
100
101
	}

	if(cfg.node_misc&NM_7BITONLY
102
		&& (!(sys_status&SS_USERON) || term_supports(NO_EXASCII)))
103
104
		ch&=0x7f; 

105
	this->timeout=time(NULL);
106

107
	/* Is this a control key */
108
	if(!(mode & K_CTRLKEYS) && ch < ' ') {
109
110
111
112
		if(cfg.ctrlkey_passthru&(1<<ch))	/*  flagged as passthru? */
			return(ch);						/* do not handle here */
		return(handle_ctrlkey(ch,mode));
	}
113

rswindell's avatar
rswindell committed
114
115
116
117
118
119
120
121
122
123
124
125
	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;

126
	if(ch==TERM_KEY_ABORT) {  /* Ctrl-C Abort */
127
128
		sys_status|=SS_ABORT;
		if(mode&K_SPIN) /* back space once if on spinning cursor */
129
			backspace();
130
131
		return(0); 
	}
132
	if(ch==CTRL_Z && !(mode&(K_MSG|K_GETSTR))
133
		&& action!=NODE_PCHT) {	 /* Ctrl-Z toggle raw input mode */
134
		if(hotkey_inside&(1<<ch))
135
			return(0);
136
		hotkey_inside |= (1<<ch);
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
		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;
152
		hotkey_inside &= ~(1<<ch);
153
		if(action!=NODE_MAIN && action!=NODE_XFER)
154
155
156
			return(CTRL_Z);
		return(0); 
	}
157

158
159
160
	if(console&CON_RAW_IN)	 /* ignore ctrl-key commands if in raw mode */
		return(ch);

161
#if 0	/* experimental removal to fix Tracker1's pause module problem with down-arrow */
rswindell's avatar
rswindell committed
162
163
	if(ch==LF)				/* ignore LF's if not in raw mode */
		return(0);
164
#endif
rswindell's avatar
rswindell committed
165
166

	/* Global hot key event */
167
168
169
170
171
	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) {
172
			if(hotkey_inside&(1<<ch))
173
				return(0);
174
			hotkey_inside |= (1<<ch);
175
176
177
178
179
180
181
			if(mode&K_SPIN)
				bputs("\b ");
			if(!(sys_status&SS_SPLITP)) {
				SAVELINE;
				attr(LIGHTGRAY);
				CRLF; 
			}
182
183
184
185
186
			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);
				}
187
				js_execfile(cmdstr(cfg.hotkey[i]->cmd+1,nulstr,nulstr,tmp), /* startup_dir: */NULL, /* scope: */js_hotkey_glob, js_hotkey_cx, js_hotkey_glob);
188
189
			} else
				external(cmdstr(cfg.hotkey[i]->cmd,nulstr,nulstr,tmp),0);
190
191
192
193
194
			if(!(sys_status&SS_SPLITP)) {
				CRLF;
				RESTORELINE; 
			}
			lncntr=0;
195
			hotkey_inside &= ~(1<<ch);
196
			return(0);
rswindell's avatar
rswindell committed
197
198
199
200
201
		}
	}

	switch(ch) {
		case CTRL_O:	/* Ctrl-O toggles pause temporarily */
202
			console^=CON_PAUSEOFF;
203
			return(0); 
rswindell's avatar
rswindell committed
204
		case CTRL_P:	/* Ctrl-P Private node-node comm */
205
			if(!(sys_status&SS_USERON))
206
				break;;
207
			if(hotkey_inside&(1<<ch))
208
				return(0);
209
			hotkey_inside |= (1<<ch);
210
211
			if(mode&K_SPIN)
				bputs("\b ");
212
			if(!(sys_status&SS_SPLITP)) {
213
214
				SAVELINE;
				attr(LIGHTGRAY);
215
216
				CRLF; 
			}
217
218
219
			nodesync(); 	/* read any waiting messages */
			nodemsg();		/* send a message */
			SYNC;
220
			if(!(sys_status&SS_SPLITP)) {
221
				CRLF;
222
223
				RESTORELINE; 
			}
224
			lncntr=0;
225
			hotkey_inside &= ~(1<<ch);
226
			return(0); 
227

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

543
544
545
			for(j = i; j > 0; j--)
				ungetkey(str[j - 1], /* insert: */true);
			ungetkey('[', /* insert: */true);
546
			return(ESC); 
rswindell's avatar
rswindell committed
547
	}
548
	return(ch);
549
}
rswindell's avatar
rswindell committed
550
551
552

void sbbs_t::set_mouse(long flags)
{
553
554
	long term = term_supports();
	if((term&ANSI) && ((term&MOUSE) || flags == MOUSE_MODE_OFF)) {
rswindell's avatar
rswindell committed
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
		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
573
struct mouse_hotspot* sbbs_t::add_hotspot(struct mouse_hotspot* spot)
rswindell's avatar
rswindell committed
574
575
576
577
{
	if(spot->y < 0)
		spot->y = row;
	if(spot->minx < 0)
rswindell's avatar
rswindell committed
578
		spot->minx = column;
rswindell's avatar
rswindell committed
579
580
	if(spot->maxx < 0)
		spot->maxx = cols - 1;
581
#if 0 //def _DEBUG
582
	char dbg[128];
583
	lprintf(LOG_DEBUG, "Adding mouse hot spot %ld-%ld x %ld = '%s'"
584
		,spot->minx, spot->maxx, spot->y, c_escape_str(spot->cmd, dbg, sizeof(dbg), /* Ctrl-only? */true));
rswindell's avatar
rswindell committed
585
#endif
rswindell's avatar
rswindell committed
586
587
588
	list_node_t* node = listInsertNodeData(&mouse_hotspots, spot, sizeof(*spot));
	if(node == NULL)
		return NULL;
589
	set_mouse(MOUSE_MODE_NORM | MOUSE_MODE_EXT);
rswindell's avatar
rswindell committed
590
	return (struct mouse_hotspot*)node->data;
rswindell's avatar
rswindell committed
591
592
593
594
595
}

void sbbs_t::clear_hotspots(void)
{
	long spots = listCountNodes(&mouse_hotspots);
596
	if(spots) {
597
#if 0 //def _DEBUG
rswindell's avatar
rswindell committed
598
599
		lprintf(LOG_DEBUG, "Clearing %ld mouse hot spots", spots);
#endif
600
		listFreeNodes(&mouse_hotspots);
601
		if(!(console&CON_MOUSE_SCROLL))
602
603
			set_mouse(MOUSE_MODE_OFF);
	}
rswindell's avatar
rswindell committed
604
605
606
607
608
}

void sbbs_t::scroll_hotspots(long count)
{
	long spots = 0;
rswindell's avatar
rswindell committed
609
	long remain = 0;
rswindell's avatar
rswindell committed
610
611
612
613
	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
614
615
		if(spot->y >= 0)
			remain++;
rswindell's avatar
rswindell committed
616
617
618
	}
#ifdef _DEBUG
	if(spots)
rswindell's avatar
rswindell committed
619
		lprintf(LOG_DEBUG, "Scrolled %ld mouse hot-spots %ld rows (%ld remain)", spots, count, remain);
rswindell's avatar
rswindell committed
620
#endif
rswindell's avatar
rswindell committed
621
622
	if(remain < 1)
		clear_hotspots();
rswindell's avatar
rswindell committed
623
624
}

625
struct mouse_hotspot* sbbs_t::add_hotspot(char cmd, bool hungry, long minx, long maxx, long y)
rswindell's avatar
rswindell committed
626
627
628
{
	struct mouse_hotspot spot = {0};
	spot.cmd[0] = cmd;
629
630
	spot.minx = minx < 0 ? column : minx;
	spot.maxx = maxx < 0 ? column : maxx;
rswindell's avatar
rswindell committed
631
	spot.y = y;
632
	spot.hungry = hungry;
rswindell's avatar
rswindell committed
633
	return add_hotspot(&spot);
rswindell's avatar
rswindell committed
634
635
}

636
struct mouse_hotspot* sbbs_t::add_hotspot(ulong num, bool hungry, long minx, long maxx, long y)
rswindell's avatar
rswindell committed
637
638
{
	struct mouse_hotspot spot = {0};
rswindell's avatar
rswindell committed
639
	SAFEPRINTF(spot.cmd, "%lu\r", num);
rswindell's avatar
rswindell committed
640
641
642
	spot.minx = minx;
	spot.maxx = maxx;
	spot.y = y;
643
	spot.hungry = hungry;
rswindell's avatar
rswindell committed
644
	return add_hotspot(&spot);
rswindell's avatar
rswindell committed
645
646
}

647
struct mouse_hotspot* sbbs_t::add_hotspot(const char* cmd, bool hungry, long minx, long maxx, long y)
rswindell's avatar
rswindell committed
648
649
650
651
652
653
{
	struct mouse_hotspot spot = {0};
	SAFECOPY(spot.cmd, cmd);
	spot.minx = minx;
	spot.maxx = maxx;
	spot.y = y;
654
	spot.hungry = hungry;
rswindell's avatar
rswindell committed
655
	return add_hotspot(&spot);
rswindell's avatar
rswindell committed
656
}