2
0
mirror of https://github.com/xcat2/confluent.git synced 2024-11-21 17:11:58 +00:00

Migrate VT buffering to c

C implementation to improve memory and cpu utilization.

Forked off to further move the work off the main process.

Still needs attribute rendition and packaging before merging to main
branch.
This commit is contained in:
Jarrod Johnson 2021-03-31 17:28:26 -04:00
parent 5e0ebce300
commit beab6a3c02
5 changed files with 869 additions and 127 deletions

View File

@ -33,42 +33,67 @@ import confluent.tlvdata as tlvdata
import confluent.util as util
import eventlet
import eventlet.event
import eventlet.green.os as os
import eventlet.green.select as select
import eventlet.green.socket as socket
import eventlet.green.subprocess as subprocess
import eventlet.green.ssl as ssl
import pyte
import eventlet.semaphore as semaphore
import fcntl
import random
import struct
import time
import traceback
_handled_consoles = {}
_tracelog = None
_bufferdaemon = None
_bufferlock = None
try:
range = xrange
except NameError:
pass
pytecolors2ansi = {
'black': 0,
'red': 1,
'green': 2,
'brown': 3,
'blue': 4,
'magenta': 5,
'cyan': 6,
'white': 7,
'default': 9,
}
# might be able to use IBMPC map from pyte charsets,
# in that case, would have to mask out certain things (like ESC)
# in the same way that Screen's draw method would do
# for now at least get some of the arrows in there (note ESC is one
# of those arrows... so skip it...
ansichars = dict(zip((0x18, 0x19), u'\u2191\u2193'))
def chunk_output(output, n):
for i in range(0, len(output), n):
yield output[i:i + n]
def get_buffer_output(nodename):
out = _bufferdaemon.stdin
instream = _bufferdaemon.stdout
if not isinstance(nodename, bytes):
nodename = nodename.encode('utf8')
outdata = b''
with _bufferlock:
out.write(struct.pack('I', len(nodename)))
out.write(nodename)
out.flush()
select.select((instream,), (), (), 30)
while not outdata or outdata[-1]:
chunk = instream.read(128)
if chunk:
outdata += chunk
else:
select.select((instream,), (), (), 0)
return outdata[:-1]
def _utf8_normalize(data, shiftin, decoder):
def send_output(nodename, output):
if not isinstance(nodename, bytes):
nodename = nodename.encode('utf8')
with _bufferlock:
_bufferdaemon.stdin.write(struct.pack('I', len(nodename) | (1 << 29)))
_bufferdaemon.stdin.write(nodename)
_bufferdaemon.stdin.flush()
for chunk in chunk_output(output, 8192):
_bufferdaemon.stdin.write(struct.pack('I', len(chunk) | (2 << 29)))
_bufferdaemon.stdin.write(chunk)
_bufferdaemon.stdin.flush()
def _utf8_normalize(data, decoder):
# first we give the stateful decoder a crack at the byte stream,
# we may come up empty in the event of a partial multibyte
try:
@ -88,60 +113,9 @@ def _utf8_normalize(data, shiftin, decoder):
# Finally, the low part of ascii is valid utf-8, but we are going to be
# more interested in the cp437 versions (since this is console *output*
# not input
if shiftin is None:
data = data.translate(ansichars)
return data.encode('utf-8')
def pytechars2line(chars, maxlen=None):
line = b'\x1b[m' # start at default params
lb = False # last bold
li = False # last italic
lu = False # last underline
ls = False # last strikethrough
lr = False # last reverse
lfg = 'default' # last fg color
lbg = 'default' # last bg color
hasdata = False
len = 1
for charidx in range(maxlen):
char = chars[charidx]
csi = bytearray([])
if char.fg != lfg:
csi.append(30 + pytecolors2ansi.get(char.fg, 9))
lfg = char.fg
if char.bg != lbg:
csi.append(40 + pytecolors2ansi.get(char.bg, 9))
lbg = char.bg
if char.bold != lb:
lb = char.bold
csi.append(1 if lb else 22)
if char.italics != li:
li = char.italics
csi.append(3 if li else 23)
if char.underscore != lu:
lu = char.underscore
csi.append(4 if lu else 24)
if char.strikethrough != ls:
ls = char.strikethrough
csi.append(9 if ls else 29)
if char.reverse != lr:
lr = char.reverse
csi.append(7 if lr else 27)
if csi:
line += b'\x1b[' + b';'.join(['{0}'.format(x).encode('utf-8') for x in csi]) + b'm'
if not hasdata and char.data.rstrip():
hasdata = True
chardata = char.data
if not isinstance(chardata, bytes):
chardata = chardata.encode('utf-8')
line += chardata
if maxlen and len >= maxlen:
break
len += 1
return line, hasdata
class ConsoleHandler(object):
_plugin_path = '/nodes/{0}/_console/session'
_logtobuffer = True
@ -161,15 +135,15 @@ class ConsoleHandler(object):
self.node = node
self.connectstate = 'unconnected'
self._isalive = True
self.buffer = pyte.Screen(100, 31)
self.termstream = pyte.ByteStream()
self.termstream.attach(self.buffer)
#self.buffer = pyte.Screen(100, 31)
#self.termstream = pyte.ByteStream()
#self.termstream.attach(self.buffer)
self.livesessions = set([])
self.utf8decoder = codecs.getincrementaldecoder('utf-8')()
if self._logtobuffer:
self.logger = log.Logger(node, console=True,
tenant=configmanager.tenant)
(text, termstate, timestamp) = (b'', 0, False)
timestamp = False
# when reading from log file, we will use wall clock
# it should usually match walltime.
self.lasttime = 0
@ -182,13 +156,7 @@ class ConsoleHandler(object):
# guess
self.lasttime = util.monotonic_time()
self.clearbuffer()
self.appmodedetected = False
self.shiftin = None
self.reconnect = None
if termstate & 1:
self.appmodedetected = True
if termstate & 2:
self.shiftin = b'0'
self.users = {}
self._attribwatcher = None
self._console = None
@ -216,10 +184,7 @@ class ConsoleHandler(object):
if not isinstance(data, bytes):
data = data.encode('utf-8')
try:
self.termstream.feed(data)
except StopIteration: # corrupt parser state, start over
self.termstream = pyte.ByteStream()
self.termstream.attach(self.buffer)
send_output(self.node, data)
except Exception:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
@ -536,19 +501,7 @@ class ConsoleHandler(object):
return
if not isinstance(data, bytes):
data = data.encode('utf-8')
if b'\x1b[?1l' in data: # request for ansi mode cursor keys
self.appmodedetected = False
if b'\x1b[?1h' in data: # remember the session wants the client to use
# 'application mode' Thus far only observed on esxi
self.appmodedetected = True
if b'\x1b)0' in data:
# console indicates it wants access to special drawing characters
self.shiftin = b'0'
eventdata = 0
if self.appmodedetected:
eventdata |= 1
if self.shiftin is not None:
eventdata |= 2
# TODO: analyze buffer for registered events, examples:
# panics
# certificate signing request
@ -557,7 +510,7 @@ class ConsoleHandler(object):
self.clearerror = False
self.feedbuffer(b'\x1bc\x1b[2J\x1b[1;1H')
self._send_rcpts(b'\x1bc\x1b[2J\x1b[1;1H')
self._send_rcpts(_utf8_normalize(data, self.shiftin, self.utf8decoder))
self._send_rcpts(_utf8_normalize(data, self.utf8decoder))
self.log(data, eventdata=eventdata)
self.lasttime = util.monotonic_time()
self.feedbuffer(data)
@ -583,36 +536,7 @@ class ConsoleHandler(object):
'connectstate': self.connectstate,
'clientcount': len(self.livesessions),
}
retdata = b'\x1b[H\x1b[J' # clear screen
pendingbl = b'' # pending blank lines
maxlen = 0
for line in self.buffer.display:
line = line.rstrip()
if len(line) > maxlen:
maxlen = len(line)
for line in range(self.buffer.lines):
nline, notblank = pytechars2line(self.buffer.buffer[line], maxlen)
if notblank:
if pendingbl:
retdata += pendingbl
pendingbl = b''
retdata += nline + b'\r\n'
else:
pendingbl += nline + b'\r\n'
if len(retdata) > 6:
retdata = retdata[:-2] # remove the last \r\n
cursordata = '\x1b[{0};{1}H'.format(self.buffer.cursor.y + 1,
self.buffer.cursor.x + 1)
if not isinstance(cursordata, bytes):
cursordata = cursordata.encode('utf-8')
retdata += cursordata
if self.shiftin is not None: # detected that terminal requested a
# shiftin character set, relay that to the terminal that cannected
retdata += b'\x1b)' + self.shiftin
#if self.appmodedetected:
# retdata += b'\x1b[?1h'
#else:
# retdata += b'\x1b[?1l'
retdata = get_buffer_output(self.node)
return retdata, connstate
def write(self, data):
@ -661,7 +585,16 @@ def _start_tenant_sessions(cfm):
def start_console_sessions():
global _tracelog
global _bufferdaemon
global _bufferlock
_bufferlock = semaphore.Semaphore()
_tracelog = log.Logger('trace')
_bufferdaemon = subprocess.Popen(
['/opt/confluent/bin/vtbufferd'], stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
fl = fcntl.fcntl(_bufferdaemon.stdout.fileno(), fcntl.F_GETFL)
fcntl.fcntl(_bufferdaemon.stdout.fileno(),
fcntl.F_SETFL, fl | os.O_NONBLOCK)
configmodule.hook_new_configmanagers(_start_tenant_sessions)

View File

@ -0,0 +1,5 @@
vtbufferd:
gcc -O3 -o vtbufferd vtbufferd.c tmt.c
clean:
rm vtbufferd

521
confluent_vtbufferd/tmt.c Normal file
View File

@ -0,0 +1,521 @@
/* Copyright (c) 2017 Rob King
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the copyright holder nor the
* names of contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS,
* COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tmt.h"
#define BUF_MAX 100
#define PAR_MAX 8
#define TAB 8
#define MAX(x, y) (((size_t)(x) > (size_t)(y)) ? (size_t)(x) : (size_t)(y))
#define MIN(x, y) (((size_t)(x) < (size_t)(y)) ? (size_t)(x) : (size_t)(y))
#define CLINE(vt) (vt)->screen.lines[MIN((vt)->curs.r, (vt)->screen.nline - 1)]
#define P0(x) (vt->pars[x])
#define P1(x) (vt->pars[x]? vt->pars[x] : 1)
#define CB(vt, m, a) ((vt)->cb? (vt)->cb(m, vt, a, (vt)->p) : (void)0)
#define INESC ((vt)->state)
#define COMMON_VARS \
TMTSCREEN *s = &vt->screen; \
TMTPOINT *c = &vt->curs; \
TMTLINE *l = CLINE(vt); \
TMTCHAR *t = vt->tabs->chars
#define HANDLER(name) static void name (TMT *vt) { COMMON_VARS;
struct TMT{
TMTPOINT curs, oldcurs;
TMTATTRS attrs, oldattrs;
bool dirty, acs, decdraw, ignored;
TMTSCREEN screen;
TMTLINE *tabs;
TMTCALLBACK cb;
void *p;
const wchar_t *acschars;
const wchar_t *decchars;
mbstate_t ms;
size_t nmb;
char mb[BUF_MAX + 1];
size_t pars[PAR_MAX];
size_t npar;
size_t arg;
enum {S_NUL, S_ESC, S_ARG, S_SCS} state;
};
static TMTATTRS defattrs = {.fg = TMT_COLOR_DEFAULT, .bg = TMT_COLOR_DEFAULT};
static void writecharatcurs(TMT *vt, wchar_t w);
static wchar_t
decchar(const TMT *vt, unsigned char c)
{
if (c > 94 && c < 127)
return vt->decchars[c - 95];
return (wchar_t)c;
}
static wchar_t
tacs(const TMT *vt, unsigned char c)
{
/* The terminfo alternate character set for ANSI. */
static unsigned char map[] = {0020U, 0021U, 0030U, 0031U, 0333U, 0004U,
0261U, 0370U, 0361U, 0260U, 0331U, 0277U,
0332U, 0300U, 0305U, 0176U, 0304U, 0304U,
0304U, 0137U, 0303U, 0264U, 0301U, 0302U,
0263U, 0363U, 0362U, 0343U, 0330U, 0234U,
0376U};
for (size_t i = 0; i < sizeof(map); i++) if (map[i] == c)
return vt->acschars[i];
return (wchar_t)c;
}
static void
dirtylines(TMT *vt, size_t s, size_t e)
{
vt->dirty = true;
for (size_t i = s; i < e; i++)
vt->screen.lines[i]->dirty = true;
}
static void
clearline(TMT *vt, TMTLINE *l, size_t s, size_t e)
{
vt->dirty = l->dirty = true;
for (size_t i = s; i < e && i < vt->screen.ncol; i++){
l->chars[i].a = defattrs;
l->chars[i].c = L' ';
}
}
static void
clearlines(TMT *vt, size_t r, size_t n)
{
for (size_t i = r; i < r + n && i < vt->screen.nline; i++)
clearline(vt, vt->screen.lines[i], 0, vt->screen.ncol);
}
static void
scrup(TMT *vt, size_t r, size_t n)
{
n = MIN(n, vt->screen.nline - 1 - r);
if (n){
TMTLINE *buf[n];
memcpy(buf, vt->screen.lines + r, n * sizeof(TMTLINE *));
memmove(vt->screen.lines + r, vt->screen.lines + r + n,
(vt->screen.nline - n - r) * sizeof(TMTLINE *));
memcpy(vt->screen.lines + (vt->screen.nline - n),
buf, n * sizeof(TMTLINE *));
clearlines(vt, vt->screen.nline - n, n);
dirtylines(vt, r, vt->screen.nline);
}
}
static void
scrdn(TMT *vt, size_t r, size_t n)
{
n = MIN(n, vt->screen.nline - 1 - r);
if (n){
TMTLINE *buf[n];
memcpy(buf, vt->screen.lines + (vt->screen.nline - n),
n * sizeof(TMTLINE *));
memmove(vt->screen.lines + r + n, vt->screen.lines + r,
(vt->screen.nline - n - r) * sizeof(TMTLINE *));
memcpy(vt->screen.lines + r, buf, n * sizeof(TMTLINE *));
clearlines(vt, r, n);
dirtylines(vt, r, vt->screen.nline);
}
}
HANDLER(ed)
size_t b = 0;
size_t e = s->nline;
switch (P0(0)){
case 0: b = c->r + 1; clearline(vt, l, c->c, vt->screen.ncol); break;
case 1: e = c->r - 1; clearline(vt, l, 0, c->c); break;
case 2: /* use defaults */ break;
default: /* do nothing */ return;
}
clearlines(vt, b, e - b);
}
HANDLER(ich)
size_t n = P1(0); /* XXX use MAX */
if (n > s->ncol - c->c - 1) n = s->ncol - c->c - 1;
memmove(l->chars + c->c + n, l->chars + c->c,
MIN(s->ncol - 1 - c->c,
(s->ncol - c->c - n - 1)) * sizeof(TMTCHAR));
clearline(vt, l, c->c, n);
}
HANDLER(dch)
size_t n = P1(0); /* XXX use MAX */
if (n > s->ncol - c->c) n = s->ncol - c->c;
else if (n == 0) return;
memmove(l->chars + c->c, l->chars + c->c + n,
(s->ncol - c->c - n) * sizeof(TMTCHAR));
clearline(vt, l, s->ncol - n, s->ncol);
/* VT102 manual says the attribute for the newly empty characters
* should be the same as the last character moved left, which isn't
* what clearline() currently does.
*/
}
HANDLER(el)
switch (P0(0)){
case 0: clearline(vt, l, c->c, vt->screen.ncol); break;
case 1: clearline(vt, l, 0, MIN(c->c + 1, s->ncol - 1)); break;
case 2: clearline(vt, l, 0, vt->screen.ncol); break;
}
}
HANDLER(sgr)
#define FGBG(c) *(P0(i) < 40? &vt->attrs.fg : &vt->attrs.bg) = c
for (size_t i = 0; i < vt->npar; i++) switch (P0(i)){
case 0: vt->attrs = defattrs; break;
case 1: case 22: vt->attrs.bold = P0(0) < 20; break;
case 2: case 23: vt->attrs.dim = P0(0) < 20; break;
case 4: case 24: vt->attrs.underline = P0(0) < 20; break;
case 5: case 25: vt->attrs.blink = P0(0) < 20; break;
case 7: case 27: vt->attrs.reverse = P0(0) < 20; break;
case 8: case 28: vt->attrs.invisible = P0(0) < 20; break;
case 10: case 11: vt->acs = P0(0) > 10; break;
case 30: case 40: FGBG(TMT_COLOR_BLACK); break;
case 31: case 41: FGBG(TMT_COLOR_RED); break;
case 32: case 42: FGBG(TMT_COLOR_GREEN); break;
case 33: case 43: FGBG(TMT_COLOR_YELLOW); break;
case 34: case 44: FGBG(TMT_COLOR_BLUE); break;
case 35: case 45: FGBG(TMT_COLOR_MAGENTA); break;
case 36: case 46: FGBG(TMT_COLOR_CYAN); break;
case 37: case 47: FGBG(TMT_COLOR_WHITE); break;
case 39: case 49: FGBG(TMT_COLOR_DEFAULT); break;
}
}
HANDLER(rep)
if (!c->c) return;
wchar_t r = l->chars[c->c - 1].c;
for (size_t i = 0; i < P1(0); i++)
writecharatcurs(vt, r);
}
HANDLER(dsr)
char r[BUF_MAX + 1] = {0};
snprintf(r, BUF_MAX, "\033[%zd;%zdR", c->r + 1, c->c + 1);
CB(vt, TMT_MSG_ANSWER, (const char *)r);
}
HANDLER(resetparser)
memset(vt->pars, 0, sizeof(vt->pars));
vt->state = vt->npar = vt->arg = vt->ignored = (bool)0;
}
HANDLER(consumearg)
if (vt->npar < PAR_MAX)
vt->pars[vt->npar++] = vt->arg;
vt->arg = 0;
}
HANDLER(fixcursor)
c->r = MIN(c->r, s->nline - 1);
c->c = MIN(c->c, s->ncol - 1);
}
static bool
handlechar(TMT *vt, char i)
{
COMMON_VARS;
char cs[] = {i, 0};
#define ON(S, C, A) if (vt->state == (S) && strchr(C, i)){ A; return true;}
#define DO(S, C, A) ON(S, C, consumearg(vt); if (!vt->ignored) {A;} \
fixcursor(vt); resetparser(vt););
DO(S_NUL, "\x07", CB(vt, TMT_MSG_BELL, NULL))
DO(S_NUL, "\x08", if (c->c) c->c--)
DO(S_NUL, "\x09", while (++c->c < s->ncol - 1 && t[c->c].c != L'*'))
DO(S_NUL, "\x0a", c->r < s->nline - 1? (void)c->r++ : scrup(vt, 0, 1))
DO(S_NUL, "\x0d", c->c = 0)
ON(S_NUL, "\x1b", vt->state = S_ESC)
ON(S_ESC, "\x1b", vt->state = S_ESC)
DO(S_ESC, "H", t[c->c].c = L'*')
DO(S_ESC, "7", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs)
DO(S_ESC, "8", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs)
ON(S_ESC, "+*)", vt->ignored = true; vt->state = S_ARG)
ON(S_ESC, "(", vt->state = S_SCS)
DO(S_SCS, "0", vt->decdraw = true)
DO(S_SCS, "B", vt->decdraw = false)
DO(S_ESC, "c", tmt_reset(vt))
DO(S_ESC, "M", if (c->r) c->r--)
ON(S_ESC, "[", vt->state = S_ARG)
ON(S_ARG, "\x1b", vt->state = S_ESC)
ON(S_ARG, ";", consumearg(vt))
ON(S_ARG, "?", (void)0)
ON(S_ARG, "0123456789", vt->arg = vt->arg * 10 + atoi(cs))
DO(S_ARG, "A", c->r = MAX(c->r - P1(0), 0))
DO(S_ARG, "B", c->r = MIN(c->r + P1(0), s->nline - 1))
DO(S_ARG, "C", c->c = MIN(c->c + P1(0), s->ncol - 1))
DO(S_ARG, "D", c->c = MIN(c->c - P1(0), c->c))
DO(S_ARG, "E", c->c = 0; c->r = MIN(c->r + P1(0), s->nline - 1))
DO(S_ARG, "F", c->c = 0; c->r = MAX(c->r - P1(0), 0))
DO(S_ARG, "G", c->c = MIN(P1(0) - 1, s->ncol - 1))
DO(S_ARG, "d", c->r = MIN(P1(0) - 1, s->nline - 1))
DO(S_ARG, "Hf", c->r = P1(0) - 1; c->c = P1(1) - 1)
DO(S_ARG, "I", while (++c->c < s->ncol - 1 && t[c->c].c != L'*'))
DO(S_ARG, "J", ed(vt))
DO(S_ARG, "K", el(vt))
DO(S_ARG, "L", scrdn(vt, c->r, P1(0)))
DO(S_ARG, "M", scrup(vt, c->r, P1(0)))
DO(S_ARG, "P", dch(vt))
DO(S_ARG, "S", scrup(vt, 0, P1(0)))
DO(S_ARG, "T", scrdn(vt, 0, P1(0)))
DO(S_ARG, "X", clearline(vt, l, c->c, P1(0)))
DO(S_ARG, "Z", while (c->c && t[--c->c].c != L'*'))
DO(S_ARG, "b", rep(vt));
DO(S_ARG, "c", CB(vt, TMT_MSG_ANSWER, "\033[?6c"))
DO(S_ARG, "g", if (P0(0) == 3) clearline(vt, vt->tabs, 0, s->ncol))
DO(S_ARG, "m", sgr(vt))
DO(S_ARG, "n", if (P0(0) == 6) dsr(vt))
DO(S_ARG, "h", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "t"))
DO(S_ARG, "i", (void)0)
DO(S_ARG, "l", if (P0(0) == 25) CB(vt, TMT_MSG_CURSOR, "f"))
DO(S_ARG, "s", vt->oldcurs = vt->curs; vt->oldattrs = vt->attrs)
DO(S_ARG, "u", vt->curs = vt->oldcurs; vt->attrs = vt->oldattrs)
DO(S_ARG, "@", ich(vt))
return resetparser(vt), false;
}
static void
notify(TMT *vt, bool update, bool moved)
{
if (update) CB(vt, TMT_MSG_UPDATE, &vt->screen);
if (moved) CB(vt, TMT_MSG_MOVED, &vt->curs);
}
static TMTLINE *
allocline(TMT *vt, TMTLINE *o, size_t n, size_t pc)
{
TMTLINE *l = realloc(o, sizeof(TMTLINE) + n * sizeof(TMTCHAR));
if (!l) return NULL;
clearline(vt, l, pc, n);
return l;
}
static void
freelines(TMT *vt, size_t s, size_t n, bool screen)
{
for (size_t i = s; vt->screen.lines && i < s + n; i++){
free(vt->screen.lines[i]);
vt->screen.lines[i] = NULL;
}
if (screen) free(vt->screen.lines);
}
TMT *
tmt_open(size_t nline, size_t ncol, TMTCALLBACK cb, void *p,
const wchar_t *acs)
{
TMT *vt = calloc(1, sizeof(TMT));
if (!nline || !ncol || !vt) return free(vt), NULL;
/* ASCII-safe defaults for box-drawing characters. */
vt->acschars = acs? acs : L"→←↑↓■◆▒°±▒┘┐┌└┼⎺───⎽├┤┴┬│≤≥π≠£•"; //L"><^v#+:o##+++++~---_++++|<>*!fo";
vt->decchars = L" ◆▒\t\f\r\n°±\n\v┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£•";
vt->cb = cb;
vt->p = p;
if (!tmt_resize(vt, nline, ncol)) return tmt_close(vt), NULL;
return vt;
}
void
tmt_close(TMT *vt)
{
free(vt->tabs);
freelines(vt, 0, vt->screen.nline, true);
free(vt);
}
bool
tmt_resize(TMT *vt, size_t nline, size_t ncol)
{
if (nline < 2 || ncol < 2) return false;
if (nline < vt->screen.nline)
freelines(vt, nline, vt->screen.nline - nline, false);
TMTLINE **l = realloc(vt->screen.lines, nline * sizeof(TMTLINE *));
if (!l) return false;
size_t pc = vt->screen.ncol;
vt->screen.lines = l;
vt->screen.ncol = ncol;
for (size_t i = 0; i < nline; i++){
TMTLINE *nl = NULL;
if (i >= vt->screen.nline)
nl = vt->screen.lines[i] = allocline(vt, NULL, ncol, 0);
else
nl = allocline(vt, vt->screen.lines[i], ncol, pc);
if (!nl) return false;
vt->screen.lines[i] = nl;
}
vt->screen.nline = nline;
vt->tabs = allocline(vt, vt->tabs, ncol, 0);
if (!vt->tabs) return free(l), false;
vt->tabs->chars[0].c = vt->tabs->chars[ncol - 1].c = L'*';
for (size_t i = 0; i < ncol; i++) if (i % TAB == 0)
vt->tabs->chars[i].c = L'*';
fixcursor(vt);
dirtylines(vt, 0, nline);
notify(vt, true, true);
return true;
}
static void
writecharatcurs(TMT *vt, wchar_t w)
{
COMMON_VARS;
#ifdef TMT_HAS_WCWIDTH
extern int wcwidth(wchar_t c);
if (wcwidth(w) > 1) w = TMT_INVALID_CHAR;
if (wcwidth(w) < 0) return;
#endif
CLINE(vt)->chars[vt->curs.c].c = w;
CLINE(vt)->chars[vt->curs.c].a = vt->attrs;
CLINE(vt)->dirty = vt->dirty = true;
if (c->c < s->ncol - 1)
c->c++;
else{
c->c = 0;
c->r++;
}
if (c->r >= s->nline){
c->r = s->nline - 1;
scrup(vt, 0, 1);
}
}
static inline size_t
testmbchar(TMT *vt)
{
mbstate_t ts = vt->ms;
return vt->nmb? mbrtowc(NULL, vt->mb, vt->nmb, &ts) : (size_t)-2;
}
static inline wchar_t
getmbchar(TMT *vt)
{
wchar_t c = 0;
size_t n = mbrtowc(&c, vt->mb, vt->nmb, &vt->ms);
vt->nmb = 0;
return (n == (size_t)-1 || n == (size_t)-2)? TMT_INVALID_CHAR : c;
}
void
tmt_write(TMT *vt, const char *s, size_t n)
{
TMTPOINT oc = vt->curs;
n = n? n : strlen(s);
for (size_t p = 0; p < n; p++){
if (handlechar(vt, s[p]))
continue;
else if (vt->acs)
writecharatcurs(vt, tacs(vt, (unsigned char)s[p]));
else if (vt->decdraw)
writecharatcurs(vt, decchar(vt, (unsigned char)s[p]));
else if (vt->nmb >= BUF_MAX)
writecharatcurs(vt, getmbchar(vt));
else{
switch (testmbchar(vt)){
case (size_t)-1: writecharatcurs(vt, getmbchar(vt)); break;
case (size_t)-2: vt->mb[vt->nmb++] = s[p]; break;
}
if (testmbchar(vt) <= MB_LEN_MAX)
writecharatcurs(vt, getmbchar(vt));
}
}
notify(vt, vt->dirty, memcmp(&oc, &vt->curs, sizeof(oc)) != 0);
}
const TMTSCREEN *
tmt_screen(const TMT *vt)
{
return &vt->screen;
}
const TMTPOINT *
tmt_cursor(const TMT *vt)
{
return &vt->curs;
}
void
tmt_clean(TMT *vt)
{
for (size_t i = 0; i < vt->screen.nline; i++)
vt->dirty = vt->screen.lines[i]->dirty = false;
}
void
tmt_reset(TMT *vt)
{
vt->curs.r = vt->curs.c = vt->oldcurs.r = vt->oldcurs.c = vt->acs = (bool)0;
resetparser(vt);
vt->attrs = vt->oldattrs = defattrs;
memset(&vt->ms, 0, sizeof(vt->ms));
clearlines(vt, 0, vt->screen.nline);
CB(vt, TMT_MSG_CURSOR, "t");
notify(vt, true, true);
}

140
confluent_vtbufferd/tmt.h Normal file
View File

@ -0,0 +1,140 @@
/* Copyright (c) 2017 Rob King
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the copyright holder nor the
* names of contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS,
* COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TMT_H
#define TMT_H
#include <stdbool.h>
#include <stddef.h>
#include <wchar.h>
/**** INVALID WIDE CHARACTER */
#ifndef TMT_INVALID_CHAR
#define TMT_INVALID_CHAR ((wchar_t)0xfffd)
#endif
/**** INPUT SEQUENCES */
#define TMT_KEY_UP "\033[A"
#define TMT_KEY_DOWN "\033[B"
#define TMT_KEY_RIGHT "\033[C"
#define TMT_KEY_LEFT "\033[D"
#define TMT_KEY_HOME "\033[H"
#define TMT_KEY_END "\033[Y"
#define TMT_KEY_INSERT "\033[L"
#define TMT_KEY_BACKSPACE "\x08"
#define TMT_KEY_ESCAPE "\x1b"
#define TMT_KEY_BACK_TAB "\033[Z"
#define TMT_KEY_PAGE_UP "\033[V"
#define TMT_KEY_PAGE_DOWN "\033[U"
#define TMT_KEY_F1 "\033OP"
#define TMT_KEY_F2 "\033OQ"
#define TMT_KEY_F3 "\033OR"
#define TMT_KEY_F4 "\033OS"
#define TMT_KEY_F5 "\033OT"
#define TMT_KEY_F6 "\033OU"
#define TMT_KEY_F7 "\033OV"
#define TMT_KEY_F8 "\033OW"
#define TMT_KEY_F9 "\033OX"
#define TMT_KEY_F10 "\033OY"
/**** BASIC DATA STRUCTURES */
typedef struct TMT TMT;
typedef enum{
TMT_COLOR_DEFAULT = -1,
TMT_COLOR_BLACK = 1,
TMT_COLOR_RED,
TMT_COLOR_GREEN,
TMT_COLOR_YELLOW,
TMT_COLOR_BLUE,
TMT_COLOR_MAGENTA,
TMT_COLOR_CYAN,
TMT_COLOR_WHITE,
TMT_COLOR_MAX
} tmt_color_t;
typedef struct TMTATTRS TMTATTRS;
struct TMTATTRS{
bool bold;
bool dim;
bool underline;
bool blink;
bool reverse;
bool invisible;
tmt_color_t fg;
tmt_color_t bg;
};
typedef struct TMTCHAR TMTCHAR;
struct TMTCHAR{
wchar_t c;
TMTATTRS a;
};
typedef struct TMTPOINT TMTPOINT;
struct TMTPOINT{
size_t r;
size_t c;
};
typedef struct TMTLINE TMTLINE;
struct TMTLINE{
bool dirty;
TMTCHAR chars[];
};
typedef struct TMTSCREEN TMTSCREEN;
struct TMTSCREEN{
size_t nline;
size_t ncol;
TMTLINE **lines;
};
/**** CALLBACK SUPPORT */
typedef enum{
TMT_MSG_MOVED,
TMT_MSG_UPDATE,
TMT_MSG_ANSWER,
TMT_MSG_BELL,
TMT_MSG_CURSOR
} tmt_msg_t;
typedef void (*TMTCALLBACK)(tmt_msg_t m, struct TMT *v, const void *r, void *p);
/**** PUBLIC FUNCTIONS */
TMT *tmt_open(size_t nline, size_t ncol, TMTCALLBACK cb, void *p,
const wchar_t *acs);
void tmt_close(TMT *vt);
bool tmt_resize(TMT *vt, size_t nline, size_t ncol);
void tmt_write(TMT *vt, const char *s, size_t n);
const TMTSCREEN *tmt_screen(const TMT *vt);
const TMTPOINT *tmt_cursor(const TMT *vt);
void tmt_clean(TMT *vt);
void tmt_reset(TMT *vt);
#endif

View File

@ -0,0 +1,143 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <locale.h>
#include <unistd.h>
#include "tmt.h"
#define HASHSIZE 2053
#define MAXNAMELEN 256
#define MAXDATALEN 8192
struct terment {
struct terment *next;
char *name;
TMT *vt;
};
#define SETNODE 1
#define WRITE 2
#define READBUFF 0
static struct terment *buffers[HASHSIZE];
unsigned long hash(char *str)
/* djb2a */
{
unsigned long idx = 5381;
int c;
while ((c = *str++))
idx = ((idx << 5) + idx) + c;
return idx % HASHSIZE;
}
TMT *get_termentbyname(char *name) {
struct terment *ret;
for (ret = buffers[hash(name)]; ret != NULL; ret = ret->next)
if (strcmp(name, ret->name) == 0)
return ret->vt;
return NULL;
}
TMT *set_termentbyname(char *name) {
struct terment *ret;
int idx;
idx = hash(name);
for (ret = buffers[idx]; ret != NULL; ret = ret->next)
if (strcmp(name, ret->name) == 0)
return ret->vt;
ret = (struct terment *)malloc(sizeof(*ret));
ret->next = buffers[idx];
ret->name = strdup(name);
ret->vt = tmt_open(31, 100, NULL, NULL, L"→←↑↓■◆▒°±▒┘┐┌└┼⎺───⎽├┤┴┬│≤≥π≠£•");
buffers[idx] = ret;
return ret->vt;
}
void dump_vt(TMT* outvt) {
const TMTSCREEN *out = tmt_screen(outvt);
const TMTPOINT *curs = tmt_cursor(outvt);
int line, idx, deferredlines, deferredspaces;
bool printedline, skipnl;
wprintf(L"\033[H\033[J");
deferredlines = 0;
skipnl = true;
for (line = 0; line < out->nline; line++) {
deferredspaces = 0;
printedline = false;
for (idx = 0; idx < out->ncol; idx++) {
if (out->lines[line]->chars[idx].c == L' ') {
deferredspaces += 1;
} else {
if (!printedline) {
printedline = true;
if (skipnl)
skipnl = false;
else
wprintf(L"\r\n");
}
while (deferredlines) {
wprintf(L"\r\n");
deferredlines -= 1;
}
while (deferredspaces > 0) {
wprintf(L" ");
deferredspaces -= 1;
}
wprintf(L"%lc", out->lines[line]->chars[idx].c);
}
}
if (!printedline)
deferredlines += 1;
}
fflush(stdout);
wprintf(L"\x1b[%ld;%ldH", curs->r + 1, curs->c + 1);
fflush(stdout);
idx = write(1, "\x00", 1);
if (idx < 0) {
return;
}
}
int main(int argc, char* argv[]) {
int cmd, length;
setlocale(LC_ALL, "");
char cmdbuf[MAXDATALEN];
char currnode[MAXNAMELEN];
TMT *currvt = NULL;
TMT *outvt = NULL;
stdin = freopen(NULL, "rb", stdin);
if (stdin == NULL) {
exit(1);
}
while (1) {
length = fread(&cmd, 4, 1, stdin);
if (length < 0)
continue;
length = cmd & 536870911;
cmd = cmd >> 29;
if (cmd == SETNODE) {
currnode[length] = 0;
cmd = fread(currnode, 1, length, stdin);
if (cmd < 0)
continue;
currvt = set_termentbyname(currnode);
} else if (cmd == WRITE) {
if (currvt == NULL)
currvt = set_termentbyname("");
cmdbuf[length] = 0;
cmd = fread(cmdbuf, 1, length, stdin);
if (cmd < 0)
continue;
tmt_write(currvt, cmdbuf, length);
} else if (cmd == READBUFF) {
cmdbuf[length] = 0;
cmd = fread(cmdbuf, 1, length, stdin);
if (cmd < 0)
continue;
outvt = get_termentbyname(cmdbuf);
if (outvt != NULL) {
dump_vt(outvt);
}
}
}
}