1/*
2 * Copyright 2005-2010, Axel D��rfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 *
5 * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
6 * Distributed under the terms of the NewOS License.
7 */
8
9
10#include "blue_screen.h"
11
12#include <KernelExport.h>
13#include <frame_buffer_console.h>
14#include <console.h>
15#include <debug.h>
16#include <arch/debug_console.h>
17#include <safemode.h>
18
19#include <string.h>
20#include <stdio.h>
21
22#include "debug_commands.h"
23
24
25#define USE_SCROLLING 0
26#define NO_CLEAR 1
27
28#define MAX_ARGS 8
29
30#define FMASK 0x0f
31#define BMASK 0x70
32
33typedef enum {
34	CONSOLE_STATE_NORMAL = 0,
35	CONSOLE_STATE_GOT_ESCAPE,
36	CONSOLE_STATE_SEEN_BRACKET,
37	CONSOLE_STATE_NEW_ARG,
38	CONSOLE_STATE_PARSING_ARG,
39} console_state;
40
41typedef enum {
42	LINE_ERASE_WHOLE,
43	LINE_ERASE_LEFT,
44	LINE_ERASE_RIGHT
45} erase_line_mode;
46
47struct screen_info {
48	int32	columns;
49	int32	rows;
50	int32	x, y;
51	uint8	attr;
52	bool	bright_attr;
53	bool	reverse_attr;
54	int32	in_command_rows;
55	bool	paging;
56	bool	paging_timeout;
57	bool	boot_debug_output;
58	bool	ignore_output;
59
60	// state machine
61	console_state state;
62	int32	arg_count;
63	int32	args[MAX_ARGS];
64} sScreen;
65
66console_module_info *sModule;
67
68
69static inline void
70hide_cursor(void)
71{
72	sModule->move_cursor(-1, -1);
73}
74
75
76static inline void
77update_cursor(int32 x, int32 y)
78{
79	sModule->move_cursor(x, y);
80}
81
82
83static inline void
84move_cursor(int32 x, int32 y)
85{
86	sScreen.x = x;
87	sScreen.y = y;
88	update_cursor(x, y);
89}
90
91
92#if USE_SCROLLING
93
94/*!	Scroll from the cursor line up to the top of the scroll region up one
95	line.
96*/
97static void
98scroll_up(void)
99{
100	// move the screen up one
101	sModule->blit(0, 1, sScreen.columns, sScreen.rows - 1, 0, 0);
102
103	// clear the bottom line
104	sModule->fill_glyph(0, 0, sScreen.columns, 1, ' ', sScreen.attr);
105}
106#endif
107
108
109static void
110next_line(void)
111{
112	bool abortCommand = false;
113
114#if USE_SCROLLING
115	// TODO: scrolling is usually too slow; we could probably just remove it
116	if (sScreen.y == sScreen.rows - 1)
117 		scroll_up();
118 	else
119 		sScreen.y++;
120#else
121	if (in_command_invocation())
122		sScreen.in_command_rows++;
123	else
124		sScreen.in_command_rows = 0;
125
126	if (sScreen.paging && ((sScreen.in_command_rows > 0
127			&& ((sScreen.in_command_rows + 3) % sScreen.rows) == 0)
128		|| (sScreen.boot_debug_output && sScreen.y == sScreen.rows - 1))) {
129		if (sScreen.paging_timeout)
130			spin(1000 * 1000 * 3);
131		else {
132			// Use the paging mechanism: either, we're in the debugger, and a
133			// command is being executed, or we're currently showing boot debug
134			// output
135			const char *text = in_command_invocation()
136				? "Press key to continue, Q to quit, S to skip output"
137				: "Press key to continue, S to skip output, P to disable paging";
138			int32 length = strlen(text);
139			if (sScreen.x + length > sScreen.columns) {
140				// make sure we don't overwrite too much
141				text = "P";
142				length = 1;
143			}
144
145			for (int32 i = 0; i < length; i++) {
146				// yellow on black (or reverse, during boot)
147				sModule->put_glyph(sScreen.columns - length + i, sScreen.y,
148					text[i], sScreen.boot_debug_output ? 0x6f : 0xf6);
149			}
150
151			char c = kgetc();
152			if (c == 's') {
153				sScreen.ignore_output = true;
154			} else if (c == 'q' && in_command_invocation()) {
155				abortCommand = true;
156				sScreen.ignore_output = true;
157			} else if (c == 'p' && !in_command_invocation())
158				sScreen.paging = false;
159			else if (c == 't' && !in_command_invocation())
160				sScreen.paging_timeout = true;
161
162			// remove on screen text again
163			sModule->fill_glyph(sScreen.columns - length, sScreen.y, length,
164				1, ' ', sScreen.attr);
165		}
166
167		if (sScreen.in_command_rows > 0)
168			sScreen.in_command_rows += 2;
169	}
170	if (sScreen.y == sScreen.rows - 1) {
171		sScreen.y = 0;
172		sModule->fill_glyph(0, 0, sScreen.columns, 2, ' ', sScreen.attr);
173	} else
174		sScreen.y++;
175#endif
176
177#if NO_CLEAR
178	if (sScreen.y + 2 < sScreen.rows) {
179		sModule->fill_glyph(0, (sScreen.y + 2) % sScreen.rows, sScreen.columns,
180			1, ' ', sScreen.attr);
181	}
182#endif
183	sScreen.x = 0;
184
185	if (abortCommand) {
186		abort_debugger_command();
187			// should not return
188	}
189}
190
191
192static void
193erase_line(erase_line_mode mode)
194{
195	switch (mode) {
196		case LINE_ERASE_WHOLE:
197			sModule->fill_glyph(0, sScreen.y, sScreen.columns, 1, ' ',
198				sScreen.attr);
199			break;
200		case LINE_ERASE_LEFT:
201			sModule->fill_glyph(0, sScreen.y, sScreen.x + 1, 1, ' ',
202				sScreen.attr);
203			break;
204		case LINE_ERASE_RIGHT:
205			sModule->fill_glyph(sScreen.x, sScreen.y, sScreen.columns
206				- sScreen.x, 1, ' ', sScreen.attr);
207			break;
208	}
209}
210
211
212static void
213back_space(void)
214{
215	if (sScreen.x <= 0)
216		return;
217
218	sScreen.x--;
219	sModule->put_glyph(sScreen.x, sScreen.y, ' ', sScreen.attr);
220}
221
222
223static void
224put_character(char c)
225{
226	if (++sScreen.x >= sScreen.columns) {
227		next_line();
228		sScreen.x++;
229	}
230
231	sModule->put_glyph(sScreen.x - 1, sScreen.y, c, sScreen.attr);
232}
233
234
235static void
236set_vt100_attributes(int32 *args, int32 argCount)
237{
238	if (argCount == 0) {
239		// that's the default (attributes off)
240		argCount++;
241		args[0] = 0;
242	}
243
244	for (int32 i = 0; i < argCount; i++) {
245		switch (args[i]) {
246			case 0: // reset
247				sScreen.attr = sScreen.boot_debug_output ? 0xf0 : 0x0f;
248				sScreen.bright_attr = true;
249				sScreen.reverse_attr = false;
250				break;
251			case 1: // bright
252				sScreen.bright_attr = true;
253				sScreen.attr |= 0x08; // set the bright bit
254				break;
255			case 2: // dim
256				sScreen.bright_attr = false;
257				sScreen.attr &= ~0x08; // unset the bright bit
258				break;
259			case 4: // underscore we can't do
260				break;
261			case 5: // blink
262				sScreen.attr |= 0x80; // set the blink bit
263				break;
264			case 7: // reverse
265				sScreen.reverse_attr = true;
266				sScreen.attr = ((sScreen.attr & BMASK) >> 4)
267					| ((sScreen.attr & FMASK) << 4);
268				if (sScreen.bright_attr)
269					sScreen.attr |= 0x08;
270				break;
271			case 8: // hidden?
272				break;
273
274			/* foreground colors */
275			case 30: // black
276			case 31: // red
277			case 32: // green
278			case 33: // yellow
279			case 34: // blue
280			case 35: // magenta
281			case 36: // cyan
282			case 37: // white
283			{
284				const uint8 colors[] = {0, 4, 2, 6, 1, 5, 3, 7};
285				sScreen.attr = (sScreen.attr & ~FMASK) | colors[args[i] - 30]
286					| (sScreen.bright_attr ? 0x08 : 0);
287				break;
288			}
289
290			/* background colors */
291			case 40: sScreen.attr = (sScreen.attr & ~BMASK) | (0 << 4); break; // black
292			case 41: sScreen.attr = (sScreen.attr & ~BMASK) | (4 << 4); break; // red
293			case 42: sScreen.attr = (sScreen.attr & ~BMASK) | (2 << 4); break; // green
294			case 43: sScreen.attr = (sScreen.attr & ~BMASK) | (6 << 4); break; // yellow
295			case 44: sScreen.attr = (sScreen.attr & ~BMASK) | (1 << 4); break; // blue
296			case 45: sScreen.attr = (sScreen.attr & ~BMASK) | (5 << 4); break; // magenta
297			case 46: sScreen.attr = (sScreen.attr & ~BMASK) | (3 << 4); break; // cyan
298			case 47: sScreen.attr = (sScreen.attr & ~BMASK) | (7 << 4); break; // white
299		}
300	}
301}
302
303
304static bool
305process_vt100_command(const char c, bool seenBracket, int32 *args,
306	int32 argCount)
307{
308	bool ret = true;
309
310//	kprintf("process_vt100_command: c '%c', argCount %ld, arg[0] %ld, arg[1] %ld, seenBracket %d\n",
311//		c, argCount, args[0], args[1], seenBracket);
312
313	if (seenBracket) {
314		switch (c) {
315			case 'H':	// set cursor position
316			case 'f':
317			{
318				int32 row = argCount > 0 ? args[0] : 1;
319				int32 col = argCount > 1 ? args[1] : 1;
320				if (row > 0)
321					row--;
322				if (col > 0)
323					col--;
324				move_cursor(col, row);
325				break;
326			}
327			case 'A':	// move up
328			{
329				int32 deltaY = argCount > 0 ? -args[0] : -1;
330				if (deltaY == 0)
331					deltaY = -1;
332				move_cursor(sScreen.x, sScreen.y + deltaY);
333				break;
334			}
335			case 'e':
336			case 'B':	// move down
337			{
338				int32 deltaY = argCount > 0 ? args[0] : 1;
339				if (deltaY == 0)
340					deltaY = 1;
341				move_cursor(sScreen.x, sScreen.y + deltaY);
342				break;
343			}
344			case 'D':	// move left
345			{
346				int32 deltaX = argCount > 0 ? -args[0] : -1;
347				if (deltaX == 0)
348					deltaX = -1;
349				move_cursor(sScreen.x + deltaX, sScreen.y);
350				break;
351			}
352			case 'a':
353			case 'C':	// move right
354			{
355				int32 deltaX = argCount > 0 ? args[0] : 1;
356				if (deltaX == 0)
357					deltaX = 1;
358				move_cursor(sScreen.x + deltaX, sScreen.y);
359				break;
360			}
361			case '`':
362			case 'G':	// set X position
363			{
364				int32 newX = argCount > 0 ? args[0] : 1;
365				if (newX > 0)
366					newX--;
367				move_cursor(newX, sScreen.y);
368				break;
369			}
370			case 'd':	// set y position
371			{
372				int32 newY = argCount > 0 ? args[0] : 1;
373				if (newY > 0)
374					newY--;
375				move_cursor(sScreen.x, newY);
376				break;
377			}
378#if 0
379			case 's':	// save current cursor
380				save_cur(console, false);
381				break;
382			case 'u':	// restore cursor
383				restore_cur(console, false);
384				break;
385			case 'r':	// set scroll region
386			{
387				int32 low = argCount > 0 ? args[0] : 1;
388				int32 high = argCount > 1 ? args[1] : sScreen.lines;
389				if (low <= high)
390					set_scroll_region(console, low - 1, high - 1);
391				break;
392			}
393			case 'L':	// scroll virtual down at cursor
394			{
395				int32 lines = argCount > 0 ? args[0] : 1;
396				while (lines > 0) {
397					scrdown(console);
398					lines--;
399				}
400				break;
401			}
402			case 'M':	// scroll virtual up at cursor
403			{
404				int32 lines = argCount > 0 ? args[0] : 1;
405				while (lines > 0) {
406					scrup(console);
407					lines--;
408				}
409				break;
410			}
411#endif
412			case 'K':
413				if (argCount == 0 || args[0] == 0) {
414					// erase to end of line
415					erase_line(LINE_ERASE_RIGHT);
416				} else if (argCount > 0) {
417					if (args[0] == 1)
418						erase_line(LINE_ERASE_LEFT);
419					else if (args[0] == 2)
420						erase_line(LINE_ERASE_WHOLE);
421				}
422				break;
423#if 0
424			case 'J':
425				if (argCount == 0 || args[0] == 0) {
426					// erase to end of screen
427					erase_screen(console, SCREEN_ERASE_DOWN);
428				} else {
429					if (args[0] == 1)
430						erase_screen(console, SCREEN_ERASE_UP);
431					else if (args[0] == 2)
432						erase_screen(console, SCREEN_ERASE_WHOLE);
433				}
434				break;
435#endif
436			case 'm':
437				if (argCount >= 0)
438					set_vt100_attributes(args, argCount);
439				break;
440			default:
441				ret = false;
442		}
443	} else {
444		switch (c) {
445#if 0
446			case 'c':
447				reset_console(console);
448				break;
449			case 'D':
450				rlf(console);
451				break;
452			case 'M':
453				lf(console);
454				break;
455			case '7':
456				save_cur(console, true);
457				break;
458			case '8':
459				restore_cur(console, true);
460				break;
461#endif
462			default:
463				ret = false;
464		}
465	}
466
467	return ret;
468}
469
470
471static void
472parse_character(char c)
473{
474	switch (sScreen.state) {
475		case CONSOLE_STATE_NORMAL:
476			// just output the stuff
477			switch (c) {
478				case '\n':
479					next_line();
480					break;
481				case 0x8:
482					back_space();
483					break;
484				case '\t':
485					// TODO: real tab...
486					sScreen.x = (sScreen.x + 8) & ~7;
487					if (sScreen.x >= sScreen.columns)
488						next_line();
489					break;
490
491				case '\r':
492				case '\0':
493				case '\a': // beep
494					break;
495
496				case 0x1b:
497					// escape character
498					sScreen.arg_count = 0;
499					sScreen.state = CONSOLE_STATE_GOT_ESCAPE;
500					break;
501				default:
502					put_character(c);
503			}
504			break;
505		case CONSOLE_STATE_GOT_ESCAPE:
506			// look for either commands with no argument, or the '[' character
507			switch (c) {
508				case '[':
509					sScreen.state = CONSOLE_STATE_SEEN_BRACKET;
510					break;
511				default:
512					sScreen.args[0] = 0;
513					process_vt100_command(c, false, sScreen.args, 0);
514					sScreen.state = CONSOLE_STATE_NORMAL;
515			}
516			break;
517		case CONSOLE_STATE_SEEN_BRACKET:
518			switch (c) {
519				case '0'...'9':
520					sScreen.arg_count = 0;
521					sScreen.args[0] = c - '0';
522					sScreen.state = CONSOLE_STATE_PARSING_ARG;
523					break;
524				case '?':
525					// private DEC mode parameter follows - we ignore those
526					// anyway
527					break;
528				default:
529					process_vt100_command(c, true, sScreen.args, 0);
530					sScreen.state = CONSOLE_STATE_NORMAL;
531					break;
532			}
533			break;
534		case CONSOLE_STATE_NEW_ARG:
535			switch (c) {
536				case '0'...'9':
537					if (++sScreen.arg_count == MAX_ARGS) {
538						sScreen.state = CONSOLE_STATE_NORMAL;
539						break;
540					}
541					sScreen.args[sScreen.arg_count] = c - '0';
542					sScreen.state = CONSOLE_STATE_PARSING_ARG;
543					break;
544				default:
545					process_vt100_command(c, true, sScreen.args,
546						sScreen.arg_count + 1);
547					sScreen.state = CONSOLE_STATE_NORMAL;
548					break;
549			}
550			break;
551		case CONSOLE_STATE_PARSING_ARG:
552			// parse args
553			switch (c) {
554				case '0'...'9':
555					sScreen.args[sScreen.arg_count] *= 10;
556					sScreen.args[sScreen.arg_count] += c - '0';
557					break;
558				case ';':
559					sScreen.state = CONSOLE_STATE_NEW_ARG;
560					break;
561				default:
562					process_vt100_command(c, true, sScreen.args,
563						sScreen.arg_count + 1);
564					sScreen.state = CONSOLE_STATE_NORMAL;
565					break;
566			}
567	}
568}
569
570
571static int
572set_paging(int argc, char **argv)
573{
574	if (argc > 1 && !strcmp(argv[1], "--help")) {
575		kprintf("usage: %s [on|off]\n", argv[0]);
576		return 0;
577	}
578
579	if (argc == 1)
580		sScreen.paging = !sScreen.paging;
581	else if (!strcmp(argv[1], "on"))
582		sScreen.paging = true;
583	else if (!strcmp(argv[1], "off"))
584		sScreen.paging = false;
585	else
586		sScreen.paging = parse_expression(argv[1]) != 0;
587
588	kprintf("paging is turned %s now.\n", sScreen.paging ? "on" : "off");
589	return 0;
590}
591
592
593//	#pragma mark -
594
595
596status_t
597blue_screen_init(void)
598{
599	extern console_module_info gFrameBufferConsoleModule;
600
601	// we can't use get_module() here, since it's too early in the boot process
602
603	if (!frame_buffer_console_available())
604		return B_ERROR;
605
606	sModule = &gFrameBufferConsoleModule;
607	sScreen.paging = !get_safemode_boolean(
608		"disable_onscreen_paging", false);
609	sScreen.paging_timeout = false;
610
611	add_debugger_command("paging", set_paging, "Enable or disable paging");
612	return B_OK;
613}
614
615
616status_t
617blue_screen_enter(bool debugOutput)
618{
619	sScreen.attr = debugOutput ? 0xf0 : 0x0f;
620		// black on white for KDL, white on black for debug output
621	sScreen.boot_debug_output = debugOutput;
622	sScreen.ignore_output = false;
623
624	sScreen.x = sScreen.y = 0;
625	sScreen.state = CONSOLE_STATE_NORMAL;
626
627	if (sModule == NULL)
628		return B_NO_INIT;
629
630	sModule->get_size(&sScreen.columns, &sScreen.rows);
631#if !NO_CLEAR
632	sModule->clear(sScreen.attr);
633#else
634	sModule->fill_glyph(0, sScreen.y, sScreen.columns, 3, ' ', sScreen.attr);
635#endif
636	return B_OK;
637}
638
639
640bool
641blue_screen_paging_enabled(void)
642{
643	return sScreen.paging;
644}
645
646
647void
648blue_screen_set_paging(bool enabled)
649{
650	sScreen.paging = enabled;
651}
652
653
654void
655blue_screen_clear_screen(void)
656{
657	sModule->clear(sScreen.attr);
658	move_cursor(0, 0);
659}
660
661
662int
663blue_screen_try_getchar(void)
664{
665	return arch_debug_blue_screen_try_getchar();
666}
667
668
669char
670blue_screen_getchar(void)
671{
672	return arch_debug_blue_screen_getchar();
673}
674
675
676void
677blue_screen_putchar(char c)
678{
679	if (sScreen.ignore_output
680		&& (in_command_invocation() || sScreen.boot_debug_output))
681		return;
682
683	sScreen.ignore_output = false;
684	hide_cursor();
685
686	parse_character(c);
687
688	update_cursor(sScreen.x, sScreen.y);
689}
690
691
692void
693blue_screen_puts(const char *text)
694{
695	if (sScreen.ignore_output
696		&& (in_command_invocation() || sScreen.boot_debug_output))
697		return;
698
699	sScreen.ignore_output = false;
700	hide_cursor();
701
702	while (text[0] != '\0') {
703		parse_character(text[0]);
704		text++;
705	}
706
707	update_cursor(sScreen.x, sScreen.y);
708}
709