diff --git a/HOWTO/INSTALL-WIN32.md b/HOWTO/INSTALL-WIN32.md index 4ad0159bcd25..bd8387aaae65 100644 --- a/HOWTO/INSTALL-WIN32.md +++ b/HOWTO/INSTALL-WIN32.md @@ -68,7 +68,7 @@ This is the short story though, for the experienced and impatient: ) and unpack with `tar` to the windows disk for example to: /mnt/c/src/ - * Install mingw-gcc, and make: `sudo apt install g++-mingw-w64 gcc-mingw-w64 make` + * Install mingw-gcc, and make: `sudo apt update && sudo apt install g++-mingw-w64 gcc-mingw-w64 make` * `$ cd UNPACK_DIR` @@ -150,7 +150,7 @@ the different tools: Install into `C:/OpenSSL-Win64` (or `C:/OpenSSL-Win32`) * wxWidgets (optional) - You need this to build wx and use gui's in debugger and observer. + You need this to build wx to use gui's in debugger and observer. We recommend v3.1.4 or later. Unpack into `c:/opt/local64/pgm/wxWidgets-3.1.4` diff --git a/erts/Makefile b/erts/Makefile index d6d9dee40d08..a0f0dcfdb3ab 100644 --- a/erts/Makefile +++ b/erts/Makefile @@ -86,10 +86,8 @@ local_setup: cp $(ERL_TOP)/bin/$(TARGET)/erlc.exe $(ERL_TOP)/bin/erlc.exe; \ cp $(ERL_TOP)/bin/$(TARGET)/erl.exe $(ERL_TOP)/bin/erl.exe; \ cp $(ERL_TOP)/bin/$(TARGET)/erl_call.exe $(ERL_TOP)/bin/erl_call.exe; \ - cp $(ERL_TOP)/bin/$(TARGET)/werl.exe $(ERL_TOP)/bin/werl.exe; \ cp $(ERL_TOP)/bin/$(TARGET)/escript.exe $(ERL_TOP)/bin/escript.exe; \ - chmod 755 $(ERL_TOP)/bin/erl.exe $(ERL_TOP)/bin/erlc.exe \ - $(ERL_TOP)/bin/werl.exe; \ + chmod 755 $(ERL_TOP)/bin/erl.exe $(ERL_TOP)/bin/erlc.exe; \ make_local_ini.sh $(ERL_TOP); \ cp $(ERL_TOP)/bin/erl.ini $(ERL_TOP)/bin/$(TARGET)/erl.ini; \ else \ diff --git a/erts/doc/src/erlsrv_cmd.xml b/erts/doc/src/erlsrv_cmd.xml index e8f066b21b5d..fe952f690e66 100644 --- a/erts/doc/src/erlsrv_cmd.xml +++ b/erts/doc/src/erlsrv_cmd.xml @@ -112,8 +112,7 @@

The location of the Erlang emulator. The default is the located in the same - directory as erlsrv.exe. Do not specify - as this emulator, it will not work.

+ directory as erlsrv.exe.

If the system uses release handling, this is to be set to a program similar to .

diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index 42f7a6bb1c03..3791d2caa24c 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -1129,6 +1129,7 @@ RUN_OBJS += \ LTTNG_OBJS = $(OBJDIR)/erlang_lttng.o NIF_OBJS = \ + $(OBJDIR)/prim_tty_nif.o \ $(OBJDIR)/erl_tracer_nif.o \ $(OBJDIR)/prim_buffer_nif.o \ $(OBJDIR)/prim_file_nif.o \ @@ -1139,10 +1140,8 @@ ifeq ($(TARGET),win32) DRV_OBJS = \ $(OBJDIR)/registry_drv.o \ $(OBJDIR)/inet_drv.o \ - $(OBJDIR)/ram_file_drv.o \ - $(OBJDIR)/ttsl_drv.o + $(OBJDIR)/ram_file_drv.o OS_OBJS = \ - $(OBJDIR)/win_con.o \ $(OBJDIR)/dll_sys.o \ $(OBJDIR)/driver_tab.o \ $(OBJDIR)/sys_float.o \ @@ -1166,8 +1165,7 @@ OS_OBJS = \ DRV_OBJS = \ $(OBJDIR)/inet_drv.o \ - $(OBJDIR)/ram_file_drv.o \ - $(OBJDIR)/ttsl_drv.o + $(OBJDIR)/ram_file_drv.o endif ifneq ($(STATIC_NIFS),no) diff --git a/erts/emulator/beam/break.c b/erts/emulator/beam/break.c index 3b6de45587a7..dff270c60f61 100644 --- a/erts/emulator/beam/break.c +++ b/erts/emulator/beam/break.c @@ -581,7 +581,7 @@ do_break(void) /* check if we're in console mode and, if so, halt immediately if break is called */ mode = erts_read_env("ERL_CONSOLE_MODE"); - if (mode && sys_strcmp(mode, "window") != 0) + if (mode && sys_strcmp(mode, "detached") == 0) erts_exit(0, ""); erts_free_read_env(mode); #endif /* __WIN32__ */ diff --git a/erts/emulator/drivers/unix/ttsl_drv.c b/erts/emulator/drivers/unix/ttsl_drv.c deleted file mode 100644 index 3fb5bdb8fb4d..000000000000 --- a/erts/emulator/drivers/unix/ttsl_drv.c +++ /dev/null @@ -1,1607 +0,0 @@ -/* - * %CopyrightBegin% - * - * Copyright Ericsson AB 1996-2022. 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 "erl_driver.h" - -static int ttysl_init(void); -static ErlDrvData ttysl_start(ErlDrvPort, char*); - -#ifdef HAVE_TERMCAP /* else make an empty driver that cannot be opened */ - -#ifndef WANT_NONBLOCKING -#define WANT_NONBLOCKING -#endif - -#include "sys.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_WCWIDTH -#include -#endif -#ifdef HAVE_FCNTL_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif -#if !defined(HAVE_SETLOCALE) || !defined(HAVE_NL_LANGINFO) || !defined(HAVE_LANGINFO_H) -#define PRIMITIVE_UTF8_CHECK 1 -#else -#include -#endif - -#if defined IOV_MAX -#define MAXIOV IOV_MAX -#elif defined UIO_MAXIOV -#define MAXIOV UIO_MAXIOV -#else -#define MAXIOV 16 -#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 capabilities 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 -#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 - -/* We use 1024 as the buf size as that was the default buf size of FILE streams - on all platforms that I checked. */ -#define TTY_BUFFSIZE 1024 - -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 */ - /* NOTE: not the same as column position a char may not take a" - * column to display or it might take many columns - */ -/* - * 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*, ErlDrvSizeT); -static void ttysl_to_tty(ErlDrvData, ErlDrvEvent); -static void ttysl_flush_tty(ErlDrvData); -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 int ttysl_terminate = 0; -static int ttysl_send_ok = 0; -static ErlDrvBinary *putcbuf; -static int putcpos; -static int putclen; - -/* 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,int); -static int outc(int c); -static int move_cursor(int,int); -static int cp_pos_to_col(int cp_pos); - - -/* 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 ErlDrvSSizeT ttysl_control(ErlDrvData, unsigned int, - char *, ErlDrvSizeT, char **, ErlDrvSizeT); -#ifdef ERTS_NOT_USED -static RETSIGTYPE suspend(int); -#endif -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), - IF_IMPL(ttysl_to_tty), - "tty_sl", /* driver_name */ - NULL, /* finish */ - NULL, /* handle */ - IF_IMPL(ttysl_control), - NULL, /* timeout */ - NULL, /* outputv */ - NULL, /* ready_async */ - IF_IMPL(ttysl_flush_tty), - NULL, /* call */ - NULL, /* event */ - ERL_DRV_EXTENDED_MARKER, - ERL_DRV_EXTENDED_MAJOR_VERSION, - ERL_DRV_EXTENDED_MINOR_VERSION, - 0, /* ERL_DRV_FLAGs */ - NULL, /* handle2 */ - 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(("ttysl_init: 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, *l; - int canon, echo, sig; /* Terminal characteristics */ - int flag; - extern int using_oldshell; /* set this to let the rest of erts know */ - - DEBUGLOG(("ttysl_start: driver input \"%s\", ttysl_port = %d (-1 expected)", buf, ttysl_port)); - utf8buf_size = 0; - if (ttysl_port != (ErlDrvPort)-1) { - DEBUGLOG(("ttysl_start: failure with ttysl_port = %d, not initialized properly?\n", ttysl_port)); - return ERL_DRV_ERROR_GENERAL; - } - - DEBUGLOG(("ttysl_start: isatty(0) = %d (1 expected), isatty(1) = %d (1 expected)", isatty(0), isatty(1))); - if (!isatty(0) || !isatty(1)) { - DEBUGLOG(("ttysl_start: failure in isatty, isatty(0) = %d, isatty(1) = %d", 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, ' '))) { - *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) { - DEBUGLOG(("ttysl_start: failed to open ttysl_fd, open(%s, O_RDWR, 0)) = %d\n", s, ttysl_fd)); - 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) { - DEBUGLOG(("ttysl_start: failed init tty or set tty\n")); - 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()) { - DEBUGLOG(("ttysl_start: failed to start_lbuf or start_termcap\n")); - stop_lbuf(); /* Must free this */ - tty_reset(ttysl_fd); - return ERL_DRV_ERROR_GENERAL; - } - - SET_NONBLOCKING(ttysl_fd); - -#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(("ttysl_start: setlocale: %s",l)); - } -#endif - DEBUGLOG(("ttysl_start: utf8_mode is %s",(utf8_mode) ? "on" : "off")); - sys_signal(SIGCONT, cont); - sys_signal(SIGWINCH, winch); - - driver_select(port, (ErlDrvEvent)(UWord)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; - - DEBUGLOG(("ttysl_start: successful start\n")); - 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 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) -{ - DEBUGLOG(("ttysl_stop: ttysl_port = %d\n",ttysl_port)); - if (ttysl_port != (ErlDrvPort)-1) { - stop_lbuf(); - stop_termcap(); - tty_reset(ttysl_fd); - driver_select(ttysl_port, (ErlDrvEvent)(UWord)ttysl_fd, - ERL_DRV_WRITE|ERL_DRV_READ|ERL_DRV_USE, 0); - sys_signal(SIGCONT, SIG_DFL); - sys_signal(SIGWINCH, SIG_DFL); - } - ttysl_port = (ErlDrvPort)-1; - ttysl_fd = -1; - ttysl_terminate = 0; - /* 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) { - ASSERT(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; - - DEBUGLOG(("check_buf_size: n = %d",n)); - 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(("check_buf_size: Invalid UTF8:%d",ch)); - ++pos; - } - if (utf8_mode) { /* That is, terminal is UTF8 compliant */ - if (ch >= 128 || isprint(ch)) { -#ifdef HAVE_WCWIDTH - int width; -#endif - DEBUGLOG(("check_buf_size: Printable(UTF-8:%d):%d",pos,ch)); - size++; -#ifdef HAVE_WCWIDTH - if ((width = wcwidth(ch)) > 1) { - size += width - 1; - } -#endif - } else if (ch == '\t') { - size += 8; - } else { - DEBUGLOG(("check_buf_size: Magic(UTF-8:%d):%d",pos,ch)); - size += 2; - } - } else { - if (ch <= 255 && isprint(ch)) { - DEBUGLOG(("check_buf_size: Printable:%d",ch)); - size++; - } else if (ch == '\t') - size += 8; - else if (ch >= 128) { - DEBUGLOG(("check_buf_size: Non printable:%d",ch)); - size += (octal_or_hex_positions(ch) + 1); - } - else { - DEBUGLOG(("check_buf_size: 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) { - DEBUGLOG(("check_buf_size: alloc failure of %d bytes", lbuf_size * sizeof(Uint32))); - driver_failure(ttysl_port, -1); - return(0); - } - } - DEBUGLOG(("check_buf_size: success\n")); - return(1); -} - - -static void ttysl_from_erlang(ErlDrvData ttysl_data, char* buf, ErlDrvSizeT count) -{ - ErlDrvSizeT sz; - - sz = driver_sizeq(ttysl_port); - - putclen = count > TTY_BUFFSIZE ? TTY_BUFFSIZE : count; - putcbuf = driver_alloc_binary(putclen); - putcpos = 0; - - if (lpos > MAXSIZE) - put_chars((byte*)"\n", 1); - - DEBUGLOG(("ttysl_from_erlang: OP = %d", buf[0])); - - switch (buf[0]) { - case OP_PUTC_SYNC: - /* Using sync means that we have to send an ok to the - controlling process for each command call. We delay - sending ok if the driver queue exceeds a certain size. - We do not set ourselves as a busy port, as this - could be very bad for user_drv, if it gets blocked on - the port_command. */ - /* fall through */ - case OP_PUTC: - DEBUGLOG(("ttysl_from_erlang: OP: Putc(%lu)",(unsigned 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: - outc('\007'); - break; - default: - /* Unknown op, just ignore. */ - break; - } - - driver_enq_bin(ttysl_port,putcbuf,0,putcpos); - driver_free_binary(putcbuf); - - if (sz == 0) { - for (;;) { - int written, qlen; - SysIOVec *iov; - - iov = driver_peekq(ttysl_port,&qlen); - if (iov) - written = writev(ttysl_fd, iov, qlen > MAXIOV ? MAXIOV : qlen); - else - written = 0; - if (written < 0) { - if (errno == ERRNO_BLOCK || errno == EINTR) { - driver_select(ttysl_port,(ErlDrvEvent)(long)ttysl_fd, - ERL_DRV_USE|ERL_DRV_WRITE,1); - break; - } else { - DEBUGLOG(("ttysl_from_erlang: driver failure in writev(%d,..) = %d (errno = %d)\n", ttysl_fd, written, errno)); - driver_failure_posix(ttysl_port, errno); - return; - } - } else { - if (driver_deq(ttysl_port, written) == 0) - break; - } - } - } - - if (buf[0] == OP_PUTC_SYNC) { - if (driver_sizeq(ttysl_port) > TTY_BUFFSIZE && !ttysl_terminate) { - /* We delay sending the ack until the buffer has been consumed */ - ttysl_send_ok = 1; - } else { - ErlDrvTermData spec[] = { - ERL_DRV_PORT, driver_mk_port(ttysl_port), - ERL_DRV_ATOM, driver_mk_atom("ok"), - ERL_DRV_TUPLE, 2 - }; - ASSERT(ttysl_send_ok == 0); - erl_drv_output_term(driver_mk_port(ttysl_port), spec, - sizeof(spec) / sizeof(spec[0])); - } - } - - return; /* TRUE; */ -} - -static void ttysl_to_tty(ErlDrvData ttysl_data, ErlDrvEvent fd) { - for (;;) { - int written, qlen; - SysIOVec *iov; - ErlDrvSizeT sz; - - iov = driver_peekq(ttysl_port,&qlen); - - DEBUGLOG(("ttysl_to_tty: qlen = %d", qlen)); - - if (iov) - written = writev(ttysl_fd, iov, qlen > MAXIOV ? MAXIOV : qlen); - else - written = 0; - if (written < 0) { - if (errno == EINTR) { - continue; - } else if (errno != ERRNO_BLOCK){ - DEBUGLOG(("ttysl_to_tty: driver failure in writev(%d,..) = %d (errno = %d)\n", ttysl_fd, written, errno)); - driver_failure_posix(ttysl_port, errno); - } - break; - } else { - sz = driver_deq(ttysl_port, written); - if (sz < TTY_BUFFSIZE && ttysl_send_ok) { - ErlDrvTermData spec[] = { - ERL_DRV_PORT, driver_mk_port(ttysl_port), - ERL_DRV_ATOM, driver_mk_atom("ok"), - ERL_DRV_TUPLE, 2 - }; - ttysl_send_ok = 0; - erl_drv_output_term(driver_mk_port(ttysl_port), spec, - sizeof(spec) / sizeof(spec[0])); - } - if (sz == 0) { - driver_select(ttysl_port,(ErlDrvEvent)(long)ttysl_fd, - ERL_DRV_WRITE,0); - if (ttysl_terminate) { - /* flush has been called, which means we should terminate - when queue is empty. This will not send any exit - message */ - DEBUGLOG(("ttysl_to_tty: ttysl_terminate normal\n")); - driver_failure_atom(ttysl_port, "normal"); - } - break; - } - } - } - - return; -} - -static void ttysl_flush_tty(ErlDrvData ttysl_data) { - DEBUGLOG(("ttysl_flush_tty: ..")); - ttysl_terminate = 1; - return; -} - -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; - } - - DEBUGLOG(("ttysl_from_tty: remainder = %d", left)); - - if ((i = read((int)(SWord)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(("ttysl_from_tty: 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 if (errno != EAGAIN && errno != EWOULDBLOCK) { - DEBUGLOG(("ttysl_from_tty: driver failure in read(%d,..) = %d (errno = %d)\n", (int)(SWord)fd, i, errno)); - 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 (lpos > llen) - llen = lpos; - if (n > 0) - write_buf(lbuf + lpos - n, n, 0); - return TRUE; -} - -/* - * Move the current position 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), 0); - 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; - int gcs; /* deleted grapheme characters */ - - update_cols(); - - /* Step forward or backwards over n logical characters. */ - pos = step_over_chars(n); - DEBUGLOG(("del_chars: %d from %d %d %d\n", n, lpos, pos, llen)); - if (pos > lpos) { - l = pos - lpos; /* Buffer characters to delete */ - r = llen - lpos - l; /* Characters after deleted */ - gcs = cp_pos_to_col(pos) - cp_pos_to_col(lpos); - /* Fix up buffer and buffer pointers. */ - if (r > 0) - memmove(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, 0); - for (i = gcs ; i > 0; --i) - outc(' '); - if (xn && COL(cp_pos_to_col(llen)+gcs) == 0) - { - outc(' '); - move_left(1); - } - move_cursor(llen + gcs, lpos); - } - else if (pos < lpos) { - l = lpos - pos; /* Buffer characters */ - r = llen - lpos; /* Characters after deleted */ - gcs = -move_cursor(lpos, lpos-l); /* Move back */ - /* Fix up buffer and buffer pointers. */ - if (r > 0) - memmove(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, 0); - for (i = gcs ; i > 0; --i) - outc(' '); - if (xn && COL(cp_pos_to_col(llen)+gcs) == 0) - { - outc(' '); - move_left(1); - } - move_cursor(llen + gcs, 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 == '\e') { - DEBUGLOG(("insert_buf: ANSI Escape: \\e")); - lbuf[lpos++] = (CONTROL_TAG | ((Uint32) ch)); - } else if (ch == '\n' || ch == '\r') { - write_buf(lbuf + buffpos, lpos - buffpos, 1); - outc('\r'); - if (ch == '\n') - outc('\n'); - if (llen > lpos) { - memmove(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, int next_char_is_crnl) -{ - byte ubuf[4]; - int ubytes = 0, i; - byte lastput = ' '; - - update_cols(); - - DEBUGLOG(("write_buf(%d, %d)",n,next_char_is_crnl)); - - 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 | ((Uint32) '\e'))) { - outc(lastput = '\e'); - --n; - ++s; - } else if (*s & CONTROL_TAG) { - byte c = (byte)*s; - outc('^'); - outc(lastput = ((byte) ((c == 0177 ? '?' : c | 0x40)))); - n -= 2; - s += 2; - } else if (*s & ESCAPED_TAG) { - Uint32 ch = *s & ~(TAG_MASK); - byte *octbuff; - byte octtmp[256]; - int octbytes; - DEBUGLOG(("write_buf: 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(("write_buf: octbytes: %d", octbytes)); - outc('\\'); - for (i = 0; i < octbytes; ++i) { - outc(lastput = octbuff[i]); - DEBUGLOG(("write_buf: outc: %d", (int) lastput)); - } - n -= octbytes+1; - s += octbytes+1; - if (octbuff != octtmp) { - driver_free(octbuff); - } - } else { - DEBUGLOG(("write_buf: Very unexpected character %d",(int) *s)); - ++n; - --s; - } - } - /* Check landed in first column of new line and have 'xn' bug. - * https://www.gnu.org/software/termutils/manual/termcap-1.3/html_node/termcap_27.html - * - * The 'xn' bugs (from what I understand) is that the terminal cursor does - * not wrap to the next line when the current line is full. For example: - * - * If the terminal column size is 20 and we output 20 'a' the cursor will be - * on row 1, column 21. While we actually want it at row 2 column 0. So to - * achieve this the code below emits " \b", which will move the cursor to the - * correct place. - * - * We should not apply this 'xn' workaround if we know that the next character - * to be emitted is a cr|nl as that will wrap by itself. - */ - n = s - lbuf; - if (!next_char_is_crnl && xn && n != 0 && COL(cp_pos_to_col(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) -{ - putcbuf->orig_bytes[putcpos++] = c; - if (putcpos == putclen) { - driver_enq_bin(ttysl_port,putcbuf,0,putclen); - driver_free_binary(putcbuf); - putcpos = 0; - putclen = TTY_BUFFSIZE; - putcbuf = driver_alloc_binary(BUFSIZ); - } - return 1; -} - -static int move_cursor(int from_pos, int to_pos) -{ - int from_col, to_col; - int dc, dl; - update_cols(); - - from_col = cp_pos_to_col(from_pos); - to_col = cp_pos_to_col(to_pos); - - dc = COL(to_col) - COL(from_col); - dl = LINE(to_col) - LINE(from_col); - DEBUGLOG(("move_cursor: from %d %d to %d %d => %d %d\n", - from_pos, from_col, to_pos, to_col, dl, dc)); - 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 to_col-from_col; -} - -/* - * Returns the length of an ANSI escape code in a buffer, this function only consider - * color escape sequences like `\e[33m` or `\e[21;33m`. If a sequence has no valid - * terminator, the length is equal the number of characters between `\e` and the first - * invalid character, inclusive. - */ - -static int ansi_escape_width(Uint32 *s, int max_length) -{ - int i; - - if (*s != (CONTROL_TAG | ((Uint32) '\e'))) { - return 0; - } else if (max_length <= 1) { - return 1; - } else if (s[1] != '[') { - return 2; - } - - for (i = 2; i < max_length && (s[i] == ';' || (s[i] >= '0' && s[i] <= '9')); i++); - - return i + 1; -} - -static int cp_pos_to_col(int cp_pos) -{ - /* - * If we don't have any character width information. Assume that - * code points are one column wide - */ - int w = 1; - int col = 0; - int i = 0; - int j; - - if (cp_pos > llen) { - col += cp_pos - llen; - cp_pos = llen; - } - - while (i < cp_pos) { - j = ansi_escape_width(lbuf + i, llen - i); - - if (j > 0) { - i += j; - } else { -#ifdef HAVE_WCWIDTH - w = wcwidth(lbuf[i]); -#endif - if (w > 0) { - col += w; - } - i++; - } - } - - return col; -} - -static int start_termcap(void) -{ - int eres; - size_t envsz = 1024; - char *env = NULL; - char *c; - int tres; - - DEBUGLOG(("start_termcap: ..")); - - capbuf = driver_alloc(1024); - if (!capbuf) - goto termcap_false; - eres = erl_drv_getenv("TERM", capbuf, &envsz); - if (eres == 0) - env = capbuf; - else if (eres < 0) { - DEBUGLOG(("start_termcap: failure in erl_drv_getenv(\"TERM\", ..) = %d\n", eres)); - goto termcap_false; - } else /* if (eres > 1) */ { - char *envbuf = driver_alloc(envsz); - if (!envbuf) - goto termcap_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) { - DEBUGLOG(("start_termcap: failure in erl_drv_getenv(\"TERM\", ..) = %d or realloc buf == %p\n", eres, newenvbuf)); - env = newenvbuf ? newenvbuf : envbuf; - goto termcap_false; - } - envbuf = newenvbuf; - } - env = envbuf; - } - if ((tres = tgetent((char*)lbuf, env)) <= 0) { - DEBUGLOG(("start_termcap: failure in tgetent(..) = %d\n", tres)); - goto termcap_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) { - DEBUGLOG(("start_termcap: successful start\n")); - return TRUE; - } - DEBUGLOG(("start_termcap: failed start\n")); - termcap_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) { - int tres; - DEBUGLOG(("tty_init: fd = %d, canon = %d, echo = %d, sig = %d", fd, canon, echo, sig)); - if ((tres = tcgetattr(fd, &tty_rmode)) < 0) { - DEBUGLOG(("tty_init: failure in tcgetattr(%d,..) = %d\n", fd, tres)); - 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); - } - DEBUGLOG(("tty_init: successful init\n")); - 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) -{ - int tres; - DEBUGF(("tty_set: Setting tty...\n")); - - if ((tres = tcsetattr(fd, TCSANOW, &tty_smode)) < 0) { - DEBUGLOG(("tty_set: failure in tcgetattr(%d,..) = %d\n", fd, tres)); - return(-1); - } - return(0); -} - -static int tty_reset(int fd) /* of terminal device */ -{ - int tres; - DEBUGF(("tty_reset: Resetting tty...\n")); - - if ((tres = tcsetattr(fd, TCSANOW, &tty_rmode)) < 0) { - DEBUGLOG(("tty_reset: failure in tcsetattr(%d,..) = %d\n", fd, tres)); - return(-1); - } - return(0); -} - -/* - * Signal handler to cope with signals so that we can reset the tty - * to the original settings - */ - -#ifdef ERTS_NOT_USED -/* XXX: A mistake that it isn't used, or should it be removed? */ - -static RETSIGTYPE suspend(int sig) -{ - if (tty_reset(ttysl_fd) < 0) { - DEBUGLOG(("signal: failure in suspend(%d), can't reset tty %d\n", sig, ttysl_fd)); - fprintf(stderr,"Can't reset tty \n"); - exit(1); - } - - sys_signal(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_signal(sig, suspend); /* Reset signal handler */ - - if (tty_set(ttysl_fd) < 0) { - DEBUGLOG(("signal: failure in suspend(%d), can't set tty %d\n", sig, ttysl_fd)); - fprintf(stderr,"Can't set tty raw \n"); - exit(1); - } -} - -#endif - -static RETSIGTYPE cont(int sig) -{ - if (tty_set(ttysl_fd) < 0) { - DEBUGLOG(("signal: failure in cont(%d), can't set tty raw %d\n", sig, ttysl_fd)); - fprintf(stderr,"Can't set tty raw\n"); - exit(1); - } -} - -static RETSIGTYPE winch(int sig) -{ - cols_needs_update = TRUE; -} -#endif /* HAVE_TERMCAP */ diff --git a/erts/emulator/drivers/win32/ttsl_drv.c b/erts/emulator/drivers/win32/ttsl_drv.c deleted file mode 100644 index 8917e48919f6..000000000000 --- a/erts/emulator/drivers/win32/ttsl_drv.c +++ /dev/null @@ -1,786 +0,0 @@ -/* - * %CopyrightBegin% - * - * Copyright Ericsson AB 1996-2021. 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 ((SWord)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 ((SWord)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 immediately. 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 position 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); -} diff --git a/erts/emulator/drivers/win32/win_con.c b/erts/emulator/drivers/win32/win_con.c deleted file mode 100644 index 2e3c12dc58d1..000000000000 --- a/erts/emulator/drivers/win32/win_con.c +++ /dev/null @@ -1,2355 +0,0 @@ -/* - * %CopyrightBegin% - * - * Copyright Ericsson AB 1997-2021. 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% - */ - -#define UNICODE 1 -#define _UNICODE 1 -#include -#include -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif -#include "sys.h" -#include -#include "resource.h" -#include "erl_version.h" -#include -#include -#include "erl_driver.h" -#include "win_con.h" - -#define ALLOC(X) malloc(X) -#define REALLOC(X,Y) realloc(X,Y) -#define FREE(X) free(X) - -#if SIZEOF_VOID_P == 8 -#define WIN64 1 -#ifndef GCL_HBRBACKGROUND -#define GCL_HBRBACKGROUND GCLP_HBRBACKGROUND -#endif -#define DIALOG_PROC_RET INT_PTR -#define CF_HOOK_RET INT_PTR -#define CC_HOOK_RET INT_PTR -#define OFN_HOOK_RET INT_PTR -#else -#define DIALOG_PROC_RET BOOL -#define CF_HOOK_RET UINT -#define CC_HOOK_RET UINT -#define OFN_HOOK_RET UINT -#endif - - -#ifndef STATE_SYSTEM_INVISIBLE -/* Mingw problem with oleacc.h and WIN32_LEAN_AND_MEAN */ -#define STATE_SYSTEM_INVISIBLE 0x00008000 -#endif - -#define WM_CONTEXT (0x0401) -#define WM_CONBEEP (0x0402) -#define WM_SAVE_PREFS (0x0403) - -#define USER_KEY TEXT("Software\\Ericsson\\Erlang\\") TEXT(ERLANG_VERSION) - -#define FRAME_HEIGHT ((2*GetSystemMetrics(SM_CYEDGE))+(2*GetSystemMetrics(SM_CYFRAME))+GetSystemMetrics(SM_CYCAPTION)) -#define FRAME_WIDTH (2*GetSystemMetrics(SM_CXFRAME)+(2*GetSystemMetrics(SM_CXFRAME))+GetSystemMetrics(SM_CXVSCROLL)) - -#define LINE_LENGTH canvasColumns -#define COL(_l) ((_l) % LINE_LENGTH) -#define LINE(_l) ((_l) / LINE_LENGTH) - -#ifdef UNICODE -/* - * We use a character in the invalid unicode range - */ -#define SET_CURSOR (0xD8FF) -#else -/* - * XXX There is no escape to send a character 0x80. Fortunately, - * the ttsl driver currently replaces 0x80 with an octal sequence. - */ -#define SET_CURSOR (0x80) -#endif - -#define SCAN_CODE_BREAK 0x46 /* scan code for Ctrl-Break */ - - -typedef struct ScreenLine_s { - struct ScreenLine_s* next; - struct ScreenLine_s* prev; - int width; -#ifdef HARDDEBUG - int allocated; -#endif - int newline; /* Ends with hard newline: 1, wrapped at end: 0 */ - TCHAR *text; -} ScreenLine_t; - -extern Uint32 *lbuf; /* The current line buffer */ -extern int llen; /* The current line length */ -extern int lpos; - -HANDLE console_input_event; -HANDLE console_thread = NULL; - -#define DEF_CANVAS_COLUMNS 80 -#define DEF_CANVAS_ROWS 26 - -#define BUFSIZE 4096 -#define MAXBUFSIZE 32768 -typedef struct { - TCHAR *data; - int size; - int wrPos; - int rdPos; -} buffer_t; - -static buffer_t inbuf; -static buffer_t outbuf; - -static CHOOSEFONT cf; - -static TCHAR szFrameClass[] = TEXT("FrameClass"); -static TCHAR szClientClass[] = TEXT("ClientClass"); -static HWND hFrameWnd; -static HWND hClientWnd; -static HWND hTBWnd; -static HWND hComboWnd; -static HANDLE console_input; -static HANDLE console_output; -static int cxChar,cyChar, cxCharMax; -static int cxClient,cyClient; -static int cyToolBar; -static int iVscrollPos,iHscrollPos; -static int iVscrollMax,iHscrollMax; -static int nBufLines; -static int cur_x; -static int cur_y; -static int canvasColumns = DEF_CANVAS_COLUMNS; -static int canvasRows = DEF_CANVAS_ROWS; -static ScreenLine_t *buffer_top,*buffer_bottom; -static ScreenLine_t* cur_line; -static POINT editBeg,editEnd; -static BOOL fSelecting = FALSE; -static BOOL fTextSelected = FALSE; -static HKEY key; -static BOOL has_key = FALSE; -static LOGFONT logfont; -static DWORD fgColor; -static DWORD bkgColor; -static FILE *logfile = NULL; -static RECT winPos; -static BOOL toolbarVisible; -static BOOL destroyed = FALSE; - -static int lines_to_save = 10000; /* Maximum number of screen lines to save. */ - -#define TITLE_BUF_SZ 256 - -struct title_buf { - TCHAR *name; - TCHAR buf[TITLE_BUF_SZ]; -}; - -static TCHAR *erlang_window_title = TEXT("Erlang"); - -static unsigned __stdcall ConThreadInit(LPVOID param); -static LRESULT CALLBACK ClientWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam); -static LRESULT CALLBACK FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam); -static DIALOG_PROC_RET CALLBACK AboutDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam); -static ScreenLine_t *ConNewLine(void); -static void DeleteTopLine(void); -static void ensure_line_below(void); -static ScreenLine_t *GetLineFromY(int y); -static void LoadUserPreferences(void); -static void SaveUserPreferences(void); -static void set_scroll_info(HWND hwnd); -static void ConCarriageFeed(int); -static void ConScrollScreen(void); -static BOOL ConChooseFont(HWND hwnd); -static void ConFontInitialize(HWND hwnd); -static void ConSetFont(HWND hwnd); -static void ConChooseColor(HWND hwnd); -static void DrawSelection(HWND hwnd, POINT pt1, POINT pt2); -static void InvertSelectionArea(HWND hwnd); -static void OnEditCopy(HWND hwnd); -static void OnEditPaste(HWND hwnd); -static void OnEditSelAll(HWND hwnd); -static void GetFileName(HWND hwnd, TCHAR *pFile); -static void OpenLogFile(HWND hwnd); -static void CloseLogFile(HWND hwnd); -static void LogFileWrite(TCHAR *buf, int n); -static int write_inbuf(TCHAR *data, int n); -static void init_buffers(void); -static void AddToCmdHistory(void); -static int write_outbuf(TCHAR *data, int num_chars); -static void ConDrawText(HWND hwnd); -static BOOL (WINAPI *ctrl_handler)(DWORD); -static HWND InitToolBar(HWND hwndParent); -static void window_title(struct title_buf *); -static void free_window_title(struct title_buf *); -static void Client_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags); - -#ifdef HARDDEBUG -/* For really hard GUI startup debugging, place DEBUGBOX() macros in code - and get modal message boxes with the line number. */ -static void debug_box(int line) { - TCHAR buff[1024]; - swprintf(buff,1024,TEXT("DBG:%d"),line); - MessageBox(NULL,buff,TEXT("DBG"),MB_OK|MB_APPLMODAL); -} - -#define DEBUGBOX() debug_box(__LINE__) -#endif - -#define CON_VPRINTF_BUF_INC_SIZE 1024 - -static erts_dsprintf_buf_t * -grow_con_vprintf_buf(erts_dsprintf_buf_t *dsbufp, size_t need) -{ - char *buf; - size_t size; - - ASSERT(dsbufp); - - if (!dsbufp->str) { - size = (((need + CON_VPRINTF_BUF_INC_SIZE - 1) - / CON_VPRINTF_BUF_INC_SIZE) - * CON_VPRINTF_BUF_INC_SIZE); - buf = (char *) ALLOC(size * sizeof(char)); - } - else { - size_t free_size = dsbufp->size - dsbufp->str_len; - - if (need <= free_size) - return dsbufp; - - size = need - free_size + CON_VPRINTF_BUF_INC_SIZE; - size = (((size + CON_VPRINTF_BUF_INC_SIZE - 1) - / CON_VPRINTF_BUF_INC_SIZE) - * CON_VPRINTF_BUF_INC_SIZE); - size += dsbufp->size; - buf = (char *) REALLOC((void *) dsbufp->str, - size * sizeof(char)); - } - if (!buf) - return NULL; - if (buf != dsbufp->str) - dsbufp->str = buf; - dsbufp->size = size; - return dsbufp; -} - -static int con_vprintf(char *format, va_list arg_list) -{ - int res,i; - erts_dsprintf_buf_t dsbuf = ERTS_DSPRINTF_BUF_INITER(grow_con_vprintf_buf); - res = erts_vdsprintf(&dsbuf, format, arg_list); - if (res >= 0) { - TCHAR *tmp = ALLOC(dsbuf.str_len*sizeof(TCHAR)); - for (i=0;iwidth < xpos) { - return (canvasColumns-hscroll)*cxChar; - } - /* Not needed (?): SelectObject(hdc,CreateFontIndirect(&logfont)); */ - if (GetTextExtentPoint32(hdc,pLine->text,xpos,&size)) { -#ifdef HARDDEBUG - fprintf(stderr,"size.cx:%d\n",(int)size.cx); - fflush(stderr); -#endif - if (hscrollPix >= size.cx) { - return 0; - } - return ((int) size.cx) - hscrollPix; - } else { - return (xpos-hscroll)*cxChar; - } -} - -static int GetXFromCurrentY(HDC hdc, int hscroll, int xpos) { - return GetXFromLine(hdc, hscroll, xpos, GetLineFromY(cur_y)); -} - -void ConSetCursor(int from, int to) -{ TCHAR cmd[9]; - int *p; - //DebugBreak(); - cmd[0] = SET_CURSOR; - /* - * XXX Expect trouble on CPUs which don't allow misaligned read and writes. - */ - p = (int *)&cmd[1]; - *p++ = from; - *p = to; - write_outbuf(cmd, 1 + (2*sizeof(int)/sizeof(TCHAR))); -} - -void ConPrintf(char *format, ...) -{ - va_list va; - - va_start(va, format); - (void) con_vprintf(format, va); - va_end(va); -} - -void ConBeep(void) -{ - SendMessage(hClientWnd, WM_CONBEEP, 0L, 0L); -} - -int ConReadInput(Uint32 *data, int num_chars) -{ - TCHAR *buf; - int nread; - WaitForSingleObject(console_input,INFINITE); - nread = num_chars = min(num_chars,inbuf.wrPos-inbuf.rdPos); - buf = &inbuf.data[inbuf.rdPos]; - inbuf.rdPos += nread; - while (nread--) - *data++ = *buf++; - if (inbuf.rdPos >= inbuf.wrPos) { - inbuf.rdPos = 0; - inbuf.wrPos = 0; - ResetEvent(console_input_event); - } - ReleaseSemaphore(console_input,1,NULL); - return num_chars; -} - -int ConGetKey(void) -{ - Uint32 c; - WaitForSingleObject(console_input,INFINITE); - ResetEvent(console_input_event); - inbuf.rdPos = inbuf.wrPos = 0; - ReleaseSemaphore(console_input,1,NULL); - WaitForSingleObject(console_input_event,INFINITE); - ConReadInput(&c, 1); - return (int) c; -} - -int ConGetColumns(void) -{ - return (int) canvasColumns; /* 32bit atomic on windows */ -} - -int ConGetRows(void) { - return (int) canvasRows; -} - - -static HINSTANCE hInstance; -extern HMODULE beam_module; - -static unsigned __stdcall -ConThreadInit(LPVOID param) -{ - MSG msg; - WNDCLASSEX wndclass; - int iCmdShow; - STARTUPINFO StartupInfo; - HACCEL hAccel; - int x, y, w, h; - struct title_buf title; - - /*DebugBreak();*/ -#ifdef HARDDEBUG - if(AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) { - freopen("CONOUT$", "w", stdout); - freopen("CONOUT$", "w", stderr); - } -#endif - - hInstance = GetModuleHandle(NULL); - StartupInfo.dwFlags = 0; - GetStartupInfo(&StartupInfo); - iCmdShow = StartupInfo.dwFlags & STARTF_USESHOWWINDOW ? - StartupInfo.wShowWindow : SW_SHOWDEFAULT; - - LoadUserPreferences(); - - /* frame window class */ - wndclass.cbSize = sizeof (wndclass); - wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNCLIENT; - wndclass.lpfnWndProc = FrameWndProc; - wndclass.cbClsExtra = 0; - wndclass.cbWndExtra = 0; - wndclass.hInstance = hInstance; - wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(1)); - wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); - wndclass.hbrBackground = NULL; - wndclass.lpszMenuName = NULL; - wndclass.lpszClassName = szFrameClass; - wndclass.hIconSm = LoadIcon (hInstance, MAKEINTRESOURCE(1)); - RegisterClassExW (&wndclass); - - /* client window class */ - wndclass.cbSize = sizeof (wndclass); - wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; - wndclass.lpfnWndProc = ClientWndProc; - wndclass.cbClsExtra = 0; - wndclass.cbWndExtra = 0; - wndclass.hInstance = hInstance; - wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(1)); - wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); - wndclass.hbrBackground = CreateSolidBrush(bkgColor); - wndclass.lpszMenuName = NULL; - wndclass.lpszClassName = szClientClass; - wndclass.hIconSm = LoadIcon (hInstance, MAKEINTRESOURCE(1)); - RegisterClassExW (&wndclass); - - InitCommonControls(); - init_buffers(); - - nBufLines = 0; - buffer_top = cur_line = ConNewLine(); - cur_line->next = buffer_bottom = ConNewLine(); - buffer_bottom->prev = cur_line; - - /* Create Frame Window */ - window_title(&title); - hFrameWnd = CreateWindowEx(0, szFrameClass, title.name, - WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, - CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, - NULL,LoadMenu(beam_module,MAKEINTRESOURCE(1)), - hInstance,NULL); - free_window_title(&title); - - /* XXX OTP-5522: - The window position is not saved correctly and if the window - is closed when minimized, it's not possible to start werl again - with the window open. Temporary fix so far is to ignore saved values - and always start with initial settings. */ - /* Original: if (winPos.left == -1) { */ - /* Temporary: if (1) { */ - if (1) { - - /* initial window position */ - x = 0; - y = 0; - w = cxChar*LINE_LENGTH+FRAME_WIDTH+GetSystemMetrics(SM_CXVSCROLL); - h = cyChar*30+FRAME_HEIGHT; - } else { - /* saved window position */ - x = winPos.left; - y = winPos.top; - w = winPos.right - x; - h = winPos.bottom - y; - } - SetWindowPos(hFrameWnd, NULL, x, y, w, h, SWP_NOZORDER); - - ShowWindow(hFrameWnd, iCmdShow); - UpdateWindow(hFrameWnd); - - hAccel = LoadAccelerators(beam_module,MAKEINTRESOURCE(1)); - - ReleaseSemaphore(console_input, 1, NULL); - ReleaseSemaphore(console_output, 1, NULL); - - - /* Main message loop */ - while (GetMessage (&msg, NULL, 0, 0)) - { - if (!TranslateAccelerator(hFrameWnd,hAccel,&msg)) - { - TranslateMessage (&msg); - DispatchMessage (&msg); - } - } - /* - PostQuitMessage() results in WM_QUIT which makes GetMessage() - return 0 (which stops the main loop). Before we return from - the console thread, the ctrl_handler is called to do erts_exit. - */ - (*ctrl_handler)(CTRL_CLOSE_EVENT); - return msg.wParam; -} - -static LRESULT CALLBACK -FrameWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) -{ - RECT r; - int cy,i,bufsize; - TCHAR c; - unsigned long l; - TCHAR buf[128]; - struct title_buf title; - - switch (iMsg) { - case WM_CREATE: - /* client window creation */ - window_title(&title); - hClientWnd = CreateWindowEx(0, szClientClass, title.name, - WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL, - CW_USEDEFAULT, CW_USEDEFAULT, - CW_USEDEFAULT, CW_USEDEFAULT, - hwnd, (HMENU)0, hInstance, NULL); - free_window_title(&title); - hTBWnd = InitToolBar(hwnd); - UpdateWindow (hClientWnd); - return 0; - case WM_SIZE : - if (IsWindowVisible(hTBWnd)) { - SendMessage(hTBWnd,TB_AUTOSIZE,0,0L); - GetWindowRect(hTBWnd,&r); - cy = r.bottom-r.top; - } else cy = 0; - MoveWindow(hClientWnd,0,cy,LOWORD(lParam),HIWORD(lParam)-cy,TRUE); - return 0; - case WM_ERASEBKGND: - return 1; - case WM_SETFOCUS : - CreateCaret(hClientWnd, NULL, cxChar, cyChar); - SetCaretPos(GetXFromCurrentY(GetDC(hClientWnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); - ShowCaret(hClientWnd); - return 0; - case WM_KILLFOCUS: - HideCaret(hClientWnd); - DestroyCaret(); - return 0; - case WM_INITMENUPOPUP : - if (lParam == 0) /* File popup menu */ - { - EnableMenuItem((HMENU)wParam, IDMENU_STARTLOG, - logfile ? MF_GRAYED : MF_ENABLED); - EnableMenuItem((HMENU)wParam, IDMENU_STOPLOG, - logfile ? MF_ENABLED : MF_GRAYED); - return 0; - } - else if (lParam == 1) /* Edit popup menu */ - { - EnableMenuItem((HMENU)wParam, IDMENU_COPY, - fTextSelected ? MF_ENABLED : MF_GRAYED); - EnableMenuItem((HMENU)wParam, IDMENU_PASTE, - IsClipboardFormatAvailable(CF_TEXT) ? MF_ENABLED : MF_GRAYED); - return 0; - } - else if (lParam == 3) /* View popup menu */ - { - CheckMenuItem((HMENU)wParam,IDMENU_TOOLBAR, - IsWindowVisible(hTBWnd) ? MF_CHECKED : MF_UNCHECKED); - return 0; - } - break; - case WM_NOTIFY: - switch (((LPNMHDR) lParam)->code) { - case TTN_NEEDTEXT: - { - LPTOOLTIPTEXT lpttt; - lpttt = (LPTOOLTIPTEXT) lParam; - lpttt->hinst = hInstance; - /* check for combobox handle */ - if (lpttt->uFlags&TTF_IDISHWND) { - if ((lpttt->hdr.idFrom == (UINT_PTR) hComboWnd)) { - lstrcpy(lpttt->lpszText,TEXT("Command History")); - break; - } - } - /* check for toolbar buttons */ - switch (lpttt->hdr.idFrom) { - case IDMENU_COPY: - lstrcpy(lpttt->lpszText,TEXT("Copy (Ctrl+C)")); - break; - case IDMENU_PASTE: - lstrcpy(lpttt->lpszText,TEXT("Paste (Ctrl+V)")); - break; - case IDMENU_FONT: - lstrcpy(lpttt->lpszText,TEXT("Fonts")); - break; - case IDMENU_ABOUT: - lstrcpy(lpttt->lpszText,TEXT("Help")); - break; - } - } - } - break; - case WM_COMMAND: - switch(LOWORD(wParam)) - { - case IDMENU_STARTLOG: - OpenLogFile(hwnd); - return 0; - case IDMENU_STOPLOG: - CloseLogFile(hwnd); - return 0; - case IDMENU_EXIT: - SendMessage(hwnd, WM_CLOSE, 0, 0L); - return 0; - case IDMENU_COPY: - if (fTextSelected) - OnEditCopy(hClientWnd); - return 0; - case IDMENU_PASTE: - OnEditPaste(hClientWnd); - return 0; - case IDMENU_SELALL: - OnEditSelAll(hClientWnd); - return 0; - case IDMENU_FONT: - if (ConChooseFont(hClientWnd)) { - ConSetFont(hClientWnd); - } - SaveUserPreferences(); - return 0; - case IDMENU_SELECTBKG: - ConChooseColor(hClientWnd); - SaveUserPreferences(); - return 0; - case IDMENU_TOOLBAR: - if (toolbarVisible) { - ShowWindow(hTBWnd,SW_HIDE); - toolbarVisible = FALSE; - } else { - ShowWindow(hTBWnd,SW_SHOW); - toolbarVisible = TRUE; - } - GetClientRect(hwnd,&r); - PostMessage(hwnd,WM_SIZE,0,MAKELPARAM(r.right,r.bottom)); - return 0; - case IDMENU_ABOUT: - DialogBox(beam_module,TEXT("AboutBox"),hwnd,AboutDlgProc); - return 0; - case ID_COMBOBOX: - switch (HIWORD(wParam)) { - case CBN_SELENDOK: - i = SendMessage(hComboWnd,CB_GETCURSEL,0,0); - if (i != CB_ERR) { - buf[0] = 0x01; /* CTRL+A */ - buf[1] = 0x0B; /* CTRL+K */ - bufsize = SendMessage(hComboWnd,CB_GETLBTEXT,i,(LPARAM)&buf[2]); - if (bufsize != CB_ERR) - write_inbuf(buf,bufsize+2); - SetFocus(hwnd); - } - break; - case CBN_SELENDCANCEL: - break; - } - break; - case ID_BREAK: /* CTRL+BRK */ - /* pass on break char if the ctrl_handler is disabled */ - if ((*ctrl_handler)(CTRL_C_EVENT) == FALSE) { - c = 0x03; - write_inbuf(&c,1); - } - return 0; - } - break; - case WM_KEYDOWN : - switch (wParam) { - case VK_UP: c = 'P'-'@'; break; - case VK_DOWN : c = 'N'-'@'; break; - case VK_RIGHT : c = 'F'-'@'; break; - case VK_LEFT : c = 'B'-'@'; break; - case VK_DELETE : c = 'D' -'@'; break; - case VK_HOME : c = 'A'-'@'; break; - case VK_END : c = 'E'-'@'; break; - case VK_RETURN : AddToCmdHistory(); return 0; - case VK_PRIOR : /* PageUp */ - PostMessage(hClientWnd, WM_VSCROLL, SB_PAGEUP, 0); - return 0; - case VK_NEXT : /* PageDown */ - PostMessage(hClientWnd, WM_VSCROLL, SB_PAGEDOWN, 0); - return 0; - default: return 0; - } - write_inbuf(&c, 1); - return 0; - case WM_MOUSEWHEEL: - { - int delta = GET_WHEEL_DELTA_WPARAM(wParam); - if (delta < 0) { - PostMessage(hClientWnd, WM_VSCROLL, MAKELONG(SB_THUMBTRACK, - (iVscrollPos + 5)),0); - } else { - WORD pos = ((iVscrollPos - 5) < 0) ? 0 : (iVscrollPos - 5); - PostMessage(hClientWnd, WM_VSCROLL, MAKELONG(SB_THUMBTRACK,pos),0); - } - return 0; - } - case WM_CHAR: - c = (TCHAR)wParam; - write_inbuf(&c,1); - return 0; - case WM_CLOSE : - break; - case WM_DESTROY : - SaveUserPreferences(); - destroyed = TRUE; - PostQuitMessage(0); - return 0; - case WM_SAVE_PREFS : - SaveUserPreferences(); - return 0; - } - return DefWindowProc(hwnd, iMsg, wParam, lParam); -} - -static BOOL -Client_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) -{ - ConFontInitialize(hwnd); - cur_x = cur_y = 0; - iVscrollPos = 0; - iHscrollPos = 0; - return TRUE; -} - -static void -Client_OnPaint(HWND hwnd) -{ - ScreenLine_t *pLine; - int x,y,i,iTop,iBot; - PAINTSTRUCT ps; - RECT rcInvalid; - HDC hdc; - - hdc = BeginPaint(hwnd, &ps); - rcInvalid = ps.rcPaint; - hdc = ps.hdc; - iTop = max(0, iVscrollPos + rcInvalid.top/cyChar); - iBot = min(nBufLines, iVscrollPos + rcInvalid.bottom/cyChar+1); - pLine = GetLineFromY(iTop); - for (i = iTop; i < iBot && pLine != NULL; i++) { - y = cyChar*(i-iVscrollPos); - x = -cxChar*iHscrollPos; - TextOut(hdc, x, y, &pLine->text[0], pLine->width); - pLine = pLine->next; - } - if (fTextSelected || fSelecting) { - InvertSelectionArea(hwnd); - } - SetCaretPos(GetXFromCurrentY(hdc,iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); - EndPaint(hwnd, &ps); -} -#ifdef HARDDEBUG -static void dump_linebufs(void) { - char *buff; - ScreenLine_t *s = buffer_top; - fprintf(stderr,"LinebufDump------------------------\n"); - while(s) { - if (s == buffer_top) fprintf(stderr,"BT-> "); - if (s == buffer_bottom) fprintf(stderr,"BB-> "); - if (s == cur_line) fprintf(stderr,"CL-> "); - - buff = (char *) ALLOC(s->width+1); - memcpy(buff,s->text,s->width); - buff[s->width] = '\0'; - fprintf(stderr,"{\"%s\",%d,%d}\n",buff,s->newline,s->allocated); - FREE(buff); - s = s->next; - } - fprintf(stderr,"LinebufDumpEnd---------------------\n"); - fflush(stderr); -} -#endif - -static void reorganize_linebufs(HWND hwnd) { - ScreenLine_t *otop = buffer_top; - ScreenLine_t *obot = buffer_bottom; - ScreenLine_t *next; - int i,cpos; - - cpos = 0; - i = nBufLines - cur_y; - while (i > 1) { - cpos += obot->width; - obot = obot->prev; - i--; - } - cpos += (obot->width - cur_x); -#ifdef HARDDEBUG - fprintf(stderr,"nBufLines = %d, cur_x = %d, cur_y = %d, cpos = %d\n", - nBufLines,cur_x,cur_y,cpos); - fflush(stderr); -#endif - - - nBufLines = 0; - buffer_top = cur_line = ConNewLine(); - cur_line->next = buffer_bottom = ConNewLine(); - buffer_bottom->prev = cur_line; - - cur_x = cur_y = 0; - iVscrollPos = 0; - iHscrollPos = 0; - - while(otop) { - for(i=0;iwidth;++i) { - cur_line->text[cur_x] = otop->text[i]; - cur_x++; - if (cur_x > cur_line->width) - cur_line->width = cur_x; - if (GetXFromCurrentY(GetDC(hwnd),0,cur_x) + cxChar > - (LINE_LENGTH * cxChar)) { - ConCarriageFeed(0); - } - } - if (otop->newline) { - ConCarriageFeed(1); - /*ConScrollScreen();*/ - } - next = otop->next; - FREE(otop->text); - FREE(otop); - otop = next; - } - while (cpos) { - cur_x--; - if (cur_x < 0) { - cur_y--; - cur_line = cur_line->prev; - cur_x = cur_line->width-1; - } - cpos--; - } - SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); -#ifdef HARDDEBUG - fprintf(stderr,"canvasColumns = %d,nBufLines = %d, cur_x = %d, cur_y = %d\n", - canvasColumns,nBufLines,cur_x,cur_y); - fflush(stderr); -#endif -} - - -static void -Client_OnSize(HWND hwnd, UINT state, int cx, int cy) -{ - RECT r; - SCROLLBARINFO sbi; - int w,h,columns; - int scrollheight; - cxClient = cx; - cyClient = cy; - set_scroll_info(hwnd); - GetClientRect(hwnd,&r); - w = r.right - r.left; - h = r.bottom - r.top; - sbi.cbSize = sizeof(SCROLLBARINFO); - if (!GetScrollBarInfo(hwnd, OBJID_HSCROLL,&sbi) || - (sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE)) { - scrollheight = 0; - } else { - scrollheight = sbi.rcScrollBar.bottom - sbi.rcScrollBar.top; - } - canvasRows = (h - scrollheight) / cyChar; - if (canvasRows < DEF_CANVAS_ROWS) { - canvasRows = DEF_CANVAS_ROWS; - } - columns = (w - GetSystemMetrics(SM_CXVSCROLL)) /cxChar; - if (columns < DEF_CANVAS_COLUMNS) - columns = DEF_CANVAS_COLUMNS; - if (columns != canvasColumns) { - canvasColumns = columns; - /*dump_linebufs();*/ - reorganize_linebufs(hwnd); - fSelecting = fTextSelected = FALSE; - InvalidateRect(hwnd, NULL, TRUE); -#ifdef HARDDEBUG - fprintf(stderr,"Paint: cols = %d, rows = %d\n",canvasColumns,canvasRows); - fflush(stderr); -#endif - } - - SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); -} - -static void calc_charpoint_from_point(HDC dc, int x, int y, int y_offset, POINT *pt) -{ - int r; - int hscrollPix = iHscrollPos * cxChar; - - pt->y = y/cyChar + iVscrollPos + y_offset; - - if (x > (LINE_LENGTH-iHscrollPos) * cxChar) { - x = (LINE_LENGTH-iHscrollPos) * cxChar; - } - if (pt->y - y_offset > 0 && GetLineFromY(pt->y - y_offset) == NULL) { - pt->y = nBufLines - 1 + y_offset; - pt->x = GetLineFromY(pt->y - y_offset)->width; - } else { - for (pt->x = 1; - (r = GetXFromLine(dc, 0, pt->x, GetLineFromY(pt->y - y_offset))) != 0 && - (r - hscrollPix) < x; - ++(pt->x)) - ; - if ((r - hscrollPix) > x) - --(pt->x); -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"pt->x = %d, iHscrollPos = %d\n",(int) pt->x, iHscrollPos); - fflush(stderr); -#endif - if (pt->x <= 0) { - pt->x = x/cxChar + iHscrollPos; - } - } -} - - -static void -Client_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) -{ - int r; - SetFocus(GetParent(hwnd)); /* In case combobox steals the focus */ -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"OnLButtonDown fSelecting = %d, fTextSelected = %d:\n", - fSelecting,fTextSelected); - fflush(stderr); -#endif - if (fTextSelected) { - InvertSelectionArea(hwnd); - } - fTextSelected = FALSE; - - calc_charpoint_from_point(GetDC(hwnd), x, y, 0, &editBeg); - - editEnd.x = editBeg.x; - editEnd.y = editBeg.y + 1; - fSelecting = TRUE; - SetCapture(hwnd); -} - -static void -Client_OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) -{ - if (fTextSelected) { - fSelecting = TRUE; - Client_OnMouseMove(hwnd,x,y,keyFlags); - fSelecting = FALSE; - } -} - -static void -Client_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags) -{ -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"OnLButtonUp fSelecting = %d, fTextSelected = %d:\n", - fSelecting,fTextSelected); - fprintf(stderr,"(Beg.x = %d, Beg.y = %d, " - "End.x = %d, End.y = %d)\n",editBeg.x,editBeg.y, - editEnd.x,editEnd.y); -#endif - if (fSelecting && - !(editBeg.x == editEnd.x && editBeg.y == (editEnd.y - 1))) { - fTextSelected = TRUE; - } -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"OnLButtonUp fTextSelected = %d:\n", - fTextSelected); - fflush(stderr); -#endif - fSelecting = FALSE; - ReleaseCapture(); -} - -#define EMPTY_RECT(R) \ -(((R).bottom - (R).top == 0) || ((R).right - (R).left == 0)) -#define ABS(X) (((X)< 0) ? -1 * (X) : X) -#define DIFF(A,B) ABS(((int)(A)) - ((int)(B))) - -static int diff_sel_area(RECT old[3], RECT new[3], RECT result[6]) -{ - int absposold = old[0].left + old[0].top * canvasColumns; - int absposnew = new[0].left + new[0].top * canvasColumns; - int absendold = absposold, absendnew = absposnew; - int i, x, ret = 0; - int abspos[2],absend[2]; - for(i = 0; i < 3; ++i) { - if (!EMPTY_RECT(old[i])) { - absendold += (old[i].right - old[i].left) * - (old[i].bottom - old[i].top); - } - if (!EMPTY_RECT(new[i])) { - absendnew += (new[i].right - new[i].left) * - (new[i].bottom - new[i].top); - } - } - abspos[0] = min(absposold, absposnew); - absend[0] = DIFF(absposold, absposnew) + abspos[0]; - abspos[1] = min(absendold, absendnew); - absend[1] = DIFF(absendold, absendnew) + abspos[1]; -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"abspos[0] = %d, absend[0] = %d, abspos[1] = %d, absend[1] = %d\n",abspos[0],absend[0],abspos[1],absend[1]); - fflush(stderr); -#endif - i = 0; - for (x = 0; x < 2; ++x) { - if (abspos[x] != absend[x]) { - int consumed = 0; - result[i].left = abspos[x] % canvasColumns; - result[i].top = abspos[x] / canvasColumns; - result[i].bottom = result[i].top + 1; - if ((absend[x] - abspos[x]) + result[i].left < canvasColumns) { -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"Nowrap, %d < canvasColumns\n", - (absend[x] - abspos[x]) + result[i].left); - fflush(stderr); -#endif - result[i].right = (absend[x] - abspos[x]) + result[i].left; - consumed += result[i].right - result[i].left; - } else { -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"Wrap, %d >= canvasColumns\n", - (absend[x] - abspos[x]) + result[i].left); - fflush(stderr); -#endif - result[i].right = canvasColumns; - consumed += result[i].right - result[i].left; - if (absend[x] - abspos[x] - consumed >= canvasColumns) { - ++i; - result[i].top = result[i-1].bottom; - result[i].left = 0; - result[i].right = canvasColumns; - result[i].bottom = (absend[x] - abspos[x] - consumed) / canvasColumns + result[i].top; - consumed += (result[i].bottom - result[i].top) * canvasColumns; - } - if (absend[x] - abspos[x] - consumed > 0) { - ++i; - result[i].top = result[i-1].bottom; - result[i].bottom = result[i].top + 1; - result[i].left = 0; - result[i].right = absend[x] - abspos[x] - consumed; - } - } - ++i; - } - } -#ifdef HARD_SEL_DEBUG - if (i > 2) { - int x; - fprintf(stderr,"i = %d\n",i); - fflush(stderr); - for (x = 0; x < i; ++x) { - fprintf(stderr, "result[%d]: top = %d, left = %d, " - "bottom = %d. right = %d\n", - x, result[x].top, result[x].left, - result[x].bottom, result[x].right); - } - } -#endif - return i; -} - - - -static void calc_sel_area(RECT rects[3], POINT beg, POINT end) -{ - /* These are not really rects and points, these are character - based positions, need to be multiplied by cxChar and cyChar to - make up canvas coordinates */ - memset(rects,0,3*sizeof(RECT)); - rects[0].left = beg.x; - rects[0].top = beg.y; - rects[0].bottom = beg.y+1; - if (end.y - beg.y == 1) { /* Only one row */ - rects[0].right = end.x; - goto out; - } - rects[0].right = canvasColumns; - if (end.y - beg.y > 2) { - rects[1].left = 0; - rects[1].top = rects[0].bottom; - rects[1].right = canvasColumns; - rects[1].bottom = end.y - 1; - } - rects[2].left = 0; - rects[2].top = end.y - 1; - rects[2].bottom = end.y; - rects[2].right = end.x; - - out: -#ifdef HARD_SEL_DEBUG - { - int i; - fprintf(stderr,"beg.x = %d, beg.y = %d, end.x = %d, end.y = %d\n", - beg.x,beg.y,end.x,end.y); - for (i = 0; i < 3; ++i) { - fprintf(stderr,"[%d] left = %d, top = %d, " - "right = %d, bottom = %d\n", - i, rects[i].left, rects[i].top, - rects[i].right, rects[i].bottom); - } - fflush(stderr); - } -#endif - return; -} - -static void calc_sel_area_turned(RECT rects[3], POINT eBeg, POINT eEnd) { - POINT from,to; - if (eBeg.y >= eEnd.y || - (eBeg.y == eEnd.y - 1 && eBeg.x > eEnd.x)) { -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"Reverting (Beg.x = %d, Beg.y = %d, " - "End.x = %d, End.y = %d)\n",eBeg.x,eBeg.y, - eEnd.x,eEnd.y); - fflush(stderr); -#endif - from.x = eEnd.x; - from.y = eEnd.y - 1; - to.x = eBeg.x; - to.y = eBeg.y + 1; - calc_sel_area(rects,from,to); - } else { - calc_sel_area(rects,eBeg,eEnd); - } -} - - -static void InvertSelectionArea(HWND hwnd) -{ - RECT rects[3]; - POINT from,to; - int i; - calc_sel_area_turned(rects,editBeg,editEnd); - for (i = 0; i < 3; ++i) { - if (!EMPTY_RECT(rects[i])) { - from.x = rects[i].left; - to.x = rects[i].right; - from.y = rects[i].top; - to.y = rects[i].bottom; - DrawSelection(hwnd,from,to); - } - } -} - -static void -Client_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags) -{ - if (fSelecting) { - RECT rold[3], rnew[3], rupdate[6]; - int num_updates,i,r; - POINT from,to; - calc_sel_area_turned(rold,editBeg,editEnd); - - calc_charpoint_from_point(GetDC(hwnd), x, y, 1, &editEnd); - - calc_sel_area_turned(rnew,editBeg,editEnd); - num_updates = diff_sel_area(rold,rnew,rupdate); - for (i = 0; i < num_updates;++i) { - from.x = rupdate[i].left; - to.x = rupdate[i].right; - from.y = rupdate[i].top; - to.y = rupdate[i].bottom; -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"from: x=%d,y=%d, to: x=%d, y=%d\n", - from.x, from.y,to.x,to.y); - fflush(stderr); -#endif - DrawSelection(hwnd,from,to); - } - } -} - -static void -Client_OnVScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos) -{ - int iVscroll; - - switch(code) { - case SB_LINEDOWN: - iVscroll = 1; - break; - case SB_LINEUP: - iVscroll = -1; - break; - case SB_PAGEDOWN: - iVscroll = max(1, cyClient/cyChar); - break; - case SB_PAGEUP: - iVscroll = min(-1, -cyClient/cyChar); - break; - case SB_THUMBTRACK: - iVscroll = pos - iVscrollPos; - break; - default: - iVscroll = 0; - } - iVscroll = max(-iVscrollPos, min(iVscroll, iVscrollMax-iVscrollPos)); - if (iVscroll != 0) { - iVscrollPos += iVscroll; - ScrollWindowEx(hwnd, 0, -cyChar*iVscroll, NULL, NULL, - NULL, NULL, SW_ERASE | SW_INVALIDATE); - SetScrollPos(hwnd, SB_VERT, iVscrollPos, TRUE); - iVscroll = GetScrollPos(hwnd, SB_VERT); - UpdateWindow(hwnd); - } -} - -static void -Client_OnHScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos) -{ - int iHscroll, curCharWidth = cxClient/cxChar; - - switch(code) { - case SB_LINEDOWN: - iHscroll = 1; - break; - case SB_LINEUP: - iHscroll = -1; - break; - case SB_PAGEDOWN: - iHscroll = max(1,curCharWidth-1); - break; - case SB_PAGEUP: - iHscroll = min(-1,-(curCharWidth-1)); - break; - case SB_THUMBTRACK: - iHscroll = pos - iHscrollPos; - break; - default: - iHscroll = 0; - } - iHscroll = max(-iHscrollPos, min(iHscroll, iHscrollMax-iHscrollPos-(curCharWidth-1))); - if (iHscroll != 0) { - iHscrollPos += iHscroll; - ScrollWindow(hwnd, -cxChar*iHscroll, 0, NULL, NULL); - SetScrollPos(hwnd, SB_HORZ, iHscrollPos, TRUE); - UpdateWindow(hwnd); - } -} - -static LRESULT CALLBACK -ClientWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) -{ - switch (iMsg) { - HANDLE_MSG(hwnd, WM_CREATE, Client_OnCreate); - HANDLE_MSG(hwnd, WM_SIZE, Client_OnSize); - HANDLE_MSG(hwnd, WM_PAINT, Client_OnPaint); - HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Client_OnLButtonDown); - HANDLE_MSG(hwnd, WM_RBUTTONDOWN, Client_OnRButtonDown); - HANDLE_MSG(hwnd, WM_LBUTTONUP, Client_OnLButtonUp); - HANDLE_MSG(hwnd, WM_MOUSEMOVE, Client_OnMouseMove); - HANDLE_MSG(hwnd, WM_VSCROLL, Client_OnVScroll); - HANDLE_MSG(hwnd, WM_HSCROLL, Client_OnHScroll); - case WM_CONBEEP: - if (0) Beep(440, 400); - return 0; - case WM_CONTEXT: - ConDrawText(hwnd); - return 0; - case WM_CLOSE: - break; - case WM_DESTROY: - PostQuitMessage(0); - return 0; - } - return DefWindowProc (hwnd, iMsg, wParam, lParam); -} - -static void -LoadUserPreferences(void) -{ - DWORD size; - DWORD res; - DWORD type; - HFONT hfont; - /* default prefs */ - hfont = CreateFont(0,0, 0,0, 0, FALSE,FALSE,FALSE, - ANSI_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, - CLEARTYPE_QUALITY, FIXED_PITCH, TEXT("Consolas")); - if(hfont) { - GetObject(hfont, sizeof(LOGFONT), (PSTR)&logfont); - DeleteObject(hfont); - } else { - GetObject(GetStockObject(SYSTEM_FIXED_FONT),sizeof(LOGFONT),(PSTR)&logfont); - } - fgColor = GetSysColor(COLOR_WINDOWTEXT); - bkgColor = GetSysColor(COLOR_WINDOW); - winPos.left = -1; - toolbarVisible = FALSE; - - if (RegCreateKeyEx(HKEY_CURRENT_USER, USER_KEY, 0, 0, - REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, - &key, &res) != ERROR_SUCCESS) - return; - has_key = TRUE; - if (res == REG_CREATED_NEW_KEY) - return; - size = sizeof(logfont); - res = RegQueryValueEx(key,TEXT("Font"),NULL,&type,(LPBYTE)&logfont,&size); - size = sizeof(fgColor); - res = RegQueryValueEx(key,TEXT("FgColor"),NULL,&type,(LPBYTE)&fgColor,&size); - size = sizeof(bkgColor); - res = RegQueryValueEx(key,TEXT("BkColor"),NULL,&type,(LPBYTE)&bkgColor,&size); - size = sizeof(winPos); - res = RegQueryValueEx(key,TEXT("Pos"),NULL,&type,(LPBYTE)&winPos,&size); - size = sizeof(toolbarVisible); - res = RegQueryValueEx(key,TEXT("Toolbar"),NULL,&type,(LPBYTE)&toolbarVisible,&size); -} - -static void -SaveUserPreferences(void) -{ - WINDOWPLACEMENT wndPlace; - - if (has_key == TRUE) { - RegSetValueEx(key,TEXT("Font"),0,REG_BINARY,(CONST BYTE *)&logfont,sizeof(LOGFONT)); - RegSetValueEx(key,TEXT("FgColor"),0,REG_DWORD,(CONST BYTE *)&fgColor,sizeof(fgColor)); - RegSetValueEx(key,TEXT("BkColor"),0,REG_DWORD,(CONST BYTE *)&bkgColor,sizeof(bkgColor)); - RegSetValueEx(key,TEXT("Toolbar"),0,REG_DWORD,(CONST BYTE *)&toolbarVisible,sizeof(toolbarVisible)); - - wndPlace.length = sizeof(WINDOWPLACEMENT); - GetWindowPlacement(hFrameWnd,&wndPlace); - /* If wndPlace.showCmd == SW_MINIMIZE, then the window is minimized. - We don't care, wndPlace.rcNormalPosition always holds the last known position. */ - winPos = wndPlace.rcNormalPosition; - RegSetValueEx(key,TEXT("Pos"),0,REG_BINARY,(CONST BYTE *)&winPos,sizeof(winPos)); - } -} - - -static void -set_scroll_info(HWND hwnd) -{ - SCROLLINFO info; - int hScrollBy; - /* - * Set vertical scrolling range and scroll box position. - */ - - iVscrollMax = nBufLines-1; - iVscrollPos = min(iVscrollPos, iVscrollMax); - info.cbSize = sizeof(info); - info.fMask = SIF_PAGE|SIF_RANGE|SIF_POS; - info.nMin = 0; - info.nPos = iVscrollPos; - info.nPage = min(cyClient/cyChar, iVscrollMax); - info.nMax = iVscrollMax; - SetScrollInfo(hwnd, SB_VERT, &info, TRUE); - - /* - * Set horizontal scrolling range and scroll box position. - */ - - iHscrollMax = LINE_LENGTH-1; - hScrollBy = max(0, (iHscrollPos - (iHscrollMax-cxClient/cxChar))*cxChar); - iHscrollPos = min(iHscrollPos, iHscrollMax); - info.nPos = iHscrollPos; - info.nPage = cxClient/cxChar; - info.nMax = iHscrollMax; - SetScrollInfo(hwnd, SB_HORZ, &info, TRUE); - /*ScrollWindow(hwnd, hScrollBy, 0, NULL, NULL);*/ -} - - -static void -ensure_line_below(void) -{ - if (cur_line->next == NULL) { - if (nBufLines >= lines_to_save) { - ScreenLine_t* pLine = buffer_top->next; - FREE(buffer_top->text); - FREE(buffer_top); - buffer_top = pLine; - buffer_top->prev = NULL; - nBufLines--; - } - cur_line->next = ConNewLine(); - cur_line->next->prev = cur_line; - buffer_bottom = cur_line->next; - set_scroll_info(hClientWnd); - } -} - -static ScreenLine_t* -ConNewLine(void) -{ - ScreenLine_t *pLine; - - pLine = (ScreenLine_t *)ALLOC(sizeof(ScreenLine_t)); - if (!pLine) - return NULL; - pLine->text = (TCHAR *) ALLOC(canvasColumns * sizeof(TCHAR)); -#ifdef HARDDEBUG - pLine->allocated = canvasColumns; -#endif - pLine->width = 0; - pLine->prev = pLine->next = NULL; - pLine->newline = 0; - nBufLines++; - return pLine; -} - -static ScreenLine_t* -GetLineFromY(int y) -{ - ScreenLine_t *pLine = buffer_top; - int i; - - for (i = 0; i < nBufLines && pLine != NULL; i++) { - if (i == y) - return pLine; - pLine = pLine->next; - } - return NULL; -} - -void ConCarriageFeed(int hard_newline) -{ - cur_x = 0; - ensure_line_below(); - cur_line->newline = hard_newline; - cur_line = cur_line->next; - if (cur_y < nBufLines-1) { - cur_y++; - } else if (iVscrollPos > 0) { - iVscrollPos--; - } -} - -/* - * Scroll screen if cursor is not visible. - */ -static void -ConScrollScreen(void) -{ - if (cur_y >= iVscrollPos + cyClient/cyChar) { - int iVscroll; - - iVscroll = cur_y - iVscrollPos - cyClient/cyChar + 1; - iVscrollPos += iVscroll; - ScrollWindowEx(hClientWnd, 0, -cyChar*iVscroll, NULL, NULL, - NULL, NULL, SW_ERASE | SW_INVALIDATE); - SetScrollPos(hClientWnd, SB_VERT, iVscrollPos, TRUE); - UpdateWindow(hClientWnd); - } -} - -static void -DrawSelection(HWND hwnd, POINT pt1, POINT pt2) -{ - HDC hdc; - int width,height; -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"pt1.x = %d, pt1.y = %d, pt2.x = %d, pt2.y = %d\n", - (int) pt1.x, (int) pt1.y, (int) pt2.x, (int) pt2.y); -#endif - pt1.x = GetXFromLine(GetDC(hwnd),iHscrollPos,pt1.x,GetLineFromY(pt1.y)); - pt2.x = GetXFromLine(GetDC(hwnd),iHscrollPos,pt2.x,GetLineFromY(pt2.y-1)); - pt1.y -= iVscrollPos; - pt2.y -= iVscrollPos; - pt1.y *= cyChar; - pt2.y *= cyChar; -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"pt1.x = %d, pt1.y = %d, pt2.x = %d, pt2.y = %d\n", - (int) pt1.x, (int) pt1.y, (int) pt2.x, (int) pt2.y); - fflush(stderr); -#endif - width = pt2.x-pt1.x; - height = pt2.y - pt1.y; - hdc = GetDC(hwnd); - PatBlt(hdc,pt1.x,pt1.y,width,height,DSTINVERT); - ReleaseDC(hwnd,hdc); -} - -static void -OnEditCopy(HWND hwnd) -{ - HGLOBAL hMem; - TCHAR *pMem; - ScreenLine_t *pLine; - RECT rects[3]; - POINT from,to; - int i,j,sum,len; - if (editBeg.y >= editEnd.y || - (editBeg.y == editEnd.y - 1 && editBeg.x > editEnd.x)) { -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"CopyReverting (Beg.x = %d, Beg.y = %d, " - "End.x = %d, End.y = %d)\n",editBeg.x,editBeg.y, - editEnd.x,editEnd.y); - fflush(stderr); -#endif - from.x = editEnd.x; - from.y = editEnd.y - 1; - to.x = editBeg.x; - to.y = editBeg.y + 1; - calc_sel_area(rects,from,to); - } else { - calc_sel_area(rects,editBeg,editEnd); - } - sum = 1; - for (i = 0; i < 3; ++i) { - if (!EMPTY_RECT(rects[i])) { - pLine = GetLineFromY(rects[i].top); - for (j = rects[i].top; j < rects[i].bottom ;++j) { - if (pLine == NULL) { - sum += 2; - break; - } - if (pLine->width > rects[i].left) { - sum += (pLine->width < rects[i].right) ? - pLine->width - rects[i].left : - rects[i].right - rects[i].left; - } - if(pLine->newline && rects[i].right >= pLine->width) { - sum += 2; - } - pLine = pLine->next; - } - } - } -#ifdef HARD_SEL_DEBUG - fprintf(stderr,"sum = %d\n",sum); - fflush(stderr); -#endif - hMem = GlobalAlloc(GHND, sum * sizeof(TCHAR)); - pMem = GlobalLock(hMem); - for (i = 0; i < 3; ++i) { - if (!EMPTY_RECT(rects[i])) { - pLine = GetLineFromY(rects[i].top); - for (j = rects[i].top; j < rects[i].bottom; ++j) { - if (pLine == NULL) { - memcpy(pMem,TEXT("\r\n"),2 * sizeof(TCHAR)); - pMem += 2; - break; - } - if (pLine->width > rects[i].left) { - len = (pLine->width < rects[i].right) ? - pLine->width - rects[i].left : - rects[i].right - rects[i].left; - memcpy(pMem,pLine->text + rects[i].left,len * sizeof(TCHAR)); - pMem +=len; - } - if(pLine->newline && rects[i].right >= pLine->width) { - memcpy(pMem,TEXT("\r\n"),2 * sizeof(TCHAR)); - pMem += 2; - } - pLine = pLine->next; - } - } - } - *pMem = TEXT('\0'); - /* Flash de selection area to give user feedback about copying */ - InvertSelectionArea(hwnd); - Sleep(100); - InvertSelectionArea(hwnd); - - OpenClipboard(hwnd); - EmptyClipboard(); - GlobalUnlock(hMem); - SetClipboardData(CF_UNICODETEXT,hMem); - CloseClipboard(); -} - -/* XXX:PaN Tchar or char? */ -static void -OnEditPaste(HWND hwnd) -{ - HANDLE hClipMem; - TCHAR *pClipMem,*pMem,*pMem2; - if (!OpenClipboard(hwnd)) - return; - if ((hClipMem = GetClipboardData(CF_UNICODETEXT)) != NULL) { - pClipMem = GlobalLock(hClipMem); - pMem = (TCHAR *)ALLOC(GlobalSize(hClipMem) * sizeof(TCHAR)); - pMem2 = pMem; - while ((*pMem2 = *pClipMem) != TEXT('\0')) { - if (*pClipMem == TEXT('\r')) - *pMem2 = TEXT('\n'); - ++pMem2; - ++pClipMem; - } - GlobalUnlock(hClipMem); - write_inbuf(pMem, _tcsclen(pMem)); - } - CloseClipboard(); -} - -static void -OnEditSelAll(HWND hwnd) -{ - editBeg.x = 0; - editBeg.y = 0; - editEnd.x = LINE_LENGTH-1; - editEnd.y = cur_y; - fTextSelected = TRUE; - InvalidateRect(hwnd, NULL, TRUE); -} - -CF_HOOK_RET APIENTRY CFHookProc(HWND hDlg,UINT iMsg,WPARAM wParam,LPARAM lParam) -{ - /* Hook procedure for font dialog box */ - HWND hOwner; - RECT rc,rcOwner,rcDlg; - switch (iMsg) { - case WM_INITDIALOG: - /* center dialogbox within its owner window */ - if ((hOwner = GetParent(hDlg)) == NULL) - hOwner = GetDesktopWindow(); - GetWindowRect(hOwner, &rcOwner); - GetWindowRect(hDlg, &rcDlg); - CopyRect(&rc, &rcOwner); - OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); - OffsetRect(&rc, -rc.left, -rc.top); - OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); - SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), - rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); - return (CF_HOOK_RET) 1; - default: - break; - } - return (CF_HOOK_RET) 0; /* Let the default procedure process the message */ -} - -static BOOL -ConChooseFont(HWND hwnd) -{ - HDC hdc; - hdc = GetDC(hwnd); - cf.lStructSize = sizeof(CHOOSEFONT); - cf.hwndOwner = hwnd; - cf.hDC = NULL; - cf.lpLogFont = &logfont; - cf.iPointSize = 0; - cf.Flags = CF_INITTOLOGFONTSTRUCT|CF_SCREENFONTS|CF_FIXEDPITCHONLY|CF_EFFECTS|CF_ENABLEHOOK; - cf.rgbColors = GetTextColor(hdc); - cf.lCustData = 0L; - cf.lpfnHook = CFHookProc; - cf.lpTemplateName = NULL; - cf.hInstance = NULL; - cf.lpszStyle = NULL; - cf.nFontType = 0; - cf.nSizeMin = 0; - cf.nSizeMax = 0; - ReleaseDC(hwnd,hdc); - return ChooseFont(&cf); -} - -static void -ConFontInitialize(HWND hwnd) -{ - HDC hdc; - TEXTMETRIC tm; - HFONT hFont; - - hFont = CreateFontIndirect(&logfont); - hdc = GetDC(hwnd); - SelectObject(hdc, hFont); - SetTextColor(hdc,fgColor); - SetBkColor(hdc,bkgColor); - GetTextMetrics(hdc, &tm); - cxChar = tm.tmAveCharWidth; - cxCharMax = tm.tmMaxCharWidth; - cyChar = tm.tmHeight + tm.tmExternalLeading; - ReleaseDC(hwnd, hdc); -} - -static void -ConSetFont(HWND hwnd) -{ - HDC hdc; - TEXTMETRIC tm; - HFONT hFontNew; - - hFontNew = CreateFontIndirect(&logfont); - SendMessage(hComboWnd,WM_SETFONT,(WPARAM)hFontNew, - MAKELPARAM(1,0)); - hdc = GetDC(hwnd); - DeleteObject(SelectObject(hdc, hFontNew)); - GetTextMetrics(hdc, &tm); - cxChar = tm.tmAveCharWidth; - cxCharMax = tm.tmMaxCharWidth; - cyChar = tm.tmHeight + tm.tmExternalLeading; - fgColor = cf.rgbColors; - SetTextColor(hdc,fgColor); - ReleaseDC(hwnd, hdc); - set_scroll_info(hwnd); - HideCaret(hwnd); - if (DestroyCaret()) { - CreateCaret(hwnd, NULL, cxChar, cyChar); - SetCaretPos(GetXFromCurrentY(hdc,iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); - } - ShowCaret(hwnd); - InvalidateRect(hwnd, NULL, TRUE); -} - -CC_HOOK_RET APIENTRY -CCHookProc(HWND hDlg,UINT iMsg,WPARAM wParam,LPARAM lParam) -{ - /* Hook procedure for choose color dialog box */ - HWND hOwner; - RECT rc,rcOwner,rcDlg; - switch (iMsg) { - case WM_INITDIALOG: - /* center dialogbox within its owner window */ - if ((hOwner = GetParent(hDlg)) == NULL) - hOwner = GetDesktopWindow(); - GetWindowRect(hOwner, &rcOwner); - GetWindowRect(hDlg, &rcDlg); - CopyRect(&rc, &rcOwner); - OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); - OffsetRect(&rc, -rc.left, -rc.top); - OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); - SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), - rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); - return (CC_HOOK_RET) 1; - default: - break; - } - return (CC_HOOK_RET) 0; /* Let the default procedure process the message */ -} - -void ConChooseColor(HWND hwnd) -{ - CHOOSECOLOR cc; - static COLORREF acrCustClr[16]; - HBRUSH hbrush; - HDC hdc; - - /* Initialize CHOOSECOLOR */ - ZeroMemory(&cc, sizeof(CHOOSECOLOR)); - cc.lStructSize = sizeof(CHOOSECOLOR); - cc.hwndOwner = hwnd; - cc.lpCustColors = (LPDWORD) acrCustClr; - cc.rgbResult = bkgColor; - cc.lpfnHook = CCHookProc; - cc.Flags = CC_FULLOPEN|CC_RGBINIT|CC_SOLIDCOLOR|CC_ENABLEHOOK; - - if (ChooseColor(&cc)==TRUE) { - bkgColor = cc.rgbResult; - hdc = GetDC(hwnd); - SetBkColor(hdc,bkgColor); - ReleaseDC(hwnd,hdc); - hbrush = CreateSolidBrush(bkgColor); - DeleteObject((HBRUSH)SetClassLongPtr(hClientWnd,GCL_HBRBACKGROUND,(LONG_PTR)hbrush)); - InvalidateRect(hwnd,NULL,TRUE); - } -} - -OFN_HOOK_RET APIENTRY OFNHookProc(HWND hwndDlg,UINT iMsg, - WPARAM wParam,LPARAM lParam) -{ - /* Hook procedure for open file dialog box */ - HWND hOwner,hDlg; - RECT rc,rcOwner,rcDlg; - hDlg = GetParent(hwndDlg); - switch (iMsg) { - case WM_INITDIALOG: - /* center dialogbox within its owner window */ - if ((hOwner = GetParent(hDlg)) == NULL) - hOwner = GetDesktopWindow(); - GetWindowRect(hOwner, &rcOwner); - GetWindowRect(hDlg, &rcDlg); - CopyRect(&rc, &rcOwner); - OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); - OffsetRect(&rc, -rc.left, -rc.top); - OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); - SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), - rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); - return (OFN_HOOK_RET) 1; - default: - break; - } - return (OFN_HOOK_RET) 0; /* the let default procedure process the message */ -} - -static void -GetFileName(HWND hwnd, TCHAR *pFile) -{ - /* Open the File Open dialog box and */ - /* retrieve the file name */ - OPENFILENAME ofn; - TCHAR szFilterSpec [128] = TEXT("logfiles (*.log)\0*.log\0All files (*.*)\0*.*\0\0"); - #define MAXFILENAME 256 - TCHAR szFileName[MAXFILENAME]; - TCHAR szFileTitle[MAXFILENAME]; - - /* these need to be filled in */ - _tcscpy(szFileName, TEXT("erlshell.log")); - _tcscpy(szFileTitle, TEXT("")); /* must be NULL */ - - ofn.lStructSize = sizeof(OPENFILENAME); - ofn.hwndOwner = NULL; - ofn.lpstrFilter = szFilterSpec; - ofn.lpstrCustomFilter = NULL; - ofn.nMaxCustFilter = 0; - ofn.nFilterIndex = 0; - ofn.lpstrFile = szFileName; - ofn.nMaxFile = MAXFILENAME; - ofn.lpstrInitialDir = NULL; - ofn.lpstrFileTitle = szFileTitle; - ofn.nMaxFileTitle = MAXFILENAME; - ofn.lpstrTitle = TEXT("Open logfile"); - ofn.lpstrDefExt = TEXT("log"); - ofn.Flags = OFN_CREATEPROMPT|OFN_HIDEREADONLY|OFN_EXPLORER|OFN_ENABLEHOOK|OFN_NOCHANGEDIR; /* OFN_NOCHANGEDIR only works in Vista :( */ - ofn.lpfnHook = OFNHookProc; - - if (!GetOpenFileName ((LPOPENFILENAME)&ofn)){ - *pFile = TEXT('\0'); - } else { - _tcscpy(pFile, ofn.lpstrFile); - } -} - -void OpenLogFile(HWND hwnd) -{ - /* open a file for logging */ - TCHAR filename[_MAX_PATH]; - - GetFileName(hwnd, filename); - if (filename[0] == '\0') - return; - if (NULL == (logfile = _tfopen(filename,TEXT("w,ccs=UNICODE")))) - return; -} - -void CloseLogFile(HWND hwnd) -{ - /* close log file */ - fclose(logfile); - logfile = NULL; -} - -void LogFileWrite(TCHAR *buf, int num_chars) -{ - /* write to logfile */ - int from,to; - while (num_chars-- > 0) { - switch (*buf) { - case SET_CURSOR: - buf++; - from = *((int *)buf); - buf += sizeof(int)/sizeof(TCHAR); - to = *((int *)buf); - buf += (sizeof(int)/sizeof(TCHAR))-1; - num_chars -= 2 * (sizeof(int)/sizeof(TCHAR)); - // Won't seek in Unicode file, sorry... - // fseek(logfile,to-from *sizeof(TCHAR),SEEK_CUR); - break; - default: - _fputtc(*buf,logfile); - break; - } - buf++; - } -} - -static void -init_buffers(void) -{ - inbuf.data = (TCHAR *) ALLOC(BUFSIZE * sizeof(TCHAR)); - outbuf.data = (TCHAR *) ALLOC(BUFSIZE * sizeof(TCHAR)); - inbuf.size = BUFSIZE; - inbuf.rdPos = inbuf.wrPos = 0; - outbuf.size = BUFSIZE; - outbuf.rdPos = outbuf.wrPos = 0; -} - -static int -check_realloc(buffer_t *buf, int num_chars) -{ - if (buf->wrPos + num_chars >= buf->size) { - if (buf->size > MAXBUFSIZE) - return 0; - buf->size += num_chars + BUFSIZE; - if (!(buf->data = (TCHAR *)REALLOC(buf->data, buf->size * sizeof(TCHAR)))) { - buf->size = buf->rdPos = buf->wrPos = 0; - return 0; - } - } - return 1; -} - -static int -write_inbuf(TCHAR *data, int num_chars) -{ - TCHAR *buf; - int nwrite; - WaitForSingleObject(console_input,INFINITE); - if (!check_realloc(&inbuf,num_chars)) { - ReleaseSemaphore(console_input,1,NULL); - return -1; - } - buf = &inbuf.data[inbuf.wrPos]; - inbuf.wrPos += num_chars; - nwrite = num_chars; - while (nwrite--) - *buf++ = *data++; - SetEvent(console_input_event); - ReleaseSemaphore(console_input,1,NULL); - return num_chars; -} - -static int -write_outbuf(TCHAR *data, int num_chars) -{ - TCHAR *buf; - int nwrite; - - WaitForSingleObject(console_output,INFINITE); - if (!check_realloc(&outbuf, num_chars)) { - ReleaseSemaphore(console_output,1,NULL); - return -1; - } - if (outbuf.rdPos == outbuf.wrPos) - PostMessage(hClientWnd, WM_CONTEXT, 0L, 0L); - buf = &outbuf.data[outbuf.wrPos]; - outbuf.wrPos += num_chars; - nwrite = num_chars; - while (nwrite--) - *buf++ = *data++; - ReleaseSemaphore(console_output,1,NULL); - return num_chars; -} - -DIALOG_PROC_RET CALLBACK AboutDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam) -{ - HWND hOwner; - RECT rc,rcOwner,rcDlg; - - switch (iMsg) { - case WM_INITDIALOG: - /* center dialogbox within its owner window */ - if ((hOwner = GetParent(hDlg)) == NULL) - hOwner = GetDesktopWindow(); - GetWindowRect(hOwner, &rcOwner); - GetWindowRect(hDlg, &rcDlg); - CopyRect(&rc, &rcOwner); - OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); - OffsetRect(&rc, -rc.left, -rc.top); - OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); - SetWindowPos(hDlg,HWND_TOP,rcOwner.left + (rc.right / 2), - rcOwner.top + (rc.bottom / 2),0,0,SWP_NOSIZE); - SetDlgItemText(hDlg, ID_OTP_VERSIONSTRING, - TEXT("OTP version ") TEXT(ERLANG_OTP_VERSION)); - SetDlgItemText(hDlg, ID_ERTS_VERSIONSTRING, - TEXT("Erlang emulator version ") TEXT(ERLANG_VERSION)); - return (DIALOG_PROC_RET) TRUE; - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDCANCEL: - EndDialog(hDlg,0); - return (DIALOG_PROC_RET) TRUE; - } - break; - } - return (DIALOG_PROC_RET) FALSE; -} - -static void -ConDrawText(HWND hwnd) -{ - int num_chars; - int nchars; - TCHAR *buf; - int from, to; - int dl; - int dc; - RECT rc; - - WaitForSingleObject(console_output, INFINITE); - nchars = 0; - num_chars = outbuf.wrPos - outbuf.rdPos; - buf = &outbuf.data[outbuf.rdPos]; - if (logfile != NULL) - LogFileWrite(buf, num_chars); - - -#ifdef HARDDEBUG - { - TCHAR *bu = (TCHAR *) ALLOC((num_chars+1) * sizeof(TCHAR)); - memcpy(bu,buf,num_chars * sizeof(TCHAR)); - bu[num_chars]='\0'; - fprintf(stderr,"ConDrawText\"%S\"\n",bu); - FREE(bu); - fflush(stderr); - } -#endif - /* - * Don't draw any text in the window; just update the line buffers - * and invalidate the appropriate part of the window. The window - * will be updated on the next WM_PAINT message. - */ - - while (num_chars-- > 0) { - switch (*buf) { - case '\r': - break; - case '\n': - if (nchars > 0) { - rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); - rc.right = rc.left + cxCharMax*nchars; - rc.top = cyChar * (cur_y-iVscrollPos); - rc.bottom = rc.top + cyChar; - InvalidateRect(hwnd, &rc, TRUE); - nchars = 0; - } - ConCarriageFeed(1); - ConScrollScreen(); - break; - case SET_CURSOR: - if (nchars > 0) { - rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); - rc.right = rc.left + cxCharMax*nchars; - rc.top = cyChar * (cur_y-iVscrollPos); - rc.bottom = rc.top + cyChar; - InvalidateRect(hwnd, &rc, TRUE); - nchars = 0; - } - buf++; - from = *((int *)buf); - buf += sizeof(int)/sizeof(TCHAR); - to = *((int *)buf); - buf += (sizeof(int)/sizeof(TCHAR))-1; - num_chars -= 2 * (sizeof(int)/sizeof(TCHAR)); - while (to > from) { - cur_x++; - if (GetXFromCurrentY(GetDC(hwnd),0,cur_x)+cxChar > - (LINE_LENGTH * cxChar)) { - cur_x = 0; - cur_y++; - ensure_line_below(); - cur_line = cur_line->next; - } - from++; - } - while (to < from) { - cur_x--; - if (cur_x < 0) { - cur_y--; - cur_line = cur_line->prev; - cur_x = cur_line->width-1; - } - from--; - } - - break; - default: - nchars++; - cur_line->text[cur_x] = *buf; - cur_x++; - if (cur_x > cur_line->width) - cur_line->width = cur_x; - if (GetXFromCurrentY(GetDC(hwnd),0,cur_x)+cxChar > - (LINE_LENGTH * cxChar)) { - if (nchars > 0) { - rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); - rc.right = rc.left + cxCharMax*nchars; - rc.top = cyChar * (cur_y-iVscrollPos); - rc.bottom = rc.top + cyChar; - InvalidateRect(hwnd, &rc, TRUE); - } - ConCarriageFeed(0); - nchars = 0; - } - } - buf++; - } - if (nchars > 0) { - rc.left = GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x - nchars); - rc.right = rc.left + cxCharMax*nchars; - rc.top = cyChar * (cur_y-iVscrollPos); - rc.bottom = rc.top + cyChar; - InvalidateRect(hwnd, &rc, TRUE); - } - ConScrollScreen(); - SetCaretPos(GetXFromCurrentY(GetDC(hwnd),iHscrollPos,cur_x), (cur_y-iVscrollPos)*cyChar); - outbuf.wrPos = outbuf.rdPos = 0; - ReleaseSemaphore(console_output, 1, NULL); -} - -static void -AddToCmdHistory(void) -{ - int i; - int size; - Uint32 *buf; - wchar_t cmdBuf[128]; - - if (llen != 0) { - for (i = 0, size = 0; i < llen-1; i++) { - /* - * Find end of prompt. - */ - if ((lbuf[i] == '>') && lbuf[i+1] == ' ') { - buf = &lbuf[i+2]; - size = llen-i-2; - break; - } - } - if (size > 0 && size < 128) { - for (i = 0;i < size; ++i) { - cmdBuf[i] = (wchar_t) buf[i]; - } - cmdBuf[size] = 0; - SendMessage(hComboWnd,CB_INSERTSTRING,0,(LPARAM)cmdBuf); - } - } -} - -/*static TBBUTTON tbb[] = -{ - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - 0, IDMENU_COPY, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, - 1, IDMENU_PASTE, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, - 2, IDMENU_FONT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, - 3, IDMENU_ABOUT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE, 0, 0, 0, 0, - 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, 0, - };*/ -static TBBUTTON tbb[] = -{ - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP}, - {0, IDMENU_COPY, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE}, - {1, IDMENU_PASTE, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE}, - {2, IDMENU_FONT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE}, - {3, IDMENU_ABOUT, TBSTATE_ENABLED, TBSTYLE_AUTOSIZE}, - {0, 0, TBSTATE_ENABLED, TBSTYLE_SEP} -}; - -static TBADDBITMAP tbbitmap = -{ - HINST_COMMCTRL, IDB_STD_SMALL_COLOR, -}; - - -static HWND -InitToolBar(HWND hwndParent) -{ - int x,y,cx; - HWND hwndTB,hwndTT; - RECT r; - TOOLINFO ti; - HFONT hFontNew; - DWORD backgroundColor = GetSysColor(COLOR_BTNFACE); - COLORMAP colorMap; - colorMap.from = RGB(192, 192, 192); - colorMap.to = backgroundColor; - /* Create toolbar window with tooltips */ - hwndTB = CreateWindowEx(0,TOOLBARCLASSNAME,(TCHAR *)NULL, - WS_CHILD|CCS_TOP|WS_CLIPSIBLINGS|TBSTYLE_TOOLTIPS, - 0,0,0,0,hwndParent, - (HMENU)2,hInstance,NULL); - SendMessage(hwndTB,TB_BUTTONSTRUCTSIZE, - (WPARAM) sizeof(TBBUTTON),0); - tbbitmap.hInst = NULL; - tbbitmap.nID = (UINT_PTR) CreateMappedBitmap(beam_module, 1,0, &colorMap, 1); - SendMessage(hwndTB, TB_ADDBITMAP, (WPARAM) 4, - (LPARAM) &tbbitmap); - - SendMessage(hwndTB,TB_ADDBUTTONS, (WPARAM) 30, - (LPARAM) tbb); - if (toolbarVisible) - ShowWindow(hwndTB, SW_SHOW); - - /* Create combobox window */ - SendMessage(hwndTB,TB_GETITEMRECT,0,(LPARAM)&r); - x = r.left; y = r.top; - SendMessage(hwndTB,TB_GETITEMRECT,23,(LPARAM)&r); - cx = r.right - x + 1; - hComboWnd = CreateWindow(TEXT("combobox"),NULL,WS_VSCROLL|WS_CHILD|WS_VISIBLE|CBS_DROPDOWNLIST, - x,y,cx,100,hwndParent,(HMENU)ID_COMBOBOX, hInstance,NULL); - SetParent(hComboWnd,hwndTB); - hFontNew = CreateFontIndirect(&logfont); - SendMessage(hComboWnd,WM_SETFONT,(WPARAM)hFontNew, - MAKELPARAM(1,0)); - - /* Add tooltip for combo box */ - ZeroMemory(&ti,sizeof(TOOLINFO)); - ti.cbSize = sizeof(TOOLINFO); - ti.uFlags = TTF_IDISHWND|TTF_CENTERTIP|TTF_SUBCLASS; - ti.hwnd = hwndTB;; - ti.uId = (UINT_PTR)hComboWnd; - ti.lpszText = LPSTR_TEXTCALLBACK; - hwndTT = (HWND)SendMessage(hwndTB,TB_GETTOOLTIPS,0,0); - SendMessage(hwndTT,TTM_ADDTOOL,0,(LPARAM)&ti); - - return hwndTB; -} - -static void -window_title(struct title_buf *tbuf) -{ - int res, i; - size_t bufsz = TITLE_BUF_SZ; - unsigned char charbuff[TITLE_BUF_SZ]; - - res = erl_drv_getenv("ERL_WINDOW_TITLE", charbuff, &bufsz); - if (res < 0) - tbuf->name = erlang_window_title; - else if (res == 0) { - for (i = 0; i < bufsz; ++i) { - tbuf->buf[i] = charbuff[i]; - } - tbuf->buf[bufsz - 1] = 0; - tbuf->name = &tbuf->buf[0]; - } else { - char *buf = ALLOC(bufsz); - if (!buf) - tbuf->name = erlang_window_title; - else { - while (1) { - char *newbuf; - res = erl_drv_getenv("ERL_WINDOW_TITLE", buf, &bufsz); - if (res <= 0) { - if (res == 0) { - TCHAR *wbuf = ALLOC(bufsz *sizeof(TCHAR)); - for (i = 0; i < bufsz ; ++i) { - wbuf[i] = buf[i]; - } - wbuf[bufsz - 1] = 0; - FREE(buf); - tbuf->name = wbuf; - } else { - tbuf->name = erlang_window_title; - FREE(buf); - } - break; - } - newbuf = REALLOC(buf, bufsz); - if (newbuf) - buf = newbuf; - else { - tbuf->name = erlang_window_title; - FREE(buf); - break; - } - } - } - } -} - -static void -free_window_title(struct title_buf *tbuf) -{ - if (tbuf->name != erlang_window_title && tbuf->name != &tbuf->buf[0]) - FREE(tbuf->name); -} diff --git a/erts/emulator/drivers/win32/win_con.h b/erts/emulator/drivers/win32/win_con.h deleted file mode 100644 index 7a642cd7edb7..000000000000 --- a/erts/emulator/drivers/win32/win_con.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * %CopyrightBegin% - * - * Copyright Ericsson AB 2007-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% - */ - -/* - * External API for the windows console (aka werl window) - * used by ttsl_drv.c - */ -#ifndef _WIN_CON_H_VISITED -#define _WIN_CON_H_VISITED 1 -void ConNormalExit(void); -void ConWaitForExit(void); -void ConSetCtrlHandler(BOOL (WINAPI *handler)(DWORD)); -int ConPutChar(Uint32 c); -void ConSetCursor(int from, int to); -void ConPrintf(char *format, ...); -void ConVprintf(char *format, va_list va); -void ConBeep(void); -int ConReadInput(Uint32 *data, int nbytes); -int ConGetKey(void); -int ConGetColumns(void); -int ConGetRows(void); -void ConInit(void); -#endif /* _WIN_CON_H_VISITED */ diff --git a/erts/emulator/nifs/common/prim_tty_nif.c b/erts/emulator/nifs/common/prim_tty_nif.c new file mode 100644 index 000000000000..309549bbd9cc --- /dev/null +++ b/erts/emulator/nifs/common/prim_tty_nif.c @@ -0,0 +1,1012 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson 2015-2021. 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% + */ + +/* + * Purpose: NIF library for interacting with the tty + * + */ + +#define STATIC_ERLANG_NIF 1 + +#ifndef WANT_NONBLOCKING +#define WANT_NONBLOCKING +#endif + +#include "config.h" +#include "sys.h" +#include "erl_nif.h" +#include "erl_driver.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_TERMCAP + #include + #include + #include +#endif +#ifndef __WIN32__ + #include + #include +#endif +#ifdef HAVE_SYS_UIO_H + #include +#endif + +#if defined IOV_MAX +#define MAXIOV IOV_MAX +#elif defined UIO_MAXIOV +#define MAXIOV UIO_MAXIOV +#else +#define MAXIOV 16 +#endif + +#if !defined(HAVE_SETLOCALE) || !defined(HAVE_NL_LANGINFO) || !defined(HAVE_LANGINFO_H) +#define PRIMITIVE_UTF8_CHECK 1 +#else +#include +#endif + +#ifdef VALGRIND +# include +#endif + +#define DEF_HEIGHT 24 +#define DEF_WIDTH 80 + +typedef struct { +#ifdef __WIN32__ + HANDLE ofd; + HANDLE ifd; +#else + int ofd; /* stdout */ + int ifd; /* stdin */ +#endif + ErlNifPid self; + ErlNifPid reader; + int tty; /* if the tty is initialized */ +#ifdef THREADED_READER + ErlNifTid reader_tid; +#endif +#ifndef __WIN32__ + int signal[2]; /* Pipe used for signal (winch + cont) notifications */ +#endif +#ifdef HAVE_TERMCAP + struct termios tty_smode; + struct termios tty_rmode; +#endif +} TTYResource; + +static ErlNifResourceType *tty_rt; + +/* The NIFs: */ +static ERL_NIF_TERM isatty_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_create_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_set_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM setlocale_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_select_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_write_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_read_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM isprint_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM wcwidth_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM wcswidth_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM sizeof_wchar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_window_size_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_tgetent_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_tgetnum_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_tgetflag_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_tgetstr_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_tgoto_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM tty_read_signal_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +static ErlNifFunc nif_funcs[] = { + {"isatty", 1, isatty_nif}, + {"tty_create", 0, tty_create_nif}, + {"tty_init", 3, tty_init_nif}, + {"tty_set", 1, tty_set_nif}, + {"tty_read_signal", 2, tty_read_signal_nif}, + {"setlocale", 0, setlocale_nif}, + {"tty_select", 3, tty_select_nif}, + {"tty_window_size", 1, tty_window_size_nif}, + {"write_nif", 2, tty_write_nif, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"read_nif", 2, tty_read_nif, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"isprint", 1, isprint_nif}, + {"wcwidth", 1, wcwidth_nif}, + {"wcswidth", 1, wcswidth_nif}, + {"sizeof_wchar", 0, sizeof_wchar_nif}, + {"tgetent_nif", 1, tty_tgetent_nif}, + {"tgetnum_nif", 1, tty_tgetnum_nif}, + {"tgetflag_nif", 1, tty_tgetflag_nif}, + {"tgetstr_nif", 1, tty_tgetstr_nif}, + {"tgoto_nif", 2, tty_tgoto_nif}, + {"tgoto_nif", 3, tty_tgoto_nif} +}; + +/* NIF interface declarations */ +static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); +static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info); +static void unload(ErlNifEnv* env, void* priv_data); + +ERL_NIF_INIT(prim_tty, nif_funcs, load, NULL, upgrade, unload) + +#define ATOMS \ + ATOM_DECL(canon); \ + ATOM_DECL(echo); \ + ATOM_DECL(ebadf); \ + ATOM_DECL(undefined); \ + ATOM_DECL(error); \ + ATOM_DECL(true); \ + ATOM_DECL(ok); \ + ATOM_DECL(input); \ + ATOM_DECL(false); \ + ATOM_DECL(stdin); \ + ATOM_DECL(stdout); \ + ATOM_DECL(stderr); \ + ATOM_DECL(sig); + + +#define ATOM_DECL(A) static ERL_NIF_TERM atom_##A +ATOMS +#undef ATOM_DECL + +static ERL_NIF_TERM make_error(ErlNifEnv *env, ERL_NIF_TERM reason) { + return enif_make_tuple2(env, atom_error, reason); +} + +static ERL_NIF_TERM make_enotsup(ErlNifEnv *env) { + return make_error(env, enif_make_atom(env, "enotsup")); +} + +static ERL_NIF_TERM make_errno_error(ErlNifEnv *env, const char *function) { + ERL_NIF_TERM errorInfo; +#ifdef __WIN32__ + errorInfo = enif_make_atom(env, last_error()); +#else + errorInfo = enif_make_atom(env, erl_errno_id(errno)); +#endif + return make_error( + env, enif_make_tuple2( + env, enif_make_atom(env, function), errorInfo)); +} + +static int tty_get_fd(ErlNifEnv *env, ERL_NIF_TERM atom, int *fd) { + if (enif_is_identical(atom, atom_stdout)) { + *fd = fileno(stdout); + } else if (enif_is_identical(atom, atom_stdin)) { + *fd = fileno(stdin); + } else if (enif_is_identical(atom, atom_stderr)) { + *fd = fileno(stderr); + } else { + return 0; + } + return 1; +} + +static ERL_NIF_TERM isatty_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + int fd; + if (tty_get_fd(env, argv[0], &fd)) { + if (isatty(fd)) { + return atom_true; + } else if (errno == EINVAL || errno == ENOTTY) { + return atom_false; + } else { + return atom_ebadf; + } + } + return enif_make_badarg(env); +} + +static ERL_NIF_TERM isprint_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + int i; + if (enif_get_int(env, argv[0], &i)) { + ASSERT(i > 0 && i < 256); + return isprint((char)i) ? atom_true : atom_false; + } + return enif_make_badarg(env); +} + +static ERL_NIF_TERM wcwidth_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + int i; + if (enif_get_int(env, argv[0], &i)) { +#ifndef __WIN32__ + int width; + ASSERT(i > 0 && i < (1l << 21)); + width = wcwidth((wchar_t)i); + if (width == -1) { + return make_error(env, enif_make_atom(env, "not_printable")); + } + return enif_make_int(env, width); +#else + return make_enotsup(env); +#endif + } + return enif_make_badarg(env); +} + +static ERL_NIF_TERM wcswidth_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + ErlNifBinary bin; + if (enif_inspect_iolist_as_binary(env, argv[0], &bin)) { + wchar_t *chars = (wchar_t*)bin.data; + int width; +#ifdef DEBUG + for (int i = 0; i < bin.size / sizeof(wchar_t); i++) { + ASSERT(chars[i] >= 0 && chars[i] < (1l << 21)); + } +#endif +#ifndef __WIN32__ + width = wcswidth(chars, bin.size / sizeof(wchar_t)); +#else + width = bin.size / sizeof(wchar_t); +#endif + if (width == -1) { + return make_error(env, enif_make_atom(env, "not_printable")); + } + return enif_make_tuple2(env, atom_ok, enif_make_int(env, width)); + } + return enif_make_badarg(env); +} + +static ERL_NIF_TERM sizeof_wchar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + return enif_make_int(env, sizeof(wchar_t)); +} + +static ERL_NIF_TERM tty_write_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + ERL_NIF_TERM head = argv[1], tail; + ErlNifIOQueue *q = NULL; + ErlNifIOVec vec, *iovec = &vec; + SysIOVec *iov; + int iovcnt; + TTYResource *tty; + ssize_t res = 0; + size_t size; + if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty)) + return enif_make_badarg(env); + + while (!enif_is_identical(head, enif_make_list(env, 0))) { + if (!enif_inspect_iovec(env, MAXIOV, head, &tail, &iovec)) + return enif_make_badarg(env); + + head = tail; + + iov = iovec->iov; + size = iovec->size; + iovcnt = iovec->iovcnt; + + do { +#ifndef __WIN32__ + do { + res = writev(tty->ofd, iov, iovcnt); + } while(res < 0 && (errno == EINTR || errno == EAGAIN)); +#else + for (int i = 0; i < iovec->iovcnt; i++) { + ssize_t written; + BOOL r = WriteFile(tty->ofd, iovec->iov[i].iov_base, iovec->iov[i].iov_len, &written, NULL); + if (!r) { + res = -1; + break; + } + res += written; + } +#endif + if (res < 0) { + if (q) enif_ioq_destroy(q); + return make_errno_error(env, "writev"); + } + if (res != size) { + if (!q) { + q = enif_ioq_create(ERL_NIF_IOQ_NORMAL); + enif_ioq_enqv(q, iovec, 0); + } + } + + if (q) { + enif_ioq_deq(q, res, &size); + if (size == 0) { + enif_ioq_destroy(q); + q = NULL; + } else { + iov = enif_ioq_peek(q, &iovcnt); + } + } + } while(q); + + }; + return atom_ok; +} + +static ERL_NIF_TERM tty_read_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + TTYResource *tty; + ErlNifBinary bin; + ERL_NIF_TERM res_term; + ssize_t res = 0; + if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty)) + return enif_make_badarg(env); +#ifdef __WIN32__ + { + ssize_t inputs_read, num_characters = 0; + wchar_t *characters = NULL; + INPUT_RECORD inputs[128]; + if (!ReadConsoleInputW(tty->ifd, inputs, sizeof(inputs)/sizeof(*inputs), + &inputs_read)) { + return make_errno_error(env, "ReadConsoleInput"); + } + for (int i = 0; i < inputs_read; i++) { + if (inputs[i].EventType == KEY_EVENT) { + if (inputs[i].Event.KeyEvent.bKeyDown && + inputs[i].Event.KeyEvent.uChar.UnicodeChar < 256 && + inputs[i].Event.KeyEvent.uChar.UnicodeChar != 0) { + num_characters++; + } + if (!inputs[i].Event.KeyEvent.bKeyDown && + inputs[i].Event.KeyEvent.uChar.UnicodeChar > 255 && + inputs[i].Event.KeyEvent.uChar.UnicodeChar != 0) { + num_characters++; + } + } + } + enif_alloc_binary(num_characters * sizeof(wchar_t), &bin); + characters = (wchar_t*)bin.data; + for (int i = 0; i < inputs_read; i++) { + switch (inputs[i].EventType) + { + case KEY_EVENT: + if (inputs[i].Event.KeyEvent.bKeyDown && + inputs[i].Event.KeyEvent.uChar.UnicodeChar < 256 && + inputs[i].Event.KeyEvent.uChar.UnicodeChar != 0) { + characters[res++] = inputs[i].Event.KeyEvent.uChar.UnicodeChar; + } + if (!inputs[i].Event.KeyEvent.bKeyDown && + inputs[i].Event.KeyEvent.uChar.UnicodeChar > 255 && + inputs[i].Event.KeyEvent.uChar.UnicodeChar != 0) { + characters[res++] = inputs[i].Event.KeyEvent.uChar.UnicodeChar; + } + break; + case WINDOW_BUFFER_SIZE_EVENT: + enif_send(env, &tty->self, NULL, + enif_make_tuple2(env, enif_make_atom(env, "resize"), + enif_make_tuple2(env, + enif_make_int(env, inputs[i].Event.WindowBufferSizeEvent.dwSize.Y), + enif_make_int(env, inputs[i].Event.WindowBufferSizeEvent.dwSize.X)))); + break; + case MENU_EVENT: + case FOCUS_EVENT: + /* Should be ignored according to + https://docs.microsoft.com/en-us/windows/console/input-record-str */ + break; + default: + fprintf(stderr,"Unknown event: %d\r\n", inputs[i].EventType); + break; + } + } + res *= sizeof(wchar_t); + } +#else + enif_alloc_binary(1024, &bin); + res = read(tty->ifd, bin.data, bin.size); + if (res < 0) { + if (errno != EAGAIN && errno != EINTR) { + enif_release_binary(&bin); + return make_errno_error(env, "read"); + } + res = 0; + } else if (res == 0) { + enif_release_binary(&bin); + return make_error(env, enif_make_atom(env, "closed")); + } +#endif + enif_select(env, tty->ifd, ERL_NIF_SELECT_READ, tty, NULL, argv[1]); + if (res == bin.size) { + res_term = enif_make_binary(env, &bin); + } else if (res < bin.size / 2) { + unsigned char *buff = enif_make_new_binary(env, res, &res_term); + if (res > 0) { + memcpy(buff, bin.data, res); + } + enif_release_binary(&bin); + } else { + enif_realloc_binary(&bin, res); + res_term = enif_make_binary(env, &bin); + } + + return enif_make_tuple2(env, atom_ok, res_term); +} + +static ERL_NIF_TERM setlocale_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { +#ifdef __WIN32__ + if (!SetConsoleOutputCP(CP_UTF8)) { + return make_errno_error(env, "SetConsoleOutputCP"); + } + return atom_true; +#elif defined(PRIMITIVE_UTF8_CHECK) + setlocale(LC_CTYPE, ""); /* Set international environment, + ignore result */ + return enif_make_atom(env, "primitive"); +#else + char *l = setlocale(LC_CTYPE, ""); /* Set international environment */ + if (l != NULL) { + if (strcmp(nl_langinfo(CODESET), "UTF-8") == 0) + return atom_true; + } + return atom_false; +#endif +} + +static ERL_NIF_TERM tty_tgetent_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { +#ifdef HAVE_TERMCAP + ErlNifBinary TERM; + if (!enif_inspect_iolist_as_binary(env, argv[0], &TERM)) + return enif_make_badarg(env); + if (tgetent((char *)NULL /* ignored */, (char *)TERM.data) <= 0) { + return make_errno_error(env, "tgetent"); + } + return atom_ok; +#else + return make_enotsup(env); +#endif +} + +static ERL_NIF_TERM tty_tgetnum_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { +#ifdef HAVE_TERMCAP + ErlNifBinary TERM; + if (!enif_inspect_iolist_as_binary(env, argv[0], &TERM)) + return enif_make_badarg(env); + return enif_make_int(env, tgetnum((char*)TERM.data)); +#else + return make_enotsup(env); +#endif +} + +static ERL_NIF_TERM tty_tgetflag_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { +#ifdef HAVE_TERMCAP + ErlNifBinary TERM; + if (!enif_inspect_iolist_as_binary(env, argv[0], &TERM)) + return enif_make_badarg(env); + if (tgetflag((char*)TERM.data)) + return atom_true; + return atom_false; +#else + return make_enotsup(env); +#endif +} + +static ERL_NIF_TERM tty_tgetstr_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { +#ifdef HAVE_TERMCAP + ErlNifBinary TERM, ret; + /* tgetstr seems to use a lot of stack buffer space, + so buff needs to be relatively "small" */ + char *str = NULL; + char buff[BUFSIZ] = {0}; + + if (!enif_inspect_iolist_as_binary(env, argv[0], &TERM)) + return enif_make_badarg(env); + str = tgetstr((char*)TERM.data, (char**)&buff); + if (!str) return atom_false; + enif_alloc_binary(strlen(str), &ret); + memcpy(ret.data, str, strlen(str)); + return enif_make_tuple2( + env, atom_ok, enif_make_binary(env, &ret)); +#else + return make_enotsup(env); +#endif +} + +#ifdef HAVE_TERMCAP +static int tputs_buffer_index; +static unsigned char tputs_buffer[1024]; + +#if defined(__sun) && defined(__SVR4) /* Solaris */ +static int tty_puts_putc(char c) { +#else +static int tty_puts_putc(int c) { +#endif + tputs_buffer[tputs_buffer_index++] = (unsigned char)c; + return 0; +} +#endif + +static ERL_NIF_TERM tty_tgoto_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { +#ifdef HAVE_TERMCAP + ErlNifBinary TERM; + char *ent; + int value1, value2 = 0; + if (!enif_inspect_iolist_as_binary(env, argv[0], &TERM) || + !enif_get_int(env, argv[1], &value1)) + return enif_make_badarg(env); + if (argc == 2) { + ent = tgoto((char*)TERM.data, 0, value1); + } else { + ASSERT(argc == 3); + ent = tgoto((char*)TERM.data, value1, value2); + } + if (!ent) return make_errno_error(env, "tgoto"); + + tputs_buffer_index = 0; + if (tputs(ent, 1, tty_puts_putc)) { + return make_errno_error(env, "tputs"); + } else { + ERL_NIF_TERM ret; + unsigned char *buff = enif_make_new_binary(env, tputs_buffer_index, &ret); + memcpy(buff, tputs_buffer, tputs_buffer_index); + return enif_make_tuple2(env, atom_ok, ret); + } +#else + return make_enotsup(env); +#endif +} + +static ERL_NIF_TERM tty_create_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + + TTYResource *tty = enif_alloc_resource(tty_rt, sizeof(TTYResource)); + ERL_NIF_TERM tty_term; + memset(tty, 0, sizeof(*tty)); +#ifndef __WIN32__ + tty->ifd = 0; + tty->ofd = 1; +#else + tty->ifd = GetStdHandle(STD_INPUT_HANDLE); + tty->ofd = GetStdHandle(STD_OUTPUT_HANDLE); +#endif + + tty_term = enif_make_resource(env, tty); + enif_release_resource(tty); + + enif_set_pid_undefined(&tty->self); + enif_set_pid_undefined(&tty->reader); + + return enif_make_tuple2(env, atom_ok, tty_term); +} + +static ERL_NIF_TERM tty_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + +#if defined(HAVE_TERMCAP) || defined(__WIN32__) + ERL_NIF_TERM canon, echo, sig; + TTYResource *tty; + int fd; + + if (argc != 3 || + !tty_get_fd(env, argv[1], &fd) || + !enif_is_map(env, argv[2])) { + return enif_make_badarg(env); + } + + if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty)) + return enif_make_badarg(env); + + if (!enif_get_map_value(env, argv[2], enif_make_atom(env,"canon"), &canon)) + canon = enif_make_atom(env, "undefined"); + if (!enif_get_map_value(env, argv[2], enif_make_atom(env,"echo"), &echo)) + echo = enif_make_atom(env, "undefined"); + if (!enif_get_map_value(env, argv[2], enif_make_atom(env,"sig"), &sig)) + sig = enif_make_atom(env, "undefined"); + +#ifndef __WIN32__ + if (tcgetattr(fd, &tty->tty_rmode) < 0) { + return make_errno_error(env, "tcgetattr"); + } + + tty->tty_smode = tty->tty_rmode; + + /* Default characteristics for all usage including termcap output. */ + tty->tty_smode.c_iflag &= ~ISTRIP; + + /* erts_fprintf(stderr,"canon %T\r\n", canon); */ + /* Turn canonical (line mode) on off. */ + if (enif_is_identical(canon, atom_true)) { + tty->tty_smode.c_iflag |= ICRNL; + tty->tty_smode.c_lflag |= ICANON; + tty->tty_smode.c_oflag |= OPOST; + tty->tty_smode.c_cc[VEOF] = tty->tty_rmode.c_cc[VEOF]; +#ifdef VDSUSP + tty->tty_smode.c_cc[VDSUSP] = tty->tty_rmode.c_cc[VDSUSP]; +#endif + } + if (enif_is_identical(canon, atom_false)) { + tty->tty_smode.c_iflag &= ~ICRNL; + tty->tty_smode.c_lflag &= ~ICANON; + tty->tty_smode.c_oflag &= ~OPOST; + + tty->tty_smode.c_cc[VMIN] = 1; + tty->tty_smode.c_cc[VTIME] = 0; +#ifdef VDSUSP + tty->tty_smode.c_cc[VDSUSP] = 0; +#endif + } + + /* Turn echo on or off. */ + /* erts_fprintf(stderr,"echo %T\r\n", echo); */ + if (enif_is_identical(echo, atom_true)) + tty->tty_smode.c_lflag |= ECHO; + if (enif_is_identical(echo, atom_false)) + tty->tty_smode.c_lflag &= ~ECHO; + + /* erts_fprintf(stderr,"sig %T\r\n", sig); */ + /* Set extra characteristics for "RAW" mode, no signals. */ + if (enif_is_identical(sig, atom_true)) { + /* Ignore IMAXBEL as not POSIX. */ +#ifndef QNX + tty->tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON|IXANY); +#else + tty->tty_smode.c_iflag |= (BRKINT|IGNPAR|ICRNL|IXON); +#endif + tty->tty_smode.c_lflag |= (ISIG|IEXTEN); + } + if (enif_is_identical(sig, atom_false)) { + /* Ignore IMAXBEL as not POSIX. */ +#ifndef QNX + tty->tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON|IXANY); +#else + tty->tty_smode.c_iflag &= ~(BRKINT|IGNPAR|ICRNL|IXON); +#endif + tty->tty_smode.c_lflag &= ~(ISIG|IEXTEN); + } + +#else + /* Set output mode to handle virtual terminal sequences */ + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut == INVALID_HANDLE_VALUE) + { + return make_errno_error(env, "GetStdHandle"); + } + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + if (hIn == INVALID_HANDLE_VALUE) + { + return make_errno_error(env, "GetStdHandle"); + } + + DWORD dwOriginalOutMode = 0; + DWORD dwOriginalInMode = 0; + if (!GetConsoleMode(hOut, &dwOriginalOutMode)) + { + return make_errno_error(env, "GetConsoleMode"); + } + if (!GetConsoleMode(hIn, &dwOriginalInMode)) + { + return make_errno_error(env, "GetConsoleMode"); + } + + /* fprintf(stderr, "origOutMode: %x origInMode: %x\r\n", */ + /* dwOriginalOutMode, dwOriginalInMode); */ + + DWORD dwRequestedOutModes = ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + DWORD dwRequestedInModes = ENABLE_VIRTUAL_TERMINAL_INPUT; + DWORD dwDisabledInModes = ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT; + + DWORD dwOutMode = dwOriginalOutMode | dwRequestedOutModes; + if (!SetConsoleMode(hOut, dwOutMode)) + { + /* we failed to set both modes, try to step down mode gracefully. */ + dwRequestedOutModes = ENABLE_VIRTUAL_TERMINAL_PROCESSING; + dwOutMode = dwOriginalOutMode | dwRequestedOutModes; + if (!SetConsoleMode(hOut, dwOutMode)) + { + /* Failed to set any VT mode, can't do anything here. */ + return make_errno_error(env, "SetConsoleMode"); + } + } + + DWORD dwInMode = (dwOriginalInMode | dwRequestedInModes) & ~dwDisabledInModes; + if (!SetConsoleMode(hIn, dwInMode)) + { + /* Failed to set VT input mode, can't do anything here. */ + return make_errno_error(env, "SetConsoleMode"); + } + +#endif /* __WIN32__ */ + + tty->tty = 1; + + return atom_ok; +#else + return make_enotsup(env); +#endif +} + +static ERL_NIF_TERM tty_set_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { +#if defined(HAVE_TERMCAP) || defined(__WIN32__) + TTYResource *tty; + if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty)) + return enif_make_badarg(env); +#ifdef HAVE_TERMCAP + if (tty->tty && tcsetattr(tty->ifd, TCSANOW, &tty->tty_smode) < 0) { + return make_errno_error(env, "tcsetattr"); + } +#endif + enif_self(env, &tty->self); + enif_monitor_process(env, tty, &tty->self, NULL); + return atom_ok; +#else + return make_enotsup(env); +#endif +} + +static ERL_NIF_TERM tty_window_size_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + TTYResource *tty; + int width = -1, height = -1; + if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty)) + return enif_make_badarg(env); + { +#ifdef TIOCGWINSZ + struct winsize ws; + if (ioctl(tty->ifd,TIOCGWINSZ,&ws) == 0) { + if (ws.ws_col > 0) + width = ws.ws_col; + if (ws.ws_row > 0) + height = ws.ws_row; + } else if (ioctl(tty->ofd,TIOCGWINSZ,&ws) == 0) { + if (ws.ws_col > 0) + width = ws.ws_col; + if (ws.ws_row > 0) + height = ws.ws_row; + } +#elif defined(__WIN32__) + CONSOLE_SCREEN_BUFFER_INFOEX buffer_info; + buffer_info.cbSize = sizeof(buffer_info); + if (GetConsoleScreenBufferInfoEx(tty->ofd, &buffer_info)) { + height = buffer_info.dwSize.Y; + width = buffer_info.dwSize.X; + } else { + return make_errno_error(env,"GetConsoleScreenBufferInfoEx"); + } +#endif + } + if (width == -1 && height == -1) { + return make_enotsup(env); + } + return enif_make_tuple2( + env, atom_ok, + enif_make_tuple2( + env, + enif_make_int(env, width), + enif_make_int(env, height) + )); +} + +#ifndef __WIN32__ + +static int tty_signal_fd = -1; + +static RETSIGTYPE tty_cont(int sig) +{ + if (tty_signal_fd != 1) { + while (write(tty_signal_fd, "c", 1) < 0 && errno == EINTR) { }; + } +} + + +static RETSIGTYPE tty_winch(int sig) +{ + if (tty_signal_fd != 1) { + while (write(tty_signal_fd, "w", 1) < 0 && errno == EINTR) { }; + } +} + +#endif + +static ERL_NIF_TERM tty_read_signal_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + TTYResource *tty; + char buff[1]; + ssize_t ret; + ERL_NIF_TERM res; + if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty)) + return enif_make_badarg(env); +#ifndef __WIN32__ + do { + ret = read(tty->signal[0], buff, 1); + } while (ret < 0 && errno == EAGAIN); + + if (ret < 0) { + return make_errno_error(env, "read"); + } else if (ret == 0) { + return make_error(env, enif_make_atom(env,"empty")); + } + + enif_select(env, tty->signal[0], ERL_NIF_SELECT_READ, tty, NULL, argv[1]); + + if (buff[0] == 'w') { + res = enif_make_atom(env, "winch"); + } else if (buff[0] == 'c') { + res = enif_make_atom(env, "cont"); + } else { + res = enif_make_string_len(env, buff, 1, ERL_NIF_LATIN1); + } + return enif_make_tuple2(env, atom_ok, res); +#else + return make_enotsup(env); +#endif +} + +#ifdef THREADED_READED +struct tty_reader_init { + ErlNifEnv *env; + ERL_NIF_TERM tty; +}; + +#define TTY_READER_BUF_SIZE 1024 + +static void *tty_reader_thread(void *args) { + struct tty_reader_init *tty_reader_init = (struct tty_reader_init*)args; + TTYResource *tty; + ErlNifBinary binary; + ErlNifEnv *env = NULL; + ERL_NIF_TERM data[10]; + int cnt = 0; + + enif_alloc_binary(TTY_READER_BUF_SIZE, &binary); + + enif_get_resource(tty_reader_init->env, tty_reader_init->tty, tty_rt, (void **)&tty); + + SET_BLOCKING(tty->ifd); + + while(true) { + ssize_t i = read(tty->ifd, binary.data, TTY_READER_BUF_SIZE); + /* fprintf(stderr,"Read: %ld bytes from %d\r\n", i, tty->ifd); */ + if (i < 0) { + int saved_errno = errno; + if (env) { + ERL_NIF_TERM msg = enif_make_list_from_array(env, data, cnt); + enif_send(env, &tty->self, NULL, enif_make_tuple2(env, atom_input, msg)); + cnt = 0; + env = NULL; + } + if (saved_errno != EAGAIN) { + env = enif_alloc_env(); + errno = saved_errno; + enif_send(env, &tty->self, NULL, make_errno_error(env, "read")); + break; + } + } else { + if (!env) { + env = enif_alloc_env(); + } + enif_realloc_binary(&binary, i); + data[cnt++] = enif_make_binary(env, &binary); + if (cnt == 10 || i != TTY_READER_BUF_SIZE) { + ERL_NIF_TERM msg = enif_make_list_from_array(env, data, cnt); + enif_send(env, &tty->self, NULL, enif_make_tuple2(env, atom_input, msg)); + cnt = 0; + env = NULL; + } + enif_alloc_binary(TTY_READER_BUF_SIZE, &binary); + } + } + + enif_free_env(tty_reader_init->env); + enif_free(tty_reader_init); + return (void*)0; +} + +#endif + +static ERL_NIF_TERM tty_select_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { + TTYResource *tty; +#ifdef THREADED_READER + struct tty_reader_init *tty_reader_init; +#endif +#ifndef __WIN32__ + extern int using_oldshell; /* set this to let the rest of erts know */ +#endif + if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty)) + return enif_make_badarg(env); + +#ifndef __WIN32__ + if (pipe(tty->signal) == -1) { + return make_errno_error(env, "pipe"); + } + SET_NONBLOCKING(tty->signal[0]); + enif_select(env, tty->signal[0], ERL_NIF_SELECT_READ, tty, NULL, argv[1]); + tty_signal_fd = tty->signal[1]; + + sys_signal(SIGCONT, tty_cont); + sys_signal(SIGWINCH, tty_winch); + + using_oldshell = 0; +#endif + + enif_select(env, tty->ifd, ERL_NIF_SELECT_READ, tty, NULL, argv[2]); + + enif_self(env, &tty->reader); + enif_monitor_process(env, tty, &tty->reader, NULL); + +#ifdef THREADED_READER + + tty_reader_init = enif_alloc(sizeof(struct tty_reader_init)); + tty_reader_init->env = enif_alloc_env(); + tty_reader_init->tty = enif_make_copy(tty_reader_init->env, argv[0]); + + if (enif_thread_create( + "stdin_reader", + &tty->reader_tid, + tty_reader_thread, tty_reader_init, NULL)) { + enif_free(tty_reader_init); + return make_errno_error(env, "enif_thread_create"); + } +#endif + return atom_ok; +} + +static void tty_monitor_down(ErlNifEnv* caller_env, void* obj, ErlNifPid* pid, ErlNifMonitor* mon) { + TTYResource *tty = obj; +#ifdef HAVE_TERMCAP + if (enif_compare_pids(pid, &tty->self) == 0) { + tcsetattr(tty->ifd, TCSANOW, &tty->tty_rmode); + } +#endif + if (enif_compare_pids(pid, &tty->reader) == 0) { + enif_select(caller_env, tty->ifd, ERL_NIF_SELECT_STOP, tty, NULL, atom_undefined); +#ifndef __WIN32__ + enif_select(caller_env, tty->signal[0], ERL_NIF_SELECT_STOP, tty, NULL, atom_undefined); + close(tty->signal[1]); + sys_signal(SIGCONT, SIG_DFL); + sys_signal(SIGWINCH, SIG_DFL); +#endif + } +} + +static void tty_select_stop(ErlNifEnv* caller_env, void* obj, ErlNifEvent event, int is_direct_call) { +/* Only used to close the signal pipe on unix */ +#ifndef __WIN32__ + if (event != 0) + close(event); +#endif +} + +static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +{ + + ErlNifResourceTypeInit rt = { + NULL /* dtor */, + tty_select_stop, + tty_monitor_down}; + +#define ATOM_DECL(A) atom_##A = enif_make_atom(env, #A) +ATOMS +#undef ATOM_DECL + + *priv_data = NULL; + + tty_rt = enif_open_resource_type_x(env, "tty", &rt, ERL_NIF_RT_CREATE, NULL); + + return 0; +} + +static void unload(ErlNifEnv* env, void* priv_data) +{ + +} + +static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, + ERL_NIF_TERM load_info) +{ + if (*old_priv_data != NULL) { + return -1; /* Don't know how to do that */ + } + if (*priv_data != NULL) { + return -1; /* Don't know how to do that */ + } + if (load(env, priv_data, load_info)) { + return -1; + } + return 0; +} diff --git a/erts/emulator/sys/win32/sys.c b/erts/emulator/sys/win32/sys.c index c3505eddc54d..5f269ea938b1 100644 --- a/erts/emulator/sys/win32/sys.c +++ b/erts/emulator/sys/win32/sys.c @@ -32,7 +32,6 @@ #include "erl_sys_driver.h" #include "global.h" #include "erl_threads.h" -#include "../../drivers/win32/win_con.h" #include "erl_cpu_topology.h" #include @@ -125,8 +124,6 @@ BOOL WINAPI ctrl_handler(DWORD dwCtrlType); static int max_files = 1024; static BOOL use_named_pipes; -static BOOL win_console = FALSE; - static OSVERSIONINFO int_os_version; /* Version information for Win32. */ @@ -205,10 +202,6 @@ erts_sys_misc_mem_sz(void) */ void sys_tty_reset(int exit_code) { - if (exit_code == ERTS_ERROR_EXIT) - ConWaitForExit(); - else - ConNormalExit(); } void erl_sys_args(int* argc, char** argv) @@ -304,25 +297,16 @@ int erts_set_signal(Eterm signal, Eterm type) { return 0; } +static DWORD dwOriginalOutMode = 0; +static DWORD dwOriginalInMode = 0; + static void init_console(void) { - char* mode = erts_read_env("ERL_CONSOLE_MODE"); - - if (!mode || strcmp(mode, "window") == 0) { - win_console = TRUE; - ConInit(); - /*nohup = 0;*/ - } else if (strncmp(mode, "tty:", 4) == 0) { - if (mode[5] == 'c') { - setvbuf(stdout, NULL, _IONBF, 0); - } - if (mode[6] == 'c') { - setvbuf(stderr, NULL, _IONBF, 0); - } - } - - erts_free_read_env(mode); + GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &dwOriginalOutMode); + GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &dwOriginalInMode); + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); } int sys_max_files(void) @@ -2194,7 +2178,6 @@ fd_start(ErlDrvPort port_num, char* name, SysDriverOpts* opts) return ERL_DRV_ERROR_GENERAL; } - fd_driver_input = &(dp->in); dp->in.flags = DF_XLAT_CR; if (is_std_error) { dp->out.flags |= DF_DROP_IF_INVH; /* Just drop messages if stderror @@ -2202,6 +2185,7 @@ fd_start(ErlDrvPort port_num, char* name, SysDriverOpts* opts) } if ( in == 0 && out == 1) { + fd_driver_input = &(dp->in); save_01_port = dp; } else if (in == 2 && out == 2) { save_22_port = dp; @@ -2945,10 +2929,6 @@ sys_get_key(int fd) { ASSERT(fd == 0); - if (win_console) { - return ConGetKey(); - } - /* * Black magic follows. (Code stolen from get_overlapped_result()) */ @@ -2974,6 +2954,32 @@ sys_get_key(int fd) } } } + else { + char c[64]; + DWORD dwBytesRead, dwCurrentOutMode = 0, dwCurrentInMode = 0; + + /* Get current console information */ + GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &dwCurrentOutMode); + GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &dwCurrentInMode); + + /* Set the a "oldstyle" terminal with line input that we can use ReadFile on */ + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), dwOriginalOutMode); + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), + ENABLE_PROCESSED_INPUT | + ENABLE_LINE_INPUT | + ENABLE_ECHO_INPUT | + ENABLE_INSERT_MODE | + ENABLE_QUICK_EDIT_MODE | + ENABLE_AUTO_POSITION + ); + + if (ReadFile(GetStdHandle(STD_INPUT_HANDLE), &c, sizeof(c), &dwBytesRead, NULL) && dwBytesRead > 0) { + /* Restore original console information */ + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), dwCurrentOutMode); + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), dwCurrentInMode); + return c[0]; + } + } return '*'; /* Error! */ } diff --git a/erts/emulator/sys/win32/sys_interrupt.c b/erts/emulator/sys/win32/sys_interrupt.c index cee269eed4b1..b8821e47aa61 100644 --- a/erts/emulator/sys/win32/sys_interrupt.c +++ b/erts/emulator/sys/win32/sys_interrupt.c @@ -23,11 +23,13 @@ #ifdef HAVE_CONFIG_H # include "config.h" #endif + +#define ERTS_WANT_BREAK_HANDLING + #include "sys.h" #include "erl_alloc.h" #include "erl_thr_progress.h" #include "erl_driver.h" -#include "../../drivers/win32/win_con.h" #if defined(__GNUC__) # define WIN_SYS_INLINE __inline__ @@ -82,7 +84,6 @@ BOOL WINAPI ctrl_handler_ignore_break(DWORD dwCtrlType) } void erts_set_ignore_break(void) { - ConSetCtrlHandler(ctrl_handler_ignore_break); SetConsoleCtrlHandler(ctrl_handler_ignore_break, TRUE); } @@ -92,6 +93,9 @@ BOOL WINAPI ctrl_handler_replace_intr(DWORD dwCtrlType) case CTRL_C_EVENT: return FALSE; case CTRL_BREAK_EVENT: + if (ERTS_BREAK_REQUESTED) { + erts_exit(ERTS_INTR_EXIT, ""); + } SetEvent(erts_sys_break_event); break; case CTRL_LOGOFF_EVENT: @@ -110,7 +114,11 @@ BOOL WINAPI ctrl_handler_replace_intr(DWORD dwCtrlType) /* Don't use ctrl-c for break handler but let it be used by the shell instead (see user_drv.erl) */ void erts_replace_intr(void) { - ConSetCtrlHandler(ctrl_handler_replace_intr); + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + DWORD dwOriginalInMode = 0; + if (GetConsoleMode(hIn, &dwOriginalInMode)) { + SetConsoleMode(hIn, dwOriginalInMode & ~ENABLE_PROCESSED_INPUT); + } SetConsoleCtrlHandler(ctrl_handler_replace_intr, TRUE); } @@ -119,6 +127,9 @@ BOOL WINAPI ctrl_handler(DWORD dwCtrlType) switch (dwCtrlType) { case CTRL_C_EVENT: case CTRL_BREAK_EVENT: + if (ERTS_BREAK_REQUESTED) { + erts_exit(ERTS_INTR_EXIT, ""); + } SetEvent(erts_sys_break_event); break; case CTRL_LOGOFF_EVENT: @@ -135,7 +146,6 @@ BOOL WINAPI ctrl_handler(DWORD dwCtrlType) void init_break_handler() { - ConSetCtrlHandler(ctrl_handler); SetConsoleCtrlHandler(ctrl_handler, TRUE); } diff --git a/erts/emulator/test/statistics_SUITE.erl b/erts/emulator/test/statistics_SUITE.erl index c230aa9f194c..014c0a114e3d 100644 --- a/erts/emulator/test/statistics_SUITE.erl +++ b/erts/emulator/test/statistics_SUITE.erl @@ -382,7 +382,7 @@ run_scheduler_wall_time_test(Type) -> Pid end, StartDirtyHog = fun(Func) -> - F = fun () -> + F = fun() -> erts_debug:Func(alive_waitexiting, MeMySelfAndI) end, @@ -470,7 +470,7 @@ online_statistics(Stats) -> DirtyCPUSchedulersOnline = erlang:system_info(dirty_cpu_schedulers_online), DirtyIOSchedulersOnline = erlang:system_info(dirty_io_schedulers), SortedStats = lists:sort(Stats), - ct:pal("Stats: ~p~n", [SortedStats]), + ct:log("Stats: ~p~n", [SortedStats]), SchedulersStats = lists:sublist(SortedStats, 1, SchedulersOnline), DirtyCPUSchedulersStats = diff --git a/erts/etc/common/Makefile.in b/erts/etc/common/Makefile.in index 42d4395eb246..37f77d294b5f 100644 --- a/erts/etc/common/Makefile.in +++ b/erts/etc/common/Makefile.in @@ -168,7 +168,6 @@ INSTALL_PROGS = \ $(BINDIR)/erlsrv.exe \ $(BINDIR)/erl.exe \ $(BINDIR)/erl_log.exe\ - $(BINDIR)/werl.exe \ $(BINDIR)/$(ERLEXEC) \ $(INSTALL_EMBEDDED_PROGS) @@ -267,13 +266,12 @@ endif rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/vxcall.o rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/erl.o rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/erl_log.o - rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/werl.o rm -f $(TEXTFILES) rm -f *~ core #------------------------------------------------------------------------ # Windows specific targets -# The windows platform is quite different from the others. erl/werl are small C programs +# The windows platform is quite different from the others. erl are small C programs # loading a DLL. INI files are used instead of environment variables and the Install # script is actually a program, also Install has an INI file which tells of emulator # versions etc. @@ -287,9 +285,6 @@ $(BINDIR)/$(ERLEXEC): $(OBJDIR)/erlexec.o $(OBJDIR)/win_erlexec.o $(OBJDIR)/init $(BINDIR)/erl@EXEEXT@: $(OBJDIR)/erl.o $(OBJDIR)/init_file.o $(OBJDIR)/$(ERLRES_OBJ) $(V_LD) $(LDFLAGS) -o $@ $(OBJDIR)/erl.o $(OBJDIR)/init_file.o $(OBJDIR)/$(ERLRES_OBJ) -$(BINDIR)/werl@EXEEXT@: $(OBJDIR)/werl.o $(OBJDIR)/init_file.o $(OBJDIR)/$(ERLRES_OBJ) - $(V_LD) $(LDFLAGS) -o $@ $(OBJDIR)/werl.o $(OBJDIR)/init_file.o $(OBJDIR)/$(ERLRES_OBJ) - $(BINDIR)/erl_log@EXEEXT@: $(OBJDIR)/erl_log.o $(V_LD) $(LDFLAGS) -o $@ $(OBJDIR)/erl_log.o @@ -367,10 +362,6 @@ $(OBJDIR)/erlsrv_util.o: $(WINETC)/erlsrv/erlsrv_util.c $(ERLSRV_HEADERS) \ $(OBJDIR)/erlsrv_logmess.h $(RC_GENERATED) $(V_CC) $(CFLAGS) -I$(OBJDIR) $(MT_FLAG) -o $@ -c $< -$(OBJDIR)/werl.o: $(WINETC)/erl.c $(WINETC)/init_file.h $(RC_GENERATED) - $(V_CC) $(CFLAGS) -DBUILD_TYPE=\"-$(TYPE)\" -DERL_RUN_SHARED_LIB=1 \ - -DWIN32_WERL -o $@ -c $(WINETC)/erl.c - $(OBJDIR)/erl_log.o: $(WINETC)/erl_log.c $(RC_GENERATED) $(V_CC) $(CFLAGS) -DBUILD_TYPE=\"-$(TYPE)\" -DERL_RUN_SHARED_LIB=1 \ -o $@ -c $(WINETC)/erl_log.c diff --git a/erts/etc/common/erlexec.c b/erts/etc/common/erlexec.c index 5d4432789be2..b666b4adec17 100644 --- a/erts/etc/common/erlexec.c +++ b/erts/etc/common/erlexec.c @@ -39,14 +39,12 @@ #define DIRSEP "\\" #define PATHSEP ";" #define NULL_DEVICE "nul" -#define BINARY_EXT "" #define DLL_EXT ".dll" #define EMULATOR_EXECUTABLE "beam.dll" #else #define PATHSEP ":" #define DIRSEP "/" #define NULL_DEVICE "/dev/null" -#define BINARY_EXT "" #define EMULATOR_EXECUTABLE "beam" #endif @@ -218,7 +216,6 @@ static char* possibly_quote(char* arg); /* * Functions from win_erlexec.c */ -int start_win_emulator(char* emu, char *startprog,char** argv, int start_detached); int start_emulator(char* emu, char*start_prog, char** argv, int start_detached); #endif @@ -246,7 +243,7 @@ static const char* emu_flavor = DEFAULT_SUFFIX; /* Flavor of emulator (smp, jit #ifdef __WIN32__ static char *start_emulator_program = NULL; /* For detached mode - - erl.exe/werl.exe */ + erl.exe */ static char* key_val_name = ERLANG_VERSION; /* Used by the registry * access functions. */ @@ -256,7 +253,6 @@ static int config_script_cnt = 0; static int got_start_erl = 0; static HANDLE this_module_handle; -static int run_werl; static WCHAR *utf8_to_utf16(unsigned char *bytes); static char *utf16_to_utf8(WCHAR *wstr); static WCHAR *latin1_to_utf16(char *str); @@ -414,7 +410,7 @@ static void add_boot_config(void) #define NEXT_ARG_CHECK() NEXT_ARG_CHECK_NAMED(argv[i]) #ifdef __WIN32__ -__declspec(dllexport) int win_erlexec(int argc, char **argv, HANDLE module, int windowed) +__declspec(dllexport) int win_erlexec(int argc, char **argv, HANDLE module) #else int main(int argc, char **argv) #endif @@ -435,7 +431,6 @@ int main(int argc, char **argv) #ifdef __WIN32__ this_module_handle = module; - run_werl = windowed; /* if we started this erl just to get a detached emulator, * the arguments are already prepared for beam, so we skip * directly to start_emulator */ @@ -534,7 +529,7 @@ int main(int argc, char **argv) emu = add_extra_suffixes(emu); emu_name = strsave(emu); - erts_snprintf(tmpStr, sizeof(tmpStr), "%s" DIRSEP "%s" BINARY_EXT, bindir, emu); + erts_snprintf(tmpStr, sizeof(tmpStr), "%s" DIRSEP "%s", bindir, emu); emu = strsave(tmpStr); s = get_env("ESCRIPT_NAME"); @@ -1127,24 +1122,7 @@ int main(int argc, char **argv) skip_arg_massage: /*DebugBreak();*/ - if (run_werl) { - if (start_detached) { - char *p; - /* transform werl to erl */ - p = start_emulator_program+strlen(start_emulator_program); - while (--p >= start_emulator_program && *p != '/' && *p != '\\' && - *p != 'W' && *p != 'w') - ; - if (p >= start_emulator_program && (*p == 'W' || *p == 'w') && - (p[1] == 'E' || p[1] == 'e') && (p[2] == 'R' || p[2] == 'r') && - (p[3] == 'L' || p[3] == 'l')) { - memmove(p,p+1,strlen(p)); - } - } - return start_win_emulator(emu, start_emulator_program, Eargsp, start_detached); - } else { - return start_emulator(emu, start_emulator_program, Eargsp, start_detached); - } + return start_emulator(emu, start_emulator_program, Eargsp, start_detached); #else @@ -1610,6 +1588,14 @@ static void get_parameters(int argc, char** argv) emu = EMULATOR_EXECUTABLE; start_emulator_program = strsave(argv[0]); + /* in wsl argv[0] is given as "erl.exe", but start_emulator_program should be + an absolute path, so we prepend BINDIR to it */ + if (strcmp(start_emulator_program, "erl.exe") == 0) { + erts_snprintf(tmpStr, sizeof(tmpStr), "%s" DIRSEP "%s", bindir, + start_emulator_program); + start_emulator_program = strsave(tmpStr); + } + free(ini_filename); } diff --git a/erts/etc/common/etc_common.h b/erts/etc/common/etc_common.h index 289a33b42a82..865cb6a6c6ff 100644 --- a/erts/etc/common/etc_common.h +++ b/erts/etc/common/etc_common.h @@ -35,6 +35,7 @@ # include # include # include +# include // _getcwd #endif #include diff --git a/erts/etc/win32/Install.c b/erts/etc/win32/Install.c index 1b8f894dc946..497dd537fd2a 100644 --- a/erts/etc/win32/Install.c +++ b/erts/etc/win32/Install.c @@ -24,6 +24,7 @@ */ #include +#include #include #include #include "init_file.h" @@ -47,11 +48,12 @@ int wmain(int argc, wchar_t **argv) InitFile *ini_file; InitSection *ini_section; HANDLE module = GetModuleHandle(NULL); - wchar_t *binaries[] = { L"erl.exe", L"werl.exe", L"erlc.exe", L"erl_call.exe", + wchar_t *binaries[] = { L"erl.exe", L"erlc.exe", L"erl_call.exe", L"dialyzer.exe", L"typer.exe", L"escript.exe", L"ct_run.exe", NULL }; wchar_t *scripts[] = { L"start_clean.boot", L"start_sasl.boot", L"no_dot_erlang.boot", NULL }; + wchar_t *links[][2] = { { L"erl.exe", L"werl.exe" }, NULL }; wchar_t fromname[MAX_PATH]; wchar_t toname[MAX_PATH]; size_t converted; @@ -175,7 +177,32 @@ int wmain(int argc, wchar_t **argv) fprintf(stderr,"Continuing installation anyway...\n"); } } - + + for (i = 0; links[i][0] != NULL; ++i) { + swprintf(toname,MAX_PATH,L"%s\\%s",bin_dir,links[i][1]); + if (!CreateSymbolicLinkW(toname,links[i][0],0)) { + DWORD err = GetLastError(); + if (err == ERROR_PRIVILEGE_NOT_HELD) { + fprintf(stderr,"Must be administrator to create link, copying %S instead.\n", + links[i][0]); + swprintf(fromname,MAX_PATH,L"%s\\%s",bin_dir,links[i][0]); + if (!CopyFileW(fromname,toname,FALSE)) { + fprintf(stderr,"Could not copy file %S to %S\n", + fromname,toname); + fprintf(stderr,"Continuing installation anyway...\n"); + } + } else { + wchar_t buf[256]; + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buf, (sizeof(buf) / sizeof(wchar_t)), NULL); + fprintf(stderr,"Could not create links from %S to %S %d: %S\n", + fromname, toname, err, buf); + fprintf(stderr,"Continuing installation anyway...\n"); + } + } + } + for (i = 0; scripts[i] != NULL; ++i) { swprintf(fromname,MAX_PATH,L"%s\\%s",release_dir,scripts[i]); swprintf(toname,MAX_PATH,L"%s\\%s",bin_dir,scripts[i]); diff --git a/erts/etc/win32/Makefile b/erts/etc/win32/Makefile index c6376ebe7405..f553f83e9220 100644 --- a/erts/etc/win32/Makefile +++ b/erts/etc/win32/Makefile @@ -39,7 +39,6 @@ ROOTDIR = $(ERL_TOP)/erts INSTALL_PROGS = \ $(BINDIR)/inet_gethost.exe \ $(BINDIR)/erl.exe \ - $(BINDIR)/werl.exe \ $(BINDIR)/heart.exe \ $(BINDIR)/erlc.exe \ $(BINDIR)/erlsrv.exe \ diff --git a/erts/etc/win32/erl.c b/erts/etc/win32/erl.c index 99a41b99e5f9..31650de83198 100644 --- a/erts/etc/win32/erl.c +++ b/erts/etc/win32/erl.c @@ -23,7 +23,7 @@ #include #include "init_file.h" -typedef int ErlexecFunction(int, char **, HANDLE, int); +typedef int ErlexecFunction(int, char **, HANDLE); #define INI_FILENAME L"erl.ini" #define INI_SECTION "erlang" @@ -35,18 +35,8 @@ static void error(char* format, ...); static wchar_t *erlexec_name; static wchar_t *erlexec_dir; -#ifdef WIN32_WERL -#define WERL 1 -int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, - PWSTR szCmdLine, int iCmdShow) -{ - int argc = __argc; - wchar_t **argv = __wargv; -#else -#define WERL 0 int wmain(int argc, wchar_t **argv) { -#endif HANDLE erlexec_handle; /* Instance */ ErlexecFunction *win_erlexec; wchar_t *path = malloc(100*sizeof(wchar_t)); @@ -120,7 +110,7 @@ int wmain(int argc, wchar_t **argv) } #endif - return (*win_erlexec)(argc,utf8argv,erlexec_handle,WERL); + return (*win_erlexec)(argc,utf8argv,erlexec_handle); } @@ -316,7 +306,6 @@ static void get_parameters(void) free(ini_filename); } - static void error(char* format, ...) { char sbuf[2048]; @@ -326,11 +315,6 @@ static void error(char* format, ...) vsprintf(sbuf, format, ap); va_end(ap); -#ifndef WIN32_WERL - fprintf(stderr, "%s\n", sbuf); -#else - MessageBox(NULL, sbuf, "Werl", MB_OK|MB_ICONERROR); -#endif + fprintf(stderr, "%s\n", sbuf); exit(1); } - diff --git a/erts/etc/win32/nsis/erlang20.nsi b/erts/etc/win32/nsis/erlang20.nsi index ae933f59af97..2e317fd3629a 100644 --- a/erts/etc/win32/nsis/erlang20.nsi +++ b/erts/etc/win32/nsis/erlang20.nsi @@ -190,7 +190,7 @@ cp_files: CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER" continue_create: CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Erlang.lnk" \ - "$INSTDIR\bin\werl.exe" + "$INSTDIR\bin\erl.exe" !insertmacro MUI_STARTMENU_WRITE_END ; And once again, the verbosity... diff --git a/erts/etc/win32/win_erlexec.c b/erts/etc/win32/win_erlexec.c index 7b21ed37850c..53b1ac92b0a8 100644 --- a/erts/etc/win32/win_erlexec.c +++ b/erts/etc/win32/win_erlexec.c @@ -48,7 +48,6 @@ static char* win32_errorstr(int error); static int has_console(void); static char** fnuttify_argv(char **argv); static void free_fnuttified(char **v); -static int windowed = 0; #ifdef LOAD_BEAM_DYNAMICALLY typedef int SysGetKeyFunction(int); @@ -133,103 +132,6 @@ free_env_val(char *value) free(value); } - -int -start_win_emulator(char* utf8emu, char *utf8start_prog, char** utf8argv, int start_detached) -{ - int len; - int argc = 0; - - windowed = 1; - while (utf8argv[argc] != NULL) { - ++argc; - } - - if (start_detached) { - wchar_t *start_prog=NULL; - int result; - int i; - wchar_t **argv; - close(0); - close(1); - close(2); - - set_env("ERL_CONSOLE_MODE", "detached"); - set_env(DLL_ENV, utf8emu); - - utf8argv[0] = utf8start_prog; - utf8argv = fnuttify_argv(utf8argv); - - len = MultiByteToWideChar(CP_UTF8, 0, utf8start_prog, -1, NULL, 0); - start_prog = malloc(len*sizeof(wchar_t)); - MultiByteToWideChar(CP_UTF8, 0, utf8start_prog, -1, start_prog, len); - - /* Convert utf8argv to multibyte argv */ - argv = malloc((argc+1) * sizeof(wchar_t*)); - for (i=0; i>, + down = <<"\n">>, + left = <<"\b">>, + right = <<"\e[C">>, + %% Tab to next 8 column windows is "\e[1I", for unix "ta" termcap + tab = <<"\e[1I">>, + insert = false, + delete = false, + position = <<"\e[6n">>, %% "u7" on my Linux + position_reply = <<"\e\\[([0-9]+);([0-9]+)R">>, + %% Copied from https://github.com/chalk/ansi-regex/blob/main/index.js + ansi_regexp = <<"^[\e",194,155,"][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?",7,")|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))">>, + %% The SGR (Select Graphic Rendition) parameters https://en.wikipedia.org/wiki/ANSI_escape_code#SGR + ansi_sgr = <<"^[\e",194,155,"]\\[[0-9;:]*m">> + }). + +-type options() :: #{ tty => boolean(), + canon => boolean(), + echo => boolean(), + sig => boolean() + }. +-type request() :: + {putc, unicode:unicode_binary()} | + {insert, unicode:unicode_binary()} | + {delete, integer()} | + {move, integer()} | + beep. +-opaque state() :: #state{}. +-export_type([state/0]). + +-spec on_load() -> ok. +on_load() -> + on_load(#{}). + +-spec on_load(Extra) -> ok when + Extra :: map(). +on_load(Extra) -> + case erlang:load_nif(atom_to_list(?MODULE), Extra) of + ok -> ok; + {error,{reload,_}} -> + ok + end. + +window_size(State = #state{ tty = TTY }) -> + case tty_window_size(TTY) of + {error, enotsup} when map_get(tty, State#state.options) -> + %% When the TTY is enabled, we should return a "dummy" row and column + %% when we cannot find the proper size. + {ok, {State#state.cols, State#state.rows}}; + WinSz -> + WinSz + end. + +-spec init(options()) -> state(). +init(UserOptions) when is_map(UserOptions) -> + + Options = options(UserOptions), + {ok, TTY} = tty_create(), + + %% Initialize the locale to see if we support utf-8 or not + UnicodeMode = + case setlocale() of + primitive -> + lists:any( + fun(Key) -> + string:find(os:getenv(Key,""),"UTF-8") =/= nomatch + end, ["LC_ALL", "LC_CTYPE", "LANG"]); + UnicodeLocale when is_boolean(UnicodeLocale) -> + UnicodeLocale + end, + + init_term(#state{ tty = TTY, unicode = UnicodeMode, options = Options }). +init_term(State = #state{ tty = TTY, options = Options }) -> + TTYState = + case maps:get(tty, Options) of + true -> + ok = tty_init(TTY, stdout, Options), + ok = tty_set(TTY), + init(State, os:type()); + false -> + State + end, + + {ok, Writer} = proc_lib:start_link(?MODULE, writer, [State#state.tty]), + {ok, Reader} = proc_lib:start_link(?MODULE, reader, [[State#state.tty, self()]]), + + update_geometry(TTYState#state{ reader = Reader, writer = Writer }). + +-spec reinit(state(), options()) -> state(). +reinit(State, UserOptions) -> + init_term(State#state{ options = options(UserOptions) }). + +options(UserOptions) -> + maps:merge( + #{ input => true, + tty => true, + canon => false, + echo => false }, UserOptions). + +init(State, {unix,_}) -> + ok = tgetent(os:getenv("TERM")), + + %% See https://www.gnu.org/software/termutils/manual/termcap-1.3/html_mono/termcap.html#SEC23 + %% for a list of all possible termcap capabilities + Cols = case tgetnum("co") of + {ok, Cs} -> Cs; + _ -> (#state{})#state.cols + end, + Up = case tgetstr("up") of + {ok, U} -> U; + false -> error(enotsup) + end, + Down = case tgetstr("do") of + false -> (#state{})#state.down; + {ok, D} -> D + end, + Left = case {tgetflag("bs"),tgetstr("bc")} of + {true,_} -> (#state{})#state.left; + {_,false} -> (#state{})#state.left; + {_,{ok, L}} -> L + end, + + Right = case tgetstr("nd") of + {ok, R} -> R; + false -> error(enotsup) + end, + Insert = + case tgetstr("IC") of + {ok, IC} -> IC; + false -> (#state{})#state.insert + end, + + Tab = case tgetstr("ta") of + {ok, TA} -> TA; + false -> (#state{})#state.tab + end, + + Delete = case tgetstr("DC") of + {ok, DC} -> DC; + false -> (#state{})#state.delete + end, + + Position = case tgetstr("u7") of + {ok, <<"\e[6n">> = U7} -> + %% User 7 should contain the codes for getting + %% cursor position. + % User 6 should contain how to parse the reply + {ok, <<"\e[%i%d;%dR">>} = tgetstr("u6"), + <<"\e[6n">> = U7; + false -> (#state{})#state.position + end, + + State#state{ + cols = Cols, + xn = tgetflag("xn"), + up = Up, + down = Down, + left = Left, + right = Right, + insert = Insert, + delete = Delete, + tab = Tab, + position = Position + }; +init(State, {win32, _}) -> + State#state{ + %% position = false, + xn = true }. + +-spec handles(state()) -> #{ read := undefined | reference(), + write := reference() }. +handles(#state{ reader = undefined, + writer = {_WriterPid, WriterRef}}) -> + #{ read => undefined, write => WriterRef }; +handles(#state{ reader = {_ReaderPid, ReaderRef}, + writer = {_WriterPid, WriterRef}}) -> + #{ read => ReaderRef, write => WriterRef }. + +-spec unicode(state()) -> boolean(). +unicode(State) -> + State#state.unicode. + +-spec unicode(state(), boolean()) -> state(). +unicode(#state{ reader = {ReaderPid, _} } = State, Bool) -> + MonRef = erlang:monitor(process, ReaderPid), + ReaderPid ! {self(), set_unicode_state, Bool}, + receive + {ReaderPid, set_unicode_state, _} -> ok; + {'DOWN',MonRef,_,_,_} -> ok + end, + State#state{ unicode = Bool }. + +-spec handle_signal(state(), winch | cont) -> state(). +handle_signal(State, winch) -> + update_geometry(State); +handle_signal(State, cont) -> + tty_set(State#state.tty), + State. + +reader([TTY, Parent]) -> + register(user_drv_reader, self()), + ReaderRef = make_ref(), + SignalRef = make_ref(), + ok = tty_select(TTY, SignalRef, ReaderRef), + proc_lib:init_ack({ok, {self(), ReaderRef}}), + FromEnc = case os:type() of + {unix, _} -> utf8; + {win32, _} -> {utf16, little} + end, + reader_loop(TTY, Parent, SignalRef, ReaderRef, FromEnc, <<>>). + +reader_loop(TTY, Parent, SignalRef, ReaderRef, FromEnc, Acc) -> + receive + {select, TTY, SignalRef, ready_input} -> + {ok, Signal} = tty_read_signal(TTY, SignalRef), + Parent ! {ReaderRef,{signal,Signal}}, + reader_loop(TTY, Parent, SignalRef, ReaderRef, FromEnc, Acc); + {Parent, set_unicode_state, _} when FromEnc =:= {utf16, little} -> + %% Ignore requests on windows + Parent ! {self(), set_unicode_state, true}, + reader_loop(TTY, Parent, SignalRef, ReaderRef, FromEnc, Acc); + {Parent, set_unicode_state, Bool} -> + Parent ! {self(), set_unicode_state, FromEnc =/= latin1}, + NewFromEnc = if Bool -> utf8; not Bool -> latin1 end, + reader_loop(TTY, Parent, SignalRef, ReaderRef, NewFromEnc, Acc); + {select, TTY, ReaderRef, ready_input} -> + case read_nif(TTY, ReaderRef) of + {error, closed} -> + Parent ! {ReaderRef, eof}, + ok; + {ok, <<>>} -> + %% EAGAIN or EINTR + reader_loop(TTY, Parent, SignalRef, ReaderRef, FromEnc, Acc); + {ok, UtfXBytes} -> + + {Bytes, NewAcc, NewFromEnc} = + case unicode:characters_to_binary([Acc, UtfXBytes], FromEnc, utf8) of + {error, B, Error} -> + %% We should only be able to get incorrect encoded data when + %% using utf8 (i.e. we are on unix) + FromEnc = utf8, + Parent ! {self(), set_unicode_state, false}, + receive + {Parent, set_unicode_state, false} -> + Parent ! {self(), set_unicode_state, true} + end, + receive + {Parent, set_unicode_state, true} -> ok + end, + Latin1Chars = unicode:characters_to_binary(Error, latin1, utf8), + {<>, <<>>, latin1}; + {incomplete, B, Inc} -> + {B, Inc, FromEnc}; + B when is_binary(B) -> + {B, <<>>, FromEnc} + end, + Parent ! {ReaderRef, {data, Bytes}}, + reader_loop(TTY, Parent, SignalRef, ReaderRef, NewFromEnc, NewAcc) + end + end. + +writer(TTY) -> + register(user_drv_writer, self()), + WriterRef = make_ref(), + proc_lib:init_ack({ok, {self(), WriterRef}}), + writer_loop(TTY, WriterRef). + +-spec write(state(), unicode:chardata()) -> ok. +write(#state{ writer = {WriterPid, _}}, Chars) -> + WriterPid ! {write, erlang:iolist_to_iovec(Chars)}, ok. +-spec write(state(), unicode:chardata(), From :: pid()) -> ok. +write(#state{ writer = {WriterPid, _}}, Chars, From) -> + WriterPid ! {write, From, erlang:iolist_to_iovec(Chars)}, ok. + +writer_loop(TTY, WriterRef) -> + receive + {write, []} -> + writer_loop(TTY, WriterRef); + {write, Chars} -> + ok = write_nif(TTY, Chars), + writer_loop(TTY, WriterRef); + {write, From, []} -> + From ! {WriterRef, ok}, + writer_loop(TTY, WriterRef); + {write, From, Chars} -> + case write_nif(TTY, Chars) of + ok -> + From ! {WriterRef, ok}, + writer_loop(TTY, WriterRef); + Else -> + From ! {WriterRef, Else}, + writer_loop(TTY, WriterRef) + end + end. + +-spec handle_request(state(), request()) -> {erlang:iovec(), state()}. +handle_request(State = #state{ options = #{ tty := false } }, Request) -> + case Request of + {putc, Binary} -> + {encode(Binary, State#state.unicode), State}; + beep -> + {<<7>>, State}; + _Ignore -> + {<<>>, State} + end; +%% putc prints Binary and overwrites any existing characters +handle_request(State = #state{ unicode = U }, {putc, Binary}) -> + %% Todo should handle invalid unicode? + {PutBuffer, NewState} = insert_buf(State, Binary), + if NewState#state.buffer_after =:= [] -> + {encode(PutBuffer, U), NewState}; + true -> + %% Delete any overwritten characters after current the cursor + OldLength = logical(State#state.buffer_before), + NewLength = logical(NewState#state.buffer_before), + {_, _, _, NewBA} = split(NewLength - OldLength, NewState#state.buffer_after, U), + {encode(PutBuffer, U), NewState#state{ buffer_after = NewBA }} + end; +handle_request(State = #state{ unicode = U }, {delete, N}) when N > 0 -> + {_DelNum, DelCols, _, NewBA} = split(N, State#state.buffer_after, U), + BBCols = cols(State#state.buffer_before, U), + NewBACols = cols(NewBA, U), + {[encode(NewBA, U), + lists:duplicate(DelCols, $\s), + xnfix(State, BBCols + NewBACols + DelCols), + move_cursor(State, + BBCols + NewBACols + DelCols, + BBCols)], + State#state{ buffer_after = NewBA }}; +handle_request(State = #state{ unicode = U }, {delete, N}) when N < 0 -> + {_DelNum, DelCols, _, NewBB} = split(-N, State#state.buffer_before, U), + NewBBCols = cols(NewBB, U), + BACols = cols(State#state.buffer_after, U), + {[move_cursor(State, NewBBCols + DelCols, NewBBCols), + encode(State#state.buffer_after,U), + lists:duplicate(DelCols, $\s), + xnfix(State, NewBBCols + BACols + DelCols), + move_cursor(State, NewBBCols + BACols + DelCols, NewBBCols)], + State#state{ buffer_before = NewBB } }; +handle_request(State, {delete, 0}) -> + {"",State}; +handle_request(State = #state{ unicode = U }, {move, N}) when N < 0 -> + {_DelNum, DelCols, NewBA, NewBB} = split(-N, State#state.buffer_before, U), + NewBBCols = cols(NewBB, U), + Moves = move_cursor(State, NewBBCols + DelCols, NewBBCols), + {Moves, State#state{ buffer_before = NewBB, + buffer_after = NewBA ++ State#state.buffer_after} }; +handle_request(State = #state{ unicode = U }, {move, N}) when N > 0 -> + {_DelNum, DelCols, NewBB, NewBA} = split(N, State#state.buffer_after, U), + BBCols = cols(State#state.buffer_before, U), + {move_cursor(State, BBCols, BBCols + DelCols), + State#state{ buffer_after = NewBA, + buffer_before = NewBB ++ State#state.buffer_before} }; +handle_request(State, {move, 0}) -> + {"",State}; +handle_request(State = #state{ xn = OrigXn, unicode = U }, {insert, Chars}) -> + {InsertBuffer, NewState0} = insert_buf(State#state{ xn = false }, Chars), + NewState = NewState0#state{ xn = OrigXn }, + BBCols = cols(NewState#state.buffer_before, U), + BACols = cols(NewState#state.buffer_after, U), + {[ encode(InsertBuffer, U), + encode(NewState#state.buffer_after, U), + xnfix(State, BBCols + BACols), + move_cursor(State, BBCols + BACols, BBCols) ], + NewState}; +handle_request(State, beep) -> + {<<7>>, State}; +handle_request(State, Req) -> + erlang:display({unhandled_request, Req}), + {"", State}. + +%% Split the buffer after N logical characters returning +%% the number of real characters deleted and the column length +%% of those characters +split(N, Buff, Unicode) -> + ?dbg({?FUNCTION_NAME, N, Buff, Unicode}), + split(N, Buff, [], 0, 0, Unicode). +split(0, Buff, Acc, Chars, Cols, _Unicode) -> + ?dbg({?FUNCTION_NAME, {Chars, Cols, Acc, Buff}}), + {Chars, Cols, Acc, Buff}; +split(N, _Buff, _Acc, _Chars, _Cols, _Unicode) when N < 0 -> + ok = N; +split(_N, [], Acc, Chars, Cols, _Unicode) -> + {Chars, Cols, Acc, []}; +split(N, [Char | T], Acc, Cnt, Cols, Unicode) when is_integer(Char) -> + split(N - 1, T, [Char | Acc], Cnt + 1, Cols + npwcwidth(Char, Unicode), Unicode); +split(N, [Chars | T], Acc, Cnt, Cols, Unicode) when is_list(Chars) -> + split(N - length(Chars), T, [Chars | Acc], + Cnt + length(Chars), Cols + cols(Chars, Unicode), Unicode); +split(N, [SkipChars | T], Acc, Cnt, Cols, Unicode) when is_binary(SkipChars) -> + split(N, T, [SkipChars | Acc], Cnt, Cols, Unicode). + +logical([]) -> + 0; +logical([Char | T]) when is_integer(Char) -> + 1 + logical(T); +logical([Chars | T]) when is_list(Chars) -> + length(Chars) + logical(T); +logical([SkipChars | T]) when is_binary(SkipChars) -> + logical(T). + +move_cursor(#state{ cols = W } = State, FromCol, ToCol) -> + ?dbg({?FUNCTION_NAME, FromCol, ToCol}), + [case (ToCol div W) - (FromCol div W) of + 0 -> ""; + N when N < 0 -> + ?dbg({move, up, -N}), + move(up, State, -N); + N -> + ?dbg({move, down, N}), + move(down, State, N) + end, + case (ToCol rem W) - (FromCol rem W) of + 0 -> ""; + N when N < 0 -> + ?dbg({down, left, -N}), + move(left, State, -N); + N -> + ?dbg({down, right, N}), + move(right, State, N) + end]. + +move(up, #state{ up = Up }, N) -> + lists:duplicate(N, Up); +move(down, #state{ down = Down }, N) -> + lists:duplicate(N, Down); +move(left, #state{ left = Left }, N) -> + lists:duplicate(N, Left); +move(right, #state{ right = Right }, N) -> + lists:duplicate(N, Right). + +cols([],_Unicode) -> + 0; +cols([Char | T], Unicode) when is_integer(Char) -> + npwcwidth(Char, Unicode) + cols(T, Unicode); +cols([Chars | T], Unicode) when is_list(Chars) -> + cols(Chars, Unicode) + cols(T, Unicode); +cols([SkipSeq | T], Unicode) when is_binary(SkipSeq) -> + %% Any binary should be an ANSI escape sequence + %% so we skip that + cols(T, Unicode). + +update_geometry(State) -> + case tty_window_size(State#state.tty) of + {ok, {Cols, Rows}} when Cols > 0 -> + ?dbg({?FUNCTION_NAME, Cols}), + State#state{ cols = Cols, rows = Rows }; + _Error -> + ?dbg({?FUNCTION_NAME, _Error}), + State + end. + +npwcwidth(Char) -> + npwcwidth(Char, true). +npwcwidth(Char, true) -> + case wcwidth(Char) of + {error, not_printable} -> 0; + {error, enotsup} -> + case unicode_util:is_wide(Char) of + true -> 2; + false -> 1 + end; + C -> C + end; +npwcwidth(Char, false) -> + byte_size(char_to_latin1(Char)). + + +%% Return the xn fix for the current cursor position. +%% We use get_position to figure out if we need to calculate the current columns +%% or not. +%% +%% We need to know the actual column because get_position will return the last +%% column number when the cursor is: +%% * in the last column +%% * off screen +%% +%% and it is when the cursor is off screen that we should do the xnfix. +xnfix(#state{ position = _, unicode = U } = State) -> + xnfix(State, cols(State#state.buffer_before, U)). +%% Return the xn fix for CurrCols location. +xnfix(#state{ xn = true, cols = Cols } = State, CurrCols) + when CurrCols =/= 0, CurrCols rem Cols == 0 -> + [<<"\s">>,move(left, State, 1)]; +xnfix(_, _CurrCols) -> + ?dbg({xnfix, _CurrCols}), + []. + +characters_to_output(Chars) -> + try unicode:characters_to_binary(Chars) of + Binary -> + Binary + catch error:badarg -> + unicode:characters_to_binary( + lists:map( + fun({ansi, Ansi}) -> + Ansi; + (Char) -> + Char + end, Chars) + ) + end. +characters_to_buffer(Chars) -> + lists:flatmap( + fun({ansi, _Ansi}) -> + ""; + (Char) -> + [Char] + end, Chars). + +insert_buf(State, Binary) when is_binary(Binary) -> + insert_buf(State, Binary, [], []). +insert_buf(State, Bin, LineAcc, Acc) -> + case string:next_grapheme(Bin) of + [] -> + NewBB = characters_to_buffer(LineAcc) ++ State#state.buffer_before, + NewState = State#state{ buffer_before = NewBB }, + {[Acc, characters_to_output(lists:reverse(LineAcc)), xnfix(NewState)], + NewState}; + [$\t | Rest] -> + insert_buf(State, Rest, [State#state.tab | LineAcc], Acc); + [$\e | Rest] -> + case re:run(Bin, State#state.ansi_regexp, [unicode]) of + {match, [{0, N}]} -> + <> = Bin, + case re:run(Bin, State#state.ansi_sgr, [unicode]) of + {match, [{0, N}]} -> + %% We include the graphics ansi sequences in the + %% buffer that we step over + insert_buf(State, AnsiRest, [Ansi | LineAcc], Acc); + _ -> + %% Any other ansi sequences are just printed and + %% then dropped as they "should" not effect rendering + insert_buf(State, AnsiRest, [{ansi, Ansi} | LineAcc], Acc) + end; + _ -> + insert_buf(State, Rest, [$\e | LineAcc], Acc) + end; + [NLCR | Rest] when NLCR =:= $\n; NLCR =:= $\r -> + Tail = + if NLCR =:= $\n -> + <<$\r,$\n>>; + true -> + <<$\r>> + end, + insert_buf(State#state{ buffer_before = [], buffer_after = [] }, Rest, [], + [Acc, [characters_to_output(lists:reverse(LineAcc)), Tail]]); + [Cluster | Rest] when is_list(Cluster) -> + insert_buf(State, Rest, [Cluster | LineAcc], Acc); + %% We have gotten a code point that may be part of the previous grapheme cluster. + [Char | Rest] when Char >= 128, LineAcc =:= [], State#state.buffer_before =/= [] -> + [PrevChar | BB] = State#state.buffer_before, + case string:next_grapheme([PrevChar | Bin]) of + [PrevChar | _] -> + %% It was not part of the previous cluster, so just insert + %% it as a normal character + insert_buf(State, Rest, [Char | LineAcc], Acc); + [Cluster | ClusterRest] -> + %% It was part of the previous grapheme cluster, so we output + %% it and insert it into the before_buffer + %% TODO: If an xnfix was done on PrevChar, + %% then we should rewrite the entire grapheme cluster. + {_, ToWrite} = lists:split(length(lists:flatten([PrevChar])), Cluster), + insert_buf(State#state{ buffer_before = [Cluster | BB] }, + ClusterRest, LineAcc, + [Acc, unicode:characters_to_binary(ToWrite)]) + end; + [Char | Rest] when Char >= 128 -> + insert_buf(State, Rest, [Char | LineAcc], Acc); + [Char | Rest] -> + case {isprint(Char), Char} of + {true,_} -> + insert_buf(State, Rest, [Char | LineAcc], Acc); + {false, 8#177} -> %% DEL + insert_buf(State, Rest, ["^?" | LineAcc], Acc); + {false, _} -> + insert_buf(State, Rest, ["^" ++ [Char bor 8#40] | LineAcc], Acc) + end + end. + +-spec to_latin1(erlang:binary()) -> erlang:iovec(). +to_latin1(Bin) -> + case is_usascii(Bin) of + true -> [Bin]; + false -> lists:flatten([binary_to_latin1(Bin)]) + end. + +is_usascii(<>) when Char < 128 -> + is_usascii(T); +is_usascii(<<>>) -> + true; +is_usascii(_) -> + false. + +binary_to_latin1(Buffer) -> + [char_to_latin1(CP) || CP <- unicode:characters_to_list(Buffer)]. +char_to_latin1(UnicodeChar) when UnicodeChar >= 512 -> + <<"\\x{",(integer_to_binary(UnicodeChar, 16))/binary,"}">>; +char_to_latin1(UnicodeChar) when UnicodeChar >= 128 -> + <<"\\",(integer_to_binary(UnicodeChar, 8))/binary>>; +char_to_latin1(UnicodeChar) -> + <>. + +encode(UnicodeChars, true) -> + unicode:characters_to_binary(UnicodeChars); +encode(UnicodeChars, false) -> + to_latin1(unicode:characters_to_binary(UnicodeChars)). + +%% Using get_position adds about 10ms of latency +%% get_position(#state{ position = false }) -> +%% unknown; +%% get_position(State) -> +%% [] = write(State, State#state.position), +%% get_position(State, <<>>). +%% get_position(State, Acc) -> +%% receive +%% {select,TTY,Ref,ready_input} +%% when TTY =:= State#state.tty, +%% Ref =:= State#state.read -> +%% {Bytes, <<>>} = read_input(State#state{ acc = Acc }), +%% case re:run(Bytes, State#state.position_reply, [unicode]) of +%% {match,[{Start,Length},Row,Col]} -> +%% <> = Bytes, +%% %% This should be put in State in order to not screw up the +%% %% message order... +%% [State#state.parent ! {{self(), State#state.tty}, {data, <>}} +%% || Before =/= <<>>, After =/= <<>>], +%% {binary_to_integer(binary:part(Bytes,Row)), +%% binary_to_integer(binary:part(Bytes,Col))}; +%% nomatch -> +%% get_position(State, Bytes) +%% end +%% after 1000 -> +%% unknown +%% end. + +-ifdef(debug). +dbg(_) -> + ok. +-endif + +%% Nif functions +-spec isatty(stdin | stdout | stderr) -> boolean() | ebadf. +isatty(_Fd) -> + erlang:nif_error(undef). +tty_create() -> + erlang:nif_error(undef). +tty_init(_TTY, _Fd, _Options) -> + erlang:nif_error(undef). +tty_set(_TTY) -> + erlang:nif_error(undef). +setlocale() -> + erlang:nif_error(undef). +tty_select(_TTY, _SignalRef, _ReadRef) -> + erlang:nif_error(undef). +write_nif(_TTY, _IOVec) -> + erlang:nif_error(undef). +read_nif(_TTY, _Ref) -> + erlang:nif_error(undef). +tty_window_size(_TTY) -> + erlang:nif_error(undef). +isprint(_Char) -> + erlang:nif_error(undef). +wcwidth(_Char) -> + erlang:nif_error(undef). +sizeof_wchar() -> + erlang:nif_error(undef). +wcswidth(_Char) -> + erlang:nif_error(undef). +tgetent(Char) -> + tgetent_nif([Char,0]). +tgetnum(Char) -> + tgetnum_nif([Char,0]). +tgetflag(Char) -> + tgetflag_nif([Char,0]). +tgetstr(Char) -> + tgetstr_nif([Char,0]). +tgoto(Char, Arg) -> + tgoto_nif([Char,0], Arg). +tgoto(Char, Arg1, Arg2) -> + tgoto_nif([Char,0], Arg1, Arg2). +tgetent_nif(_Char) -> + erlang:nif_error(undef). +tgetnum_nif(_Char) -> + erlang:nif_error(undef). +tgetflag_nif(_Char) -> + erlang:nif_error(undef). +tgetstr_nif(_Char) -> + erlang:nif_error(undef). +tgoto_nif(_Ent, _Arg) -> + erlang:nif_error(undef). +tgoto_nif(_Ent, _Arg1, _Arg2) -> + erlang:nif_error(undef). +tty_read_signal(_TTY, _Ref) -> + erlang:nif_error(undef). + diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl index 4ae0498a8c9f..6c6a4b7024fc 100644 --- a/lib/kernel/src/user_drv.erl +++ b/lib/kernel/src/user_drv.erl @@ -19,13 +19,14 @@ %% -module(user_drv). -%% Basic interface to a port. +%% Basic interface to stdin/stdout. %% %% This is responsible for a couple of things: -%% - Dispatching I/O messages when erl is running as a terminal. +%% - Dispatching I/O messages when erl is running +%% * as a terminal. %% The messages are listed in the type message/0. %% - Any data received from the terminal is sent to the current group like this: -%% `{DrvPid :: pid(), {data, UnicodeBinary :: binary()}}` +%% `{DrvPid :: pid(), {data, UnicodeCharacters :: list()}}` %% - It serves as the job control manager (i.e. what happens when you type ^G) %% - Starts potential -remsh sessions to other nodes %% @@ -76,28 +77,24 @@ -include_lib("kernel/include/logger.hrl"). --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(ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER, 16#018b0900). --define(CTRL_OP_GET_WINSIZE, (100 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)). --define(CTRL_OP_GET_UNICODE_STATE, (101 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)). --define(CTRL_OP_SET_UNICODE_STATE, (102 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)). +-record(state, { tty, write, read, shell_started = true, user, current_group, groups, queue }). --record(state, { port, user, current_group, groups, queue }). - -%% start() +-type shell() :: {module(), atom(), arity()} | {node(), module(), atom(), arity()}. +-type arguments() :: #{ initial_shell => shell() | {remote, unicode:charlist()} }. +%% Default line editing shell -spec start() -> pid(). - -start() -> %Default line editing shell - start(#{}). +start() -> + case init:get_argument(remsh) of + {ok,[[Node]]} -> + start(#{ initial_shell => {remote, Node} }); + E when E =:= error ; E =:= {ok,[[]]} -> + start(#{ }) + end. %% Backwards compatibility with pre OTP-26 for Elixir/LFE etc +-spec start(['tty_sl -c -e'| shell()]) -> pid(); + (arguments()) -> pid(). start(['tty_sl -c -e', Shell]) -> start(#{ initial_shell => Shell }); start(Args) when is_map(Args) -> @@ -109,93 +106,143 @@ start(Args) when is_map(Args) -> callback_mode() -> state_functions. +-spec init(arguments()) -> gen_statem:init_result(init). init(Args) -> process_flag(trap_exit, true), - case catch open_port({spawn,"tty_sl -c -e"}, [eof]) of - {'EXIT', _Reason} -> - {stop, normal}; - Port -> - {ok, init, {Args, #state{ user = start_user() } }, - {next_event, internal, Port}} + prim_tty:on_load(), + IsTTY = prim_tty:isatty(stdin) =:= true andalso prim_tty:isatty(stdout) =:= true, + if IsTTY -> + try prim_tty:init(#{}) of + TTYState -> + init_standard_error(TTYState, true), + {ok, init, {Args, #state{ user = start_user() } }, + {next_event, internal, TTYState}} + catch error:enotsup -> + %% This is thrown by prim_tty:init when + %% it could not start the terminal, + %% probably because TERM=dumb was set. + {stop, normal} + end; + not IsTTY -> + {stop, normal} end. -init(internal, Port, {Args, State = #state{ user = User }}) -> +%% Initialize standard_error +init_standard_error(TTY, NewlineCarriageReturn) -> + Encoding = case prim_tty:unicode(TTY) of + true -> unicode; + false -> latin1 + end, + ok = io:setopts(standard_error, [{encoding, Encoding}, + {onlcr, NewlineCarriageReturn}]). + +init(internal, TTYState, {Args, State = #state{ user = User }}) -> %% Cleanup ancestors so that observer looks nice put('$ancestors',[User|get('$ancestors')]), - %% Initialize standard_error - Encoding = - case get_unicode_state(Port) of - true -> unicode; - false -> latin1 - end, - ok = io:setopts(standard_error, [{encoding, Encoding}, {onlcr,true}]), - - %% Initialize the starting shell - {Curr,Shell} = - case init:get_argument(remsh) of - {ok,[[Node]]} -> - ANode = - if - node() =:= nonode@nohost -> - %% We try to connect to the node if the current node is not - %% a distributed node yet. If this succeeds it means that we - %% are running using "-sname undefined". - _ = net_kernel:start([undefined, shortnames]), - NodeName = append_hostname(Node, net_kernel:nodename()), - case net_kernel:connect_node(NodeName) of - true -> - NodeName; - _Else -> - ?LOG_ERROR("Could not connect to ~p",[Node]) - end; - true -> - append_hostname(Node, node()) - end, + #{ read := ReadHandle, write := WriteHandle } = prim_tty:handles(TTYState), + + NewState = State#state{ tty = TTYState, + read = ReadHandle, write = WriteHandle, + user = User, queue = {false, queue:new()}, + groups = gr_add_cur(gr_new(), User, {}) + }, - RShell = {ANode,shell,start,[]}, - {group:start(self(), RShell, rem_sh_opts(ANode)), RShell}; - E when E =:= error ; E =:= {ok,[[]]} -> - LShell = maps:get(initial_shell, Args, {shell,start,[init]}), - {group:start(self(), LShell), LShell} - end, + case Args of + #{ initial_shell := {remote, Node} } -> + init_remote_shell(NewState, Node); + #{ initial_shell := InitialShell } -> + init_local_shell(NewState, InitialShell); + _ -> + init_local_shell(NewState, {shell,start,[init]}) + end. - Gr1 = gr_add_cur(gr_new(), User, {}), - Gr = gr_add_cur(Gr1, Curr, Shell), +init_remote_shell(State, Node) -> - NewState = State#state{ port = Port, current_group = Curr, user = User, - groups = Gr, queue = {false, queue:new()} - }, + StartedDist = + case net_kernel:get_state() of + #{ started := no } -> + {ok, _} = net_kernel:start([undefined, shortnames]), + true; + _ -> + false + end, - %% Print some information. - Slogan = case application:get_env(stdlib, shell_slogan, - fun() -> erlang:system_info(system_version) end) of - Fun when is_function(Fun, 0) -> - Fun(); - SloganEnv -> - SloganEnv - end, + LocalNode = + case net_kernel:get_state() of + #{ name_type := dynamic } -> + net_kernel:nodename(); + #{ name_type := static } -> + node() + end, - {next_state, server, NewState, - {next_event, info, - {Curr, {put_chars, unicode, lists:flatten(io_lib:format("~ts\n", [Slogan]))}}}}. + RemoteNode = + case string:find(Node,"@") of + nomatch -> + list_to_atom(Node ++ string:find(atom_to_list(LocalNode),"@")); + _ -> + list_to_atom(Node) + end, -append_hostname(Node, LocalNode) -> - case string:find(Node,"@") of - nomatch -> - list_to_atom(Node ++ string:find(atom_to_list(LocalNode),"@")); - _ -> - list_to_atom(Node) + case net_kernel:connect_node(RemoteNode) of + true -> + %% We fetch the shell slogan from the remote node + Slogan = + case erpc:call(RemoteNode, application, get_env, + [stdlib, shell_slogan, + erpc:call(RemoteNode, erlang, system_info, [system_version])]) of + Fun when is_function(Fun, 0) -> + erpc:call(RemoteNode, Fun); + SloganEnv -> + SloganEnv + end, + + RShellOpts = [{expand_fun,fun(B)-> rpc:call(RemoteNode,edlin_expand,expand,[B]) end}], + + RShell = {RemoteNode,shell,start,[]}, + Gr = gr_add_cur(State#state.groups, + group:start(self(), RShell, RShellOpts), + RShell), + + init_shell(State#state{ groups = Gr }, [Slogan,$\n]); + false -> + ?LOG_ERROR("Could not connect to ~p, starting local shell",[RemoteNode]), + _ = [net_kernel:stop() || StartedDist], + init_local_shell(State, {shell, start, []}) end. -rem_sh_opts(Node) -> - [{expand_fun,fun(B)-> rpc:call(Node,edlin_expand,expand,[B]) end}]. +init_local_shell(State, InitialShell) -> + + Slogan = + case application:get_env( + stdlib, shell_slogan, + fun() -> erlang:system_info(system_version) end) of + Fun when is_function(Fun, 0) -> + Fun(); + SloganEnv -> + SloganEnv + end, + + Gr = gr_add_cur(State#state.groups, + group:start(self(), InitialShell), + InitialShell), + + init_shell(State#state{ groups = Gr }, [Slogan,$\n]). + +init_shell(State, Slogan) -> + + init_standard_error(State#state.tty, State#state.shell_started), + + {next_state, server, State#state{ current_group = gr_cur_pid(State#state.groups) }, + {next_event, info, + {gr_cur_pid(State#state.groups), + {put_chars, unicode, + unicode:characters_to_binary(io_lib:format("~ts", [Slogan]))}}}}. %% start_user() %% Start a group leader process and register it as 'user', unless, %% of course, a 'user' already exists. - start_user() -> case whereis(user) of undefined -> @@ -206,8 +253,12 @@ start_user() -> User end. -server(info, {Port,{data,Bs}}, State = #state{ port = Port }) -> - UTF8Binary = list_to_binary(Bs), +server(info, {ReadHandle,{data,UTF8Binary}}, State = #state{ read = ReadHandle }) + when State#state.current_group =:= State#state.user -> + State#state.current_group ! + {self(), {data, unicode:characters_to_list(UTF8Binary, utf8)}}, + keep_state_and_data; +server(info, {ReadHandle,{data,UTF8Binary}}, State = #state{ read = ReadHandle }) -> case contains_ctrl_g_or_ctrl_c(UTF8Binary) of ctrl_g -> {next_state, switch_loop, State, {next_event, internal, init}}; ctrl_c -> @@ -221,30 +272,51 @@ server(info, {Port,{data,Bs}}, State = #state{ port = Port }) -> {self(), {data, unicode:characters_to_list(UTF8Binary, utf8)}}, keep_state_and_data end; -server(info, {Port,eof}, State = #state{ port = Port }) -> - State#state.current_group ! {self(),eof}, +server(info, {ReadHandle,eof}, State = #state{ read = ReadHandle }) -> + State#state.current_group ! {self(), eof}, keep_state_and_data; -server(info, {Requester,tty_geometry}, #state{ port = Port }) -> - Requester ! {self(),tty_geometry,get_tty_geometry(Port)}, - keep_state_and_data; -server(info, {Requester,get_unicode_state}, #state{ port = Port }) -> - Requester ! {self(),get_unicode_state,get_unicode_state(Port)}, +server(info,{ReadHandle,{signal,Signal}}, State = #state{ tty = TTYState, read = ReadHandle }) -> + {keep_state, State#state{ tty = prim_tty:handle_signal(TTYState, Signal) }}; + +server(info, {Requester, tty_geometry}, #state{ tty = TTYState }) -> + case prim_tty:window_size(TTYState) of + {ok, Geometry} -> + Requester ! {self(), tty_geometry, Geometry}, + ok; + Error -> + Requester ! {self(), tty_geometry, Error}, + ok + end, keep_state_and_data; -server(info, {Requester,set_unicode_state,Bool}, #state{ port = Port }) -> - Requester ! {self(),set_unicode_state,set_unicode_state(Port, Bool)}, +server(info, {Requester, get_unicode_state}, #state{ tty = TTYState }) -> + Requester ! {self(), get_unicode_state, prim_tty:unicode(TTYState) }, keep_state_and_data; +server(info, {Requester, set_unicode_state, Bool}, #state{ tty = TTYState } = State) -> + OldUnicode = prim_tty:unicode(TTYState), + NewTTYState = prim_tty:unicode(TTYState, Bool), + ok = io:setopts(standard_error,[{encoding, if Bool -> unicode; true -> latin1 end}]), + Requester ! {self(), set_unicode_state, OldUnicode}, + {keep_state, State#state{ tty = NewTTYState }}; server(info, Req, State = #state{ user = User, current_group = Curr }) when element(1,Req) =:= User orelse element(1,Req) =:= Curr, tuple_size(Req) =:= 2 orelse tuple_size(Req) =:= 3 -> %% We match {User|Curr,_}|{User|Curr,_,_} - {keep_state, State#state{ queue = handle_req(Req, State#state.port, State#state.queue) }}; -server(info, {Port, ok}, State = #state{ port = Port, queue = {{Origin, Reply}, IOQ} }) -> + {NewTTYState, NewQueue} = handle_req(Req, State#state.tty, State#state.queue), + {keep_state, State#state{ tty = NewTTYState, queue = NewQueue }}; +server(info, {WriteRef, ok}, State = #state{ write = WriteRef, + queue = {{Origin, Reply}, IOQ} }) -> %% We get this ok from the port, in io_request we store %% info about where to send reply at head of queue - Origin ! {reply,Reply}, - {keep_state, State#state{ queue = handle_req(next, Port, {false, IOQ}) }}; -server(info,{'EXIT',Port, _Reason}, #state{ port = Port }) -> + Origin ! {reply, Reply}, + {NewTTYState, NewQueue} = handle_req(next, State#state.tty, {false, IOQ}), + {keep_state, State#state{ tty = NewTTYState, queue = NewQueue }}; +server(info,{Requester, {put_chars_sync, _, _, Reply}}, _State) -> + %% This is a sync request from an unknown or inactive group. + %% We need to ack the Req otherwise originating process will hang forever. + %% We discard the output to non visible shells + Requester ! {reply, Reply}, keep_state_and_data; + server(info,{'EXIT',User, shutdown}, #state{ user = User }) -> keep_state_and_data; server(info,{'EXIT',User, _Reason}, State = #state{ user = User }) -> @@ -253,66 +325,39 @@ server(info,{'EXIT',User, _Reason}, State = #state{ user = User }) -> groups = gr_set_num(State#state.groups, 1, NewUser, {})}}; server(info,{'EXIT', Group, Reason}, State) -> % shell and group leader exit case gr_cur_pid(State#state.groups) of - Group when Reason =/= die , - Reason =/= terminated -> % current shell exited - if Reason =/= normal -> - io_requests([{put_chars,unicode,"*** ERROR: "}], State#state.port); - true -> % exit not caused by error - io_requests([{put_chars,unicode,"*** "}], State#state.port) - end, - io_requests([{put_chars,unicode,"Shell process terminated! "}], State#state.port), + Group when Reason =/= die, Reason =/= terminated -> % current shell exited + Reqs = [if + Reason =/= normal -> + {put_chars,unicode,<<"*** ERROR: ">>}; + true -> % exit not caused by error + {put_chars,unicode,<<"*** ">>} + end, + {put_chars,unicode,<<"Shell process terminated! ">>}], Gr1 = gr_del_pid(State#state.groups, Group), case gr_get_info(State#state.groups, Group) of {Ix,{shell,start,Params}} -> % 3-tuple == local shell - io_requests([{put_chars,unicode,"***\n"}], State#state.port), + NewTTyState = io_requests(Reqs ++ [{put_chars,unicode,<<"***\n">>}], + State#state.tty), %% restart group leader and shell, same index NewGroup = group:start(self(), {shell,start,Params}), {ok,Gr2} = gr_set_cur(gr_set_num(Gr1, Ix, NewGroup, {shell,start,Params}), Ix), - {keep_state, State#state{ current_group = NewGroup, groups = Gr2 }}; + {keep_state, State#state{ tty = NewTTyState, + current_group = NewGroup, + groups = Gr2 }}; _ -> % remote shell - io_requests([{put_chars,unicode,"(^G to start new job) ***\n"}], - State#state.port), - {keep_state, State#state{ groups = Gr1 }} + NewTTYState = io_requests( + Reqs ++ [{put_chars,unicode,<<"(^G to start new job) ***\n">>}], + State#state.tty), + {keep_state, State#state{ tty = NewTTYState, groups = Gr1 }} end; _ -> % not current, just remove it {keep_state, State#state{ groups = gr_del_pid(State#state.groups, Group) }} end; -server(info,{Requester, {put_chars_sync, _, _, Reply}}, _State) -> - %% This is a sync request from an unknown or inactive group. - %% We need to ack the Req otherwise originating process will hang forever. - %% We discard the output to non visible shells - Requester ! {reply, Reply}, - keep_state_and_data; server(_, _, _) -> %% Ignore unknown messages. keep_state_and_data. -handle_req(next,Port,{false,IOQ}=IOQueue) -> - case queue:out(IOQ) of - {empty,_} -> - IOQueue; - {{value,{Origin,Req}},ExecQ} -> - case io_request(Req,Port) of - ok -> - handle_req(next,Port,{false,ExecQ}); - Reply -> - {{Origin,Reply},ExecQ} - end - end; -handle_req(Msg,Port,{false,IOQ}=IOQueue) -> - empty = queue:peek(IOQ), - {Origin,Req} = Msg, - case io_request(Req, Port) of - ok -> - IOQueue; - Reply -> - {{Origin,Reply}, IOQ} - end; -handle_req(Msg,_Port,{Resp, IOQ}) -> - %% All requests are queued when we have outstanding sync put_chars - {Resp, queue:in(Msg,IOQ)}. - contains_ctrl_g_or_ctrl_c(<<$\^G,_/binary>>) -> ctrl_g; contains_ctrl_g_or_ctrl_c(<<$\^C,_/binary>>) -> @@ -339,96 +384,95 @@ switch_loop(internal, init, State) -> end end, NewGroup = group:start(self(), {shell,start,[]}), - io_request({put_chars,unicode,"\n"}, State#state.port), + NewTTYState = io_requests([{put_chars,unicode,<<"\n">>}], State#state.tty), {next_state, server, - State#state{ groups = gr_add_cur(Gr1, NewGroup, {shell,start,[]})}}; + State#state{ tty = NewTTYState, + groups = gr_add_cur(Gr1, NewGroup, {shell,start,[]})}}; jcl -> - io_request({put_chars,unicode,"\nUser switch command\n"}, State#state.port), + NewTTYState = + io_requests([{put_chars,unicode,<<"\nUser switch command\n">>}], + State#state.tty), %% init edlin used by switch command and have it copy the %% text buffer from current group process edlin:init(gr_cur_pid(State#state.groups)), - {keep_state_and_data, + {keep_state, State#state{ tty = NewTTYState }, {next_event, internal, line}} end; switch_loop(internal, line, State) -> {more_chars, Cont, Rs} = edlin:start(" --> "), - io_requests(Rs, State#state.port), - {keep_state, {Cont, State}}; + {keep_state, {Cont, State#state{ tty = io_requests(Rs, State#state.tty) }}}; switch_loop(internal, {line, Line}, State) -> case erl_scan:string(Line) of {ok, Tokens, _} -> - case switch_cmd(Tokens, State#state.port, State#state.groups) of + case switch_cmd(Tokens, State#state.groups) of {ok, Groups} -> {next_state, server, State#state{ current_group = gr_cur_pid(Groups), groups = Groups } }; - retry -> - {keep_state_and_data, + {retry, Requests} -> + {keep_state, State#state{ tty = io_requests(Requests, State#state.tty) }, {next_event, internal, line}}; - {retry, Groups} -> - {keep_state, State#state{ current_group = gr_cur_pid(Groups), - groups = Groups }, + {retry, Requests, Groups} -> + {keep_state, State#state{ + tty = io_requests(Requests, State#state.tty), + current_group = gr_cur_pid(Groups), + groups = Groups }, {next_event, internal, line}} end; {error, _, _} -> - io_request({put_chars,unicode,"Illegal input\n"}, State#state.port), - {keep_state_and_data, + NewTTYState = + io_requests([{put_chars,unicode,<<"Illegal input\n">>}], State#state.tty), + {keep_state, State#state{ tty = NewTTYState }, {next_event, internal, line}} end; -switch_loop(info,{Port,{data,Cs}}, {Cont, State}) -> - case edlin:edit_line(Cs, Cont) of +switch_loop(info,{ReadHandle,{data,Cs}}, {Cont, #state{ read = ReadHandle } = State}) -> + case edlin:edit_line(unicode:characters_to_list(Cs), Cont) of {done,Line,_Rest, Rs} -> - io_requests(Rs, State#state.port), - {keep_state, State, {next_event, internal, {line, Line}}}; + {keep_state, State#state{ tty = io_requests(Rs, State#state.tty) }, + {next_event, internal, {line, Line}}}; {undefined,_Char,MoreCs,NewCont,Rs} -> - io_requests(Rs, State#state.port), - io_request(beep, State#state.port), - {keep_state, {NewCont, State}, - {next_event, info, {Port,{data,MoreCs}}}}; + {keep_state, + {NewCont, State#state{ tty = io_requests(Rs ++ [beep], State#state.tty)}}, + {next_event, info, {ReadHandle,{data,MoreCs}}}}; {more_chars,NewCont,Rs} -> - io_requests(Rs, State#state.port), - {keep_state, {NewCont, State}}; + {keep_state, + {NewCont, State#state{ tty = io_requests(Rs, State#state.tty)}}}; {blink,NewCont,Rs} -> - io_requests(Rs, State#state.port), - {keep_state, {NewCont, State}, 1000} + {keep_state, + {NewCont, State#state{ tty = io_requests(Rs, State#state.tty)}}, + 1000} end; -switch_loop(timeout, _, State) -> +switch_loop(timeout, _, {_Cont, State}) -> {keep_state_and_data, - {next_state, info,{State#state.port,{data,[]}}}}; + {next_event, info, {State#state.read,{data,[]}}}}; switch_loop(info, _Unknown, _State) -> {keep_state_and_data, postpone}. -switch_cmd([{atom,_,Key},{Type,_,Value}], Port, Gr) +switch_cmd([{atom,_,Key},{Type,_,Value}], Gr) when Type =:= atom; Type =:= integer -> - switch_cmd({Key, Value}, Port, Gr); -switch_cmd([{atom,_,Key},{atom,_,V1},{atom,_,V2}], Port, Gr) -> - switch_cmd({Key, V1, V2}, Port, Gr); -switch_cmd([{atom,_,Key}], Port, Gr) -> - switch_cmd(Key, Port, Gr); -switch_cmd([{'?',_}], Port, Gr) -> - switch_cmd(h, Port, Gr); - -switch_cmd(Cmd, Port, Gr) when Cmd =:= c; Cmd =:= i; Cmd =:= k -> - Pid = gr_cur_pid(Gr), - CurrIndex = - case gr_get_info(Gr, Pid) of - undefined -> undefined; - {Ix, _} -> Ix - end, - switch_cmd({Cmd, CurrIndex}, Port, Gr); -switch_cmd({c, I}, Port, Gr0) -> + switch_cmd({Key, Value}, Gr); +switch_cmd([{atom,_,Key},{atom,_,V1},{atom,_,V2}], Gr) -> + switch_cmd({Key, V1, V2}, Gr); +switch_cmd([{atom,_,Key}], Gr) -> + switch_cmd(Key, Gr); +switch_cmd([{'?',_}], Gr) -> + switch_cmd(h, Gr); + +switch_cmd(Cmd, Gr) when Cmd =:= c; Cmd =:= i; Cmd =:= k -> + switch_cmd({Cmd, gr_cur_index(Gr)}, Gr); +switch_cmd({c, I}, Gr0) -> case gr_set_cur(Gr0, I) of {ok,Gr} -> {ok, Gr}; - undefined -> unknown_group(Port) + undefined -> unknown_group() end; -switch_cmd({i, I}, Port, Gr) -> +switch_cmd({i, I}, Gr) -> case gr_get_num(Gr, I) of {pid,Pid} -> exit(Pid, interrupt), - retry; + {retry, []}; undefined -> - unknown_group(Port) + unknown_group() end; -switch_cmd({k, I}, Port, Gr) -> +switch_cmd({k, I}, Gr) -> case gr_get_num(Gr, I) of {pid,Pid} -> exit(Pid, die), @@ -437,163 +481,132 @@ switch_cmd({k, I}, Port, Gr) -> retry; _ -> receive {'EXIT',Pid,_} -> - {retry,gr_del_pid(Gr, Pid)} + {retry,[],gr_del_pid(Gr, Pid)} after 1000 -> - {retry,Gr} + {retry,[],Gr} end end; undefined -> - unknown_group(Port) + unknown_group() end; -switch_cmd(j, Port, Gr) -> - io_requests(gr_list(Gr), Port), - retry; -switch_cmd({s, Shell}, _Port, Gr0) when is_atom(Shell) -> +switch_cmd(j, Gr) -> + {retry, gr_list(Gr)}; +switch_cmd({s, Shell}, Gr0) when is_atom(Shell) -> Pid = group:start(self(), {Shell,start,[]}), Gr = gr_add_cur(Gr0, Pid, {Shell,start,[]}), - {retry, Gr}; -switch_cmd(s, Port, Gr) -> - switch_cmd({s, shell}, Port, Gr); -switch_cmd(r, Port, Gr0) -> + {retry, [], Gr}; +switch_cmd(s, Gr) -> + switch_cmd({s, shell}, Gr); +switch_cmd(r, Gr0) -> case is_alive() of true -> Node = pool:get_node(), Pid = group:start(self(), {Node,shell,start,[]}), Gr = gr_add_cur(Gr0, Pid, {Node,shell,start,[]}), - {retry, Gr}; + {retry, [], Gr}; false -> - io_request({put_chars,unicode,"Node is not alive\n"}, Port), - retry + {retry, [{put_chars,unicode,<<"Node is not alive\n">>}]} end; -switch_cmd({r, Node}, Port, Gr) when is_atom(Node)-> - switch_cmd({r, Node, shell}, Port, Gr); -switch_cmd({r,Node,Shell}, Port, Gr0) when is_atom(Node), - is_atom(Shell) -> +switch_cmd({r, Node}, Gr) when is_atom(Node)-> + switch_cmd({r, Node, shell}, Gr); +switch_cmd({r,Node,Shell}, Gr0) when is_atom(Node), is_atom(Shell) -> case is_alive() of true -> Pid = group:start(self(), {Node,Shell,start,[]}), Gr = gr_add_cur(Gr0, Pid, {Node,Shell,start,[]}), - {retry, Gr}; + {retry, [], Gr}; false -> - io_request({put_chars,unicode,"Node is not alive\n"}, Port), - retry + {retry, [{put_chars,unicode,"Node is not alive\n"}]} end; -switch_cmd(q, Port, _Gr) -> +switch_cmd(q, _Gr) -> case erlang:system_info(break_ignored) of true -> % noop - io_request({put_chars,unicode,"Unknown command\n"}, Port), - retry; + {retry, [{put_chars,unicode,<<"Unknown command\n">>}]}; false -> halt() end; -switch_cmd(h, Port, _Gr) -> - list_commands(Port), - retry; -switch_cmd([], _Port, _Gr) -> - retry; -switch_cmd(_Ts, Port, _Gr) -> - io_request({put_chars,unicode,"Unknown command\n"}, Port), - retry. +switch_cmd(h, _Gr) -> + {retry, list_commands()}; +switch_cmd([], _Gr) -> + {retry,[]}; +switch_cmd(_Ts, _Gr) -> + {retry, [{put_chars,unicode,<<"Unknown command\n">>}]}. -unknown_group(Port) -> - io_request({put_chars,unicode,"Unknown job\n"}, Port), - retry. +unknown_group() -> + {retry,[{put_chars,unicode,<<"Unknown job\n">>}]}. - -list_commands(Port) -> +list_commands() -> QuitReq = case erlang:system_info(break_ignored) of - true -> + true -> []; false -> - [{put_chars, unicode," q - quit erlang\n"}] + [{put_chars, unicode,<<" q - quit erlang\n">>}] end, - io_requests([{put_chars, unicode," c [nn] - connect to job\n"}, - {put_chars, unicode," i [nn] - interrupt job\n"}, - {put_chars, unicode," k [nn] - kill job\n"}, - {put_chars, unicode," j - list all jobs\n"}, - {put_chars, unicode," s [shell] - start local shell\n"}, - {put_chars, unicode," r [node [shell]] - start remote shell\n"}] ++ - QuitReq ++ - [{put_chars, unicode," ? | h - this message\n"}], - Port). - -% Let driver report window geometry, -% definitely outside of the common interface -get_tty_geometry(Port) -> - case (catch port_control(Port,?CTRL_OP_GET_WINSIZE,[])) of - List when length(List) =:= 8 -> - <> = list_to_binary(List), - {W,H}; - _ -> - error - end. -get_unicode_state(Port) -> - case (catch port_control(Port,?CTRL_OP_GET_UNICODE_STATE,[])) of - [Int] when Int > 0 -> - true; - [Int] when Int =:= 0 -> - false; - _ -> - error - end. - -set_unicode_state(Port, Bool) -> - Data = case Bool of - true -> [1]; - false -> [0] - end, - case (catch port_control(Port,?CTRL_OP_SET_UNICODE_STATE,Data)) of - [Int] when Int > 0 -> - true; - [Int] when Int =:= 0 -> - false; - _ -> - error - end. - -%% io_request(Request, InPort, OutPort) -%% io_requests(Requests, InPort, OutPort) -%% Note: InPort is unused. -io_request({requests,Rs}, Port) -> - io_requests(Rs, Port); -io_request(Request, Port) -> - case io_command(Request) of - {Data, Reply} -> - true = port_command(Port, Data), - Reply; - unhandled -> - ok - end. - -io_requests([R|Rs], Port) -> - io_request(R, Port), - io_requests(Rs, Port); -io_requests([], _Port) -> - ok. - -put_int16(N, Tail) -> - [(N bsr 8)band 255,N band 255|Tail]. - -%% When a put_chars_sync command is used, user_drv guarantees that -%% the bytes have been put in the buffer of the port before an acknowledgement -%% is sent back to the process sending the request. This command was added in -%% OTP 18 to make sure that data sent from io:format is actually printed -%% to the console before the vm stops when calling erlang:halt(integer()). --dialyzer({no_improper_lists, io_command/1}). -io_command({put_chars_sync, unicode, Cs, Reply}) -> - {[?OP_PUTC_SYNC|unicode:characters_to_binary(Cs, utf8)], Reply}; -io_command({put_chars, unicode, Cs}) -> - {[?OP_PUTC|unicode:characters_to_binary(Cs, utf8)], ok}; -io_command({move_rel, N}) -> - {[?OP_MOVE|put_int16(N, [])], ok}; -io_command({insert_chars, unicode, Cs}) -> - {[?OP_INSC|unicode:characters_to_binary(Cs, utf8)], ok}; -io_command({delete_chars, N}) -> - {[?OP_DELC|put_int16(N, [])], ok}; -io_command(beep) -> - {[?OP_BEEP], ok}; -io_command(_) -> - unhandled. + [{put_chars, unicode,<<" c [nn] - connect to job\n">>}, + {put_chars, unicode,<<" i [nn] - interrupt job\n">>}, + {put_chars, unicode,<<" k [nn] - kill job\n">>}, + {put_chars, unicode,<<" j - list all jobs\n">>}, + {put_chars, unicode,<<" s [shell] - start local shell\n">>}, + {put_chars, unicode,<<" r [node [shell]] - start remote shell\n">>}] ++ + QuitReq ++ + [{put_chars, unicode,<<" ? | h - this message\n">>}]. + +-spec io_request(request(), prim_tty:state()) -> {noreply | term(), prim_tty:state()}. +io_request({requests,Rs}, TTY) -> + {noreply, io_requests(Rs, TTY)}; +io_request({put_chars, unicode, Chars}, TTY) -> + write(prim_tty:handle_request(TTY, {putc, unicode:characters_to_binary(Chars)})); +io_request({put_chars_sync, unicode, Chars, Reply}, TTY) -> + {Output, NewTTY} = prim_tty:handle_request(TTY, {putc, unicode:characters_to_binary(Chars)}), + ok = prim_tty:write(NewTTY, Output, self()), + {Reply, NewTTY}; +io_request({move_rel, N}, TTY) -> + write(prim_tty:handle_request(TTY, {move, N})); +io_request({insert_chars, unicode, Chars}, TTY) -> + write(prim_tty:handle_request(TTY, {insert, unicode:characters_to_binary(Chars)})); +io_request({delete_chars, N}, TTY) -> + write(prim_tty:handle_request(TTY, {delete, N})); +io_request(beep, TTY) -> + write(prim_tty:handle_request(TTY, beep)). + +write({Output, TTY}) -> + ok = prim_tty:write(TTY, Output), + {noreply, TTY}. + +io_requests([{insert_chars, unicode, C1},{insert_chars, unicode, C2}|Rs], TTY) -> + io_requests([{insert_chars, unicode, [C1,C2]}|Rs], TTY); +io_requests([{put_chars, unicode, C1},{put_chars, unicode, C2}|Rs], TTY) -> + io_requests([{put_chars, unicode, [C1,C2]}|Rs], TTY); +io_requests([R|Rs], TTY) -> + {noreply, NewTTY} = io_request(R, TTY), + io_requests(Rs, NewTTY); +io_requests([], TTY) -> + TTY. + +handle_req(next,TTYState,{false,IOQ}=IOQueue) -> + case queue:out(IOQ) of + {empty,_} -> + {TTYState, IOQueue}; + {{value,{Origin,Req}},ExecQ} -> + case io_request(Req,TTYState) of + {noreply, NewTTYState} -> + handle_req(next,NewTTYState,{false,ExecQ}); + {Reply, NewTTYState} -> + {NewTTYState, {{Origin,Reply},ExecQ}} + end + end; +handle_req(Msg,TTYState,{false,IOQ}=IOQueue) -> + empty = queue:peek(IOQ), + {Origin, Req} = Msg, + case io_request(Req, TTYState) of + {noreply, NewTTYState} -> + {NewTTYState, IOQueue}; + {Reply, NewTTYState} -> + {NewTTYState, {{Origin,Reply}, IOQ}} + end; +handle_req(Msg,TTYState,{Resp, IOQ}) -> + %% All requests are queued when we have outstanding sync put_chars + {TTYState, {Resp, queue:in(Msg,IOQ)}}. %% gr_new() %% gr_get_num(Group, Index) @@ -663,5 +676,6 @@ gr_list(#gr{ current = Current, groups = Groups}) -> (#group{ index = I, shell = S }) -> Marker = ["*" || Current =:= I], [{put_chars, unicode, - lists:flatten(io_lib:format("~4w~.1ts ~w\n", [I,Marker,S]))}] + unicode:characters_to_binary( + io_lib:format("~4w~.1ts ~w\n", [I,Marker,S]))}] end, Groups). diff --git a/lib/sasl/test/systools_SUITE.erl b/lib/sasl/test/systools_SUITE.erl index 801660ddacf4..6065c099fa9e 100644 --- a/lib/sasl/test/systools_SUITE.erl +++ b/lib/sasl/test/systools_SUITE.erl @@ -1109,9 +1109,9 @@ erts_tar(Config) -> {win32, _} -> {["beam.smp.pdb","erl.exe", "erl.pdb","erl_log.exe","erlexec.dll","erlsrv.exe","heart.exe", - "start_erl.exe","werl.exe","beam.smp.dll", + "start_erl.exe","beam.smp.dll", "epmd.exe","erl.ini","erl_call.exe", - "erlexec.pdb","escript.exe","inet_gethost.exe","werl.pdb"], + "erlexec.pdb","escript.exe","inet_gethost.exe"], ["dialyzer.exe","erlc.exe","yielding_c_fun.exe","ct_run.exe","typer.exe"]} end, diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src index 90c19b2cade4..653e92812784 100644 --- a/lib/stdlib/src/stdlib.app.src +++ b/lib/stdlib/src/stdlib.app.src @@ -112,6 +112,6 @@ dets]}, {applications, [kernel]}, {env, []}, - {runtime_dependencies, ["sasl-3.0","kernel-8.4","erts-@OTP-17934@","crypto-4.5", + {runtime_dependencies, ["sasl-3.0","kernel-@OTP-17932@","erts-@OTP-17934@","crypto-4.5", "compiler-5.0"]} ]}.