⛏️ index : haiku.git

/*
 * Copyright 2005-2010, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 *
 * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
 * Distributed under the terms of the NewOS License.
 */


#include "blue_screen.h"

#include <KernelExport.h>
#include <console.h>
#include <debug.h>
#include <arch/debug_console.h>
#include <safemode.h>

#include <string.h>
#include <stdio.h>

#include "debug_commands.h"


#define USE_SCROLLING 0
#define NO_CLEAR 1

#define MAX_ARGS 8

#define FMASK 0x0f
#define BMASK 0x70

typedef enum {
	CONSOLE_STATE_NORMAL = 0,
	CONSOLE_STATE_GOT_ESCAPE,
	CONSOLE_STATE_SEEN_BRACKET,
	CONSOLE_STATE_NEW_ARG,
	CONSOLE_STATE_PARSING_ARG,
} console_state;

typedef enum {
	LINE_ERASE_WHOLE,
	LINE_ERASE_LEFT,
	LINE_ERASE_RIGHT
} erase_line_mode;

struct screen_info {
	int32	columns;
	int32	rows;
	int32	x, y;
	uint8	attr;
	bool	bright_attr;
	bool	reverse_attr;
	int32	in_command_rows;
	bool	paging;
	bool	paging_timeout;
	bool	boot_debug_output;
	bool	ignore_output;

	// state machine
	console_state state;
	int32	arg_count;
	int32	args[MAX_ARGS];
} sScreen;

console_module_info *sModule = NULL;


static inline void
hide_cursor(void)
{
	sModule->move_cursor(-1, -1);
}


static inline void
update_cursor(int32 x, int32 y)
{
	sModule->move_cursor(x, y);
}


static inline void
move_cursor(int32 x, int32 y)
{
	sScreen.x = x;
	sScreen.y = y;
	update_cursor(x, y);
}


#if USE_SCROLLING

/*!	Scroll from the cursor line up to the top of the scroll region up one
	line.
*/
static void
scroll_up(void)
{
	// move the screen up one
	sModule->blit(0, 1, sScreen.columns, sScreen.rows - 1, 0, 0);

	// clear the bottom line
	sModule->fill_glyph(0, 0, sScreen.columns, 1, ' ', sScreen.attr);
}
#endif


static void
next_line(void)
{
	bool abortCommand = false;

#if USE_SCROLLING
	// TODO: scrolling is usually too slow; we could probably just remove it
	if (sScreen.y == sScreen.rows - 1)
 		scroll_up();
 	else
 		sScreen.y++;
#else
	if (in_command_invocation())
		sScreen.in_command_rows++;
	else
		sScreen.in_command_rows = 0;

	if (sScreen.paging && ((sScreen.in_command_rows > 0
			&& ((sScreen.in_command_rows + 3) % sScreen.rows) == 0)
		|| (sScreen.boot_debug_output && sScreen.y == sScreen.rows - 1))) {
		if (sScreen.paging_timeout)
			spin(1000 * 1000 * 3);
		else {
			// Use the paging mechanism: either, we're in the debugger, and a
			// command is being executed, or we're currently showing boot debug
			// output
			const char *text = in_command_invocation()
				? "Press key to continue, Q to quit, S to skip output"
				: "Press key to continue, S to skip output, P to disable paging";
			int32 length = strlen(text);
			if (sScreen.x + length > sScreen.columns) {
				// make sure we don't overwrite too much
				text = "P";
				length = 1;
			}

			for (int32 i = 0; i < length; i++) {
				// yellow on black (or reverse, during boot)
				sModule->put_glyph(sScreen.columns - length + i, sScreen.y,
					text[i], sScreen.boot_debug_output ? 0x6f : 0xf6);
			}

			char c = kgetc();
			if (c == 's') {
				sScreen.ignore_output = true;
			} else if (c == 'q' && in_command_invocation()) {
				abortCommand = true;
				sScreen.ignore_output = true;
			} else if (c == 'p' && !in_command_invocation())
				sScreen.paging = false;
			else if (c == 't' && !in_command_invocation())
				sScreen.paging_timeout = true;

			// remove on screen text again
			sModule->fill_glyph(sScreen.columns - length, sScreen.y, length,
				1, ' ', sScreen.attr);
		}

		if (sScreen.in_command_rows > 0)
			sScreen.in_command_rows += 2;
	}
	if (sScreen.y == sScreen.rows - 1) {
		sScreen.y = 0;
		sModule->fill_glyph(0, 0, sScreen.columns, 2, ' ', sScreen.attr);
	} else
		sScreen.y++;
#endif

#if NO_CLEAR
	if (sScreen.y + 2 < sScreen.rows) {
		sModule->fill_glyph(0, (sScreen.y + 2) % sScreen.rows, sScreen.columns,
			1, ' ', sScreen.attr);
	}
#endif
	sScreen.x = 0;

	if (abortCommand) {
		abort_debugger_command();
			// should not return
	}
}


static void
erase_line(erase_line_mode mode)
{
	switch (mode) {
		case LINE_ERASE_WHOLE:
			sModule->fill_glyph(0, sScreen.y, sScreen.columns, 1, ' ',
				sScreen.attr);
			break;
		case LINE_ERASE_LEFT:
			sModule->fill_glyph(0, sScreen.y, sScreen.x + 1, 1, ' ',
				sScreen.attr);
			break;
		case LINE_ERASE_RIGHT:
			sModule->fill_glyph(sScreen.x, sScreen.y, sScreen.columns
				- sScreen.x, 1, ' ', sScreen.attr);
			break;
	}
}


static void
back_space(void)
{
	if (sScreen.x <= 0)
		return;

	sScreen.x--;
	sModule->put_glyph(sScreen.x, sScreen.y, ' ', sScreen.attr);
}


static void
put_character(char c)
{
	if (++sScreen.x >= sScreen.columns) {
		next_line();
		sScreen.x++;
	}

	sModule->put_glyph(sScreen.x - 1, sScreen.y, c, sScreen.attr);
}


static void
set_vt100_attributes(int32 *args, int32 argCount)
{
	if (argCount == 0) {
		// that's the default (attributes off)
		argCount++;
		args[0] = 0;
	}

	for (int32 i = 0; i < argCount; i++) {
		switch (args[i]) {
			case 0: // reset
				sScreen.attr = sScreen.boot_debug_output ? 0xf0 : 0x0f;
				sScreen.bright_attr = true;
				sScreen.reverse_attr = false;
				break;
			case 1: // bright
				sScreen.bright_attr = true;
				sScreen.attr |= 0x08; // set the bright bit
				break;
			case 2: // dim
				sScreen.bright_attr = false;
				sScreen.attr &= ~0x08; // unset the bright bit
				break;
			case 4: // underscore we can't do
				break;
			case 5: // blink
				sScreen.attr |= 0x80; // set the blink bit
				break;
			case 7: // reverse
				sScreen.reverse_attr = true;
				sScreen.attr = ((sScreen.attr & BMASK) >> 4)
					| ((sScreen.attr & FMASK) << 4);
				if (sScreen.bright_attr)
					sScreen.attr |= 0x08;
				break;
			case 8: // hidden?
				break;

			/* foreground colors */
			case 30: // black
			case 31: // red
			case 32: // green
			case 33: // yellow
			case 34: // blue
			case 35: // magenta
			case 36: // cyan
			case 37: // white
			{
				const uint8 colors[] = {0, 4, 2, 6, 1, 5, 3, 7};
				sScreen.attr = (sScreen.attr & ~FMASK) | colors[args[i] - 30]
					| (sScreen.bright_attr ? 0x08 : 0);
				break;
			}

			/* background colors */
			case 40: sScreen.attr = (sScreen.attr & ~BMASK) | (0 << 4); break; // black
			case 41: sScreen.attr = (sScreen.attr & ~BMASK) | (4 << 4); break; // red
			case 42: sScreen.attr = (sScreen.attr & ~BMASK) | (2 << 4); break; // green
			case 43: sScreen.attr = (sScreen.attr & ~BMASK) | (6 << 4); break; // yellow
			case 44: sScreen.attr = (sScreen.attr & ~BMASK) | (1 << 4); break; // blue
			case 45: sScreen.attr = (sScreen.attr & ~BMASK) | (5 << 4); break; // magenta
			case 46: sScreen.attr = (sScreen.attr & ~BMASK) | (3 << 4); break; // cyan
			case 47: sScreen.attr = (sScreen.attr & ~BMASK) | (7 << 4); break; // white
		}
	}
}


static bool
process_vt100_command(const char c, bool seenBracket, int32 *args,
	int32 argCount)
{
	bool ret = true;

//	kprintf("process_vt100_command: c '%c', argCount %ld, arg[0] %ld, arg[1] %ld, seenBracket %d\n",
//		c, argCount, args[0], args[1], seenBracket);

	if (seenBracket) {
		switch (c) {
			case 'H':	// set cursor position
			case 'f':
			{
				int32 row = argCount > 0 ? args[0] : 1;
				int32 col = argCount > 1 ? args[1] : 1;
				if (row > 0)
					row--;
				if (col > 0)
					col--;
				move_cursor(col, row);
				break;
			}
			case 'A':	// move up
			{
				int32 deltaY = argCount > 0 ? -args[0] : -1;
				if (deltaY == 0)
					deltaY = -1;
				move_cursor(sScreen.x, sScreen.y + deltaY);
				break;
			}
			case 'e':
			case 'B':	// move down
			{
				int32 deltaY = argCount > 0 ? args[0] : 1;
				if (deltaY == 0)
					deltaY = 1;
				move_cursor(sScreen.x, sScreen.y + deltaY);
				break;
			}
			case 'D':	// move left
			{
				int32 deltaX = argCount > 0 ? -args[0] : -1;
				if (deltaX == 0)
					deltaX = -1;
				move_cursor(sScreen.x + deltaX, sScreen.y);
				break;
			}
			case 'a':
			case 'C':	// move right
			{
				int32 deltaX = argCount > 0 ? args[0] : 1;
				if (deltaX == 0)
					deltaX = 1;
				move_cursor(sScreen.x + deltaX, sScreen.y);
				break;
			}
			case '`':
			case 'G':	// set X position
			{
				int32 newX = argCount > 0 ? args[0] : 1;
				if (newX > 0)
					newX--;
				move_cursor(newX, sScreen.y);
				break;
			}
			case 'd':	// set y position
			{
				int32 newY = argCount > 0 ? args[0] : 1;
				if (newY > 0)
					newY--;
				move_cursor(sScreen.x, newY);
				break;
			}
#if 0
			case 's':	// save current cursor
				save_cur(console, false);
				break;
			case 'u':	// restore cursor
				restore_cur(console, false);
				break;
			case 'r':	// set scroll region
			{
				int32 low = argCount > 0 ? args[0] : 1;
				int32 high = argCount > 1 ? args[1] : sScreen.lines;
				if (low <= high)
					set_scroll_region(console, low - 1, high - 1);
				break;
			}
			case 'L':	// scroll virtual down at cursor
			{
				int32 lines = argCount > 0 ? args[0] : 1;
				while (lines > 0) {
					scrdown(console);
					lines--;
				}
				break;
			}
			case 'M':	// scroll virtual up at cursor
			{
				int32 lines = argCount > 0 ? args[0] : 1;
				while (lines > 0) {
					scrup(console);
					lines--;
				}
				break;
			}
#endif
			case 'K':
				if (argCount == 0 || args[0] == 0) {
					// erase to end of line
					erase_line(LINE_ERASE_RIGHT);
				} else if (argCount > 0) {
					if (args[0] == 1)
						erase_line(LINE_ERASE_LEFT);
					else if (args[0] == 2)
						erase_line(LINE_ERASE_WHOLE);
				}
				break;
#if 0
			case 'J':
				if (argCount == 0 || args[0] == 0) {
					// erase to end of screen
					erase_screen(console, SCREEN_ERASE_DOWN);
				} else {
					if (args[0] == 1)
						erase_screen(console, SCREEN_ERASE_UP);
					else if (args[0] == 2)
						erase_screen(console, SCREEN_ERASE_WHOLE);
				}
				break;
#endif
			case 'm':
				if (argCount >= 0)
					set_vt100_attributes(args, argCount);
				break;
			default:
				ret = false;
		}
	} else {
		switch (c) {
#if 0
			case 'c':
				reset_console(console);
				break;
			case 'D':
				rlf(console);
				break;
			case 'M':
				lf(console);
				break;
			case '7':
				save_cur(console, true);
				break;
			case '8':
				restore_cur(console, true);
				break;
#endif
			default:
				ret = false;
		}
	}

	return ret;
}


static void
parse_character(char c)
{
	switch (sScreen.state) {
		case CONSOLE_STATE_NORMAL:
			// just output the stuff
			switch (c) {
				case '\n':
					next_line();
					break;
				case 0x8:
					back_space();
					break;
				case '\t':
					// TODO: real tab...
					sScreen.x = (sScreen.x + 8) & ~7;
					if (sScreen.x >= sScreen.columns)
						next_line();
					break;

				case '\r':
				case '\0':
				case '\a': // beep
					break;

				case 0x1b:
					// escape character
					sScreen.arg_count = 0;
					sScreen.state = CONSOLE_STATE_GOT_ESCAPE;
					break;
				default:
					put_character(c);
			}
			break;
		case CONSOLE_STATE_GOT_ESCAPE:
			// look for either commands with no argument, or the '[' character
			switch (c) {
				case '[':
					sScreen.state = CONSOLE_STATE_SEEN_BRACKET;
					break;
				default:
					sScreen.args[0] = 0;
					process_vt100_command(c, false, sScreen.args, 0);
					sScreen.state = CONSOLE_STATE_NORMAL;
			}
			break;
		case CONSOLE_STATE_SEEN_BRACKET:
			switch (c) {
				case '0'...'9':
					sScreen.arg_count = 0;
					sScreen.args[0] = c - '0';
					sScreen.state = CONSOLE_STATE_PARSING_ARG;
					break;
				case '?':
					// private DEC mode parameter follows - we ignore those
					// anyway
					break;
				default:
					process_vt100_command(c, true, sScreen.args, 0);
					sScreen.state = CONSOLE_STATE_NORMAL;
					break;
			}
			break;
		case CONSOLE_STATE_NEW_ARG:
			switch (c) {
				case '0'...'9':
					if (++sScreen.arg_count == MAX_ARGS) {
						sScreen.state = CONSOLE_STATE_NORMAL;
						break;
					}
					sScreen.args[sScreen.arg_count] = c - '0';
					sScreen.state = CONSOLE_STATE_PARSING_ARG;
					break;
				default:
					process_vt100_command(c, true, sScreen.args,
						sScreen.arg_count + 1);
					sScreen.state = CONSOLE_STATE_NORMAL;
					break;
			}
			break;
		case CONSOLE_STATE_PARSING_ARG:
			// parse args
			switch (c) {
				case '0'...'9':
					sScreen.args[sScreen.arg_count] *= 10;
					sScreen.args[sScreen.arg_count] += c - '0';
					break;
				case ';':
					sScreen.state = CONSOLE_STATE_NEW_ARG;
					break;
				default:
					process_vt100_command(c, true, sScreen.args,
						sScreen.arg_count + 1);
					sScreen.state = CONSOLE_STATE_NORMAL;
					break;
			}
	}
}


static int
set_paging(int argc, char **argv)
{
	if (argc > 1 && !strcmp(argv[1], "--help")) {
		kprintf("usage: %s [on|off]\n", argv[0]);
		return 0;
	}

	if (argc == 1)
		sScreen.paging = !sScreen.paging;
	else if (!strcmp(argv[1], "on"))
		sScreen.paging = true;
	else if (!strcmp(argv[1], "off"))
		sScreen.paging = false;
	else
		sScreen.paging = parse_expression(argv[1]) != 0;

	kprintf("paging is turned %s now.\n", sScreen.paging ? "on" : "off");
	return 0;
}


//	#pragma mark -


status_t
blue_screen_init_early()
{
	// we can't use get_module() here, since it's too early in the boot process
	extern console_module_info gFrameBufferConsoleModule;
	sModule = &gFrameBufferConsoleModule;

	if (sModule->info.std_ops(B_MODULE_INIT) != B_OK) {
		sModule = NULL;
		return B_ERROR;
	}

	sScreen.paging = sScreen.paging_timeout = false;
	return B_OK;
}


status_t
blue_screen_init()
{
	if (sModule == NULL) {
		// Early initialization must have previously failed, or never run.
		if (blue_screen_init_early() != B_OK)
			return B_ERROR;
	}

	sScreen.paging = !get_safemode_boolean(
		"disable_onscreen_paging", false);
	sScreen.paging_timeout = false;

	add_debugger_command("paging", set_paging, "Enable or disable paging");
	return B_OK;
}


status_t
blue_screen_enter(bool debugOutput)
{
	sScreen.attr = debugOutput ? 0xf0 : 0x0f;
		// black on white for KDL, white on black for debug output
	sScreen.boot_debug_output = debugOutput;
	sScreen.ignore_output = false;

	sScreen.x = sScreen.y = 0;
	sScreen.state = CONSOLE_STATE_NORMAL;

	if (sModule == NULL)
		return B_NO_INIT;

	sModule->get_size(&sScreen.columns, &sScreen.rows);
#if !NO_CLEAR
	sModule->clear(sScreen.attr);
#else
	sModule->fill_glyph(0, sScreen.y, sScreen.columns, 3, ' ', sScreen.attr);
#endif
	return B_OK;
}


bool
blue_screen_paging_enabled(void)
{
	return sScreen.paging;
}


void
blue_screen_set_paging(bool enabled)
{
	sScreen.paging = enabled;
}


void
blue_screen_clear_screen(void)
{
	sModule->clear(sScreen.attr);
	move_cursor(0, 0);
}


int
blue_screen_try_getchar(void)
{
	return arch_debug_blue_screen_try_getchar();
}


char
blue_screen_getchar(void)
{
	return arch_debug_blue_screen_getchar();
}


void
blue_screen_putchar(char c)
{
	if (sScreen.ignore_output
		&& (in_command_invocation() || sScreen.boot_debug_output))
		return;

	sScreen.ignore_output = false;
	hide_cursor();

	parse_character(c);

	update_cursor(sScreen.x, sScreen.y);
}


void
blue_screen_puts(const char *text)
{
	if (sScreen.ignore_output
		&& (in_command_invocation() || sScreen.boot_debug_output))
		return;

	sScreen.ignore_output = false;
	hide_cursor();

	while (text[0] != '\0') {
		parse_character(text[0]);
		text++;
	}

	update_cursor(sScreen.x, sScreen.y);
}