WIP version of SerialConnect. Not working, but added to the tree anyway so :

* 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.
This commit is contained in:
Adrien Destugues - PulkoMandy 2012-07-23 21:36:40 +02:00
parent fc3f40765a
commit dae0a4e0ab
25 changed files with 4901 additions and 0 deletions

View File

@ -50,6 +50,7 @@ HaikuSubInclude readonlybootprompt ;
HaikuSubInclude remotedesktop ;
HaikuSubInclude resedit ;
HaikuSubInclude screenshot ;
HaikuSubInclude serialconnect ;
HaikuSubInclude showimage ;
HaikuSubInclude soundrecorder ;
HaikuSubInclude stylededit ;

View File

@ -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++)
;

View File

@ -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();
}

View File

@ -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',
};

View File

@ -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";

View File

@ -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;
};

View File

@ -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,
};

View File

@ -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;
};

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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,
}
};

View File

@ -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

View File

@ -0,0 +1,6 @@
static const struct StaticTableEncoding encoding_uk = {
{ .decode = &decode_table },
{
[0x23] = 0x00a3,
}
};

View File

@ -0,0 +1 @@
2/3 = "£"

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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, &copycell, 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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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);
}

View File

@ -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 */

View File

@ -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);
}
}

View File

@ -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