PageRenderTime 85ms CodeModel.GetById 15ms app.highlight 64ms RepoModel.GetById 1ms app.codeStats 0ms

/platform/osx/scaffold/console.d

http://github.com/wilkie/djehuty
D | 759 lines | 617 code | 73 blank | 69 comment | 211 complexity | 273b60f39cc4e4d1a5de6f7983274c4d MD5 | raw file
  1/*
  2 * console.d
  3 *
  4 * This file implements the Console interfaces for the OS X system.
  5 *
  6 * Author: Dave Wilkinson
  7 *
  8 */
  9
 10module scaffold.console;
 11
 12import djehuty;
 13
 14import platform.unix.common;
 15
 16import platform.application;
 17
 18import synch.thread;
 19
 20import cui.application;
 21import cui.window;
 22
 23private int _toNearestConsoleColor(Color clr) {
 24	// 16 colors on console
 25	// For each channel, it can be 00, 88, or ff
 26	// That is, something mid range
 27
 28	int nearRed, nearGreen, nearBlue;
 29	int ret;
 30
 31	nearRed = cast(int)((clr.red * 3.0) + 0.5);
 32	nearGreen = cast(int)((clr.green * 3.0) + 0.5);
 33	nearBlue = cast(int)((clr.blue * 3.0) + 0.5);
 34
 35	if ((nearRed == nearGreen) && (nearGreen == nearBlue)) {
 36		// gray
 37		if (clr.red < (Color.DarkGray.red / 2.0)) {
 38			// Closer to black
 39			ret = 0;
 40		}
 41		else if (clr.red < ((Color.Gray.red - Color.DarkGray.red) / 2.0) + Color.DarkGray.red) {
 42			// Closer to dark gray
 43			ret = 8;
 44		}
 45		else if (clr.red < ((Color.White.red - Color.Gray.red) / 2.0) + Color.Gray.red) {
 46			// Closer to light gray
 47			ret = 7;
 48		}
 49		else {
 50			// Closer to white
 51			ret = 15;
 52		}
 53	}
 54	else {
 55		// Nearest color match
 56		static int[3][] translations = [
 57			[1,0,0],	// 1, Dark Red
 58			[0,1,0],	// 2, Dark Green
 59			[1,1,0],	// 3, Dark Yellow
 60			[0,0,1],	// 4, Dark Blue
 61			[1,0,1],	// 5, Dark Magenta
 62			[0,1,1],	// 6, Dark Cyan
 63
 64			[2,0,0],	// 9, Dark Red
 65			[0,2,0],	// 10, Dark Green
 66			[2,2,0],	// 11, Dark Yellow
 67			[0,0,2],	// 12, Dark Blue
 68			[2,0,2],	// 13, Dark Magenta
 69			[0,2,2],	// 14, Dark Cyan
 70		];
 71
 72		float mindistance = 4*3;
 73
 74		foreach(size_t i, coord; translations) {
 75			// Compare euclidian distance
 76			float distance = 0.0;
 77			float intermediate;
 78
 79			intermediate = coord[0] - nearRed;
 80			intermediate *= intermediate;
 81
 82			distance += intermediate;
 83
 84			intermediate = coord[1] - nearGreen;
 85			intermediate *= intermediate;
 86
 87			distance += intermediate;
 88
 89			intermediate = coord[2] - nearBlue;
 90			intermediate *= intermediate;
 91
 92			distance += intermediate;
 93
 94			// Omitting square root, it is unnecessary for comparison
 95			if (mindistance > distance) {
 96				mindistance = distance;
 97				ret = i;
 98				ret++;
 99				if (ret > 6) {
100					ret += 2;
101				}
102			}	
103		}
104	}
105
106	return ret;
107}
108
109
110void ConsoleSetColors(Color fg, Color bg) {
111	int fgidx = _toNearestConsoleColor(fg);
112	int bgidx = _toNearestConsoleColor(bg);
113
114	int bright = 0;
115	if (fgidx > 7) {
116		fgidx %= 8;
117		bright = 1;
118	}
119	if (ApplicationController.instance.usingCurses) {
120		int idx = fgidx << 3;
121		idx |= bgidx;
122
123		if (bright) {
124			Curses.attron(Curses.A_BOLD);
125		}
126		else {
127			Curses.attroff(Curses.A_BOLD);
128		}
129
130		Curses.init_pair(idx, fgidx, bgidx);
131		Curses.attron(Curses.COLOR_PAIR(idx));
132	}
133	else {
134		printf("\x1B[%d;%d;%dm", bright, 30 + fgidx, 40 + bgidx);
135	}
136}
137
138//will return the next character pressed
139Key consoleGetKey() {
140	Key ret;
141
142	ubyte[18] tmp;
143	uint count;
144
145	ret.code = Curses.getch();
146//	Curses.move(0,0);
147//	Console.put("                                                ");
148//	Curses.move(0,0);
149//	Console.put(ret.code, " ");
150
151	if (ret.code != 0x1B) {
152		// Not an escape sequence
153
154		if (ret.code == Curses.KEY_MOUSE || ret.code == Curses.KEY_RESIZE || ret.code == Curses.KEY_EVENT || ret.code >= Curses.KEY_MAX) {
155			return ret;
156		}
157
158		if (ret.code == 127 || ret.code == 8 || ret.code == 9 || ret.code == 13) {
159		}
160		// For ctrl+char
161		else if (ret.code < 26) {
162			ret.ctrl = true;
163			ret.code = Key.A + ret.code - 1;
164	//Curses.move(1,0);
165	//Console.put("                                                ");
166	//Curses.move(1,0);
167	//Console.put("alt: ", ret.alt, " ctrl: ", ret.ctrl, " shift: ", ret.shift, " code: ", ret.code);
168			return ret;
169		}
170		// For F5-F16:
171		else if (ret.code >= 281 && ret.code <= 292) {
172			ret.code -= 12;
173			ret.shift = true;
174		}
175		else if (ret.code >= 293 && ret.code <= 304) {
176			ret.code -= 24;
177			ret.ctrl = true;
178		}
179		else if (ret.code >= 305 && ret.code <= 316) {
180			ret.code -= 36;
181			ret.ctrl = true;
182			ret.shift = true;
183		}
184		else if (ret.code >= 317 && ret.code <= 328) {
185			ret.code -= 48;
186			ret.alt = true;
187		}
188
189		consoleTranslateKey(ret);
190	//Curses.move(1,0);
191	//Console.put("                                                ");
192	//Curses.move(1,0);
193	//Console.put("alt: ", ret.alt, " ctrl: ", ret.ctrl, " shift: ", ret.shift, " code: ", ret.code);
194
195		return ret;
196	}
197
198	// Escape sequence...
199	ret.code = Curses.getch();
200	//Console.put(ret.code, " ");
201
202	// Get extended commands
203	if (ret.code == 0x1B) {
204		// ESCAPE ESCAPE -> Escape
205		ret.code = Key.Escape;
206	}
207	else if (ret.code == '[') {
208		ret.code = Curses.getch();
209	//Console.put(ret.code, " ");
210		if (ret.code == '1') {
211			ret.code = Curses.getch();
212	//Console.put(ret.code, " ");
213			if (ret.code == '~') {
214				ret.code = Key.Home;
215			}
216			else if (ret.code == '#') {
217				ret.code = Curses.getch();
218	//Console.put(ret.code, " ");
219				if (ret.code == '~') {
220					ret.code = Key.F5;
221				}
222				else if (ret.code == ';') {
223					ret.code = Curses.getch();
224	//Console.put(ret.code, " ");
225					if (ret.code == '2') {
226						ret.shift = true;
227						ret.code = Key.F5;
228					}
229				}
230			}
231			else if (ret.code == '7') {
232				ret.code = Curses.getch();
233	//Console.put(ret.code, " ");
234				if (ret.code == '~') {
235					ret.code = Key.F6;
236				}
237				else if (ret.code == ';') {
238					ret.code = Curses.getch();
239	//Console.put(ret.code, " ");
240					if (ret.code == '2') {
241						ret.shift = true;
242						ret.code = Key.F6;
243					}
244				}
245			}
246			else if (ret.code == ';') {
247				// Arrow Keys
248				ret.code = Curses.getch();
249	//Console.put(ret.code, " ");
250				getModifiers(ret);
251	//Console.put(ret.code, " ");
252
253				if (ret.code == 'A') {
254					ret.code = Key.Up;
255				}
256				else if (ret.code == 'B') {
257					ret.code = Key.Down;
258				}
259				else if (ret.code == 'C') {
260					ret.code = Key.Right;
261				}
262				else if (ret.code == 'D') {
263					ret.code = Key.Left;
264				}
265			}
266			else {
267				ret.code = Curses.getch();
268	//Console.put(ret.code, " ");
269			}
270		}
271		else if (ret.code == '2') {
272			ret.code = Curses.getch();
273	//Console.put(ret.code, " ");
274			if (ret.code == '~') {
275			}
276			else if (ret.code == ';') {
277				// Alt + Insert
278				ret.code = Curses.getch();
279	//Console.put(ret.code, " ");
280				getModifiers(ret);
281	//Console.put(ret.code, " ");
282				if (ret.code == '~') {
283					ret.code = Key.Insert;
284				}
285			}
286		}
287		else if (ret.code == '3') {
288			ret.code = Curses.getch();
289	//Console.put(ret.code, " ");
290		}
291		else if (ret.code == '4') {
292			ret.code = Curses.getch();
293	//Console.put(ret.code, " ");
294		}
295		else if (ret.code == '5') {
296			ret.code = Curses.getch();
297	//Console.put(ret.code, " ");
298		}
299		else if (ret.code == '6') {
300			ret.code = Curses.getch();
301	//Console.put(ret.code, " ");
302		}
303		else {
304		}
305	}
306	else if (ret.code == 'O') {
307		ret.code = Curses.getch();
308
309		// F1, F2, F3, F4
310		if (ret.code == '1') {
311			ret.code = Curses.getch();
312	//Console.put(ret.code, " ");
313			if (ret.code == ';') {
314				ret.code = Curses.getch();
315	//Console.put(ret.code, " ");
316				getModifiers(ret);
317	//Console.put(ret.code, " ");
318
319				if (ret.code == 'P') {
320					ret.code = Key.F1;
321				}
322				else if (ret.code == 'Q') {
323					ret.code = Key.F2;
324				}
325				else if (ret.code == 'R') {
326					ret.code = Key.F3;
327				}
328				else if (ret.code == 'S') {
329					ret.code = Key.F4;
330				}
331			}
332		}
333		else if (ret.code == 'H') {
334			ret.code = Key.Home;
335		}
336		else if (ret.code == 'F') {
337			ret.code = Key.End;
338		}
339		else if (ret.code == 0x80) {
340			ret.code = Key.F1;
341		}
342		else if (ret.code == 0x81) {
343			ret.code = Key.F2;
344		}
345		else if (ret.code == 0x82) {
346			ret.code = Key.F3;
347		}
348		else if (ret.code == 0x83) {
349			ret.code = Key.F4;
350		}
351	}
352	else {
353		// Alt + Char
354		ret.alt = true;
355		consoleTranslateKey(ret);
356	}
357
358	//Curses.move(1,0);
359	//Console.put("                                                ");
360	//Curses.move(1,0);
361	//Console.put("alt: ", ret.alt, " ctrl: ", ret.ctrl, " shift: ", ret.shift, " code: ", ret.code);
362
363	return ret;
364}
365
366void getModifiers(ref Key key) {
367	if (key.code == '2' || key.code == '4' || key.code == '6' || key.code == '8') {
368		key.shift = true;
369	}
370
371	if (key.code == '3' || key.code == '4' || key.code == '7' || key.code == '8') {
372		key.alt = true;
373	}
374
375	if (key.code == '5' || key.code == '6' || key.code == '7' || key.code == '8') {
376		key.ctrl = true;
377	}
378
379	if (key.shift || key.alt || key.ctrl) {
380		key.code = Curses.getch();
381	}
382}
383
384void consoleTranslateKey(ref Key ky)
385{
386	switch(ky.code) {
387		case '~':
388		case '!':
389		case '@':
390		case '#':
391		case '$':
392		case '%':
393		case '^':
394		case '&':
395		case '*':
396		case '(':
397		case ')':
398		case '_':
399		case '+':
400		case '{':
401		case '}':
402		case ':':
403		case '"':
404		case '<':
405		case '>':
406		case '|':
407		case '?':
408			ky.shift = true;
409			break;
410		default:
411			if (ky.code >= 'A' && ky.code <= 'Z') {
412				ky.shift = true;
413			}
414			else if (ky.code >= '0' && ky.code <= '9') {
415				ky.shift = false;
416			}
417			break;
418	}
419	ky.code = keyTranslation[ky.code];
420}
421
422uint keyTranslation[Curses.KEY_MAX] = [
423	' ': Key.Space,
424	'\n': Key.Return,
425	'\r': Key.Return,
426	'\t': Key.Tab,
427	'\b': Key.Backspace,
428	127: Key.Backspace,
429	'a': Key.A,
430	'b': Key.B,
431	'c': Key.C,
432	'd': Key.D,
433	'e': Key.E,
434	'f': Key.F,
435	'g': Key.G,
436	'h': Key.H,
437	'i': Key.I,
438	'j': Key.J,
439	'k': Key.K,
440	'l': Key.L,
441	'm': Key.M,
442	'n': Key.N,
443	'o': Key.O,
444	'p': Key.P,
445	'q': Key.Q,
446	'r': Key.R,
447	's': Key.S,
448	't': Key.T,
449	'u': Key.U,
450	'v': Key.V,
451	'w': Key.W,
452	'x': Key.X,
453	'y': Key.Y,
454	'z': Key.Z,
455	'A': Key.A,
456	'B': Key.B,
457	'C': Key.C,
458	'D': Key.D,
459	'E': Key.E,
460	'F': Key.F,
461	'G': Key.G,
462	'H': Key.H,
463	'I': Key.I,
464	'J': Key.J,
465	'K': Key.K,
466	'L': Key.L,
467	'M': Key.M,
468	'N': Key.N,
469	'O': Key.O,
470	'P': Key.P,
471	'Q': Key.Q,
472	'R': Key.R,
473	'S': Key.S,
474	'T': Key.T,
475	'U': Key.U,
476	'V': Key.V,
477	'W': Key.W,
478	'X': Key.X,
479	'Y': Key.Y,
480	'Z': Key.Z,
481	'0': Key.Zero,
482	'1': Key.One,
483	'2': Key.Two,
484	'3': Key.Three,
485	'4': Key.Four,
486	'5': Key.Five,
487	'6': Key.Six,
488	'7': Key.Seven,
489	'8': Key.Eight,
490	'9': Key.Nine,
491	'`': Key.SingleQuote,
492	'~': Key.SingleQuote,
493	'!': Key.One,
494	'@': Key.Two,
495	'#': Key.Three,
496	'$': Key.Four,
497	'%': Key.Five,
498	'^': Key.Six,
499	'&': Key.Seven,
500	'*': Key.Eight,
501	'(': Key.Nine,
502	')': Key.Zero,
503	'-': Key.Minus,
504	'_': Key.Minus,
505	'=': Key.Equals,
506	'+': Key.Equals,
507	'[': Key.LeftBracket,
508	'{': Key.LeftBracket,
509	']': Key.RightBracket,
510	'}': Key.RightBracket,
511	';': Key.Semicolon,
512	':': Key.Semicolon,
513	'\'': Key.Quote,
514	'"': Key.Quote,
515	',': Key.Comma,
516	'<': Key.Comma,
517	'>': Key.Period,
518	'.': Key.Period,
519	'/': Key.Foreslash,
520	'?': Key.Foreslash,
521	'\\': Key.Backslash,
522	'|': Key.Backslash,
523	Curses.KEY_DOWN: Key.Down,
524	Curses.KEY_UP: Key.Up,
525	Curses.KEY_LEFT: Key.Left,
526	Curses.KEY_RIGHT: Key.Right,
527	Curses.KEY_HOME: Key.Home,
528	Curses.KEY_BACKSPACE: Key.Backspace,
529	Curses.KEY_DC: Key.Delete,
530	Curses.KEY_F1: Key.F1,
531	Curses.KEY_F2: Key.F2,
532	Curses.KEY_F3: Key.F3,
533	Curses.KEY_F4: Key.F4,
534	Curses.KEY_F5: Key.F5,
535	Curses.KEY_F6: Key.F6,
536	Curses.KEY_F7: Key.F7,
537	Curses.KEY_F8: Key.F8,
538	Curses.KEY_F9: Key.F9,
539	Curses.KEY_F10: Key.F10,
540	Curses.KEY_F11: Key.F11,
541	Curses.KEY_F12: Key.F12,
542	Curses.KEY_F13: Key.F13,
543	Curses.KEY_F14: Key.F14,
544	Curses.KEY_F15: Key.F15,
545	Curses.KEY_F16: Key.F16,
546	Curses.KEY_NPAGE: Key.PageDown,
547	Curses.KEY_PPAGE: Key.PageUp,
548	Curses.KEY_ENTER: Key.Return,
549	Curses.KEY_END: Key.End
550];
551
552
553struct winsize {
554	ushort ws_row;
555	ushort ws_col;
556	ushort ws_xpixel;
557	ushort ws_ypixel;
558}
559
560//window size
561
562int m_width;
563int m_height;
564
565bool m_winsize_state;
566
567
568
569
570const auto TIOCGWINSZ = 0x5413;
571const auto SIGWINCH = 28;
572
573//position tracking
574
575int m_x;
576int m_y;
577
578winsize m_winsize_saved;
579winsize m_winsize_working;
580
581termios m_term_info_saved;
582termios m_term_info_working;
583
584
585
586//signal handler for terminal Size
587
588extern(C) void close_sig_handler(int signal) {
589//	Djehuty.end(0);
590//	for(int i=0; i<256; i++) {
591//		printf("\x1B[48;5;%dma", i);
592//	}
593	ConsoleUninit();
594	exit(0);
595}
596
597extern(C) void size_sig_handler(int signal) {
598
599    ioctl(STDIN, TIOCGWINSZ, &m_winsize_working);
600
601    if (m_width != m_winsize_working.ws_col || m_height != m_winsize_working.ws_row) {
602        m_width = m_winsize_working.ws_col;
603        m_height = m_winsize_working.ws_row;
604
605        while (m_x >= m_width)
606        {
607            m_y++;
608            m_x -= m_width;
609        }
610
611        if (m_y >= m_height) { m_y = m_height-1; }
612        if (m_x < 0) { m_x = 0; }
613        if (m_y < 0) { m_y = 0; }
614
615        //reset (this will be retained when program exits)
616        m_winsize_saved = m_winsize_working;
617
618        //the window resized through the users actions, not through the class' resize()
619        //therefore don't change it back to anything on exit
620        m_winsize_state = false;
621    }
622
623    //fire Size event
624	CuiApplication app = cast(CuiApplication)Djehuty.app;
625	app.window.onResize();
626}
627
628void ConsoleInit() {
629	printf("\x1B7");
630	setlocale(LC_ALL, "");
631	setlocale(LC_CTYPE, "");
632
633//	setvbuf (stdout, null, _IONBF, 0);
634
635	setlocale(LC_ALL, "");
636	setlocale(LC_CTYPE, "");
637}
638
639void ConsoleUninit() {
640	printf("\x1B[0m");
641}
642
643void ConsoleClear() {
644	if (ApplicationController.instance.usingCurses) {
645		Curses.clear();
646		Curses.refresh();
647	}
648	else {
649		printf("\x1B[2J\x1B[0;0H");
650	}
651}
652
653void ConsoleSetRelative(int x, int y) {
654	int newx;
655	int newy;
656
657	Curses.getyx(Curses.stdscr, m_y, m_x);
658
659	newx = m_x + x;
660	newy = m_y + y;
661
662	Curses.move(newy, newx);
663}
664
665void ConsoleGetPosition(uint* x, uint* y) {
666	Curses.getyx(Curses.stdscr, m_y, m_x);
667	*x = m_x;
668	*y = m_y;
669}
670
671void ConsoleSetPosition(uint x, uint y) {
672    if (x >= m_width) { x = m_width-1; }
673
674    if (y >= m_height) { y = m_height-1; }
675
676    if (x < 0) { x = 0; }
677
678    if (y < 0) { y = 0; }
679
680	Curses.move(y,x);
681}
682
683void ConsoleHideCaret() {
684	printf("\x1B[?25l");
685	Curses.curs_set(0);
686}
687
688void ConsoleShowCaret() {
689	printf("\x1B[?25h");
690	Curses.curs_set(1);
691}
692
693void ConsoleSetHome() {
694	if (ApplicationController.instance.usingCurses) {
695		Curses.getyx(Curses.stdscr, m_y, m_x);
696		m_x = 0;
697		Curses.move(m_y, m_x);
698	}
699	else {
700		printf("\x1B[0G");
701	}
702}
703
704void ConsolePutString(char[] chrs) {
705	chrs ~= '\0';
706	Curses.getyx(Curses.stdscr, m_y, m_x);
707	char[] utf8 = chrs;
708	bool goBackOneLine = false;
709	if (ApplicationController.instance.usingCurses) {
710		for (uint i; i < utf8.length; i++) {
711			if (utf8[i] == '\r' || utf8[i] == '\n' || utf8[i] == '\0') {
712				if (i + m_x >= m_width) {
713					i = m_width - m_x;
714					goBackOneLine = true;
715				}
716				utf8[i] = '\0';								
717				Curses.wprintw(Curses.stdscr, "%s", &utf8[0]);
718				if (goBackOneLine) {
719					ConsoleSetPosition(m_width - 1, m_y);
720				}
721				Curses.refresh();
722				return;
723			}
724		}
725	}
726	else {
727		printf("%s", utf8.ptr);
728	}
729}
730
731void ConsolePutChar(dchar chr) {
732	dchar[] chrarray = [ chr, '\0' ];
733	char[] chrs = Unicode.toUtf8(chrarray);
734
735	if (ApplicationController.instance.usingCurses) {
736		if (chr == '\r' || chr == '\n') {
737			ConsoleSetRelative(0, 1);
738			ConsoleSetHome();
739		}
740		else {
741			Curses.wprintw(Curses.stdscr, "%s", chrs.ptr);
742			Curses.refresh();
743		}
744	}
745	else {
746		printf("%s", chrs.ptr);
747	}
748}
749
750void ConsoleGetSize(out uint width, out uint height) {
751	Curses.getmaxyx(Curses.stdscr, m_height, m_width);
752
753	width = m_width;
754	height = m_height;
755
756}
757
758void ConsoleGetChar(out dchar chr, out uint code) {
759}