diff --git a/src/apps/Jamfile b/src/apps/Jamfile index cd66b3e6c1..0f90365340 100644 --- a/src/apps/Jamfile +++ b/src/apps/Jamfile @@ -50,6 +50,7 @@ HaikuSubInclude readonlybootprompt ; HaikuSubInclude remotedesktop ; HaikuSubInclude resedit ; HaikuSubInclude screenshot ; +HaikuSubInclude serialconnect ; HaikuSubInclude showimage ; HaikuSubInclude soundrecorder ; HaikuSubInclude stylededit ; diff --git a/src/apps/serialconnect/Jamfile b/src/apps/serialconnect/Jamfile new file mode 100644 index 0000000000..20043b14bc --- /dev/null +++ b/src/apps/serialconnect/Jamfile @@ -0,0 +1,21 @@ +SubDir HAIKU_TOP src apps serialconnect ; + +SubDirSysHdrs [ FDirName $(HAIKU_TOP) src apps serialconnect libvterm include ] ; + +SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src apps serialconnect libvterm src ] ; + +Application SerialConnect : + SerialApp.cpp + SerialWindow.cpp + TermView.cpp + encoding.c + input.c + parser.c + pen.c + screen.c + state.c + unicode.c + vterm.c + : be device $(HAIKU_LOCALE_LIBS) $(TARGET_LIBSUPC++) +; + diff --git a/src/apps/serialconnect/SerialApp.cpp b/src/apps/serialconnect/SerialApp.cpp new file mode 100644 index 0000000000..7697168b75 --- /dev/null +++ b/src/apps/serialconnect/SerialApp.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2012, Adrien Destugues, pulkomandy@gmail.com + * Distributed under the terms of the MIT licence. + */ + + +#include "SerialApp.h" + +#include "SerialWindow.h" + + +SerialApp::SerialApp() + : + BApplication(SerialApp::kApplicationSignature) +{ + fWindow = new SerialWindow(); + + serialLock = create_sem(0, "Serial port lock"); + thread_id id = spawn_thread(PollSerial, "Serial port poller", + B_LOW_PRIORITY, this); + resume_thread(id); +} + + +void SerialApp::ReadyToRun() +{ + fWindow->Show(); +} + + +void SerialApp::MessageReceived(BMessage* message) +{ + switch(message->what) + { + case kMsgOpenPort: + { + const char* portName; + message->FindString("port name", &portName); + serialPort.Open(portName); + release_sem(serialLock); + break; + } + case kMsgDataRead: + { + // TODO forward to the window + break; + } + default: + BApplication::MessageReceived(message); + } +} + + +/* static */ +status_t SerialApp::PollSerial(void*) +{ + SerialApp* application = (SerialApp*)be_app; + char buffer[256]; + + for(;;) + { + ssize_t bytesRead; + + bytesRead = application->serialPort.Read(buffer, 256); + if (bytesRead == B_FILE_ERROR) + { + // Port is not open - wait for it and start over + acquire_sem(application->serialLock); + } else { + // We read something, forward it to the app for handling + BMessage* serialData = new BMessage(kMsgDataRead); + // TODO bytesRead is not nul terminated. Use generic data rather + serialData->AddString("data", buffer); + be_app_messenger.SendMessage(serialData); + } + } +} + +const char* SerialApp::kApplicationSignature + = "application/x-vnd.haiku.SerialConnect"; + + +int main(int argc, char** argv) +{ + SerialApp app; + app.Run(); +} diff --git a/src/apps/serialconnect/SerialApp.h b/src/apps/serialconnect/SerialApp.h new file mode 100644 index 0000000000..a210feb36c --- /dev/null +++ b/src/apps/serialconnect/SerialApp.h @@ -0,0 +1,35 @@ +/* + * Copyright 2012, Adrien Destugues, pulkomandy@gmail.com + * Distributed under the terms of the MIT licence. + */ + + +#include +#include + + +class SerialWindow; + + +class SerialApp: public BApplication +{ + public: + SerialApp(); + void ReadyToRun(); + void MessageReceived(BMessage* message); + + private: + BSerialPort serialPort; + static status_t PollSerial(void*); + + sem_id serialLock; + SerialWindow* fWindow; + + static const char* kApplicationSignature; +}; + +enum messageConstants { + kMsgOpenPort = 'open', + kMsgDataRead = 'dare', +}; + diff --git a/src/apps/serialconnect/SerialWindow.cpp b/src/apps/serialconnect/SerialWindow.cpp new file mode 100644 index 0000000000..bb30691b5e --- /dev/null +++ b/src/apps/serialconnect/SerialWindow.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2012, Adrien Destugues, pulkomandy@gmail.com + * Distributed under the terms of the MIT licence. + */ + + +#include "SerialWindow.h" + +#include +#include +#include +#include +#include + +#include "TermView.h" + + +SerialWindow::SerialWindow() + : + BWindow(BRect(100, 100, 400, 400), SerialWindow::kWindowTitle, + B_DOCUMENT_WINDOW, B_QUIT_ON_WINDOW_CLOSE) +{ + SetLayout(new BGroupLayout(B_VERTICAL, 0.0f)); + + BMenuBar* menuBar = new BMenuBar("menuBar"); + TermView* termView = new TermView(); + + AddChild(menuBar); + AddChild(termView); + + BMenu* connectionMenu = new BMenu("Connections"); + BMenu* editMenu = new BMenu("Edit"); + BMenu* settingsMenu = new BMenu("Settings"); + + menuBar->AddItem(connectionMenu); + menuBar->AddItem(editMenu); + menuBar->AddItem(settingsMenu); + + // TODO messages + BMenu* connect = new BMenu("Connect"); + connectionMenu->AddItem(connect); + + BSerialPort serialPort; + int deviceCount = serialPort.CountDevices(); + + for(int i = 0; i < deviceCount; i++) + { + char buffer[256]; + serialPort.GetDeviceName(i, buffer, 256); + + BMenuItem* portItem = new BMenuItem(buffer, NULL); + + connect->AddItem(portItem); + } + +#if SUPPORTS_MODEM + BMenuItem* connectModem = new BMenuItem( + "Connect via modem" B_UTF8_ELLIPSIS, NULL, 'M', 0); + connectionMenu->AddItem(connectModem); +#endif + BMenuItem* Disconnect = new BMenuItem("Disconnect", NULL, + 'Z', B_OPTION_KEY); + connectionMenu->AddItem(Disconnect); + + // TODO edit menu - what's in it ? + + // Configuring all this by menus may be a bit unhandy. Make a setting + // window instead ? + BMenu* parity = new BMenu("Parity"); + settingsMenu->AddItem(parity); + BMenu* dataBits = new BMenu("Data bits"); + settingsMenu->AddItem(dataBits); + BMenu* stopBits = new BMenu("Stop bits"); + settingsMenu->AddItem(stopBits); + BMenu* baudRate = new BMenu("Baud rate"); + settingsMenu->AddItem(baudRate); + BMenu* flowControl = new BMenu("Flow control"); + settingsMenu->AddItem(flowControl); + + BMenuItem* parityNone = new BMenuItem("None", NULL); + parity->AddItem(parityNone); + BMenuItem* parityOdd = new BMenuItem("Odd", NULL); + parity->AddItem(parityOdd); + BMenuItem* parityEven = new BMenuItem("Even", NULL); + parity->AddItem(parityEven); + + BMenuItem* data7 = new BMenuItem("7", NULL); + dataBits->AddItem(data7); + BMenuItem* data8 = new BMenuItem("8", NULL); + dataBits->AddItem(data8); + + BMenuItem* stop1 = new BMenuItem("1", NULL); + stopBits->AddItem(stop1); + BMenuItem* stop2 = new BMenuItem("2", NULL); + stopBits->AddItem(stop2); + + static const char* baudrates[] = { "50", "75", "110", "134", "150", "200", + "300", "600", "1200", "1800", "2400", "4800", "9600", "19200", "31250", + "38400", "57600", "115200", "230400" + }; + + // Loop backwards to add fastest rates at top of menu + for (int i = sizeof(baudrates) / sizeof(char*); --i >= 0;) + { + BMenuItem* item = new BMenuItem(baudrates[i], NULL); + baudRate->AddItem(item); + } + + BMenuItem* rtsCts = new BMenuItem("RTS/CTS", NULL); + flowControl->AddItem(rtsCts); + BMenuItem* noFlow = new BMenuItem("None", NULL); + flowControl->AddItem(noFlow); + + CenterOnScreen(); +} + + +const char* SerialWindow::kWindowTitle = "SerialConnect"; diff --git a/src/apps/serialconnect/SerialWindow.h b/src/apps/serialconnect/SerialWindow.h new file mode 100644 index 0000000000..c54129010b --- /dev/null +++ b/src/apps/serialconnect/SerialWindow.h @@ -0,0 +1,17 @@ +/* + * Copyright 2012, Adrien Destugues, pulkomandy@gmail.com + * Distributed under the terms of the MIT licence. + */ + + +#include + + +class SerialWindow: public BWindow +{ + public: + SerialWindow::SerialWindow(); + + private: + static const char* kWindowTitle; +}; diff --git a/src/apps/serialconnect/TermView.cpp b/src/apps/serialconnect/TermView.cpp new file mode 100644 index 0000000000..7899bf0a5d --- /dev/null +++ b/src/apps/serialconnect/TermView.cpp @@ -0,0 +1,120 @@ +/* + * Copyright 2012, Adrien Destugues, pulkomandy@gmail.com + * Distributed under the terms of the MIT licence. + */ + + +#include "TermView.h" + +#include + +#include + + +TermView::TermView() + : + BView("TermView", B_WILL_DRAW) +{ + fTerm = vterm_new(kDefaultWidth, kDefaultHeight); + vterm_parser_set_utf8(fTerm, 1); + + fTermScreen = vterm_obtain_screen(fTerm); + vterm_screen_set_callbacks(fTermScreen, &sScreenCallbacks, this); + vterm_screen_reset(fTermScreen, 1); + + SetFont(be_fixed_font); + + font_height height; + GetFontHeight(&height); + fFontHeight = height.ascent + height.descent + height.leading; + fFontWidth = be_fixed_font->StringWidth("X"); + + // TEST + //vterm_push_bytes(fTerm,"Hello World!",11); +} + + +TermView::~TermView() +{ + vterm_free(fTerm); +} + + +void TermView::Draw(BRect updateRect) +{ + VTermRect updatedChars = PixelsToGlyphs(updateRect); + + VTermPos pos; + font_height height; + GetFontHeight(&height); + MovePenTo(kBorderSpacing, height.ascent + kBorderSpacing); + for (pos.row = updatedChars.start_row; pos.row < updatedChars.end_row; + pos.row++) + { + for (pos.col = updatedChars.start_col; + pos.col < updatedChars.end_col; pos.col++) + { + VTermScreenCell cell; + vterm_screen_get_cell(fTermScreen, pos, &cell); + + char buffer[6]; + wcstombs(buffer, (wchar_t*)cell.chars, 6); + + DrawString(buffer); + } + } +} + + +VTermRect TermView::PixelsToGlyphs(BRect pixels) const +{ + pixels.OffsetBy(-kBorderSpacing, -kBorderSpacing); + + VTermRect rect; + rect.start_col = (int)floor(pixels.left / fFontWidth); + rect.end_col = (int)ceil(pixels.right / fFontWidth); + rect.start_row = (int)floor(pixels.top / fFontHeight); + rect.end_row = (int)ceil(pixels.bottom / fFontHeight); + +#if 0 + printf("pixels:\t%d\t%d\t%d\t%d\n" + "glyps:\t%d\t%d\t%d\t%d\n", + (int)pixels.top, (int)pixels.bottom, (int)pixels.left, (int)pixels.right, + rect.start_row, rect.end_row, rect.start_col, rect.end_col); +#endif + return rect; +} + + +BRect TermView::GlyphsToPixels(const VTermRect& glyphs) const +{ + BRect rect; + rect.top = glyphs.start_row * fFontHeight; + rect.bottom = glyphs.end_row * fFontHeight; + rect.left = glyphs.start_col * fFontWidth; + rect.right = glyphs.end_col * fFontWidth; + + return rect; +} + + +BRect TermView::GlyphsToPixels(const int width, const int height) const +{ + VTermRect rect; + rect.start_row = 0; + rect.start_col = 0; + rect.end_row = height; + rect.end_col = width; + return GlyphsToPixels(rect); +} + + +const VTermScreenCallbacks TermView::sScreenCallbacks = { + /*.damage =*/ NULL, + /*.moverect =*/ NULL, + /*.movecursor =*/ NULL, + /*.settermprop =*/ NULL, + /*.setmousefunc =*/ NULL, + /*.bell =*/ NULL, + /*.resize =*/ NULL, +}; diff --git a/src/apps/serialconnect/TermView.h b/src/apps/serialconnect/TermView.h new file mode 100644 index 0000000000..4e485c7e79 --- /dev/null +++ b/src/apps/serialconnect/TermView.h @@ -0,0 +1,37 @@ +/* + * Copyright 2012, Adrien Destugues, pulkomandy@gmail.com + * Distributed under the terms of the MIT licence. + */ + + +#include + +extern "C" { + #include +} + +class TermView: public BView +{ + public: + TermView(); + ~TermView(); + + void Draw(BRect updateRect); + + private: + VTermRect PixelsToGlyphs(BRect pixels) const; + BRect GlyphsToPixels(const VTermRect& glyphs) const; + BRect GlyphsToPixels(const int width, const int height) const; + + private: + VTerm* fTerm; + VTermScreen* fTermScreen; + float fFontWidth; + float fFontHeight; + + static const VTermScreenCallbacks sScreenCallbacks; + + static const int kDefaultWidth = 80; + static const int kDefaultHeight = 25; + static const int kBorderSpacing = 3; +}; diff --git a/src/apps/serialconnect/libvterm/include/vterm.h b/src/apps/serialconnect/libvterm/include/vterm.h new file mode 100644 index 0000000000..47286511f0 --- /dev/null +++ b/src/apps/serialconnect/libvterm/include/vterm.h @@ -0,0 +1,245 @@ +#ifndef __VTERM_H__ +#define __VTERM_H__ + +#include +#include + +#include "vterm_input.h" + +typedef struct VTerm VTerm; +typedef struct VTermState VTermState; +typedef struct VTermScreen VTermScreen; + +typedef struct { + int row; + int col; +} VTermPos; + +/* some small utility functions; we can just keep these static here */ + +/* order points by on-screen flow order */ +static inline int vterm_pos_cmp(VTermPos a, VTermPos b) +{ + return (a.row == b.row) ? a.col - b.col : a.row - b.row; +} + +typedef struct { + int start_row; + int end_row; + int start_col; + int end_col; +} VTermRect; + +/* true if the rect contains the point */ +static inline int vterm_rect_contains(VTermRect r, VTermPos p) +{ + return p.row >= r.start_row && p.row < r.end_row && + p.col >= r.start_col && p.col < r.end_col; +} + +/* move a rect */ +static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta) +{ + rect->start_row += row_delta; rect->end_row += row_delta; + rect->start_col += col_delta; rect->end_col += col_delta; +} + +/* Flag to indicate non-final subparameters in a single CSI parameter. + * Consider + * CSI 1;2:3:4;5a + * 1 4 and 5 are final. + * 2 and 3 are non-final and will have this bit set + * + * Don't confuse this with the final byte of the CSI escape; 'a' in this case. + */ +#define CSI_ARG_FLAG_MORE (1<<31) +#define CSI_ARG_MASK (~(1<<31)) + +#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE) +#define CSI_ARG(a) ((a) & CSI_ARG_MASK) + +/* Can't use -1 to indicate a missing argument; use this instead */ +#define CSI_ARG_MISSING ((1UL<<31)-1) + +#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING) +#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a)) +#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) + +typedef struct { + int (*text)(const char *bytes, size_t len, void *user); + int (*control)(unsigned char control, void *user); + int (*escape)(const char *bytes, size_t len, void *user); + int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); + int (*osc)(const char *command, size_t cmdlen, void *user); + int (*dcs)(const char *command, size_t cmdlen, void *user); + int (*resize)(int rows, int cols, void *user); +} VTermParserCallbacks; + +typedef struct { + uint8_t red, green, blue; +} VTermColor; + +typedef enum { + /* VTERM_VALUETYPE_NONE = 0 */ + VTERM_VALUETYPE_BOOL = 1, + VTERM_VALUETYPE_INT, + VTERM_VALUETYPE_STRING, + VTERM_VALUETYPE_COLOR, +} VTermValueType; + +typedef union { + int boolean; + int number; + char *string; + VTermColor color; +} VTermValue; + +typedef enum { + /* VTERM_ATTR_NONE = 0 */ + VTERM_ATTR_BOLD = 1, // bool: 1, 22 + VTERM_ATTR_UNDERLINE, // number: 4, 21, 24 + VTERM_ATTR_ITALIC, // bool: 3, 23 + VTERM_ATTR_BLINK, // bool: 5, 25 + VTERM_ATTR_REVERSE, // bool: 7, 27 + VTERM_ATTR_STRIKE, // bool: 9, 29 + VTERM_ATTR_FONT, // number: 10-19 + VTERM_ATTR_FOREGROUND, // color: 30-39 90-97 + VTERM_ATTR_BACKGROUND, // color: 40-49 100-107 +} VTermAttr; + +typedef enum { + /* VTERM_PROP_NONE = 0 */ + VTERM_PROP_CURSORVISIBLE = 1, // bool + VTERM_PROP_CURSORBLINK, // bool + VTERM_PROP_ALTSCREEN, // bool + VTERM_PROP_TITLE, // string + VTERM_PROP_ICONNAME, // string + VTERM_PROP_REVERSE, // bool + VTERM_PROP_CURSORSHAPE, // number +} VTermProp; + +enum { + VTERM_PROP_CURSORSHAPE_BLOCK = 1, + VTERM_PROP_CURSORSHAPE_UNDERLINE, +}; + +typedef void (*VTermMouseFunc)(int x, int y, int button, int pressed, int modifiers, void *data); + +typedef struct { + int (*putglyph)(const uint32_t chars[], int width, VTermPos pos, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*erase)(VTermRect rect, void *user); + int (*initpen)(void *user); + int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*setmousefunc)(VTermMouseFunc func, void *data, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, void *user); +} VTermStateCallbacks; + +typedef struct { + int (*damage)(VTermRect rect, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*setmousefunc)(VTermMouseFunc func, void *data, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, void *user); +} VTermScreenCallbacks; + +typedef struct { + /* libvterm relies on this memory to be zeroed out before it is returned + * by the allocator. */ + void *(*malloc)(size_t size, void *allocdata); + void (*free)(void *ptr, void *allocdata); +} VTermAllocatorFunctions; + +VTerm *vterm_new(int rows, int cols); +VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata); +void vterm_free(VTerm* vt); + +void vterm_get_size(VTerm *vt, int *rowsp, int *colsp); +void vterm_set_size(VTerm *vt, int rows, int cols); + +void vterm_set_parser_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user); + +VTermState *vterm_obtain_state(VTerm *vt); + +void vterm_state_reset(VTermState *state, int hard); +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); +void vterm_state_get_cursorpos(VTermState *state, VTermPos *cursorpos); +void vterm_state_set_default_colors(VTermState *state, VTermColor *default_fg, VTermColor *default_bg); +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); +int vterm_state_get_penattr(VTermState *state, VTermAttr attr, VTermValue *val); + +VTermValueType vterm_get_attr_type(VTermAttr attr); +VTermValueType vterm_get_prop_type(VTermProp prop); + +VTermScreen *vterm_obtain_screen(VTerm *vt); + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen); +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); + +typedef enum { + VTERM_DAMAGE_CELL, /* every cell */ + VTERM_DAMAGE_ROW, /* entire rows */ + VTERM_DAMAGE_SCREEN, /* entire screen */ + VTERM_DAMAGE_SCROLL, /* entire screen + scrollrect */ +} VTermDamageSize; + +void vterm_screen_flush_damage(VTermScreen *screen); +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size); + +void vterm_screen_reset(VTermScreen *screen, int hard); +size_t vterm_screen_get_chars(VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect); +size_t vterm_screen_get_text(VTermScreen *screen, char *str, size_t len, const VTermRect rect); + +typedef struct { +#define VTERM_MAX_CHARS_PER_CELL 6 + uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; + char width; + struct { + unsigned int bold : 1; + unsigned int underline : 2; + unsigned int italic : 1; + unsigned int blink : 1; + unsigned int reverse : 1; + unsigned int strike : 1; + unsigned int font : 4; /* 0 to 9 */ + } attrs; + VTermColor fg, bg; +} VTermScreenCell; + +void vterm_screen_get_cell(VTermScreen *screen, VTermPos pos, VTermScreenCell *cell); + +int vterm_screen_is_eol(VTermScreen *screen, VTermPos pos); + +void vterm_input_push_char(VTerm *vt, VTermModifier state, uint32_t c); +void vterm_input_push_key(VTerm *vt, VTermModifier state, VTermKey key); + +void vterm_parser_set_utf8(VTerm *vt, int is_utf8); +void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len); + +size_t vterm_output_bufferlen(VTerm *vt); /* deprecated */ + +size_t vterm_output_get_buffer_size(VTerm *vt); +size_t vterm_output_get_buffer_current(VTerm *vt); +size_t vterm_output_get_buffer_remaining(VTerm *vt); + +size_t vterm_output_bufferread(VTerm *vt, char *buffer, size_t len); + +void vterm_scroll_rect(VTermRect rect, + int downward, + int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, void *user), + void *user); + +void vterm_copy_cells(VTermRect dest, + VTermRect src, + void (*copycell)(VTermPos dest, VTermPos src, void *user), + void *user); + +#endif diff --git a/src/apps/serialconnect/libvterm/include/vterm_input.h b/src/apps/serialconnect/libvterm/include/vterm_input.h new file mode 100644 index 0000000000..69cc6cb914 --- /dev/null +++ b/src/apps/serialconnect/libvterm/include/vterm_input.h @@ -0,0 +1,39 @@ +#ifndef __VTERM_INPUT_H__ +#define __VTERM_INPUT_H__ + +typedef enum { + VTERM_MOD_NONE = 0x00, + VTERM_MOD_SHIFT = 0x01, + VTERM_MOD_ALT = 0x02, + VTERM_MOD_CTRL = 0x04, +} VTermModifier; + +typedef enum { + VTERM_KEY_NONE, + + VTERM_KEY_ENTER, + VTERM_KEY_TAB, + VTERM_KEY_BACKSPACE, + VTERM_KEY_ESCAPE, + + VTERM_KEY_UP, + VTERM_KEY_DOWN, + VTERM_KEY_LEFT, + VTERM_KEY_RIGHT, + + VTERM_KEY_INS, + VTERM_KEY_DEL, + VTERM_KEY_HOME, + VTERM_KEY_END, + VTERM_KEY_PAGEUP, + VTERM_KEY_PAGEDOWN, + + VTERM_KEY_FUNCTION_0, + VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255, + + VTERM_KEY_MAX, // Must be last +} VTermKey; + +#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n)) + +#endif diff --git a/src/apps/serialconnect/libvterm/src/encoding.c b/src/apps/serialconnect/libvterm/src/encoding.c new file mode 100644 index 0000000000..373c9bcd77 --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/encoding.c @@ -0,0 +1,221 @@ +#include "vterm_internal.h" + +#include + +#define UNICODE_INVALID 0xFFFD + +#ifdef DEBUG +# define DEBUG_PRINT_UTF8 +#endif + +struct UTF8DecoderData { + // number of bytes remaining in this codepoint + int bytes_remaining; + + // number of bytes total in this codepoint once it's finished + // (for detecting overlongs) + int bytes_total; + + int this_cp; +}; + +static void init_utf8(VTermEncoding *enc, void *data_) +{ + struct UTF8DecoderData *data = data_; + + data->bytes_remaining = 0; + data->bytes_total = 0; +} + +static void decode_utf8(VTermEncoding *enc, void *data_, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + struct UTF8DecoderData *data = data_; + +#ifdef DEBUG_PRINT_UTF8 + printf("BEGIN UTF-8\n"); +#endif + + for( ; *pos < bytelen; (*pos)++) { + unsigned char c = bytes[*pos]; + +#ifdef DEBUG_PRINT_UTF8 + printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining); +#endif + + if(c < 0x20) + return; + + else if(c >= 0x20 && c < 0x80) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + cp[(*cpi)++] = c; +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 char: U+%04x\n", c); +#endif + data->bytes_remaining = 0; + } + + else if(c >= 0x80 && c < 0xc0) { + if(!data->bytes_remaining) { + cp[(*cpi)++] = UNICODE_INVALID; + continue; + } + + data->this_cp <<= 6; + data->this_cp |= c & 0x3f; + data->bytes_remaining--; + + if(!data->bytes_remaining) { +#ifdef DEBUG_PRINT_UTF8 + printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total); +#endif + // Check for overlong sequences + switch(data->bytes_total) { + case 2: + if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID; break; + case 3: + if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID; break; + case 4: + if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID; break; + case 5: + if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID; break; + case 6: + if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID; break; + } + // Now look for plain invalid ones + if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) || + data->this_cp == 0xFFFE || + data->this_cp == 0xFFFF) + data->this_cp = UNICODE_INVALID; +#ifdef DEBUG_PRINT_UTF8 + printf(" char: U+%04x\n", data->this_cp); +#endif + cp[(*cpi)++] = data->this_cp; + } + } + + else if(c >= 0xc0 && c < 0xe0) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x1f; + data->bytes_total = 2; + data->bytes_remaining = 1; + } + + else if(c >= 0xe0 && c < 0xf0) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x0f; + data->bytes_total = 3; + data->bytes_remaining = 2; + } + + else if(c >= 0xf0 && c < 0xf8) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x07; + data->bytes_total = 4; + data->bytes_remaining = 3; + } + + else if(c >= 0xf8 && c < 0xfc) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x03; + data->bytes_total = 5; + data->bytes_remaining = 4; + } + + else if(c >= 0xfc && c < 0xfe) { + if(data->bytes_remaining) + cp[(*cpi)++] = UNICODE_INVALID; + + data->this_cp = c & 0x01; + data->bytes_total = 6; + data->bytes_remaining = 5; + } + + else { + cp[(*cpi)++] = UNICODE_INVALID; + } + } +} + +static VTermEncoding encoding_utf8 = { + .init = &init_utf8, + .decode = &decode_utf8, +}; + +static void decode_usascii(VTermEncoding *enc, void *data, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + for(; *pos < bytelen; (*pos)++) { + unsigned char c = bytes[*pos]; + + if(c < 0x20 || c >= 0x80) + return; + + cp[(*cpi)++] = c; + } +} + +static VTermEncoding encoding_usascii = { + .decode = &decode_usascii, +}; + +struct StaticTableEncoding { + const VTermEncoding enc; + const uint32_t chars[128]; +}; + +static void decode_table(VTermEncoding *enc, void *data, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t bytelen) +{ + struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc; + + for(; *pos < bytelen; (*pos)++) { + unsigned char c = (bytes[*pos]) & 0x7f; + + if(c < 0x20) + return; + + if(table->chars[c]) + cp[(*cpi)++] = table->chars[c]; + else + cp[(*cpi)++] = c; + } +} + +#include "encoding/DECdrawing.inc" +#include "encoding/uk.inc" + +static struct { + VTermEncodingType type; + char designation; + VTermEncoding *enc; +} +encodings[] = { + { ENC_UTF8, 'u', &encoding_utf8 }, + { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing }, + { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk }, + { ENC_SINGLE_94, 'B', &encoding_usascii }, + { 0, 0 }, +}; + +VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation) +{ + int i; + for(i = 0; encodings[i].designation; i++) + if(encodings[i].type == type && encodings[i].designation == designation) + return encodings[i].enc; + return NULL; +} diff --git a/src/apps/serialconnect/libvterm/src/encoding/DECdrawing.inc b/src/apps/serialconnect/libvterm/src/encoding/DECdrawing.inc new file mode 100644 index 0000000000..47093ed0a8 --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/encoding/DECdrawing.inc @@ -0,0 +1,36 @@ +static const struct StaticTableEncoding encoding_DECdrawing = { + { .decode = &decode_table }, + { + [0x60] = 0x25C6, + [0x61] = 0x2592, + [0x62] = 0x2409, + [0x63] = 0x240C, + [0x64] = 0x240D, + [0x65] = 0x240A, + [0x66] = 0x00B0, + [0x67] = 0x00B1, + [0x68] = 0x2424, + [0x69] = 0x240B, + [0x6a] = 0x2518, + [0x6b] = 0x2510, + [0x6c] = 0x250C, + [0x6d] = 0x2514, + [0x6e] = 0x253C, + [0x6f] = 0x23BA, + [0x70] = 0x23BB, + [0x71] = 0x2500, + [0x72] = 0x23BC, + [0x73] = 0x23BD, + [0x74] = 0x251C, + [0x75] = 0x2524, + [0x76] = 0x2534, + [0x77] = 0x252C, + [0x78] = 0x2502, + [0x79] = 0x2A7D, + [0x7a] = 0x2A7E, + [0x7b] = 0x03C0, + [0x7c] = 0x2260, + [0x7d] = 0x00A3, + [0x7e] = 0x00B7, + } +}; diff --git a/src/apps/serialconnect/libvterm/src/encoding/DECdrawing.tbl b/src/apps/serialconnect/libvterm/src/encoding/DECdrawing.tbl new file mode 100644 index 0000000000..6e19c5066e --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/encoding/DECdrawing.tbl @@ -0,0 +1,31 @@ +6/0 = U+25C6 # BLACK DIAMOND +6/1 = U+2592 # MEDIUM SHADE (checkerboard) +6/2 = U+2409 # SYMBOL FOR HORIZONTAL TAB +6/3 = U+240C # SYMBOL FOR FORM FEED +6/4 = U+240D # SYMBOL FOR CARRIAGE RETURN +6/5 = U+240A # SYMBOL FOR LINE FEED +6/6 = U+00B0 # DEGREE SIGN +6/7 = U+00B1 # PLUS-MINUS SIGN (plus or minus) +6/8 = U+2424 # SYMBOL FOR NEW LINE +6/9 = U+240B # SYMBOL FOR VERTICAL TAB +6/10 = U+2518 # BOX DRAWINGS LIGHT UP AND LEFT (bottom-right corner) +6/11 = U+2510 # BOX DRAWINGS LIGHT DOWN AND LEFT (top-right corner) +6/12 = U+250C # BOX DRAWINGS LIGHT DOWN AND RIGHT (top-left corner) +6/13 = U+2514 # BOX DRAWINGS LIGHT UP AND RIGHT (bottom-left corner) +6/14 = U+253C # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL (crossing lines) +6/15 = U+23BA # HORIZONTAL SCAN LINE-1 +7/0 = U+23BB # HORIZONTAL SCAN LINE-3 +7/1 = U+2500 # BOX DRAWINGS LIGHT HORIZONTAL +7/2 = U+23BC # HORIZONTAL SCAN LINE-7 +7/3 = U+23BD # HORIZONTAL SCAN LINE-9 +7/4 = U+251C # BOX DRAWINGS LIGHT VERTICAL AND RIGHT +7/5 = U+2524 # BOX DRAWINGS LIGHT VERTICAL AND LEFT +7/6 = U+2534 # BOX DRAWINGS LIGHT UP AND HORIZONTAL +7/7 = U+252C # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL +7/8 = U+2502 # BOX DRAWINGS LIGHT VERTICAL +7/9 = U+2A7D # LESS-THAN OR SLANTED EQUAL-TO +7/10 = U+2A7E # GREATER-THAN OR SLANTED EQUAL-TO +7/11 = U+03C0 # GREEK SMALL LETTER PI +7/12 = U+2260 # NOT EQUAL TO +7/13 = U+00A3 # POUND SIGN +7/14 = U+00B7 # MIDDLE DOT diff --git a/src/apps/serialconnect/libvterm/src/encoding/uk.inc b/src/apps/serialconnect/libvterm/src/encoding/uk.inc new file mode 100644 index 0000000000..da1445deca --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/encoding/uk.inc @@ -0,0 +1,6 @@ +static const struct StaticTableEncoding encoding_uk = { + { .decode = &decode_table }, + { + [0x23] = 0x00a3, + } +}; diff --git a/src/apps/serialconnect/libvterm/src/encoding/uk.tbl b/src/apps/serialconnect/libvterm/src/encoding/uk.tbl new file mode 100644 index 0000000000..b27b1a2193 --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/encoding/uk.tbl @@ -0,0 +1 @@ +2/3 = "£" diff --git a/src/apps/serialconnect/libvterm/src/input.c b/src/apps/serialconnect/libvterm/src/input.c new file mode 100644 index 0000000000..f85116284b --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/input.c @@ -0,0 +1,171 @@ +#include "vterm_internal.h" + +#include + +#include "utf8.h" + +void vterm_input_push_char(VTerm *vt, VTermModifier mod, uint32_t c) +{ + int needs_CSIu; + /* The shift modifier is never important for Unicode characters + * apart from Space + */ + if(c != ' ') + mod &= ~VTERM_MOD_SHIFT; + /* However, since Shift-Space is too easy to mistype accidentally, remove + * shift if it's the only modifier + */ + else if(mod == VTERM_MOD_SHIFT) + mod = 0; + + if(mod == 0) { + // Normal text - ignore just shift + char str[6]; + int seqlen = fill_utf8(c, str); + vterm_push_output_bytes(vt, str, seqlen); + return; + } + + switch(c) { + /* Special Ctrl- letters that can't be represented elsewise */ + case 'h': case 'i': case 'j': case 'm': case '[': + needs_CSIu = 1; + break; + /* Ctrl-\ ] ^ _ don't need CSUu */ + case '\\': case ']': case '^': case '_': + needs_CSIu = 0; + break; + /* All other characters needs CSIu except for letters a-z */ + default: + needs_CSIu = (c < 'a' || c > 'z'); + } + + /* ALT we can just prefix with ESC; anything else requires CSI u */ + if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) { + vterm_push_output_sprintf(vt, "\e[%d;%du", c, mod+1); + return; + } + + if(mod & VTERM_MOD_CTRL) + c &= 0x1f; + + vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? "\e" : "", c); +} + +typedef struct { + enum { + KEYCODE_NONE, + KEYCODE_LITERAL, + KEYCODE_TAB, + KEYCODE_ENTER, + KEYCODE_CSI, + KEYCODE_CSI_CURSOR, + KEYCODE_CSINUM, + } type; + char literal; + int csinum; +} keycodes_s; + +keycodes_s keycodes[] = { + { KEYCODE_NONE }, // NONE + + { KEYCODE_ENTER, '\r' }, // ENTER + { KEYCODE_TAB, '\t' }, // TAB + { KEYCODE_LITERAL, '\x7f' }, // BACKSPACE == ASCII DEL + { KEYCODE_LITERAL, '\e' }, // ESCAPE + + { KEYCODE_CSI_CURSOR, 'A' }, // UP + { KEYCODE_CSI_CURSOR, 'B' }, // DOWN + { KEYCODE_CSI_CURSOR, 'D' }, // LEFT + { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT + + { KEYCODE_CSINUM, '~', 2 }, // INS + { KEYCODE_CSINUM, '~', 3 }, // DEL + { KEYCODE_CSI_CURSOR, 'H' }, // HOME + { KEYCODE_CSI_CURSOR, 'F' }, // END + { KEYCODE_CSINUM, '~', 5 }, // PAGEUP + { KEYCODE_CSINUM, '~', 6 }, // PAGEDOWN + + { KEYCODE_NONE }, // F0 - shouldn't happen + { KEYCODE_CSI_CURSOR, 'P' }, // F1 + { KEYCODE_CSI_CURSOR, 'Q' }, // F2 + { KEYCODE_CSI_CURSOR, 'R' }, // F3 + { KEYCODE_CSI_CURSOR, 'S' }, // F4 + { KEYCODE_CSINUM, '~', 15 }, // F5 + { KEYCODE_CSINUM, '~', 17 }, // F6 + { KEYCODE_CSINUM, '~', 18 }, // F7 + { KEYCODE_CSINUM, '~', 19 }, // F8 + { KEYCODE_CSINUM, '~', 20 }, // F9 + { KEYCODE_CSINUM, '~', 21 }, // F10 + { KEYCODE_CSINUM, '~', 23 }, // F11 + { KEYCODE_CSINUM, '~', 24 }, // F12 +}; + +void vterm_input_push_key(VTerm *vt, VTermModifier mod, VTermKey key) +{ + keycodes_s k; + /* Since Shift-Enter and Shift-Backspace are too easy to mistype + * accidentally, remove shift if it's the only modifier + */ + if((key == VTERM_KEY_ENTER || key == VTERM_KEY_BACKSPACE) && mod == VTERM_MOD_SHIFT) + mod = 0; + + if(key == VTERM_KEY_NONE || key >= VTERM_KEY_MAX) + return; + + if(key >= sizeof(keycodes)/sizeof(keycodes[0])) + return; + + k = keycodes[key]; + + switch(k.type) { + case KEYCODE_NONE: + break; + + case KEYCODE_TAB: + /* Shift-Tab is CSI Z but plain Tab is 0x09 */ + if(mod == VTERM_MOD_SHIFT) + vterm_push_output_sprintf(vt, "\e[Z"); + else if(mod & VTERM_MOD_SHIFT) + vterm_push_output_sprintf(vt, "\e[1;%dZ", mod+1); + else + goto literal; + break; + + case KEYCODE_ENTER: + /* Enter is CRLF in newline mode, but just LF in linefeed */ + if(vt->state->mode.newline) + vterm_push_output_sprintf(vt, "\r\n"); + else + goto literal; + break; + +literal: + case KEYCODE_LITERAL: + if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) + vterm_push_output_sprintf(vt, "\e[%d;%du", k.literal, mod+1); + else + vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? "\e%c" : "%c", k.literal); + break; + + case KEYCODE_CSI_CURSOR: + if(vt->state->mode.cursor && mod == 0) { + vterm_push_output_sprintf(vt, "\eO%c", k.literal); + break; + } + /* else FALLTHROUGH */ + case KEYCODE_CSI: + if(mod == 0) + vterm_push_output_sprintf(vt, "\e[%c", k.literal); + else + vterm_push_output_sprintf(vt, "\e[1;%d%c", mod + 1, k.literal); + break; + + case KEYCODE_CSINUM: + if(mod == 0) + vterm_push_output_sprintf(vt, "\e[%d%c", k.csinum, k.literal); + else + vterm_push_output_sprintf(vt, "\e[%d;%d%c", k.csinum, mod + 1, k.literal); + break; + } +} diff --git a/src/apps/serialconnect/libvterm/src/parser.c b/src/apps/serialconnect/libvterm/src/parser.c new file mode 100644 index 0000000000..cfd7e2ca5f --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/parser.c @@ -0,0 +1,344 @@ +#include "vterm_internal.h" + +#include +#include +#include + +#define CSI_ARGS_MAX 16 +#define CSI_LEADER_MAX 16 +#define CSI_INTERMED_MAX 16 + +static void do_control(VTerm *vt, unsigned char control) +{ + if(vt->parser_callbacks && vt->parser_callbacks->control) + if((*vt->parser_callbacks->control)(control, vt->cbdata)) + return; + + fprintf(stderr, "libvterm: Unhandled control 0x%02x\n", control); +} + +static void do_string_csi(VTerm *vt, const char *args, size_t arglen, char command) +{ + size_t i = 0; + + int leaderlen = 0; + char leader[CSI_LEADER_MAX]; + int argcount = 1; // Always at least 1 arg + long csi_args[CSI_ARGS_MAX]; + int argi; + int intermedlen = 0; + char intermed[CSI_INTERMED_MAX]; + + // Extract leader bytes 0x3c to 0x3f + for( ; i < arglen; i++) { + if(args[i] < 0x3c || args[i] > 0x3f) + break; + if(leaderlen < CSI_LEADER_MAX-1) + leader[leaderlen++] = args[i]; + } + + leader[leaderlen] = 0; + + for( ; i < arglen; i++) + if(args[i] == 0x3b || args[i] == 0x3a) // ; or : + argcount++; + + /* TODO: Consider if these buffers should live in the VTerm struct itself */ + if(argcount > CSI_ARGS_MAX) + argcount = CSI_ARGS_MAX; + + for(argi = 0; argi < argcount; argi++) + csi_args[argi] = CSI_ARG_MISSING; + + argi = 0; + for(i = leaderlen; i < arglen && argi < argcount; i++) { + switch(args[i]) { + case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: + case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: + if(csi_args[argi] == CSI_ARG_MISSING) + csi_args[argi] = 0; + csi_args[argi] *= 10; + csi_args[argi] += args[i] - '0'; + break; + case 0x3a: + csi_args[argi] |= CSI_ARG_FLAG_MORE; + /* FALLTHROUGH */ + case 0x3b: + argi++; + break; + default: + goto done_leader; + } + } +done_leader: ; + + + for( ; i < arglen; i++) { + if((args[i] & 0xf0) != 0x20) + break; + + if(intermedlen < CSI_INTERMED_MAX-1) + intermed[intermedlen++] = args[i]; + } + + intermed[intermedlen] = 0; + + if(i < arglen) { + fprintf(stderr, "libvterm: TODO unhandled CSI bytes \"%.*s\"\n", (int)(arglen - i), args + i); + } + + //printf("Parsed CSI args %.*s as:\n", arglen, args); + //printf(" leader: %s\n", leader); + //for(argi = 0; argi < argcount; argi++) { + // printf(" %lu", CSI_ARG(csi_args[argi])); + // if(!CSI_ARG_HAS_MORE(csi_args[argi])) + // printf("\n"); + //printf(" intermed: %s\n", intermed); + //} + + if(vt->parser_callbacks && vt->parser_callbacks->csi) + if((*vt->parser_callbacks->csi)(leaderlen ? leader : NULL, csi_args, argcount, intermedlen ? intermed : NULL, command, vt->cbdata)) + return; + + fprintf(stderr, "libvterm: Unhandled CSI %.*s %c\n", (int)arglen, args, command); +} + +static void append_strbuffer(VTerm *vt, const char *str, size_t len) +{ + if(len > vt->strbuffer_len - vt->strbuffer_cur) { + len = vt->strbuffer_len - vt->strbuffer_cur; + fprintf(stderr, "Truncating strbuffer preserve to %zd bytes\n", len); + } + + if(len > 0) { + strncpy(vt->strbuffer + vt->strbuffer_cur, str, len); + vt->strbuffer_cur += len; + } +} + +static size_t do_string(VTerm *vt, const char *str_frag, size_t len) +{ + size_t eaten; + + if(vt->strbuffer_cur) { + if(str_frag) + append_strbuffer(vt, str_frag, len); + + str_frag = vt->strbuffer; + len = vt->strbuffer_cur; + } + else if(!str_frag) { + fprintf(stderr, "parser.c: TODO: No strbuffer _and_ no final fragment???\n"); + len = 0; + } + + vt->strbuffer_cur = 0; + + switch(vt->parser_state) { + case NORMAL: + if(vt->parser_callbacks && vt->parser_callbacks->text) + if((eaten = (*vt->parser_callbacks->text)(str_frag, len, vt->cbdata))) + return eaten; + + fprintf(stderr, "libvterm: Unhandled text (%zu chars)\n", len); + return 0; + + case ESC: + if(len == 1 && str_frag[0] >= 0x40 && str_frag[0] < 0x60) { + // C1 emulations using 7bit clean + // ESC 0x40 == 0x80 + do_control(vt, str_frag[0] + 0x40); + return 0; + } + + if(vt->parser_callbacks && vt->parser_callbacks->escape) + if((*vt->parser_callbacks->escape)(str_frag, len, vt->cbdata)) + return 0; + + fprintf(stderr, "libvterm: Unhandled escape ESC 0x%02x\n", str_frag[len-1]); + return 0; + + case CSI: + do_string_csi(vt, str_frag, len - 1, str_frag[len - 1]); + return 0; + + case OSC: + if(vt->parser_callbacks && vt->parser_callbacks->osc) + if((*vt->parser_callbacks->osc)(str_frag, len, vt->cbdata)) + return 0; + + fprintf(stderr, "libvterm: Unhandled OSC %.*s\n", (int)len, str_frag); + return 0; + + case DCS: + if(vt->parser_callbacks && vt->parser_callbacks->dcs) + if((*vt->parser_callbacks->dcs)(str_frag, len, vt->cbdata)) + return 0; + + fprintf(stderr, "libvterm: Unhandled DCS %.*s\n", (int)len, str_frag); + return 0; + + case ESC_IN_OSC: + case ESC_IN_DCS: + fprintf(stderr, "libvterm: ARGH! Should never do_string() in ESC_IN_{OSC,DCS}\n"); + return 0; + } + + return 0; +} + +void vterm_push_bytes(VTerm *vt, const char *bytes, size_t len) +{ + size_t pos = 0; + const char *string_start = NULL; + + switch(vt->parser_state) { + case NORMAL: + string_start = NULL; + break; + case ESC: + case ESC_IN_OSC: + case ESC_IN_DCS: + case CSI: + case OSC: + case DCS: + string_start = bytes; + break; + } + +#define ENTER_STRING_STATE(st) do { vt->parser_state = st; string_start = bytes + pos + 1; } while(0) +#define ENTER_NORMAL_STATE() do { vt->parser_state = NORMAL; string_start = NULL; } while(0) + + for( ; pos < len; pos++) { + unsigned char c = bytes[pos]; + + if(c == 0x00 || c == 0x7f) { // NUL, DEL + if(vt->parser_state != NORMAL) { + append_strbuffer(vt, string_start, bytes + pos - string_start); + string_start = bytes + pos + 1; + } + continue; + } + if(c == 0x18 || c == 0x1a) { // CAN, SUB + ENTER_NORMAL_STATE(); + continue; + } + else if(c == 0x1b) { // ESC + if(vt->parser_state == OSC) + vt->parser_state = ESC_IN_OSC; + else if(vt->parser_state == DCS) + vt->parser_state = ESC_IN_DCS; + else + ENTER_STRING_STATE(ESC); + continue; + } + else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state + (vt->parser_state == OSC || vt->parser_state == DCS)) { + // fallthrough + } + else if(c < 0x20) { // other C0 + if(vt->parser_state != NORMAL) + append_strbuffer(vt, string_start, bytes + pos - string_start); + do_control(vt, c); + if(vt->parser_state != NORMAL) + string_start = bytes + pos + 1; + continue; + } + // else fallthrough + + switch(vt->parser_state) { + case ESC_IN_OSC: + case ESC_IN_DCS: + if(c == 0x5c) { // ST + switch(vt->parser_state) { + case ESC_IN_OSC: vt->parser_state = OSC; break; + case ESC_IN_DCS: vt->parser_state = DCS; break; + default: break; + } + do_string(vt, string_start, bytes + pos - string_start - 1); + ENTER_NORMAL_STATE(); + break; + } + vt->parser_state = ESC; + string_start = bytes + pos; + // else fallthrough + + case ESC: + switch(c) { + case 0x50: // DCS + ENTER_STRING_STATE(DCS); + break; + case 0x5b: // CSI + ENTER_STRING_STATE(CSI); + break; + case 0x5d: // OSC + ENTER_STRING_STATE(OSC); + break; + default: + if(c >= 0x30 && c < 0x7f) { + /* +1 to pos because we want to include this command byte as well */ + do_string(vt, string_start, bytes + pos - string_start + 1); + ENTER_NORMAL_STATE(); + } + else if(c >= 0x20 && c < 0x30) { + /* intermediate byte */ + } + else { + fprintf(stderr, "TODO: Unhandled byte %02x in Escape\n", c); + } + } + break; + + case CSI: + if(c >= 0x40 && c <= 0x7f) { + /* +1 to pos because we want to include this command byte as well */ + do_string(vt, string_start, bytes + pos - string_start + 1); + ENTER_NORMAL_STATE(); + } + break; + + case OSC: + case DCS: + if(c == 0x07 || (c == 0x9c && !vt->is_utf8)) { + do_string(vt, string_start, bytes + pos - string_start); + ENTER_NORMAL_STATE(); + } + break; + + case NORMAL: + if(c >= 0x80 && c < 0xa0 && !vt->is_utf8) { + switch(c) { + case 0x90: // DCS + ENTER_STRING_STATE(DCS); + break; + case 0x9b: // CSI + ENTER_STRING_STATE(CSI); + break; + case 0x9d: // OSC + ENTER_STRING_STATE(OSC); + break; + default: + do_control(vt, c); + break; + } + } + else { + size_t text_eaten = do_string(vt, bytes + pos, len - pos); + + if(text_eaten == 0) { + string_start = bytes + pos; + goto pause; + } + + pos += (text_eaten - 1); // we'll ++ it again in a moment + } + break; + } + } + +pause: + if(string_start && string_start < len + bytes) { + size_t remaining = len - (string_start - bytes); + append_strbuffer(vt, string_start, remaining); + } +} diff --git a/src/apps/serialconnect/libvterm/src/pen.c b/src/apps/serialconnect/libvterm/src/pen.c new file mode 100644 index 0000000000..3f42b6051f --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/pen.c @@ -0,0 +1,373 @@ +#include "vterm_internal.h" + +#include + +static const VTermColor ansi_colors[] = { + /* R G B */ + { 0, 0, 0 }, // black + { 224, 0, 0 }, // red + { 0, 224, 0 }, // green + { 224, 224, 0 }, // yellow + { 0, 0, 224 }, // blue + { 224, 0, 224 }, // magenta + { 0, 224, 224 }, // cyan + { 224, 224, 224 }, // white == light grey + + // high intensity + { 128, 128, 128 }, // black + { 255, 64, 64 }, // red + { 64, 255, 64 }, // green + { 255, 255, 64 }, // yellow + { 64, 64, 255 }, // blue + { 255, 64, 255 }, // magenta + { 64, 255, 255 }, // cyan + { 255, 255, 255 }, // white for real +}; + +static int ramp6[] = { + 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF, +}; + +static int ramp24[] = { + 0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79, + 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF, +}; + +static void lookup_colour_ansi(long index, char is_bg, VTermColor *col) +{ + if(index >= 0 && index < 16) { + *col = ansi_colors[index]; + } +} + +static int lookup_colour(int palette, const long args[], int argcount, char is_bg, VTermColor *col) +{ + long index; + + switch(palette) { + case 2: // RGB mode - 3 args contain colour values directly + if(argcount < 3) + return argcount; + + col->red = CSI_ARG(args[0]); + col->green = CSI_ARG(args[1]); + col->blue = CSI_ARG(args[2]); + + return 3; + + case 5: // XTerm 256-colour mode + index = argcount ? CSI_ARG_OR(args[0], -1) : -1; + + if(index >= 0 && index < 16) { + // Normal 8 colours or high intensity - parse as palette 0 + lookup_colour_ansi(index, is_bg, col); + } + else if(index >= 16 && index < 232) { + // 216-colour cube + index -= 16; + + col->blue = ramp6[index % 6]; + col->green = ramp6[index/6 % 6]; + col->red = ramp6[index/6/6 % 6]; + } + else if(index >= 232 && index < 256) { + // 24 greyscales + index -= 232; + + col->red = ramp24[index]; + col->green = ramp24[index]; + col->blue = ramp24[index]; + } + + return argcount ? 1 : 0; + + default: + fprintf(stderr, "Unrecognised colour palette %d\n", palette); + return 0; + } +} + +// Some conveniences + +static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) +{ +#ifdef DEBUG + if(type != vterm_get_attr_type(attr)) { + fprintf(stderr, "Cannot set attr %d as it has type %d, not type %d\n", + attr, vterm_get_attr_type(attr), type); + return; + } +#endif + if(state->callbacks && state->callbacks->setpenattr) + (*state->callbacks->setpenattr)(attr, val, state->cbdata); +} + +static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) +{ + VTermValue val = { .boolean = boolean }; + setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val); +} + +static void setpenattr_int(VTermState *state, VTermAttr attr, int number) +{ + VTermValue val = { .number = number }; + setpenattr(state, attr, VTERM_VALUETYPE_INT, &val); +} + +static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) +{ + VTermValue val = { .color = color }; + setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val); +} + +static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) +{ + VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; + + lookup_colour_ansi(col, attr == VTERM_ATTR_BACKGROUND, colp); + + setpenattr_col(state, attr, *colp); +} + +void vterm_state_resetpen(VTermState *state) +{ + state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + state->pen.underline = 0; setpenattr_int( state, VTERM_ATTR_UNDERLINE, 0); + state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + state->pen.font = 0; setpenattr_int( state, VTERM_ATTR_FONT, 0); + + state->fg_ansi = -1; + state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); + state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); +} + +void vterm_state_savepen(VTermState *state, int save) +{ + if(save) { + state->saved.pen = state->pen; + } + else { + state->pen = state->saved.pen; + + setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold); + setpenattr_int( state, VTERM_ATTR_UNDERLINE, state->pen.underline); + setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic); + setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink); + setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse); + setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike); + setpenattr_int( state, VTERM_ATTR_FONT, state->pen.font); + setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg); + setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg); + } +} + +void vterm_state_set_default_colors(VTermState *state, VTermColor *default_fg, VTermColor *default_bg) +{ + state->default_fg = *default_fg; + state->default_bg = *default_bg; +} + +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) +{ + state->bold_is_highbright = bold_is_highbright; +} + +void vterm_state_setpen(VTermState *state, const long args[], int argcount) +{ + // SGR - ECMA-48 8.3.117 + + int argi = 0; + int value; + + while(argi < argcount) { + // This logic is easier to do 'done' backwards; set it true, and make it + // false again in the 'default' case + int done = 1; + + long arg; + switch(arg = CSI_ARG(args[argi])) { + case CSI_ARG_MISSING: + case 0: // Reset + vterm_state_resetpen(state); + break; + + case 1: // Bold on + state->pen.bold = 1; + setpenattr_bool(state, VTERM_ATTR_BOLD, 1); + if(state->fg_ansi > -1 && state->bold_is_highbright) + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, state->fg_ansi + (state->pen.bold ? 8 : 0)); + break; + + case 3: // Italic on + state->pen.italic = 1; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 1); + break; + + case 4: // Underline single + state->pen.underline = 1; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, 1); + break; + + case 5: // Blink + state->pen.blink = 1; + setpenattr_bool(state, VTERM_ATTR_BLINK, 1); + break; + + case 7: // Reverse on + state->pen.reverse = 1; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 1); + break; + + case 9: // Strikethrough on + state->pen.strike = 1; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 1); + break; + + case 10: case 11: case 12: case 13: case 14: + case 15: case 16: case 17: case 18: case 19: // Select font + state->pen.font = CSI_ARG(args[argi]) - 10; + setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); + break; + + case 21: // Underline double + state->pen.underline = 2; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, 2); + break; + + case 22: // Bold off + state->pen.bold = 0; + setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + break; + + case 23: // Italic and Gothic (currently unsupported) off + state->pen.italic = 0; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + break; + + case 24: // Underline off + state->pen.underline = 0; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); + break; + + case 25: // Blink off + state->pen.blink = 0; + setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + break; + + case 27: // Reverse off + state->pen.reverse = 0; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + break; + + case 29: // Strikethrough off + state->pen.strike = 0; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + break; + + case 30: case 31: case 32: case 33: + case 34: case 35: case 36: case 37: // Foreground colour palette + value = CSI_ARG(args[argi]) - 30; + state->fg_ansi = value; + if(state->pen.bold && state->bold_is_highbright) + value += 8; + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 38: // Foreground colour alternative palette + state->fg_ansi = -1; + if(argcount - argi < 1) + return; + argi += 1 + lookup_colour(CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, 0, &state->pen.fg); + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 39: // Foreground colour default + state->fg_ansi = -1; + state->pen.fg = state->default_fg; + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 40: case 41: case 42: case 43: + case 44: case 45: case 46: case 47: // Background colour palette + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, CSI_ARG(args[argi]) - 40); + break; + + case 48: // Background colour alternative palette + if(argcount - argi < 1) + return; + argi += 1 + lookup_colour(CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, 1, &state->pen.bg); + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 49: // Default background + state->pen.bg = state->default_bg; + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 90: case 91: case 92: case 93: + case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, CSI_ARG(args[argi]) - 90 + 8); + break; + + case 100: case 101: case 102: case 103: + case 104: case 105: case 106: case 107: // Background colour high-intensity palette + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, CSI_ARG(args[argi]) - 100 + 8); + break; + + default: + done = 0; + break; + } + + if(!done) + fprintf(stderr, "libvterm: Unhandled CSI SGR %lu\n", arg); + + while(CSI_ARG_HAS_MORE(args[argi++])); + } +} + +int vterm_state_get_penattr(VTermState *state, VTermAttr attr, VTermValue *val) +{ + switch(attr) { + case VTERM_ATTR_BOLD: + val->boolean = state->pen.bold; + return 1; + + case VTERM_ATTR_UNDERLINE: + val->number = state->pen.underline; + return 1; + + case VTERM_ATTR_ITALIC: + val->boolean = state->pen.italic; + return 1; + + case VTERM_ATTR_BLINK: + val->boolean = state->pen.blink; + return 1; + + case VTERM_ATTR_REVERSE: + val->boolean = state->pen.reverse; + return 1; + + case VTERM_ATTR_STRIKE: + val->boolean = state->pen.strike; + return 1; + + case VTERM_ATTR_FONT: + val->number = state->pen.font; + return 1; + + case VTERM_ATTR_FOREGROUND: + val->color = state->pen.fg; + return 1; + + case VTERM_ATTR_BACKGROUND: + val->color = state->pen.bg; + return 1; + } + + return 0; +} diff --git a/src/apps/serialconnect/libvterm/src/rect.h b/src/apps/serialconnect/libvterm/src/rect.h new file mode 100644 index 0000000000..2114f24c1b --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/rect.h @@ -0,0 +1,56 @@ +/* + * Some utility functions on VTermRect structures + */ + +#define STRFrect "(%d,%d-%d,%d)" +#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col + +/* Expand dst to contain src as well */ +static void rect_expand(VTermRect *dst, VTermRect *src) +{ + if(dst->start_row > src->start_row) dst->start_row = src->start_row; + if(dst->start_col > src->start_col) dst->start_col = src->start_col; + if(dst->end_row < src->end_row) dst->end_row = src->end_row; + if(dst->end_col < src->end_col) dst->end_col = src->end_col; +} + +/* Clip the dst to ensure it does not step outside of bounds */ +static void rect_clip(VTermRect *dst, VTermRect *bounds) +{ + if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row; + if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col; + if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row; + if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col; + /* Ensure it doesn't end up negatively-sized */ + if(dst->end_row < dst->start_row) dst->end_row = dst->start_row; + if(dst->end_col < dst->start_col) dst->end_col = dst->start_col; +} + +/* True if the two rectangles are equal */ +static int rect_equal(VTermRect *a, VTermRect *b) +{ + return (a->start_row == b->start_row) && + (a->start_col == b->start_col) && + (a->end_row == b->end_row) && + (a->end_col == b->end_col); +} + +/* True if small is contained entirely within big */ +static int rect_contains(VTermRect *big, VTermRect *small) +{ + if(small->start_row < big->start_row) return 0; + if(small->start_col < big->start_col) return 0; + if(small->end_row > big->end_row) return 0; + if(small->end_col > big->end_col) return 0; + return 1; +} + +/* True if the rectangles overlap at all */ +static int rect_intersects(VTermRect *a, VTermRect *b) +{ + if(a->start_row > b->end_row || b->start_row > a->end_row) + return 0; + if(a->start_col > b->end_col || b->start_col > a->end_col) + return 0; + return 1; +} diff --git a/src/apps/serialconnect/libvterm/src/screen.c b/src/apps/serialconnect/libvterm/src/screen.c new file mode 100644 index 0000000000..439d586530 --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/screen.c @@ -0,0 +1,664 @@ +#include "vterm_internal.h" + +#include + +#include "rect.h" +#include "utf8.h" + +#define UNICODE_SPACE 0x20 +#define UNICODE_LINEFEED 0x0a + +/* State of the pen at some moment in time, also used in a cell */ +typedef struct +{ + /* After the bitfield */ + VTermColor fg, bg; + + unsigned int bold : 1; + unsigned int underline : 2; + unsigned int italic : 1; + unsigned int blink : 1; + unsigned int reverse : 1; + unsigned int strike : 1; + unsigned int font : 4; /* 0 to 9 */ +} ScreenPen; + +/* Internal representation of a screen cell */ +typedef struct +{ + uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; + ScreenPen pen; +} ScreenCell; + +struct VTermScreen +{ + VTerm *vt; + VTermState *state; + + const VTermScreenCallbacks *callbacks; + void *cbdata; + + VTermDamageSize damage_merge; + /* start_row == -1 => no damage */ + VTermRect damaged; + VTermRect pending_scrollrect; + int pending_scroll_downward, pending_scroll_rightward; + + int rows; + int cols; + int global_reverse; + + /* Primary and Altscreen. buffers[1] is lazily allocated as needed */ + ScreenCell *buffers[2]; + + /* buffer will == buffers[0] or buffers[1], depending on altscreen */ + ScreenCell *buffer; + + ScreenPen pen; +}; + +static inline ScreenCell *getcell(VTermScreen *screen, int row, int col) +{ + /* TODO: Bounds checking */ + return screen->buffer + (screen->cols * row) + col; +} + +static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols) +{ + ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols); + int row, col; + + for(row = 0; row < new_rows; row++) { + for(col = 0; col < new_cols; col++) { + ScreenCell *new_cell = new_buffer + row*new_cols + col; + + if(buffer && row < screen->rows && col < screen->cols) + *new_cell = buffer[row * screen->cols + col]; + else { + new_cell->chars[0] = 0; + new_cell->pen = screen->pen; + } + } + } + + if(buffer) + vterm_allocator_free(screen->vt, buffer); + + return new_buffer; +} + +static void damagerect(VTermScreen *screen, VTermRect rect) +{ + VTermRect emit; + + switch(screen->damage_merge) { + case VTERM_DAMAGE_CELL: + /* Always emit damage event */ + emit = rect; + break; + + case VTERM_DAMAGE_ROW: + /* Emit damage longer than one row. Try to merge with existing damage in + * the same row */ + if(rect.end_row > rect.start_row + 1) { + // Bigger than 1 line - flush existing, emit this + vterm_screen_flush_damage(screen); + emit = rect; + } + else if(screen->damaged.start_row == -1) { + // None stored yet + screen->damaged = rect; + return; + } + else if(rect.start_row == screen->damaged.start_row) { + // Merge with the stored line + if(screen->damaged.start_col > rect.start_col) + screen->damaged.start_col = rect.start_col; + if(screen->damaged.end_col < rect.end_col) + screen->damaged.end_col = rect.end_col; + return; + } + else { + // Emit the currently stored line, store a new one + emit = screen->damaged; + screen->damaged = rect; + } + break; + + case VTERM_DAMAGE_SCREEN: + case VTERM_DAMAGE_SCROLL: + /* Never emit damage event */ + if(screen->damaged.start_row == -1) + screen->damaged = rect; + else { + rect_expand(&screen->damaged, &rect); + } + return; + + default: + fprintf(stderr, "TODO: Maybe merge damage for level %d\n", screen->damage_merge); + return; + } + + if(screen->callbacks && screen->callbacks->damage) + (*screen->callbacks->damage)(emit, screen->cbdata); +} + +static void damagescreen(VTermScreen *screen) +{ + VTermRect rect = { + .start_row = 0, + .end_row = screen->rows, + .start_col = 0, + .end_col = screen->cols, + }; + + damagerect(screen, rect); +} + +static int putglyph(const uint32_t chars[], int width, VTermPos pos, void *user) +{ + VTermScreen *screen = user; + ScreenCell *cell = getcell(screen, pos.row, pos.col); + int i; + int col; + + VTermRect rect = { + .start_row = pos.row, + .end_row = pos.row+1, + .start_col = pos.col, + .end_col = pos.col+width, + }; + + for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && chars[i]; i++) { + cell->chars[i] = chars[i]; + cell->pen = screen->pen; + } + if(i < VTERM_MAX_CHARS_PER_CELL) + cell->chars[i] = 0; + + for(col = 1; col < width; col++) + getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1; + + damagerect(screen, rect); + + return 1; +} + +static void copycell(VTermPos dest, VTermPos src, void *user) +{ + VTermScreen *screen = user; + ScreenCell *destcell = getcell(screen, dest.row, dest.col); + ScreenCell *srccell = getcell(screen, src.row, src.col); + + *destcell = *srccell; +} + +static int moverect_internal(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + + vterm_copy_cells(dest, src, ©cell, screen); + + return 1; +} + +static int moverect_user(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->moverect) { + if(screen->damage_merge != VTERM_DAMAGE_SCROLL) + // Avoid an infinite loop + vterm_screen_flush_damage(screen); + + if((*screen->callbacks->moverect)(dest, src, screen->cbdata)) + return 1; + } + + damagerect(screen, dest); + + return 1; +} + +static int erase_internal(VTermRect rect, void *user) +{ + VTermScreen *screen = user; + int row, col; + + for(row = rect.start_row; row < rect.end_row; row++) + for(col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + cell->chars[0] = 0; + cell->pen = screen->pen; + } + + return 1; +} + +static int erase_user(VTermRect rect, void *user) +{ + VTermScreen *screen = user; + + damagerect(screen, rect); + + return 1; +} + +static int erase(VTermRect rect, void *user) +{ + erase_internal(rect, user); + return erase_user(rect, user); +} + +static int scrollrect(VTermRect rect, int downward, int rightward, void *user) +{ + VTermScreen *screen = user; + + vterm_scroll_rect(rect, downward, rightward, + moverect_internal, erase_internal, screen); + + if(screen->damage_merge == VTERM_DAMAGE_SCROLL) { + if(screen->damaged.start_row != -1 && + !rect_intersects(&rect, &screen->damaged)) { + vterm_screen_flush_damage(screen); + } + + if(screen->pending_scrollrect.start_row == -1) { + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } + else if(rect_equal(&screen->pending_scrollrect, &rect) && + ((screen->pending_scroll_downward == 0 && downward == 0) || + (screen->pending_scroll_rightward == 0 && rightward == 0))) { + screen->pending_scroll_downward += downward; + screen->pending_scroll_rightward += rightward; + } + else { + vterm_screen_flush_damage(screen); + + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } + + if(screen->damaged.start_row != -1) { + if(rect_contains(&rect, &screen->damaged)) { + vterm_rect_move(&screen->damaged, -downward, -rightward); + rect_clip(&screen->damaged, &rect); + } + else { + fprintf(stderr, "TODO: scrollrect split damage\n"); + } + } + + return 1; + } + + vterm_screen_flush_damage(screen); + + vterm_scroll_rect(rect, downward, rightward, + moverect_user, erase_user, screen); + + return 1; +} + +static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->movecursor) + return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); + + return 0; +} + +static int setpenattr(VTermAttr attr, VTermValue *val, void *user) +{ + VTermScreen *screen = user; + + switch(attr) { + case VTERM_ATTR_BOLD: + screen->pen.bold = val->boolean; + return 1; + case VTERM_ATTR_UNDERLINE: + screen->pen.underline = val->number; + return 1; + case VTERM_ATTR_ITALIC: + screen->pen.italic = val->boolean; + return 1; + case VTERM_ATTR_BLINK: + screen->pen.blink = val->boolean; + return 1; + case VTERM_ATTR_REVERSE: + screen->pen.reverse = val->boolean; + return 1; + case VTERM_ATTR_STRIKE: + screen->pen.strike = val->boolean; + return 1; + case VTERM_ATTR_FONT: + screen->pen.font = val->number; + return 1; + case VTERM_ATTR_FOREGROUND: + screen->pen.fg = val->color; + return 1; + case VTERM_ATTR_BACKGROUND: + screen->pen.bg = val->color; + return 1; + } + + return 0; +} + +static int settermprop(VTermProp prop, VTermValue *val, void *user) +{ + VTermScreen *screen = user; + + switch(prop) { + case VTERM_PROP_ALTSCREEN: + if(val->boolean && !screen->buffers[1]) + return 0; + + screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0]; + /* only send a damage event on disable; because during enable there's an + * erase that sends a damage anyway + */ + if(!val->boolean) + damagescreen(screen); + break; + case VTERM_PROP_REVERSE: + screen->global_reverse = val->boolean; + damagescreen(screen); + break; + default: + ; /* ignore */ + } + + if(screen->callbacks && screen->callbacks->settermprop) + return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); + + return 1; +} + +static int setmousefunc(VTermMouseFunc func, void *data, void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->setmousefunc) + return (*screen->callbacks->setmousefunc)(func, data, screen->cbdata); + + return 0; +} + +static int bell(void *user) +{ + VTermScreen *screen = user; + + if(screen->callbacks && screen->callbacks->bell) + return (*screen->callbacks->bell)(screen->cbdata); + + return 0; +} + +static int resize(int new_rows, int new_cols, void *user) +{ + VTermScreen *screen = user; + int old_rows, old_cols; + + int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]); + + screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols); + if(screen->buffers[1]) + screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols); + + screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0]; + + old_rows = screen->rows; + old_cols = screen->cols; + + screen->rows = new_rows; + screen->cols = new_cols; + + if(new_cols > old_cols) { + VTermRect rect = { + .start_row = 0, + .end_row = old_rows, + .start_col = old_cols, + .end_col = new_cols, + }; + damagerect(screen, rect); + } + + if(new_rows > old_rows) { + VTermRect rect = { + .start_row = old_rows, + .end_row = new_rows, + .start_col = 0, + .end_col = new_cols, + }; + damagerect(screen, rect); + } + + if(screen->callbacks && screen->callbacks->resize) + return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); + + return 1; +} + +static VTermStateCallbacks state_cbs = { + .putglyph = &putglyph, + .movecursor = &movecursor, + .scrollrect = &scrollrect, + .erase = &erase, + .setpenattr = &setpenattr, + .settermprop = &settermprop, + .setmousefunc = &setmousefunc, + .bell = &bell, + .resize = &resize, +}; + +static VTermScreen *screen_new(VTerm *vt) +{ + VTermState *state = vterm_obtain_state(vt); + VTermScreen *screen; + int rows, cols; + + if(!state) + return NULL; + + screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); + + vterm_get_size(vt, &rows, &cols); + + screen->vt = vt; + screen->state = state; + + screen->damage_merge = VTERM_DAMAGE_CELL; + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + + screen->rows = rows; + screen->cols = cols; + + screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols); + + screen->buffer = screen->buffers[0]; + + vterm_state_set_callbacks(screen->state, &state_cbs, screen); + + return screen; +} + +void vterm_screen_free(VTermScreen *screen) +{ + vterm_allocator_free(screen->vt, screen->buffers[0]); + if(screen->buffers[1]) + vterm_allocator_free(screen->vt, screen->buffers[1]); + + vterm_allocator_free(screen->vt, screen); +} + +void vterm_screen_reset(VTermScreen *screen, int hard) +{ + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + vterm_state_reset(screen->state, hard); + vterm_screen_flush_damage(screen); +} + +static size_t _get_chars(VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect) +{ + size_t outpos = 0; + int padding = 0; + int row, col; + int i; + +#define PUT(c) \ + if(utf8) { \ + size_t thislen = utf8_seqlen(c); \ + if(buffer && outpos + thislen <= len) \ + outpos += fill_utf8((c), (char *)buffer + outpos); \ + else \ + outpos += thislen; \ + } \ + else { \ + if(buffer && outpos + 1 <= len) \ + ((uint32_t*)buffer)[outpos++] = (c); \ + else \ + outpos++; \ + } + + for(row = rect.start_row; row < rect.end_row; row++) { + for(col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if(cell->chars[0] == 0) + // Erased cell, might need a space + padding++; + else if(cell->chars[0] == (uint32_t)-1) + // Gap behind a double-width char, do nothing + ; + else { + while(padding) { + PUT(UNICODE_SPACE); + padding--; + } + for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) { + PUT(cell->chars[i]); + } + } + } + + if(row < rect.end_row - 1) { + PUT(UNICODE_LINEFEED); + padding = 0; + } + } + + return outpos; +} + +size_t vterm_screen_get_chars(VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect) +{ + return _get_chars(screen, 0, chars, len, rect); +} + +size_t vterm_screen_get_text(VTermScreen *screen, char *str, size_t len, const VTermRect rect) +{ + return _get_chars(screen, 1, str, len, rect); +} + +/* Copy internal to external representation of a screen cell */ +void vterm_screen_get_cell(VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) +{ + ScreenCell *intcell = getcell(screen, pos.row, pos.col); + int i; + + for(i = 0; ; i++) { + cell->chars[i] = intcell->chars[i]; + if(!intcell->chars[i]) + break; + } + + cell->attrs.bold = intcell->pen.bold; + cell->attrs.underline = intcell->pen.underline; + cell->attrs.italic = intcell->pen.italic; + cell->attrs.blink = intcell->pen.blink; + cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; + cell->attrs.strike = intcell->pen.strike; + cell->attrs.font = intcell->pen.font; + + cell->fg = intcell->pen.fg; + cell->bg = intcell->pen.bg; + + if(pos.col < (screen->cols - 1) && + getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1) + cell->width = 2; + else + cell->width = 1; +} + +int vterm_screen_is_eol(VTermScreen *screen, VTermPos pos) +{ + /* This cell is EOL if this and every cell to the right is black */ + for(; pos.col < screen->cols; pos.col++) { + ScreenCell *cell = getcell(screen, pos.row, pos.col); + if(cell->chars[0] != 0) + return 0; + } + + return 1; +} + +VTermScreen *vterm_obtain_screen(VTerm *vt) +{ + VTermScreen *screen; + if(vt->screen) + return vt->screen; + + screen = screen_new(vt); + vt->screen = screen; + + return screen; +} + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) +{ + + if(!screen->buffers[1] && altscreen) { + int rows, cols; + vterm_get_size(screen->vt, &rows, &cols); + + screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols); + } +} + +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user) +{ + screen->callbacks = callbacks; + screen->cbdata = user; +} + +void vterm_screen_flush_damage(VTermScreen *screen) +{ + if(screen->pending_scrollrect.start_row != -1) { + vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward, + moverect_user, erase_user, screen); + + screen->pending_scrollrect.start_row = -1; + } + + if(screen->damaged.start_row != -1) { + if(screen->callbacks && screen->callbacks->damage) + (*screen->callbacks->damage)(screen->damaged, screen->cbdata); + + screen->damaged.start_row = -1; + } +} + +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) +{ + vterm_screen_flush_damage(screen); + screen->damage_merge = size; +} diff --git a/src/apps/serialconnect/libvterm/src/state.c b/src/apps/serialconnect/libvterm/src/state.c new file mode 100644 index 0000000000..47c0a49575 --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/state.c @@ -0,0 +1,1416 @@ +#include "vterm_internal.h" + +#include +#include + +#define strneq(a,b,n) (strncmp(a,b,n)==0) + +#include "utf8.h" + +#ifdef DEBUG +# define DEBUG_GLYPH_COMBINE +#endif + +#define MOUSE_WANT_DRAG 0x01 +#define MOUSE_WANT_MOVE 0x02 + +/* Some convenient wrappers to make callback functions easier */ + +static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos) +{ + if(state->callbacks && state->callbacks->putglyph) + if((*state->callbacks->putglyph)(chars, width, pos, state->cbdata)) + return; + + fprintf(stderr, "libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); +} + +static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) +{ + if(state->pos.col == oldpos->col && state->pos.row == oldpos->row) + return; + + if(cancel_phantom) + state->at_phantom = 0; + + if(state->callbacks && state->callbacks->movecursor) + if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata)) + return; +} + +static void erase(VTermState *state, VTermRect rect) +{ + if(state->callbacks && state->callbacks->erase) + if((*state->callbacks->erase)(rect, state->cbdata)) + return; +} + +static VTermState *vterm_state_new(VTerm *vt) +{ + VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); + + state->vt = vt; + + state->rows = vt->rows; + state->cols = vt->cols; + + // 90% grey so that pure white is brighter + state->default_fg.red = state->default_fg.green = state->default_fg.blue = 240; + state->default_bg.red = state->default_bg.green = state->default_bg.blue = 0; + + state->bold_is_highbright = 0; + + return state; +} + +void vterm_state_free(VTermState *state) +{ + vterm_allocator_free(state->vt, state->combine_chars); + vterm_allocator_free(state->vt, state); +} + +static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) +{ + if(!downward && !rightward) + return; + + if(state->callbacks && state->callbacks->scrollrect) + if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) + return; + + if(state->callbacks) + vterm_scroll_rect(rect, downward, rightward, + state->callbacks->moverect, state->callbacks->erase, state->cbdata); +} + +static void linefeed(VTermState *state) +{ + if(state->pos.row == SCROLLREGION_END(state) - 1) { + VTermRect rect = { + .start_row = state->scrollregion_start, + .end_row = SCROLLREGION_END(state), + .start_col = 0, + .end_col = state->cols, + }; + + scroll(state, rect, 1, 0); + } + else if(state->pos.row < state->rows-1) + state->pos.row++; +} + +static void grow_combine_buffer(VTermState *state) +{ + size_t new_size = state->combine_chars_size * 2; + uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0])); + + memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0])); + + vterm_allocator_free(state->vt, state->combine_chars); + state->combine_chars = new_chars; +} + +static void set_col_tabstop(VTermState *state, int col) +{ + unsigned char mask = 1 << (col & 7); + state->tabstops[col >> 3] |= mask; +} + +static void clear_col_tabstop(VTermState *state, int col) +{ + unsigned char mask = 1 << (col & 7); + state->tabstops[col >> 3] &= ~mask; +} + +static int is_col_tabstop(VTermState *state, int col) +{ + unsigned char mask = 1 << (col & 7); + return state->tabstops[col >> 3] & mask; +} + +static void tab(VTermState *state, int count, int direction) +{ + while(count--) + while(state->pos.col >= 0 && state->pos.col < state->cols-1) { + state->pos.col += direction; + + if(is_col_tabstop(state, state->pos.col)) + break; + } +} + +static int on_text(const char bytes[], size_t len, void *user) +{ + VTermState *state = user; + uint32_t* chars; + + VTermPos oldpos = state->pos; + + // We'll have at most len codepoints + uint32_t codepoints[len]; + int npoints = 0; + size_t eaten = 0; + int i = 0; + + VTermEncodingInstance *encoding = + !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] : + state->vt->is_utf8 ? &state->encoding_utf8 : + &state->encoding[state->gr_set]; + + (*encoding->enc->decode)(encoding->enc, encoding->data, + codepoints, &npoints, len, bytes, &eaten, len); + + /* This is a combining char. that needs to be merged with the previous + * glyph output */ + if(vterm_unicode_is_combining(codepoints[i])) { + /* See if the cursor has moved since */ + if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) { + size_t saved_i = 0; +#ifdef DEBUG_GLYPH_COMBINE + int printpos; + printf("DEBUG: COMBINING SPLIT GLYPH of chars {"); + for(printpos = 0; state->combine_chars[printpos]; printpos++) + printf("U+%04x ", state->combine_chars[printpos]); + printf("} + {"); +#endif + + /* Find where we need to append these combining chars */ + while(state->combine_chars[saved_i]) + saved_i++; + + /* Add extra ones */ + while(i < npoints && vterm_unicode_is_combining(codepoints[i])) { + if(saved_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[saved_i++] = codepoints[i++]; + } + if(saved_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[saved_i] = 0; + +#ifdef DEBUG_GLYPH_COMBINE + for(; state->combine_chars[printpos]; printpos++) + printf("U+%04x ", state->combine_chars[printpos]); + printf("}\n"); +#endif + + /* Now render it */ + putglyph(state, state->combine_chars, state->combine_width, state->combine_pos); + } + else { + fprintf(stderr, "libvterm: TODO: Skip over split char+combining\n"); + } + } + + for(; i < npoints; i++) { + // Try to find combining characters following this + int glyph_starts = i; + int glyph_ends; + int width = 0; + + for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++) + if(!vterm_unicode_is_combining(codepoints[glyph_ends])) + break; + + chars = alloca(sizeof(uint32_t) * (glyph_ends - glyph_starts + 1)); + + for( ; i < glyph_ends; i++) { + chars[i - glyph_starts] = codepoints[i]; + width += vterm_unicode_width(codepoints[i]); + } + + chars[glyph_ends - glyph_starts] = 0; + i--; + +#ifdef DEBUG_GLYPH_COMBINE + { + int printpos; + printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts); + for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++) + printf("U+%04x ", chars[printpos]); + printf("}, onscreen width %d\n", width); + } +#endif + + if(state->at_phantom) { + linefeed(state); + state->pos.col = 0; + state->at_phantom = 0; + } + + if(state->mode.insert) { + /* TODO: This will be a little inefficient for large bodies of text, as + * it'll have to 'ICH' effectively before every glyph. We should scan + * ahead and ICH as many times as required + */ + VTermRect rect = { + .start_row = state->pos.row, + .end_row = state->pos.row + 1, + .start_col = state->pos.col, + .end_col = state->cols, + }; + scroll(state, rect, 0, -1); + } + putglyph(state, chars, width, state->pos); + + if(i == npoints - 1) { + /* End of the buffer. Save the chars in case we have to combine with + * more on the next call */ + unsigned int save_i; + for(save_i = 0; chars[save_i]; save_i++) { + if(save_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[save_i] = chars[save_i]; + } + if(save_i >= state->combine_chars_size) + grow_combine_buffer(state); + state->combine_chars[save_i] = 0; + state->combine_width = width; + state->combine_pos = state->pos; + } + + if(state->pos.col + width >= state->cols) { + if(state->mode.autowrap) + state->at_phantom = 1; + } + else { + state->pos.col += width; + } + } + + updatecursor(state, &oldpos, 0); + + return eaten; +} + +static int on_control(unsigned char control, void *user) +{ + VTermState *state = user; + + VTermPos oldpos = state->pos; + + switch(control) { + case 0x07: // BEL - ECMA-48 8.3.3 + if(state->callbacks && state->callbacks->bell) + (*state->callbacks->bell)(state->cbdata); + break; + + case 0x08: // BS - ECMA-48 8.3.5 + if(state->pos.col > 0) + state->pos.col--; + break; + + case 0x09: // HT - ECMA-48 8.3.60 + tab(state, 1, +1); + break; + + case 0x0a: // LF - ECMA-48 8.3.74 + case 0x0b: // VT + case 0x0c: // FF + linefeed(state); + if(state->mode.newline) + state->pos.col = 0; + break; + + case 0x0d: // CR - ECMA-48 8.3.15 + state->pos.col = 0; + break; + + case 0x0e: // LS1 - ECMA-48 8.3.76 + state->gl_set = 1; + break; + + case 0x0f: // LS0 - ECMA-48 8.3.75 + state->gl_set = 0; + break; + + case 0x84: // IND - DEPRECATED but implemented for completeness + linefeed(state); + break; + + case 0x85: // NEL - ECMA-48 8.3.86 + linefeed(state); + state->pos.col = 0; + break; + + case 0x88: // HTS - ECMA-48 8.3.62 + set_col_tabstop(state, state->pos.col); + break; + + case 0x8d: // RI - ECMA-48 8.3.104 + if(state->pos.row == state->scrollregion_start) { + VTermRect rect = { + .start_row = state->scrollregion_start, + .end_row = SCROLLREGION_END(state), + .start_col = 0, + .end_col = state->cols, + }; + + scroll(state, rect, -1, 0); + } + else if(state->pos.row > 0) + state->pos.row--; + break; + + default: + return 0; + } + + updatecursor(state, &oldpos, 1); + + return 1; +} + +static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) +{ + modifiers <<= 2; + + switch(state->mouse_protocol) { + case MOUSE_X10: + if(col + 0x21 > 0xff) + col = 0xff - 0x21; + if(row + 0x21 > 0xff) + row = 0xff - 0x21; + + if(!pressed) + code = 3; + + vterm_push_output_sprintf(state->vt, "\e[M%c%c%c", + (code | modifiers) + 0x20, col + 0x21, row + 0x21); + break; + + case MOUSE_UTF8: + { + char utf8[18]; size_t len = 0; + + if(!pressed) + code = 3; + + len += fill_utf8((code | modifiers) + 0x20, utf8 + len); + len += fill_utf8(col + 0x21, utf8 + len); + len += fill_utf8(row + 0x21, utf8 + len); + + vterm_push_output_sprintf(state->vt, "\e[M%s", utf8); + } + break; + + case MOUSE_SGR: + vterm_push_output_sprintf(state->vt, "\e[<%d;%d;%d%c", + code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm'); + break; + + case MOUSE_RXVT: + if(!pressed) + code = 3; + + vterm_push_output_sprintf(state->vt, "\e[%d;%d;%dM", + code | modifiers, col + 1, row + 1); + break; + } +} + +static void mousefunc(int col, int row, int button, int pressed, int modifiers, void *data) +{ + VTermState *state = data; + + int old_col = state->mouse_col; + int old_row = state->mouse_row; + int old_buttons = state->mouse_buttons; + + state->mouse_col = col; + state->mouse_row = row; + + if(button > 0 && button <= 3) { + if(pressed) + state->mouse_buttons |= (1 << (button-1)); + else + state->mouse_buttons &= ~(1 << (button-1)); + } + + modifiers &= 0x7; + + + /* Most of the time we don't get button releases from 4/5 */ + if(state->mouse_buttons != old_buttons || button >= 4) { + if(button < 4) { + output_mouse(state, button-1, pressed, modifiers, col, row); + } + else if(button < 6) { + output_mouse(state, button-4 + 0x40, pressed, modifiers, col, row); + } + } + else if(col != old_col || row != old_row) { + if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) || + (state->mouse_flags & MOUSE_WANT_MOVE)) { + int button = state->mouse_buttons & 0x01 ? 1 : + state->mouse_buttons & 0x02 ? 2 : + state->mouse_buttons & 0x04 ? 3 : 4; + output_mouse(state, button-1 + 0x20, 1, modifiers, col, row); + } + } +} + +static int settermprop_bool(VTermState *state, VTermProp prop, int v) +{ + VTermValue val; + val.boolean = v; + +#ifdef DEBUG + if(VTERM_VALUETYPE_BOOL != vterm_get_prop_type(prop)) { + fprintf(stderr, "Cannot set prop %d as it has type %d, not type BOOL\n", + prop, vterm_get_prop_type(prop)); + return -1; + } +#endif + + if(state->callbacks && state->callbacks->settermprop) + if((*state->callbacks->settermprop)(prop, &val, state->cbdata)) + return 1; + + return 0; +} + +static int settermprop_int(VTermState *state, VTermProp prop, int v) +{ + VTermValue val; + val.number = v; + +#ifdef DEBUG + if(VTERM_VALUETYPE_INT != vterm_get_prop_type(prop)) { + fprintf(stderr, "Cannot set prop %d as it has type %d, not type int\n", + prop, vterm_get_prop_type(prop)); + return -1; + } +#endif + + if(state->callbacks && state->callbacks->settermprop) + if((*state->callbacks->settermprop)(prop, &val, state->cbdata)) + return 1; + + return 0; +} + +static int settermprop_string(VTermState *state, VTermProp prop, const char *str, size_t len) +{ + char strvalue[len+1]; + VTermValue val; + + strncpy(strvalue, str, len); + strvalue[len] = 0; + + val.string = strvalue; + +#ifdef DEBUG + if(VTERM_VALUETYPE_STRING != vterm_get_prop_type(prop)) { + fprintf(stderr, "Cannot set prop %d as it has type %d, not type STRING\n", + prop, vterm_get_prop_type(prop)); + return -1; + } +#endif + + if(state->callbacks && state->callbacks->settermprop) + if((*state->callbacks->settermprop)(prop, &val, state->cbdata)) + return 1; + + return 0; +} + +static void savecursor(VTermState *state, int save) +{ + if(save) { + state->saved.pos = state->pos; + state->saved.mode.cursor_visible = state->mode.cursor_visible; + state->saved.mode.cursor_blink = state->mode.cursor_blink; + state->saved.mode.cursor_shape = state->mode.cursor_shape; + + vterm_state_savepen(state, 1); + } + else { + VTermPos oldpos = state->pos; + + state->pos = state->saved.pos; + state->mode.cursor_visible = state->saved.mode.cursor_visible; + state->mode.cursor_blink = state->saved.mode.cursor_blink; + state->mode.cursor_shape = state->saved.mode.cursor_shape; + + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->mode.cursor_visible); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->mode.cursor_blink); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->mode.cursor_shape); + + vterm_state_savepen(state, 0); + + updatecursor(state, &oldpos, 1); + } +} + +static void altscreen(VTermState *state, int alt) +{ + /* Only store that we're on the alternate screen if the usercode said it + * switched */ + if(!settermprop_bool(state, VTERM_PROP_ALTSCREEN, alt)) + return; + + state->mode.alt_screen = alt; + if(alt) { + VTermRect rect = { + .start_row = 0, + .start_col = 0, + .end_row = state->rows, + .end_col = state->cols, + }; + erase(state, rect); + } +} + +static int on_escape(const char *bytes, size_t len, void *user) +{ + VTermState *state = user; + + /* Easier to decode this from the first byte, even though the final + * byte terminates it + */ + switch(bytes[0]) { + case '#': + if(len != 2) + return 0; + + switch(bytes[1]) { + case '8': // DECALN + { + VTermPos pos; + uint32_t E[] = { 'E', 0 }; + for(pos.row = 0; pos.row < state->rows; pos.row++) + for(pos.col = 0; pos.col < state->cols; pos.col++) + putglyph(state, E, 1, pos); + break; + } + + default: + return 0; + } + return 2; + + case '(': case ')': case '*': case '+': // SCS + if(len != 2) + return 0; + + { + int setnum = bytes[0] - 0x28; + VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); + + if(newenc) { + state->encoding[setnum].enc = newenc; + + if(newenc->init) + (*newenc->init)(newenc, state->encoding[setnum].data); + } + } + + return 2; + + case '7': // DECSC + savecursor(state, 1); + return 1; + + case '8': // DECRC + savecursor(state, 0); + return 1; + + case '=': // DECKPAM + state->mode.keypad = 1; + return 1; + + case '>': // DECKPNM + state->mode.keypad = 0; + return 1; + + case 'c': // RIS - ECMA-48 8.3.105 + { + VTermPos oldpos = state->pos; + vterm_state_reset(state, 1); + if(state->callbacks && state->callbacks->movecursor) + (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata); + return 1; + } + + case 'n': // LS2 - ECMA-48 8.3.78 + state->gl_set = 2; + return 1; + + case 'o': // LS3 - ECMA-48 8.3.80 + state->gl_set = 3; + return 1; + + default: + return 0; + } +} + +static void set_mode(VTermState *state, int num, int val) +{ + switch(num) { + case 4: // IRM - ECMA-48 7.2.10 + state->mode.insert = val; + break; + + case 20: // LNM - ANSI X3.4-1977 + state->mode.newline = val; + break; + + default: + fprintf(stderr, "libvterm: Unknown mode %d\n", num); + return; + } +} + +static void set_dec_mode(VTermState *state, int num, int val) +{ + switch(num) { + case 1: + state->mode.cursor = val; + break; + + case 5: + settermprop_bool(state, VTERM_PROP_REVERSE, val); + break; + + case 6: // DECOM - origin mode + { + VTermPos oldpos = state->pos; + state->mode.origin = val; + state->pos.row = state->mode.origin ? state->scrollregion_start : 0; + state->pos.col = 0; + updatecursor(state, &oldpos, 1); + } + break; + + case 7: + state->mode.autowrap = val; + break; + + case 12: + state->mode.cursor_blink = val; + settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); + break; + + case 25: + state->mode.cursor_visible = val; + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); + break; + + case 1000: + case 1002: + case 1003: + if(val) { + state->mouse_col = 0; + state->mouse_row = 0; + state->mouse_buttons = 0; + + state->mouse_flags = 0; + state->mouse_protocol = MOUSE_X10; + + if(num == 1002) + state->mouse_flags |= MOUSE_WANT_DRAG; + if(num == 1003) + state->mouse_flags |= MOUSE_WANT_MOVE; + } + + if(state->callbacks && state->callbacks->setmousefunc) + (*state->callbacks->setmousefunc)(val ? mousefunc : NULL, state, state->cbdata); + + break; + + case 1005: + state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; + break; + + case 1006: + state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; + break; + + case 1015: + state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; + break; + + case 1047: + altscreen(state, val); + break; + + case 1048: + savecursor(state, val); + break; + + case 1049: + altscreen(state, val); + savecursor(state, val); + break; + + default: + fprintf(stderr, "libvterm: Unknown DEC mode %d\n", num); + return; + } +} + +static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) +{ + VTermState *state = user; + int leader_byte = 0; + int intermed_byte = 0; + VTermPos oldpos; + + // Some temporaries for later code + int count, val; + int row, col; + VTermRect rect; + + if(leader && leader[0]) { + if(leader[1]) // longer than 1 char + return 0; + + switch(leader[0]) { + case '?': + case '>': + leader_byte = leader[0]; + break; + default: + return 0; + } + } + + if(intermed && intermed[0]) { + if(intermed[1]) // longer than 1 char + return 0; + + switch(intermed[0]) { + case ' ': + intermed_byte = intermed[0]; + break; + default: + return 0; + } + } + + oldpos = state->pos; + +#define LEADER(l,b) ((l << 8) | b) +#define INTERMED(i,b) ((i << 16) | b) + + switch(intermed_byte << 16 | leader_byte << 8 | command) { + case 0x40: // ICH - ECMA-48 8.3.64 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + rect.end_col = state->cols; + + scroll(state, rect, 0, -count); + + break; + + case 0x41: // CUU - ECMA-48 8.3.22 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x42: // CUD - ECMA-48 8.3.19 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x43: // CUF - ECMA-48 8.3.20 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x44: // CUB - ECMA-48 8.3.18 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x45: // CNL - ECMA-48 8.3.12 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x46: // CPL - ECMA-48 8.3.13 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x47: // CHA - ECMA-48 8.3.9 + val = CSI_ARG_OR(args[0], 1); + state->pos.col = val-1; + state->at_phantom = 0; + break; + + case 0x48: // CUP - ECMA-48 8.3.21 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row-1; + state->pos.col = col-1; + if(state->mode.origin) + state->pos.row += state->scrollregion_start; + state->at_phantom = 0; + break; + + case 0x49: // CHT - ECMA-48 8.3.10 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, +1); + break; + + case 0x4a: // ED - ECMA-48 8.3.39 + switch(CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; rect.end_col = state->cols; + if(rect.end_col > rect.start_col) + erase(state, rect); + + rect.start_row = state->pos.row + 1; rect.end_row = state->rows; + rect.start_col = 0; + if(rect.end_row > rect.start_row) + erase(state, rect); + break; + + case 1: + rect.start_row = 0; rect.end_row = state->pos.row; + rect.start_col = 0; rect.end_col = state->cols; + if(rect.end_col > rect.start_col) + erase(state, rect); + + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; + rect.end_col = state->pos.col + 1; + if(rect.end_row > rect.start_row) + erase(state, rect); + break; + + case 2: + rect.start_row = 0; rect.end_row = state->rows; + rect.start_col = 0; rect.end_col = state->cols; + erase(state, rect); + break; + } + break; + + case 0x4b: // EL - ECMA-48 8.3.41 + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + + switch(CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_col = state->pos.col; rect.end_col = state->cols; break; + case 1: + rect.start_col = 0; rect.end_col = state->pos.col + 1; break; + case 2: + rect.start_col = 0; rect.end_col = state->cols; break; + default: + return 0; + } + + if(rect.end_col > rect.start_col) + erase(state, rect); + + break; + + case 0x4c: // IL - ECMA-48 8.3.67 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_END(state); + rect.start_col = 0; + rect.end_col = state->cols; + + scroll(state, rect, -count, 0); + + break; + + case 0x4d: // DL - ECMA-48 8.3.32 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_END(state); + rect.start_col = 0; + rect.end_col = state->cols; + + scroll(state, rect, count, 0); + + break; + + case 0x50: // DCH - ECMA-48 8.3.26 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + rect.end_col = state->cols; + + scroll(state, rect, 0, count); + + break; + + case 0x53: // SU - ECMA-48 8.3.147 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_start; + rect.end_row = SCROLLREGION_END(state); + rect.start_col = 0; + rect.end_col = state->cols; + + scroll(state, rect, count, 0); + + break; + + case 0x54: // SD - ECMA-48 8.3.113 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_start; + rect.end_row = SCROLLREGION_END(state); + rect.start_col = 0; + rect.end_col = state->cols; + + scroll(state, rect, -count, 0); + + break; + + case 0x58: // ECH - ECMA-48 8.3.38 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + rect.end_col = state->pos.col + count; + + erase(state, rect); + break; + + case 0x5a: // CBT - ECMA-48 8.3.7 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, -1); + break; + + case 0x60: // HPA - ECMA-48 8.3.57 + col = CSI_ARG_OR(args[0], 1); + state->pos.col = col-1; + state->at_phantom = 0; + break; + + case 0x61: // HPR - ECMA-48 8.3.59 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x63: // DA - ECMA-48 8.3.24 + val = CSI_ARG_OR(args[0], 0); + if(val == 0) + // DEC VT100 response + vterm_push_output_sprintf(state->vt, "\e[?1;2c"); + break; + + case LEADER('>', 0x63): // DEC secondary Device Attributes + vterm_push_output_sprintf(state->vt, "\e[>%d;%d;%dc", 0, 100, 0); + break; + + case 0x64: // VPA - ECMA-48 8.3.158 + row = CSI_ARG_OR(args[0], 1); + state->pos.row = row-1; + if(state->mode.origin) + state->pos.row += state->scrollregion_start; + state->at_phantom = 0; + break; + + case 0x65: // VPR - ECMA-48 8.3.160 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x66: // HVP - ECMA-48 8.3.63 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row-1; + state->pos.col = col-1; + if(state->mode.origin) + state->pos.row += state->scrollregion_start; + state->at_phantom = 0; + break; + + case 0x67: // TBC - ECMA-48 8.3.154 + val = CSI_ARG_OR(args[0], 0); + + switch(val) { + case 0: + clear_col_tabstop(state, state->pos.col); + break; + case 3: + case 5: + for(col = 0; col < state->cols; col++) + clear_col_tabstop(state, col); + break; + case 1: + case 2: + case 4: + break; + /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */ + default: + return 0; + } + break; + + case 0x68: // SM - ECMA-48 8.3.125 + if(!CSI_ARG_IS_MISSING(args[0])) + set_mode(state, CSI_ARG(args[0]), 1); + break; + + case LEADER('?', 0x68): // DEC private mode set + if(!CSI_ARG_IS_MISSING(args[0])) + set_dec_mode(state, CSI_ARG(args[0]), 1); + break; + + case 0x6a: // HPB - ECMA-48 8.3.58 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x6b: // VPB - ECMA-48 8.3.159 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x6c: // RM - ECMA-48 8.3.106 + if(!CSI_ARG_IS_MISSING(args[0])) + set_mode(state, CSI_ARG(args[0]), 0); + break; + + case LEADER('?', 0x6c): // DEC private mode reset + if(!CSI_ARG_IS_MISSING(args[0])) + set_dec_mode(state, CSI_ARG(args[0]), 0); + break; + + case 0x6d: // SGR - ECMA-48 8.3.117 + vterm_state_setpen(state, args, argcount); + break; + + case 0x6e: // DSR - ECMA-48 8.3.35 + val = CSI_ARG_OR(args[0], 0); + + switch(val) { + case 0: case 1: case 2: case 3: case 4: + // ignore - these are replies + break; + case 5: + vterm_push_output_sprintf(state->vt, "\e[0n"); + break; + case 6: + vterm_push_output_sprintf(state->vt, "\e[%d;%dR", state->pos.row + 1, state->pos.col + 1); + break; + } + break; + + case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset + vterm_state_reset(state, 0); + break; + + case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape + val = CSI_ARG_OR(args[0], 1); + + switch(val) { + case 0: case 1: + state->mode.cursor_blink = 1; + state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK; + break; + case 2: + state->mode.cursor_blink = 0; + state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK; + break; + case 3: + state->mode.cursor_blink = 1; + state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_UNDERLINE; + break; + case 4: + state->mode.cursor_blink = 0; + state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_UNDERLINE; + break; + } + + settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->mode.cursor_blink); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->mode.cursor_shape); + break; + + case 0x72: // DECSTBM - DEC custom + state->scrollregion_start = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_end = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + if(state->scrollregion_start == 0 && state->scrollregion_end == state->rows) + state->scrollregion_end = -1; + break; + + case 0x73: // ANSI SAVE + savecursor(state, 1); + break; + + case 0x75: // ANSI RESTORE + savecursor(state, 0); + break; + + default: + return 0; + } + +#define LBOUND(v,min) if((v) < (min)) (v) = (min) +#define UBOUND(v,max) if((v) > (max)) (v) = (max) + + LBOUND(state->pos.col, 0); + UBOUND(state->pos.col, state->cols-1); + + if(state->mode.origin) { + LBOUND(state->pos.row, state->scrollregion_start); + UBOUND(state->pos.row, state->scrollregion_end-1); + } + else { + LBOUND(state->pos.row, 0); + UBOUND(state->pos.row, state->rows-1); + } + + updatecursor(state, &oldpos, 1); + + return 1; +} + +static int on_osc(const char *command, size_t cmdlen, void *user) +{ + VTermState *state = user; + + if(cmdlen < 2) + return 0; + + if(strneq(command, "0;", 2)) { + settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); + settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); + return 1; + } + else if(strneq(command, "1;", 2)) { + settermprop_string(state, VTERM_PROP_ICONNAME, command + 2, cmdlen - 2); + return 1; + } + else if(strneq(command, "2;", 2)) { + settermprop_string(state, VTERM_PROP_TITLE, command + 2, cmdlen - 2); + return 1; + } + + return 0; +} + +static void request_status_string(VTermState *state, const char *command, size_t cmdlen) +{ + if(cmdlen == 1) + switch(command[0]) { + case 'r': // Query DECSTBM + vterm_push_output_sprintf(state->vt, "\eP1$r%d;%dr\e\\", state->scrollregion_start+1, SCROLLREGION_END(state)); + return; + } + + if(cmdlen == 2) + if(strneq(command, " q", 2)) { + int reply = 0; + switch(state->mode.cursor_shape) { + case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; + case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; + } + if(state->mode.cursor_blink) + reply--; + vterm_push_output_sprintf(state->vt, "\eP1$r%d q\e\\", reply); + return; + } + + vterm_push_output_sprintf(state->vt, "\eP0$r%.s\e\\", (int)cmdlen, command); +} + +static int on_dcs(const char *command, size_t cmdlen, void *user) +{ + VTermState *state = user; + + if(cmdlen >= 2 && strneq(command, "$q", 2)) { + request_status_string(state, command+2, cmdlen-2); + return 1; + } + + return 0; +} + +static int on_resize(int rows, int cols, void *user) +{ + VTermState *state = user; + VTermPos oldpos = state->pos; + + if(cols != state->cols) { + unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8); + + /* TODO: This can all be done much more efficiently bytewise */ + int col; + for(col = 0; col < state->cols && col < cols; col++) { + unsigned char mask = 1 << (col & 7); + if(state->tabstops[col >> 3] & mask) + newtabstops[col >> 3] |= mask; + else + newtabstops[col >> 3] &= ~mask; + } + + for( ; col < cols; col++) { + unsigned char mask = 1 << (col & 7); + if(col % 8 == 0) + newtabstops[col >> 3] |= mask; + else + newtabstops[col >> 3] &= ~mask; + } + + vterm_allocator_free(state->vt, state->tabstops); + state->tabstops = newtabstops; + } + + state->rows = rows; + state->cols = cols; + + if(state->pos.row >= rows) + state->pos.row = rows - 1; + if(state->pos.col >= cols) + state->pos.col = cols - 1; + + if(state->at_phantom && state->pos.col < cols-1) { + state->at_phantom = 0; + state->pos.col++; + } + + if(state->callbacks && state->callbacks->resize) + (*state->callbacks->resize)(rows, cols, state->cbdata); + + updatecursor(state, &oldpos, 1); + + return 1; +} + +static const VTermParserCallbacks parser_callbacks = { + .text = on_text, + .control = on_control, + .escape = on_escape, + .csi = on_csi, + .osc = on_osc, + .dcs = on_dcs, + .resize = on_resize, +}; + +VTermState *vterm_obtain_state(VTerm *vt) +{ + VTermState *state; + if(vt->state) + return vt->state; + + state = vterm_state_new(vt); + vt->state = state; + + state->combine_chars_size = 16; + state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0])); + + state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8); + + state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); + if(*state->encoding_utf8.enc->init) + (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); + + vterm_set_parser_callbacks(vt, &parser_callbacks, state); + + return state; +} + +void vterm_state_reset(VTermState *state, int hard) +{ + int col, i; + VTermEncoding *default_enc; + + state->scrollregion_start = 0; + state->scrollregion_end = -1; + + state->mode.keypad = 0; + state->mode.cursor = 0; + state->mode.autowrap = 1; + state->mode.insert = 0; + state->mode.newline = 0; + state->mode.cursor_visible = 1; + state->mode.cursor_blink = 1; + state->mode.cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK; + state->mode.alt_screen = 0; + state->mode.origin = 0; + + for(col = 0; col < state->cols; col++) + if(col % 8 == 0) + set_col_tabstop(state, col); + else + clear_col_tabstop(state, col); + + if(state->callbacks && state->callbacks->initpen) + (*state->callbacks->initpen)(state->cbdata); + + vterm_state_resetpen(state); + + default_enc = state->vt->is_utf8 ? + vterm_lookup_encoding(ENC_UTF8, 'u') : + vterm_lookup_encoding(ENC_SINGLE_94, 'B'); + + for(i = 0; i < 4; i++) { + state->encoding[i].enc = default_enc; + if(default_enc->init) + (*default_enc->init)(default_enc, state->encoding[i].data); + } + + state->gl_set = 0; + state->gr_set = 0; + + // Initialise the props + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->mode.cursor_visible); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->mode.cursor_blink); + settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->mode.cursor_shape); + + if(hard) { + VTermRect rect = { 0, state->rows, 0, state->cols }; + + state->pos.row = 0; + state->pos.col = 0; + state->at_phantom = 0; + + erase(state, rect); + } +} + +void vterm_state_get_cursorpos(VTermState *state, VTermPos *cursorpos) +{ + *cursorpos = state->pos; +} + +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) +{ + if(callbacks) { + state->callbacks = callbacks; + state->cbdata = user; + + if(state->callbacks && state->callbacks->initpen) + (*state->callbacks->initpen)(state->cbdata); + } + else { + state->callbacks = NULL; + state->cbdata = NULL; + } +} diff --git a/src/apps/serialconnect/libvterm/src/unicode.c b/src/apps/serialconnect/libvterm/src/unicode.c new file mode 100644 index 0000000000..d50e80d5d9 --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/unicode.c @@ -0,0 +1,332 @@ +#include "vterm_internal.h" + +// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c +// With modifications: +// made functions static +// moved 'combining' table to file scope, so other functions can see it +// ################################################################### + +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +#include + +struct interval { + int first; + int last; +}; + +/* sorted list of non-overlapping intervals of non-spacing characters */ +/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ +static const struct interval combining[] = { + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, + { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0xE0100, 0xE01EF } +}; + + +/* auxiliary function for binary search in interval table */ +static int bisearch(wchar_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that wchar_t characters are encoded + * in ISO 10646. + */ + + +static int mk_wcwidth(wchar_t ucs) +{ + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + +#if 0 +static int mk_wcswidth(const wchar_t *pwcs, size_t n) +{ + int w, width = 0; + + for (;*pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + +/* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +static int mk_wcwidth_cjk(wchar_t ucs) +{ + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ + static const struct interval ambiguous[] = { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, + { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, + { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, + { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, + { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, + { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, + { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, + { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, + { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, + { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, + { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, + { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, + { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, + { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, + { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, + { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, + { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, + { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, + { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, + { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, + { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, + { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, + { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, + { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, + { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, + { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, + { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, + { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, + { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, + { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, + { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, + { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, + { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } + }; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, + sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return mk_wcwidth(ucs); +} + + +static int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) +{ + int w, width = 0; + + for (;*pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth_cjk(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} +#endif + +// ################################ +// ### The rest added by Paul Evans + +int vterm_unicode_width(int codepoint) +{ + return mk_wcwidth(codepoint); +} + +int vterm_unicode_is_combining(int codepoint) +{ + return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1); +} diff --git a/src/apps/serialconnect/libvterm/src/utf8.h b/src/apps/serialconnect/libvterm/src/utf8.h new file mode 100644 index 0000000000..f8cfe4659a --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/utf8.h @@ -0,0 +1,41 @@ +/* The following functions copied and adapted from libtermkey + * + * http://www.leonerd.org.uk/code/libtermkey/ + */ +static inline unsigned int utf8_seqlen(long codepoint) +{ + if(codepoint < 0x0000080) return 1; + if(codepoint < 0x0000800) return 2; + if(codepoint < 0x0010000) return 3; + if(codepoint < 0x0200000) return 4; + if(codepoint < 0x4000000) return 5; + return 6; +} + +static int fill_utf8(long codepoint, char *str) +{ + int b; + int nbytes = utf8_seqlen(codepoint); + + str[nbytes] = 0; + + // This is easier done backwards + b = nbytes; + while(b > 1) { + b--; + str[b] = 0x80 | (codepoint & 0x3f); + codepoint >>= 6; + } + + switch(nbytes) { + case 1: str[0] = (codepoint & 0x7f); break; + case 2: str[0] = 0xc0 | (codepoint & 0x1f); break; + case 3: str[0] = 0xe0 | (codepoint & 0x0f); break; + case 4: str[0] = 0xf0 | (codepoint & 0x07); break; + case 5: str[0] = 0xf8 | (codepoint & 0x03); break; + case 6: str[0] = 0xfc | (codepoint & 0x01); break; + } + + return nbytes; +} +/* end copy */ diff --git a/src/apps/serialconnect/libvterm/src/vterm.c b/src/apps/serialconnect/libvterm/src/vterm.c new file mode 100644 index 0000000000..a3f3edb345 --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/vterm.c @@ -0,0 +1,322 @@ +#include "vterm_internal.h" + +#include +#include +#include +#include + +/***************** + * API functions * + *****************/ + +static void *default_malloc(size_t size, void *allocdata) +{ + void *ptr = malloc(size); + if(ptr) + memset(ptr, 0, size); + return ptr; +} + +static void default_free(void *ptr, void *allocdata) +{ + free(ptr); +} + +static VTermAllocatorFunctions default_allocator = { + .malloc = &default_malloc, + .free = &default_free, +}; + +VTerm *vterm_new(int rows, int cols) +{ + return vterm_new_with_allocator(rows, cols, &default_allocator, NULL); +} + +VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata) +{ + /* Need to bootstrap using the allocator function directly */ + VTerm *vt = (*funcs->malloc)(sizeof(VTerm), allocdata); + + vt->allocator = funcs; + vt->allocdata = allocdata; + + vt->rows = rows; + vt->cols = cols; + + vt->parser_state = NORMAL; + + vt->strbuffer_len = 64; + vt->strbuffer_cur = 0; + vt->strbuffer = vterm_allocator_malloc(vt, vt->strbuffer_len); + + vt->outbuffer_len = 64; + vt->outbuffer_cur = 0; + vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len); + + return vt; +} + +void vterm_free(VTerm *vt) +{ + if(vt->screen) + vterm_screen_free(vt->screen); + + if(vt->state) + vterm_state_free(vt->state); + + vterm_allocator_free(vt, vt->strbuffer); + vterm_allocator_free(vt, vt->outbuffer); + + vterm_allocator_free(vt, vt); +} + +void *vterm_allocator_malloc(VTerm *vt, size_t size) +{ + return (*vt->allocator->malloc)(size, vt->allocdata); +} + +void vterm_allocator_free(VTerm *vt, void *ptr) +{ + (*vt->allocator->free)(ptr, vt->allocdata); +} + +void vterm_get_size(VTerm *vt, int *rowsp, int *colsp) +{ + if(rowsp) + *rowsp = vt->rows; + if(colsp) + *colsp = vt->cols; +} + +void vterm_set_size(VTerm *vt, int rows, int cols) +{ + vt->rows = rows; + vt->cols = cols; + + if(vt->parser_callbacks && vt->parser_callbacks->resize) + (*vt->parser_callbacks->resize)(rows, cols, vt->cbdata); +} + +void vterm_set_parser_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) +{ + vt->parser_callbacks = callbacks; + vt->cbdata = user; +} + +void vterm_parser_set_utf8(VTerm *vt, int is_utf8) +{ + vt->is_utf8 = is_utf8; +} + +void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) +{ + if(len > vt->outbuffer_len - vt->outbuffer_cur) { + fprintf(stderr, "vterm_push_output(): buffer overflow; truncating output\n"); + len = vt->outbuffer_len - vt->outbuffer_cur; + } + + memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len); + vt->outbuffer_cur += len; +} + +void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args) +{ + int written = vsnprintf(vt->outbuffer + vt->outbuffer_cur, + vt->outbuffer_len - vt->outbuffer_cur, + format, args); + vt->outbuffer_cur += written; +} + +void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) +{ + va_list args; + va_start(args, format); + vterm_push_output_vsprintf(vt, format, args); + va_end(args); +} + +size_t vterm_output_bufferlen(VTerm *vt) +{ + return vterm_output_get_buffer_current(vt); +} + +size_t vterm_output_get_buffer_size(VTerm *vt) +{ + return vt->outbuffer_len; +} + +size_t vterm_output_get_buffer_current(VTerm *vt) +{ + return vt->outbuffer_cur; +} + +size_t vterm_output_get_buffer_remaining(VTerm *vt) +{ + return vt->outbuffer_len - vt->outbuffer_cur; +} + +size_t vterm_output_bufferread(VTerm *vt, char *buffer, size_t len) +{ + if(len > vt->outbuffer_cur) + len = vt->outbuffer_cur; + + memcpy(buffer, vt->outbuffer, len); + + if(len < vt->outbuffer_cur) + memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len); + + vt->outbuffer_cur -= len; + + return len; +} + +VTermValueType vterm_get_attr_type(VTermAttr attr) +{ + switch(attr) { + case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT; + case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT; + case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR; + case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR; + } + return 0; /* UNREACHABLE */ +} + +VTermValueType vterm_get_prop_type(VTermProp prop) +{ + switch(prop) { + case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING; + case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING; + case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT; + } + return 0; /* UNREACHABLE */ +} + +void vterm_scroll_rect(VTermRect rect, + int downward, + int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, void *user), + void *user) +{ + VTermRect src; + VTermRect dest; + + if(abs(downward) >= rect.end_row - rect.start_row || + abs(rightward) >= rect.end_col - rect.start_col) { + /* Scroll more than area; just erase the lot */ + (*eraserect)(rect, user); + return; + } + + if(rightward >= 0) { + /* rect: [XXX................] + * src: [----------------] + * dest: [----------------] + */ + dest.start_col = rect.start_col; + dest.end_col = rect.end_col - rightward; + src.start_col = rect.start_col + rightward; + src.end_col = rect.end_col; + } + else { + /* rect: [................XXX] + * src: [----------------] + * dest: [----------------] + */ + int leftward = -rightward; + dest.start_col = rect.start_col + leftward; + dest.end_col = rect.end_col; + src.start_col = rect.start_col; + src.end_col = rect.end_col - leftward; + } + + if(downward >= 0) { + dest.start_row = rect.start_row; + dest.end_row = rect.end_row - downward; + src.start_row = rect.start_row + downward; + src.end_row = rect.end_row; + } + else { + int upward = -downward; + dest.start_row = rect.start_row + upward; + dest.end_row = rect.end_row; + src.start_row = rect.start_row; + src.end_row = rect.end_row - upward; + } + + if(moverect) + (*moverect)(dest, src, user); + + if(downward > 0) + rect.start_row = rect.end_row - downward; + else if(downward < 0) + rect.end_row = rect.start_row - downward; + + if(rightward > 0) + rect.start_col = rect.end_col - rightward; + else if(rightward < 0) + rect.end_col = rect.start_col - rightward; + + (*eraserect)(rect, user); +} + +void vterm_copy_cells(VTermRect dest, + VTermRect src, + void (*copycell)(VTermPos dest, VTermPos src, void *user), + void *user) +{ + int downward = src.start_row - dest.start_row; + int rightward = src.start_col - dest.start_col; + + int init_row, test_row, init_col, test_col; + int inc_row, inc_col; + + VTermPos pos; + + if(downward < 0) { + init_row = dest.end_row - 1; + test_row = dest.start_row - 1; + inc_row = -1; + } + else if(downward == 0) { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + else /* downward > 0 */ { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + + if(rightward < 0) { + init_col = dest.end_col - 1; + test_col = dest.start_col - 1; + inc_col = -1; + } + else if(rightward == 0) { + init_col = dest.start_col; + test_col = dest.end_col; + inc_col = +1; + } + else /* rightward > 0 */ { + init_col = dest.start_col; + test_col = dest.end_col; + inc_col = +1; + } + + for(pos.row = init_row; pos.row != test_row; pos.row += inc_row) + for(pos.col = init_col; pos.col != test_col; pos.col += inc_col) { + VTermPos srcpos = { pos.row + downward, pos.col + rightward }; + (*copycell)(pos, srcpos, user); + } +} diff --git a/src/apps/serialconnect/libvterm/src/vterm_internal.h b/src/apps/serialconnect/libvterm/src/vterm_internal.h new file mode 100644 index 0000000000..03f14af1ac --- /dev/null +++ b/src/apps/serialconnect/libvterm/src/vterm_internal.h @@ -0,0 +1,167 @@ +#ifndef __VTERM_INTERNAL_H__ +#define __VTERM_INTERNAL_H__ + +#include "vterm.h" + +#include + +typedef struct VTermEncoding VTermEncoding; + +typedef struct { + VTermEncoding *enc; + + // This size should be increased if required by other stateful encodings + char data[4*sizeof(uint32_t)]; +} VTermEncodingInstance; + +struct VTermPen +{ + VTermColor fg; + VTermColor bg; + unsigned int bold:1; + unsigned int underline:2; + unsigned int italic:1; + unsigned int blink:1; + unsigned int reverse:1; + unsigned int strike:1; + unsigned int font:4; /* To store 0-9 */ +}; + +struct VTermState +{ + VTerm *vt; + + const VTermStateCallbacks *callbacks; + void *cbdata; + + int rows; + int cols; + + /* Current cursor position */ + VTermPos pos; + + int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */ + + int scrollregion_start; + int scrollregion_end; /* -1 means unbounded */ +#define SCROLLREGION_END(state) ((state)->scrollregion_end > -1 ? (state)->scrollregion_end : (state)->rows) + + /* Bitvector of tab stops */ + unsigned char *tabstops; + + /* Mouse state */ + int mouse_col, mouse_row; + int mouse_buttons; + int mouse_flags; + enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol; + + /* Last glyph output, for Unicode recombining purposes */ + uint32_t *combine_chars; + size_t combine_chars_size; // Number of ELEMENTS in the above + int combine_width; // The width of the glyph above + VTermPos combine_pos; // Position before movement + + struct { + int keypad:1; + int cursor:1; + int autowrap:1; + int insert:1; + int newline:1; + int cursor_visible:1; + int cursor_blink:1; + unsigned int cursor_shape:2; + int alt_screen:1; + int origin:1; + } mode; + + VTermEncodingInstance encoding[4], encoding_utf8; + int gl_set, gr_set; + + struct VTermPen pen; + + VTermColor default_fg; + VTermColor default_bg; + int fg_ansi; + int bold_is_highbright; + + /* Saved state under DEC mode 1048/1049 */ + struct { + VTermPos pos; + struct VTermPen pen; + + struct { + int cursor_visible:1; + int cursor_blink:1; + unsigned int cursor_shape:2; + } mode; + } saved; +}; + +struct VTerm +{ + VTermAllocatorFunctions *allocator; + void *allocdata; + + int rows; + int cols; + + int is_utf8; + + enum VTermParserState { + NORMAL, + CSI, + OSC, + DCS, + ESC, + ESC_IN_OSC, + ESC_IN_DCS, + } parser_state; + const VTermParserCallbacks *parser_callbacks; + void *cbdata; + + /* len == malloc()ed size; cur == number of valid bytes */ + char *strbuffer; + size_t strbuffer_len; + size_t strbuffer_cur; + + char *outbuffer; + size_t outbuffer_len; + size_t outbuffer_cur; + + VTermState *state; + VTermScreen *screen; +}; + +struct VTermEncoding { + void (*init) (VTermEncoding *enc, void *data); + void (*decode)(VTermEncoding *enc, void *data, + uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t len); +}; + +typedef enum { + ENC_UTF8, + ENC_SINGLE_94 +} VTermEncodingType; + +void *vterm_allocator_malloc(VTerm *vt, size_t size); +void vterm_allocator_free(VTerm *vt, void *ptr); + +void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len); +void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args); +void vterm_push_output_sprintf(VTerm *vt, const char *format, ...); + +void vterm_state_free(VTermState *state); + +void vterm_state_resetpen(VTermState *state); +void vterm_state_setpen(VTermState *state, const long args[], int argcount); +void vterm_state_savepen(VTermState *state, int save); + +void vterm_screen_free(VTermScreen *screen); + +VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation); + +int vterm_unicode_width(int codepoint); +int vterm_unicode_is_combining(int codepoint); + +#endif