The console can now be rendered to any text buffer.

The console renderer now renders to a text buffer, which can be implemented
on any device, whether it's the VGA text buffer or a bitmap graphics device
with font rendering. This replaces the older code that could only render to
a VGA framebuffer and where the input parsing was tightly coupled with the
device rendering phase.
This commit is contained in:
Jonas 'Sortie' Termansen 2012-07-23 00:01:12 +02:00
parent 8c5ab54c9b
commit f5c4b64aff
11 changed files with 959 additions and 595 deletions

View File

@ -110,8 +110,10 @@ uart.o \
terminal.o \
linebuffer.o \
logterminal.o \
vgaterminal.o \
textterminal.o \
serialterminal.o \
textbuffer.o \
vgatextbuffer.o \
descriptors.o \
device.o \
refcount.o \

View File

@ -0,0 +1,99 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2012.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
textbuffer.h
Provides a indexable text buffer for used by text mode terminals.
*******************************************************************************/
#ifndef SORTIX_TEXTBUFFER_H
#define SORTIX_TEXTBUFFER_H
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/refcount.h>
namespace Sortix {
struct TextPos
{
TextPos() { }
TextPos(size_t x, size_t y) : x(x), y(y) { }
size_t x;
size_t y;
};
struct TextChar
{
TextChar() { }
TextChar(char c, uint8_t vgacolor) : c(c), vgacolor(vgacolor) { }
char c;
uint8_t vgacolor; // Format of <sortix/vga.h>
};
class TextBuffer
{
public:
virtual ~TextBuffer() { }
virtual size_t Width() const = 0;
virtual size_t Height() const = 0;
virtual TextChar GetChar(TextPos pos) const = 0;
virtual void SetChar(TextPos pos, TextChar c) = 0;
virtual uint16_t GetCharAttr(TextPos pos) const = 0;
virtual void SetCharAttr(TextPos pos, uint16_t attr) = 0;
virtual void Scroll(ssize_t off, TextChar fillwith) = 0;
virtual void Move(TextPos to, TextPos from, size_t numchars) = 0;
virtual void Fill(TextPos from, TextPos to, TextChar fillwith,
uint16_t fillattr) = 0;
virtual bool GetCursorEnabled() const = 0;
virtual void SetCursorEnabled(bool enablecursor) = 0;
virtual TextPos GetCursorPos() const = 0;
virtual void SetCursorPos(TextPos cursorpos) = 0;
};
// The purpose of this handle class is such that the terminal driver can have
// its backing storage replaced at runtime, for instance if the user changes
// the screen resolution or the graphics driver. The backing text buffer can
// only be changed when there are no references (but our own) to the text buffer
// so don't forget to release it when you are done.
class TextBufferHandle : public Refcounted
{
public:
TextBufferHandle(TextBuffer* textbuf = NULL, bool deletebuf = true,
TextBuffer* def = NULL, bool deletedef = true);
~TextBufferHandle();
TextBuffer* Acquire();
void Release(TextBuffer* textbuf);
void Replace(TextBuffer* newtextbuf, bool deletebuf = true);
private:
kthread_mutex_t mutex;
kthread_cond_t unusedcond;
TextBuffer* textbuf;
TextBuffer* def;
size_t numused;
bool deletedef;
bool deletebuf;
};
} // namespace Sortix
#endif

View File

@ -24,6 +24,9 @@
*******************************************************************************/
#include <sortix/kernel/platform.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/textbuffer.h>
#include <libmaxsi/memory.h>
#include <libmaxsi/string.h>
#include <libmaxsi/format.h>
@ -42,9 +45,10 @@
#include "pci.h"
#include "com.h"
#include "uart.h"
#include "vgatextbuffer.h"
#include "terminal.h"
#include "serialterminal.h"
#include "vgaterminal.h"
#include "textterminal.h"
#include "elf.h"
#include "initrd.h"
#include "vga.h"
@ -90,6 +94,11 @@ void DoWelcome()
Log::Print(" BOOTING OPERATING SYSTEM... ");
}
static size_t PrintToTextTerminal(void* user, const char* str, size_t len)
{
return ((TextTerminal*) user)->Print(str, len);
}
extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo)
{
// Initialize system calls.
@ -98,13 +107,19 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo)
// Detect and initialize any serial COM ports in the system.
COM::EarlyInit();
// Initialize the default terminal.
VGATerminal::Init();
Maxsi::Format::Callback logcallback = VGATerminal::Print;
void* logpointer = NULL;
// Setup a text buffer handle for use by the text terminal.
uint16_t* const VGAFB = (uint16_t*) 0xB8000;
const size_t VGA_WIDTH = 80;
const size_t VGA_HEIGHT = 25;
static uint16_t vga_attr_buffer[VGA_WIDTH*VGA_HEIGHT];
VGATextBuffer textbuf(VGAFB, vga_attr_buffer, VGA_WIDTH, VGA_HEIGHT);
TextBufferHandle textbufhandle(NULL, false, &textbuf, false);
// Initialize the kernel log.
Log::Init(logcallback, logpointer);
// Setup a text terminal instance.
TextTerminal textterm(&textbufhandle);
// Register the text terminal as the kernel log and initialize it.
Log::Init(PrintToTextTerminal, &textterm);
// Display the boot welcome screen.
DoWelcome();

View File

@ -29,7 +29,6 @@
#include "keyboard.h"
#include "uart.h"
#include "serialterminal.h"
#include "vgaterminal.h"
#include "scheduler.h"
using namespace Maxsi;
@ -144,7 +143,10 @@ namespace Sortix
size_t Print(void* /*user*/, const char* string, size_t stringlen)
{
#warning Echoing to the VGA terminal is broken
#if 0
if ( ECHO_TO_VGA ) { VGATerminal::Print(NULL, string, stringlen); }
#endif
if ( cursordisabled )
{
const char* msg = "\e[h";

83
sortix/textbuffer.cpp Normal file
View File

@ -0,0 +1,83 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2012.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
textbuffer.cpp
Provides a indexable text buffer for used by text mode terminals.
*******************************************************************************/
#include <sortix/kernel/platform.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/textbuffer.h>
namespace Sortix {
TextBufferHandle::TextBufferHandle(TextBuffer* textbuf, bool deletebuf,
TextBuffer* def, bool deletedef)
{
this->textbuf = textbuf;
this->deletebuf = deletebuf;
this->def = def;
this->deletedef = deletedef;
this->numused = 0;
this->mutex = KTHREAD_MUTEX_INITIALIZER;
this->unusedcond = KTHREAD_COND_INITIALIZER;
}
TextBufferHandle::~TextBufferHandle()
{
if ( deletebuf )
delete textbuf;
if ( deletedef )
delete def;
}
TextBuffer* TextBufferHandle::Acquire()
{
ScopedLock lock(&mutex);
numused++;
if ( textbuf )
return textbuf;
if ( !def )
numused--;
return def;
}
void TextBufferHandle::Release(TextBuffer* textbuf)
{
ASSERT(textbuf);
ScopedLock lock(&mutex);
ASSERT(numused);
if ( !--numused )
kthread_cond_signal(&unusedcond);
}
void TextBufferHandle::Replace(TextBuffer* newtextbuf, bool deletebuf)
{
ScopedLock lock(&mutex);
while ( numused )
kthread_cond_wait(&unusedcond, &mutex);
if ( deletebuf )
delete textbuf;
this->textbuf = newtextbuf;
this->deletebuf = deletebuf;
}
} // namespace Sortix

417
sortix/textterminal.cpp Normal file
View File

@ -0,0 +1,417 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
textterminal.cpp
Translates a character stream to a 2 dimensional array of character.
*******************************************************************************/
#include <sortix/kernel/platform.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/textbuffer.h>
#include <sortix/vga.h>
#include "textterminal.h"
namespace Sortix {
const uint16_t DEFAULT_COLOR = COLOR8_LIGHT_GREY << 0U | COLOR8_BLACK << 4U;
const uint16_t ATTR_CHAR = 1U << 0U;
TextTerminal::TextTerminal(TextBufferHandle* textbufhandle)
{
this->textbufhandle = textbufhandle; textbufhandle->Refer();
Reset();
}
void TextTerminal::Reset()
{
vgacolor = DEFAULT_COLOR;
column = line = 0;
ansisavedposx = ansisavedposy = 0;
ansimode = NONE;
TextBuffer* textbuf = textbufhandle->Acquire();
TextPos fillfrom(0, 0);
TextPos fillto(textbuf->Width()-1, textbuf->Height()-1);
TextChar fillwith(' ', vgacolor);
textbuf->Fill(fillfrom, fillto, fillwith, 0);
textbuf->SetCursorEnabled(true);
UpdateCursor(textbuf);
textbufhandle->Release(textbuf);
}
size_t TextTerminal::Print(const char* string, size_t stringlen)
{
TextBuffer* textbuf = textbufhandle->Acquire();
for ( size_t i = 0; i < stringlen; i++ )
PutChar(textbuf, string[i]);
UpdateCursor(textbuf);
textbufhandle->Release(textbuf);
return stringlen;
}
void TextTerminal::PutChar(TextBuffer* textbuf, char c)
{
if ( ansimode )
PutAnsiEscaped(textbuf, c);
else switch ( c )
{
case '\n': Newline(textbuf); break;
case '\r': column = 0; break;
case '\b': Backspace(textbuf); break;
case '\t': Tab(textbuf); break;
case '\e': AnsiReset(); break;
default:
{
if ( textbuf->Width() <= column )
Newline(textbuf);
TextPos pos(column++, line);
TextChar tc(c, vgacolor);
textbuf->SetChar(pos, tc);
textbuf->SetCharAttr(pos, ATTR_CHAR);
} break;
}
}
void TextTerminal::UpdateCursor(TextBuffer* textbuf)
{
textbuf->SetCursorPos(TextPos(column, line));
}
void TextTerminal::Newline(TextBuffer* textbuf)
{
textbuf->SetCharAttr(TextPos(column, line), ATTR_CHAR);
column = 0;
if ( line < textbuf->Height()-1 )
line++;
else
textbuf->Scroll(1, TextChar(' ', vgacolor)),
line = textbuf->Height()-1;
}
static TextPos DecrementTextPos(TextBuffer* textbuf, TextPos pos)
{
if ( !pos.x && !pos.y )
return pos;
if ( !pos.x )
return TextPos(textbuf->Width(), pos.y-1);
return TextPos(pos.x-1, pos.y);
}
void TextTerminal::Backspace(TextBuffer* textbuf)
{
TextPos pos(column, line);
while ( pos.x || pos.y )
{
pos = DecrementTextPos(textbuf, pos);
uint16_t attr = textbuf->GetCharAttr(pos);
textbuf->SetChar(pos, TextChar(' ', vgacolor));
textbuf->SetCharAttr(pos, attr & ~ATTR_CHAR);
if ( attr & ATTR_CHAR )
break;
}
column = pos.x;
line = pos.y;
}
void TextTerminal::Tab(TextBuffer* textbuf)
{
if ( column == textbuf->Width() )
Newline(textbuf);
// TODO: This does not work correctly if the text buffer width is not a
// multiple of four and the column is near the edge.
unsigned until = 4 - (column % 4);
textbuf->SetCharAttr(TextPos(column, line), ATTR_CHAR);
while ( (until--) != 0 )
textbuf->SetChar(TextPos(column++, line), TextChar(' ', vgacolor));
}
// TODO: This implementation of the 'Ansi Escape Codes' is incomplete and hacky.
void TextTerminal::AnsiReset()
{
ansiusedparams = 0;
currentparamindex = 0;
ansiparams[0] = 0;
paramundefined = true;
ignoresequence = false;
ansimode = CSI;
}
void TextTerminal::PutAnsiEscaped(TextBuffer* textbuf, char c)
{
// Check the proper prefixes are used.
if ( ansimode == CSI )
{
if ( c != '[' ) { ansimode = NONE; return; }
ansimode = COMMAND;
return;
}
// Read part of a parameter.
if ( '0' <= c && c <= '9' )
{
if ( paramundefined )
ansiusedparams++;
paramundefined = false;
unsigned val = c - '0';
ansiparams[currentparamindex] *= 10;
ansiparams[currentparamindex] += val;
}
// Parameter delimiter.
else if ( c == ';' )
{
if ( currentparamindex == ANSI_NUM_PARAMS - 1 )
{
ansimode = NONE;
return;
}
paramundefined = true;
ansiparams[++currentparamindex] = 0;
}
// Left for future standardization, so discard this sequence.
else if ( c == ':' )
{
ignoresequence = true;
}
// Run a command.
else if ( 64 <= c && c <= 126 )
{
if ( !ignoresequence )
RunAnsiCommand(textbuf, c);
}
// Something I don't understand, and ignore intentionally.
else if ( c == '?' )
{
}
// TODO: There are some rare things that should be supported here.
// Ignore unknown input.
else
{
ansimode = NONE;
}
}
void TextTerminal::RunAnsiCommand(TextBuffer* textbuf, char c)
{
const unsigned width = (unsigned) textbuf->Width();
const unsigned height = (unsigned) textbuf->Height();
switch ( c )
{
case 'A': // Cursor up
{
unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1;
if ( line < dist )
line = 0;
else
line -= dist;
} break;
case 'B': // Cursor down
{
unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1;
if ( height <= line + dist )
line = height-1;
else
line += dist;
} break;
case 'C': // Cursor forward
{
unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1;
if ( width <= column + dist )
column = width-1;
else
column += dist;
} break;
case 'D': // Cursor backward
{
unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1;
if ( column < dist )
column = 0;
else
column -= dist;
} break;
case 'E': // Move to beginning of line N lines down.
{
column = 0;
unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1;
if ( height <= line + dist )
line = height-1;
else
line += dist;
} break;
case 'F': // Move to beginning of line N lines up.
{
column = 0;
unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1;
if ( line < dist )
line = 0;
else
line -= dist;
} break;
case 'G': // Move the cursor to column N.
{
unsigned pos = 0 < ansiusedparams ? ansiparams[0]-1 : 0;
if ( width <= pos )
pos = width-1;
column = pos;
} break;
case 'H': // Move the cursor to line Y, column X.
case 'f':
{
unsigned posy = 0 < ansiusedparams ? ansiparams[0]-1 : 0;
unsigned posx = 1 < ansiusedparams ? ansiparams[1]-1 : 0;
if ( width <= posx )
posx = width-1;
if ( height <= posy )
posy = height-1;
column = posx;
line = posy;
} break;
case 'J': // Erase parts of the screen.
{
unsigned mode = 0 < ansiusedparams ? ansiparams[0] : 0;
TextPos from(0, 0);
TextPos to(0, 0);
if ( mode == 0 ) // From cursor to end.
from = TextPos{column, line},
to = TextPos{width-1, height-1};
if ( mode == 1 ) // From start to cursor.
from = TextPos{0, 0},
to = TextPos{column, line};
if ( mode == 2 ) // Everything.
from = TextPos{0, 0},
to = TextPos{width-1, height-1};
textbuf->Fill(from, to, TextChar(' ', vgacolor), 0);
} break;
case 'K': // Erase parts of the current line.
{
unsigned mode = 0 < ansiusedparams ? ansiparams[0] : 0;
TextPos from(0, 0);
TextPos to(0, 0);
if ( mode == 0 ) // From cursor to end.
from = TextPos{column, line},
to = TextPos{width-1, line};
if ( mode == 1 ) // From start to cursor.
from = TextPos{0, line},
to = TextPos{column, line};
if ( mode == 2 ) // Everything.
from = TextPos{0, line},
to = TextPos{width-1, line};
textbuf->Fill(from, to, TextChar(' ', vgacolor), 0);
} break;
case 'S': // Scroll a line up and place a new line at the buttom.
{
textbuf->Scroll(1, TextChar(' ', vgacolor));
line = height-1;
} break;
case 'T': // Scroll a line up and place a new line at the top.
{
textbuf->Scroll(-1, TextChar(' ', vgacolor));
line = 0;
} break;
case 'm': // Change how the text is rendered.
{
if ( ansiusedparams == 0 )
{
ansiparams[0] = 0;
ansiusedparams++;
}
// Convert from the ANSI color scheme to the VGA color scheme.
const unsigned conversion[8] =
{
COLOR8_BLACK, COLOR8_RED, COLOR8_GREEN, COLOR8_BROWN,
COLOR8_BLUE, COLOR8_MAGENTA, COLOR8_CYAN, COLOR8_LIGHT_GREY,
};
for ( size_t i = 0; i < ansiusedparams; i++ )
{
unsigned cmd = ansiparams[i];
// Turn all attributes off.
if ( cmd == 0 )
{
vgacolor = DEFAULT_COLOR;
}
// Set text color.
else if ( 30 <= cmd && cmd <= 37 )
{
unsigned val = cmd - 30;
vgacolor &= 0xF0;
vgacolor |= conversion[val] << 0;
}
// Set background color.
else if ( 40 <= cmd && cmd <= 47 )
{
unsigned val = cmd - 40;
vgacolor &= 0x0F;
vgacolor |= conversion[val] << 4;
}
// TODO: There are many other things we don't support.
}
} break;
case 'n': // Request special information from terminal.
{
// TODO: Handle this code.
} break;
case 's': // Save cursor position.
{
ansisavedposx = column;
ansisavedposx = line;
} break;
case 'u': // Restore cursor position.
{
column = ansisavedposx;
line = ansisavedposx;
if ( width <= column )
column = width-1;
if ( height <= line )
line = height-1;
} break;
case 'l': // Hide cursor.
{
// TODO: This is somehow related to the special char '?'.
if ( 0 < ansiusedparams && ansiparams[0] == 25 )
textbuf->SetCursorEnabled(false);
} break;
case 'h': // Show cursor.
{
// TODO: This is somehow related to the special char '?'.
if ( 0 < ansiusedparams && ansiparams[0] == 25 )
textbuf->SetCursorEnabled(true);
} break;
// TODO: Handle other cases.
}
ansimode = NONE;
}
} // namespace Sortix

69
sortix/textterminal.h Normal file
View File

@ -0,0 +1,69 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
textterminal.cpp
An indexable text buffer with the VGA text mode framebuffer as backend.
*******************************************************************************/
#ifndef SORTIX_TEXTTERMINAL_H
#define SORTIX_TEXTTERMINAL_H
namespace Sortix {
class TextBufferHandle;
class TextTerminal //: public Printable ?
{
public:
TextTerminal(TextBufferHandle* textbufhandle);
~TextTerminal();
size_t Print(const char* string, size_t stringlen);
private:
void PutChar(TextBuffer* textbuf, char c);
void UpdateCursor(TextBuffer* textbuf);
void Newline(TextBuffer* textbuf);
void Backspace(TextBuffer* textbuf);
void Tab(TextBuffer* textbuf);
void PutAnsiEscaped(TextBuffer* textbuf, char c);
void RunAnsiCommand(TextBuffer* textbuf, char c);
void AnsiReset();
void Reset();
private:
TextBufferHandle* textbufhandle;
uint8_t vgacolor;
unsigned column;
unsigned line;
unsigned ansisavedposx;
unsigned ansisavedposy;
enum { NONE = 0, CSI, COMMAND, } ansimode;
static const size_t ANSI_NUM_PARAMS = 16;
unsigned ansiusedparams;
unsigned ansiparams[ANSI_NUM_PARAMS];
unsigned currentparamindex;
bool paramundefined;
bool ignoresequence;
};
} // namespace Sortix
#endif

View File

@ -1,547 +0,0 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
vgaterminal.cpp
A terminal based on a text mode buffer.
*******************************************************************************/
#include <sortix/kernel/platform.h>
#include <sortix/kernel/log.h>
#include <sortix/vga.h>
#include <libmaxsi/memory.h>
#include "vga.h"
#include "vgaterminal.h"
using namespace Maxsi;
namespace Sortix {
namespace VGATerminal {
const unsigned width = 80;
const unsigned height = 25;
const uint16_t DEFAULT_COLOR = (COLOR8_LIGHT_GREY << 8) | (COLOR8_BLACK << 12);
uint16_t* const vga = (uint16_t* const) 0xB8000;
uint16_t vgaattr[width * height];
const uint16_t VGAATTR_CHAR = (1U<<0U);
unsigned line;
unsigned column;
uint16_t currentcolor;
unsigned ansisavedposx;
unsigned ansisavedposy;
bool showcursor;
enum
{
NONE,
CSI,
COMMAND,
} ansimode;
void UpdateCursor()
{
if ( showcursor )
VGA::SetCursor(column, line);
else
VGA::SetCursor(width, height-1);
}
// Clear the screen, put the cursor at the top left corner, set default
// text color, and reset ANSI escape sequence state.
void Reset()
{
ansimode = NONE;
line = 0;
column = 0;
ansisavedposx = 0;
ansisavedposy = 0;
currentcolor = DEFAULT_COLOR;
for ( nat y = 0; y < height; y++ )
{
for ( nat x = 0; x < width; x++ )
{
unsigned index = y * width + x;
vga[index] = ' ' | DEFAULT_COLOR;
vgaattr[index] = 0;
}
}
// Reset the VGA cursor.
showcursor = true;
UpdateCursor();
}
// Initialize the terminal driver.
void Init()
{
Reset();
}
// Move every line one row up and leaves an empty line at the bottom.
void ScrollUp()
{
for ( nat y = 1; y < height; y++ )
{
size_t linesize = width * sizeof(uint16_t);
size_t fromoff = (y-0) * width;
size_t destoff = (y-1) * width;
Memory::Copy(vga + destoff, vga + fromoff, linesize);
Memory::Copy(vgaattr + destoff, vgaattr + fromoff, linesize);
}
for ( nat x = 0; x < width; x++ )
{
unsigned index = (height-1) * width + x;
vga[index] = ' ' | currentcolor;
vgaattr[index] = 0;
}
}
// Move every line one row down and leaves an empty line at the top.
void ScrollDown()
{
for ( nat y = 1; y < height; y++ )
{
size_t linesize = width * sizeof(uint16_t);
size_t fromoff = (y-1) * width;
size_t destoff = (y-0) * width;
Memory::Copy(vga + destoff, vga + fromoff, linesize);
Memory::Copy(vgaattr + destoff, vgaattr + fromoff, linesize);
}
for ( nat x = 0; x < width; x++ )
{
unsigned index = x;
vga[index] = ' ' | currentcolor;
vgaattr[index] = 0;
}
}
// Move to the next line. If at bottom, scroll one line up.
void Newline()
{
vgaattr[line * width + column] |= VGAATTR_CHAR;
if ( line < height - 1 )
line++;
else
ScrollUp();
column = 0;
UpdateCursor();
}
void ANSIReset();
void ParseANSIEscape(char c);
// Print text to the vga framebuffer, simulating how a terminal would
// display the text.
size_t Print(void* /*user*/, const char* string, size_t stringlen)
{
// Iterate over each character.
size_t left = stringlen;
while ( (left--) > 0 )
{
char c = *(string++);
// If we are parsing an escape sequence, parse another char.
if ( ansimode != NONE ) { ParseANSIEscape(c); continue; }
switch ( c )
{
// Move cursor to the next line and scroll up (if needed).
case '\n':
{
Newline();
break;
}
// Move cursor to start of line.
case '\r':
{
column = 0;
break;
}
// Delete the previous character.
case '\b':
{
unsigned pos = line * width + column;
while ( pos )
{
unsigned nextpos = pos-1;
vga[nextpos] = ' ' | currentcolor;
pos = nextpos;
uint16_t attr = vgaattr[pos];
vgaattr[pos] = 0;
if ( attr & VGAATTR_CHAR ) { break; }
}
column = pos % width;
line = pos / width;
break;
}
// Expand a tab to a few spaces.
case '\t':
{
if ( column == width ) { Newline(); }
nat until = 4 - (column % 4);
vgaattr[line * width + (column)] |= VGAATTR_CHAR;
while ( (until--) != 0 )
{
vga[line * width + (column++)] = ' ' | currentcolor;
}
break;
}
// Initialize an ANSI escape sequence, allowing much more
// powerful control of the terminal than ASCII does.
case '\e':
{
ANSIReset();
break;
}
// Print this character at the cursor and increment the cursor.
default:
{
if ( column == width ) { Newline(); }
unsigned index = line * width + (column++);
vga[index] = c | currentcolor;
vgaattr[index] |= VGAATTR_CHAR;
break;
}
}
}
// Update the VGA hardware cursor.
UpdateCursor();
return stringlen;
}
const size_t ANSI_NUM_PARAMS = 16;
nat ansiusedparams;
nat ansiparams[ANSI_NUM_PARAMS];
nat currentparamindex;
bool paramundefined;
bool ignoresequence;
// TODO: The ANSI escape code is only partially implemented!
// Begin an ANSI esacpe sequence.
void ANSIReset()
{
if ( ansimode == NONE )
{
ansiusedparams = 0;
currentparamindex = 0;
ansiparams[0] = 0;
paramundefined = true;
ignoresequence = false;
ansimode = CSI;
}
}
// Reads parameters and changes font properties accordingly.
void AnsiSetFont()
{
// Convert from the ANSI color scheme to the VGA color scheme.
const unsigned conversion[8] =
{
COLOR8_BLACK, COLOR8_RED, COLOR8_GREEN, COLOR8_BROWN,
COLOR8_BLUE, COLOR8_MAGENTA, COLOR8_CYAN, COLOR8_LIGHT_GREY,
};
for ( size_t i = 0; i < ansiusedparams; i++ )
{
nat cmd = ansiparams[i];
// Turn all attributes off.
if ( cmd == 0 )
{
currentcolor = DEFAULT_COLOR;
}
// Set text color.
else if ( 30 <= cmd && cmd <= 37 )
{
nat val = cmd - 30;
currentcolor &= (0xF0FF);
currentcolor |= (conversion[val] << 8);
}
// Set background color.
else if ( 40 <= cmd && cmd <= 47 )
{
nat val = cmd - 40;
currentcolor &= (0x0FFF);
currentcolor |= (conversion[val] << 12);
}
// TODO: There are many other things we don't support.
}
}
// Executes an ASNI escape command based on given parameters.
void RunANSICommand(char c)
{
switch ( c )
{
// Cursor up
case 'A':
{
nat dist = ( 0 < ansiusedparams ) ? ansiparams[0] : 1;
if ( line < dist ) { line = 0; } else { line -= dist; }
break;
}
// Cursor down
case 'B':
{
nat dist = ( 0 < ansiusedparams ) ? ansiparams[0] : 1;
if ( height <= line + dist ) { line = height-1; } else { line += dist; }
break;
}
// Cursor forward
case 'C':
{
nat dist = ( 0 < ansiusedparams ) ? ansiparams[0] : 1;
if ( width <= column + dist ) { column = width-1; } else { column += dist; }
break;
}
// Cursor backward
case 'D':
{
nat dist = ( 0 < ansiusedparams ) ? ansiparams[0] : 1;
if ( column < dist ) { column = 0; } else { column -= dist; }
break;
}
// Move to beginning of line N lines down.
case 'E':
{
column = 0;
nat dist = ( 0 < ansiusedparams ) ? ansiparams[0] : 1;
if ( height <= line + dist ) { line = height-1; } else { line += dist; }
break;
}
// Move to beginning of line N lines up.
case 'F':
{
column = 0;
nat dist = ( 0 < ansiusedparams ) ? ansiparams[0] : 1;
if ( line < dist ) { line = 0; } else { line -= dist; }
break;
}
// Move the cursor to column N.
case 'G':
{
nat pos = ( 0 < ansiusedparams ) ? ansiparams[0]-1 : 0;
if ( width <= pos ) { pos = width-1; }
column = pos;
break;
}
// Move the cursor to line Y, column X.
case 'H':
case 'f':
{
nat posy = ( 0 < ansiusedparams ) ? ansiparams[0]-1 : 0;
nat posx = ( 1 < ansiusedparams ) ? ansiparams[1]-1 : 0;
if ( width <= posx ) { posx = width-1; }
if ( height <= posy ) { posy = height-1; }
column = posx;
line = posy;
break;
}
// Erase parts of the screen.
case 'J':
{
nat mode = ( 0 < ansiusedparams ) ? ansiparams[0] : 0;
nat from = 0;
nat to = 0;
// From cursor to end.
if ( mode == 0 ) { from = line*width + column; to = height*width - 1; }
// From start to cursor.
if ( mode == 1 ) { from = 0; to = line*width + column; }
// Everything.
if ( mode == 2 ) { from = 0; to = height*width - 1; }
for ( nat i = from; i <= to; i++ )
{
vga[i] = ' ' | currentcolor;
vgaattr[i] = 0;
}
break;
}
// Erase parts of the current line.
case 'K':
{
nat mode = ( 0 < ansiusedparams ) ? ansiparams[0] : 0;
nat from = 0;
nat to = 0;
// From cursor to end.
if ( mode == 0 ) { from = column; to = width - 1; }
// From start to cursor.
if ( mode == 1 ) { from = 0; to = column; }
// Everything.
if ( mode == 2 ) { from = 0; to = width - 1; }
for ( nat i = from; i <= to; i++ )
{
unsigned index = line * width + i;
vga[index] = ' ' | currentcolor;
vgaattr[index] = 0;
}
break;
}
// Scroll a line up and place a new line at the buttom.
case 'S':
{
ScrollUp(); line = height-1;
break;
}
// Scroll a line up and place a new line at the top.
case 'T':
{
ScrollDown(); line = 0;
break;
}
// Change how the text is rendered.
case 'm':
{
if ( ansiusedparams == 0 )
{
ansiparams[0] = 0;
ansiusedparams++;
}
AnsiSetFont();
break;
}
// Request special information from terminal.
case 'n':
{
// TODO: Handle this code.
break;
}
// Save cursor position.
case 's':
{
ansisavedposx = column;
ansisavedposx = line;
break;
}
// Restore cursor position.
case 'u':
{
column = ansisavedposx;
line = ansisavedposx;
if ( width <= column ) { column = width-1; }
if ( height <= line ) { line = height-1; }
break;
}
// Hide cursor.
case 'l':
{
// TODO: This is somehow related to the special char '?'.
if ( 0 < ansiusedparams && ansiparams[0] == 25 )
{
showcursor = false;
UpdateCursor();
}
break;
}
// Show cursor.
case 'h':
{
// TODO: This is somehow related to the special char '?'.
if ( 0 < ansiusedparams && ansiparams[0] == 25 )
{
showcursor = true;
UpdateCursor();
}
break;
}
// TODO: Handle other cases.
}
ansimode = NONE;
}
// Parse another char of an ASNI escape code.
void ParseANSIEscape(char c)
{
// Check the proper prefixes are used.
if ( ansimode == CSI )
{
if ( c != '[' ) { ansimode = NONE; return; }
ansimode = COMMAND;
}
// Parse parameters and the command.
else if ( ansimode == COMMAND )
{
// Read part of a parameter.
if ( '0' <= c && c <= '9' )
{
if ( paramundefined ) { ansiusedparams++; }
paramundefined = false;
nat val = c - '0';
ansiparams[currentparamindex] *= 10;
ansiparams[currentparamindex] += val;
}
// Parameter delimiter.
else if ( c == ';' )
{
if ( currentparamindex == ANSI_NUM_PARAMS - 1 ) { ansimode = NONE; return; }
paramundefined = true;
ansiparams[++currentparamindex] = 0;
}
// Left for future standardization, so discard this sequence.
else if ( c == ':' )
{
ignoresequence = true;
}
// Run a command.
else if ( 64 <= c && c <= 126 )
{
if ( !ignoresequence ) { RunANSICommand(c); }
}
// Something I don't understand, and ignore intentionally.
else if ( c == '?' )
{
}
// TODO: There are some rare things that should be supported here.
// Ignore unknown input.
else
{
ansimode = NONE;
}
}
}
} // namespace VGATerminal
} // namespace Sortix

View File

@ -1,39 +0,0 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
vgaterminal.cpp
A terminal based on a text mode buffer.
*******************************************************************************/
#ifndef SORTIX_VGATERMINAL_H
#define SORTIX_VGATERMINAL_H
namespace Sortix {
namespace VGATerminal {
void Init();
void Reset();
size_t Print(void* user, const char* string, size_t stringlen);
} // namespace VGATerminal
} // namespace Sortix
#endif

193
sortix/vgatextbuffer.cpp Normal file
View File

@ -0,0 +1,193 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2012.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
vgatextbuffer.cpp
An indexable text buffer with the VGA text mode framebuffer as backend.
*******************************************************************************/
#include <sortix/kernel/platform.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/refcount.h>
#include <sortix/kernel/textbuffer.h>
#include <sortix/vga.h>
#include "vga.h"
#include "vgatextbuffer.h"
namespace Sortix {
VGATextBuffer::VGATextBuffer(uint16_t* vga, uint16_t* attr,
size_t width, size_t height)
{
this->vga = vga;
this->attr = attr;
this->width = width;
this->height = height;
cursorpos = {0, 0};
cursorenabled = true;
UpdateCursor();
}
VGATextBuffer::~VGATextBuffer()
{
}
static TextChar EntryToTextChar(uint16_t entry)
{
char c = entry & 0x00FF;
uint8_t vgacolor = entry >> 8U;
return TextChar{c, vgacolor};
}
static uint16_t CharToTextEntry(TextChar c)
{
return (uint16_t) c.c | (uint16_t) c.vgacolor << 8U;
}
bool VGATextBuffer::UsablePosition(TextPos pos) const
{
return pos.x < width && pos.y < height;
}
TextPos VGATextBuffer::CropPosition(TextPos pos) const
{
if ( width <= pos.x )
pos.x = width - 1;
if ( height <= pos.y )
pos.y = height -1;
return pos;
}
size_t VGATextBuffer::OffsetOfPos(TextPos pos) const
{
return pos.y * width + pos.x;
}
size_t VGATextBuffer::Width() const
{
return width;
}
size_t VGATextBuffer::Height() const
{
return height;
}
TextChar VGATextBuffer::GetChar(TextPos pos) const
{
if ( UsablePosition(pos) )
return EntryToTextChar(vga[OffsetOfPos(pos)]);
return {0, 0};
}
void VGATextBuffer::SetChar(TextPos pos, TextChar c)
{
if ( UsablePosition(pos) )
vga[OffsetOfPos(pos)] = CharToTextEntry(c);
}
uint16_t VGATextBuffer::GetCharAttr(TextPos pos) const
{
if ( UsablePosition(pos) )
return attr[OffsetOfPos(pos)];
return 0;
}
void VGATextBuffer::SetCharAttr(TextPos pos, uint16_t attrval)
{
if ( UsablePosition(pos) )
attr[OffsetOfPos(pos)] = attrval;
}
void VGATextBuffer::Scroll(ssize_t off, TextChar fillwith)
{
if ( !off )
return;
bool neg = 0 < off;
size_t absoff = off < 0 ? -off : off;
if ( height < absoff )
absoff = height;
TextPos scrollfrom = neg ? TextPos{0, absoff} : TextPos{0, 0};
TextPos scrollto = neg ? TextPos{0, 0} : TextPos{0, absoff};
TextPos fillfrom = neg ? TextPos{0, height-absoff} : TextPos{0, 0};
TextPos fillto = neg ? TextPos{width-1, height-1} : TextPos{width-1, absoff-1};
size_t scrollchars = width * (height-absoff);
Move(scrollto, scrollfrom, scrollchars);
Fill(fillfrom, fillto, fillwith, 0);
}
void VGATextBuffer::Move(TextPos to, TextPos from, size_t numchars)
{
size_t dest = OffsetOfPos(CropPosition(to));
size_t src = OffsetOfPos(CropPosition(from));
if ( dest < src )
for ( size_t i = 0; i < numchars; i++ )
vga[dest + i] = vga[src + i],
attr[dest + i] = attr[src + i];
else if ( src < dest )
for ( size_t i = 0; i < numchars; i++ )
vga[dest + numchars-1 - i] = vga[src + numchars-1 - i],
attr[dest + numchars-1 - i] = attr[src + numchars-1 - i];
}
void VGATextBuffer::Fill(TextPos from, TextPos to, TextChar fillwith,
uint16_t fillattr)
{
from = CropPosition(from);
to = CropPosition(to);
size_t start = OffsetOfPos(from);
size_t end = OffsetOfPos(to);
size_t entry = CharToTextEntry(fillwith);
for ( size_t i = start; i <= end; i++ )
vga[i] = entry,
attr[i] = fillattr;
}
bool VGATextBuffer::GetCursorEnabled() const
{
return cursorenabled;
}
void VGATextBuffer::SetCursorEnabled(bool enablecursor)
{
cursorenabled = enablecursor;
UpdateCursor();
}
TextPos VGATextBuffer::GetCursorPos() const
{
return cursorpos;
}
void VGATextBuffer::SetCursorPos(TextPos cursorpos)
{
this->cursorpos = cursorpos;
UpdateCursor();
}
void VGATextBuffer::UpdateCursor()
{
if ( cursorenabled )
VGA::SetCursor(cursorpos.x, cursorpos.y);
else
VGA::SetCursor(width, height-1);
}
} // namespace Sortix

70
sortix/vgatextbuffer.h Normal file
View File

@ -0,0 +1,70 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2012.
This file is part of Sortix.
Sortix is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License along with
Sortix. If not, see <http://www.gnu.org/licenses/>.
vgatextbuffer.h
An indexable text buffer with the VGA text mode framebuffer as backend.
*******************************************************************************/
#ifndef SORTIX_VGATEXTBUFFER_H
#define SORTIX_VGATEXTBUFFER_H
#include <sortix/kernel/textbuffer.h>
namespace Sortix {
class VGATextBuffer : public TextBuffer
{
public:
VGATextBuffer(uint16_t* vga, uint16_t* attr, size_t width, size_t height);
virtual ~VGATextBuffer();
virtual size_t Width() const;
virtual size_t Height() const;
virtual TextChar GetChar(TextPos pos) const;
virtual void SetChar(TextPos pos, TextChar c);
virtual uint16_t GetCharAttr(TextPos pos) const ;
virtual void SetCharAttr(TextPos pos, uint16_t attrval);
virtual void Scroll(ssize_t off, TextChar fillwith);
virtual void Move(TextPos to, TextPos from, size_t numchars);
virtual void Fill(TextPos from, TextPos to, TextChar fillwith,
uint16_t fillattr);
virtual bool GetCursorEnabled() const;
virtual void SetCursorEnabled(bool enablecursor);
virtual TextPos GetCursorPos() const;
virtual void SetCursorPos(TextPos cursorpos);
private:
bool UsablePosition(TextPos pos) const;
TextPos CropPosition(TextPos pos) const;
size_t OffsetOfPos(TextPos pos) const;
void UpdateCursor();
private:
uint16_t* vga;
uint16_t* attr;
size_t width;
size_t height;
TextPos cursorpos;
bool cursorenabled;
};
} // namespace Sortix
#endif