/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1996-2013. 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 "sys.h"
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include "erl_driver.h"
#include "win_con.h"
#define TRUE 1
#define FALSE 0
static int cols; /* Number of columns available. */
static int rows; /* Number of rows available. */
/* 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;
Uint32 *lbuf; /* The current line buffer */
int llen; /* The current line length */
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 ISPRINT(c) (isprint(c) || (128+32 <= (c) && (c) < 256))
#define DEBUGLOG(X) /* nothing */
/*
* XXX These are used by win_con.c (for command history).
* Should be cleaned up.
*/
#define NL '\n'
/* Main interface functions. */
static int ttysl_init(void);
static ErlDrvData ttysl_start(ErlDrvPort, char*);
static void ttysl_stop(ErlDrvData);
static ErlDrvSSizeT ttysl_control(ErlDrvData, unsigned int,
char *, ErlDrvSizeT, char **, ErlDrvSizeT);
static void ttysl_from_erlang(ErlDrvData, char*, ErlDrvSizeT);
static void ttysl_from_tty(ErlDrvData, ErlDrvEvent);
static Sint16 get_sint16(char *s);
static ErlDrvPort ttysl_port;
extern ErlDrvEvent console_input_event;
extern HANDLE console_thread;
static HANDLE ttysl_in = INVALID_HANDLE_VALUE; /* Handle for console input. */
static HANDLE ttysl_out = INVALID_HANDLE_VALUE; /* Handle for console output */
/* Functions that work on the line buffer. */
static int start_lbuf();
static int stop_lbuf();
static int put_chars();
static int move_rel();
static int ins_chars();
static int del_chars();
static int step_over_chars(int n);
static int insert_buf();
static int write_buf();
static void move_cursor(int, int);
/* Define the driver table entry. */
struct erl_drv_entry ttsl_driver_entry = {
ttysl_init,
ttysl_start,
ttysl_stop,
ttysl_from_erlang,
ttysl_from_tty,
NULL,
"tty_sl",
NULL,
NULL,
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,
NULL,
NULL,
NULL,
};
static int utf8_mode = 0;
static int ttysl_init()
{
lbuf = NULL; /* For line buffer handling */
ttysl_port = (ErlDrvPort)-1;
return 0;
}
static ErlDrvData ttysl_start(ErlDrvPort port, char* buf)
{
if ((int)ttysl_port != -1 || console_thread == NULL) {
return ERL_DRV_ERROR_GENERAL;
}
start_lbuf();
utf8_mode = 1;
driver_select(port, console_input_event, ERL_DRV_READ, 1);
ttysl_port = port;
return (ErlDrvData)ttysl_port;/* Nothing important to return */
}
#define DEF_HEIGHT 24
#define DEF_WIDTH 80
static void ttysl_get_window_size(Uint32 *width, Uint32 *height)
{
*width = ConGetColumns();
*height = ConGetRows();
}
static ErlDrvSSizeT ttysl_control(ErlDrvData drv_data,
unsigned int command,
char *buf, ErlDrvSizeT len,
char **rbuf, ErlDrvSizeT rlen)
{
char resbuff[2*sizeof(Uint32)];
ErlDrvSizeT 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 ((int)ttysl_port != -1) {
driver_select(ttysl_port, console_input_event, ERL_DRV_READ, 0);
}
ttysl_in = ttysl_out = INVALID_HANDLE_VALUE;
stop_lbuf();
ttysl_port = (ErlDrvPort)-1;
}
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,ch));
size++; /* Buffer contains wide characters... */
} else if (ch == '\t') {
size += 8;
} else {
DEBUGLOG(("Magic(UTF-8:%d):%d",pos,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, ErlDrvSizeT count)
{
if (lpos > MAXSIZE)
put_chars((byte*)"\n", 1);
switch (buf[0]) {
case OP_PUTC:
DEBUGLOG(("OP: Putc(%I64u)",(unsigned long long)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:
ConBeep();
break;
default:
/* Unknown op, just ignore. */
break;
}
return;
}
extern int read_inbuf(char *data, int n);
static void ttysl_from_tty(ErlDrvData ttysl_data, ErlDrvEvent fd)
{
Uint32 inbuf[64];
byte t[1024];
int i,pos,tpos;
i = ConReadInput(inbuf,1);
pos = 0;
tpos = 0;
while (pos < i) {
while (tpos < 1020 && pos < i) { /* Max 4 bytes for UTF8 */
put_utf8((int) inbuf[pos++], t, 1024, &tpos);
}
driver_output(ttysl_port, (char *) t, tpos);
tpos = 0;
}
}
/*
* Gets signed 16 bit integer from binary buffer.
*/
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;
}
llen = 0; /* To avoid access error in win_con:AddToCmdHistory during exit*/
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)
ConPutChar(' ');
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)
ConPutChar(' ');
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;
}
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);
ConPutChar('\r');
if (ch == '\n')
ConPutChar('\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) */
}
static int write_buf(Uint32 *s, int n)
{
int i;
/*update_cols();*/
while (n > 0) {
if (!(*s & TAG_MASK) ) {
ConPutChar(*s);
--n;
++s;
}
else if (*s == (CONTROL_TAG | ((Uint32) '\t'))) {
ConPutChar(' ');
--n; s++;
while (n > 0 && *s == CONTROL_TAG) {
ConPutChar(' ');
--n; s++;
}
} else if (*s & CONTROL_TAG) {
ConPutChar('^');
ConPutChar((*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));
ConPutChar('\\');
for (i = 0; i < octbytes; ++i) {
ConPutChar(octbuff[i]);
}
n -= octbytes+1;
s += octbytes+1;
if (octbuff != octtmp) {
driver_free(octbuff);
}
} else {
DEBUGLOG(("Very unexpected character %d",(int) *s));
++n;
--s;
}
}
return TRUE;
}
static void
move_cursor(int from, int to)
{
ConSetCursor(from,to);
}