aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/drivers/unix/ttsl_drv.c
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/drivers/unix/ttsl_drv.c')
-rw-r--r--erts/emulator/drivers/unix/ttsl_drv.c1299
1 files changed, 1299 insertions, 0 deletions
diff --git a/erts/emulator/drivers/unix/ttsl_drv.c b/erts/emulator/drivers/unix/ttsl_drv.c
new file mode 100644
index 0000000000..4c2514669b
--- /dev/null
+++ b/erts/emulator/drivers/unix/ttsl_drv.c
@@ -0,0 +1,1299 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1996-2009. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Erlang Public License,
+ * Version 1.1, (the "License"); you may not use this file except in
+ * compliance with the License. You should have received a copy of the
+ * Erlang Public License along with this software. If not, it can be
+ * retrieved online at http://www.erlang.org/.
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * %CopyrightEnd%
+ */
+/*
+ * Tty driver that reads one character at the time and provides a
+ * smart line for output.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "erl_driver.h"
+
+static int ttysl_init(void);
+static ErlDrvData ttysl_start(ErlDrvPort, char*);
+
+#ifdef HAVE_TERMCAP /* else make an empty driver that can not be opened */
+
+#include "sys.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <unistd.h>
+#include <termios.h>
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#if !defined(HAVE_SETLOCALE) || !defined(HAVE_NL_LANGINFO) || !defined(HAVE_LANGINFO_H)
+#define PRIMITIVE_UTF8_CHECK 1
+#else
+#include <langinfo.h>
+#endif
+
+#define TRUE 1
+#define FALSE 0
+
+/* Termcap functions. */
+int tgetent(char* bp, char *name);
+int tgetnum(char* cap);
+int tgetflag(char* cap);
+char *tgetstr(char* cap, char** buf);
+char *tgoto(char* cm, int col, int line);
+int tputs(char* cp, int affcnt, int (*outc)(int c));
+
+/* Terminal capabilites in which we are interested. */
+static char *capbuf;
+static char *up, *down, *left, *right;
+static int cols, xn;
+static volatile int cols_needs_update = FALSE;
+
+/* The various opcodes. */
+#define OP_PUTC 0
+#define OP_MOVE 1
+#define OP_INSC 2
+#define OP_DELC 3
+#define OP_BEEP 4
+/* Control op */
+#define CTRL_OP_GET_WINSIZE 100
+#define CTRL_OP_GET_UNICODE_STATE 101
+#define CTRL_OP_SET_UNICODE_STATE 102
+
+
+
+static int lbuf_size = BUFSIZ;
+static Uint32 *lbuf; /* The current line buffer */
+static int llen; /* The current line length */
+static int lpos; /* The current "cursor position" in the line buffer */
+
+/*
+ * Tags used in line buffer to show that these bytes represent special characters,
+ * Max unicode is 0x0010ffff, so we have lots of place for meta tags...
+ */
+#define CONTROL_TAG 0x10000000U /* Control character, value in first position */
+#define ESCAPED_TAG 0x01000000U /* Escaped character, value in first position */
+#define TAG_MASK 0xFF000000U
+
+#define MAXSIZE (1 << 16)
+
+#define COL(_l) ((_l) % cols)
+#define LINE(_l) ((_l) / cols)
+
+#define NL '\n'
+
+/* Main interface functions. */
+static void ttysl_stop(ErlDrvData);
+static void ttysl_from_erlang(ErlDrvData, char*, int);
+static void ttysl_from_tty(ErlDrvData, ErlDrvEvent);
+static void ttysl_stop_select(ErlDrvEvent, void*);
+static Sint16 get_sint16(char*);
+
+static ErlDrvPort ttysl_port;
+static int ttysl_fd;
+static FILE *ttysl_out;
+
+/* Functions that work on the line buffer. */
+static int start_lbuf(void);
+static int stop_lbuf(void);
+static int put_chars(byte*,int);
+static int move_rel(int);
+static int ins_chars(byte *,int);
+static int del_chars(int);
+static int step_over_chars(int);
+static int insert_buf(byte*,int);
+static int write_buf(Uint32 *,int);
+static int outc(int c);
+static int move_cursor(int,int);
+
+/* Termcap functions. */
+static int start_termcap(void);
+static int stop_termcap(void);
+static int move_left(int);
+static int move_right(int);
+static int move_up(int);
+static int move_down(int);
+static void update_cols(void);
+
+/* Terminal setting functions. */
+static int tty_init(int,int,int,int);
+static int tty_set(int);
+static int tty_reset(int);
+static int ttysl_control(ErlDrvData, unsigned int, char *, int, char **, int);
+static RETSIGTYPE suspend(int);
+static RETSIGTYPE cont(int);
+static RETSIGTYPE winch(int);
+
+/*#define LOG_DEBUG*/
+
+#ifdef LOG_DEBUG
+FILE *debuglog = NULL;
+
+#define DEBUGLOG(X) \
+do { \
+ if (debuglog != NULL) { \
+ my_debug_printf X; \
+ } \
+} while (0)
+
+static void my_debug_printf(char *fmt, ...)
+{
+ char buffer[1024];
+ va_list args;
+
+ va_start(args, fmt);
+ erts_vsnprintf(buffer,1024,fmt,args);
+ va_end(args);
+ erts_fprintf(debuglog,"%s\n",buffer);
+ //erts_printf("Debuglog = %s\n",buffer);
+}
+
+#else
+
+#define DEBUGLOG(X)
+
+#endif
+
+static int utf8_mode = 0;
+static byte utf8buf[4]; /* for incomplete input */
+static int utf8buf_size; /* size of incomplete input */
+
+# define IF_IMPL(x) x
+#else
+# define IF_IMPL(x) NULL
+#endif /* HAVE_TERMCAP */
+
+/* Define the driver table entry. */
+struct erl_drv_entry ttsl_driver_entry = {
+ ttysl_init,
+ ttysl_start,
+ IF_IMPL(ttysl_stop),
+ IF_IMPL(ttysl_from_erlang),
+ IF_IMPL(ttysl_from_tty),
+ NULL,
+ "tty_sl",
+ NULL,
+ NULL,
+ IF_IMPL(ttysl_control),
+ NULL, /* timeout */
+ NULL, /* outputv */
+ NULL, /* ready_async */
+ NULL, /* flush */
+ NULL, /* call */
+ NULL, /* event */
+ ERL_DRV_EXTENDED_MARKER,
+ ERL_DRV_EXTENDED_MAJOR_VERSION,
+ ERL_DRV_EXTENDED_MINOR_VERSION,
+ 0, /* ERL_DRV_FLAGs */
+ NULL,
+ NULL, /* process_exit */
+ IF_IMPL(ttysl_stop_select)
+};
+
+
+static int ttysl_init(void)
+{
+#ifdef HAVE_TERMCAP
+ ttysl_port = (ErlDrvPort)-1;
+ ttysl_fd = -1;
+ lbuf = NULL; /* For line buffer handling */
+ capbuf = NULL; /* For termcap handling */
+#endif
+#ifdef LOG_DEBUG
+ {
+ char *dl;
+ if ((dl = getenv("TTYSL_DEBUG_LOG")) != NULL && *dl) {
+ debuglog = fopen(dl,"w+");
+ if (debuglog != NULL)
+ setbuf(debuglog,NULL);
+ }
+ DEBUGLOG(("Debuglog = %s(0x%ld)\n",dl,(long) debuglog));
+ }
+#endif
+ return 0;
+}
+
+static ErlDrvData ttysl_start(ErlDrvPort port, char* buf)
+{
+#ifndef HAVE_TERMCAP
+ return ERL_DRV_ERROR_GENERAL;
+#else
+ char *s, *t, c, *l;
+ int canon, echo, sig; /* Terminal characteristics */
+ int flag;
+ extern int using_oldshell; /* set this to let the rest of erts know */
+
+ utf8buf_size = 0;
+ if (ttysl_port != (ErlDrvPort)-1)
+ return ERL_DRV_ERROR_GENERAL;
+
+ if (!isatty(0) || !isatty(1))
+ return ERL_DRV_ERROR_GENERAL;
+
+ /* Set the terminal modes to default leave as is. */
+ canon = echo = sig = 0;
+
+ /* Parse the input parameters. */
+ for (s = strchr(buf, ' '); s; s = t) {
+ s++;
+ /* Find end of this argument (start of next) and insert NUL. */
+ if ((t = strchr(s, ' '))) {
+ c = *t;
+ *t = '\0';
+ }
+ if ((flag = ((*s == '+') ? 1 : ((*s == '-') ? -1 : 0)))) {
+ if (s[1] == 'c') canon = flag;
+ if (s[1] == 'e') echo = flag;
+ if (s[1] == 's') sig = flag;
+ }
+ else if ((ttysl_fd = open(s, O_RDWR, 0)) < 0)
+ return ERL_DRV_ERROR_GENERAL;
+ }
+ if (ttysl_fd < 0)
+ ttysl_fd = 0;
+
+ if (tty_init(ttysl_fd, canon, echo, sig) < 0 ||
+ tty_set(ttysl_fd) < 0) {
+ ttysl_port = (ErlDrvPort)-1;
+ tty_reset(ttysl_fd);
+ return ERL_DRV_ERROR_GENERAL;
+ }
+
+ /* Set up smart line and termcap stuff. */
+ if (!start_lbuf() || !start_termcap()) {
+ stop_lbuf(); /* Must free this */
+ tty_reset(ttysl_fd);
+ return ERL_DRV_ERROR_GENERAL;
+ }
+
+ /* Open the terminal and set the terminal */
+ ttysl_out = fdopen(ttysl_fd, "w");
+
+#ifdef PRIMITIVE_UTF8_CHECK
+ setlocale(LC_CTYPE, ""); /* Set international environment,
+ ignore result */
+ if (((l = getenv("LC_ALL")) && *l) ||
+ ((l = getenv("LC_CTYPE")) && *l) ||
+ ((l = getenv("LANG")) && *l)) {
+ if (strstr(l, "UTF-8"))
+ utf8_mode = 1;
+ }
+
+#else
+ l = setlocale(LC_CTYPE, ""); /* Set international environment */
+ if (l != NULL) {
+ utf8_mode = (strcmp(nl_langinfo(CODESET), "UTF-8") == 0);
+ DEBUGLOG(("setlocale: %s\n",l));
+ }
+#endif
+ DEBUGLOG(("utf8_mode is %s\n",(utf8_mode) ? "on" : "off"));
+ sys_sigset(SIGCONT, cont);
+ sys_sigset(SIGWINCH, winch);
+
+ driver_select(port, (ErlDrvEvent)(Uint)ttysl_fd, ERL_DRV_READ|ERL_DRV_USE, 1);
+ ttysl_port = port;
+
+ /* we need to know this when we enter the break handler */
+ using_oldshell = 0;
+
+ return (ErlDrvData)ttysl_port; /* Nothing important to return */
+#endif /* HAVE_TERMCAP */
+}
+
+#ifdef HAVE_TERMCAP
+
+#define DEF_HEIGHT 24
+#define DEF_WIDTH 80
+static void ttysl_get_window_size(Uint32 *width, Uint32 *height)
+{
+#ifdef TIOCGWINSZ
+ struct winsize ws;
+ if (ioctl(ttysl_fd,TIOCGWINSZ,&ws) == 0) {
+ *width = (Uint32) ws.ws_col;
+ *height = (Uint32) ws.ws_row;
+ if (*width <= 0)
+ *width = DEF_WIDTH;
+ if (*height <= 0)
+ *height = DEF_HEIGHT;
+ return;
+ }
+#endif
+ *width = DEF_WIDTH;
+ *height = DEF_HEIGHT;
+}
+
+static int ttysl_control(ErlDrvData drv_data,
+ unsigned int command,
+ char *buf, int len,
+ char **rbuf, int rlen)
+{
+ char resbuff[2*sizeof(Uint32)];
+ int res_size;
+ switch (command) {
+ case CTRL_OP_GET_WINSIZE:
+ {
+ Uint32 w,h;
+ ttysl_get_window_size(&w,&h);
+ memcpy(resbuff,&w,sizeof(Uint32));
+ memcpy(resbuff+sizeof(Uint32),&h,sizeof(Uint32));
+ res_size = 2*sizeof(Uint32);
+ }
+ break;
+ case CTRL_OP_GET_UNICODE_STATE:
+ *resbuff = (utf8_mode) ? 1 : 0;
+ res_size = 1;
+ break;
+ case CTRL_OP_SET_UNICODE_STATE:
+ if (len > 0) {
+ int m = (int) *buf;
+ *resbuff = (utf8_mode) ? 1 : 0;
+ res_size = 1;
+ utf8_mode = (m) ? 1 : 0;
+ } else {
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+ if (rlen < res_size) {
+ *rbuf = driver_alloc(res_size);
+ }
+ memcpy(*rbuf,resbuff,res_size);
+ return res_size;
+}
+
+
+static void ttysl_stop(ErlDrvData ttysl_data)
+{
+ if (ttysl_port != (ErlDrvPort)-1) {
+ stop_lbuf();
+ stop_termcap();
+ tty_reset(ttysl_fd);
+ driver_select(ttysl_port, (ErlDrvEvent)(Uint)ttysl_fd, ERL_DRV_READ|ERL_DRV_USE, 0);
+ sys_sigset(SIGCONT, SIG_DFL);
+ sys_sigset(SIGWINCH, SIG_DFL);
+ }
+ ttysl_port = (ErlDrvPort)-1;
+ ttysl_fd = -1;
+ /* return TRUE; */
+}
+
+static int put_utf8(int ch, byte *target, int sz, int *pos)
+{
+ Uint x = (Uint) ch;
+ if (x < 0x80) {
+ if (*pos >= sz) {
+ return -1;
+ }
+ target[(*pos)++] = (byte) x;
+ }
+ else if (x < 0x800) {
+ if (((*pos) + 1) >= sz) {
+ return -1;
+ }
+ target[(*pos)++] = (((byte) (x >> 6)) |
+ ((byte) 0xC0));
+ target[(*pos)++] = (((byte) (x & 0x3F)) |
+ ((byte) 0x80));
+ } else if (x < 0x10000) {
+ if ((x >= 0xD800 && x <= 0xDFFF) ||
+ (x == 0xFFFE) ||
+ (x == 0xFFFF)) { /* Invalid unicode range */
+ return -1;
+ }
+ if (((*pos) + 2) >= sz) {
+ return -1;
+ }
+
+ target[(*pos)++] = (((byte) (x >> 12)) |
+ ((byte) 0xE0));
+ target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) |
+ ((byte) 0x80));
+ target[(*pos)++] = (((byte) (x & 0x3F)) |
+ ((byte) 0x80));
+ } else if (x < 0x110000) { /* Standard imposed max */
+ if (((*pos) + 3) >= sz) {
+ return -1;
+ }
+ target[(*pos)++] = (((byte) (x >> 18)) |
+ ((byte) 0xF0));
+ target[(*pos)++] = ((((byte) (x >> 12)) & 0x3F) |
+ ((byte) 0x80));
+ target[(*pos)++] = ((((byte) (x >> 6)) & 0x3F) |
+ ((byte) 0x80));
+ target[(*pos)++] = (((byte) (x & 0x3F)) |
+ ((byte) 0x80));
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
+
+static int pick_utf8(byte *s, int sz, int *pos)
+{
+ int size = sz - (*pos);
+ byte *source;
+ Uint unipoint;
+
+ if (size > 0) {
+ source = s + (*pos);
+ if (((*source) & ((byte) 0x80)) == 0) {
+ unipoint = (int) *source;
+ ++(*pos);
+ return (int) unipoint;
+ } else if (((*source) & ((byte) 0xE0)) == 0xC0) {
+ if (size < 2) {
+ return -2;
+ }
+ if (((source[1] & ((byte) 0xC0)) != 0x80) ||
+ ((*source) < 0xC2) /* overlong */) {
+ return -1;
+ }
+ (*pos) += 2;
+ unipoint =
+ (((Uint) ((*source) & ((byte) 0x1F))) << 6) |
+ ((Uint) (source[1] & ((byte) 0x3F)));
+ return (int) unipoint;
+ } else if (((*source) & ((byte) 0xF0)) == 0xE0) {
+ if (size < 3) {
+ return -2;
+ }
+ if (((source[1] & ((byte) 0xC0)) != 0x80) ||
+ ((source[2] & ((byte) 0xC0)) != 0x80) ||
+ (((*source) == 0xE0) && (source[1] < 0xA0)) /* overlong */ ) {
+ return -1;
+ }
+ if ((((*source) & ((byte) 0xF)) == 0xD) &&
+ ((source[1] & 0x20) != 0)) {
+ return -1;
+ }
+ if (((*source) == 0xEF) && (source[1] == 0xBF) &&
+ ((source[2] == 0xBE) || (source[2] == 0xBF))) {
+ return -1;
+ }
+ (*pos) += 3;
+ unipoint =
+ (((Uint) ((*source) & ((byte) 0xF))) << 12) |
+ (((Uint) (source[1] & ((byte) 0x3F))) << 6) |
+ ((Uint) (source[2] & ((byte) 0x3F)));
+ return (int) unipoint;
+ } else if (((*source) & ((byte) 0xF8)) == 0xF0) {
+ if (size < 4) {
+ return -2 ;
+ }
+ if (((source[1] & ((byte) 0xC0)) != 0x80) ||
+ ((source[2] & ((byte) 0xC0)) != 0x80) ||
+ ((source[3] & ((byte) 0xC0)) != 0x80) ||
+ (((*source) == 0xF0) && (source[1] < 0x90)) /* overlong */) {
+ return -1;
+ }
+ if ((((*source) & ((byte)0x7)) > 0x4U) ||
+ ((((*source) & ((byte)0x7)) == 0x4U) &&
+ ((source[1] & ((byte)0x3F)) > 0xFU))) {
+ return -1;
+ }
+ (*pos) += 4;
+ unipoint =
+ (((Uint) ((*source) & ((byte) 0x7))) << 18) |
+ (((Uint) (source[1] & ((byte) 0x3F))) << 12) |
+ (((Uint) (source[2] & ((byte) 0x3F))) << 6) |
+ ((Uint) (source[3] & ((byte) 0x3F)));
+ return (int) unipoint;
+ } else {
+ return -1;
+ }
+ } else {
+ return -1;
+ }
+}
+
+static int octal_or_hex_positions(Uint c)
+{
+ int x = 0;
+ Uint ch = c;
+ if (!ch) {
+ return 1;
+ }
+ while(ch) {
+ ++x;
+ ch >>= 3;
+ }
+ if (x <= 3) {
+ return 3;
+ }
+ /* \x{H ...} format when larger than \777 */
+ x = 0;
+ ch = c;
+ while(ch) {
+ ++x;
+ ch >>= 4;
+ }
+ return x+3;
+}
+
+static void octal_or_hex_format(Uint ch, byte *buf, int *pos)
+{
+ static byte hex_chars[] = { '0','1','2','3','4','5','6','7','8','9',
+ 'A','B','C','D','E','F'};
+ int num = octal_or_hex_positions(ch);
+ if (num != 3) {
+ buf[(*pos)++] = 'x';
+ buf[(*pos)++] = '{';
+ num -= 3;
+ while(num--) {
+ buf[(*pos)++] = hex_chars[((ch >> (4*num)) & 0xFU)];
+ }
+ buf[(*pos)++] = '}';
+ } else {
+ while(num--) {
+ buf[(*pos)++] = ((byte) ((ch >> (3*num)) & 0x7U) + '0');
+ }
+ }
+}
+
+/*
+ * Check that there is enough room in all buffers to copy all pad chars
+ * and stiff we need If not, realloc lbuf.
+ */
+static int check_buf_size(byte *s, int n)
+{
+ int pos = 0;
+ int ch;
+ int size = 10;
+
+ while(pos < n) {
+ /* Indata is always UTF-8 */
+ if ((ch = pick_utf8(s,n,&pos)) < 0) {
+ /* XXX temporary allow invalid chars */
+ ch = (int) s[pos];
+ DEBUGLOG(("Invalid UTF8:%d",ch));
+ ++pos;
+ }
+ if (utf8_mode) { /* That is, terminal is UTF8 compliant */
+ if (ch >= 128 || isprint(ch)) {
+ DEBUGLOG(("Printable(UTF-8:%d):%d",(pos - opos),ch));
+ size++; /* Buffer contains wide characters... */
+ } else if (ch == '\t') {
+ size += 8;
+ } else {
+ DEBUGLOG(("Magic(UTF-8:%d):%d",(pos - opos),ch));
+ size += 2;
+ }
+ } else {
+ if (ch <= 255 && isprint(ch)) {
+ DEBUGLOG(("Printable:%d",ch));
+ size++;
+ } else if (ch == '\t')
+ size += 8;
+ else if (ch >= 128) {
+ DEBUGLOG(("Non printable:%d",ch));
+ size += (octal_or_hex_positions(ch) + 1);
+ }
+ else {
+ DEBUGLOG(("Magic:%d",ch));
+ size += 2;
+ }
+ }
+ }
+
+ if (size + lpos >= lbuf_size) {
+
+ lbuf_size = size + lpos + BUFSIZ;
+ if ((lbuf = driver_realloc(lbuf, lbuf_size * sizeof(Uint32))) == NULL) {
+ driver_failure(ttysl_port, -1);
+ return(0);
+ }
+ }
+ return(1);
+}
+
+
+static void ttysl_from_erlang(ErlDrvData ttysl_data, char* buf, int count)
+{
+ if (lpos > MAXSIZE)
+ put_chars((byte*)"\n", 1);
+
+ switch (buf[0]) {
+ case OP_PUTC:
+ DEBUGLOG(("OP: Putc(%d)",count-1));
+ if (check_buf_size((byte*)buf+1, count-1) == 0)
+ return;
+ put_chars((byte*)buf+1, count-1);
+ break;
+ case OP_MOVE:
+ move_rel(get_sint16(buf+1));
+ break;
+ case OP_INSC:
+ if (check_buf_size((byte*)buf+1, count-1) == 0)
+ return;
+ ins_chars((byte*)buf+1, count-1);
+ break;
+ case OP_DELC:
+ del_chars(get_sint16(buf+1));
+ break;
+ case OP_BEEP:
+ outc('\007');
+ break;
+ default:
+ /* Unknown op, just ignore. */
+ break;
+ }
+ fflush(ttysl_out);
+ return; /* TRUE; */
+}
+
+static void ttysl_from_tty(ErlDrvData ttysl_data, ErlDrvEvent fd)
+{
+ byte b[1024];
+ ssize_t i;
+ int ch = 0, pos = 0;
+ int left = 1024;
+ byte *p = b;
+ byte t[1024];
+ int tpos;
+
+ if (utf8buf_size > 0) {
+ memcpy(b,utf8buf,utf8buf_size);
+ left -= utf8buf_size;
+ p += utf8buf_size;
+ utf8buf_size = 0;
+ }
+
+ if ((i = read((int)(Sint)fd, (char *) p, left)) >= 0) {
+ if (p != b) {
+ i += (p - b);
+ }
+ if (utf8_mode) { /* Hopefully an UTF8 terminal */
+ while(pos < i && (ch = pick_utf8(b,i,&pos)) >= 0)
+ ;
+ if (ch == -2 && i - pos <= 4) {
+ /* bytes left to care for */
+ utf8buf_size = i -pos;
+ memcpy(utf8buf,b+pos,utf8buf_size);
+ } else if (ch == -1) {
+ DEBUGLOG(("Giving up on UTF8 mode, invalid character"));
+ utf8_mode = 0;
+ goto latin_terminal;
+ }
+ driver_output(ttysl_port, (char *) b, pos);
+ } else {
+ latin_terminal:
+ tpos = 0;
+ while (pos < i) {
+ while (tpos < 1020 && pos < i) { /* Max 4 bytes for UTF8 */
+ put_utf8((int) b[pos++], t, 1024, &tpos);
+ }
+ driver_output(ttysl_port, (char *) t, tpos);
+ tpos = 0;
+ }
+ }
+ } else {
+ driver_failure(ttysl_port, -1);
+ }
+}
+
+static void ttysl_stop_select(ErlDrvEvent e, void* _)
+{
+ int fd = (int)(long)e;
+ if (fd != 0) {
+ close(fd);
+ }
+}
+
+/* Procedures for putting and getting integers to/from strings. */
+static Sint16 get_sint16(char *s)
+{
+ return ((*s << 8) | ((byte*)s)[1]);
+}
+
+static int start_lbuf(void)
+{
+ if (!lbuf && !(lbuf = ( Uint32*) driver_alloc(lbuf_size * sizeof(Uint32))))
+ return FALSE;
+ llen = 0;
+ lpos = 0;
+ return TRUE;
+}
+
+static int stop_lbuf(void)
+{
+ if (lbuf) {
+ driver_free(lbuf);
+ lbuf = NULL;
+ }
+ return TRUE;
+}
+
+/* Put l bytes (in UTF8) from s into the buffer and output them. */
+static int put_chars(byte *s, int l)
+{
+ int n;
+
+ n = insert_buf(s, l);
+ if (n > 0)
+ write_buf(lbuf + lpos - n, n);
+ if (lpos > llen)
+ llen = lpos;
+ return TRUE;
+}
+
+/*
+ * Move the current postition forwards or backwards within the current
+ * line. We know about padding.
+ */
+static int move_rel(int n)
+{
+ int npos; /* The new position */
+
+ /* Step forwards or backwards over the buffer. */
+ npos = step_over_chars(n);
+
+ /* Calculate move, updates pointers and move the cursor. */
+ move_cursor(lpos, npos);
+ lpos = npos;
+ return TRUE;
+}
+
+/* Insert characters into the buffer at the current position. */
+static int ins_chars(byte *s, int l)
+{
+ int n, tl;
+ Uint32 *tbuf = NULL; /* Suppress warning about use-before-set */
+
+ /* Move tail of buffer to make space. */
+ if ((tl = llen - lpos) > 0) {
+ if ((tbuf = driver_alloc(tl * sizeof(Uint32))) == NULL)
+ return FALSE;
+ memcpy(tbuf, lbuf + lpos, tl * sizeof(Uint32));
+ }
+ n = insert_buf(s, l);
+ if (tl > 0) {
+ memcpy(lbuf + lpos, tbuf, tl * sizeof(Uint32));
+ driver_free(tbuf);
+ }
+ llen += n;
+ write_buf(lbuf + (lpos - n), llen - (lpos - n));
+ move_cursor(llen, lpos);
+ return TRUE;
+}
+
+/*
+ * Delete characters in the buffer. Can delete characters before (n < 0)
+ * and after (n > 0) the current position. Cursor left at beginning of
+ * deleted block.
+ */
+static int del_chars(int n)
+{
+ int i, l, r;
+ int pos;
+
+ update_cols();
+
+ /* Step forward or backwards over n logical characters. */
+ pos = step_over_chars(n);
+
+ if (pos > lpos) {
+ l = pos - lpos; /* Buffer characters to delete */
+ r = llen - lpos - l; /* Characters after deleted */
+ /* Fix up buffer and buffer pointers. */
+ if (r > 0)
+ memcpy(lbuf + lpos, lbuf + pos, r * sizeof(Uint32));
+ llen -= l;
+ /* Write out characters after, blank the tail and jump back to lpos. */
+ write_buf(lbuf + lpos, r);
+ for (i = l ; i > 0; --i)
+ outc(' ');
+ if (COL(llen+l) == 0 && xn)
+ {
+ outc(' ');
+ move_left(1);
+ }
+ move_cursor(llen + l, lpos);
+ }
+ else if (pos < lpos) {
+ l = lpos - pos; /* Buffer characters */
+ r = llen - lpos; /* Characters after deleted */
+ move_cursor(lpos, lpos-l); /* Move back */
+ /* Fix up buffer and buffer pointers. */
+ if (r > 0)
+ memcpy(lbuf + pos, lbuf + lpos, r * sizeof(Uint32));
+ lpos -= l;
+ llen -= l;
+ /* Write out characters after, blank the tail and jump back to lpos. */
+ write_buf(lbuf + lpos, r);
+ for (i = l ; i > 0; --i)
+ outc(' ');
+ if (COL(llen+l) == 0 && xn)
+ {
+ outc(' ');
+ move_left(1);
+ }
+ move_cursor(llen + l, lpos);
+ }
+ return TRUE;
+}
+
+/* Step over n logical characters, check for overflow. */
+static int step_over_chars(int n)
+{
+ Uint32 *c, *beg, *end;
+
+ beg = lbuf;
+ end = lbuf + llen;
+ c = lbuf + lpos;
+ for ( ; n > 0 && c < end; --n) {
+ c++;
+ while (c < end && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0))
+ c++;
+ }
+ for ( ; n < 0 && c > beg; n++) {
+ --c;
+ while (c > beg && (*c & TAG_MASK) && ((*c & ~TAG_MASK) == 0))
+ --c;
+ }
+ return c - lbuf;
+}
+
+/*
+ * Insert n characters into the buffer at lpos.
+ * Know about pad characters and treat \n specially.
+ */
+
+static int insert_buf(byte *s, int n)
+{
+ int pos = 0;
+ int buffpos = lpos;
+ int ch;
+
+ while (pos < n) {
+ if ((ch = pick_utf8(s,n,&pos)) < 0) {
+ /* XXX temporary allow invalid chars */
+ ch = (int) s[pos];
+ DEBUGLOG(("insert_buf: Invalid UTF8:%d",ch));
+ ++pos;
+ }
+ if ((utf8_mode && (ch >= 128 || isprint(ch))) || (ch <= 255 && isprint(ch))) {
+ DEBUGLOG(("insert_buf: Printable(UTF-8):%d",ch));
+ lbuf[lpos++] = (Uint32) ch;
+ } else if (ch >= 128) { /* not utf8 mode */
+ int nc = octal_or_hex_positions(ch);
+ lbuf[lpos++] = ((Uint32) ch) | ESCAPED_TAG;
+ while (nc--) {
+ lbuf[lpos++] = ESCAPED_TAG;
+ }
+ } else if (ch == '\t') {
+ do {
+ lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch));
+ ch = 0;
+ } while (lpos % 8);
+ } else if (ch == '\n' || ch == '\r') {
+ write_buf(lbuf + buffpos, lpos - buffpos);
+ outc('\r');
+ if (ch == '\n')
+ outc('\n');
+ if (llen > lpos) {
+ memcpy(lbuf, lbuf + lpos, llen - lpos);
+ }
+ llen -= lpos;
+ lpos = buffpos = 0;
+ } else {
+ DEBUGLOG(("insert_buf: Magic(UTF-8):%d",ch));
+ lbuf[lpos++] = ch | CONTROL_TAG;
+ lbuf[lpos++] = CONTROL_TAG;
+ }
+ }
+ return lpos - buffpos; /* characters "written" into
+ current buffer (may be less due to newline) */
+}
+
+
+
+/*
+ * Write n characters in line buffer starting at s. Be smart about
+ * non-printables. Know about pad characters and that \n can never
+ * occur normally.
+ */
+
+static int write_buf(Uint32 *s, int n)
+{
+ byte ubuf[4];
+ int ubytes = 0, i;
+ byte lastput = ' ';
+
+ update_cols();
+
+ while (n > 0) {
+ if (!(*s & TAG_MASK) ) {
+ if (utf8_mode) {
+ ubytes = 0;
+ if (put_utf8((int) *s, ubuf, 4, &ubytes) == 0) {
+ for (i = 0; i < ubytes; ++i) {
+ outc(ubuf[i]);
+ }
+ lastput = 0; /* Means the last written character was multibyte UTF8 */
+ }
+ } else {
+ outc((byte) *s);
+ lastput = (byte) *s;
+ }
+ --n;
+ ++s;
+ }
+ else if (*s == (CONTROL_TAG | ((Uint32) '\t'))) {
+ outc(lastput = ' ');
+ --n; s++;
+ while (n > 0 && *s == CONTROL_TAG) {
+ outc(lastput = ' ');
+ --n; s++;
+ }
+ } else if (*s & CONTROL_TAG) {
+ outc('^');
+ outc(lastput = ((byte) ((*s == 0177) ? '?' : *s | 0x40)));
+ n -= 2;
+ s += 2;
+ } else if (*s & ESCAPED_TAG) {
+ Uint32 ch = *s & ~(TAG_MASK);
+ byte *octbuff;
+ byte octtmp[256];
+ int octbytes;
+ DEBUGLOG(("Escaped: %d", ch));
+ octbytes = octal_or_hex_positions(ch);
+ if (octbytes > 256) {
+ octbuff = driver_alloc(octbytes);
+ } else {
+ octbuff = octtmp;
+ }
+ octbytes = 0;
+ octal_or_hex_format(ch, octbuff, &octbytes);
+ DEBUGLOG(("octbytes: %d", octbytes));
+ outc('\\');
+ for (i = 0; i < octbytes; ++i) {
+ outc(lastput = octbuff[i]);
+ DEBUGLOG(("outc: %d", (int) lastput));
+ }
+ n -= octbytes+1;
+ s += octbytes+1;
+ if (octbuff != octtmp) {
+ driver_free(octbuff);
+ }
+ } else {
+ DEBUGLOG(("Very unexpected character %d",(int) *s));
+ ++n;
+ --s;
+ }
+ }
+ /* Check landed in first column of new line and have 'xn' bug. */
+ n = s - lbuf;
+ if (COL(n) == 0 && xn && n != 0) {
+ if (n >= llen) {
+ outc(' ');
+ } else if (lastput == 0) { /* A multibyte UTF8 character */
+ for (i = 0; i < ubytes; ++i) {
+ outc(ubuf[i]);
+ }
+ } else {
+ outc(lastput);
+ }
+ move_left(1);
+ }
+ return TRUE;
+}
+
+
+/* The basic procedure for outputting one character. */
+static int outc(int c)
+{
+ return (int)putc(c, ttysl_out);
+}
+
+static int move_cursor(int from, int to)
+{
+ int dc, dl;
+
+ update_cols();
+
+ dc = COL(to) - COL(from);
+ dl = LINE(to) - LINE(from);
+ if (dl > 0)
+ move_down(dl);
+ else if (dl < 0)
+ move_up(-dl);
+ if (dc > 0)
+ move_right(dc);
+ else if (dc < 0)
+ move_left(-dc);
+ return TRUE;
+}
+
+static int start_termcap(void)
+{
+ int eres;
+ size_t envsz = 1024;
+ char *env = NULL;
+ char *c;
+
+ capbuf = driver_alloc(1024);
+ if (!capbuf)
+ goto false;
+ eres = erl_drv_getenv("TERM", capbuf, &envsz);
+ if (eres == 0)
+ env = capbuf;
+ else if (eres < 0)
+ goto false;
+ else /* if (eres > 1) */ {
+ char *envbuf = driver_alloc(envsz);
+ if (!envbuf)
+ goto false;
+ while (1) {
+ char *newenvbuf;
+ eres = erl_drv_getenv("TERM", envbuf, &envsz);
+ if (eres == 0)
+ break;
+ newenvbuf = driver_realloc(envbuf, envsz);
+ if (eres < 0 || !newenvbuf) {
+ env = newenvbuf ? newenvbuf : envbuf;
+ goto false;
+ }
+ envbuf = newenvbuf;
+ }
+ env = envbuf;
+ }
+ if (tgetent((char*)lbuf, env) <= 0)
+ goto false;
+ if (env != capbuf) {
+ env = NULL;
+ driver_free(env);
+ }
+ c = capbuf;
+ cols = tgetnum("co");
+ if (cols <= 0)
+ cols = DEF_WIDTH;
+ xn = tgetflag("xn");
+ up = tgetstr("up", &c);
+ if (!(down = tgetstr("do", &c)))
+ down = "\n";
+ if (!(left = tgetflag("bs") ? "\b" : tgetstr("bc", &c)))
+ left = "\b"; /* Can't happen - but does on Solaris 2 */
+ right = tgetstr("nd", &c);
+ if (up && down && left && right)
+ return TRUE;
+ false:
+ if (env && env != capbuf)
+ driver_free(env);
+ if (capbuf)
+ driver_free(capbuf);
+ capbuf = NULL;
+ return FALSE;
+}
+
+static int stop_termcap(void)
+{
+ if (capbuf) driver_free(capbuf);
+ capbuf = NULL;
+ return TRUE;
+}
+
+static int move_left(int n)
+{
+ while (n-- > 0)
+ tputs(left, 1, outc);
+ return TRUE;
+}
+
+static int move_right(int n)
+{
+ while (n-- > 0)
+ tputs(right, 1, outc);
+ return TRUE;
+}
+
+static int move_up(int n)
+{
+ while (n-- > 0)
+ tputs(up, 1, outc);
+ return TRUE;
+}
+
+static int move_down(int n)
+{
+ while (n-- > 0)
+ tputs(down, 1, outc);
+ return TRUE;
+}
+
+
+/*
+ * Updates cols if terminal has resized (SIGWINCH). Should be called
+ * at the start of any function that uses the COL or LINE macros. If
+ * the terminal is resized after calling this function but before use
+ * of the macros, then we may write to the wrong screen location.
+ *
+ * We cannot call this from the SIGWINCH handler because it uses
+ * ioctl() which is not a safe function as listed in the signal(7)
+ * man page.
+ */
+static void update_cols(void)
+{
+ Uint32 width, height;
+
+ if (cols_needs_update) {
+ cols_needs_update = FALSE;
+ ttysl_get_window_size(&width, &height);
+ cols = width;
+ }
+}
+
+
+/*
+ * Put a terminal device into non-canonical mode with ECHO off.
+ * Before doing so we first save the terminal's current mode,
+ * assuming the caller will call the tty_reset() function
+ * (also in this file) when it's done with raw mode.
+ */
+
+static struct termios tty_smode, tty_rmode;
+
+static int tty_init(int fd, int canon, int echo, int sig)
+{
+ if (tcgetattr(fd, &tty_rmode) < 0)
+ return -1;
+ tty_smode = tty_rmode;
+
+ /* Default characteristics for all usage including termcap output. */
+ tty_smode.c_iflag &= ~ISTRIP;
+
+ /* Turn canonical (line mode) on off. */
+ if (canon > 0) {
+ tty_smode.c_iflag |= ICRNL;
+ tty_smode.c_lflag |= ICANON;
+ tty_smode.c_oflag |= OPOST;
+ tty_smode.c_cc[VEOF] = tty_rmode.c_cc[VEOF];
+#ifdef VDSUSP
+ tty_smode.c_cc[VDSUSP] = tty_rmode.c_cc[VDSUSP];
+#endif
+ }
+ if (canon < 0) {
+ tty_smode.c_iflag &= ~ICRNL;
+ tty_smode.c_lflag &= ~ICANON;
+ tty_smode.c_oflag &= ~OPOST;
+ /* Must get these really right or funny effects can occur. */
+ tty_smode.c_cc[VMIN] = 1;
+ tty_smode.c_cc[VTIME] = 0;
+#ifdef VDSUSP
+ tty_smode.c_cc[VDSUSP] = 0;
+#endif
+ }
+
+ /* Turn echo on or off. */
+ if (echo > 0)
+ tty_smode.c_lflag |= ECHO;
+ if (echo < 0)
+ tty_smode.c_lflag &= ~ECHO;
+
+ /* Set extra characteristics for "RAW" mode, no signals. */
+ if (sig > 0) {
+ /* Ignore IMAXBEL as not POSIX. */
+#ifndef QNX
+ tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON|IXANY);
+#else
+ tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON);
+#endif
+ tty_smode.c_lflag |= (ISIG|IEXTEN);
+ }
+ if (sig < 0) {
+ /* Ignore IMAXBEL as not POSIX. */
+#ifndef QNX
+ tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON|IXANY);
+#else
+ tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON);
+#endif
+ tty_smode.c_lflag &= ~(ISIG|IEXTEN);
+ }
+ return 0;
+}
+
+/*
+ * Set/restore a terminal's mode to whatever it was on the most
+ * recent call to the tty_init() function above.
+ */
+
+static int tty_set(int fd)
+{
+ DEBUGF(("Setting tty...\n"));
+
+ if (tcsetattr(fd, TCSANOW, &tty_smode) < 0)
+ return(-1);
+ return(0);
+}
+
+static int tty_reset(int fd) /* of terminal device */
+{
+ DEBUGF(("Resetting tty...\n"));
+
+ if (tcsetattr(fd, TCSANOW, &tty_rmode) < 0)
+ return(-1);
+
+ return(0);
+}
+
+/*
+ * Signal handler to cope with signals so that we can reset the tty
+ * to the orignal settings
+ */
+
+static RETSIGTYPE suspend(int sig)
+{
+ if (tty_reset(ttysl_fd) < 0) {
+ fprintf(stderr,"Can't reset tty \n");
+ exit(1);
+ }
+
+ sys_sigset(sig, SIG_DFL); /* Set signal handler to default */
+ sys_sigrelease(sig); /* Allow 'sig' to come through */
+ kill(getpid(), sig); /* Send ourselves the signal */
+ sys_sigblock(sig); /* Reset to old mask */
+ sys_sigset(sig, suspend); /* Reset signal handler */
+
+ if (tty_set(ttysl_fd) < 0) {
+ fprintf(stderr,"Can't set tty raw \n");
+ exit(1);
+ }
+}
+
+static RETSIGTYPE cont(int sig)
+{
+ if (tty_set(ttysl_fd) < 0) {
+ fprintf(stderr,"Can't set tty raw\n");
+ exit(1);
+ }
+}
+
+static RETSIGTYPE winch(int sig)
+{
+ cols_needs_update = TRUE;
+}
+#endif /* HAVE_TERMCAP */