* 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;
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
line.
*/
static void
scroll_up(void)
{
sModule->blit(0, 1, sScreen.columns, sScreen.rows - 1, 0, 0);
sModule->fill_glyph(0, 0, sScreen.columns, 1, ' ', sScreen.attr);
}
#endif
static void
next_line(void)
{
bool abortCommand = false;
#if USE_SCROLLING
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 {
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) {
text = "P";
length = 1;
}
for (int32 i = 0; i < length; i++) {
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;
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();
}
}
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) {
argCount++;
args[0] = 0;
}
for (int32 i = 0; i < argCount; i++) {
switch (args[i]) {
case 0:
sScreen.attr = sScreen.boot_debug_output ? 0xf0 : 0x0f;
sScreen.bright_attr = true;
sScreen.reverse_attr = false;
break;
case 1:
sScreen.bright_attr = true;
sScreen.attr |= 0x08;
break;
case 2:
sScreen.bright_attr = false;
sScreen.attr &= ~0x08;
break;
case 4:
break;
case 5:
sScreen.attr |= 0x80;
break;
case 7:
sScreen.reverse_attr = true;
sScreen.attr = ((sScreen.attr & BMASK) >> 4)
| ((sScreen.attr & FMASK) << 4);
if (sScreen.bright_attr)
sScreen.attr |= 0x08;
break;
case 8:
break;
case 30:
case 31:
case 32:
case 33:
case 34:
case 35:
case 36:
case 37:
{
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;
}
case 40: sScreen.attr = (sScreen.attr & ~BMASK) | (0 << 4); break;
case 41: sScreen.attr = (sScreen.attr & ~BMASK) | (4 << 4); break;
case 42: sScreen.attr = (sScreen.attr & ~BMASK) | (2 << 4); break;
case 43: sScreen.attr = (sScreen.attr & ~BMASK) | (6 << 4); break;
case 44: sScreen.attr = (sScreen.attr & ~BMASK) | (1 << 4); break;
case 45: sScreen.attr = (sScreen.attr & ~BMASK) | (5 << 4); break;
case 46: sScreen.attr = (sScreen.attr & ~BMASK) | (3 << 4); break;
case 47: sScreen.attr = (sScreen.attr & ~BMASK) | (7 << 4); break;
}
}
}
static bool
process_vt100_command(const char c, bool seenBracket, int32 *args,
int32 argCount)
{
bool ret = true;
if (seenBracket) {
switch (c) {
case 'H':
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':
{
int32 deltaY = argCount > 0 ? -args[0] : -1;
if (deltaY == 0)
deltaY = -1;
move_cursor(sScreen.x, sScreen.y + deltaY);
break;
}
case 'e':
case 'B':
{
int32 deltaY = argCount > 0 ? args[0] : 1;
if (deltaY == 0)
deltaY = 1;
move_cursor(sScreen.x, sScreen.y + deltaY);
break;
}
case 'D':
{
int32 deltaX = argCount > 0 ? -args[0] : -1;
if (deltaX == 0)
deltaX = -1;
move_cursor(sScreen.x + deltaX, sScreen.y);
break;
}
case 'a':
case 'C':
{
int32 deltaX = argCount > 0 ? args[0] : 1;
if (deltaX == 0)
deltaX = 1;
move_cursor(sScreen.x + deltaX, sScreen.y);
break;
}
case '`':
case 'G':
{
int32 newX = argCount > 0 ? args[0] : 1;
if (newX > 0)
newX--;
move_cursor(newX, sScreen.y);
break;
}
case 'd':
{
int32 newY = argCount > 0 ? args[0] : 1;
if (newY > 0)
newY--;
move_cursor(sScreen.x, newY);
break;
}
#if 0
case 's':
save_cur(console, false);
break;
case 'u':
restore_cur(console, false);
break;
case 'r':
{
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':
{
int32 lines = argCount > 0 ? args[0] : 1;
while (lines > 0) {
scrdown(console);
lines--;
}
break;
}
case 'M':
{
int32 lines = argCount > 0 ? args[0] : 1;
while (lines > 0) {
scrup(console);
lines--;
}
break;
}
#endif
case 'K':
if (argCount == 0 || args[0] == 0) {
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_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:
switch (c) {
case '\n':
next_line();
break;
case 0x8:
back_space();
break;
case '\t':
sScreen.x = (sScreen.x + 8) & ~7;
if (sScreen.x >= sScreen.columns)
next_line();
break;
case '\r':
case '\0':
case '\a':
break;
case 0x1b:
sScreen.arg_count = 0;
sScreen.state = CONSOLE_STATE_GOT_ESCAPE;
break;
default:
put_character(c);
}
break;
case CONSOLE_STATE_GOT_ESCAPE:
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 '?':
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:
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;
}
status_t
blue_screen_init_early()
{
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) {
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;
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);
}