/* * %CopyrightBegin% * * Copyright Ericsson AB 1996-2016. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions 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 #include #include #include #include #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 #define OP_PUTC_SYNC 5 /* 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; command -= ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER; 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 -1; } 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: case OP_PUTC_SYNC: 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; } if (buf[0] == OP_PUTC_SYNC) { /* On windows we do a blocking write to the tty so we just send the ack immidiately. If at some point in the future someone has a problem with tty output being blocking this has to be changed. */ ErlDrvTermData spec[] = { ERL_DRV_PORT, driver_mk_port(ttysl_port), ERL_DRV_ATOM, driver_mk_atom("ok"), ERL_DRV_TUPLE, 2 }; erl_drv_output_term(driver_mk_port(ttysl_port), spec, sizeof(spec) / sizeof(spec[0])); } 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); }