diff options
| author | Adrien Destugues - PulkoMandy <pulkomandy@pulkomandy.tk> | 2012-07-23 21:36:40 +0200 |
|---|---|---|
| committer | Adrien Destugues - PulkoMandy <pulkomandy@pulkomandy.tk> | 2012-07-23 21:36:40 +0200 |
| commit | dae0a4e0abda9ce3dff8e31007a8f66bc14421c8 (patch) | |
| tree | e9e29f7af7eb1ea71edf25c73ec334a8e81180a0 | |
| parent | fc3f40765a4d5bcbb2f554be085823dedd83ffe0 (diff) | |
WIP version of SerialConnect. Not working, but added to the tree anyway so :hrev44388
* You can code review it
* You can help developping
Uses libvterm as the backend for parsing ANSI escape sequences. The lib was
changed slightly to build with GCC2. It could be used by Terminal as well as
it seems cleaner and more reliable than our current parser.
25 files changed, 4901 insertions, 0 deletions
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 <Application.h> +#include <SerialPort.h> + + +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 <GroupLayout.h> +#include <Menu.h> +#include <MenuBar.h> +#include <MenuItem.h> +#include <SerialPort.h> + +#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 <Window.h> + + +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 <stdio.h> + +#include <Layout.h> + + +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 <View.h> + +extern "C" { + #include <vterm.h> +} + +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 <stdint.h> +#include <stdlib.h> + +#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 <stdio.h> + +#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 <stdio.h> + +#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 <inttypes.h> +#include <stdio.h> +#include <string.h> + +#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 <stdio.h> + +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 <stdio.h> + +#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 <stdio.h> +#include <string.h> + +#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 <wchar.h> + +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 <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +/***************** + * 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 <stdarg.h> + +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 |
