⛏️ index : haiku.git

/*
 * Console.cpp - Mimicing the console driver
 * Based on the console driver.
 *
 * Copyright 2005 Michael Lotz. All rights reserved.
 * Distributed under the MIT License.
 *
 * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
 * 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 "Console.h"
#include "ViewBuffer.h"
#include <stdio.h>

Console::Console(ViewBuffer *output)
	:	fState(CONSOLE_STATE_NORMAL),
		fOutput(output)
{
	fOutput->GetSize(&fColumns, &fLines);
	fOutput->SetResizeCallback(&ResizeCallback, this);
	ResetConsole();
	GotoXY(0, 0);
	SaveCursor(true);
	fOutput->Clear(0x0f);
}


Console::~Console()
{
}


void
Console::ResetConsole()
{
	fAttr = 0x0f;
	fScrollTop = 0;
	fScrollBottom = fLines - 1;
	fBrightAttr = true;
	fReverseAttr = false;
}


void
Console::ResizeCallback(int32 width, int32 height, void *data)
{
	Console *console = (Console *)data;
	
	console->fColumns = width;
	console->fLines = height;
	console->SetScrollRegion(console->fScrollTop, height - 1);
}


void
Console::SetScrollRegion(int top, int bottom)
{
	if (top < 0)
		top = 0;
	if (bottom >= fLines)
		bottom = fLines - 1;
	if (top > bottom)
		return;
	
	fScrollTop = top;
	fScrollBottom = bottom;
}


void
Console::ScrollUp()
{
	// see if cursor is outside of scroll region
	if (fY < fScrollTop || fY > fScrollBottom)
		return;
	
	if (fY - fScrollTop > 1) {
		// move the screen up one
		fOutput->Blit(0, fScrollTop + 1, fColumns, fY - fScrollTop, 0, fScrollTop);
	}
	
	// clear the bottom line
	fOutput->FillGlyph(0, fY, fColumns, 1, ' ', fAttr);
}


void
Console::ScrollDown()
{
	// see if cursor is outside of scroll region
	if (fY < fScrollTop || fY > fScrollBottom)
		return;
	
	if (fScrollBottom - fY > 1) {
		// move the screen down one
		fOutput->Blit(0, fY, fColumns, fScrollBottom - fY, 0, fY + 1);
	}
	
	// clear the top line
	fOutput->FillGlyph(0, fY, fColumns, 1, ' ', fAttr);
}


void
Console::LineFeed()
{
	if (fY == fScrollBottom) {
 		// we hit the bottom of our scroll region
 		ScrollUp();
	} else if (fY < fScrollBottom) {
		fY++;
	}
}


void
Console::RLineFeed()
{
	if (fY == fScrollTop) {
 		// we hit the top of our scroll region
 		ScrollDown();
	} else if (fY > fScrollTop) {
		fY--;
	}
}


void
Console::CariageReturn()
{
	fX = 0;
}


void
Console::Delete()
{
	if (fX > 0) {
		fX--;
	} else if (fY > 0) {
        fY--;
        fX = fColumns - 1;
    } else {
        ScrollDown();
        fY--;
        fX = fColumns - 1;
        return;
    }
	
	fOutput->PutGlyph(fX, fY, ' ', fAttr);
}


void
Console::Tab()
{
	fX = (fX + TAB_SIZE) & ~TAB_MASK;
	if (fX >= fColumns) {
		fX -= fColumns;
		LineFeed();
	}
}


void
Console::EraseLine(erase_line_mode mode)
{
	switch (mode) {
		case LINE_ERASE_WHOLE:
			fOutput->FillGlyph(0, fY, fColumns, 1, ' ', fAttr);
			break;
		case LINE_ERASE_LEFT:
			fOutput->FillGlyph(0, fY, fX + 1, 1, ' ', fAttr);
			break;
		case LINE_ERASE_RIGHT:
			fOutput->FillGlyph(fX, fY, fColumns - fX, 1, ' ', fAttr);
			break;
		default:
			return;
	}
}


void
Console::EraseScreen(erase_screen_mode mode)
{
	switch (mode) {
		case SCREEN_ERASE_WHOLE:
			fOutput->Clear(fAttr);
			break;
		case SCREEN_ERASE_UP:
			fOutput->FillGlyph(0, 0, fColumns, fY + 1, ' ', fAttr);
			break;
		case SCREEN_ERASE_DOWN:
			fOutput->FillGlyph(fY, 0, fColumns, fLines - fY, ' ', fAttr);
			break;
		default:
			return;
	}
}


void
Console::SaveCursor(bool save_attrs)
{
	fSavedX = fX;
	fSavedY = fY;
	
	if (save_attrs)
		fSavedAttr = fAttr;
}


void
Console::RestoreCursor(bool restore_attrs)
{
	fX = fSavedX;
	fY = fSavedY;
	
	if (restore_attrs)
		fAttr = fSavedAttr;
}


void
Console::UpdateCursor(int x, int y)
{
	fOutput->MoveCursor(x, y);
}


void
Console::GotoXY(int new_x, int new_y)
{
	if (new_x >= fColumns)
		new_x = fColumns - 1;
	if (new_x < 0)
		new_x = 0;
	if (new_y >= fLines)
		new_y = fLines - 1;
	if (new_y < 0)
		new_y = 0;
	
	fX = new_x;
	fY = new_y;
}


void
Console::PutChar(const char c)
{
	fOutput->PutGlyph(fX, fY, c, fAttr);
	if (++fX >= fColumns) {
		CariageReturn();
		LineFeed();
	}
}


void
Console::SetVT100Attributes(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
				fAttr = 0x0f;
				fBrightAttr = true;
				fReverseAttr = false;
				break;
			case 1: // bright
				fBrightAttr = true;
				fAttr |= 0x08; // set the bright bit
				break;
			case 2: // dim
				fBrightAttr = false;
				fAttr &= ~0x08; // unset the bright bit
				break;
			case 4: // underscore we can't do
				break;
			case 5: // blink
				fAttr |= 0x80; // set the blink bit
				break;
			case 7: // reverse
				fReverseAttr = true;
				fAttr = ((fAttr & BMASK) >> 4) | ((fAttr & FMASK) << 4);
				if (fBrightAttr)
					fAttr |= 0x08;
				break;
			case 8: // hidden?
				break;
			
			/* foreground colors */
			case 30: fAttr = (fAttr & ~FMASK) | 0 | (fBrightAttr ? 0x08 : 0); break; // black
			case 31: fAttr = (fAttr & ~FMASK) | 4 | (fBrightAttr ? 0x08 : 0); break; // red
			case 32: fAttr = (fAttr & ~FMASK) | 2 | (fBrightAttr ? 0x08 : 0); break; // green
			case 33: fAttr = (fAttr & ~FMASK) | 6 | (fBrightAttr ? 0x08 : 0); break; // yellow
			case 34: fAttr = (fAttr & ~FMASK) | 1 | (fBrightAttr ? 0x08 : 0); break; // blue
			case 35: fAttr = (fAttr & ~FMASK) | 5 | (fBrightAttr ? 0x08 : 0); break; // magenta
			case 36: fAttr = (fAttr & ~FMASK) | 3 | (fBrightAttr ? 0x08 : 0); break; // cyan
			case 37: fAttr = (fAttr & ~FMASK) | 7 | (fBrightAttr ? 0x08 : 0); break; // white
			
			/* background colors */
			case 40: fAttr = (fAttr & ~BMASK) | (0 << 4); break; // black
			case 41: fAttr = (fAttr & ~BMASK) | (4 << 4); break; // red
			case 42: fAttr = (fAttr & ~BMASK) | (2 << 4); break; // green
			case 43: fAttr = (fAttr & ~BMASK) | (6 << 4); break; // yellow
			case 44: fAttr = (fAttr & ~BMASK) | (1 << 4); break; // blue
			case 45: fAttr = (fAttr & ~BMASK) | (5 << 4); break; // magenta
			case 46: fAttr = (fAttr & ~BMASK) | (3 << 4); break; // cyan
			case 47: fAttr = (fAttr & ~BMASK) | (7 << 4); break; // white
		}
	}
}


bool
Console::ProcessVT100Command(const char c, bool seen_bracket, int32 *args, int32 argCount)
{
	bool ret = true;
	
	if (seen_bracket) {
		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--;
				GotoXY(col, row);
				break;
			}
			case 'A': { /* move up */
				int32 deltay = argCount > 0 ? -args[0] : -1;
				if (deltay == 0)
					deltay = -1;
				GotoXY(fX, fY + deltay);
				break;
			}
			case 'e':
			case 'B': { /* move down */
				int32 deltay = argCount > 0 ? args[0] : 1;
				if (deltay == 0)
					deltay = 1;
				GotoXY(fX, fY + deltay);
				break;
			}
			case 'D': { /* move left */
				int32 deltax = argCount > 0 ? -args[0] : -1;
				if (deltax == 0)
					deltax = -1;
				GotoXY(fX + deltax, fY);
				break;
			}
			case 'a':
			case 'C': { /* move right */
				int32 deltax = argCount > 0 ? args[0] : 1;
				if (deltax == 0)
					deltax = 1;
				GotoXY(fX + deltax, fY);
				break;
			}
			case '`':
			case 'G': { /* set X position */
				int32 newx = argCount > 0 ? args[0] : 1;
				if (newx > 0)
					newx--;
				GotoXY(newx, fY);
				break;
			}
			case 'd': { /* set y position */
				int32 newy = argCount > 0 ? args[0] : 1;
				if (newy > 0)
					newy--;
				GotoXY(fX, newy);
				break;
			}
			case 's': /* save current cursor */
				SaveCursor(false);
				break;
			case 'u': /* restore cursor */
				RestoreCursor(false);
				break;
			case 'r': { /* set scroll region */
				int32 low = argCount > 0 ? args[0] : 1;
				int32 high = argCount > 1 ? args[1] : fLines;
				if (low <= high)
					SetScrollRegion(low - 1, high - 1);
				break;
			}
			case 'L': { /* scroll virtual down at cursor */
				int32 lines = argCount > 0 ? args[0] : 1;
				while (lines > 0) {
					ScrollDown();
					lines--;
				}
				break;
			}
			case 'M': { /* scroll virtual up at cursor */
				int32 lines = argCount > 0 ? args[0] : 1;
				while (lines > 0) {
					ScrollUp();
					lines--;
				}
				break;
			}
			case 'K':
				if (argCount == 0 || args[0] == 0) {
					// erase to end of line
					EraseLine(LINE_ERASE_RIGHT);
				} else if (argCount > 0) {
					if (args[0] == 1)
						EraseLine(LINE_ERASE_LEFT);
					else if (args[0] == 2)
						EraseLine(LINE_ERASE_WHOLE);
				}
				break;
			case 'J':
				if (argCount == 0 || args[0] == 0) {
					// erase to end of screen
					EraseScreen(SCREEN_ERASE_DOWN);
				} else if (argCount > 0) {
					if (args[0] == 1)
						EraseScreen(SCREEN_ERASE_UP);
					else if (args[0] == 2)
						EraseScreen(SCREEN_ERASE_WHOLE);
				}
				break;
			case 'm':
				if (argCount >= 0)
					SetVT100Attributes(args, argCount);
				break;
			default:
				ret = false;
		}
	} else {
		switch (c) {
			case 'c':
				ResetConsole();
				break;
			case 'D':
				RLineFeed();
				break;
			case 'M':
				LineFeed();
				break;
			case '7':
				SaveCursor(true);
				break;
			case '8':
				RestoreCursor(true);
				break;
			default:
				ret = false;
		}
	}
	
	return ret;
}


void
Console::Write(const void *buf, size_t len)
{
	UpdateCursor(-1, -1); // hide the cursor
	
	const char *c;
	size_t pos = 0;
	
	while (pos < len) {
		c = &((const char *)buf)[pos++];
		
		switch (fState) {
			case CONSOLE_STATE_NORMAL:
				// just output the stuff
				switch (*c) {
					case '\n':
						LineFeed();
						break;
					case '\r':
						CariageReturn();
						break;
					case 0x8: // backspace
						Delete();
						break;
					case '\t':
						Tab();
						break;
					case '\a':
						// beep
						//printf("<BEEP>\n");
						break;
					case '\0':
						break;
					case 0x1b:
						// escape character
						fArgCount = -1;
						fState = CONSOLE_STATE_GOT_ESCAPE;
						break;
					default:
						PutChar(*c);
				}
				break;
			case CONSOLE_STATE_GOT_ESCAPE:
				// look for either commands with no argument, or the '[' character
				switch (*c) {
					case '[':
						fState = CONSOLE_STATE_SEEN_BRACKET;
						break;
					default:
						fArgs[fArgCount] = 0;
						ProcessVT100Command(*c, false, fArgs, fArgCount + 1);
						fState = CONSOLE_STATE_NORMAL;
				}
				break;
			case CONSOLE_STATE_SEEN_BRACKET:
				switch (*c) {
					case '0'...'9':
						fArgCount = 0;
						fArgs[fArgCount] = *c - '0';
						fState = CONSOLE_STATE_PARSING_ARG;
						break;
					case '?':
						// private DEC mode parameter follows - we ignore those anyway
						// ToDo: check if it was really used in combination with a mode command
						break;
					default:
						ProcessVT100Command(*c, true, fArgs, fArgCount + 1);
						fState = CONSOLE_STATE_NORMAL;
				}
				break;
			case CONSOLE_STATE_NEW_ARG:
				switch (*c) {
					case '0'...'9':
						fArgCount++;
						if (fArgCount == MAX_ARGS) {
							fState = CONSOLE_STATE_NORMAL;
							break;
						}
						fArgs[fArgCount] = *c - '0';
						fState = CONSOLE_STATE_PARSING_ARG;
						break;
					default:
						ProcessVT100Command(*c, true, fArgs, fArgCount + 1);
						fState = CONSOLE_STATE_NORMAL;
				}
				break;
			case CONSOLE_STATE_PARSING_ARG:
				// parse args
				switch (*c) {
					case '0'...'9':
						fArgs[fArgCount] *= 10;
						fArgs[fArgCount] += *c - '0';
						break;
					case ';':
						fState = CONSOLE_STATE_NEW_ARG;
						break;
					default:
						ProcessVT100Command(*c, true, fArgs, fArgCount + 1);
						fState = CONSOLE_STATE_NORMAL;
				}
			}
	}
	
	UpdateCursor(fX, fY); // show it again
}