diff options
Diffstat (limited to 'erts/emulator/drivers/win32/ttsl_drv.c')
-rw-r--r-- | erts/emulator/drivers/win32/ttsl_drv.c | 751 |
1 files changed, 751 insertions, 0 deletions
diff --git a/erts/emulator/drivers/win32/ttsl_drv.c b/erts/emulator/drivers/win32/ttsl_drv.c new file mode 100644 index 0000000000..fd88dafd34 --- /dev/null +++ b/erts/emulator/drivers/win32/ttsl_drv.c @@ -0,0 +1,751 @@ +/* + * %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. + */ + +#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 int ttysl_control(ErlDrvData, unsigned int, char *, int, char **, int); +static void ttysl_from_erlang(ErlDrvData, char*, int); +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 +}; + +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 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 ((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 - 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: + 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); +} |