From 3bf25161f4e1cba15152c5485dcf391aba90b1e9 Mon Sep 17 00:00:00 2001 From: John Hodge Date: Sat, 7 Sep 2013 23:08:16 +0800 Subject: [PATCH] Usermode/GUI Terminal - Reworking terminal code. Incomplete - Works for basic usage, but backspace and more complex escape codes cause instant quit. --- Usermode/Applications/gui_shell_src/display.c | 309 +++++++++++------- .../gui_shell_src/include/display.h | 26 +- .../gui_shell_src/include/vt100.h | 4 +- Usermode/Applications/gui_shell_src/main.c | 14 +- Usermode/Applications/gui_shell_src/vt100.c | 60 +++- 5 files changed, 268 insertions(+), 145 deletions(-) diff --git a/Usermode/Applications/gui_shell_src/display.c b/Usermode/Applications/gui_shell_src/display.c index bb07e39f..00496088 100644 --- a/Usermode/Applications/gui_shell_src/display.c +++ b/Usermode/Applications/gui_shell_src/display.c @@ -13,17 +13,44 @@ #include #include #include +#include +#include #define UNIMPLIMENTED() do{_SysDebug("UNIMPLIMENTED %s", __func__); exit(-1);}while(0) // === EXTERN == extern tHWND gMainWindow; +typedef struct sLine tLine; + +struct sLine { + char *Data; + // TODO: Cache offsets to avoid scan-forward + size_t Len; + size_t Size; + bool IsDirty; +}; + +struct sTerminal { + int ViewCols; + int ViewRows; + + int CursorRow; + int CursorCol; + + size_t CursorByte; + + bool bUsingAltBuf; + + size_t ViewOffset; + size_t TotalLines; + struct sLine *PriBuf; + + struct sLine *AltBuf; +}; + // === GLOBALS === - int giDisplayCols; - int giDisplayLines; - int giDisplayTotalLines; - int giDisplayBufSize; +tTerminal gMainBuffer; int giCurrentLine; int giCurrentLinePos; // byte offset, not column int giCurrentCol; @@ -34,147 +61,205 @@ char **gasDisplayLines; char *gabDisplayLinesDirty; // === CODE === -void Display_Init(int Cols, int Lines, int ExtraScrollbackLines) +tTerminal *Display_Init(int Cols, int Lines, int ExtraScrollbackLines) { - giDisplayCols = Cols; - giDisplayLines = Lines; - giDisplayTotalLines = Lines + ExtraScrollbackLines; - gasDisplayLines = calloc( sizeof(char*), (Lines + ExtraScrollbackLines) ); - gaiDisplayLineSizes = calloc( sizeof(int), (Lines + ExtraScrollbackLines) ); - gabDisplayLinesDirty = calloc( sizeof(char), (Lines + ExtraScrollbackLines) ); + tTerminal *term = &gMainBuffer; + term->ViewCols = Cols; + term->ViewRows = Lines; + term->TotalLines = Lines + ExtraScrollbackLines; + term->PriBuf = calloc( sizeof(tLine), (Lines + ExtraScrollbackLines) ); AxWin3_RichText_SetLineCount(gMainWindow, Lines+ExtraScrollbackLines); AxWin3_RichText_SetCursorType(gMainWindow, 1); // TODO: enum + return term; } -void Display_int_PushString(int Length, const char *Text) +// Return the byte length of a single on-screen character +size_t _GetCharLength(size_t AvailLength, const char *Text, uint32_t *BaseCodepoint) { - _SysDebug("Line %i += %i '%*C'", giCurrentLine, Length, Length, Text); - if( !gasDisplayLines[giCurrentLine] || giCurrentLinePos + Length >= gaiDisplayLineSizes[giCurrentLine] ) + if( !AvailLength ) + return 0; + + size_t charlen = ReadUTF8(Text, BaseCodepoint); + + while(charlen < AvailLength) { - int reqsize = giCurrentLinePos + Length; - gaiDisplayLineSizes[giCurrentLine] = (reqsize + 32-1) & ~(32-1); - void *tmp = realloc(gasDisplayLines[giCurrentLine], gaiDisplayLineSizes[giCurrentLine]); - if( !tmp ) perror("Display_AddText - realloc"); - gasDisplayLines[giCurrentLine] = tmp; + uint32_t cp; + size_t size = ReadUTF8(Text+charlen, &cp); + if( Unicode_IsPrinting(cp) ) + break; + charlen += size; + } + + return charlen; +} + +tLine *Display_int_GetCurLine(tTerminal *Term) +{ + int lineidx = Term->CursorRow + (Term->bUsingAltBuf ? 0 : Term->ViewOffset); + return (Term->bUsingAltBuf ? Term->AltBuf : Term->PriBuf) + lineidx; +} + +size_t Display_int_PushCharacter(tTerminal *Term, size_t AvailLength, const char *Text) +{ + tLine *lineptr = Display_int_GetCurLine(Term); + uint32_t cp; + size_t charlen = _GetCharLength(AvailLength, Text, &cp); + bool bOverwrite = Unicode_IsPrinting(cp); + + _SysDebug("Line %b:%i += %i '%.*s'", Term->bUsingAltBuf, Term->CursorRow, charlen, charlen, Text); + + // Figure out how much we need to shift the stream + int shift; + if( bOverwrite ) { + size_t nextlen = _GetCharLength( + lineptr->Len-Term->CursorByte, + lineptr->Data+Term->CursorByte, + NULL); + _SysDebug("Char at +%i is %i long (%.*s)", Term->CursorByte, nextlen, + nextlen, lineptr->Data+Term->CursorByte); + shift = charlen - nextlen; + } + else { + shift = charlen; + } + _SysDebug("shift = %i", shift); + + // Ensure we have space enough + if( !lineptr->Data || shift > 0 ) { + const size_t size_step = 64; + assert(shift > 0); + lineptr->Size = (lineptr->Len+shift+1 + size_step-1) & ~(size_step-1); + void *tmp = realloc(lineptr->Data, lineptr->Size); + if( !tmp ) perror("Display_int_PushCharacter - realloc"); + lineptr->Data = tmp; + } + + // Insert into stream + char *base = lineptr->Data + Term->CursorByte; + if( Term->CursorByte == lineptr->Len ) { + // No shifting needed + } + else if( shift >= 0 ) { + size_t bytes = lineptr->Len - (Term->CursorByte+shift); + _SysDebug("memmove(base+%i, base, %i)", shift, bytes); + memmove(base+shift, base, bytes); + } + else { + shift = -shift; + size_t bytes = lineptr->Len - (Term->CursorByte+shift); + _SysDebug("memmove(base, base+%i, %i)", shift, bytes); + memmove(base, base+shift, bytes); } + memcpy(base, Text, charlen); + lineptr->IsDirty = true; + lineptr->Len += shift; + lineptr->Data[lineptr->Len] = 0; // NULL Terminate - memcpy(gasDisplayLines[giCurrentLine]+giCurrentLinePos, Text, Length); - gabDisplayLinesDirty[giCurrentLine] = 1; - gasDisplayLines[giCurrentLine][giCurrentLinePos+Length] = 0; - giCurrentLinePos += Length; + Term->CursorByte += charlen; + // HACKY: Prevents the CursorCol++ in Display_AddText from having an effect + if( !bOverwrite ) + Term->CursorCol --; + + return charlen; } -void Display_AddText(int Length, const char *UTF8Text) +void Display_AddText(tTerminal *Term, size_t Length, const char *UTF8Text) { _SysDebug("%i '%.*s'", Length, Length, UTF8Text); - // Copy as many characters (not bytes, have to trim off the last char) as we can to the current line - // - then roll over to the next line while( Length > 0 ) { - int space = giDisplayCols - giCurrentCol; - int bytes = 0; - while( space && bytes < Length ) - { - uint32_t cp; - bytes += ReadUTF8(UTF8Text+bytes, &cp); - if( Unicode_IsPrinting(cp) ) { - space --; - giCurrentCol ++; - } - } + size_t used = Display_int_PushCharacter(Term, Length, UTF8Text); - Display_int_PushString(bytes, UTF8Text); - - UTF8Text += bytes; - Length -= bytes; - if( Length != 0 ) - { - // Next line - giCurrentLinePos = 0; - giCurrentCol = 0; - giCurrentLine ++; + Length -= used; + UTF8Text += used; + + Term->CursorCol ++; + if( Term->CursorCol == Term->ViewCols ) { + Display_Newline(Term, 1); } } } -void Display_Newline(int bCarriageReturn) +void Display_Newline(tTerminal *Term, bool bCarriageReturn) { // Display_Flush(); // Going down! - giCurrentLine ++; - if( giCurrentLine == giDisplayLines ) - giCurrentLine = 0; - if( giCurrentLine == giFirstLine ) - { - giFirstLine ++; - if(giFirstLine == giDisplayLines) - giFirstLine = 0; + Term->CursorRow ++; + if( Term->CursorRow == Term->TotalLines ) { + // TODO: Scrolling } if( bCarriageReturn ) { - giCurrentLinePos = 0; - giCurrentCol = 0; + Term->CursorByte = 0; + Term->CursorCol = 0; + return ; } - else { - giCurrentLinePos = 0; - int i = giCurrentCol; - if( !gasDisplayLines[giCurrentLine] ) - { - giCurrentCol = 0; - while(i--) - Display_AddText(1, " "); - } - else - { - while( i -- ) - { - uint32_t cp; - giCurrentLinePos += ReadUTF8(gasDisplayLines[giCurrentLine]+giCurrentLinePos, &cp); - if( !Unicode_IsPrinting(cp) ) - i ++; - } - } + + tLine *line = Display_int_GetCurLine(Term); + + Term->CursorByte = 0; + int old_col = Term->CursorCol; + Term->CursorCol = 0; + + size_t ofs = 0; + while( Term->CursorCol < old_col && ofs < line->Len ) { + ofs += _GetCharLength(line->Len-ofs, line->Data+ofs, NULL); + Term->CursorCol ++; } + Term->CursorByte = ofs; + + while( Term->CursorCol < old_col ) + Display_AddText(Term, 1, " "); } -void Display_SetCursor(int Row, int Col) +void Display_SetCursor(tTerminal *Term, int Row, int Col) { UNIMPLIMENTED(); } -void Display_MoveCursor(int RelRow, int RelCol) +void Display_MoveCursor(tTerminal *Term, int RelRow, int RelCol) { - if( RelRow < 0 ) + if( RelRow != 0 ) { - for( ; RelRow < 0; RelRow ++ ) - { - uint32_t cp; - int delta = ReadUTF8Rev(gasDisplayLines[giCurrentLine], giCurrentLinePos, &cp); - if( !Unicode_IsPrinting(cp) ) - RelRow --; - else - giCurrentCol --; - giCurrentLinePos -= delta; - } + UNIMPLIMENTED(); } - else + + if( RelCol != 0 ) { - UNIMPLIMENTED(); + int req_col = Term->CursorCol + RelCol; + if( req_col < 0 ) req_col = 0; + if( req_col > Term->ViewCols ) req_col = Term->ViewCols; + + tLine *line = Display_int_GetCurLine(Term); + size_t ofs = 0; + for( int i = 0; i < req_col; i ++ ) + { + size_t clen = _GetCharLength(line->Len-ofs, line->Data+ofs, NULL); + if( clen == 0 ) { + req_col = i; + break; + } + ofs += clen; + } + + Term->CursorCol = req_col; + Term->CursorByte = ofs; } } -void Display_ClearLine(int Dir) // 0: All, 1: Forward, -1: Reverse +void Display_ClearLine(tTerminal *Term, int Dir) // 0: All, 1: Forward, -1: Reverse { if( Dir == 0 ) { + tLine *line = Display_int_GetCurLine(Term); // Completely clear line - if( gasDisplayLines[giCurrentLine] ) - free(gasDisplayLines[giCurrentLine]); - gasDisplayLines[giCurrentLine] = NULL; - gabDisplayLinesDirty[giCurrentLine] = 1; + if( line->Data ) + free(line->Data); + line->Data = NULL; + line->IsDirty = true; } else if( Dir == 1 ) { @@ -190,7 +275,7 @@ void Display_ClearLine(int Dir) // 0: All, 1: Forward, -1: Reverse } } -void Display_ClearLines(int Dir) // 0: All, 1: Forward, -1: Reverse +void Display_ClearLines(tTerminal *Term, int Dir) // 0: All, 1: Forward, -1: Reverse { if( Dir == 0 ) { @@ -211,36 +296,36 @@ void Display_ClearLines(int Dir) // 0: All, 1: Forward, -1: Reverse } } -void Display_SetForeground(uint32_t RGB) +void Display_SetForeground(tTerminal *Term, uint32_t RGB) { char buf[7+1]; sprintf(buf, "\1%06x", RGB&0xFFFFFF); - Display_int_PushString(7, buf); + Display_AddText(Term, 7, buf); } -void Display_SetBackground(uint32_t RGB) +void Display_SetBackground(tTerminal *Term, uint32_t RGB) { char buf[7+1]; sprintf(buf, "\2%06x", RGB&0xFFFFFF); - Display_int_PushString(7, buf); + Display_AddText(Term, 7, buf); } -void Display_Flush(void) +void Display_Flush(tTerminal *Term) { - int i; - for( i = 0; i < giDisplayCols; i ++ ) + for( int i = 0; i < Term->ViewRows; i ++ ) { - int line = (giFirstLine + i) % giDisplayTotalLines; - if( !gabDisplayLinesDirty[line] ) + int line = (Term->ViewOffset + i) % Term->TotalLines; + tLine *lineptr = &Term->PriBuf[line]; + if( !lineptr->IsDirty ) continue; - _SysDebug("Line %i+%i '%s'", giFirstLine, i, gasDisplayLines[line]); - AxWin3_RichText_SendLine(gMainWindow, giFirstLine + i, gasDisplayLines[line] ); - gabDisplayLinesDirty[line] = 0; + _SysDebug("Line %i+%i '%.*s'", Term->ViewOffset, i, lineptr->Len, lineptr->Data); + AxWin3_RichText_SendLine(gMainWindow, Term->ViewOffset + i, lineptr->Data ); + lineptr->IsDirty = 0; } - AxWin3_RichText_SetCursorPos(gMainWindow, giCurrentLine, giCurrentCol); + AxWin3_RichText_SetCursorPos(gMainWindow, Term->CursorRow, Term->CursorCol); } -void Display_ShowAltBuffer(int AltBufEnabled) +void Display_ShowAltBuffer(tTerminal *Term, bool AltBufEnabled) { UNIMPLIMENTED(); } diff --git a/Usermode/Applications/gui_shell_src/include/display.h b/Usermode/Applications/gui_shell_src/include/display.h index 4feaf352..26601fe1 100644 --- a/Usermode/Applications/gui_shell_src/include/display.h +++ b/Usermode/Applications/gui_shell_src/include/display.h @@ -9,27 +9,31 @@ #define _DISPLAY_H_ #include +#include // size_t +#include -extern void Display_Init(int Cols, int Lines, int ExtraScrollbackLines); +typedef struct sTerminal tTerminal; -extern void Display_AddText(int Length, const char *UTF8Text); -extern void Display_Newline(int bCarriageReturn); -extern void Display_SetCursor(int Row, int Col); -extern void Display_MoveCursor(int RelRow, int RelCol); -extern void Display_ClearLine(int Dir); // 0: All, 1: Forward, -1: Reverse -extern void Display_ClearLines(int Dir); // 0: All, 1: Forward, -1: Reverse -extern void Display_SetForeground(uint32_t RGB); -extern void Display_SetBackground(uint32_t RGB); +extern tTerminal *Display_Init(int Cols, int Lines, int ExtraScrollbackLines); + +extern void Display_AddText(tTerminal *Term, size_t Length, const char *UTF8Text); +extern void Display_Newline(tTerminal *Term, bool bCarriageReturn); +extern void Display_SetCursor(tTerminal *Term, int Row, int Col); +extern void Display_MoveCursor(tTerminal *Term, int RelRow, int RelCol); +extern void Display_ClearLine(tTerminal *Term, int Dir); // 0: All, 1: Forward, -1: Reverse +extern void Display_ClearLines(tTerminal *Term, int Dir); // 0: All, 1: Forward, -1: Reverse +extern void Display_SetForeground(tTerminal *Term, uint32_t RGB); +extern void Display_SetBackground(tTerminal *Term, uint32_t RGB); /** * \brief Ensure that recent updates are flushed to the server * \note Called at the end of an "input" buffer */ -extern void Display_Flush(void); +extern void Display_Flush(tTerminal *Term); /** * \brief Switch the display to the alternate buffer (no scrollback) */ -extern void Display_ShowAltBuffer(int AltBufEnabled); +extern void Display_ShowAltBuffer(tTerminal *Term, bool AltBufEnabled); #endif diff --git a/Usermode/Applications/gui_shell_src/include/vt100.h b/Usermode/Applications/gui_shell_src/include/vt100.h index 13f830e2..162991b0 100644 --- a/Usermode/Applications/gui_shell_src/include/vt100.h +++ b/Usermode/Applications/gui_shell_src/include/vt100.h @@ -8,6 +8,8 @@ #ifndef _VT100_H_ #define _VT100_H_ +#include "display.h" + /** * Returns either a positive or negative byte count. * Positive means that many bytes were used as part of the escape sequence @@ -15,7 +17,7 @@ * Negative means that there were that many bytes before the next escape * sequence (and hence those should be displayed). */ -extern int Term_HandleVT100(int Len, const char *Buf); +extern int Term_HandleVT100(tTerminal *Term, int Len, const char *Buf); #endif diff --git a/Usermode/Applications/gui_shell_src/main.c b/Usermode/Applications/gui_shell_src/main.c index 3f9a656c..344bba0c 100644 --- a/Usermode/Applications/gui_shell_src/main.c +++ b/Usermode/Applications/gui_shell_src/main.c @@ -22,7 +22,7 @@ int main(int argc, char *argv[], const char **envp); int Term_KeyHandler(tHWND Window, int bPress, uint32_t KeySym, uint32_t Translated); int Term_MouseHandler(tHWND Window, int bPress, int Button, int Row, int Col); -void Term_HandleOutput(int Len, const char *Buf); +void Term_HandleOutput(tTerminal *Term, int Len, const char *Buf); // === GLOBALS === tHWND gMainWindow; @@ -55,7 +55,7 @@ int main(int argc, char *argv[], const char **envp) AxWin3_RichText_SetCursorType (gMainWindow, AXWIN3_RICHTEXT_CURSOR_INV); AxWin3_RichText_SetCursorBlink (gMainWindow, 1); - Display_Init(80, 25, 100); + tTerminal *term = Display_Init(80, 25, 100); AxWin3_ResizeWindow(gMainWindow, 80*8, 25*16); AxWin3_MoveWindow(gMainWindow, 20, 50); AxWin3_ShowWindow(gMainWindow, 1); @@ -105,7 +105,7 @@ int main(int argc, char *argv[], const char **envp) int len = _SysRead(giPTYHandle, buf, sizeof(buf)); if( len <= 0 ) break; - Term_HandleOutput(len, buf); + Term_HandleOutput(term, len, buf); } } @@ -184,7 +184,7 @@ int Term_MouseHandler(tHWND Window, int bPress, int Button, int Row, int Col) return 0; } -void Term_HandleOutput(int Len, const char *Buf) +void Term_HandleOutput(tTerminal *Term, int Len, const char *Buf) { // TODO: Handle graphical / accelerated modes @@ -193,15 +193,15 @@ void Term_HandleOutput(int Len, const char *Buf) while( ofs < Len ) { - esc_len = Term_HandleVT100(Len - ofs, Buf + ofs); + esc_len = Term_HandleVT100(Term, Len - ofs, Buf + ofs); if( esc_len < 0 ) { - Display_AddText(-esc_len, Buf + ofs); + Display_AddText(Term, -esc_len, Buf + ofs); esc_len = -esc_len; } ofs += esc_len; //_SysDebug("Len = %i, ofs = %i", Len, ofs); } - Display_Flush(); + Display_Flush(Term); } diff --git a/Usermode/Applications/gui_shell_src/vt100.c b/Usermode/Applications/gui_shell_src/vt100.c index 09a96d9d..0a62ba9f 100644 --- a/Usermode/Applications/gui_shell_src/vt100.c +++ b/Usermode/Applications/gui_shell_src/vt100.c @@ -19,14 +19,14 @@ const uint32_t caVT100Colours[] = { 0xCCCCCC, 0xFF0000, 0x00FF00, 0xFFFF00, 0x0000FF, 0xFF00FF, 0x00FFFF, 0xFFFFFFF }; - int Term_HandleVT100_Long(int Len, const char *Buf); + int Term_HandleVT100_Long(tTerminal *Term, int Len, const char *Buf); static inline int min(int a, int b) { return a < b ? a : b; } -int Term_HandleVT100(int Len, const char *Buf) +int Term_HandleVT100(tTerminal *Term, int Len, const char *Buf) { #define MAX_VT100_ESCAPE_LEN 16 static char inc_buf[MAX_VT100_ESCAPE_LEN]; @@ -47,7 +47,7 @@ int Term_HandleVT100(int Len, const char *Buf) switch(inc_buf[1]) { case '[': // Multibyte, funtime starts - ret = Term_HandleVT100_Long(inc_len-2, inc_buf+2); + ret = Term_HandleVT100_Long(Term, inc_len-2, inc_buf+2); if( ret > 0 ) { ret += 2; } @@ -67,19 +67,19 @@ int Term_HandleVT100(int Len, const char *Buf) switch( *Buf ) { case '\b': - Display_MoveCursor(-1, 0); - Display_AddText(1, " "); - Display_MoveCursor(-1, 0); + Display_MoveCursor(Term, -1, 0); + Display_AddText(Term, 1, " "); + Display_MoveCursor(Term, -1, 0); // TODO: Need to handle \t and ^A-Z return 1; case '\t': // TODO: tab (get current cursor pos, space until multiple of 8) return 1; case '\n': - Display_Newline(1); + Display_Newline(Term, 1); return 1; case '\r': - Display_MoveCursor(INT_MIN, 0); + Display_MoveCursor(Term, INT_MIN, 0); return 1; } @@ -96,7 +96,7 @@ int Term_HandleVT100(int Len, const char *Buf) return -ret; } -int Term_HandleVT100_Long(int Len, const char *Buffer) +int Term_HandleVT100_Long(tTerminal *Term, int Len, const char *Buffer) { char c; int argc = 0, j = 0; @@ -137,9 +137,26 @@ int Term_HandleVT100_Long(int Len, const char *Buffer) if( bQuestionMark ) { + int set = 0; // Special commands switch( c ) { + case 'h': // set + set = 1; + case 'l': // unset + switch(args[0]) + { + case 25: // Hide cursor + _SysDebug("TODO: \\e[?25%c Show/Hide cursor", c); + break; + case 1047: // Alternate buffer + Display_ShowAltBuffer(Term, set); + break; + default: + _SysDebug("TODO: \\e[?%i%c Unknow DEC private mode", args[0], c); + break; + } + break; default: _SysDebug("Unknown VT100 extended escape char 0x%x", c); break; @@ -150,14 +167,24 @@ int Term_HandleVT100_Long(int Len, const char *Buffer) // Standard commands switch( c ) { + case 'H': + if( argc != 2 ) { + } + else { + Display_SetCursor(Term, args[0], args[1]); + } + break; case 'J': if( argc == 0 ) - Display_ClearLine(0); + Display_ClearLine(Term, 0); else if( args[0] == 2 ) - Display_ClearLines(0); // Entire screen! + Display_ClearLines(Term, 0); // Entire screen! else _SysDebug("TODO: VT100 %i J", args[0]); break; + case 'T': // Scroll down n=1 + _SysDebug("TODO: \\x1B[nT - Scroll down"); + break; case 'm': if( argc == 0 ) { @@ -175,18 +202,23 @@ int Term_HandleVT100_Long(int Len, const char *Buffer) else if( 30 <= args[i] && args[i] <= 37 ) { // TODO: Bold/bright - Display_SetForeground( caVT100Colours[ args[i]-30 ] ); + Display_SetForeground( Term, caVT100Colours[ args[i]-30 ] ); } else if( 40 <= args[i] && args[i] <= 47 ) { // TODO: Bold/bright - Display_SetBackground( caVT100Colours[ args[i]-30 ] ); + Display_SetBackground( Term, caVT100Colours[ args[i]-30 ] ); } } } break; + // Set scrolling region + case 'r': + _SysDebug("TODO: \\x1B[%i;%ir - Set Scroll Region", + args[0], args[1]); + break; default: - _SysDebug("Unknown VT100 escape char 0x%x", c); + _SysDebug("Unknown VT100 long escape char 0x%x '%c'", c, c); break; } } -- 2.20.1