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:
parent
5e0ebce300
commit
beab6a3c02
@ -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)
|
||||
|
||||
|
||||
|
5
confluent_vtbufferd/Makefile
Normal file
5
confluent_vtbufferd/Makefile
Normal file
@ -0,0 +1,5 @@
|
||||
vtbufferd:
|
||||
gcc -O3 -o vtbufferd vtbufferd.c tmt.c
|
||||
|
||||
clean:
|
||||
rm vtbufferd
|
521
confluent_vtbufferd/tmt.c
Normal file
521
confluent_vtbufferd/tmt.c
Normal 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
140
confluent_vtbufferd/tmt.h
Normal 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
|
143
confluent_vtbufferd/vtbufferd.c
Normal file
143
confluent_vtbufferd/vtbufferd.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user