shrub/vere/term.c

1504 lines
35 KiB
C

/* v/term.c
**
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <setjmp.h>
#include <gmp.h>
#include <stdint.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <uv.h>
#include <errno.h>
#include <curses.h>
#include <termios.h>
#include <term.h>
#include <anachronism/common.h>
#include <anachronism/nvt.h>
#include "all.h"
#include "vere/vere.h"
static void _term_spinner_cb(void*);
static void _term_read_tn_cb(uv_stream_t* tcp_u,
ssize_t siz_i,
const uv_buf_t * buf_u);
static void _term_read_cb(uv_stream_t* tcp_u,
ssize_t siz_i,
const uv_buf_t * buf_u);
static inline void _term_suck(u3_utty*, const c3_y*, ssize_t);
static void _tel_event(telnet_nvt*, telnet_event*);
static void _tel_opt(telnet_nvt*, telnet_byte, telnet_telopt_event*);
#define _T_ECHO 1 // local echo
#define _T_CTIM 3 // suppress GA/char-at-a-time
#define _T_NAWS 31 // negotiate about window size
#define _SPIN_COOL_US 500000 // spinner activation delay when cool
#define _SPIN_WARM_US 50000 // spinner activation delay when warm
#define _SPIN_RATE_US 250000 // spinner rate (microseconds/frame)
#define _SPIN_IDLE_US 500000 // spinner cools down if stopped this long
/* _term_msc_out_host(): unix microseconds from current host time.
*/
static c3_d
_term_msc_out_host()
{
struct timeval tim_tv;
gettimeofday(&tim_tv, 0);
return 1000000ULL * tim_tv.tv_sec + tim_tv.tv_usec;
}
static void
_term_alloc(uv_handle_t* had_u,
size_t len_i,
uv_buf_t* buf
)
{
void* ptr_v = c3_malloc(len_i);
*buf = uv_buf_init(ptr_v, len_i);
}
/* _term_close_cb(): free terminal.
*/
static void
_term_close_cb(uv_handle_t* han_t)
{
u3_utty* tty_u = (void*) han_t;
if ( u3_Host.uty_u == tty_u ) {
u3_Host.uty_u = tty_u->nex_u;
}
else {
u3_utty* uty_u;
for (uty_u = u3_Host.uty_u; uty_u; uty_u = uty_u->nex_u ) {
if ( uty_u->nex_u == tty_u ) {
uty_u->nex_u = tty_u->nex_u;
break;
}
}
}
{
u3_noun tid = u3dc("scot", c3__ud, tty_u->tid_l);
u3_noun pax = u3nq(u3_blip, c3__term, tid, u3_nul);
u3v_plan(u3k(pax), u3nc(c3__hook, u3_nul));
u3z(pax);
}
free(tty_u);
}
/* _tel_close_cb(): close telnet terminal
*/
static void
_tel_close_cb(uv_handle_t* han_t)
{
u3_utel* pty_u = (u3_utel*)(void*)han_t;
telnet_nvt_free(pty_u->tel_u);
_term_close_cb(han_t);
}
/* u3_term_io_init(): initialize terminal.
*/
void
u3_term_io_init()
{
u3_utty* uty_u = calloc(1, sizeof(u3_utty));
if ( c3y == u3_Host.ops_u.dem ) {
uty_u->fid_i = 1;
uv_pipe_init(u3L, &(uty_u->pop_u), 0);
uv_pipe_open(&(uty_u->pop_u), uty_u->fid_i);
}
else {
// Initialize event processing. Rawdog it.
//
{
uty_u->fid_i = 0; // stdin, yes we write to it...
uv_pipe_init(u3L, &(uty_u->pop_u), 0);
uv_pipe_open(&(uty_u->pop_u), uty_u->fid_i);
uv_read_start((uv_stream_t*)&(uty_u->pop_u), _term_alloc, _term_read_cb);
}
// Configure horrible stateful terminfo api.
//
{
if ( 0 != setupterm(0, 2, 0) ) {
c3_assert(!"init-setupterm");
}
}
// Load terminfo strings.
//
{
c3_w len_w;
# define _utfo(way, nam) \
{ \
uty_u->ufo_u.way.nam##_y = (const c3_y *) tigetstr(#nam); \
c3_assert(uty_u->ufo_u.way.nam##_y); \
}
uty_u->ufo_u.inn.max_w = 0;
_utfo(inn, kcuu1);
_utfo(inn, kcud1);
_utfo(inn, kcub1);
_utfo(inn, kcuf1);
_utfo(out, clear);
_utfo(out, el);
// _utfo(out, el1);
_utfo(out, ed);
_utfo(out, bel);
_utfo(out, cub1);
_utfo(out, cuf1);
_utfo(out, cuu1);
_utfo(out, cud1);
// _utfo(out, cub);
// _utfo(out, cuf);
// Terminfo chronically reports the wrong sequence for arrow
// keys on xterms. Drastic fix for ridiculous unacceptable bug.
// Yes, we could fix this with smkx/rmkx, but this is retarded as well.
{
uty_u->ufo_u.inn.kcuu1_y = (const c3_y*)"\033[A";
uty_u->ufo_u.inn.kcud1_y = (const c3_y*)"\033[B";
uty_u->ufo_u.inn.kcuf1_y = (const c3_y*)"\033[C";
uty_u->ufo_u.inn.kcub1_y = (const c3_y*)"\033[D";
}
uty_u->ufo_u.inn.max_w = 0;
if ( (len_w = strlen((c3_c*)uty_u->ufo_u.inn.kcuu1_y)) >
uty_u->ufo_u.inn.max_w )
{
uty_u->ufo_u.inn.max_w = len_w;
}
if ( (len_w = strlen((c3_c*)uty_u->ufo_u.inn.kcud1_y)) >
uty_u->ufo_u.inn.max_w )
{
uty_u->ufo_u.inn.max_w = len_w;
}
if ( (len_w = strlen((c3_c*)uty_u->ufo_u.inn.kcub1_y)) >
uty_u->ufo_u.inn.max_w )
{
uty_u->ufo_u.inn.max_w = len_w;
}
if ( (len_w = strlen((c3_c*)uty_u->ufo_u.inn.kcuf1_y)) >
uty_u->ufo_u.inn.max_w )
{
uty_u->ufo_u.inn.max_w = len_w;
}
}
// Load old terminal state to restore.
//
{
if ( 0 != tcgetattr(uty_u->fid_i, &uty_u->bak_u) ) {
c3_assert(!"init-tcgetattr");
}
if ( -1 == fcntl(uty_u->fid_i, F_GETFL, &uty_u->cug_i) ) {
c3_assert(!"init-fcntl");
}
uty_u->cug_i &= ~O_NONBLOCK; // could fix?
uty_u->nob_i = uty_u->cug_i | O_NONBLOCK; // O_NDELAY on older unix
}
// Construct raw termios configuration.
//
{
uty_u->raw_u = uty_u->bak_u;
uty_u->raw_u.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN);
uty_u->raw_u.c_iflag &= ~(ICRNL | INPCK | ISTRIP);
uty_u->raw_u.c_cflag &= ~(CSIZE | PARENB);
uty_u->raw_u.c_cflag |= CS8;
uty_u->raw_u.c_oflag &= ~(OPOST);
uty_u->raw_u.c_cc[VMIN] = 0;
uty_u->raw_u.c_cc[VTIME] = 0;
}
// Initialize mirror and accumulator state.
//
{
uty_u->tat_u.mir.lin_w = 0;
uty_u->tat_u.mir.len_w = 0;
uty_u->tat_u.mir.cus_w = 0;
uty_u->tat_u.esc.ape = c3n;
uty_u->tat_u.esc.bra = c3n;
uty_u->tat_u.fut.len_w = 0;
uty_u->tat_u.fut.wid_w = 0;
}
}
// This is terminal 1, linked in host.
//
{
uty_u->tid_l = 1;
uty_u->nex_u = 0;
u3_Host.uty_u = uty_u;
}
if ( c3n == u3_Host.ops_u.dem ) {
// Start raw input.
//
{
if ( 0 != tcsetattr(uty_u->fid_i, TCSADRAIN, &uty_u->raw_u) ) {
c3_assert(!"init-tcsetattr");
}
if ( -1 == fcntl(uty_u->fid_i, F_SETFL, uty_u->nob_i) ) {
c3_assert(!"init-fcntl");
}
}
// Start spinner thread.
//
{
uty_u->tat_u.sun.sit_u = (uv_thread_t*)malloc(sizeof(uv_thread_t));
if ( uty_u->tat_u.sun.sit_u ) {
uv_mutex_init(&uty_u->tat_u.mex_u);
uv_mutex_lock(&uty_u->tat_u.mex_u);
c3_w ret_w = uv_thread_create(uty_u->tat_u.sun.sit_u,
_term_spinner_cb,
uty_u);
if ( 0 != ret_w ) {
uL(fprintf(uH, "term: spinner start: %s\n", uv_strerror(ret_w)));
free(uty_u->tat_u.sun.sit_u);
uty_u->tat_u.sun.sit_u = NULL;
uv_mutex_unlock(&uty_u->tat_u.mex_u);
uv_mutex_destroy(&uty_u->tat_u.mex_u);
}
}
}
}
}
void
_term_listen_cb(uv_stream_t *wax_u, int sas_i)
{
u3_utel* pty_u = calloc(1, sizeof(*pty_u));
u3_utty* tty_u = &pty_u->uty_t;
uv_tcp_init(u3L, &tty_u->wax_u);
c3_w ret_w;
if ( 0 != (ret_w = uv_accept(wax_u, (uv_stream_t*)&tty_u->wax_u)) ) {
uL(fprintf(uH, "term: accept: %s\n",
uv_strerror(ret_w)));
uv_close((uv_handle_t*)&tty_u->wax_u, NULL);
free(tty_u);
}
else {
uv_read_start((uv_stream_t*)&tty_u->wax_u,
_term_alloc,
_term_read_tn_cb);
tty_u->ufo_u.out.clear_y = (const c3_y*)"\033[H\033[J";
tty_u->ufo_u.out.el_y = (const c3_y*)"\033[K";
tty_u->ufo_u.out.ed_y = (const c3_y*)"\033[J";
tty_u->ufo_u.out.bel_y = (const c3_y*)"\007";
tty_u->ufo_u.out.cub1_y = (const c3_y*)"\010";
tty_u->ufo_u.out.cud1_y = (const c3_y*)"\033[B";
tty_u->ufo_u.out.cuu1_y = (const c3_y*)"\033[A";
tty_u->ufo_u.out.cuf1_y = (const c3_y*)"\033[C";
tty_u->ufo_u.inn.kcuu1_y = (const c3_y*)"\033[A";
tty_u->ufo_u.inn.kcud1_y = (const c3_y*)"\033[B";
tty_u->ufo_u.inn.kcuf1_y = (const c3_y*)"\033[C";
tty_u->ufo_u.inn.kcub1_y = (const c3_y*)"\033[D";
tty_u->ufo_u.inn.max_w = strlen("\033[D");
tty_u->fid_i = -1;
tty_u->tat_u.mir.lin_w = 0;
tty_u->tat_u.mir.len_w = 0;
tty_u->tat_u.mir.cus_w = 0;
tty_u->tat_u.esc.ape = c3n;
tty_u->tat_u.esc.bra = c3n;
tty_u->tat_u.fut.len_w = 0;
tty_u->tat_u.fut.wid_w = 0;
tty_u->tat_u.siz.col_l = 80;
tty_u->tat_u.siz.row_l = 25;
tty_u->tid_l = u3_Host.uty_u->tid_l + 1;
tty_u->nex_u = u3_Host.uty_u;
u3_Host.uty_u = tty_u;
pty_u->tel_u = telnet_nvt_new(tty_u, _tel_event, _tel_opt, NULL);
{
u3_noun tid = u3dc("scot", c3__ud, tty_u->tid_l);
u3_noun pax = u3nq(u3_blip, c3__term, tid, u3_nul);
// u3v_plan(u3k(pax), u3nq(c3__flow, c3__seat, c3__dojo, u3_nul));
u3v_plan(u3k(pax), u3nc(c3__blew, u3nc(80, 25)));
u3v_plan(u3k(pax), u3nc(c3__hail, u3_nul));
u3z(pax);
}
telnet_telopt_enable(pty_u->tel_u, _T_ECHO, TELNET_LOCAL);
telnet_telopt_enable(pty_u->tel_u, _T_CTIM, TELNET_LOCAL);
telnet_telopt_enable(pty_u->tel_u, _T_NAWS, TELNET_REMOTE);
}
}
void
u3_term_io_talk(void)
{
struct sockaddr_in add_u;
u3_utel* tel_u = &u3_Host.tel_u;
uv_tcp_init(u3L, &tel_u->uty_t.wax_u);
tel_u->por_s = 10023;
memset(&add_u, 0, sizeof(add_u));
add_u.sin_family = AF_INET;
add_u.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
/* Try ascending ports.
*/
while ( 1 ) {
add_u.sin_port = htons(tel_u->por_s);
c3_w ret ;
if ( 0 != (ret = uv_tcp_bind(&tel_u->uty_t.wax_u, (const struct sockaddr*) & add_u, 0)) ) {
if ( UV_EADDRINUSE == ret ) {
tel_u->por_s++;
continue;
}
else {
uL(fprintf(uH, "term: bind: %s\n", uv_strerror(ret)));
}
}
c3_w ret_w;
if ( 0 != (ret_w = uv_listen((uv_stream_t*)&tel_u->uty_t.wax_u,
16, _term_listen_cb)) )
{
if ( UV_EADDRINUSE == ret_w ) {
tel_u->por_s++;
continue;
}
else {
uL(fprintf(uH, "term: listen: %s\n", uv_strerror(ret_w)));
}
}
#if 0
uL(fprintf(uH, "term: live (but unsupported) on %d\n", tel_u->por_s));
#endif
break;
}
}
/* u3_term_io_exit(): clean up terminal.
*/
void
u3_term_io_exit(void)
{
if ( c3y == u3_Host.ops_u.dem ) {
uv_close((uv_handle_t*)&u3_Host.uty_u->pop_u, NULL);
}
else {
u3_utty* uty_u;
for ( uty_u = u3_Host.uty_u; uty_u; uty_u = uty_u->nex_u ) {
if ( uty_u->fid_i == -1 ) { continue; }
if ( 0 != tcsetattr(uty_u->fid_i, TCSADRAIN, &uty_u->bak_u) ) {
c3_assert(!"exit-tcsetattr");
}
if ( -1 == fcntl(uty_u->fid_i, F_SETFL, uty_u->cug_i) ) {
c3_assert(!"exit-fcntl");
}
write(uty_u->fid_i, "\r\n", 2);
#if 0
if ( uty_u->tat_u.sun.sit_u ) {
uv_thread_t* sit_u = uty_u->tat_u.sun.sit_u;
uty_u->tat_u.sun.sit_u = NULL;
uv_mutex_unlock(&uty_u->tat_u.mex_u);
// XX can block exit waiting for wakeup (max _SPIN_COOL_US)
c3_w ret_w;
if ( 0 != (ret_w = uv_thread_join(sit_u)) ) {
uL(fprintf(uH, "term: spinner exit: %s\n", uv_strerror(ret_w)));
}
else {
uv_mutex_destroy(&uty_u->tat_u.mex_u);
}
free(sit_u);
}
#endif
}
}
}
void
u3_term_io_poll(void)
{
}
/* _term_it_buf(): create a data buffer.
*/
static u3_ubuf*
_term_it_buf(c3_w len_w, const c3_y* hun_y)
{
u3_ubuf* buf_u = c3_malloc(len_w + sizeof(*buf_u));
buf_u->len_w = len_w;
memcpy(buf_u->hun_y, hun_y, len_w);
buf_u->nex_u = 0;
return buf_u;
}
/* An unusual lameness in libuv.
*/
typedef struct {
uv_write_t wri_u;
c3_y* buf_y;
} _u3_write_t;
/* _term_write_cb(): general write callback.
*/
static void
_term_write_cb(uv_write_t* wri_u, c3_i sas_i)
{
_u3_write_t* ruq_u = (void *)wri_u;
if ( 0 != sas_i ) {
// uL(fprintf(uH, "term: write: ERROR\n"));
}
free(ruq_u->buf_y);
free(ruq_u);
}
/* _term_it_write_buf(): write buffer uv style.
*/
static void
_term_it_write_buf(u3_utty* uty_u, uv_buf_t buf_u)
{
_u3_write_t* ruq_u = (_u3_write_t*) c3_malloc(sizeof(_u3_write_t));
ruq_u->buf_y = (c3_y*)buf_u.base;
c3_w ret_w;
if ( 0 != (ret_w = uv_write(&ruq_u->wri_u,
(uv_stream_t*)&(uty_u->pop_u),
&buf_u, 1,
_term_write_cb)) )
{
uL(fprintf(uH, "terminal: %s\n", uv_strerror(ret_w)));
}
}
/* _term_it_write_old(): write buffer, transferring pointer.
*/
static void
_term_it_write_old(u3_utty* uty_u,
u3_ubuf* old_u)
{
uv_buf_t buf_u;
// XX extra copy here due to old code. Use hbod as base directly.
//
{
c3_y* buf_y = c3_malloc(old_u->len_w);
memcpy(buf_y, old_u->hun_y, old_u->len_w);
buf_u = uv_buf_init((c3_c*)buf_y, old_u->len_w);
free(old_u);
}
_term_it_write_buf(uty_u, buf_u);
}
/* _term_it_write_bytes(): write bytes, retaining pointer.
*/
static void
_term_it_write_bytes(u3_utty* uty_u,
c3_w len_w,
const c3_y* hun_y)
{
_term_it_write_old(uty_u, _term_it_buf(len_w, hun_y));
}
/* _term_it_write_txt(): write null-terminated string, retaining pointer.
*/
static void
_term_it_write_txt(u3_utty* uty_u,
const c3_y* hun_y)
{
_term_it_write_bytes(uty_u, strlen((const c3_c*)hun_y), hun_y);
}
/* _term_it_write_str(): write null-terminated string, retaining pointer.
*/
static void
_term_it_write_str(u3_utty* uty_u,
const c3_c* str_c)
{
_term_it_write_txt(uty_u, (const c3_y*) str_c);
}
/* _term_it_show_wide(): show wide text, retaining.
*/
static void
_term_it_show_wide(u3_utty* uty_u, c3_w len_w, c3_w* txt_w)
{
u3_noun wad = u3i_words(len_w, txt_w);
u3_noun txt = u3do("tuft", wad);
c3_c* txt_c = u3r_string(txt);
_term_it_write_str(uty_u, txt_c);
free(txt_c);
u3z(txt);
uty_u->tat_u.mir.cus_w += len_w;
}
/* _term_it_show_clear(): clear to the beginning of the current line.
*/
static void
_term_it_show_clear(u3_utty* uty_u)
{
u3_utat* tat_u = &uty_u->tat_u;
if ( tat_u->siz.col_l ) {
c3_w ful_w = tat_u->mir.cus_w / tat_u->siz.col_l;
if ( 0 != tat_u->mir.cus_w &&
ful_w * tat_u->siz.col_l == tat_u->mir.cus_w )
{
ful_w--;
}
while ( ful_w-- ) {
_term_it_write_txt(uty_u, uty_u->ufo_u.out.cuu1_y);
}
_term_it_write_str(uty_u, "\r");
_term_it_write_txt(uty_u, uty_u->ufo_u.out.ed_y);
tat_u->mir.len_w = 0;
tat_u->mir.cus_w = 0;
}
}
/* _term_it_show_blank(): blank the screen.
*/
static void
_term_it_show_blank(u3_utty* uty_u)
{
_term_it_write_txt(uty_u, uty_u->ufo_u.out.clear_y);
uty_u->tat_u.mir.cus_w = 0;
}
/* _term_it_show_cursor(): set current line, transferring pointer.
*/
static void
_term_it_show_cursor(u3_utty* uty_u, c3_w cur_w)
{
if ( cur_w < uty_u->tat_u.mir.cus_w ) {
c3_w dif_w = (uty_u->tat_u.mir.cus_w - cur_w);
while ( dif_w-- ) {
_term_it_write_txt(uty_u, uty_u->ufo_u.out.cub1_y);
}
}
else if ( cur_w > uty_u->tat_u.mir.cus_w ) {
c3_w dif_w = (cur_w - uty_u->tat_u.mir.cus_w);
while ( dif_w-- ) {
_term_it_write_txt(uty_u, uty_u->ufo_u.out.cuf1_y);
}
}
uty_u->tat_u.mir.cus_w = cur_w;
}
/* _term_it_show_line(): set current line
*/
static void
_term_it_show_line(u3_utty* uty_u, c3_w* lin_w, c3_w len_w)
{
_term_it_show_wide(uty_u, len_w, lin_w);
if ( lin_w != uty_u->tat_u.mir.lin_w ) {
if ( uty_u->tat_u.mir.lin_w ) {
free(uty_u->tat_u.mir.lin_w);
}
uty_u->tat_u.mir.lin_w = lin_w;
}
uty_u->tat_u.mir.len_w = len_w;
}
/* _term_it_refresh_line(): refresh current line.
*/
static void
_term_it_refresh_line(u3_utty* uty_u)
{
c3_w len_w = uty_u->tat_u.mir.len_w;
c3_w cus_w = uty_u->tat_u.mir.cus_w;
_term_it_show_clear(uty_u);
_term_it_show_line(uty_u, uty_u->tat_u.mir.lin_w, len_w);
_term_it_show_cursor(uty_u, cus_w);
}
/* _term_it_show_more(): new current line.
*/
static void
_term_it_show_more(u3_utty* uty_u)
{
if ( c3y == u3_Host.ops_u.dem ) {
_term_it_write_str(uty_u, "\n");
} else {
_term_it_write_str(uty_u, "\r\n");
}
uty_u->tat_u.mir.cus_w = 0;
}
/* _term_it_path(): path for console file.
*/
static c3_c*
_term_it_path(u3_noun fyl, u3_noun pax)
{
c3_w len_w;
c3_c *pas_c;
// measure
//
len_w = strlen(u3_Host.dir_c);
{
u3_noun wiz = pax;
while ( u3_nul != wiz ) {
len_w += (1 + u3r_met(3, u3h(wiz)));
wiz = u3t(wiz);
}
}
// cut
//
pas_c = c3_malloc(len_w + 1);
strncpy(pas_c, u3_Host.dir_c, len_w);
pas_c[len_w] = '\0';
{
u3_noun wiz = pax;
c3_c* waq_c = (pas_c + strlen(pas_c));
while ( u3_nul != wiz ) {
c3_w tis_w = u3r_met(3, u3h(wiz));
if ( (c3y == fyl) && (u3_nul == u3t(wiz)) ) {
*waq_c++ = '.';
} else *waq_c++ = '/';
u3r_bytes(0, tis_w, (c3_y*)waq_c, u3h(wiz));
waq_c += tis_w;
wiz = u3t(wiz);
}
*waq_c = 0;
}
u3z(pax);
return pas_c;
}
/* _term_it_save(): save file by path.
*/
static void
_term_it_save(u3_noun pax, u3_noun pad)
{
c3_c* pax_c;
pax = u3nt(c3_s4('.','u','r','b'), c3_s3('p','u','t'), pax);
pax_c = _term_it_path(c3y, pax);
u3_walk_save(pax_c, 0, pad);
free(pax_c);
}
/* _term_io_belt(): send belt.
*/
static void
_term_io_belt(u3_utty* uty_u, u3_noun blb)
{
u3_noun tid = u3dc("scot", c3__ud, uty_u->tid_l);
u3_noun pax = u3nq(u3_blip, c3__term, tid, u3_nul);
u3v_plan(pax, u3nc(c3__belt, blb));
}
/* _tel_event(): telnet sucker
*/
#define _te_nvt telnet_nvt
#define _te_evt telnet_event
#define _te_dvt telnet_data_event
#define _te_svt telnet_send_event
static void
_tel_event(_te_nvt* nvt, _te_evt* evt)
{
u3_utel* tel_u;
c3_assert(0 < telnet_get_userdata(nvt, (void**)&tel_u));
switch (evt->type)
{
case TELNET_EV_DATA:
{
_te_dvt* dv = (_te_dvt*)evt;
_term_suck((u3_utty*)tel_u, dv->data, dv->length);
break;
}
case TELNET_EV_SEND:
{
_te_svt* sv = (_te_svt*)evt;
_term_it_write_bytes((u3_utty*)tel_u, sv->length, sv->data);
break;
}
default:
{
break;
}
}
}
#define _to_evt telnet_telopt_event
#define _to_dvt telnet_telopt_data_event
#define _to_tvt telnet_telopt_toggle_event
/* _tel_opt(): telnet event sucker
*/
static void
_tel_opt(_te_nvt* nvt, telnet_byte opt, _to_evt* evt)
{
switch (evt->type)
{
default: break;
case TELNET_EV_TELOPT_DATA:
{
_to_dvt* dv = (_to_dvt*)evt;
u3_utel* tel_u;
u3_noun pax;
u3_noun blu;
u3_noun tid;
c3_s col_s;
c3_s row_s;
if ( opt != _T_NAWS ) {
return;
}
c3_assert(0 < telnet_get_userdata(nvt, (void**)&tel_u));
col_s = dv->data[1] | (dv->data[0] << 8);
row_s = dv->data[3] | (dv->data[2] << 8);
tel_u->uty_t.tat_u.siz.col_l = col_s;
tel_u->uty_t.tat_u.siz.row_l = row_s;
tid = u3dc("scot", c3__ud, tel_u->uty_t.tid_l);
pax = u3nq(u3_blip, c3__term, tid, u3_nul);
blu = u3nc(col_s, row_s);
u3v_plan(pax, u3nc(c3__blew, blu));
break;
}
}
}
/* _term_io_suck_char(): process a single character.
*/
static void
_term_io_suck_char(u3_utty* uty_u, c3_y cay_y)
{
u3_utat* tat_u = &uty_u->tat_u;
if ( c3y == tat_u->esc.ape ) {
if ( c3y == tat_u->esc.bra ) {
switch ( cay_y ) {
default: {
_term_it_write_txt(uty_u, uty_u->ufo_u.out.bel_y);
break;
}
case 'A': _term_io_belt(uty_u, u3nc(c3__aro, 'u')); break;
case 'B': _term_io_belt(uty_u, u3nc(c3__aro, 'd')); break;
case 'C': _term_io_belt(uty_u, u3nc(c3__aro, 'r')); break;
case 'D': _term_io_belt(uty_u, u3nc(c3__aro, 'l')); break;
}
tat_u->esc.ape = tat_u->esc.bra = c3n;
}
else {
if ( (cay_y >= 'a') && (cay_y <= 'z') ) {
tat_u->esc.ape = c3n;
_term_io_belt(uty_u, u3nc(c3__met, cay_y));
}
else if ( ('[' == cay_y) || ('O' == cay_y) ) {
tat_u->esc.bra = c3y;
}
else {
tat_u->esc.ape = c3n;
_term_it_write_txt(uty_u, uty_u->ufo_u.out.bel_y);
}
}
}
else if ( 0 != tat_u->fut.wid_w ) {
tat_u->fut.syb_y[tat_u->fut.len_w++] = cay_y;
if ( tat_u->fut.len_w == tat_u->fut.wid_w ) {
u3_noun huv = u3i_bytes(tat_u->fut.wid_w, tat_u->fut.syb_y);
u3_noun wug;
// uL(fprintf(uH, "muck-utf8 len %d\n", tat_u->fut.len_w));
// uL(fprintf(uH, "muck-utf8 %x\n", huv));
wug = u3do("turf", huv);
// uL(fprintf(uH, "muck-utf32 %x\n", tat_u->fut.len_w));
tat_u->fut.len_w = tat_u->fut.wid_w = 0;
_term_io_belt(uty_u, u3nt(c3__txt, wug, u3_nul));
}
}
else {
if ( (cay_y >= 32) && (cay_y < 127) ) {
_term_io_belt(uty_u, u3nt(c3__txt, cay_y, u3_nul));
}
else if ( 0 == cay_y ) {
_term_it_write_txt(uty_u, uty_u->ufo_u.out.bel_y);
}
else if ( 8 == cay_y || 127 == cay_y ) {
_term_io_belt(uty_u, u3nc(c3__bac, u3_nul));
}
else if ( 13 == cay_y ) {
_term_io_belt(uty_u, u3nc(c3__ret, u3_nul));
}
#if 0
else if ( 6 == cay_y ) {
_term_io_flow(uty_u); // XX hack
}
#endif
else if ( cay_y <= 26 ) {
_term_io_belt(uty_u, u3nc(c3__ctl, ('a' + (cay_y - 1))));
}
else if ( 27 == cay_y ) {
tat_u->esc.ape = c3y;
}
else if ( cay_y >= 128 ) {
tat_u->fut.len_w = 1;
tat_u->fut.syb_y[0] = cay_y;
if ( cay_y < 224 ) {
tat_u->fut.wid_w = 2;
} else if ( cay_y < 240 ) {
tat_u->fut.wid_w = 3;
} else tat_u->fut.wid_w = 4;
}
}
}
/* _term_read_tn_cb(): telnet read callback.
*/
static void
_term_read_tn_cb(uv_stream_t* tcp_u,
ssize_t siz_i,
const uv_buf_t * buf_u)
{
u3_utel* pty_u = (u3_utel*)(void*) tcp_u;
u3_lo_open();
{
if ( siz_i == UV_EOF ) {
// nothing
} else if ( siz_i < 0 ) {
uL(fprintf(uH, "term teln: read: %s\n", uv_strerror(siz_i)));
uv_close((uv_handle_t*) tcp_u, _tel_close_cb);
goto err;
}
else {
telnet_receive(pty_u->tel_u, (const telnet_byte*) buf_u->base, siz_i, 0);
}
err:
free(buf_u->base);
}
u3_lo_shut(c3y);
}
/* _term_suck(): process a chunk of input
*/
/*
* `nread` (siz_w) is > 0 if there is data available, 0 if libuv is done reading for
* now, or < 0 on error.
*
* The callee is responsible for closing the stream when an error happens
* by calling uv_close(). Trying to read from the stream again is undefined.
*
* The callee is responsible for freeing the buffer, libuv does not reuse it.
* The buffer may be a null buffer (where buf->base=NULL and buf->len=0) on
* error.
*/
static inline void
_term_suck(u3_utty* uty_u, const c3_y* buf, ssize_t siz_i)
{
u3_lo_open();
{
if ( siz_i == UV_EOF ) {
// nothing
} else if ( siz_i < 0 ) {
uL(fprintf(uH, "term %d: read: %s\n", uty_u->tid_l, uv_strerror(siz_i)));
}
else {
c3_i i;
for ( i=0; i < siz_i; i++ ) {
_term_io_suck_char(uty_u, buf[i]);
}
}
}
u3_lo_shut(c3y);
}
/* _term_read_cb(): server read callback.
*/
static void
_term_read_cb(uv_stream_t* tcp_u,
ssize_t siz_i,
const uv_buf_t * buf_u)
{
u3_utty* uty_u = (u3_utty*)(void*)tcp_u;
_term_suck(uty_u, (const c3_y*)buf_u->base, siz_i);
free(buf_u->base);
}
/* _term_try_write_str(): write null-terminated string (off-thread, retain).
*/
static void
_term_try_write_str(u3_utty* uty_u,
const c3_c* hun_y)
{
// c3_i fid_i = uv_fileno(&uty_u->pop_u);
c3_i fid_i = uty_u->pop_u.io_watcher.fd; // XX old libuv
write(fid_i, hun_y, strlen(hun_y));
}
/* _term_try_move_left(): move the cursor left (off-thread).
*/
static void
_term_try_move_left(u3_utty* uty_u)
{
_term_try_write_str(uty_u, (const c3_c*)uty_u->ufo_u.out.cub1_y);
}
/* _term_show_spinner(): render spinner (off-thread).
*/
static void
_term_show_spinner(u3_utty* uty_u, c3_d lag_d)
{
if ( 0 == uty_u->tat_u.sun.eve_d ) {
return;
}
c3_w cus_w = uty_u->tat_u.mir.cus_w;
if ( cus_w >= uty_u->tat_u.siz.col_l ) { // shenanigans!
return;
}
c3_w bac_w = uty_u->tat_u.siz.col_l - 1 - cus_w; // backoff from end of line
const c3_c daz_c[] = "|/-\\";
const c3_c dal_c[] = "\xc2\xab";
const c3_c dar_c[] = "\xc2\xbb";
c3_c buf_c[1 + 2 + 4 + 2 + 1];
// | + « + why + » + \0
c3_c* cur_c = buf_c;
*cur_c++ = daz_c[(lag_d / _SPIN_RATE_US) % strlen(daz_c)];
c3_w sol_w = 1; // spinner length (utf-32)
c3_c* why_c = uty_u->tat_u.sun.why_c;
if ( why_c && strlen(why_c) <= 4 ) {
strcpy(cur_c, dal_c);
cur_c += strlen(dal_c);
sol_w += 1; // length of dal_c (utf-32)
c3_w wel_w = strlen(why_c);
strcpy(cur_c, why_c);
cur_c += wel_w;
sol_w += wel_w;
strcpy(cur_c, dar_c);
cur_c += strlen(dar_c);
sol_w += 1; // length of dar_c (utf-32)
}
*cur_c = '\0';
// One-time cursor backoff.
if ( c3n == uty_u->tat_u.sun.diz_o ) {
c3_w i_w;
for ( i_w = bac_w; i_w < sol_w; i_w++ ) {
_term_try_move_left(uty_u);
}
}
_term_try_write_str(uty_u, buf_c);
uty_u->tat_u.sun.diz_o = c3y;
// Cursor stays on spinner.
while ( sol_w-- ) {
_term_try_move_left(uty_u);
}
}
/* _term_start_spinner(): prepare spinner state. RETAIN.
*/
static void
_term_start_spinner(u3_utty* uty_u, u3_noun ovo)
{
uty_u->tat_u.sun.diz_o = c3n;
c3_d now_d = _term_msc_out_host();
// If we receive an event shortly after a previous spin, use a shorter delay
// to avoid giving the impression of a half-idle system.
//
c3_d lag_d;
if ( now_d - uty_u->tat_u.sun.end_d < _SPIN_IDLE_US ) {
lag_d = _SPIN_WARM_US;
}
else {
lag_d = _SPIN_COOL_US;
}
u3_noun why = u3h(u3t(u3h(u3t(ovo))));
if ( c3__term == why ) {
u3_noun eve = u3t(u3t(ovo));
if ( c3__belt == u3h(eve) && c3__ret == u3h(u3t(eve)) ) {
lag_d = 0; // No delay for %ret.
}
}
else {
uty_u->tat_u.sun.why_c = (c3_c*)u3r_string(why);
}
uty_u->tat_u.sun.eve_d = now_d + lag_d;
uv_mutex_unlock(&uty_u->tat_u.mex_u);
}
/* _term_stop_spinner(): reset spinner state and restore input line.
*/
static void
_term_stop_spinner(u3_utty* uty_u)
{
uv_mutex_lock(&uty_u->tat_u.mex_u);
if ( c3y == uty_u->tat_u.sun.diz_o ) {
_term_it_refresh_line(uty_u);
uty_u->tat_u.sun.end_d = _term_msc_out_host();
}
else {
uty_u->tat_u.sun.end_d = 0;
}
uty_u->tat_u.sun.diz_o = c3n;
uty_u->tat_u.sun.eve_d = 0;
free(uty_u->tat_u.sun.why_c);
uty_u->tat_u.sun.why_c = NULL;
}
/* _term_spinner_cb(): manage spinner (off-thread).
*/
static void
_term_spinner_cb(void* ptr_v)
{
// This thread shouldn't receive signals.
//
{
sigset_t set;
sigfillset(&set);
pthread_sigmask(SIG_BLOCK, &set, NULL);
}
u3_utty* uty_u = (u3_utty*)ptr_v;
for ( uv_mutex_lock(&uty_u->tat_u.mex_u);
uty_u->tat_u.sun.sit_u;
uv_mutex_lock(&uty_u->tat_u.mex_u) )
{
c3_d eve_d = uty_u->tat_u.sun.eve_d;
if ( 0 == eve_d ) {
c3_o diz_o = uty_u->tat_u.sun.diz_o;
uv_mutex_unlock(&uty_u->tat_u.mex_u);
usleep(c3y == diz_o ? _SPIN_WARM_US : _SPIN_COOL_US);
}
else {
c3_d now_d = _term_msc_out_host();
if (now_d < eve_d) {
uv_mutex_unlock(&uty_u->tat_u.mex_u);
usleep(eve_d - now_d);
}
else {
_term_show_spinner(uty_u, now_d - eve_d);
uv_mutex_unlock(&uty_u->tat_u.mex_u);
usleep(_SPIN_RATE_US);
}
}
}
uv_mutex_unlock(&uty_u->tat_u.mex_u);
}
/* _term_main(): return main or console terminal.
*/
static u3_utty*
_term_main()
{
u3_utty* uty_u;
for ( uty_u = u3_Host.uty_u; uty_u; uty_u = uty_u->nex_u ) {
if ( (uty_u->fid_i != -1) && (uty_u->fid_i <= 2) ) {
return uty_u;
}
}
return u3_Host.uty_u;
}
/* _term_ef_get(): terminal by id.
*/
static u3_utty*
_term_ef_get(c3_l tid_l)
{
if ( 0 != tid_l ) {
u3_utty* uty_u;
for ( uty_u = u3_Host.uty_u; uty_u; uty_u = uty_u->nex_u ) {
if ( tid_l == uty_u->tid_l ) {
return uty_u;
}
}
}
return _term_main();
}
/* u3_term_get_blew(): return window size [columns rows].
*/
u3_noun
u3_term_get_blew(c3_l tid_l)
{
u3_utty* uty_u = _term_ef_get(tid_l);
c3_l col_l, row_l;
struct winsize siz_u;
if ( uty_u && (0 == ioctl(uty_u->fid_i, TIOCGWINSZ, &siz_u)) ) {
col_l = siz_u.ws_col;
row_l = siz_u.ws_row;
} else {
col_l = 80;
row_l = 24;
}
if ( uty_u ) {
uty_u->tat_u.siz.col_l = col_l;
uty_u->tat_u.siz.row_l = row_l;
}
return u3nc(col_l, row_l);
}
/* u3_term_ef_winc(): window change. Just console right now.
*/
void
u3_term_ef_winc(void)
{
u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
u3v_plan(pax, u3nc(c3__blew, u3_term_get_blew(1)));
}
/* u3_term_ef_ctlc(): send ^C on console.
*/
void
u3_term_ef_ctlc(void)
{
u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
u3v_plan(pax, u3nt(c3__belt, c3__ctl, 'c'));
}
/* u3_term_ef_boil(): initial effects for loaded servers.
*/
void
u3_term_ef_boil(void)
{
{
u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
// u3v_plan(u3k(pax), u3nc(c3__init, u3k(u3h(u3A->own))));
u3v_plan(u3k(pax), u3nc(c3__harm, u3_nul));
u3v_plan(u3k(pax), u3nc(c3__blew, u3_term_get_blew(1)));
u3v_plan(u3k(pax), u3nc(c3__hail, u3_nul));
u3z(pax);
}
}
/* u3_term_ef_verb(): initial effects for verbose events
*/
void
u3_term_ef_verb(void)
{
u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
u3v_plan(pax, u3nc(c3__verb, u3_nul));
}
/* u3_term_ef_ticket(): initial effects for new ticket.
*/
void
u3_term_ef_ticket(c3_c* who_c, c3_c* tic_c)
{
u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
u3_noun who, tic;
u3_noun whu, tuc;
whu = u3dc("slaw", 'p', u3i_string(who_c));
if ( u3_nul == whu ) {
fprintf(stderr, "ticket: invalid planet '%s'\r\n", who_c);
exit(1);
}
else { who = u3k(u3t(whu)); u3z(whu); }
tuc = u3dc("slaw", 'p', u3i_string(tic_c));
if ( u3_nul == tuc ) {
fprintf(stderr, "ticket: invalid secret '%s'\r\n", tic_c);
exit(1);
}
else { tic = u3k(u3t(tuc)); u3z(tuc); }
u3v_plan(pax, u3nt(c3__tick, who, tic));
}
/* u3_term_ef_bake(): initial effects for new terminal.
*/
void
u3_term_ef_bake(u3_noun fav)
{
u3_noun pax = u3nq(u3_blip, c3__term, '1', u3_nul);
u3v_plan(u3k(pax), u3nc(c3__boot, fav));
// u3v_plan(u3k(pax), u3nq(c3__flow, c3__seat, c3__dojo, u3_nul));
u3v_plan(u3k(pax), u3nc(c3__blew, u3_term_get_blew(1)));
u3v_plan(u3k(pax), u3nc(c3__hail, u3_nul));
u3z(pax);
}
/* _term_ef_blit(): send blit to terminal.
*/
static void
_term_ef_blit(u3_utty* uty_u,
u3_noun blt)
{
switch ( u3h(blt) ) {
default: break;
case c3__bee: {
if ( c3n == u3_Host.ops_u.dem ) {
if ( u3_nul == u3t(blt) ) {
_term_stop_spinner(uty_u);
}
else {
_term_start_spinner(uty_u, u3t(blt));
}
}
} break;
case c3__bel: {
if ( c3n == u3_Host.ops_u.dem ) {
_term_it_write_txt(uty_u, uty_u->ufo_u.out.bel_y);
}
} break;
case c3__clr: {
if ( c3n == u3_Host.ops_u.dem ) {
_term_it_show_blank(uty_u);
_term_it_refresh_line(uty_u);
}
} break;
case c3__hop: {
if ( c3n == u3_Host.ops_u.dem ) {
_term_it_show_cursor(uty_u, u3t(blt));
}
} break;
case c3__lin: {
u3_noun lin = u3t(blt);
c3_w len_w = u3kb_lent(u3k(lin));
c3_w* lin_w = c3_malloc(4 * len_w);
{
c3_w i_w;
for ( i_w = 0; u3_nul != lin; i_w++, lin = u3t(lin) ) {
lin_w[i_w] = u3r_word(0, u3h(lin));
}
}
if ( c3n == u3_Host.ops_u.dem ) {
_term_it_show_clear(uty_u);
_term_it_show_line(uty_u, lin_w, len_w);
} else {
_term_it_show_line(uty_u, lin_w, len_w);
}
} break;
case c3__mor: {
_term_it_show_more(uty_u);
} break;
case c3__sav: {
_term_it_save(u3k(u3h(u3t(blt))), u3k(u3t(u3t(blt))));
} break;
case c3__sag: {
u3_noun pib = u3k(u3t(u3t(blt)));
u3_noun jam;
jam = u3ke_jam(pib);
_term_it_save(u3k(u3h(u3t(blt))), jam);
} break;
case c3__url: {
if ( c3n == u3ud(u3t(blt)) ) {
break;
} else {
c3_c* txt_c = u3r_string(u3t(blt));
_term_it_show_clear(uty_u);
_term_it_write_str(uty_u, txt_c);
free(txt_c);
_term_it_show_more(uty_u);
_term_it_refresh_line(uty_u);
}
}
}
u3z(blt);
return;
}
/* u3_term_ef_blit(): send %blit list to specific terminal.
*/
void
u3_term_ef_blit(c3_l tid_l,
u3_noun bls)
{
u3_utty* uty_u = _term_ef_get(tid_l);
if ( 0 == uty_u ) {
// uL(fprintf(uH, "no terminal %d\n", tid_l));
// uL(fprintf(uH, "uty_u %p\n", u3_Host.uty_u));
u3z(bls); return;
}
{
u3_noun bis = bls;
while ( c3y == u3du(bis) ) {
_term_ef_blit(uty_u, u3k(u3h(bis)));
bis = u3t(bis);
}
u3z(bls);
}
}
/* u3_term_io_hija(): hijack console for fprintf, returning FILE*.
*/
FILE*
u3_term_io_hija(void)
{
u3_utty* uty_u = _term_main();
if ( uty_u ) {
if ( uty_u->fid_i > 2 ) {
// We *should* in fact, produce some kind of fake FILE* for
// non-console terminals. If we use this interface enough...
//
c3_assert(0);
}
else {
if ( c3n == u3_Host.ops_u.dem ) {
if ( 0 != tcsetattr(1, TCSADRAIN, &uty_u->bak_u) ) {
c3_assert(!"hija-tcsetattr");
}
if ( -1 == fcntl(1, F_SETFL, uty_u->cug_i) ) {
c3_assert(!"hija-fcntl");
}
if ( 0 != tcsetattr(0, TCSADRAIN, &uty_u->bak_u) ) {
c3_assert(!"hija-tcsetattr");
}
if ( -1 == fcntl(0, F_SETFL, uty_u->cug_i) ) {
c3_assert(!"hija-fcntl");
}
write(uty_u->fid_i, "\r", 1);
write(uty_u->fid_i, uty_u->ufo_u.out.el_y,
strlen((c3_c*) uty_u->ufo_u.out.el_y));
}
return stdout;
}
}
else return stdout;
}
/* u3_term_io_loja(): release console from fprintf.
*/
void
u3_term_io_loja(int x)
{
u3_utty* uty_u = _term_main();
if ( uty_u ) {
if ( uty_u->fid_i > 2 ) {
// We *should* in fact, produce some kind of fake FILE* for
// non-console terminals. If we use this interface enough...
//
c3_assert(0);
}
else {
if ( c3y == u3_Host.ops_u.dem ) {
fflush(stdout);
}
else {
if ( 0 != tcsetattr(1, TCSADRAIN, &uty_u->raw_u) ) {
c3_assert(!"loja-tcsetattr");
}
if ( -1 == fcntl(1, F_SETFL, uty_u->nob_i) ) {
c3_assert(!"loja-fcntl");
}
if ( 0 != tcsetattr(0, TCSADRAIN, &uty_u->raw_u) ) {
c3_assert(!"loja-tcsetattr");
}
if ( -1 == fcntl(0, F_SETFL, uty_u->nob_i) ) {
c3_assert(!"loja-fcntl");
}
_term_it_refresh_line(uty_u);
}
}
}
}