1 
2 module xterm;
3 
4 version (Posix)
5 {
6 
7 import core.stdc.stdio;
8 import core.stdc.stdlib;
9 import core.stdc.string;
10 import core.sys.posix.sys.ioctl;
11 
12 import ed;
13 import termio;
14 
15 enum
16 {
17     NROW    = 24,
18     NCOL    = 80,
19     BEL     = 0x07,
20     ESC     = 0x1B,
21 }
22 
23 /*********************************
24  */
25 
26 int msm_init()
27 {
28     puts("\x1b[?1000h");
29     return TRUE;
30 }
31 
32 void msm_term()
33 {
34     puts("\x1b[?1000l");
35 }
36 
37 void	msm_showcursor() { }
38 void	msm_hidecursor() { }
39 
40 struct msm_status		// current state of mouse
41 {
42     int state;
43     uint row;
44     uint col;
45 }
46 
47 __gshared msm_status mstat;
48 
49 int msm_getstatus(uint *pcol,uint *prow)
50 {
51     int c = 0;
52     switch (mstat.state)
53     {
54 	case 0:
55 	    *prow = mstat.row;
56 	    *pcol = mstat.col;
57 	    return 0;
58 
59 	case 1:
60 	    *prow = mstat.row;
61 	    *pcol = mstat.col;
62 	    mstat.state++;
63 	    return 1;
64 
65 	case 2:
66 	    c = ttgetc();
67 	    if (c == 0x1b)
68 	    {   c = ttgetc();
69 		if (c == 0x5b)
70 		{   c = ttgetc();
71 		    if (c == 0x4d)
72 		    {   c = ttgetc();
73 			if (c == 0x23)
74 			{   c = ttgetc();
75 			    if (c >= 0x21)
76 			    {   mstat.col = c - 0x21;
77 				c = ttgetc();
78 				if (c >= 0x21)
79 				    mstat.row = c - 0x21;
80 			    }
81 			}
82 		    }
83 		}
84 	    }
85 	    *prow = mstat.row;
86 	    *pcol = mstat.col;
87 	    mstat.state++;
88 	    return 1;
89 
90 	case 3:
91 	    *prow = mstat.row;
92 	    *pcol = mstat.col;
93 	    mstat.state = 0;
94 	    return 0;
95 
96 	default:
97 	    assert(0);
98     }
99 }
100 
101 
102 immutable(char)*
103         PC,
104         CM,
105         CL,
106         CE,
107         UP,
108         CD,
109 	KU,	/* up arrow key */
110 	KD,	/* down arrow key */
111 	KL,	/* left arrow key */
112 	KR,	/* right arrow key */
113 	K1,	/* second meta key */
114 	SO,	/* start standout mode */
115 	SE,	/* end standout mode */
116 	VB,	/* visible bell */
117 	CS,	/* scrolling region */
118 	SR,	/* scroll reverse */
119 	SF,	/* scroll forward */
120 	DO,	/* move down (alt for scroll forward) */
121 	AL,	/* add a line */
122 	DL;	/* delete a line */
123 
124 /*
125  * The LONGKEY structures are used to map the keys that produce
126  * escape sequences to single return values.  Each LONGKEY element
127  * is allocated as an array of LONGKEY structures.  lk[0].key is
128  * the number of structure-1, lk[0].ptr is a backpointer.  lk[1] is
129  * the first entry, etc.  These structures are dynamcially allocated
130  * and linked at initialize time.  See tcapgetc() to see how they
131  * are used.
132  */
133 int LONGFINAL(int C) { return C & 0x80; }
134 int LONGCHAR(int C)  { return C & 0x7F; }
135 
136 struct LONGKEY {
137 	ubyte key;
138 	union {
139 	  LONGKEY* ptr;
140 	  int keyv;
141 	}
142 }
143 
144 LONGKEY *lkroot = null;
145 
146 struct TERM
147 {
148     short   t_nrow;              /* Number of rows.              */
149     short   t_ncol;              /* Number of columns.           */
150     bool    t_canscroll = true;
151 
152     void t_open()
153     {
154 	// Get size of terminal
155 	winsize ws;
156 	ioctl(1, TIOCGWINSZ, &ws);
157 	//printf("Columns: %d\tRows: %d\n", ws.ws_col, ws.ws_row);
158 	term.t_ncol = ws.ws_col;
159 	term.t_nrow = ws.ws_row;
160 
161 	CD = "\x1b\x5b\x4a".ptr;
162 	CM = "\x1b\x5b\x25\x69\x25\x64\x3b\x25\x64\x48".ptr; // "\x1b"\x1b[%i%d;%dH"
163 	CE = "\x1b\x5b\x4b".ptr;
164 	UP = "\x1b\x5b\x41".ptr;
165 	VB = "\x1b\x5b\x3f\x35\x68\x1b\x5b\x3f\x35\x6c".ptr;
166 	KU = "\x1b\x4f\x41".ptr;
167 	KD = "\x1b\x4f\x42".ptr;
168 	KL = "\x1b\x4f\x44".ptr;
169 	KR = "\x1b\x4f\x43".ptr;
170 	K1 = "\x1b\x4f\x50".ptr;
171 	SO = "\x1b\x5b\x37\x6d".ptr;
172 	SE = "\x1b\x5b\x32\x37\x6d".ptr;
173 	CS = "\x1b\x5b\x25\x69\x25\x64\x3b\x25\x64\x72".ptr;
174 	SR = "\x1b\x4d".ptr;
175 	SF = "\x0a".ptr;
176 	DO = "\x0a".ptr;
177 	AL = "\x1b\x5b\x4c".ptr;
178 	DL = "\x1b\x5b\x4d".ptr;
179 
180 	build_long_keys();
181 
182         if ( term.t_ncol < 1 || term.t_nrow < 1 )
183         {
184                 puts("Incomplete termcap entry\n");
185                 exit(1);
186         }
187 
188 	if(AL && DL)
189 	{
190 		scroll_type = 0;
191 	}
192 	else if (CS && SR && (SF || DO))
193 	{
194 		scroll_type = 1;
195 		if( !SF ) SF = DO;
196 	}
197 	else
198 	{
199 		assert(0);
200 		//term.t_scrollup = null;
201 		//term.t_scrolldn = null;
202 	}
203 
204         ttopen();
205     }
206 
207     void t_close()
208     {
209 	if( strcmp(getenv("TERM"),"vt100") == 0 )
210 		fputs("\033[?7h",stdout);	/* turn on autowrap	*/
211 	ttclose();
212 	msm_term();
213     }
214 
215     int t_getchar()         /* Get character from keyboard. */
216     {
217 	int c,i;
218 	LONGKEY* lkp, tmpp;
219 
220 	static int backlen;
221 	static char[16] backc;
222 
223 	/*
224 	 * If there was a previously almost complete LONGKEY sequence
225 	 * that failed, then we have to send the characters as if they
226 	 * were pressed individually.
227 	 */
228     start:
229 	/*
230 	 * Continue following the LONGKEY structure sequence until
231 	 * either there is a complete match, or there is a complete
232 	 * failure to match.
233 	 */
234 	lkp = lkroot;
235     loop:
236 	if (backlen)
237 	    c = backc[--backlen];
238 	else
239 	    c = fgetc(stdin);
240 	for(i=1; i<=lkp[0].key; i++)
241 	{
242 	    if( LONGCHAR(lkp[i].key) == c )
243 	    {
244 		//static int x;
245 		//printf("\nmatch %d\n",++x); fflush(stdout); sleep(2);
246 		if( LONGFINAL(lkp[i].key) )
247 		{
248 			c = lkp[i].keyv;
249 			if (c == MOUSEKEY)
250 			{
251 			    if (backlen)
252 				c = backc[--backlen];
253 			    else
254 				c = ttgetc();
255 			    mstat.col = c - 0x21;
256 			    if (backlen)
257 				c = backc[--backlen];
258 			    else
259 				c = ttgetc();
260 			    mstat.row = c - 0x21;
261 			    mstat.state = 1;
262 			    c = MOUSEKEY;
263 			}
264 			return c;
265 		}
266 		lkp = lkp[i].ptr;
267 		goto loop;
268 	    }
269 	}
270 
271 	version (none)
272 	{
273 	    for(i=1; i<=lkp[0].key; i++)
274 	    {
275 	      printf("\nLONGCHAR[%d] = x%02x\n",i,lkp[i].key);
276 	    }
277 	    printf("no match for x%02x of %d entries\n",c,i); fflush(stdout); sleep(10);
278 	}
279 
280 	/*
281 	 * Upon a complete failure to match, we fill up the backc[]
282 	 * array with the ASCII characters that have been entered
283 	 * so that future calls to tcapgetc() can return these
284 	 * characters is the correct order.
285 	 */
286 	backc[backlen++] = cast(char)c;
287 	while( lkp[0].ptr != null )
288 	{
289 		tmpp = lkp;
290 		lkp = lkp[0].ptr;
291 		for(i=1; i<=lkp[0].key; i++)
292 			if( lkp[i].ptr == tmpp ) break;
293 		backc[backlen++] = lkp[i].key;
294 	}
295 	return backc[--backlen];
296     }
297 
298     extern (C) static int t_putchar(int c)   /* Put character to display.    */
299     {
300 	return fputc(c, stdout);
301     }
302 
303     void t_flush()           /* Flush output buffers.        */
304     {
305 	fflush(stdout);
306     }
307 
308     void t_move(int row, int col)  /* Move the cursor, origin 0.   */
309     {
310 	printf("\x1b[%d;%dH", row + 1, col + 1);
311         //putpad(tgoto(CM, col, row));
312     }
313 
314     void t_eeol()            /* Erase to end of line.        */
315     {
316         putpad(CE);
317     }
318 
319     void t_eeop()            /* Erase to end of page.        */
320     {
321         putpad(CD);
322     }
323 
324     void t_beep()            /* Beep.                        */
325     {
326 	if (VB)
327 	    putpad(VB);
328 	else
329 	    t_putchar(BEL);
330     }
331 
332     void t_standout()        /* Start standout mode          */
333     {
334 	if (SO)
335 	    putpad( SO );
336     }
337 
338     void t_standend()        /* End standout mode            */
339     {
340 	if (SE)
341 	    putpad( SE );
342     }
343 
344     void setColorBright(bool bright)
345     {
346 	fprintf(stdout, "\033[%dm", bright);
347     }
348 
349     // Escape sequences:
350     // https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
351     // https://opensource.com/article/19/9/linux-terminal-colors
352     void setColor(Color color)
353     {
354 	with (Color)
355 	{
356 	    resetColor();
357 	    if (color == reverse)
358 	    {
359 		fprintf(stdout, "\033[7m");
360 		return;
361 	    }
362 	    if (color & bold)
363 		fprintf(stdout, "\033[1m");
364 	    uint fg = color & 7;
365 	    uint bg = (color & bgMask) >> 4;
366 	    if (bg)
367 		fprintf(stdout, "\033[%d;%dm", 40 + bg, 30 + fg);
368 	    else
369 		fprintf(stdout, "\033[%dm", 30 + fg);
370 	    if (color & underline)
371 		fprintf(stdout, "\033[4m");
372 	}
373     }
374 
375     void resetColor()
376     {
377 	fputs("\033[m", stdout);
378     }
379 
380     void t_scrollup(int first, int last)   /* Scroll the screen up         */
381     {
382 	if (scroll_type)
383 	{
384 	    tpstr( CS, first, last );
385 	    t_move( last, 0 );
386 	    putpad( SF );
387 	    tpstr( CS, 0, term.t_nrow - 1 );
388 	    t_move( last, 0 );
389 	}
390 	else
391 	{
392 	    t_move( first, 0 );
393 	    putpad( DL );
394 	    t_move( last, 0 );
395 	    putpad( AL );
396 	}
397     }
398 
399     void t_scrolldn(int first, int last)  /* Scroll the screen down       */
400 					  /* Note: scrolling routines do  */
401 					  /*  not save cursor position.   */
402     {
403 	if (scroll_type)
404 	{
405 	    tpstr( CS, first, last );
406 	    t_move( first, 0 );
407 	    putpad( SR );
408 	    tpstr( CS, 0, term.t_nrow - 1 );
409 	    t_move( first, 0 );
410 	}
411 	else
412 	{
413 	    t_move( last, 0 );
414 	    putpad( DL );
415 	    t_move( first, 0 );
416 	    putpad( AL );
417 	}
418     }
419 
420     void t_setcursor(int insertmode)
421     {
422     }
423 }
424 
425 TERM term;
426 
427 static int scroll_type;	/* type of scrolling region (used in tcapscr{up|dn}) */
428 
429 
430 void putpad(const(char) *str)
431 {
432 	while (*str)
433 	{   ttputc(*str);
434 	    str++;
435 	}
436         //tputs(str, 1, ttputc);
437 }
438 
439 void putnpad(const char *str, int n)
440 {
441 	while (n--)
442 	    ttputc(str[n]);
443         //tputs(str, n, ttputc);
444 }
445 
446 void build_long_keys()
447 {
448 	lkroot = cast(LONGKEY *)malloc( LONGKEY.sizeof );
449 	lkroot.key = 0;	lkroot.ptr = null;
450 
451 	// Bash:
452 	// for x in {1..12}; do echo -n "F$x "; tput kf$x | cat -A; echo; done
453         build_one_long("\033[A", UPKEY);
454 	build_one_long("\033[B", DNKEY);
455 	build_one_long("\033[C", RTKEY);
456 	build_one_long("\033[D", LTKEY);
457 	//		    $   O   P
458 	build_one_long( "\033\x4F\x50", F1KEY );
459 	build_one_long( "\033\x4F\x51", F2KEY );
460 	build_one_long( "\033\x4F\x52", F3KEY );
461 	build_one_long( "\033\x4F\x53", F4KEY );
462         //                  $   [   1   1   ~
463 	build_one_long( "\033\x5B\x31\x31\x7E", F1KEY );
464 	build_one_long( "\033\x5B\x31\x32\x7E", F2KEY );
465 	build_one_long( "\033\x5B\x31\x33\x7E", F3KEY );
466 	build_one_long( "\033\x5B\x31\x34\x7E", F4KEY );
467 	build_one_long( "\033\x5B\x31\x35\x7E", F5KEY );
468 	build_one_long( "\033\x5B\x31\x37\x7E", F6KEY );
469 	build_one_long( "\033\x5B\x31\x38\x7E", F7KEY );
470 	build_one_long( "\033\x5B\x31\x39\x7E", F8KEY );
471 	build_one_long( "\033\x5B\x32\x30\x7E", F9KEY );
472 	build_one_long( "\033\x5B\x32\x31\x7E", F10KEY );
473 	build_one_long( "\033\x5B\x32\x33\x7E", F11KEY );
474 	build_one_long( "\033\x5B\x32\x34\x7E", F12KEY );
475 
476 
477 	build_one_long("\033\x62", ALTB );
478 	build_one_long("\033\x63", ALTC );
479 	build_one_long("\033\x64", ALTD );
480 	build_one_long("\033\x65", ALTE );
481 	build_one_long("\033\x66", ALTF );
482 	build_one_long("\033\x68", ALTH );
483 	build_one_long("\033\x6D", ALTM );
484 	build_one_long("\033\x78", ALTX );
485 	build_one_long("\033\x7A", ALTZ );
486 
487 	build_one_long("\033\x5B\x32\x7E", InsKEY );
488 	build_one_long("\033\x5B\x33\x7E", DelKEY );
489 	build_one_long("\033\x5B\x31\x7E", HOMEKEY );
490 	build_one_long("\033\x5B\x34\x7E", ENDKEY );
491 	build_one_long("\033\x5B\x35\x7E", PgUpKEY );
492 	build_one_long("\033\x5B\x36\x7E", PgDnKEY );
493 
494 	build_one_long("\033\x5B\x4d\x20", MOUSEKEY );
495 }
496 
497 void build_one_long( const(char) *s, int keyval )
498 {
499 	int i;
500 	LONGKEY* lkp, tmpp, tmpq;
501 
502 	if (!s)
503 		return;
504 	lkp = lkroot;
505 	while( *s )
506 	{
507 		for(i=1; i<=lkp[0].key; i++)
508 			if( lkp[i].key == *s ) break;
509 		if( i != lkp[0].key+1 )
510 		{
511 			lkp = lkp[i].ptr;
512 			s++;
513 		}
514 		else
515 		{
516 			++i;
517 			tmpp = cast(LONGKEY *)malloc( LONGKEY.sizeof * i );
518 			while( i-- )
519 			{
520 				tmpp[i].key = lkp[i].key;
521 				tmpp[i].ptr = lkp[i].ptr;
522 			}
523 			if( lkp[0].ptr )
524 			{
525 				tmpq = lkp[0].ptr;
526 				for(i=1; i<=tmpq[0].key; i++)
527 					if( tmpq[i].ptr == lkp )
528 						tmpq[i].ptr = tmpp;
529 			}
530 			else
531 				lkroot = tmpp;
532 			free( lkp );
533 			lkp = tmpp;
534 			for(i=1; i<=lkp[0].key; i++)
535 				if( !LONGFINAL(lkp[i].key) )
536 					lkp[i].ptr.ptr = lkp;
537 			lkp[0].key++;
538 			lkp[lkp[0].key].key = *s++;
539 			if( !*s )
540 			{
541 				lkp[lkp[0].key].keyv = keyval;
542 				lkp[lkp[0].key].key |= 0x80;
543 			}
544 			else
545 			{
546 				tmpp = lkp;
547 				lkp = lkp[lkp[0].key].ptr = cast(LONGKEY *)
548 					malloc( LONGKEY.sizeof );
549 				lkp[0].key = 0;
550 				lkp[0].ptr = tmpp;
551 			}
552 		}
553 	}
554 }
555 
556 void tpstr( const(char) *str, int p1, int p2 )
557 {
558 	char[128] buf;
559 	char *pt = buf.ptr;
560 	int pi;
561 
562 	for(pi=0; pi<128;) buf[pi++] = '\0';
563 	pi = p1;
564 	while( *str >= '0' && *str <= '9' ) *pt++ = *str++;
565 	if( *str == '*' ) *pt++ = *str++;
566 	while( *str )
567 	{
568 		if( *str == '%' )
569 		{	str++;
570 			switch( *str++ )
571 			{
572 			  case 'd':
573 				sprintf(pt,"%d",pi);
574 				pi = p2; break;
575 			  case '2':
576 				sprintf(pt,"%2d",pi);
577 				pi = p2; break;
578 			  case '3':
579 				sprintf(pt,"%3d",pi);
580 				pi = p2; break;
581 			  case '.':
582 				sprintf(pt,"%c",pi);
583 				pi = p2; break;
584 			  case 'r':
585 				pi = p2;
586 				p2 = p1;
587 				p1 = pi;
588 				break;
589 			  case 'i':
590 				p1++; p2++; pi++; break;
591 			  case '%':
592 				*pt++ = '%'; break;
593 			  default:
594 				strcpy(pt,"\nTERMCAP Error\n");
595 				break;
596 			}
597 			pt = buf.ptr + strlen(buf.ptr);
598 		}
599 		else
600 			*pt++ = *str++;
601 	}
602 	*pt = '\0';
603 	putpad( buf.ptr );
604 }
605 
606 debug
607 {
608     void dump_longs()
609     {
610 	printf("__dump__\n");
611 	dump_one_long( 0, lkroot, null );
612 	printf("________\n");
613     }
614 
615     int backcheck = 0;
616 
617     void dump_one_long( int offset, LONGKEY *lkp, LONGKEY *lkprev )
618     {
619 	int i,j;
620 	if( backcheck )
621 	{
622 		for(j=0; j<offset; j++) printf(" ");
623 		printf( (lkprev == lkp[0].ptr) ? "TRUE\n" : "FALSE\n" );
624 	}
625 	for(i=1; i<=lkp[0].key; i++)
626 	{
627 		for(j=0; j<offset; j++) printf(" ");
628 		printf("%d (%c)      ",
629 			LONGCHAR(lkp[i].key), LONGCHAR(lkp[i].key));
630 		if( LONGFINAL(lkp[i].key) )
631 			printf("return( %d )\n", lkp[i].keyv );
632 		else
633 		{	printf("\n");
634 			dump_one_long( offset+1, lkp[i].ptr, lkp );
635 		}
636 	}
637     }
638 }
639 
640 }