With these escapes supported, include definitions for line deletion (dl1, dl) and insertion (il1, il) in terminfo for "sortix".
920 lines
23 KiB
C++
920 lines
23 KiB
C++
/*
|
||
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
|
||
* Copyright (c) 2023 Juhani 'nortti' Krekelä.
|
||
*
|
||
* Permission to use, copy, modify, and distribute this software for any
|
||
* purpose with or without fee is hereby granted, provided that the above
|
||
* copyright notice and this permission notice appear in all copies.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
*
|
||
* textterminal.cpp
|
||
* Translates a character stream to a 2 dimensional array of characters.
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <wchar.h>
|
||
|
||
#include <sortix/vga.h>
|
||
|
||
#include <sortix/kernel/kernel.h>
|
||
#include <sortix/kernel/refcount.h>
|
||
#include <sortix/kernel/textbuffer.h>
|
||
|
||
#include "palette.h"
|
||
#include "textterminal.h"
|
||
|
||
namespace Sortix {
|
||
|
||
static const uint16_t DEFAULT_VGACOLOR = COLOR8_LIGHT_GREY | COLOR8_BLACK << 4;
|
||
static const unsigned int DEFAULT_FOREGROUND = 7;
|
||
static const unsigned int DEFAULT_BACKGROUND = 0;
|
||
|
||
static uint32_t ColorFromRGB(uint8_t r, uint8_t g, uint8_t b)
|
||
{
|
||
return (uint32_t) b << 0 | (uint32_t) g << 8 | (uint32_t) r << 16;
|
||
}
|
||
|
||
TextTerminal::TextTerminal(TextBufferHandle* textbufhandle)
|
||
{
|
||
memset(&ps, 0, sizeof(ps));
|
||
this->textbufhandle = textbufhandle;
|
||
this->termlock = KTHREAD_MUTEX_INITIALIZER;
|
||
Reset();
|
||
}
|
||
|
||
TextTerminal::~TextTerminal()
|
||
{
|
||
}
|
||
|
||
void TextTerminal::Reset()
|
||
{
|
||
attr = 0;
|
||
next_attr = 0;
|
||
vgacolor = DEFAULT_VGACOLOR;
|
||
fgcolor = palette[DEFAULT_FOREGROUND];
|
||
bgcolor = palette[DEFAULT_BACKGROUND];
|
||
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, 0, fgcolor, bgcolor);
|
||
textbuf->Fill(fillfrom, fillto, fillwith);
|
||
textbuf->SetCursorEnabled(true);
|
||
UpdateCursor(textbuf);
|
||
textbufhandle->Release(textbuf);
|
||
}
|
||
|
||
size_t TextTerminal::Print(const char* string, size_t stringlen)
|
||
{
|
||
ScopedLock lock(&termlock);
|
||
TextBuffer* textbuf = textbufhandle->Acquire();
|
||
for ( size_t i = 0; i < stringlen; i++ )
|
||
{
|
||
if ( string[i] == '\n' )
|
||
PutChar(textbuf, '\r');
|
||
PutChar(textbuf, string[i]);
|
||
}
|
||
UpdateCursor(textbuf);
|
||
textbufhandle->Release(textbuf);
|
||
return stringlen;
|
||
}
|
||
|
||
size_t TextTerminal::PrintRaw(const char* string, size_t stringlen)
|
||
{
|
||
ScopedLock lock(&termlock);
|
||
TextBuffer* textbuf = textbufhandle->Acquire();
|
||
for ( size_t i = 0; i < stringlen; i++ )
|
||
PutChar(textbuf, string[i]);
|
||
UpdateCursor(textbuf);
|
||
textbufhandle->Release(textbuf);
|
||
return stringlen;
|
||
}
|
||
|
||
size_t TextTerminal::Width()
|
||
{
|
||
ScopedLock lock(&termlock);
|
||
TextBuffer* textbuf = textbufhandle->Acquire();
|
||
size_t width = textbuf->Width();
|
||
textbufhandle->Release(textbuf);
|
||
return width;
|
||
}
|
||
|
||
size_t TextTerminal::Height()
|
||
{
|
||
ScopedLock lock(&termlock);
|
||
TextBuffer* textbuf = textbufhandle->Acquire();
|
||
size_t height = textbuf->Height();
|
||
textbufhandle->Release(textbuf);
|
||
return height;
|
||
}
|
||
|
||
void TextTerminal::GetCursor(size_t* column, size_t* row)
|
||
{
|
||
ScopedLock lock(&termlock);
|
||
*column = this->column;
|
||
*row = this->line;
|
||
}
|
||
|
||
bool TextTerminal::Sync()
|
||
{
|
||
// Reading something from the textbuffer may cause it to block while
|
||
// finishing rendering, effectively synchronizing with it.
|
||
ScopedLock lock(&termlock);
|
||
TextBuffer* textbuf = textbufhandle->Acquire();
|
||
textbuf->GetCursorPos();
|
||
textbufhandle->Release(textbuf);
|
||
return true;
|
||
}
|
||
|
||
bool TextTerminal::Invalidate()
|
||
{
|
||
ScopedLock lock(&termlock);
|
||
TextBuffer* textbuf = textbufhandle->Acquire();
|
||
textbuf->Invalidate();
|
||
textbufhandle->Release(textbuf);
|
||
return true;
|
||
}
|
||
|
||
void TextTerminal::BeginReplace()
|
||
{
|
||
kthread_mutex_lock(&termlock);
|
||
textbufhandle->BeginReplace();
|
||
}
|
||
|
||
void TextTerminal::CancelReplace()
|
||
{
|
||
textbufhandle->CancelReplace();
|
||
kthread_mutex_unlock(&termlock);
|
||
}
|
||
|
||
void TextTerminal::FinishReplace(TextBuffer* new_textbuf)
|
||
{
|
||
textbufhandle->FinishReplace(new_textbuf);
|
||
TextBuffer* textbuf = textbufhandle->Acquire();
|
||
size_t new_width = textbuf->Width();
|
||
size_t new_height = textbuf->Height();
|
||
textbufhandle->Release(textbuf);
|
||
if ( new_width < column )
|
||
column = new_width;
|
||
if ( new_height <= line )
|
||
line = new_height ? new_height - 1 : 0;
|
||
kthread_mutex_unlock(&termlock);
|
||
}
|
||
|
||
bool TextTerminal::EmergencyIsImpaired()
|
||
{
|
||
// This is during a kernel emergency where preemption has been disabled and
|
||
// this is the only thread running.
|
||
|
||
if ( !kthread_mutex_trylock(&termlock) )
|
||
return true;
|
||
kthread_mutex_unlock(&termlock);
|
||
|
||
if ( textbufhandle->EmergencyIsImpaired() )
|
||
return true;
|
||
|
||
TextBuffer* textbuf = textbufhandle->EmergencyAcquire();
|
||
bool textbuf_was_impaired = textbuf->EmergencyIsImpaired();
|
||
textbufhandle->EmergencyRelease(textbuf);
|
||
if ( textbuf_was_impaired )
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
bool TextTerminal::EmergencyRecoup()
|
||
{
|
||
// This is during a kernel emergency where preemption has been disabled and
|
||
// this is the only thread running.
|
||
|
||
if ( !kthread_mutex_trylock(&termlock) )
|
||
return false;
|
||
kthread_mutex_unlock(&termlock);
|
||
|
||
if ( textbufhandle->EmergencyIsImpaired() &&
|
||
!textbufhandle->EmergencyRecoup() )
|
||
return false;
|
||
|
||
TextBuffer* textbuf = textbufhandle->EmergencyAcquire();
|
||
bool textbuf_failure = textbuf->EmergencyIsImpaired() &&
|
||
!textbuf->EmergencyRecoup();
|
||
textbufhandle->EmergencyRelease(textbuf);
|
||
|
||
if ( !textbuf_failure )
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
void TextTerminal::EmergencyReset()
|
||
{
|
||
// This is during a kernel emergency where preemption has been disabled and
|
||
// this is the only thread running.
|
||
|
||
textbufhandle->EmergencyReset();
|
||
|
||
TextBuffer* textbuf = textbufhandle->EmergencyAcquire();
|
||
textbuf->EmergencyReset();
|
||
textbufhandle->EmergencyRelease(textbuf);
|
||
|
||
this->termlock = KTHREAD_MUTEX_INITIALIZER;
|
||
Reset();
|
||
}
|
||
|
||
size_t TextTerminal::EmergencyPrint(const char* string, size_t stringlen)
|
||
{
|
||
// This is during a kernel emergency where preemption has been disabled and
|
||
// this is the only thread running. Another thread may have been interrupted
|
||
// while it held the terminal lock. The best case is if the terminal lock is
|
||
// currently unused, which would mean everything is safe.
|
||
|
||
TextBuffer* textbuf = textbufhandle->EmergencyAcquire();
|
||
for ( size_t i = 0; i < stringlen; i++ )
|
||
{
|
||
if ( string[i] == '\n' )
|
||
PutChar(textbuf, '\r');
|
||
PutChar(textbuf, string[i]);
|
||
}
|
||
UpdateCursor(textbuf);
|
||
textbufhandle->EmergencyRelease(textbuf);
|
||
return stringlen;
|
||
}
|
||
|
||
size_t TextTerminal::EmergencyPrintRaw(const char* string, size_t stringlen)
|
||
{
|
||
// This is during a kernel emergency where preemption has been disabled and
|
||
// this is the only thread running. Another thread may have been interrupted
|
||
// while it held the terminal lock. The best case is if the terminal lock is
|
||
// currently unused, which would mean everything is safe.
|
||
|
||
TextBuffer* textbuf = textbufhandle->EmergencyAcquire();
|
||
for ( size_t i = 0; i < stringlen; i++ )
|
||
PutChar(textbuf, string[i]);
|
||
UpdateCursor(textbuf);
|
||
textbufhandle->EmergencyRelease(textbuf);
|
||
return stringlen;
|
||
}
|
||
|
||
size_t TextTerminal::EmergencyWidth()
|
||
{
|
||
// This is during a kernel emergency where preemption has been disabled and
|
||
// this is the only thread running. Another thread may have been interrupted
|
||
// while it held the terminal lock. The best case is if the terminal lock is
|
||
// currently unused, which would mean everything is safe.
|
||
|
||
TextBuffer* textbuf = textbufhandle->EmergencyAcquire();
|
||
size_t width = textbuf->Width();
|
||
textbufhandle->EmergencyRelease(textbuf);
|
||
return width;
|
||
}
|
||
|
||
size_t TextTerminal::EmergencyHeight()
|
||
{
|
||
// This is during a kernel emergency where preemption has been disabled and
|
||
// this is the only thread running. Another thread may have been interrupted
|
||
// while it held the terminal lock. The best case is if the terminal lock is
|
||
// currently unused, which would mean everything is safe.
|
||
|
||
TextBuffer* textbuf = textbufhandle->EmergencyAcquire();
|
||
size_t height = textbuf->Height();
|
||
textbufhandle->EmergencyRelease(textbuf);
|
||
return height;
|
||
}
|
||
|
||
void TextTerminal::EmergencyGetCursor(size_t* column, size_t* row)
|
||
{
|
||
// This is during a kernel emergency where preemption has been disabled and
|
||
// this is the only thread running. Another thread may have been interrupted
|
||
// while it held the terminal lock. The best case is if the terminal lock is
|
||
// currently unused, which would mean everything is safe.
|
||
|
||
*column = this->column;
|
||
*row = this->line;
|
||
}
|
||
|
||
bool TextTerminal::EmergencySync()
|
||
{
|
||
// This is during a kernel emergency where preemption has been disabled and
|
||
// this is the only thread running. There is no need to synchronize the
|
||
// text buffer here as there is no background thread rendering the console.
|
||
|
||
return true;
|
||
}
|
||
|
||
void TextTerminal::PutChar(TextBuffer* textbuf, char c)
|
||
{
|
||
if ( ansimode )
|
||
return PutAnsiEscaped(textbuf, c);
|
||
|
||
if ( mbsinit(&ps) )
|
||
{
|
||
switch ( c )
|
||
{
|
||
case '\a': return;
|
||
case '\n': Newline(textbuf); return;
|
||
case '\r': column = 0; return;
|
||
case '\b': Backspace(textbuf); return;
|
||
case '\t': Tab(textbuf); return;
|
||
case '\e': AnsiReset(); return;
|
||
case 127: return;
|
||
default: break;
|
||
}
|
||
}
|
||
|
||
wchar_t wc;
|
||
size_t result = mbrtowc(&wc, &c, 1, &ps);
|
||
if ( result == (size_t) -2 )
|
||
return;
|
||
if ( result == (size_t) -1 )
|
||
{
|
||
memset(&ps, 0, sizeof(ps));
|
||
wc = L'<EFBFBD>';
|
||
}
|
||
if ( result == (size_t) 0 )
|
||
wc = L' ';
|
||
|
||
if ( textbuf->Width() <= column )
|
||
{
|
||
column = 0;
|
||
Newline(textbuf);
|
||
}
|
||
TextPos pos(column++, line);
|
||
uint16_t tcvgacolor;
|
||
uint16_t tcattr = attr | next_attr;
|
||
uint32_t tcfgcolor;
|
||
uint32_t tcbgcolor;
|
||
if ( !(tcattr & ATTR_INVERSE) )
|
||
{
|
||
tcvgacolor = vgacolor;
|
||
tcfgcolor = fgcolor;
|
||
tcbgcolor = bgcolor;
|
||
}
|
||
else
|
||
{
|
||
tcvgacolor = (vgacolor >> 4 & 0xF) << 0 | (vgacolor >> 0 & 0xF) << 4;
|
||
tcfgcolor = bgcolor;
|
||
tcbgcolor = fgcolor;
|
||
}
|
||
TextChar tc(wc, tcvgacolor, tcattr, tcfgcolor, tcbgcolor);
|
||
textbuf->SetChar(pos, tc);
|
||
next_attr = 0;
|
||
}
|
||
|
||
void TextTerminal::UpdateCursor(TextBuffer* textbuf)
|
||
{
|
||
textbuf->SetCursorPos(TextPos(column, line));
|
||
}
|
||
|
||
void TextTerminal::Newline(TextBuffer* textbuf)
|
||
{
|
||
TextPos pos(column, line);
|
||
if ( line < textbuf->Height()-1 )
|
||
line++;
|
||
else
|
||
{
|
||
uint32_t fillfg = attr & ATTR_INVERSE ? bgcolor : fgcolor;
|
||
uint32_t fillbg = attr & ATTR_INVERSE ? fgcolor : bgcolor;
|
||
textbuf->Scroll(1, TextChar(' ', vgacolor, 0, fillfg, fillbg));
|
||
line = textbuf->Height()-1;
|
||
}
|
||
}
|
||
|
||
#if 0
|
||
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);
|
||
}
|
||
#endif
|
||
|
||
void TextTerminal::Backspace(TextBuffer* textbuf)
|
||
{
|
||
if ( column )
|
||
{
|
||
column--;
|
||
TextPos pos(column, line);
|
||
TextChar tc = textbuf->GetChar(pos);
|
||
next_attr = tc.attr & (ATTR_BOLD | ATTR_UNDERLINE);
|
||
if ( tc.c == L'_' )
|
||
next_attr |= ATTR_UNDERLINE;
|
||
else if ( tc.c == L' ' )
|
||
next_attr &= ~ATTR_BOLD;
|
||
else
|
||
next_attr |= ATTR_BOLD;
|
||
}
|
||
}
|
||
|
||
void TextTerminal::Tab(TextBuffer* textbuf)
|
||
{
|
||
if ( column == textbuf->Width() )
|
||
{
|
||
column = 0;
|
||
Newline(textbuf);
|
||
}
|
||
column++;
|
||
column = -(-column & ~0x7U);
|
||
size_t width = textbuf->Width();
|
||
if ( width <= column )
|
||
column = width;
|
||
}
|
||
|
||
// TODO: This implementation of the 'Ansi Escape Codes' is incomplete and hacky.
|
||
void TextTerminal::AnsiReset()
|
||
{
|
||
next_attr = 0;
|
||
ansiusedparams = 0;
|
||
ansiparams[0] = 0;
|
||
ignoresequence = false;
|
||
ansimode = CSI;
|
||
}
|
||
|
||
void TextTerminal::PutAnsiEscaped(TextBuffer* textbuf, char c)
|
||
{
|
||
// Check the proper prefixes are used.
|
||
if ( ansimode == CSI )
|
||
{
|
||
if ( c == '[' )
|
||
ansimode = COMMAND;
|
||
else if ( c == '(' || c == ')' || c == '*' || c == '+' ||
|
||
c == '-' || c == '.' || c == '/' )
|
||
ansimode = CHARSET;
|
||
// TODO: Enter and exit alternatve keypad mode.
|
||
else if ( c == '=' || c == '>' )
|
||
ansimode = NONE;
|
||
else
|
||
{
|
||
ansimode = NONE;
|
||
ansimode = NONE;
|
||
}
|
||
return;
|
||
}
|
||
|
||
if ( ansimode == CHARSET )
|
||
{
|
||
ansimode = NONE;
|
||
return;
|
||
}
|
||
|
||
// Read part of a parameter.
|
||
if ( '0' <= c && c <= '9' )
|
||
{
|
||
if ( ansiusedparams == 0 )
|
||
ansiusedparams++;
|
||
unsigned val = c - '0';
|
||
ansiparams[ansiusedparams-1] *= 10;
|
||
ansiparams[ansiusedparams-1] += val;
|
||
}
|
||
|
||
// Parameter delimiter.
|
||
else if ( c == ';' )
|
||
{
|
||
if ( ansiusedparams == ANSI_NUM_PARAMS )
|
||
{
|
||
ansimode = NONE;
|
||
return;
|
||
}
|
||
ansiparams[ansiusedparams++] = 0;
|
||
}
|
||
|
||
// Left for future standardization, so discard this sequence.
|
||
else if ( c == ':' )
|
||
{
|
||
ignoresequence = true;
|
||
}
|
||
|
||
else if ( c == '>' )
|
||
{
|
||
ansimode = GREATERTHAN;
|
||
}
|
||
|
||
// Run a command.
|
||
else if ( 64 <= c && c <= 126 )
|
||
{
|
||
if ( !ignoresequence )
|
||
{
|
||
if ( ansimode == COMMAND )
|
||
RunAnsiCommand(textbuf, c);
|
||
else if ( ansimode == GREATERTHAN )
|
||
{
|
||
// Send Device Attributes
|
||
if ( c == 'c' )
|
||
{
|
||
// TODO: Send an appropriate response through the terminal.
|
||
}
|
||
else
|
||
{
|
||
ansimode = NONE;
|
||
return;
|
||
}
|
||
ansimode = NONE;
|
||
}
|
||
}
|
||
else
|
||
ansimode = NONE;
|
||
}
|
||
|
||
// 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};
|
||
|
||
uint32_t fillfg = attr & ATTR_INVERSE ? bgcolor : fgcolor;
|
||
uint32_t fillbg = attr & ATTR_INVERSE ? fgcolor : bgcolor;
|
||
textbuf->Fill(from, to, TextChar(' ', vgacolor, 0, fillfg, fillbg));
|
||
} 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};
|
||
|
||
uint32_t fillfg = attr & ATTR_INVERSE ? bgcolor : fgcolor;
|
||
uint32_t fillbg = attr & ATTR_INVERSE ? fgcolor : bgcolor;
|
||
textbuf->Fill(from, to, TextChar(' ', vgacolor, 0, fillfg, fillbg));
|
||
} break;
|
||
case 'L': // Append lines before current line.
|
||
{
|
||
column = 0;
|
||
unsigned count = 0 < ansiusedparams ? ansiparams[0] : 1;
|
||
if ( height - line < count )
|
||
count = height - line;
|
||
TextPos from(0, line);
|
||
TextPos move_to(0, line + count);
|
||
unsigned move_lines = height - (line + count);
|
||
textbuf->Move(move_to, from, move_lines * width);
|
||
if ( 0 < count )
|
||
{
|
||
TextPos fill_to(width - 1, line + count - 1);
|
||
uint32_t fill_fg = attr & ATTR_INVERSE ? bgcolor : fgcolor;
|
||
uint32_t fill_bg = attr & ATTR_INVERSE ? fgcolor : bgcolor;
|
||
TextChar fill_char(' ', vgacolor, 0, fill_fg, fill_bg);
|
||
textbuf->Fill(from, fill_to, fill_char);
|
||
}
|
||
} break;
|
||
case 'M': // Delete lines starting from beginning of current line.
|
||
{
|
||
column = 0;
|
||
unsigned count = 0 < ansiusedparams ? ansiparams[0] : 1;
|
||
if ( height - line < count )
|
||
count = height - line;
|
||
TextPos move_from(0, line + count);
|
||
TextPos move_to(0, line);
|
||
unsigned move_lines = height - (line + count);
|
||
textbuf->Move(move_to, move_from, move_lines * width);
|
||
if ( 0 < count )
|
||
{
|
||
TextPos fill_from(0, height - count);
|
||
TextPos fill_to(width - 1, height - 1);
|
||
uint32_t fill_fg = attr & ATTR_INVERSE ? bgcolor : fgcolor;
|
||
uint32_t fill_bg = attr & ATTR_INVERSE ? fgcolor : bgcolor;
|
||
TextChar fill_char(' ', vgacolor, 0, fill_fg, fill_bg);
|
||
textbuf->Fill(fill_from, fill_to, fill_char);
|
||
}
|
||
} break;
|
||
// TODO: CSI Ps P Delete Ps Character(s) (default = 1) (DCH).
|
||
// (delete those characters and move the rest of the line leftward).
|
||
case 'S': // Scroll a line up and place a new line at the buttom.
|
||
{
|
||
uint32_t fillfg = attr & ATTR_INVERSE ? bgcolor : fgcolor;
|
||
uint32_t fillbg = attr & ATTR_INVERSE ? fgcolor : bgcolor;
|
||
textbuf->Scroll(1, TextChar(' ', vgacolor, 0, fillfg, fillbg));
|
||
line = height-1;
|
||
} break;
|
||
case 'T': // Scroll a line up and place a new line at the top.
|
||
{
|
||
uint32_t fillfg = attr & ATTR_INVERSE ? bgcolor : fgcolor;
|
||
uint32_t fillbg = attr & ATTR_INVERSE ? fgcolor : bgcolor;
|
||
textbuf->Scroll(-1, TextChar(' ', vgacolor, 0, fillfg, fillbg));
|
||
line = 0;
|
||
} break;
|
||
case 'd': // Move the cursor to line N.
|
||
{
|
||
unsigned posy = 0 < ansiusedparams ? ansiparams[0]-1 : 0;
|
||
if ( height <= posy )
|
||
posy = height-1;
|
||
line = posy;
|
||
} 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] =
|
||
{
|
||
#if 0
|
||
0, 4, 2, 6, 1, 5, 3, 7
|
||
#else
|
||
COLOR8_BLACK, COLOR8_RED, COLOR8_GREEN, COLOR8_BROWN,
|
||
COLOR8_BLUE, COLOR8_MAGENTA, COLOR8_CYAN, COLOR8_LIGHT_GREY,
|
||
#endif
|
||
};
|
||
|
||
for ( size_t i = 0; i < ansiusedparams; i++ )
|
||
{
|
||
unsigned cmd = ansiparams[i];
|
||
// Turn all attributes off.
|
||
if ( cmd == 0 )
|
||
{
|
||
vgacolor = DEFAULT_VGACOLOR;
|
||
attr = 0;
|
||
fgcolor = palette[DEFAULT_FOREGROUND];
|
||
bgcolor = palette[DEFAULT_BACKGROUND];
|
||
}
|
||
// Boldness.
|
||
else if ( cmd == 1 )
|
||
attr |= ATTR_BOLD;
|
||
// TODO: 2, Faint
|
||
// TODO: 3, Italicized
|
||
// Underline.
|
||
else if ( cmd == 4 )
|
||
attr |= ATTR_UNDERLINE;
|
||
// TODO: 5, Blink (appears as Bold)
|
||
// Inverse.
|
||
else if ( cmd == 7 )
|
||
attr |= ATTR_INVERSE;
|
||
// TODO: 8, Invisible
|
||
// TODO: 9, Crossed-out
|
||
// TODO: 21, Doubly-underlined
|
||
// Normal (neither bold nor faint).
|
||
else if ( cmd == 22 )
|
||
attr &= ~ATTR_BOLD;
|
||
// TODO: 23, Not italicized
|
||
// Not underlined.
|
||
else if ( cmd == 24 )
|
||
attr &= ~ATTR_UNDERLINE;
|
||
// TODO: 25, Steady (not blinking)
|
||
// Positive (not inverse).
|
||
else if ( cmd == 27 )
|
||
attr &= ~ATTR_INVERSE;
|
||
// TODO: 28, Visible (not hidden)
|
||
// Set text color.
|
||
else if ( 30 <= cmd && cmd <= 37 )
|
||
{
|
||
unsigned val = cmd - 30;
|
||
vgacolor &= 0xF0;
|
||
vgacolor |= conversion[val] << 0;
|
||
fgcolor = palette[val];
|
||
}
|
||
// Set text color.
|
||
else if ( cmd == 38 )
|
||
{
|
||
if ( 5 <= ansiusedparams - i && ansiparams[i+1] == 2 )
|
||
{
|
||
uint8_t r = ansiparams[i+2];
|
||
uint8_t g = ansiparams[i+3];
|
||
uint8_t b = ansiparams[i+4];
|
||
i += 5 - 1;
|
||
fgcolor = ColorFromRGB(r, g, b);
|
||
// TODO: Approxpiate vgacolor.
|
||
}
|
||
else if ( 3 <= ansiusedparams - i && ansiparams[i+1] == 5 )
|
||
{
|
||
uint8_t index = ansiparams[i+2];
|
||
i += 3 - 1;
|
||
fgcolor = palette[index];
|
||
// TODO: Approxpiate vgacolor.
|
||
}
|
||
}
|
||
// Set default text color.
|
||
else if ( cmd == 39 )
|
||
{
|
||
vgacolor &= 0xF0;
|
||
vgacolor |= DEFAULT_VGACOLOR & 0x0F;
|
||
fgcolor = palette[DEFAULT_FOREGROUND];
|
||
}
|
||
// Set background color.
|
||
else if ( 40 <= cmd && cmd <= 47 )
|
||
{
|
||
unsigned val = cmd - 40;
|
||
vgacolor &= 0x0F;
|
||
vgacolor |= conversion[val] << 4;
|
||
bgcolor = palette[val];
|
||
}
|
||
// Set background color.
|
||
else if ( cmd == 48 )
|
||
{
|
||
if ( 5 <= ansiusedparams - i && ansiparams[i+1] == 2 )
|
||
{
|
||
uint8_t r = ansiparams[i+2];
|
||
uint8_t g = ansiparams[i+3];
|
||
uint8_t b = ansiparams[i+4];
|
||
i += 5 - 1;
|
||
bgcolor = ColorFromRGB(r, g, b);
|
||
// TODO: Approxpiate vgacolor.
|
||
}
|
||
else if ( 3 <= ansiusedparams - i && ansiparams[i+1] == 5 )
|
||
{
|
||
uint8_t index = ansiparams[i+2];
|
||
i += 3 - 1;
|
||
bgcolor = palette[index];
|
||
// TODO: Approxpiate vgacolor.
|
||
}
|
||
}
|
||
// Set default background color.
|
||
else if ( cmd == 49 )
|
||
{
|
||
vgacolor &= 0x0F;
|
||
vgacolor |= DEFAULT_VGACOLOR & 0xF0;
|
||
bgcolor = palette[DEFAULT_BACKGROUND];
|
||
}
|
||
// Set text color.
|
||
else if ( 90 <= cmd && cmd <= 97 )
|
||
{
|
||
unsigned val = cmd - 90 + 8;
|
||
vgacolor &= 0xF0;
|
||
vgacolor |= (0x8 | conversion[val - 8]) << 0;
|
||
fgcolor = palette[val];
|
||
}
|
||
// Set background color.
|
||
else if ( 100 <= cmd && cmd <= 107 )
|
||
{
|
||
unsigned val = cmd - 100 + 8;
|
||
vgacolor &= 0x0F;
|
||
vgacolor |= (0x8 | conversion[val - 8]) << 4;
|
||
bgcolor = palette[val];
|
||
}
|
||
else
|
||
{
|
||
ansimode = NONE;
|
||
}
|
||
}
|
||
} break;
|
||
case 'n': // Request special information from terminal.
|
||
{
|
||
ansimode = NONE;
|
||
// TODO: Handle this code.
|
||
} break;
|
||
case 's': // Save cursor position.
|
||
{
|
||
ansisavedposx = column;
|
||
ansisavedposy = line;
|
||
} break;
|
||
case 'u': // Restore cursor position.
|
||
{
|
||
column = ansisavedposx;
|
||
line = ansisavedposy;
|
||
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);
|
||
if ( 0 < ansiusedparams && ansiparams[0] == 1049 )
|
||
{}; // TODO: Save scrollback.
|
||
} break;
|
||
case 'h': // Show cursor.
|
||
{
|
||
// TODO: This is somehow related to the special char '?'.
|
||
if ( 0 < ansiusedparams && ansiparams[0] == 25 )
|
||
textbuf->SetCursorEnabled(true);
|
||
if ( 0 < ansiusedparams && ansiparams[0] == 1049 )
|
||
{}; // TODO: Restore scrollback.
|
||
} break;
|
||
default:
|
||
{
|
||
ansimode = NONE;
|
||
}
|
||
// TODO: Handle other cases.
|
||
}
|
||
|
||
ansimode = NONE;
|
||
}
|
||
|
||
} // namespace Sortix
|