diff --git a/.github/dockerfiles/Dockerfile.ubuntu-base b/.github/dockerfiles/Dockerfile.ubuntu-base index dc8b28e1c8a5..76952b39a596 100644 --- a/.github/dockerfiles/Dockerfile.ubuntu-base +++ b/.github/dockerfiles/Dockerfile.ubuntu-base @@ -48,6 +48,13 @@ RUN apt-get install -y git && \ done && \ rm -rf ~/.kerl +## We use tmux to test terminals +RUN apt-get install -y libevent-dev libutf8proc-dev && \ + cd /tmp && wget https://github.com/tmux/tmux/releases/download/3.2a/tmux-3.2a.tar.gz && \ + tar xvzf tmux-3.2a.tar.gz && cd tmux-3.2a && \ + ./configure --enable-static --enable-utf8proc && \ + make && make install + ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 ARG USER=gitpod diff --git a/.github/scripts/build-macos.sh b/.github/scripts/build-macos.sh index 82b07bac7b7c..73c35a6a22d3 100755 --- a/.github/scripts/build-macos.sh +++ b/.github/scripts/build-macos.sh @@ -1,12 +1,19 @@ #!/bin/sh -export MAKEFLAGS=-j$(getconf _NPROCESSORS_ONLN) -export ERL_TOP=`pwd` -export RELEASE_ROOT=$ERL_TOP/release +export MAKEFLAGS="-j$(getconf _NPROCESSORS_ONLN)" +export ERL_TOP="$(pwd)" export ERLC_USE_SERVER=true +export RELEASE_ROOT="$ERL_TOP/release" +BUILD_DOCS=false -./otp_build configure \ - --disable-dynamic-ssl-lib +if [ "$1" = "build_docs" ]; then + BUILD_DOCS=true + shift +fi + +./otp_build configure $* ./otp_build boot -a -./otp_build release -a $RELEASE_ROOT -make release_docs DOC_TARGETS=chunks +./otp_build release -a "$RELEASE_ROOT" +if $BUILD_DOCS; then + make release_docs DOC_TARGETS=chunks +fi diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 7bdda4c618e6..e24f94eec5de 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -127,7 +127,7 @@ jobs: tar -xzf ./otp_src.tar.gz export PATH=$PWD/wxWidgets/release/bin:$PATH cd otp - $GITHUB_WORKSPACE/.github/scripts/build-macos.sh + $GITHUB_WORKSPACE/.github/scripts/build-macos.sh build_docs --disable-dynamic-ssl-lib tar -czf otp_macos_$(cat OTP_VERSION)_x86-64.tar.gz -C release . - name: Test Erlang @@ -152,6 +152,7 @@ jobs: runs-on: macos-12 needs: pack steps: + - uses: actions/checkout@v2 - name: Download source archive uses: actions/download-artifact@v2 with: @@ -161,12 +162,7 @@ jobs: run: | tar -xzf ./otp_src.tar.gz cd otp - export ERL_TOP=`pwd` - export MAKEFLAGS="-j$(($(nproc) + 2)) -O" - export ERLC_USE_SERVER=true - ./otp_build configure --xcomp-conf=./xcomp/erl-xcomp-arm64-ios.conf --without-ssl - ./otp_build boot -a - ./otp_build release -a + $GITHUB_WORKSPACE/.github/scripts/build-macos.sh --xcomp-conf=./xcomp/erl-xcomp-arm64-ios.conf --without-ssl - name: Package .xcframework run: | 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/erl_cmd.xml b/erts/doc/src/erl_cmd.xml index 9f764d2b4e4d..74b4d39df006 100644 --- a/erts/doc/src/erl_cmd.xml +++ b/erts/doc/src/erl_cmd.xml @@ -540,12 +540,12 @@ $ erl \ to implement an Alternative Carrier for the Erlang Distribution.

- +

Ensures that the Erlang runtime system never tries to read any input. Implies .

- +

Starts an Erlang runtime system with no shell. This flag makes it possible to have the Erlang runtime system as a 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/bif.c b/erts/emulator/beam/bif.c index 1d0a145f3fc7..b78942bd4533 100644 --- a/erts/emulator/beam/bif.c +++ b/erts/emulator/beam/bif.c @@ -23,6 +23,10 @@ #endif #include /* offsetof() */ +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#define WANT_NONBLOCKING #include "sys.h" #include "erl_vm.h" #include "erl_sys_driver.h" @@ -4180,27 +4184,100 @@ BIF_RETTYPE erts_debug_display_1(BIF_ALIST_1) BIF_RET(res); } - -BIF_RETTYPE display_string_1(BIF_ALIST_1) +BIF_RETTYPE display_string_2(BIF_ALIST_2) { Process* p = BIF_P; - Eterm string = BIF_ARG_1; - Sint len = erts_unicode_list_to_buf_len(string); + Eterm string = BIF_ARG_2; + Sint len; Sint written; byte *str; int res; + byte *temp_alloc = NULL; - if (len < 0) { - BIF_ERROR(p, BADARG); +#ifdef __WIN32__ + HANDLE fd; + if (ERTS_IS_ATOM_STR("stdout", BIF_ARG_1)) { + fd = GetStdHandle(STD_OUTPUT_HANDLE); + } else if (ERTS_IS_ATOM_STR("stderr", BIF_ARG_1)) { + fd = GetStdHandle(STD_ERROR_HANDLE); + } +#else + int fd; + if (ERTS_IS_ATOM_STR("stdout", BIF_ARG_1)) { + fd = fileno(stdout); + } else if (ERTS_IS_ATOM_STR("stderr", BIF_ARG_1)) { + fd = fileno(stderr); + } +#if defined(HAVE_SYS_IOCTL_H) && defined(TIOCSTI) + else if (ERTS_IS_ATOM_STR("stdin", BIF_ARG_1)) { + fd = open("/proc/self/fd/0",0); + } +#endif +#endif + else { + BIF_ERROR(p, BADARG); + } + if (is_list(string) || is_nil(string)) { + len = erts_unicode_list_to_buf_len(string); + if (len < 0) BIF_ERROR(p, BADARG); + str = temp_alloc = (byte *) erts_alloc(ERTS_ALC_T_TMP, sizeof(char)*len); + res = erts_unicode_list_to_buf(string, str, len, &written); + if (res != 0 || written != len) + erts_exit(ERTS_ERROR_EXIT, "%s:%d: Internal error (%d)\n", __FILE__, __LINE__, res); + } else if (is_binary(string)) { + Uint bitoffs, bitsize; + ERTS_GET_BINARY_BYTES(string, str, bitoffs, bitsize); + if (bitsize % 8 != 0) BIF_ERROR(p, BADARG); + len = binary_size(string); + if (bitoffs != 0) { + str = erts_get_aligned_binary_bytes(string, &temp_alloc); + } + } else { + BIF_ERROR(p, BADARG); } - str = (byte *) erts_alloc(ERTS_ALC_T_TMP, sizeof(char)*(len + 1)); - res = erts_unicode_list_to_buf(string, str, len, &written); - if (res != 0 || written != len) - erts_exit(ERTS_ERROR_EXIT, "%s:%d: Internal error (%d)\n", __FILE__, __LINE__, res); - str[len] = '\0'; - erts_fprintf(stderr, "%s", str); - erts_free(ERTS_ALC_T_TMP, (void *) str); + +#if defined(HAVE_SYS_IOCTL_H) && defined(TIOCSTI) + if (ERTS_IS_ATOM_STR("stdin", BIF_ARG_1)) { + for (int i = 0; i < len; i++) { + if (ioctl(fd, TIOCSTI, str+i) < 0) { + fprintf(stderr,"failed to write to %s (%s)\r\n", "/proc/self/fd/0", + strerror(errno)); + close(fd); + goto error; + } + } + close(fd); + } else +#endif + { +#ifdef __WIN32__ + if (!WriteFile(fd, str, len, &written, NULL)) { + goto error; + } +#else + written = 0; + do { + res = write(fd, str+written, len-written); + if (res < 0 && errno != ERRNO_BLOCK && errno != EINTR) + goto error; + written += res; + } while (written < len); +#endif + } + if (temp_alloc) + erts_free(ERTS_ALC_T_TMP, (void *) temp_alloc); BIF_RET(am_true); + +error: { +#ifdef __WIN32__ + char *errnostr = last_error(); +#else + char *errnostr = erl_errno_id(errno); +#endif + BIF_P->fvalue = am_atom_put(errnostr, strlen(errnostr)); + erts_free(ERTS_ALC_T_TMP, (void *) str); + BIF_ERROR(p, BADARG | EXF_HAS_EXT_INFO); + } } BIF_RETTYPE display_nl_0(BIF_ALIST_0) diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab index dffe3963b599..17f35a1bc775 100644 --- a/erts/emulator/beam/bif.tab +++ b/erts/emulator/beam/bif.tab @@ -56,7 +56,7 @@ bif erlang:crc32_combine/3 bif erlang:date/0 bif erlang:delete_module/1 bif erlang:display/1 -bif erlang:display_string/1 +bif erlang:display_string/2 bif erlang:display_nl/0 ubif erlang:element/2 bif erlang:erase/0 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/beam/erl_dirty_bif.tab b/erts/emulator/beam/erl_dirty_bif.tab index 3f16f3e0f330..9245a19be611 100644 --- a/erts/emulator/beam/erl_dirty_bif.tab +++ b/erts/emulator/beam/erl_dirty_bif.tab @@ -50,6 +50,7 @@ dirty-io erts_debug:dirty_io/2 dirty-cpu erts_debug:lcnt_control/2 dirty-cpu erts_debug:lcnt_collect/0 dirty-cpu erts_debug:lcnt_clear/0 +dirty-cpu erlang:display_string/2 # --- TEST of Dirty BIF functionality --- # Functions below will execute on dirty schedulers when emulator has diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c index c17921d9d4eb..77358f7812dc 100644 --- a/erts/emulator/beam/erl_nif.c +++ b/erts/emulator/beam/erl_nif.c @@ -2274,7 +2274,6 @@ static void close_dynlib(struct erl_module_nif* lib) { ASSERT(lib != NULL); ASSERT(lib->mod == NULL); - ASSERT(lib->handle != NULL); ASSERT(erts_refc_read(&lib->dynlib_refc,0) == 0); if (lib->entry.unload != NULL) { diff --git a/erts/emulator/beam/sys.h b/erts/emulator/beam/sys.h index 20b0571e43af..b57cfd6952a6 100644 --- a/erts/emulator/beam/sys.h +++ b/erts/emulator/beam/sys.h @@ -608,7 +608,7 @@ extern erts_tsd_key_t erts_is_crash_dumping_key; static unsigned long zero_value = 0, one_value = 1; # define SET_BLOCKING(fd) { if (ioctlsocket((fd), FIONBIO, &zero_value) != 0) fprintf(stderr, "Error setting socket to non-blocking: %d\n", WSAGetLastError()); } # define SET_NONBLOCKING(fd) ioctlsocket((fd), FIONBIO, &one_value) - +# define ERRNO_BLOCK EAGAIN /* We use the posix way for windows */ # else # ifdef NB_FIONBIO /* Old BSD */ # include diff --git a/erts/emulator/drivers/unix/ttsl_drv.c b/erts/emulator/drivers/unix/ttsl_drv.c deleted file mode 100644 index 08ab714153ba..000000000000 --- a/erts/emulator/drivers/unix/ttsl_drv.c +++ /dev/null @@ -1,1606 +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) { - outc('^'); - outc(lastput = ((byte) ((*s == 0177) ? '?' : *s | 0x40))); - n -= 2; - s += 2; - } else if (*s & ESCAPED_TAG) { - Uint32 ch = *s & ~(TAG_MASK); - byte *octbuff; - byte octtmp[256]; - int octbytes; - DEBUGLOG(("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..83279c832f68 --- /dev/null +++ b/erts/emulator/nifs/common/prim_tty_nif.c @@ -0,0 +1,1036 @@ +/* + * %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; + DWORD dwOriginalOutMode; + DWORD dwOriginalInMode; + DWORD dwOutMode; + DWORD dwInMode; +#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", 1, 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(ErlNifEnv *env) { +#ifdef __WIN32__ + return enif_make_atom(env, last_error()); +#else + return enif_make_atom(env, erl_errno_id(errno)); +#endif +} + +static ERL_NIF_TERM make_errno_error(ErlNifEnv *env, const char *function) { + return make_error( + env, enif_make_tuple2( + env, enif_make_atom(env, function), make_errno(env))); +} + +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_error(env, make_errno(env)); + } + 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__ + if (tty->dwInMode) { + 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 { + DWORD bytesTransferred; + enif_alloc_binary(1024, &bin); + if (ReadFile(tty->ifd, bin.data, bin.size, + &bytesTransferred, NULL)) { + res = bytesTransferred; + if (res == 0) { + enif_release_binary(&bin); + return make_error(env, enif_make_atom(env, "closed")); + } + } else { + DWORD error = GetLastError(); + enif_release_binary(&bin); + if (error == ERROR_BROKEN_PIPE) + return make_error(env, enif_make_atom(env, "closed")); + return make_errno_error(env, "ReadFile"); + } + } +#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__ + TTYResource *tty; + + if (!enif_get_resource(env, argv[0], tty_rt, (void **)&tty)) + return enif_make_badarg(env); + + if (tty->dwOutMode) + { + 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); + if (tty->ifd == INVALID_HANDLE_VALUE || tty->ifd == NULL) { + tty->ifd = CreateFile("nul", GENERIC_READ, 0, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + } + tty->ofd = GetStdHandle(STD_OUTPUT_HANDLE); + if (tty->ofd == INVALID_HANDLE_VALUE || tty->ofd == NULL) { + tty->ofd = CreateFile("nul", GENERIC_WRITE, 0, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + } + if (GetConsoleMode(tty->ofd, &tty->dwOriginalOutMode)) + { + tty->dwOutMode = ENABLE_VIRTUAL_TERMINAL_PROCESSING | tty->dwOriginalOutMode; + if (!SetConsoleMode(tty->ofd, tty->dwOutMode)) { + /* Failed to set any VT mode, can't do anything here. */ + return make_errno_error(env, "SetConsoleMode"); + } + } + if (GetConsoleMode(tty->ifd, &tty->dwOriginalInMode)) + { + tty->dwInMode = ENABLE_VIRTUAL_TERMINAL_INPUT | tty->dwOriginalInMode; + if (!SetConsoleMode(tty->ifd, tty->dwInMode)) { + /* Failed to set any VT mode, can't do anything here. */ + return make_errno_error(env, "SetConsoleMode"); + } + } +#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 + /* fprintf(stderr, "origOutMode: %x origInMode: %x\r\n", */ + /* tty->dwOriginalOutMode, tty->dwOriginalInMode); */ + + /* If we cannot disable NEWLINE_AUTO_RETURN we continue anyway as things work */ + if (SetConsoleMode(tty->ofd, tty->dwOutMode | DISABLE_NEWLINE_AUTO_RETURN)) { + tty->dwOutMode |= DISABLE_NEWLINE_AUTO_RETURN; + } + + tty->dwInMode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT); + if (!SetConsoleMode(tty->ifd, tty->dwInMode)) + { + /* Failed to set disable echo or line input mode */ + 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 void load_resources(ErlNifEnv* env, ErlNifResourceFlags rt_flags) { + 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 + + tty_rt = enif_open_resource_type_x(env, "tty", &rt, rt_flags, NULL); +} + +static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +{ + *priv_data = NULL; + load_resources(env, ERL_NIF_RT_CREATE); + 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 */ + } + *priv_data = NULL; + load_resources(env, ERL_NIF_RT_TAKEOVER); + return 0; +} diff --git a/erts/emulator/sys/common/erl_check_io.c b/erts/emulator/sys/common/erl_check_io.c index 1b12ed32934d..fe35dbd21102 100644 --- a/erts/emulator/sys/common/erl_check_io.c +++ b/erts/emulator/sys/common/erl_check_io.c @@ -1918,6 +1918,7 @@ erts_check_io(ErtsPollThread *psi, ErtsMonotonicTime timeout_time, int poll_only /* fallthrough */ case ERTS_EV_TYPE_NONE: /* Deselected ... */ case_ERTS_EV_TYPE_NONE: + state->flags &= ~ERTS_EV_FLAG_FALLBACK; ASSERT(!state->events && !state->active_events && !state->flags); check_fd_cleanup(state, &free_select, &free_nif); break; diff --git a/erts/emulator/sys/unix/erl_child_setup.c b/erts/emulator/sys/unix/erl_child_setup.c index 0a2ac9abf707..ae269292e5c5 100644 --- a/erts/emulator/sys/unix/erl_child_setup.c +++ b/erts/emulator/sys/unix/erl_child_setup.c @@ -58,6 +58,7 @@ #include #include #include +#include #define WANT_NONBLOCKING @@ -432,6 +433,17 @@ static int system_properties_fd(void) } #endif /* __ANDROID__ */ +/* + If beam is terminated using kill -9 or Ctrl-C when +B is set it may not + cleanup the terminal properly. So to clean it up we save the initial state in + erl_child_setup and then reset the terminal if we detect that beam terminated. + + Not all shells and OSs have this issue, but we do it on all unixes anyway as + it is hard for us to know where the bug exists or not and there is no hard in + doing it. + */ +static struct termios initial_tty_mode; + int main(int argc, char *argv[]) { @@ -447,6 +459,10 @@ main(int argc, char *argv[]) ABORT("Invalid arguments to child_setup"); } + if (isatty(0)) { + tcgetattr(0,&initial_tty_mode); + } + /* We close all fds except the uds from beam. All other fds from now on will have the CLOEXEC flags set on them. This means that we @@ -541,12 +557,18 @@ main(int argc, char *argv[]) pipes, 3, MSG_DONTWAIT)) < 0) { if (errno == EINTR) continue; + if (isatty(0)) { + tcsetattr(0,TCSANOW,&initial_tty_mode); + } DEBUG_PRINT("erl_child_setup failed to read from uds: %d, %d", res, errno); _exit(0); } if (res == 0) { DEBUG_PRINT("uds was closed!"); + if (isatty(0)) { + tcsetattr(0,TCSANOW,&initial_tty_mode); + } _exit(0); } /* Since we use unix domain sockets and send the entire data in 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/exception_SUITE.erl b/erts/emulator/test/exception_SUITE.erl index 5cce5d149168..dfec2a28dc05 100644 --- a/erts/emulator/test/exception_SUITE.erl +++ b/erts/emulator/test/exception_SUITE.erl @@ -851,6 +851,8 @@ error_info(_Config) -> {display, ["test erlang:display/1"], [no_fail]}, {display_string, [{a,b,c}]}, + {display_string, [standard_out,"test erlang:display/2"]}, + {display_string, [stdout,{a,b,c}]}, %% Internal undcoumented BIFs. {dist_ctrl_get_data, 1}, 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 b1e2118b7af9..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 */ @@ -453,6 +448,18 @@ int main(int argc, char **argv) Eargsp[argc] = NULL; emu = argv[0]; start_emulator_program = strsave(argv[0]); + /* We set the stdandard handles to nul in order for prim_tty_nif + and erlang:display_string to work without returning ebadf for + detached emulators */ + SetStdHandle(STD_INPUT_HANDLE, + CreateFile("nul", GENERIC_READ, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL)); + SetStdHandle(STD_OUTPUT_HANDLE, + CreateFile("nul", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL)); + SetStdHandle(STD_ERROR_HANDLE, + CreateFile("nul", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL)); goto skip_arg_massage; } free_env_val(s); @@ -522,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"); @@ -1115,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 @@ -1598,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/escript.c b/erts/etc/common/escript.c index 078937e67698..e418daf43091 100644 --- a/erts/etc/common/escript.c +++ b/erts/etc/common/escript.c @@ -510,8 +510,8 @@ main(int argc, char** argv) PUSH(emulator); PUSH("+B"); - PUSH2("-boot", "no_dot_erlang"); PUSH("-noshell"); + PUSH2("-boot", "no_dot_erlang"); /* * Read options from the %%! row in the script and add them as args 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 %% display_string/1 -spec erlang:display_string(P1) -> true when + P1 :: string() | binary(). +display_string(String) -> + try erlang:display_string(stderr, String) + catch error:badarg:ST -> + [{erlang, display_string, _, [ErrorInfo]}|_] = ST, + erlang:error(badarg, [String], [ErrorInfo]) + end. + +%% display_string/2 +-spec erlang:display_string(Device, P1) -> true when + Device :: stdin | stdout | stderr, P1 :: string(). -display_string(_P1) -> +display_string(_Stream,_P1) -> erlang:nif_error(undefined). %% dt_append_vm_tag_data/1 diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl index ceee24e629bd..a95df3741e54 100644 --- a/erts/preloaded/src/init.erl +++ b/erts/preloaded/src/init.erl @@ -53,7 +53,7 @@ get_argument/1,script_id/0,script_name/0]). %% for the on_load functionality; not for general use --export([run_on_load_handlers/0]). +-export([run_on_load_handlers/0, run_on_load_handlers/1]). %% internal exports -export([fetch_loaded/0,ensure_loaded/1,make_permanent/2, @@ -1463,8 +1463,11 @@ archive_extension() -> %%% run_on_load_handlers() -> + run_on_load_handlers(all). + +run_on_load_handlers(Mods) when is_list(Mods); Mods =:= all -> Ref = monitor(process, ?ON_LOAD_HANDLER), - catch ?ON_LOAD_HANDLER ! run_on_load, + catch ?ON_LOAD_HANDLER ! {run_on_load, self(), Ref, Mods}, receive {'DOWN',Ref,process,_,noproc} -> %% There is no on_load handler process, @@ -1477,7 +1480,9 @@ run_on_load_handlers() -> {'DOWN',Ref,process,_,Res} -> %% Failure to run an on_load handler. %% This is fatal during start-up. - exit(Res) + exit(Res); + {reply, Ref, on_load_done} -> + ok end. start_on_load_handler_process() -> @@ -1493,9 +1498,14 @@ on_load_loop(Mods, Debug0) -> on_load_loop(Mods, Debug); {loaded,Mod} -> on_load_loop([Mod|Mods], Debug0); - run_on_load -> + {run_on_load, _, _, all} -> run_on_load_handlers(Mods, Debug0), - exit(on_load_done) + exit(on_load_done); + {run_on_load, From, Ref, ModsToRun} -> + [run_on_load_handlers([Mod], Debug0) + || Mod <- ModsToRun, lists:member(Mod, Mods)], + From ! {reply, Ref, on_load_done}, + on_load_loop(Mods -- ModsToRun, Debug0) end. run_on_load_handlers([M|Ms], Debug) -> diff --git a/lib/common_test/src/test_server.erl b/lib/common_test/src/test_server.erl index 9d893847c2ab..a46394385d85 100644 --- a/lib/common_test/src/test_server.erl +++ b/lib/common_test/src/test_server.erl @@ -2860,7 +2860,7 @@ peer_compile(Erl, cover_compiled, OutDir) -> peer_compile(Erl, ModPath, OutDir) -> {ok, ModSrc} = filelib:find_source(ModPath), Erlc = filename:join(filename:dirname(Erl), "erlc"), - cmd(Erlc, ["-o", OutDir, ModSrc]), + cmd(Erlc, ["-o", OutDir, unicode:characters_to_binary(ModSrc)]), OutDir. %% This should really be implemented as os:cmd. diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile index 2149e897768e..38697002f9ca 100644 --- a/lib/kernel/src/Makefile +++ b/lib/kernel/src/Makefile @@ -140,6 +140,7 @@ MODULES = \ standard_error \ user \ user_drv \ + prim_tty \ user_sup \ raw_file_io \ raw_file_io_compressed \ diff --git a/lib/kernel/src/erl_erts_errors.erl b/lib/kernel/src/erl_erts_errors.erl index 4d23112b83a0..fe7758dae0d4 100644 --- a/lib/kernel/src/erl_erts_errors.erl +++ b/lib/kernel/src/erl_erts_errors.erl @@ -352,8 +352,19 @@ format_erlang_error(demonitor, [_], _) -> format_erlang_error(demonitor, [Ref,Options], _) -> Arg1 = must_be_ref(Ref), [Arg1,maybe_option_list_error(Options, Arg1)]; -format_erlang_error(display_string, [_], _) -> +format_erlang_error(display_string, [_], none) -> [not_string]; +format_erlang_error(display_string, [_], Cause) -> + maybe_posix_message(Cause, false); +format_erlang_error(display_string, [Device, _], none) -> + case lists:member(Device,[stdin,stdout,stderr]) of + true -> + [[],not_string]; + false -> + [not_device,[]] + end; +format_erlang_error(display_string, [_, _], Cause) -> + maybe_posix_message(Cause, true); format_erlang_error(element, [Index, Tuple], _) -> [if not is_integer(Index) -> @@ -1430,8 +1441,23 @@ is_flat_char_list([H|T]) -> is_flat_char_list([]) -> true; is_flat_char_list(_) -> false. +maybe_posix_message(Cause, HasDevice) -> + case erl_posix_msg:message(Cause) of + "unknown POSIX error" -> + unknown; + PosixStr when HasDevice -> + [unicode:characters_to_binary( + io_lib:format("~ts (~tp)",[PosixStr, Cause]))]; + PosixStr when not HasDevice -> + [{general, + unicode:characters_to_binary( + io_lib:format("~ts (~tp)",[PosixStr, Cause]))}] + end. + format_error_map([""|Es], ArgNum, Map) -> format_error_map(Es, ArgNum + 1, Map); +format_error_map([{general, E}|Es], ArgNum, Map) -> + format_error_map(Es, ArgNum, Map#{ general => expand_error(E)}); format_error_map([E|Es], ArgNum, Map) -> format_error_map(Es, ArgNum + 1, Map#{ArgNum => expand_error(E)}); format_error_map([], _, Map) -> @@ -1519,6 +1545,8 @@ expand_error(not_ref) -> <<"not a reference">>; expand_error(not_string) -> <<"not a list of characters">>; +expand_error(not_device) -> + <<"not a valid device type">>; expand_error(not_tuple) -> <<"not a tuple">>; expand_error(range) -> diff --git a/lib/kernel/src/group.erl b/lib/kernel/src/group.erl index 8410c1a4b5a4..ac841feeac17 100644 --- a/lib/kernel/src/group.erl +++ b/lib/kernel/src/group.erl @@ -21,17 +21,21 @@ %% A group leader process for user io. --export([start/2, start/3, server/3]). --export([interfaces/1]). +-export([start/2, start/3, server/4]). start(Drv, Shell) -> start(Drv, Shell, []). start(Drv, Shell, Options) -> - spawn_link(group, server, [Drv, Shell, Options]). + Ancestors = [self() | case get('$ancestors') of + undefined -> []; + Anc -> Anc + end], + spawn_link(group, server, [Ancestors, Drv, Shell, Options]). -server(Drv, Shell, Options) -> +server(Ancestors, Drv, Shell, Options) -> process_flag(trap_exit, true), + _ = [put('$ancestors', Ancestors) || Shell =/= {}], edlin:init(), put(line_buffer, proplists:get_value(line_buffer, Options, group_history:load())), put(read_mode, list), @@ -40,31 +44,8 @@ server(Drv, Shell, Options) -> proplists:get_value(expand_fun, Options, fun(B) -> edlin_expand:expand(B) end)), put(echo, proplists:get_value(echo, Options, true)), - - start_shell(Shell), - server_loop(Drv, get(shell), []). - -%% Return the pid of user_drv and the shell process. -%% Note: We can't ask the group process for this info since it -%% may be busy waiting for data from the driver. -interfaces(Group) -> - case process_info(Group, dictionary) of - {dictionary,Dict} -> - get_pids(Dict, [], false); - _ -> - [] - end. -get_pids([Drv = {user_drv,_} | Rest], Found, _) -> - get_pids(Rest, [Drv | Found], true); -get_pids([Sh = {shell,_} | Rest], Found, Active) -> - get_pids(Rest, [Sh | Found], Active); -get_pids([_ | Rest], Found, Active) -> - get_pids(Rest, Found, Active); -get_pids([], Found, true) -> - Found; -get_pids([], _Found, false) -> - []. + server_loop(Drv, start_shell(Shell), []). %% start_shell(Shell) %% Spawn a shell with its group_leader from the beginning set to ourselves. @@ -81,9 +62,9 @@ start_shell(Shell) when is_function(Shell) -> start_shell(Shell) when is_pid(Shell) -> group_leader(self(), Shell), % we are the shells group leader link(Shell), % we're linked to it. - put(shell, Shell); + Shell; start_shell(_Shell) -> - ok. + undefined. start_shell1(M, F, Args) -> G = group_leader(), @@ -92,7 +73,7 @@ start_shell1(M, F, Args) -> Shell when is_pid(Shell) -> group_leader(G, self()), link(Shell), % we're linked to it. - put(shell, Shell); + Shell; Error -> % start failure exit(Error) % let the group process crash end. @@ -104,7 +85,7 @@ start_shell1(Fun) -> Shell when is_pid(Shell) -> group_leader(G, self()), link(Shell), % we're linked to it. - put(shell, Shell); + Shell; Error -> % start failure exit(Error) % let the group process crash end. @@ -116,7 +97,7 @@ server_loop(Drv, Shell, Buf0) -> %% selective receive loops elsewhere in this module. Buf = io_request(Req, From, ReplyAs, Drv, Shell, Buf0), server_loop(Drv, Shell, Buf); - {reply,{{From,ReplyAs},Reply}} -> + {reply,{From,ReplyAs},Reply} -> io_reply(From, ReplyAs, Reply), server_loop(Drv, Shell, Buf0); {driver_id,ReplyTo} -> @@ -127,7 +108,7 @@ server_loop(Drv, Shell, Buf0) -> server_loop(Drv, Shell, Buf0); {'EXIT',Drv,interrupt} -> %% Send interrupt to the shell. - exit_shell(interrupt), + exit_shell(Shell, interrupt), server_loop(Drv, Shell, Buf0); {'EXIT',Drv,R} -> exit(R); @@ -143,11 +124,10 @@ server_loop(Drv, Shell, Buf0) -> server_loop(Drv, Shell, Buf0) end. -exit_shell(Reason) -> - case get(shell) of - undefined -> true; - Pid -> exit(Pid, Reason) - end. +exit_shell(undefined, _Reason) -> + true; +exit_shell(Pid, Reason) -> + exit(Pid, Reason). get_tty_geometry(Drv) -> Drv ! {self(),tty_geometry}, @@ -175,7 +155,16 @@ set_unicode_state(Drv,Bool) -> after 2000 -> timeout end. - +get_terminal_state(Drv) -> + Drv ! {self(),get_terminal_state}, + receive + {Drv,get_terminal_state,UniState} -> + UniState; + {Drv,get_terminal_state,error} -> + {error, internal} + after 2000 -> + {error,timeout} + end. io_request(Req, From, ReplyAs, Drv, Shell, Buf0) -> case io_request(Req, Drv, Shell, {From,ReplyAs}, Buf0) of @@ -192,7 +181,7 @@ io_request(Req, From, ReplyAs, Drv, Shell, Buf0) -> %% 'kill' instead of R, since the shell is not always in %% a state where it is ready to handle a termination %% message. - exit_shell(kill), + exit_shell(Shell, kill), exit(R) end. @@ -211,7 +200,7 @@ io_request(Req, From, ReplyAs, Drv, Shell, Buf0) -> io_request({put_chars,unicode,Chars}, Drv, _Shell, From, Buf) -> case catch unicode:characters_to_binary(Chars,utf8) of Binary when is_binary(Binary) -> - send_drv(Drv, {put_chars_sync, unicode, Binary, {From,ok}}), + send_drv(Drv, {put_chars_sync, unicode, Binary, From}), {noreply,Buf}; _ -> {error,{error,{put_chars, unicode,Chars}},Buf} @@ -219,12 +208,12 @@ io_request({put_chars,unicode,Chars}, Drv, _Shell, From, Buf) -> io_request({put_chars,unicode,M,F,As}, Drv, _Shell, From, Buf) -> case catch apply(M, F, As) of Binary when is_binary(Binary) -> - send_drv(Drv, {put_chars_sync, unicode, Binary, {From,ok}}), + send_drv(Drv, {put_chars_sync, unicode, Binary, From}), {noreply,Buf}; Chars -> case catch unicode:characters_to_binary(Chars,utf8) of B when is_binary(B) -> - send_drv(Drv, {put_chars_sync, unicode, B, {From,ok}}), + send_drv(Drv, {put_chars_sync, unicode, B, From}), {noreply,Buf}; _ -> {error,{error,F},Buf} @@ -233,12 +222,12 @@ io_request({put_chars,unicode,M,F,As}, Drv, _Shell, From, Buf) -> io_request({put_chars,latin1,Binary}, Drv, _Shell, From, Buf) when is_binary(Binary) -> send_drv(Drv, {put_chars_sync, unicode, unicode:characters_to_binary(Binary,latin1), - {From,ok}}), + From}), {noreply,Buf}; io_request({put_chars,latin1,Chars}, Drv, _Shell, From, Buf) -> case catch unicode:characters_to_binary(Chars,latin1) of Binary when is_binary(Binary) -> - send_drv(Drv, {put_chars_sync, unicode, Binary, {From,ok}}), + send_drv(Drv, {put_chars_sync, unicode, Binary, From}), {noreply,Buf}; _ -> {error,{error,{put_chars,latin1,Chars}},Buf} @@ -248,12 +237,12 @@ io_request({put_chars,latin1,M,F,As}, Drv, _Shell, From, Buf) -> Binary when is_binary(Binary) -> send_drv(Drv, {put_chars_sync, unicode, unicode:characters_to_binary(Binary,latin1), - {From,ok}}), + From}), {noreply,Buf}; Chars -> case catch unicode:characters_to_binary(Chars,latin1) of B when is_binary(B) -> - send_drv(Drv, {put_chars_sync, unicode, B, {From,ok}}), + send_drv(Drv, {put_chars_sync, unicode, B, From}), {noreply,Buf}; _ -> {error,{error,F},Buf} @@ -431,8 +420,8 @@ getopts(Drv,Buf) -> true -> unicode; _ -> latin1 end}, - {ok,[Exp,Echo,Bin,Uni],Buf}. - + Tty = {terminal, get_terminal_state(Drv)}, + {ok,[Exp,Echo,Bin,Uni,Tty],Buf}. %% get_chars_*(Prompt, Module, Function, XtraArgument, Drv, Buffer) %% Gets characters from the input Drv until as the applied function @@ -485,6 +474,8 @@ get_chars_loop(Pbs, M, F, Xa, Drv, Shell, Buf0, State, Encoding) -> get_chars_apply(Pbs, M, F, Xa, Drv, Shell, Buf, State0, Line, Encoding) -> case catch M:F(State0, cast(Line,get(read_mode), Encoding), Encoding, Xa) of + {stop,Result,eof} -> + {ok,Result,eof}; {stop,Result,Rest} -> {ok,Result,append(Rest, Buf, Encoding)}; {'EXIT',_} -> @@ -674,7 +665,7 @@ more_data(What, Cont0, Drv, Shell, Ls, Encoding) -> io_request(Req, From, ReplyAs, Drv, Shell, []), %WRONG!!! send_drv_reqs(Drv, edlin:redraw_line(Cont)), get_line1({more_chars,Cont,[]}, Drv, Shell, Ls, Encoding); - {reply,{{From,ReplyAs},Reply}} -> + {reply,{From,ReplyAs},Reply} -> %% We take care of replies from puts here as well io_reply(From, ReplyAs, Reply), more_data(What, Cont0, Drv, Shell, Ls, Encoding); @@ -702,7 +693,7 @@ get_line_echo_off1({Chars,[]}, Drv, Shell) -> {io_request,From,ReplyAs,Req} when is_pid(From) -> io_request(Req, From, ReplyAs, Drv, Shell, []), get_line_echo_off1({Chars,[]}, Drv, Shell); - {reply,{{From,ReplyAs},Reply}} when From =/= undefined -> + {reply,{From,ReplyAs},Reply} when From =/= undefined -> %% We take care of replies from puts here as well io_reply(From, ReplyAs, Reply), get_line_echo_off1({Chars,[]},Drv, Shell); @@ -713,6 +704,8 @@ get_line_echo_off1({Chars,[]}, Drv, Shell) -> {'EXIT',Shell,R} -> exit(R) end; +get_line_echo_off1(eof, _Drv, _Shell) -> + {done,eof,eof}; get_line_echo_off1({Chars,Rest}, _Drv, _Shell) -> {done,lists:reverse(Chars),case Rest of done -> []; _ -> Rest end}. @@ -729,7 +722,7 @@ get_chars_echo_off1(Drv, Shell) -> {io_request,From,ReplyAs,Req} when is_pid(From) -> io_request(Req, From, ReplyAs, Drv, Shell, []), get_chars_echo_off1(Drv, Shell); - {reply,{{From,ReplyAs},Reply}} when From =/= undefined -> + {reply,{From,ReplyAs},Reply} when From =/= undefined -> %% We take care of replies from puts here as well io_reply(From, ReplyAs, Reply), get_chars_echo_off1(Drv, Shell); @@ -750,8 +743,10 @@ get_chars_echo_off1(Drv, Shell) -> %% - ^d in posix/icanon mode: eof, delete-forward in edlin %% - ^r in posix/icanon mode: reprint (silly in echo-off mode :-)) %% - ^w in posix/icanon mode: word-erase (produces a beep in edlin) +edit_line(eof, []) -> + eof; edit_line(eof, Chars) -> - {Chars,done}; + {Chars,eof}; edit_line([],Chars) -> {Chars,[]}; edit_line([$\r,$\n|Cs],Chars) -> @@ -877,7 +872,7 @@ get_password1({Chars,[]}, Drv, Shell) -> %% set to []. But do we expect anything but plain output? get_password1({Chars, []}, Drv, Shell); - {reply,{{From,ReplyAs},Reply}} -> + {reply,{From,ReplyAs},Reply} -> %% We take care of replies from puts here as well io_reply(From, ReplyAs, Reply), get_password1({Chars, []},Drv, Shell); diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src index c9fee98cf36d..0cac3d413b8a 100644 --- a/lib/kernel/src/kernel.app.src +++ b/lib/kernel/src/kernel.app.src @@ -109,6 +109,7 @@ inet_sctp, pg, pg2, + prim_tty, raw_file_io, raw_file_io_compressed, raw_file_io_deflate, @@ -158,6 +159,6 @@ {shell_docs_ansi,auto} ]}, {mod, {kernel, []}}, - {runtime_dependencies, ["erts-@OTP-17934@", "stdlib-4.0", "sasl-3.0", "crypto-5.0"]} + {runtime_dependencies, ["erts-@OTP-17934@", "stdlib-@OTP-17932@", "sasl-3.0", "crypto-5.0"]} ] }. diff --git a/lib/kernel/src/prim_tty.erl b/lib/kernel/src/prim_tty.erl new file mode 100644 index 000000000000..4b522b22395c --- /dev/null +++ b/lib/kernel/src/prim_tty.erl @@ -0,0 +1,866 @@ +%% %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% +%% +-module(prim_tty). + +%% Todo: +%% * Try to move buffer handling logic to Erlang +%% * This may not be possible for performance reasons, but should be tried +%% * It seems like unix decodes and then encodes utf8 when emitting it +%% * user_drv module should be able to handle both nif and driver without too many changes. +%% +%% Problems to solve: +%% * Do not use non blocking io +%% * Reset tty settings at _exit +%% * Allow remsh in oldshell (can we do this?) +%% * See if we can run a tty in windows shell +%% * Allow unicode detection for noshell/noinput +%% * ?Allow multi-line editing? +%% * The current implementation only allows the cursor to move and edit on current line +%% +%% Concepts to keep in mind: +%% Code point: A single unicode "thing", examples: "a", "๐Ÿ˜€" (unicode smilie) +%% Grapheme cluster: One or more code points, " +%% Logical character: Any character that the user typed or printed. +%% One unicode grapheme cluster is a logical character +%% Examples: "a", "\t", "๐Ÿ˜€" (unicode smilie), "\x{1F600}", "\e[0m" (ansi sequences), +%% "^C" +%% When we step or delete we count logical characters even if they are multiple chars. +%% (I'm unsure how ansi should be handled with regard to delete?) +%% +%% Actual characters: The actual unicode grapheme clusters printed +%% Column: The number of rendered columns for a logical character +%% +%% When navigating using move(left) and move(right) the terminal will move one +%% actual character, so if we want to move one logical character we may have to +%% emit more moves. The same is true when overwriting. +%% +%% When calculating the current column position we have to use the column size +%% of the characters as otherwise smilies will becomes incorrect. +%% +%% In the current ttysl_drv and also this implementation there are never any newlines +%% in the buffer. +%% +%% Printing of unicode characters: +%% Read this post: https://jeffquast.com/post/terminal_wcwidth_solution/ +%% Summary: the wcwidth implementation in libc is often lacking. So we should +%% create our own. We can get the size of all unicode graphemes by rendering +%% them on a terminal and see how much the cursor moves. We can query where the +%% cursor is by using "\033[6n"[1]. How many valid grapheme clusters are there? +%% +%% On consoles that does support fetching the surrent cursor position, we may get +%% away with only using that to check where we are and where to go. And on consoles +%% that do not we just have to make a best effort and use libc wcwidth. +%% +%% We need to know the width of characters when: +%% * Printing a \t +%% * Compensating for xn +%% [1]: https://www.linuxquestions.org/questions/programming-9/get-cursor-position-in-c-947833/ +%% Notes: +%% - [129306,127996] (hand with skintone) seems to move the cursor more than +%% it should on iTerm...The skintone code point seems to not +%% work at all on Linux and instead emits hand + brown square which is 4 characters +%% wide which is what the cursor on iTerm was positioned at. So maybe +%% there is a mismatch somewhere in iTerm wcwidth handling. +%% - edlin and user needs to agree on what one "character" is, right now edlin uses +%% code points and not grapheme clusters. +%% - We can deal with xn by always emitting it after a put_chars command. We must make +%% sure not to emit it before we emit any newline in the put_chars though as that +%% potentially will insert two newlines instead of one. +%% +%% Windows: +%% Since 2017:ish we can use Virtual Terminal Sequences[2] to control the terminal. +%% It seems like these are mostly ANSI escape comparitible, so it should be possible +%% to just use the same mechanism on windows and unix. +%% [2]: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences +%% Windows does not seem to have any wcwidth, so maybe we have to generate a similar table +%% as the PR in [3] and add string:width/1. +%% [3]: https://github.com/microsoft/terminal/pull/5795 +%% +%% +%% Things I've tried and discarded: +%% * Use get position to figure out xn fix for newlines +%% * There is too large a latency (about 10ms) to get the position, so things like +%% `c:i()` becomes a lot slower. +%% * Use tty insert mode +%% * This only works when the cursor is on the last line, when it is on a previous +%% line it only edit that line. +%% * Use tty delete mode +%% * Same problem as insert mode, it only deleted current line, and does not move +%% to previous line automatically. + +-export([init/1, reinit/2, isatty/1, handles/1, unicode/1, unicode/2, handle_signal/2, + window_size/1, handle_request/2, write/2, write/3, npwcwidth/1]). + +-nifs([isatty/1, tty_create/0, tty_init/3, tty_set/1, setlocale/1, + tty_select/3, tty_window_size/1, write_nif/2, read_nif/2, isprint/1, + wcwidth/1, wcswidth/1, + sizeof_wchar/0, tgetent_nif/1, tgetnum_nif/1, tgetflag_nif/1, tgetstr_nif/1, + tgoto_nif/2, tgoto_nif/3, tty_read_signal/2]). + +%% Exported in order to remove "unused function" warning +-export([sizeof_wchar/0, wcswidth/1, tgoto/2, tgoto/3]). + +%% proc_lib exports +-export([reader/1, writer/1]). + +-on_load(on_load/0). + +%%-define(debug, true). +-ifdef(debug). +-define(dbg(Term), dbg(Term)). +-else. +-define(dbg(Term), ok). +-endif. + +-record(state, {tty, + reader, + writer, + options, + unicode, + buffer_before = [], %% Current line before cursor in reverse + buffer_after = [], %% Current line after cursor not in reverse + cols = 80, + rows = 24, + xn = false, + up = <<"\e[A">>, + 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(), + input => 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(TTY) 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, + + WriterState = + if TTYState#state.writer =:= undefined -> + {ok, Writer} = proc_lib:start_link(?MODULE, writer, [State#state.tty]), + TTYState#state{ writer = Writer }; + true -> + TTYState + end, + ReaderState = + case {maps:get(input, Options), TTYState#state.reader} of + {true, undefined} -> + {ok, Reader} = proc_lib:start_link(?MODULE, reader, [[State#state.tty, self()]]), + WriterState#state{ reader = Reader }; + {true, _} -> + WriterState; + {false, undefined} -> + WriterState + end, + + update_geometry(ReaderState). + +-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 = Reader } = State, Bool) -> + case Reader of + {ReaderPid, _} -> + MonRef = erlang:monitor(process, ReaderPid), + ReaderPid ! {self(), set_unicode_state, Bool}, + receive + {ReaderPid, set_unicode_state, _} -> ok; + {'DOWN',MonRef,_,_,_} -> ok + end; + undefined -> + 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, _} -> + case isatty(stdin) of + true -> + {utf16, little}; + _ -> + %% When not reading from a console + %% the data read is utf8 encoded + utf8 + end + 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, reference()}. +write(#state{ writer = {WriterPid, _WriterRef}}, Chars, From) -> + Ref = erlang:monitor(process, WriterPid), + WriterPid ! {write, From, erlang:iolist_to_iovec(Chars)}, + {ok, Ref}. + +writer_loop(TTY, WriterRef) -> + receive + {write, []} -> + writer_loop(TTY, WriterRef); + {write, Chars} -> + _ = 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); + {error, Reason} -> + exit(self(), Reason) + 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(_TTY) -> + 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/standard_error.erl b/lib/kernel/src/standard_error.erl index 1aad064392b9..58831f0ba71b 100644 --- a/lib/kernel/src/standard_error.erl +++ b/lib/kernel/src/standard_error.erl @@ -66,6 +66,7 @@ server(PortName,PortSettings) -> run(P) -> put(encoding, latin1), + put(onlcr, false), server_loop(P). server_loop(Port) -> @@ -161,7 +162,7 @@ io_request({get_geometry,rows},Port) -> io_request(getopts, _Port) -> getopts(); io_request({setopts,Opts}, _Port) when is_list(Opts) -> - setopts(Opts); + do_setopts(Opts); io_request({requests,Reqs}, Port) -> io_requests(Reqs, {ok,ok}, Port); io_request(R, _Port) -> %Unknown request @@ -203,19 +204,27 @@ put_chars(Chars, Port) when is_binary(Chars) -> {ok,ok}. %% setopts -setopts(Opts0) -> +do_setopts(Opts0) -> Opts = expand_encoding(Opts0), case check_valid_opts(Opts) of true -> - do_setopts(Opts); + lists:foreach( + fun({encoding, Enc}) -> + put(encoding, Enc); + ({onlcr, Bool}) -> + put(onlcr, Bool) + end, Opts), + {ok, ok}; false -> {error,{error,enotsup}} end. check_valid_opts([]) -> true; -check_valid_opts([{encoding,Valid}|T]) when Valid =:= unicode; - Valid =:= utf8; Valid =:= latin1 -> +check_valid_opts([{encoding,Valid}|T]) when Valid =:= unicode; Valid =:= utf8; + Valid =:= latin1 -> + check_valid_opts(T); +check_valid_opts([{onlcr,Bool}|T]) when is_boolean(Bool) -> check_valid_opts(T); check_valid_opts(_) -> false. @@ -226,27 +235,21 @@ expand_encoding([latin1 | T]) -> [{encoding,latin1} | expand_encoding(T)]; expand_encoding([unicode | T]) -> [{encoding,unicode} | expand_encoding(T)]; +expand_encoding([utf8 | T]) -> + [{encoding,unicode} | expand_encoding(T)]; +expand_encoding([{encoding,utf8} | T]) -> + [{encoding,unicode} | expand_encoding(T)]; expand_encoding([H|T]) -> [H|expand_encoding(T)]. -do_setopts(Opts) -> - case proplists:get_value(encoding, Opts) of - Valid when Valid =:= unicode; Valid =:= utf8 -> - put(encoding, unicode); - latin1 -> - put(encoding, latin1); - undefined -> - ok - end, - {ok,ok}. - getopts() -> Uni = {encoding,get(encoding)}, - {ok,[Uni]}. + Onlcr = {onlcr, get(onlcr)}, + {ok,[Uni, Onlcr]}. wrap_characters_to_binary(Chars,From,To) -> - TrNl = (whereis(user_drv) =/= undefined), - Limit = case To of + TrNl = get(onlcr), + Limit = case To of latin1 -> 255; _Else -> diff --git a/lib/kernel/src/user.erl b/lib/kernel/src/user.erl index 67c2eafdbee5..81048922dfdf 100644 --- a/lib/kernel/src/user.erl +++ b/lib/kernel/src/user.erl @@ -23,7 +23,6 @@ %% Basic standard i/o server for user interface port. -export([start/0, start/1, start_out/0]). --export([interfaces/1]). -define(NAME, user). @@ -55,22 +54,6 @@ start_port(PortSettings) -> register(?NAME, Id), Id. -%% Return the pid of the shell process. -%% Note: We can't ask the user process for this info since it -%% may be busy waiting for data from the port. -interfaces(User) -> - case process_info(User, dictionary) of - {dictionary,Dict} -> - case lists:keysearch(shell, 1, Dict) of - {value,Sh={shell,Shell}} when is_pid(Shell) -> - [Sh]; - _ -> - [] - end; - _ -> - [] - end. - server(Pid) when is_pid(Pid) -> process_flag(trap_exit, true), link(Pid), diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl index fa7687bf2ae3..1a7575755758 100644 --- a/lib/kernel/src/user_drv.erl +++ b/lib/kernel/src/user_drv.erl @@ -19,591 +19,668 @@ %% -module(user_drv). -%% Basic interface to a port. - --export([start/0,start/1,start/2,start/3,server/2,server/3]). - --export([interfaces/1]). +%% Basic interface to stdin/stdout. +%% +%% This is responsible for a couple of things: +%% - Dispatching I/O messages when erl is running +%% * as a terminal. +%% * with -noshell +%% * with -noinput +%% 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, UnicodeCharacters :: list()}}` +%% - It serves as the job control manager (i.e. what happens when you type ^G) +%% - Starts potential -remsh sessions to other nodes +%% +-type message() :: + %% I/O requests that modify the terminal + {Sender :: pid(), request()} | + %% Query the server of the current dimensions of the terminal. + %% `Sender` will be sent the message: + %% `{DrvPid :: pid(), tty_geometry, {Width :: integer(), Height :: integer()}}` + {Sender :: pid(), tty_geometry} | + %% Query the server if it supports unicode characters + %% `Sender` will be sent the message: + %% `{DrvPid :: pid(), get_unicode_state, SupportUnicode :: boolean()}` + {Sender :: pid(), get_unicode_state} | + %% Change whether the server supports unicode characters or not. The reply + %% contains the previous unicode state. + %% `Sender` will be sent the message: + %% `{DrvPid :: pid(), set_unicode_state, SupportedUnicode :: boolean()}` + {Sender :: pid(), set_unicode_state, boolean()}. +-type request() :: + %% Put characters at current cursor position, + %% overwriting any characters it encounters. + {put_chars, unicode, binary()} | + %% Same as put_chars/3, but sends Reply to From when the characters are + %% guaranteed to have been written to the terminal + {put_chars_sync, unicode, binary(), {From :: pid(), Reply :: term()}} | + %% Move the cursor X characters left or right (negative is left) + {move_rel, -32768..32767} | + %% Insert characters at current cursor position moving any + %% characters after the cursor. + {insert_chars, unicode, binary()} | + %% Delete X chars before or after the cursor adjusting any test remaining + %% to the right of the cursor. + {delete_chars, -32768..32767} | + %% Trigger a terminal "bell" + beep | + %% Execute multiple request() actions + {requests, [request()]}. + +-export_type([message/0]). +-export([start/0, start/1, start_shell/0, start_shell/1]). + +%% gen_statem state callbacks +-export([init/3,server/3,switch_loop/3]). + +%% gen_statem callbacks +-export([init/1, callback_mode/0]). -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)). - -%% start() -%% start(ArgumentList) -%% start(PortName, Shell) -%% start(InPortName, OutPortName, Shell) -%% Start the user driver server. The arguments to start/1 are slightly -%% strange as this may be called both at start up from the command line -%% and explicitly from other code. - --spec start() -> pid(). - -start() -> %Default line editing shell - spawn(user_drv, server, ['tty_sl -c -e',{shell,start,[init]}]). +-record(state, { tty, write, read, shell_started = true, user, current_group, groups, queue }). -start([Pname]) -> - spawn(user_drv, server, [Pname,{shell,start,[init]}]); -start([Pname|Args]) -> - spawn(user_drv, server, [Pname|Args]); -start(Pname) -> - spawn(user_drv, server, [Pname,{shell,start,[init]}]). +-type shell() :: {module(), atom(), arity()} | {node(), module(), atom(), arity()}. +-type arguments() :: #{ initial_shell => noshell | shell() | {remote, unicode:charlist()}, + input => boolean() }. -start(Pname, Shell) -> - spawn(user_drv, server, [Pname,Shell]). +%% Default line editing shell +-spec start() -> pid(). +start() -> + case init:get_argument(remsh) of + {ok,[[Node]]} -> + start(#{ initial_shell => {remote, Node} }); + E when E =:= error ; E =:= {ok,[[]]} -> + start(#{ }) + end. -start(Iname, Oname, Shell) -> - spawn(user_drv, server, [Iname,Oname,Shell]). +-spec start_shell() -> ok | {error, Reason :: term()}. +start_shell() -> + start_shell(#{ }). +-spec start_shell(arguments()) -> ok | {error, enottty | already_started}. +start_shell(Args) -> + gen_statem:call(?MODULE, {start_shell, Args}). + +%% 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) -> + case gen_statem:start({local, ?MODULE}, ?MODULE, Args, []) of + {ok, Pid} -> Pid; + {error, _Reason} -> + user:start() + end. +callback_mode() -> state_functions. -%% Return the pid of the active group process. -%% Note: We can't ask the user_drv process for this info since it -%% may be busy waiting for data from the port. +-spec init(arguments()) -> gen_statem:init_result(init). +init(Args) -> + process_flag(trap_exit, true), + %% When running in embedded mode we need to call prim_tty:on_load manually here + %% as the automatic call happens after user is started. + ok = init:run_on_load_handlers([prim_tty]), + IsTTY = prim_tty:isatty(stdin) =:= true andalso prim_tty:isatty(stdout) =:= true, + StartShell = maps:get(initial_shell, Args, undefined) =/= noshell, + try + if IsTTY, StartShell -> + TTYState = prim_tty:init(#{}), + init_standard_error(TTYState, true), + {ok, init, {Args, #state{ user = start_user() } }, + {next_event, internal, TTYState}}; + not IsTTY, StartShell -> + %% We start an oldshell if stdout or stdin are not a TTY + %% and we have been told to start a shell. + {stop, normal}; + true -> + TTYState = prim_tty:init(#{input => maps:get(input, Args, true), + tty => false}), + init_standard_error(TTYState, false), + {ok, init, {Args,#state{ user = start_user() } }, + {next_event, internal, TTYState}} + end + 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. --spec interfaces(pid()) -> [{'current_group', pid()}]. +%% 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')]), + + #{ 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, {}) + }, + + case Args of + #{ initial_shell := noshell } -> + init_noshell(NewState); + #{ 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. -interfaces(UserDrv) -> - case process_info(UserDrv, dictionary) of - {dictionary,Dict} -> - case lists:keysearch(current_group, 1, Dict) of - {value,Gr={_,Group}} when is_pid(Group) -> - [Gr]; - _ -> - [] - end; - _ -> - [] +%% We have been started with -noshell. In this mode the current_group is +%% the `user` group process. +init_noshell(State) -> + init_shell(State#state{ shell_started = false }, ""). + +init_remote_shell(State, Node) -> + + StartedDist = + case net_kernel:get_state() of + #{ started := no } -> + {ok, _} = net_kernel:start([undefined, shortnames]), + true; + _ -> + false + end, + + LocalNode = + case net_kernel:get_state() of + #{ name_type := dynamic } -> + net_kernel:nodename(); + #{ name_type := static } -> + node() + end, + + RemoteNode = + case string:find(Node,"@") of + nomatch -> + list_to_atom(Node ++ string:find(atom_to_list(LocalNode),"@")); + _ -> + list_to_atom(Node) + end, + + 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, + + RShell = {RemoteNode,shell,start,[]}, + Gr = gr_add_cur(State#state.groups, + group:start(self(), RShell, remsh_opts(RemoteNode)), + 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. -%% server(Pid, Shell) -%% server(Pname, Shell) -%% server(Iname, Oname, Shell) -%% The initial calls to run the user driver. These start the port(s) -%% then call server1/3 to set everything else up. +init_local_shell(State, InitialShell) -> -server(Pid, Shell) when is_pid(Pid) -> - server1(Pid, Pid, Shell); -server(Pname, Shell) -> - process_flag(trap_exit, true), - case catch open_port({spawn,Pname}, [eof]) of - {'EXIT', _} -> - %% Let's try a dumb user instead - user:start(); - Port -> - server1(Port, Port, Shell) - end. + 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, -server(Iname, Oname, Shell) -> - process_flag(trap_exit, true), - case catch open_port({spawn,Iname}, [eof]) of - {'EXIT', _} -> %% It might be a dumb terminal lets start dumb user - user:start(); - Iport -> - Oport = open_port({spawn,Oname}, [eof]), - server1(Iport, Oport, Shell) - end. + Gr = gr_add_cur(State#state.groups, + group:start(self(), InitialShell), + InitialShell), -server1(Iport, Oport, Shell) -> - put(eof, false), - %% Start user and initial shell. - User = start_user(), - Gr1 = gr_add_cur(gr_new(), User, {}), - - {Curr,Shell1} = - 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, + init_shell(State#state{ groups = Gr }, [Slogan,$\n]). - RShell = {ANode,shell,start,[]}, - RGr = group:start(self(), RShell, rem_sh_opts(ANode)), - {RGr,RShell}; - E when E =:= error ; E =:= {ok,[[]]} -> - {group:start(self(), Shell),Shell} - end, - - put(current_group, Curr), - Gr = gr_add_cur(Gr1, Curr, Shell1), - %% Print some information. - io_request({put_chars, unicode, - flatten(io_lib:format("~ts\n", - [erlang:system_info(system_version)]))}, - Iport, Oport), - - %% Enter the server loop. - server_loop(Iport, Oport, Curr, User, Gr, {false, queue:new()}). - -append_hostname(Node, LocalNode) -> - case string:find(Node,"@") of - nomatch -> - list_to_atom(Node ++ string:find(atom_to_list(LocalNode),"@")); - _ -> - list_to_atom(Node) - end. +init_shell(State, Slogan) -> -rem_sh_opts(Node) -> - [{expand_fun,fun(B)-> rpc:call(Node,edlin_expand,expand,[B]) end}]. + 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_drv) of - undefined -> - register(user_drv, self()); - _ -> - ok - end, case whereis(user) of undefined -> - User = group:start(self(), {}), + User = group:start(self(), {}, [{echo,false}]), register(user, User), User; User -> User end. - -server_loop(Iport, Oport, User, Gr, IOQueue) -> - Curr = gr_cur_pid(Gr), - put(current_group, Curr), - server_loop(Iport, Oport, Curr, User, Gr, IOQueue). - -server_loop(Iport, Oport, Curr, User, Gr, {Resp, IOQ} = IOQueue) -> - receive - {Iport,{data,Bs}} -> - BsBin = list_to_binary(Bs), - Unicode = unicode:characters_to_list(BsBin,utf8), - port_bytes(Unicode, Iport, Oport, Curr, User, Gr, IOQueue); - {Iport,eof} -> - Curr ! {self(),eof}, - server_loop(Iport, Oport, Curr, User, Gr, IOQueue); - - %% We always handle geometry and unicode requests - {Requester,tty_geometry} -> - Requester ! {self(),tty_geometry,get_tty_geometry(Iport)}, - server_loop(Iport, Oport, Curr, User, Gr, IOQueue); - {Requester,get_unicode_state} -> - Requester ! {self(),get_unicode_state,get_unicode_state(Iport)}, - server_loop(Iport, Oport, Curr, User, Gr, IOQueue); - {Requester,set_unicode_state, Bool} -> - Requester ! {self(),set_unicode_state,set_unicode_state(Iport,Bool)}, - server_loop(Iport, Oport, Curr, User, Gr, IOQueue); - - Req 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,_,_} - NewQ = handle_req(Req, Iport, Oport, IOQueue), - server_loop(Iport, Oport, Curr, User, Gr, NewQ); - {Oport,ok} -> - %% We get this ok from the port, in io_request we store - %% info about where to send reply at head of queue - {Origin,Reply} = Resp, - Origin ! {reply,Reply}, - NewQ = handle_req(next, Iport, Oport, {false, IOQ}), - server_loop(Iport, Oport, Curr, User, Gr, NewQ); - {'EXIT',Iport,_R} -> - server_loop(Iport, Oport, Curr, User, Gr, IOQueue); - {'EXIT',Oport,_R} -> - server_loop(Iport, Oport, Curr, User, Gr, IOQueue); - {'EXIT',User,shutdown} -> % force data to port - server_loop(Iport, Oport, Curr, User, Gr, IOQueue); - {'EXIT',User,_R} -> % keep 'user' alive - NewU = start_user(), - server_loop(Iport, Oport, Curr, NewU, gr_set_num(Gr, 1, NewU, {}), IOQueue); - {'EXIT',Pid,R} -> % shell and group leader exit - case gr_cur_pid(Gr) of - Pid when R =/= die , - R =/= terminated -> % current shell exited - if R =/= normal -> - io_requests([{put_chars,unicode,"*** ERROR: "}], Iport, Oport); - true -> % exit not caused by error - io_requests([{put_chars,unicode,"*** "}], Iport, Oport) - end, - io_requests([{put_chars,unicode,"Shell process terminated! "}], Iport, Oport), - Gr1 = gr_del_pid(Gr, Pid), - case gr_get_info(Gr, Pid) of - {Ix,{shell,start,Params}} -> % 3-tuple == local shell - io_requests([{put_chars,unicode,"***\n"}], Iport, Oport), - %% restart group leader and shell, same index - Pid1 = group:start(self(), {shell,start,Params}), - {ok,Gr2} = gr_set_cur(gr_set_num(Gr1, Ix, Pid1, - {shell,start,Params}), Ix), - put(current_group, Pid1), - server_loop(Iport, Oport, Pid1, User, Gr2, IOQueue); - _ -> % remote shell - io_requests([{put_chars,unicode,"(^G to start new job) ***\n"}], - Iport, Oport), - server_loop(Iport, Oport, Curr, User, Gr1, IOQueue) - end; - _ -> % not current, just remove it - server_loop(Iport, Oport, Curr, User, gr_del_pid(Gr, Pid), IOQueue) - end; - {Requester, {put_chars_sync, _, _, Reply}} -> - %% We need to ack the Req otherwise originating process will hang forever - %% Do discard the output to non visible shells (as was done previously) - Requester ! {reply, Reply}, - server_loop(Iport, Oport, Curr, User, Gr, IOQueue); - _X -> - %% Ignore unknown messages. - server_loop(Iport, Oport, Curr, User, Gr, IOQueue) - end. -handle_req(next,Iport,Oport,{false,IOQ}=IOQueue) -> - case queue:out(IOQ) of - {empty,_} -> - IOQueue; - {{value,{Origin,Req}},ExecQ} -> - case io_request(Req, Iport, Oport) of - ok -> - handle_req(next,Iport,Oport,{false,ExecQ}); - Reply -> - {{Origin,Reply}, ExecQ} - end +server({call, From}, {start_shell, Args}, + State = #state{ tty = TTY, shell_started = false }) -> + case prim_tty:isatty(stdin) andalso prim_tty:isatty(stdout) of + true -> + try prim_tty:reinit(TTY, #{input => maps:get(input, Args, true) }) of + NewTTY -> + #{ read := ReadHandle, write := WriteHandle } = prim_tty:handles(NewTTY), + gen_statem:reply(From, ok), + NewState = State#state{ tty = NewTTY, + read = ReadHandle, + write = WriteHandle }, + case Args of + #{ initial_shell := noshell } -> + init_noshell(NewState); + #{ 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 + catch error:enotsup -> + gen_statem:reply(From, {error, enotsup}), + keep_state_and_data + end; + false -> + gen_statem:reply(From, {error, enottty}), + keep_state_and_data end; -handle_req(Msg,Iport,Oport,{false,IOQ}=IOQueue) -> - empty = queue:peek(IOQ), - {Origin,Req} = Msg, - case io_request(Req, Iport, Oport) of - ok -> - IOQueue; - Reply -> - {{Origin,Reply}, IOQ} +server({call, From}, {start_shell, _Args}, _State) -> + gen_statem:reply(From, {error, already_started}), + keep_state_and_data; +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 -> + case gr_get_info(State#state.groups, State#state.current_group) of + undefined -> ok; + _ -> exit(State#state.current_group, interrupt) + end, + keep_state_and_data; + none -> + State#state.current_group ! + {self(), {data, unicode:characters_to_list(UTF8Binary, utf8)}}, + keep_state_and_data end; -handle_req(Msg,_Iport,_Oport,{Resp, IOQ}) -> - %% All requests are queued when we have outstanding sync put_chars - {Resp, queue:in(Msg,IOQ)}. - -%% port_bytes(Bytes, InPort, OutPort, CurrentProcess, UserProcess, Group) -%% Check the Bytes from the port to see if it contains a ^G. If so, -%% either escape to switch_loop or restart the shell. Otherwise send -%% the bytes to Curr. - -port_bytes([$\^G|_Bs], Iport, Oport, _Curr, User, Gr, IOQueue) -> - handle_escape(Iport, Oport, User, Gr, IOQueue); - -port_bytes([$\^C|_Bs], Iport, Oport, Curr, User, Gr, IOQueue) -> - interrupt_shell(Iport, Oport, Curr, User, Gr, IOQueue); - -port_bytes([B], Iport, Oport, Curr, User, Gr, IOQueue) -> - Curr ! {self(),{data,[B]}}, - server_loop(Iport, Oport, Curr, User, Gr, IOQueue); -port_bytes(Bs, Iport, Oport, Curr, User, Gr, IOQueue) -> - case member($\^G, Bs) of - true -> - handle_escape(Iport, Oport, User, Gr, IOQueue); - false -> - Curr ! {self(),{data,Bs}}, - server_loop(Iport, Oport, Curr, User, Gr, IOQueue) - end. - -interrupt_shell(Iport, Oport, Curr, User, Gr, IOQueue) -> - case gr_get_info(Gr, Curr) of - undefined -> - ok; % unknown - _ -> - exit(Curr, interrupt) +server(info, {ReadHandle,eof}, State = #state{ read = ReadHandle }) -> + State#state.current_group ! {self(), eof}, + keep_state_and_data; +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, - server_loop(Iport, Oport, Curr, User, Gr, IOQueue). - -handle_escape(Iport, Oport, User, Gr, IOQueue) -> - case application:get_env(stdlib, shell_esc) of - {ok,abort} -> - Pid = gr_cur_pid(Gr), - exit(Pid, die), + keep_state_and_data; +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, {Requester, get_terminal_state}, _State) -> + Requester ! {self(), get_terminal_state, prim_tty:isatty(stdout) }, + keep_state_and_data; +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,_,_} + {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, MonitorRef, Reply}, IOQ} }) -> + %% We get this ok from the user_drv_writer, in io_request we store + %% info about where to send reply at head of queue + Origin ! {reply, Reply, ok}, + erlang:demonitor(MonitorRef, [flush]), + {NewTTYState, NewQueue} = handle_req(next, State#state.tty, {false, IOQ}), + {keep_state, State#state{ tty = NewTTYState, queue = NewQueue }}; +server(info, {'DOWN', MonitorRef, _, _, Reason}, + #state{ queue = {{Origin, MonitorRef, Reply}, _IOQ} }) -> + %% The writer process died, we send the correct error to the caller and + %% then stop this process. This will bring down all linked groups (including 'user'). + %% All writes from now on will throw badarg terminated. + Origin ! {reply, Reply, {error, Reason}}, + ?LOG_INFO("Failed to write to standard out (~p)", [Reason]), + stop; +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, ok}, + 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 }) -> + NewUser = start_user(), + {keep_state, State#state{ user = NewUser, + 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 + 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 + 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{ tty = NewTTyState, + current_group = NewGroup, + groups = Gr2 }}; + _ -> % remote shell + 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(_, _, _) -> + %% Ignore unknown messages. + keep_state_and_data. + +contains_ctrl_g_or_ctrl_c(<<$\^G,_/binary>>) -> + ctrl_g; +contains_ctrl_g_or_ctrl_c(<<$\^C,_/binary>>) -> + ctrl_c; +contains_ctrl_g_or_ctrl_c(<<_/utf8,T/binary>>) -> + contains_ctrl_g_or_ctrl_c(T); +contains_ctrl_g_or_ctrl_c(<<>>) -> + none. + +switch_loop(internal, init, State) -> + case application:get_env(stdlib, shell_esc, jcl) of + abort -> + CurrGroup = gr_cur_pid(State#state.groups), + exit(CurrGroup, die), Gr1 = - case gr_get_info(Gr, Pid) of - {_Ix,{}} -> % no shell - Gr; + case gr_get_info(State#state.groups, CurrGroup) of + {_Ix,{}} -> % no shell + State#state.groups; _ -> - receive {'EXIT',Pid,_} -> - gr_del_pid(Gr, Pid) + receive {'EXIT',CurrGroup,_} -> + gr_del_pid(State#state.groups, CurrGroup) after 1000 -> - Gr + State#state.groups end end, - Pid1 = group:start(self(), {shell,start,[]}), - io_request({put_chars,unicode,"\n"}, Iport, Oport), - server_loop(Iport, Oport, User, - gr_add_cur(Gr1, Pid1, {shell,start,[]}), IOQueue); - - _ -> % {ok,jcl} | undefined - io_request({put_chars,unicode,"\nUser switch command\n"}, Iport, Oport), + NewGroup = group:start(self(), {shell,start,[]}), + NewTTYState = io_requests([{put_chars,unicode,<<"\n">>}], State#state.tty), + {next_state, server, + State#state{ tty = NewTTYState, + groups = gr_add_cur(Gr1, NewGroup, {shell,start,[]})}}; + jcl -> + NewTTYState = + io_requests([{put_chars,unicode,<<"\nUser switch command (type h for help)\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(Gr)), - server_loop(Iport, Oport, User, switch_loop(Iport, Oport, Gr), IOQueue) - end. - -switch_loop(Iport, Oport, Gr) -> - Line = get_line(edlin:start(" --> "), Iport, Oport), - switch_cmd(erl_scan:string(Line), Iport, Oport, Gr). - -switch_cmd({ok,[{atom,_,c},{integer,_,I}],_}, Iport, Oport, Gr0) -> - case gr_set_cur(Gr0, I) of - {ok,Gr} -> Gr; - undefined -> unknown_group(Iport, Oport, Gr0) + edlin:init(gr_cur_pid(State#state.groups)), + {keep_state, State#state{ tty = NewTTYState }, + {next_event, internal, line}} + end; +switch_loop(internal, line, State) -> + {more_chars, Cont, Rs} = edlin:start(" --> "), + {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.groups) of + {ok, Groups} -> + {next_state, server, + State#state{ current_group = gr_cur_pid(Groups), groups = Groups } }; + {retry, Requests} -> + {keep_state, State#state{ tty = io_requests(Requests, State#state.tty) }, + {next_event, internal, line}}; + {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, _, _} -> + 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,{ReadHandle,{data,Cs}}, {Cont, #state{ read = ReadHandle } = State}) -> + case edlin:edit_line(unicode:characters_to_list(Cs), Cont) of + {done,Line,_Rest, Rs} -> + {keep_state, State#state{ tty = io_requests(Rs, State#state.tty) }, + {next_event, internal, {line, Line}}}; + {undefined,_Char,MoreCs,NewCont,Rs} -> + {keep_state, + {NewCont, State#state{ tty = io_requests(Rs ++ [beep], State#state.tty)}}, + {next_event, info, {ReadHandle,{data,MoreCs}}}}; + {more_chars,NewCont,Rs} -> + {keep_state, + {NewCont, State#state{ tty = io_requests(Rs, State#state.tty)}}}; + {blink,NewCont,Rs} -> + {keep_state, + {NewCont, State#state{ tty = io_requests(Rs, State#state.tty)}}, + 1000} end; -switch_cmd({ok,[{atom,_,c}],_}, Iport, Oport, Gr) -> - case gr_get_info(Gr, gr_cur_pid(Gr)) of - undefined -> - unknown_group(Iport, Oport, Gr); - _ -> - Gr +switch_loop(timeout, _, {_Cont, State}) -> + {keep_state_and_data, + {next_event, info, {State#state.read,{data,[]}}}}; +switch_loop(info, _Unknown, _State) -> + {keep_state_and_data, postpone}. + +switch_cmd([{atom,_,Key},{Type,_,Value}], Gr) + when Type =:= atom; Type =:= integer -> + 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() end; -switch_cmd({ok,[{atom,_,i},{integer,_,I}],_}, Iport, Oport, Gr) -> +switch_cmd({i, I}, Gr) -> case gr_get_num(Gr, I) of {pid,Pid} -> exit(Pid, interrupt), - switch_loop(Iport, Oport, Gr); + {retry, []}; undefined -> - unknown_group(Iport, Oport, Gr) - end; -switch_cmd({ok,[{atom,_,i}],_}, Iport, Oport, Gr) -> - Pid = gr_cur_pid(Gr), - case gr_get_info(Gr, Pid) of - undefined -> - unknown_group(Iport, Oport, Gr); - _ -> - exit(Pid, interrupt), - switch_loop(Iport, Oport, Gr) + unknown_group() end; -switch_cmd({ok,[{atom,_,k},{integer,_,I}],_}, Iport, Oport, Gr) -> +switch_cmd({k, I}, Gr) -> case gr_get_num(Gr, I) of {pid,Pid} -> exit(Pid, die), case gr_get_info(Gr, Pid) of {_Ix,{}} -> % no shell - switch_loop(Iport, Oport, Gr); + retry; _ -> - Gr1 = - receive {'EXIT',Pid,_} -> - gr_del_pid(Gr, Pid) - after 1000 -> - Gr - end, - switch_loop(Iport, Oport, Gr1) + receive {'EXIT',Pid,_} -> + {retry,[],gr_del_pid(Gr, Pid)} + after 1000 -> + {retry,[],Gr} + end end; undefined -> - unknown_group(Iport, Oport, Gr) - end; -switch_cmd({ok,[{atom,_,k}],_}, Iport, Oport, Gr) -> - Pid = gr_cur_pid(Gr), - Info = gr_get_info(Gr, Pid), - case Info of - undefined -> - unknown_group(Iport, Oport, Gr); - {_Ix,{}} -> % no shell - switch_loop(Iport, Oport, Gr); - _ -> - exit(Pid, die), - Gr1 = - receive {'EXIT',Pid,_} -> - gr_del_pid(Gr, Pid) - after 1000 -> - Gr - end, - switch_loop(Iport, Oport, Gr1) + unknown_group() end; -switch_cmd({ok,[{atom,_,j}],_}, Iport, Oport, Gr) -> - io_requests(gr_list(Gr), Iport, Oport), - switch_loop(Iport, Oport, Gr); -switch_cmd({ok,[{atom,_,s},{atom,_,Shell}],_}, Iport, Oport, Gr0) -> +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,[]}), - switch_loop(Iport, Oport, Gr); -switch_cmd({ok,[{atom,_,s}],_}, Iport, Oport, Gr0) -> - Pid = group:start(self(), {shell,start,[]}), - Gr = gr_add_cur(Gr0, Pid, {shell,start,[]}), - switch_loop(Iport, Oport, Gr); -switch_cmd({ok,[{atom,_,r}],_}, Iport, Oport, 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,[]}), + Pid = group:start(self(), {Node,shell,start,[]}, remsh_opts(Node)), Gr = gr_add_cur(Gr0, Pid, {Node,shell,start,[]}), - switch_loop(Iport, Oport, Gr); + {retry, [], Gr}; false -> - io_request({put_chars,unicode,"Not alive\n"}, Iport, Oport), - switch_loop(Iport, Oport, Gr0) + {retry, [{put_chars,unicode,<<"Node is not alive\n">>}]} + end; +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,[]}, remsh_opts(Node)), + Gr = gr_add_cur(Gr0, Pid, {Node,Shell,start,[]}), + {retry, [], Gr}; + false -> + {retry, [{put_chars,unicode,"Node is not alive\n"}]} end; -switch_cmd({ok,[{atom,_,r},{atom,_,Node}],_}, Iport, Oport, Gr0) -> - Pid = group:start(self(), {Node,shell,start,[]}), - Gr = gr_add_cur(Gr0, Pid, {Node,shell,start,[]}), - switch_loop(Iport, Oport, Gr); -switch_cmd({ok,[{atom,_,r},{atom,_,Node},{atom,_,Shell}],_}, - Iport, Oport, Gr0) -> - Pid = group:start(self(), {Node,Shell,start,[]}), - Gr = gr_add_cur(Gr0, Pid, {Node,Shell,start,[]}), - switch_loop(Iport, Oport, Gr); -switch_cmd({ok,[{atom,_,q}],_}, Iport, Oport, Gr) -> +switch_cmd(q, _Gr) -> case erlang:system_info(break_ignored) of true -> % noop - io_request({put_chars,unicode,"Unknown command\n"}, Iport, Oport), - switch_loop(Iport, Oport, Gr); + {retry, [{put_chars,unicode,<<"Unknown command\n">>}]}; false -> halt() end; -switch_cmd({ok,[{atom,_,h}],_}, Iport, Oport, Gr) -> - list_commands(Iport, Oport), - switch_loop(Iport, Oport, Gr); -switch_cmd({ok,[{'?',_}],_}, Iport, Oport, Gr) -> - list_commands(Iport, Oport), - switch_loop(Iport, Oport, Gr); -switch_cmd({ok,[],_}, Iport, Oport, Gr) -> - switch_loop(Iport, Oport, Gr); -switch_cmd({ok,_Ts,_}, Iport, Oport, Gr) -> - io_request({put_chars,unicode,"Unknown command\n"}, Iport, Oport), - switch_loop(Iport, Oport, Gr); -switch_cmd(_Ts, Iport, Oport, Gr) -> - io_request({put_chars,unicode,"Illegal input\n"}, Iport, Oport), - switch_loop(Iport, Oport, Gr). - -unknown_group(Iport, Oport, Gr) -> - io_request({put_chars,unicode,"Unknown job\n"}, Iport, Oport), - switch_loop(Iport, Oport, Gr). - -list_commands(Iport, Oport) -> +switch_cmd(h, _Gr) -> + {retry, list_commands()}; +switch_cmd([], _Gr) -> + {retry,[]}; +switch_cmd(_Ts, _Gr) -> + {retry, [{put_chars,unicode,<<"Unknown command\n">>}]}. + +unknown_group() -> + {retry,[{put_chars,unicode,<<"Unknown job\n">>}]}. + +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"}], - Iport, Oport). - -get_line({done,Line,_Rest,Rs}, Iport, Oport) -> - io_requests(Rs, Iport, Oport), - Line; -get_line({undefined,_Char,Cs,Cont,Rs}, Iport, Oport) -> - io_requests(Rs, Iport, Oport), - io_request(beep, Iport, Oport), - get_line(edlin:edit_line(Cs, Cont), Iport, Oport); -get_line({What,Cont0,Rs}, Iport, Oport) -> - io_requests(Rs, Iport, Oport), - receive - {Iport,{data,Cs}} -> - get_line(edlin:edit_line(Cs, Cont0), Iport, Oport); - {Iport,eof} -> - get_line(edlin:edit_line(eof, Cont0), Iport, Oport) - after - get_line_timeout(What) -> - get_line(edlin:edit_line([], Cont0), Iport, Oport) - end. - -get_line_timeout(blink) -> 1000; -get_line_timeout(more_chars) -> infinity. - -% Let driver report window geometry, -% definitely outside of the common interface -get_tty_geometry(Iport) -> - case (catch port_control(Iport,?CTRL_OP_GET_WINSIZE,[])) of - List when length(List) =:= 8 -> - <> = list_to_binary(List), - {W,H}; - _ -> - error - end. -get_unicode_state(Iport) -> - case (catch port_control(Iport,?CTRL_OP_GET_UNICODE_STATE,[])) of - [Int] when Int > 0 -> - true; - [Int] when Int =:= 0 -> - false; - _ -> - error - end. - -set_unicode_state(Iport, Bool) -> - Data = case Bool of - true -> [1]; - false -> [0] - end, - case (catch port_control(Iport,?CTRL_OP_SET_UNICODE_STATE,Data)) of - [Int] when Int > 0 -> - {unicode, utf8}; - [Int] when Int =:= 0 -> - {unicode, false}; - _ -> - error - end. - -%% io_request(Request, InPort, OutPort) -%% io_requests(Requests, InPort, OutPort) -%% Note: InPort is unused. -io_request({requests,Rs}, Iport, Oport) -> - io_requests(Rs, Iport, Oport); -io_request(Request, _Iport, Oport) -> - case io_command(Request) of - {Data, Reply} -> - true = port_command(Oport, Data), - Reply; - unhandled -> - ok - end. + [{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">>}]. + +remsh_opts(Node) -> + [{expand_fun,fun(B)-> rpc:call(Node,edlin_expand,expand,[B]) end}]. -io_requests([R|Rs], Iport, Oport) -> - io_request(R, Iport, Oport), - io_requests(Rs, Iport, Oport); -io_requests([], _Iport, _Oport) -> - 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. +-spec io_request(request(), prim_tty:state()) -> {noreply, prim_tty:state()} | + {term(), reference(), 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, MonitorRef} = prim_tty:write(NewTTY, Output, self()), + {Reply, MonitorRef, 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, MonitorRef, NewTTYState} -> + {NewTTYState, {{Origin, MonitorRef, 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, MonitorRef, NewTTYState} -> + {NewTTYState, {{Origin, MonitorRef, 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) @@ -611,100 +688,68 @@ io_command(_) -> %% gr_add_cur(Group, Pid, Shell) %% gr_set_cur(Group, Index) %% gr_cur_pid(Group) +%% gr_cur_index(Group) %% gr_del_pid(Group, Pid) %% Manage the group list. The group structure has the form: %% {NextIndex,CurrIndex,CurrPid,GroupList} %% %% where each element in the group list is: %% {Index,GroupPid,Shell} - +-record(group, { index, pid, shell }). +-record(gr, { next = 0, current = 0, pid = none, groups = []}). gr_new() -> - {0,0,none,[]}. - -gr_get_num({_Next,_CurI,_CurP,Gs}, I) -> - gr_get_num1(Gs, I). - -gr_get_num1([{I,_Pid,{}}|_Gs], I) -> - undefined; -gr_get_num1([{I,Pid,_S}|_Gs], I) -> - {pid,Pid}; -gr_get_num1([_G|Gs], I) -> - gr_get_num1(Gs, I); -gr_get_num1([], _I) -> - undefined. - -gr_get_info({_Next,_CurI,_CurP,Gs}, Pid) -> - gr_get_info1(Gs, Pid). - -gr_get_info1([{I,Pid,S}|_Gs], Pid) -> - {I,S}; -gr_get_info1([_G|Gs], I) -> - gr_get_info1(Gs, I); -gr_get_info1([], _I) -> - undefined. - -gr_add_cur({Next,_CurI,_CurP,Gs}, Pid, Shell) -> - {Next+1,Next,Pid,append(Gs, [{Next,Pid,Shell}])}. - -gr_set_cur({Next,_CurI,_CurP,Gs}, I) -> - case gr_get_num1(Gs, I) of - {pid,Pid} -> {ok,{Next,I,Pid,Gs}}; + #gr{}. +gr_new_group(I, P, S) -> + #group{ index = I, pid = P, shell = S }. + +gr_get_num(#gr{ groups = Gs }, I) -> + case lists:keyfind(I, #group.index, Gs) of + false -> undefined; + #group{ shell = {} } -> + undefined; + #group{ pid = Pid } -> + {pid, Pid} + end. + +gr_get_info(#gr{ groups = Gs }, Pid) -> + case lists:keyfind(Pid, #group.pid, Gs) of + false -> undefined; + #group{ index = I, shell = S } -> + {I, S} + end. + +gr_add_cur(#gr{ next = Next, groups = Gs}, Pid, Shell) -> + #gr{ next = Next + 1, current = Next, pid = Pid, + groups = Gs ++ [gr_new_group(Next, Pid, Shell)] + }. + +gr_set_cur(Gr, I) -> + case gr_get_num(Gr, I) of + {pid,Pid} -> {ok, Gr#gr{ current = I, pid = Pid }}; undefined -> undefined end. -gr_set_num({Next,CurI,CurP,Gs}, I, Pid, Shell) -> - {Next,CurI,CurP,gr_set_num1(Gs, I, Pid, Shell)}. - -gr_set_num1([{I,_Pid,_Shell}|Gs], I, NewPid, NewShell) -> - [{I,NewPid,NewShell}|Gs]; -gr_set_num1([{I,Pid,Shell}|Gs], NewI, NewPid, NewShell) when NewI > I -> - [{I,Pid,Shell}|gr_set_num1(Gs, NewI, NewPid, NewShell)]; -gr_set_num1(Gs, NewI, NewPid, NewShell) -> - [{NewI,NewPid,NewShell}|Gs]. - -gr_del_pid({Next,CurI,CurP,Gs}, Pid) -> - {Next,CurI,CurP,gr_del_pid1(Gs, Pid)}. - -gr_del_pid1([{_I,Pid,_S}|Gs], Pid) -> - Gs; -gr_del_pid1([G|Gs], Pid) -> - [G|gr_del_pid1(Gs, Pid)]; -gr_del_pid1([], _Pid) -> - []. - -gr_cur_pid({_Next,_CurI,CurP,_Gs}) -> - CurP. - -gr_list({_Next,CurI,_CurP,Gs}) -> - gr_list(Gs, CurI, []). - -gr_list([{_I,_Pid,{}}|Gs], Cur, Jobs) -> - gr_list(Gs, Cur, Jobs); -gr_list([{Cur,_Pid,Shell}|Gs], Cur, Jobs) -> - gr_list(Gs, Cur, [{put_chars, unicode,flatten(io_lib:format("~4w* ~w\n", [Cur,Shell]))}|Jobs]); -gr_list([{I,_Pid,Shell}|Gs], Cur, Jobs) -> - gr_list(Gs, Cur, [{put_chars, unicode,flatten(io_lib:format("~4w ~w\n", [I,Shell]))}|Jobs]); -gr_list([], _Cur, Jobs) -> - lists:reverse(Jobs). - -append([H|T], X) -> - [H|append(T, X)]; -append([], X) -> - X. - -member(X, [X|_Rest]) -> true; -member(X, [_H|Rest]) -> - member(X, Rest); -member(_X, []) -> false. - -flatten(List) -> - flatten(List, [], []). - -flatten([H|T], Cont, Tail) when is_list(H) -> - flatten(H, [T|Cont], Tail); -flatten([H|T], Cont, Tail) -> - [H|flatten(T, Cont, Tail)]; -flatten([], [H|Cont], Tail) -> - flatten(H, Cont, Tail); -flatten([], [], Tail) -> - Tail. +gr_set_num(Gr = #gr{ groups = Groups }, I, Pid, Shell) -> + NewGroups = lists:keystore(I, #group.index, Groups, gr_new_group(I,Pid,Shell)), + Gr#gr{ groups = NewGroups }. + + +gr_del_pid(Gr = #gr{ groups = Groups }, Pid) -> + Gr#gr{ groups = lists:keydelete(Pid, #group.pid, Groups) }. + + +gr_cur_pid(#gr{ pid = Pid }) -> + Pid. +gr_cur_index(#gr{ current = Index }) -> + Index. + +gr_list(#gr{ current = Current, groups = Groups}) -> + lists:flatmap( + fun(#group{ shell = {} }) -> + []; + (#group{ index = I, shell = S }) -> + Marker = ["*" || Current =:= I], + [{put_chars, unicode, + unicode:characters_to_binary( + io_lib:format("~4w~.1ts ~w\n", [I,Marker,S]))}] + end, Groups). diff --git a/lib/kernel/src/user_sup.erl b/lib/kernel/src/user_sup.erl index 6206a8d0ab50..a7cb1d906541 100644 --- a/lib/kernel/src/user_sup.erl +++ b/lib/kernel/src/user_sup.erl @@ -45,7 +45,7 @@ init(Flags) -> nouser -> ignore; {master, Master} -> - Pid = start_slave(Master), + Pid = start_relay(Master), {ok, Pid, Pid}; {M, F, A} -> case start_user(M, F, A) of @@ -56,7 +56,7 @@ init(Flags) -> end end. -start_slave(Master) -> +start_relay(Master) -> case rpc:call(Master, erlang, whereis, [user]) of User when is_pid(User) -> spawn(?MODULE, relay, [User]); @@ -122,9 +122,12 @@ get_user(Flags) -> check_flags([{nouser, []} |T], _) -> check_flags(T, nouser); check_flags([{user, [User]} | T], _) -> check_flags(T, {list_to_atom(User), start, []}); -check_flags([{noshell, []} | T], _) -> check_flags(T, {user, start, []}); +check_flags([{noshell, []} | T], _) -> + check_flags(T,{user_drv, start, [#{ initial_shell => noshell }]}); check_flags([{oldshell, []} | T], _) -> check_flags(T, {user, start, []}); -check_flags([{noinput, []} | T], _) -> check_flags(T, {user, start_out, []}); +check_flags([{noinput, []} | T], _) -> + check_flags(T, {user_drv, start, [#{ initial_shell => noshell, + input => false }]}); check_flags([{master, [Node]} | T], _) -> check_flags(T, {master, list_to_atom(Node)}); check_flags([_H | T], User) -> check_flags(T, User); diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile index 413349d98a20..e3103220076b 100644 --- a/lib/kernel/test/Makefile +++ b/lib/kernel/test/Makefile @@ -214,6 +214,7 @@ release_tests_spec: make_emakefile $(EMAKEFILE) $(COVERFILE) "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" @tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -) + $(INSTALL_DIR) "$(RELSYSDIR)/kernel_SUITE_data" $(INSTALL_DATA) $(ERL_TOP)/make/otp_version_tickets "$(RELSYSDIR)/kernel_SUITE_data" release_docs_spec: diff --git a/lib/kernel/test/application_SUITE.erl b/lib/kernel/test/application_SUITE.erl index 019ca3e7f1b1..4bfa07e2d69f 100644 --- a/lib/kernel/test/application_SUITE.erl +++ b/lib/kernel/test/application_SUITE.erl @@ -2191,17 +2191,16 @@ do_configfd_test_bash() -> ok -> case proplists:get_value(system_total_memory, memsup:get_system_memory_data()) of Memory when is_integer(Memory), - Memory > 16*1024*1024*1024 -> + Memory > 8*1024*1024*1024 -> application:stop(os_mon), - true = - ("magic42" =/= - RunInBash( - "erl " - "-noshell " - "-configfd 3 " - "-eval " - "'io:format(\"magic42\"),erlang:halt()' " - "3< <(erl -noshell -eval '(fun W(D) -> io:put_chars(D), W([D,D]) end)(<<\"00000000000000000\">>)') ")); + Res = RunInBash( + "erl " + "-noshell " + "-configfd 3 " + "-eval " + "'io:format(\"magic42\"),erlang:halt()' " + "3< <(erl -noshell -eval '(fun W(D) -> io:put_chars(D), W([D,<<\"00000000000000000\">>]) end)([])') "), + {match, _} = re:run(Res,"Max size 134217728 bytes exceeded"); _ -> io:format("Skipped huge file check to avoid flaky test on machine with less than 8GB of memory") end; diff --git a/lib/kernel/test/interactive_shell_SUITE.erl b/lib/kernel/test/interactive_shell_SUITE.erl index d303d42edaaa..bd002e907ff4 100644 --- a/lib/kernel/test/interactive_shell_SUITE.erl +++ b/lib/kernel/test/interactive_shell_SUITE.erl @@ -21,6 +21,20 @@ -include_lib("kernel/include/file.hrl"). -include_lib("common_test/include/ct.hrl"). +%% Things to add tests for: +%% - TERM=dumb +%% - Editing line > MAXSIZE (1 << 16) +%% - \t tests (use io:format("\t")) +%% - xn fix after Delete and Backspace +%% - octal_to_hex > 255 length (is this possible?) +%% 1222 0 : } else if (lastput == 0) { /* A multibyte UTF8 character */ +%% 1223 0 : for (i = 0; i < ubytes; ++i) { +%% 1224 0 : outc(ubuf[i]); +%% 1225 : } +%% 1226 : } else { +%% 1227 0 : outc(lastput); +%% - $TERM set to > 1024 long value + -export([all/0, suite/0, groups/0, init_per_suite/1, end_per_suite/1, init_per_group/2, end_per_group/2, init_per_testcase/2, end_per_testcase/2, @@ -32,25 +46,39 @@ shell_history_custom/1, shell_history_custom_errors/1, job_control_remote_noshell/1,ctrl_keys/1, get_columns_and_rows_escript/1, + shell_navigation/1, shell_xnfix/1, shell_delete/1, + shell_transpose/1, shell_search/1, shell_insert/1, + shell_update_window/1, shell_huge_input/1, + shell_invalid_unicode/1, shell_support_ansi_input/1, + shell_invalid_ansi/1, shell_suspend/1, shell_full_queue/1, + shell_unicode_wrap/1, shell_delete_unicode_wrap/1, + shell_delete_unicode_not_at_cursor_wrap/1, + shell_update_window_unicode_wrap/1, remsh_basic/1, remsh_longnames/1, remsh_no_epmd/1]). %% Exports for custom shell history module -export([load/0, add/1]). +%% For custom prompt testing +-export([prompt/1]). suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,3}}]. all() -> - [get_columns_and_rows_escript,get_columns_and_rows, - exit_initial, job_control_local, - job_control_remote, job_control_remote_noshell, - ctrl_keys, stop_during_init, wrap, - {group, shell_history}, - {group, remsh}]. + [{group, to_erl}, + {group, tty}]. groups() -> - [{shell_history, [], + [{to_erl,[], + [get_columns_and_rows_escript,get_columns_and_rows, + exit_initial, job_control_local, + job_control_remote, job_control_remote_noshell, + ctrl_keys, stop_during_init, wrap, + shell_invalid_ansi, + {group, shell_history}, + {group, remsh}]}, + {shell_history, [], [shell_history, shell_history_resize, shell_history_eaccess, @@ -65,25 +93,48 @@ groups() -> {remsh, [], [remsh_basic, remsh_longnames, - remsh_no_epmd]} + remsh_no_epmd]}, + {tty,[], + [{group,tty_unicode}, + {group,tty_latin1}, + shell_suspend, + shell_full_queue + ]}, + {tty_unicode,[parallel], + [{group,tty_tests}, + shell_invalid_unicode + %% unicode wrapping does not work right yet + %% shell_unicode_wrap, + %% shell_delete_unicode_wrap, + %% shell_delete_unicode_not_at_cursor_wrap, + %% shell_update_window_unicode_wrap + ]}, + {tty_latin1,[],[{group,tty_tests}]}, + {tty_tests, [parallel], + [shell_navigation, shell_xnfix, shell_delete, + shell_transpose, shell_search, shell_insert, + shell_update_window, shell_huge_input, + shell_support_ansi_input]} ]. init_per_suite(Config) -> Term = os:getenv("TERM", "dumb"), os:putenv("TERM", "vt100"), - case rtnode:get_default_shell() of - noshell -> - os:putenv("TERM",Term), - {skip, "No run_erl"}; - DefShell -> - [{default_shell,DefShell},{term,Term}|Config] - end. + [{term,Term}|Config]. end_per_suite(Config) -> Term = proplists:get_value(term,Config), os:putenv("TERM",Term), ok. +init_per_group(to_erl, Config) -> + case rtnode:get_progs() of + {error, Error} -> + {skip, Error}; + _ -> + DefShell = rtnode:get_default_shell(), + [{default_shell,DefShell}|Config] + end; init_per_group(remsh, Config) -> case proplists:get_value(default_shell, Config) of old -> {skip, "Not supported in old shell"}; @@ -94,6 +145,33 @@ init_per_group(shell_history, Config) -> old -> {skip, "Not supported in old shell"}; new -> Config end; +init_per_group(tty, Config) -> + case string:split(tmux("-V")," ") of + ["tmux",[Num,$.|_]] when Num >= $3, Num =< $9 -> + tmux("kill-session"), + "" = tmux("-u new-session -x 50 -y 60 -d"), + ["" = tmux(["set-environment '",Name,"' '",Value,"'"]) + || {Name,Value} <- os:env()], + Config; + ["tmux", Vsn] -> + {skip, "invalid tmux version " ++ Vsn ++ ". Need vsn 3 or later"}; + Error -> + {skip, "tmux not installed " ++ Error} + end; +init_per_group(Group, Config) when Group =:= tty_unicode; + Group =:= tty_latin1 -> + [Lang,_] = + string:split( + os:getenv("LC_ALL", + os:getenv("LC_CTYPE", + os:getenv("LANG","en_US.UTF-8"))),"."), + case Group of + tty_unicode -> + [{encoding, unicode},{env,[{"LC_ALL",Lang++".UTF-8"}]}|Config]; + tty_latin1 -> + % [{encoding, latin1},{env,[{"LC_ALL",Lang++".ISO-8859-1"}]}|Config], + {skip, "latin1 tests not implemented yet"} + end; init_per_group(sh_custom, Config) -> %% Ensure that ERL_AFLAGS will not override the value of the shell_history variable. {ok, Peer, Node} = ?CT_PEER(["-noshell","-kernel","shell_history","not_overridden"]), @@ -113,18 +191,39 @@ init_per_group(sh_custom, Config) -> init_per_group(_GroupName, Config) -> Config. +end_per_group(tty, _Config) -> + Windows = string:split(tmux("list-windows"), "\n", all), + lists:foreach( + fun(W) -> + case string:split(W, " ", all) of + ["0:" | _] -> ok; + [No, _Name | _] -> + "" = os:cmd(["tmux select-window -t ", string:split(No,":")]), + ct:log("~ts~n~ts",[W, os:cmd(lists:concat(["tmux capture-pane -p -e"]))]) + end + end, Windows), +% "" = os:cmd("tmux kill-session") + ok; end_per_group(_GroupName, Config) -> Config. -init_per_testcase(_Func, Config) -> - Config. +init_per_testcase(Func, Config) -> + Path = [Func, + [proplists:get_value(name,P) || + P <- [proplists:get_value(tc_group_properties,Config,[])] ++ + proplists:get_value(tc_group_path,Config,[])]], + [{tc_path, lists:concat(lists:join("-",lists:flatten(Path)))} | Config]. -end_per_testcase(_Case, _Config) -> - %% Terminate any connected nodes. They may disturb test cases that follow. - lists:foreach(fun(Node) -> - catch erpc:call(Node, erlang, halt, []) - end, nodes()), - ok. +end_per_testcase(_Case, Config) -> + case proplists:get_value(name, proplists:get_value(tc_group_properties, Config)) of + tty_tests -> ok; + _ -> + %% Terminate any connected nodes. They may disturb test cases that follow. + lists:foreach(fun(Node) -> + catch erpc:call(Node, erlang, halt, []) + end, nodes()), + ok + end. %%-define(DEBUG,1). -ifdef(DEBUG). @@ -248,6 +347,980 @@ test_columns_and_rows(new, _Args) -> [], "stty rows 40; stty columns 90; "). +shell_navigation(Config) -> + + Term = start_tty(Config), + + try + [begin + send_tty(Term,"{aaa,'b"++U++"b',ccc}"), + check_location(Term, {0, 0}), %% Check that cursor jump backward + check_content(Term, "{aaa,'b"++U++"b',ccc}$"), + timer:sleep(1000), %% Wait for cursor to jump back + check_location(Term, {0, width("{aaa,'b"++U++"b',ccc}")}), + send_tty(Term,"Home"), + check_location(Term, {0, 0}), + send_tty(Term,"End"), + check_location(Term, {0, width("{aaa,'b"++U++"b',ccc}")}), + send_tty(Term,"Left"), + check_location(Term, {0, width("{aaa,'b"++U++"b',ccc")}), + send_tty(Term,"C-Left"), + check_location(Term, {0, width("{aaa,'b"++U++"b',")}), + send_tty(Term,"C-Left"), + check_location(Term, {0, width("{aaa,")}), + send_tty(Term,"C-Right"), + check_location(Term, {0, width("{aaa,'b"++U++"b'")}), + send_tty(Term,"C-Left"), + check_location(Term, {0, width("{aaa,")}), + send_tty(Term,"C-Left"), + check_location(Term, {0, width("{")}), + send_tty(Term,"C-Left"), + check_location(Term, {0, 0}), + send_tty(Term,"C-E"), + check_location(Term, {0, width("{aaa,'b"++U++"b',ccc}")}), + send_tty(Term,"C-A"), + check_location(Term, {0, 0}), + send_tty(Term,"Enter") + end || U <- hard_unicode()], + ok + after + stop_tty(Term) + end. + +shell_xnfix(Config) -> + + Term = start_tty(Config), + + {_Rows, Cols} = get_window_size(Term), + {_Row, Col} = get_location(Term), + + As = lists:duplicate(Cols - Col - 1,"a"), + + try + [begin + check_location(Term, {0, 0}), + send_tty(Term,As), + check_content(Term,[As,$$]), + check_location(Term, {0, Cols - Col - 1}), + send_tty(Term,"a"), + check_location(Term, {0, -Col}), + send_tty(Term,"aaa"), + check_location(Term, {0, -Col + 3}), + [send_tty(Term,"Left") || _ <- lists:seq(1,3 + width(U))], + send_tty(Term,U), + %% a{Cols-1}U\naaaaa + check_content(Term,[lists:duplicate(Cols - Col - 1 - width(U),$a), + U,"\n",lists:duplicate(3+width(U), $a),"$"]), + check_location(Term, {0, -Col}), + send_tty(Term,"Left"), + send_tty(Term,U), + %% a{Cols-1}U\nUaaaaa + check_content(Term,[lists:duplicate(Cols - Col - 1 - width(U),$a), + U,"\n",U,lists:duplicate(3+width(U), $a),"$"]), + check_location(Term, {0, -Col}), + %% send_tty(Term,"Left"), + %% send_tty(Term,"BSpace"), + %% a{Cols-2}U\nUaaaaa + %% check_content(Term,[lists:duplicate(Cols - Col - 2 - width(U),$a), + %% U,"\n",U,lists:duplicate(3+width(U), $a),"$"]), + %% send_tty(Term,"BSpace"), + %% check_content(Term,[lists:duplicate(Cols - Col - 1 - width(U),$a), + %% U,U,"\n",lists:duplicate(3+width(U), $a),"$"]), + %% send_tty(Term,"aa"), + %% check_content(Term,[lists:duplicate(Cols - Col - 2 - width(U),$a), + %% U,"a\n",U,lists:duplicate(3+width(U), $a),"$"]), + %% check_location(Term, {0, -Col}), + send_tty(Term,"C-K"), + check_location(Term, {0, -Col}), + send_tty(Term,"C-A"), + check_location(Term, {-1, 0}), + send_tty(Term,"C-E"), + check_location(Term, {0, -Col}), + send_tty(Term,"Enter"), + ok + end || U <- hard_unicode()] + after + stop_tty(Term) + end. + + +%% Characters that are larger than 2 wide need special handling when they +%% are at the end of the current line. +shell_unicode_wrap(Config) -> + + Term = start_tty(Config), + + {_Rows, Cols} = get_window_size(Term), + {_Row, Col} = get_location(Term), + + try + [begin + FirstLine = [U,lists:duplicate(Cols - Col - width(U)*2 + 1,"a")], + OtherLineA = [U,lists:duplicate(Cols - width(U) * 2+1,"a")], + OtherLineB = [U,lists:duplicate(Cols - width(U) * 2+1,"b")], + OtherLineC = [U,lists:duplicate(Cols - width(U) * 2+1,"c")], + OtherLineD = [U,lists:duplicate(Cols - width(U) * 2+1,"d")], + send_tty(Term,FirstLine), + check_content(Term, [FirstLine,$$]), + check_location(Term, {0, Cols - Col - width(U)+1}), + + send_tty(Term,OtherLineA), + check_content(Term, [OtherLineA,$$]), + check_location(Term, {0, Cols - Col - width(U)+1}), + + send_tty(Term,OtherLineB), + check_content(Term, [OtherLineB,$$]), + check_location(Term, {0, Cols - Col - width(U)+1}), + + send_tty(Term,OtherLineC), + check_content(Term, [OtherLineC,$$]), + check_location(Term, {0, Cols - Col - width(U)+1}), + + send_tty(Term,OtherLineD), + check_content(Term, [OtherLineD,$$]), + check_location(Term, {0, Cols - Col - width(U)+1}), + + send_tty(Term,"C-A"), + check_location(Term, {-4, 0}), %% Broken + send_tty(Term,"Right"), + check_location(Term, {-4, width(U)}), %% Broken + + send_tty(Term,"DC"), %% Broken + check_content(Term, ["a.*",U,"$"]), + check_content(Term, ["^b.*",U,"c$"]), + check_content(Term, ["^c.*",U,"dd$"]), + + send_tty(Term,"a"), + check_content(Term, [FirstLine,$$]), + check_content(Term, [OtherLineA,$$]), + check_content(Term, [OtherLineB,$$]), + check_content(Term, [OtherLineC,$$]), + check_content(Term, [OtherLineD,$$]), + + send_tty(Term,"Enter") + end || U <- hard_unicode()] + after + stop_tty(Term) + end. + +shell_delete(Config) -> + + Term = start_tty(Config), + + try + + [ begin + send_tty(Term,"a"), + check_content(Term, "> a$"), + check_location(Term, {0, 1}), + send_tty(Term,"BSpace"), + check_location(Term, {0, 0}), + check_content(Term, ">$"), + send_tty(Term,"a"), + send_tty(Term,U), + check_location(Term, {0, width([$a, U])}), + send_tty(Term,"a"), + send_tty(Term,U), + check_location(Term, {0, width([$a,U,$a,U])}), + check_content(Term, ["> a",U,$a,U,"$"]), + send_tty(Term,"Left"), + send_tty(Term,"Left"), + send_tty(Term,"BSpace"), + check_location(Term, {0, width([$a])}), + check_content(Term, ["> aa",U,"$"]), + send_tty(Term,U), + check_location(Term, {0, width([$a,U])}), + send_tty(Term,"Left"), + send_tty(Term,"DC"), + check_location(Term, {0, width([$a])}), + check_content(Term, ["> aa",U,"$"]), + send_tty(Term,"DC"), + send_tty(Term,"DC"), + check_content(Term, ["> a$"]), + send_tty(Term,"C-E"), + check_location(Term, {0, width([$a])}), + send_tty(Term,"BSpace"), + check_location(Term, {0, width([])}) + end || U <- hard_unicode()] + after + stop_tty(Term) + end. + +%% When deleting characters at the edge of the screen that are "large", +%% we need to take special care. +shell_delete_unicode_wrap(Config) -> + + Term = start_tty(Config), + + {_Rows, Cols} = get_window_size(Term), + {_Row, Col} = get_location(Term), + + try + [begin + send_tty(Term,lists:duplicate(Cols - Col,"a")), + check_content(Term,"> a*$"), + send_tty(Term,[U,U,"aaaaa"]), + check_content(Term,["\n",U,U,"aaaaa$"]), + [send_tty(Term,"Left") || _ <- lists:seq(1,5+2)], + check_location(Term,{0,-Col}), + send_tty(Term,"BSpace"), + check_content(Term,"> a* \n"), + check_location(Term,{-1,Cols - Col - 1}), + send_tty(Term,"BSpace"), + check_content(Term,["> a*",U,"\n"]), + check_location(Term,{-1,Cols - Col - 2}), + send_tty(Term,"BSpace"), + check_content(Term,["> a*",U," \n"]), + check_location(Term,{-1,Cols - Col - 3}), + send_tty(Term,"BSpace"), + check_content(Term,["> a*",U,U,"\n"]), + check_content(Term,["\naaaaa$"]), + check_location(Term,{-1,Cols - Col - 4}), + send_tty(Term,"BSpace"), + check_content(Term,["> a*",U,U,"a\n"]), + check_content(Term,["\naaaa$"]), + check_location(Term,{-1,Cols - Col - 5}), + send_tty(Term,"Enter") + end || U <- hard_unicode()] + after + stop_tty(Term) + end. + +%% When deleting characters and a "large" characters is changing line we need +%% to take extra care +shell_delete_unicode_not_at_cursor_wrap(Config) -> + + Term = start_tty(Config), + + {_Rows, Cols} = get_window_size(Term), + {_Row, Col} = get_location(Term), + + try + [begin + send_tty(Term,lists:duplicate(Cols - Col,"a")), + check_content(Term,"> a*$"), + send_tty(Term,["a",U,"aaaaa"]), + check_content(Term,["\na",U,"aaaaa$"]), + send_tty(Term,"C-A"), + send_tty(Term,"DC"), + check_content(Term,["\n",U,"aaaaa$"]), + send_tty(Term,"DC"), + check_content(Term,["\n",U,"aaaaa$"]), + check_content(Term,["> a* \n"]), + send_tty(Term,"DC"), + check_content(Term,["\naaaaa$"]), + check_content(Term,["> a*",U,"\n"]), + send_tty(Term,"DC"), + check_content(Term,["\naaaa$"]), + check_content(Term,["> a*",U,"a\n"]), + send_tty(Term,"Enter") + end || U <- hard_unicode()] + after + stop_tty(Term) + end. + +%% When deleting characters and a "large" characters is changing line we need +%% to take extra care +shell_update_window_unicode_wrap(Config) -> + + Term = start_tty(Config), + + {_Rows, Cols} = get_window_size(Term), + {_Row, Col} = get_location(Term), + + try + [begin + send_tty(Term,lists:duplicate(Cols - Col - width(U) + 1,"a")), + check_content(Term,"> a*$"), + send_tty(Term,[U,"aaaaa"]), + check_content(Term,["> a* ?\n",U,"aaaaa$"]), + tmux(["resize-window -t ",tty_name(Term)," -x ",Cols+1]), + check_content(Term,["> a*",U,"\naaaaa$"]), + tmux(["resize-window -t ",tty_name(Term)," -x ",Cols]), + check_content(Term,["> a* ?\n",U,"aaaaa$"]), + send_tty(Term,"Enter") + end || U <- hard_unicode()] + after + stop_tty(Term) + end. + +shell_transpose(Config) -> + + Term = start_tty(Config), + + Unicode = [[$a]] ++ hard_unicode(), + + try + [ + begin + send_tty(Term,"a"), + [send_tty(Term,[CP]) || CP <- U], + send_tty(Term,"b"), + [[send_tty(Term,[CP]) || CP <- U2] || U2 <- Unicode], + send_tty(Term,"cde"), + check_content(Term, ["a",U,"b",Unicode,"cde$"]), + check_location(Term, {0, width(["a",U,"b",Unicode,"cde"])}), + send_tty(Term,"Home"), + check_location(Term, {0, 0}), + send_tty(Term,"Right"), + send_tty(Term,"Right"), + check_location(Term, {0, 1+width([U])}), + send_tty(Term,"C-T"), + check_content(Term, ["ab",U,Unicode,"cde$"]), + send_tty(Term,"C-T"), + check_content(Term, ["ab",hd(Unicode),U,tl(Unicode),"cde$"]), + [send_tty(Term,"C-T") || _ <- lists:seq(1,length(Unicode)-1)], + check_content(Term, ["ab",Unicode,U,"cde$"]), + send_tty(Term,"C-T"), + check_content(Term, ["ab",Unicode,"c",U,"de$"]), + check_location(Term, {0, width(["ab",Unicode,"c",U])}), + send_tty(Term,"End"), + check_location(Term, {0, width(["ab",Unicode,"c",U,"de"])}), + send_tty(Term,"Left"), + send_tty(Term,"Left"), + send_tty(Term,"BSpace"), + check_content(Term, ["ab",Unicode,"cde$"]), + send_tty(Term,"End"), + send_tty(Term,"Enter") + end || U <- Unicode], + ok + after + stop_tty(Term), + ok + end. + +shell_search(C) -> + + Term = start_tty(C), + {_Row, Cols} = get_location(Term), + + try + send_tty(Term,"a"), + send_tty(Term,"."), + send_tty(Term,"Enter"), + send_tty(Term,"'"), + send_tty(Term,"a"), + send_tty(Term,[16#1f600]), + send_tty(Term,"'"), + send_tty(Term,"."), + send_tty(Term,"Enter"), + check_location(Term, {0, 0}), + send_tty(Term,"C-r"), + check_location(Term, {0, - Cols + width(C, "(search)`': 'a๐Ÿ˜€'.") }), + send_tty(Term,"C-a"), + check_location(Term, {0, width(C, "'a๐Ÿ˜€'.")}), + send_tty(Term,"Enter"), + send_tty(Term,"C-r"), + check_location(Term, {0, - Cols + width(C, "(search)`': 'a๐Ÿ˜€'.") }), + send_tty(Term,"a"), + check_location(Term, {0, - Cols + width(C, "(search)`a': 'a๐Ÿ˜€'.") }), + send_tty(Term,"C-r"), + check_location(Term, {0, - Cols + width(C, "(search)`a': a.") }), + send_tty(Term,"BSpace"), + check_location(Term, {0, - Cols + width(C, "(search)`': 'a๐Ÿ˜€'.") }), + send_tty(Term,"BSpace"), + check_location(Term, {0, - Cols + width(C, "(search)`': 'a๐Ÿ˜€'.") }), + ok + after + stop_tty(Term), + ok + end. + +shell_insert(Config) -> + Term = start_tty(Config), + + try + send_tty(Term,"abcdefghijklm"), + check_content(Term, "abcdefghijklm$"), + check_location(Term, {0, 13}), + send_tty(Term,"Home"), + send_tty(Term,"Right"), + send_tty(Term,"C-T"), + send_tty(Term,"C-T"), + send_tty(Term,"C-T"), + send_tty(Term,"C-T"), + check_content(Term, "bcdeafghijklm$"), + send_tty(Term,"End"), + send_tty(Term,"Left"), + send_tty(Term,"Left"), + send_tty(Term,"BSpace"), + check_content(Term, "bcdeafghijlm$"), + ok + after + stop_tty(Term) + end. + +shell_update_window(Config) -> + Term = start_tty(Config), + + Text = lists:flatten(["abcdefghijklmabcdefghijklm"]), + {_Row, Col} = get_location(Term), + + try + send_tty(Term,Text), + check_content(Term,Text), + check_location(Term, {0, width(Text)}), + tmux(["resize-window -t ",tty_name(Term)," -x ",width(Text)+Col+1]), + send_tty(Term,"a"), + check_location(Term, {0, -Col}), + send_tty(Term,"BSpace"), + tmux(["resize-window -t ",tty_name(Term)," -x ",width(Text)+Col]), + %% xnfix bug! at least in tmux... seems to work in iTerm as it does not + %% need xnfix when resizing + check_location(Term, {0, -Col}), + tmux(["resize-window -t ",tty_name(Term)," -x ",width(Text) div 2 + Col]), + check_location(Term, {0, -Col + width(Text) div 2}), + ok + after + stop_tty(Term) + end. + +shell_huge_input(Config) -> + Term = start_tty(Config), + + ManyUnicode = lists:duplicate(100,hard_unicode()), + + try + send_tty(Term,ManyUnicode), + check_content(Term, hard_unicode_match(Config) ++ "$", + #{ replace => {"\n",""} }), + send_tty(Term,"Enter"), + ok + after + stop_tty(Term) + end. + +%% Test that the shell works when invalid utf-8 (aka latin1) is sent to it +shell_invalid_unicode(Config) -> + Term = start_tty(Config), + + InvalidUnicode = <<$รฅ,$รค,$รถ>>, %% รฅรครถ in latin1 + + try + send_tty(Term,hard_unicode()), + check_content(Term, hard_unicode() ++ "$"), + send_tty(Term,"Enter"), + check_content(Term, "illegal character"), + %% Send invalid utf-8 + send_stdin(Term,InvalidUnicode), + %% Check that the utf-8 was echoed + check_content(Term, "\\\\345\\\\344\\\\366$"), + send_tty(Term,"Enter"), + %% Check that the terminal entered "latin1" mode + send_tty(Term,"๐Ÿ˜€ํ•œ."), + check_content(Term, "\\Q\\360\\237\\230\\200\\355\\225\\234.\\E$"), + send_tty(Term,"Enter"), + %% Check that we can reset the encoding to unicode + send_tty(Term,"io:setopts([{encoding,unicode}])."), + send_tty(Term,"Enter"), + check_content(Term, "\nok\n"), + send_tty(Term,"๐Ÿ˜€ํ•œ"), + check_content(Term, "๐Ÿ˜€ํ•œ$"), + ok + after + stop_tty(Term), + ok + end. + + +%% Test the we can handle ansi insert, navigation and delete +%% We currently can not so skip this test +shell_support_ansi_input(Config) -> + + Term = start_tty(Config), + + BoldText = "\e[;1m", + ClearText = "\e[0m", + + try + send_stdin(Term,["{",BoldText,"a๐Ÿ˜€b",ClearText,"}"]), + timer:sleep(1000), + try check_location(Term, {0, width("{1ma๐Ÿ˜€bm}")}) of + _ -> + throw({skip, "Do not support ansi input"}) + catch _:_ -> + ok + end, + check_location(Term, {0, width("{a๐Ÿ˜€b}")}), + check_content(fun() -> get_content(Term,"-e") end, + ["{", BoldText, "a๐Ÿ˜€b", ClearText, "}"]), + send_tty(Term,"Left"), + send_tty(Term,"Left"), + check_location(Term, {0, width("{a๐Ÿ˜€")}), + send_tty(Term,"C-Left"), + check_location(Term, {0, width("{")}), + send_tty(Term,"End"), + send_tty(Term,"BSpace"), + send_tty(Term,"BSpace"), + check_content(Term, ["{", BoldText, "a๐Ÿ˜€"]), + ok + after + stop_tty(Term), + ok + end. + +%% Test the we can handle invalid ansi escape chars. +%% tmux cannot handle this... so we test this using to_erl +shell_invalid_ansi(_Config) -> + + InvalidAnsiPrompt = ["\e]94m",54620,44397,50612,47,51312,49440,47568,"\e]0m"], + + rtnode:run( + [{eval, fun() -> application:set_env( + stdlib, shell_prompt_func_test, + fun() -> InvalidAnsiPrompt end) + end }, + {putline,"a."}, + {expect, "a[.]"}, + {expect, ["\\Q",InvalidAnsiPrompt,"\\E"]}], + "", "", + ["-pz",filename:dirname(code:which(?MODULE)), + "-connect_all","false", + "-kernel","logger_level","all", + "-kernel","shell_history","disabled", + "-kernel","prevent_overlapping_partitions","false", + "-eval","shell:prompt_func({interactive_shell_SUITE,prompt})." + ]). + + +%% We test that suspending of `erl` and then resuming restores the shell +shell_suspend(Config) -> + + Name = peer:random_name(proplists:get_value(tc_path,Config)), + %% In order to suspend `erl` we need it to run in a shell that has job control + %% so we start the peer within a tmux window instead of having it be the original + %% process. + os:cmd("tmux new-window -n " ++ Name ++ " -d -- bash --norc"), + + Peer = #{ name => Name, + post_process_args => + fun(["new-window","-n",_,"-d","--"|CmdAndArgs]) -> + FlatCmdAndArgs = + lists:join( + " ",[[$',A,$'] || A <- CmdAndArgs]), + ["send","-t",Name,lists:flatten(FlatCmdAndArgs),"Enter"] + end + }, + + + Term = start_tty([{peer, Peer}|Config]), + + try + send_tty(Term, hard_unicode()), + check_content(Term,["2> ",hard_unicode(),"$"]), + send_tty(Term, "C-Z"), + check_content(Term,"\\Q[1]+\\E\\s*Stopped"), + send_tty(Term, "fg"), + send_tty(Term, "Enter"), + send_tty(Term, "C-L"), + check_content(Term,["2> ",hard_unicode(),"$"]), + check_location(Term,{0,width(hard_unicode())}), + ok + after + stop_tty(Term), + ok + end. + +%% We test that suspending of `erl` and then resuming restores the shell +shell_full_queue(Config) -> + + %% In order to fill the read buffer of the terminal we need to get a + %% bit creative. We first need to start erl in bash in order to be + %% able to get access to job control for suspended processes. + %% We then also wrap `erl` in `unbuffer -p` so that we can suspend + %% that program in order to block writing to stdout for a while. + + Name = peer:random_name(proplists:get_value(tc_path,Config)), + os:cmd("tmux new-window -n " ++ Name ++ " -d -- bash --norc"), + + Peer = #{ name => Name, + post_process_args => + fun(["new-window","-n",_,"-d","--"|CmdAndArgs]) -> + FlatCmdAndArgs = ["unbuffer -p "] ++ + lists:join( + " ",[[$',A,$'] || A <- CmdAndArgs]), + ["send","-t",Name,lists:flatten(FlatCmdAndArgs),"Enter"] + end + }, + + + Term = start_tty([{peer, Peer}|Config]), + + UnbufferedPid = os:cmd("ps -o ppid= -p " ++ rpc(Term,os,getpid,[])), + + WriteUntilStopped = + fun F(Char) -> + rpc(Term,io,format,[user,[Char],[]]), + put(bytes,get(bytes,0)+1), + receive + stop -> + rpc(Term,io,format,[user,[Char+1],[]]) + after 0 -> F(Char) + end + end, + + WaitUntilBlocked = + fun(Pid, Ref) -> + (fun F(Cnt) -> + receive + {'DOWN',Ref,_,_,_} = Down -> + ct:fail({io_format_did_not_block, Down}) + after 1000 -> + ok + end, + case process_info(Pid,dictionary) of + {dictionary,[{bytes,Cnt}]} -> + ct:log("Bytes until blocked: ~p~n",[Cnt]), + %% Add one extra byte as for + %% the current blocking call + Cnt + 1; + {dictionary,[{bytes,NewCnt}]} -> + F(NewCnt) + end + end)(0) + end, + + try + %% First test that we can suspend and then resume + os:cmd("kill -TSTP " ++ UnbufferedPid), + check_content(Term,"\\Q[1]+\\E\\s*Stopped"), + {Pid, Ref} = spawn_monitor(fun() -> WriteUntilStopped($a) end), + WaitUntilBlocked(Pid, Ref), + send_tty(Term, "fg"), + send_tty(Term, "Enter"), + Pid ! stop, + check_content(Term,"b$"), + + send_tty(Term, "."), + send_tty(Term, "Enter"), + + %% Then we test that all characters are written when system + %% is terminated just after writing + {ok,Cols} = rpc(Term,io,columns,[user]), + send_tty(Term, "Enter"), + os:cmd("kill -TSTP " ++ UnbufferedPid), + check_content(Term,"\\Q[1]+\\E\\s*Stopped"), + {Pid2, Ref2} = spawn_monitor(fun() -> WriteUntilStopped($c) end), + Bytes = WaitUntilBlocked(Pid2, Ref2) - 1, + stop_tty(Term), + send_tty(Term, "fg"), + send_tty(Term, "Enter"), + check_content( + fun() -> + tmux(["capture-pane -p -S - -E - -t ",tty_name(Term)]) + end, lists:flatten([lists:duplicate(Cols,$c) ++ "\n" || + _ <- lists:seq(1,(Bytes) div Cols)] + ++ [lists:duplicate((Bytes) rem Cols,$c)])), + ct:log("~ts",[tmux(["capture-pane -p -S - -E - -t ",tty_name(Term)])]), + ok + after + stop_tty(Term), + ok + end. + +get(Key,Default) -> + case get(Key) of + undefined -> + Default; + Value -> + Value + end. + +%% A list of unicode graphemes that are notoriously hard to render +hard_unicode() -> + ZWJ = + case os:type() of + %% macOS has very good rendering of ZWJ, + %% but the cursor does not agree with it.. + {unix, darwin} -> []; + _ -> [[16#1F91A,16#1F3FC]] % Hand with skintone ๐Ÿคš๐Ÿผ + end, + [[16#1f600], % Smilie ๐Ÿ˜€ + "ํ•œ", % Hangul + "Zองฬ‘ฬ“ฬคอ”","aฬˆฬˆฬ‡อ–ฬญ","lอฎฬ’อซ","gฬŒฬšฬ—อš","oฬ”อฎฬ‡อฬ‡ฬ™" %% Vertically stacked chars + %%"๐Ÿ‘ฉโ€๐Ÿ‘ฉ", % Zero width joiner + %%"๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ" % Zero width joiner + | ZWJ]. + +hard_unicode_match(Config) -> + ["\\Q",[unicode_to_octet(Config, U) || U <- hard_unicode()],"\\E"]. + +unicode_to_octet(Config, U) -> + case ?config(encoding,Config) of + unicode -> U; + latin1 -> unicode_to_octet(U) + end. + +unicode_to_octet(U) -> + [if Byte >= 128 -> [$\\,integer_to_list(Byte,8)]; + true -> Byte + end || <> <= unicode:characters_to_binary(U)]. + +unicode_to_hex(Config, U) -> + case ?config(encoding,Config) of + unicode -> U; + latin1 -> unicode_to_hex(U) + end. + +unicode_to_hex(U) when is_integer(U) -> + unicode_to_hex([U]); +unicode_to_hex(Us) -> + [if U < 128 -> U; + U < 512 -> ["\\",integer_to_list(U,8)]; + true -> ["\\x{",integer_to_list(U,16),"}"] + end || U <- Us]. + +width(C, Str) -> + case ?config(encoding, C) of + unicode -> width(Str); + latin1 -> width(unicode_to_octet(Str)) + end. +width(Str) -> + lists:sum( + [npwcwidth(CP) || CP <- lists:flatten(Str)]). + +npwcwidth(CP) -> + try prim_tty:npwcwidth(CP) + catch error:undef -> + if CP =:= 16#D55C -> + 2; %% ํ•œ + CP =:= 16#1f91A -> + 2; %% hand + CP =:= 16#1F3Fc -> + 2; %% Skintone + CP =:= 16#1f600 -> + 2; %% smilie + true -> + case lists:member(CP, [775,776,780,785,786,787,788,791,793,794, + 804,813,848,852,854,858,871,875,878]) of + true -> + 0; + false -> + 1 + end + end + end. + +-record(tmux, {peer, node, name, orig_location }). + +tmux([Cmd|_] = Command) when is_list(Cmd) -> + tmux(lists:concat(Command)); +tmux(Command) -> + string:trim(os:cmd(["tmux ",Command])). + +rpc(#tmux{ node = N }, M, F, A) -> + erpc:call(N, M, F, A). + +start_tty(Config) -> + + %% Start an node in an xterm + %% {ok, XPeer, _XNode} = ?CT_PEER(#{ exec => + %% {os:find_executable("xterm"), + %% ["-hold","-e",os:find_executable("erl")]}, + %% detached => false }), + + Name = maps:get(name,proplists:get_value(peer, Config, #{}), + peer:random_name(proplists:get_value(tc_path, Config))), + + Envs = lists:flatmap(fun({Key,Value}) -> + ["-env",Key,Value] + end, proplists:get_value(env,Config,[])), + + ExecArgs = case os:getenv("TMUX_DEBUG") of + "strace" -> + STraceLog = filename:join(proplists:get_value(priv_dir,Config), + Name++".strace"), + ct:pal("Link to strace: file://~ts", [STraceLog]), + [os:find_executable("strace"),"-f", + "-o",STraceLog, + "-e","trace=all", + "-e","read=0,1,2", + "-e","write=0,1,2" + ] ++ string:split(ct:get_progname()," ",all); + "rr" -> + [os:find_executable("cerl"),"-rr"]; + _ -> + string:split(ct:get_progname()," ",all) + end, + DefaultPeerArgs = #{ name => Name, + exec => + {os:find_executable("tmux"), + ["new-window","-n",Name,"-d","--"] ++ ExecArgs }, + + args => ["-pz",filename:dirname(code:which(?MODULE)), + "-connect_all","false", + "-kernel","logger_level","all", + "-kernel","shell_history","disabled", + "-kernel","prevent_overlapping_partitions","false", + "-eval","shell:prompt_func({interactive_shell_SUITE,prompt})." + ] ++ Envs, + detached => false + }, + + {ok, Peer, Node} = + ?CT_PEER(maps:merge(proplists:get_value(peer,Config,#{}), + DefaultPeerArgs)), + + Self = self(), + + %% By default peer links with the starter. For these TCs we however only + %% want the peer to die if we die, so we create a "unidirection link" using + %% monitors. + spawn(fun() -> + TCRef = erlang:monitor(process, Self), + PeerRef = erlang:monitor(process, Peer), + receive + {'DOWN',TCRef,_,_,Reason} -> + exit(Peer, Reason); + {'DOWN',PeerRef,_,_,_} -> + ok + end + end), + unlink(Peer), + + Prompt = fun() -> ["\e[94m",54620,44397,50612,47,51312,49440,47568,"\e[0m"] end, + erpc:call(Node, application, set_env, + [stdlib, shell_prompt_func_test, + proplists:get_value(shell_prompt_func_test, Config, Prompt)]), + + "" = tmux(["set-option -t ",Name," remain-on-exit on"]), + Term = #tmux{ peer = Peer, node = Node, name = Name }, + {Rows, _} = get_window_size(Term), + + %% We send a lot of newlines here in order for the number of rows + %% in the window to be max so that we can predict what the cursor + %% position is. + [send_tty(Term,"\n") || _ <- lists:seq(1, Rows)], + + %% We start tracing on the remote node in order to help debugging + TraceLog = filename:join(proplists:get_value(priv_dir,Config),Name++".trace"), + ct:log("Link to trace: file://~ts",[TraceLog]), + + spawn(Node, + fun() -> + {ok, _} = dbg:tracer(file,TraceLog), + %% dbg:p(whereis(user_drv),[c,m]), + %% dbg:p(whereis(user_drv_writer),[c,m]), + %% dbg:p(whereis(user_drv_reader),[c,m]), + %% dbg:tp(user_drv,x), + %% dbg:tp(prim_tty,x), + %% dbg:tpl(prim_tty,write_nif,x), + %% dbg:tpl(prim_tty,read_nif,x), + monitor(process, Self), + receive _ -> ok end + end), + + %% We enter an 'a' here so that we can get the correct orig position + %% with an alternative prompt. + send_tty(Term,"a.\n"), + check_content(Term,"2>$"), + OrigLocation = get_location(Term), + Term#tmux{ orig_location = OrigLocation }. + +prompt(L) -> + N = proplists:get_value(history, L, 0), + Fun = application:get_env(stdlib, shell_prompt_func_test, + fun() -> atom_to_list(node()) end), + io_lib:format("(~ts)~w> ",[Fun(),N]). + +stop_tty(Term) -> + catch peer:stop(Term#tmux.peer), + ct:log("~ts",[get_content(Term, "-e")]), +% "" = tmux("kill-window -t " ++ Term#tmux.name), + ok. + +tty_name(Term) -> + Term#tmux.name. + +send_tty(Term, "Home") -> + %% https://stackoverflow.com/a/55616731 + send_tty(Term,"Escape"), + send_tty(Term,"OH"); +send_tty(Term, "End") -> + send_tty(Term,"Escape"), + send_tty(Term,"OF"); +send_tty(#tmux{ name = Name } = _Term,Value) -> + [Head | Quotes] = string:split(Value, "'", all), + "" = tmux("send -t " ++ Name ++ " '" ++ Head ++ "'"), + [begin + "" = tmux("send -t " ++ Name ++ " \"'\""), + "" = tmux("send -t " ++ Name ++ " '" ++ V ++ "'") + end || V <- Quotes]. + +%% We use send_stdin for testing of things that we cannot sent via +%% the tmux send command, such as invalid unicode +send_stdin(Term, Chars) when is_binary(Chars) -> + rpc(Term,erlang,display_string,[stdin,Chars]); +send_stdin(Term, Chars) -> + send_stdin(Term, iolist_to_binary(unicode:characters_to_binary(Chars))). + +check_location(Term, Where) -> + check_location(Term, Where, 5). +check_location(#tmux{ orig_location = {OrigRow, OrigCol} = Orig } = Term, + {AdjRow, AdjCol} = Where, Attempt) -> + NewLocation = get_location(Term), + case {OrigRow+AdjRow,OrigCol+AdjCol} of + NewLocation -> NewLocation; + _ when Attempt =:= 0 -> + {NewRow, NewCol} = NewLocation, + ct:fail({wrong_location, {expected,{AdjRow, AdjCol}}, + {got,{NewRow - OrigRow, NewCol - OrigCol}, + {NewLocation, Orig}}}); + _ -> + timer:sleep(50), + check_location(Term, Where, Attempt -1) + end. + +get_location(Term) -> + RowAndCol = tmux("display -pF '#{cursor_y} #{cursor_x}' -t "++Term#tmux.name), + [Row, Col] = string:lexemes(string:trim(RowAndCol,both)," "), + {list_to_integer(Row), list_to_integer(Col)}. + +get_window_size(Term) -> + RowAndCol = tmux("display -pF '#{window_height} #{window_width}' -t "++Term#tmux.name), + [Row, Col] = string:lexemes(string:trim(RowAndCol,both)," "), + {list_to_integer(Row), list_to_integer(Col)}. + +check_content(Term, Match) -> + check_content(Term, Match, #{}). +check_content(Term, Match, Opts) when is_map(Opts) -> + check_content(Term, Match, Opts, 5). +check_content(Term, Match, Opts, Attempt) -> + OrigContent = case Term of + #tmux{} -> get_content(Term); + Fun when is_function(Fun,0) -> Fun() + end, + Content = case maps:find(replace, Opts) of + {ok, {RE,Repl} } -> + re:replace(OrigContent, RE, Repl, [global]); + error -> + OrigContent + end, + case re:run(string:trim(Content, both), lists:flatten(Match), [unicode]) of + {match,_} -> + ok; + _ when Attempt =:= 0 -> + io:format("Failed to find '~ts' in ~n'~ts'~n", + [unicode:characters_to_binary(Match), Content]), + io:format("Failed to find '~w' in ~n'~w'~n", + [unicode:characters_to_binary(Match), Content]), + ct:fail(nomatch); + _ -> + timer:sleep(500), + check_content(Term, Match, Opts, Attempt - 1) + end. + +get_content(Term) -> + get_content(Term, ""). +get_content(#tmux{ name = Name }, Args) -> + Content = unicode:characters_to_binary(tmux("capture-pane -p " ++ Args ++ " -t " ++ Name)), + case string:split(Content,"a.\na") of + [_Ignore,C] -> + C; + [C] -> + C + end. + %% Tests that exit of initial shell restarts shell. exit_initial(Config) when is_list(Config) -> case proplists:get_value(default_shell, Config) of @@ -868,40 +1941,42 @@ remsh_longnames(Config) when is_list(Config) -> "@127.0.0.1"; _ -> "" end, - case rtnode:start(" -name " ++ atom_to_list(?FUNCTION_NAME)++Domain) of + Name = peer:random_name(?FUNCTION_NAME), + case rtnode:start(" -name " ++ Name ++ Domain) of {ok, _SRPid, STPid, SNode, SState} -> try {ok, _CRPid, CTPid, CNode, CState} = rtnode:start("-name undefined" ++ Domain ++ - " -remsh " ++ atom_to_list(?FUNCTION_NAME)), + " -remsh " ++ Name), try ok = rtnode:send_commands( SNode, STPid, [{putline, ""}, {putline, "node()."}, - {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"}], 1), + {expect, "\\Q" ++ Name ++ "\\E"}], 1), ok = rtnode:send_commands( CNode, CTPid, [{putline, ""}, {putline, "node()."}, - {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"} | quit_hosting_node()], 1) + {expect, "\\Q" ++ Name ++ "\\E"} | quit_hosting_node()], 1) after rtnode:dump_logs(rtnode:stop(CState)) end after rtnode:dump_logs(rtnode:stop(SState)) end; - Else -> + {skip, _} = Else -> Else end. %% Test that -remsh works without epmd. remsh_no_epmd(Config) when is_list(Config) -> EPMD_ARGS = "-start_epmd false -erl_epmd_port 12345 ", + Name = ?CT_PEER_NAME(), case rtnode:start([],"ERL_EPMD_PORT=12345 ", - EPMD_ARGS ++ " -sname " ++ atom_to_list(?FUNCTION_NAME)) of + EPMD_ARGS ++ " -sname " ++ Name) of {ok, _SRPid, STPid, SNode, SState} -> try ok = rtnode:send_commands( @@ -909,24 +1984,24 @@ remsh_no_epmd(Config) when is_list(Config) -> STPid, [{putline, ""}, {putline, "node()."}, - {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"}], 1), + {expect, "\\Q" ++ Name ++ "\\E"}], 1), {ok, _CRPid, CTPid, CNode, CState} = rtnode:start([],"ERL_EPMD_PORT=12345 ", - EPMD_ARGS ++ " -remsh "++atom_to_list(?FUNCTION_NAME)), + EPMD_ARGS ++ " -remsh "++Name), try ok = rtnode:send_commands( CNode, CTPid, [{putline, ""}, {putline, "node()."}, - {expect, "\\Q" ++ atom_to_list(?FUNCTION_NAME) ++ "\\E"} | quit_hosting_node()], 1) + {expect, "\\Q" ++ Name ++ "\\E"} | quit_hosting_node()], 1) after rtnode:stop(CState) end after rtnode:stop(SState) end; - Else -> + {skip, _} = Else -> Else end. diff --git a/lib/kernel/test/rtnode.erl b/lib/kernel/test/rtnode.erl index af818557de1e..a7331e8e55f5 100644 --- a/lib/kernel/test/rtnode.erl +++ b/lib/kernel/test/rtnode.erl @@ -19,7 +19,7 @@ %% -module(rtnode). --export([run/1, run/2, run/3, run/4, start/1, start/3, send_commands/3, stop/1, +-export([run/1, run/2, run/3, run/4, start/1, start/3, send_commands/4, stop/1, start_runerl_command/3, check_logs/3, check_logs/4, read_logs/1, dump_logs/1, get_default_shell/0, get_progs/0, create_tempdir/0, timeout/1]). @@ -50,8 +50,8 @@ run(Commands, Nodename, ErlPrefix) -> run(Commands, Nodename, ErlPrefix, Args) -> case start(Nodename, ErlPrefix, Args) of - {ok, _SPid, CPid, RTState} -> - Res = catch send_commands(CPid, Commands, 1), + {ok, _SPid, CPid, Node, RTState} -> + Res = catch send_commands(Node, CPid, Commands, 1), Logs = stop(RTState), case Res of ok -> @@ -84,11 +84,11 @@ start(Nodename, ErlPrefix, Args) -> lists:join($\s, ErlArgs), Tempdir,Nodename,Args), CPid = start_toerl_server(ToErl,Tempdir,undefined), - {ok, SPid, CPid, {CPid, SPid, ToErl, Tempdir}}; + {ok, SPid, CPid, undefined, {CPid, SPid, ToErl, Tempdir}}; Tempdir -> - SPid = start_peer_runerl_node(RunErl,ErlWArgs,Tempdir,Nodename,Args), + {SPid, Node} = start_peer_runerl_node(RunErl,ErlWArgs,Tempdir,Nodename,Args), CPid = start_toerl_server(ToErl,Tempdir,SPid), - {ok, SPid, CPid, {CPid, SPid, ToErl, Tempdir}} + {ok, SPid, CPid, Node, {CPid, SPid, ToErl, Tempdir}} end end. @@ -108,7 +108,7 @@ stop({CPid, SPid, ToErl, Tempdir}) -> stop_try_harder(ToErl, Tempdir, SPid) -> CPid = start_toerl_server(ToErl, Tempdir, SPid), - ok = send_commands(CPid, + ok = send_commands(undefined, CPid, [{putline,[7]}, {expect, " --> $"}, {putline, "s"}, @@ -125,36 +125,43 @@ timeout(short) -> timeout(normal) -> 10000 * test_server:timetrap_scale_factor(). -send_commands(CPid, [{sleep, X}|T], N) -> +send_commands(Node, CPid, [{sleep, X}|T], N) -> ?dbg({sleep, X}), receive after X -> - send_commands(CPid, T, N+1) + send_commands(Node, CPid, T, N+1) end; -send_commands(CPid, [{expect, Expect}|T], N) when is_list(Expect) -> - send_commands(CPid, [{expect, unicode, Expect}|T], N); -send_commands(CPid, [{expect, Encoding, Expect}|T], N) when is_list(Expect) -> +send_commands(Node, CPid, [{expect, Expect}|T], N) when is_list(Expect) -> + send_commands(Node, CPid, [{expect, unicode, Expect}|T], N); +send_commands(Node, CPid, [{expect, Encoding, Expect}|T], N) when is_list(Expect) -> ?dbg({expect, Expect}), case command(CPid, {expect, Encoding, [Expect], timeout(normal)}) of ok -> - send_commands(CPid, T, N + 1); + send_commands(Node, CPid, T, N + 1); {expect_timeout, Got} -> ct:pal("expect timed out waiting for ~p\ngot: ~p\n", [Expect,Got]), {error, timeout}; Other -> Other end; -send_commands(CPid, [{putline, Line}|T], N) -> - send_commands(CPid, [{putdata, Line ++ "\n"}|T], N); -send_commands(CPid, [{putdata, Data}|T], N) -> +send_commands(Node, CPid, [{putline, Line}|T], N) -> + send_commands(Node, CPid, [{putdata, Line ++ "\n"}|T], N); +send_commands(Node, CPid, [{putdata, Data}|T], N) -> ?dbg({putdata, Data}), case command(CPid, {send_data, Data}) of ok -> - send_commands(CPid, T, N+1); + send_commands(Node, CPid, T, N+1); Error -> Error end; -send_commands(_CPid, [], _) -> +send_commands(Node, CPid, [{eval, Fun}|T], N) -> + case erpc:call(Node, Fun) of + ok -> + send_commands(Node, CPid, T, N+1); + Error -> + Error + end; +send_commands(_Node, _CPid, [], _) -> ok. command(Pid, Req) -> @@ -300,7 +307,7 @@ start_peer_runerl_node(RunErl,Erl,Tempdir,Nodename,Args) -> erlang:raise(E,R,ST) end end), - Peer. + {Peer, Node}. start_toerl_server(ToErl,Tempdir,SPid) -> Pid = spawn(?MODULE,toerl_server,[self(),ToErl,Tempdir,SPid]), @@ -336,7 +343,9 @@ toerl_server(Parent, ToErl, TempDir, SPid) -> exit(Other) end, - State = #{port => Port, acc => [], spid => SPid}, + {ok, InitialData} = file:read_file(filename:join(TempDir,"erlang.log.1")), + + State = #{port => Port, acc => unicode:characters_to_list(InitialData), spid => SPid}, case toerl_loop(State) of normal -> ok; diff --git a/lib/kernel/test/standard_error_SUITE.erl b/lib/kernel/test/standard_error_SUITE.erl index 1d9026dc58ec..34bb880db8ed 100644 --- a/lib/kernel/test/standard_error_SUITE.erl +++ b/lib/kernel/test/standard_error_SUITE.erl @@ -34,8 +34,10 @@ badarg(Config) when is_list(Config) -> true = erlang:is_process_alive(whereis(standard_error)), ok. +%% Check that standard_out and standard_error have the same encoding getopts(Config) when is_list(Config) -> - [{encoding,latin1}] = io:getopts(standard_error), + Encoding = proplists:get_value(encoding, io:getopts(user)), + Encoding = proplists:get_value(encoding, io:getopts(standard_error)), ok. %% Test that writing a lot of output to standard_error does not cause the 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/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 2cb8d8048882..aded3fc06ed1 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -59,9 +59,9 @@ {mod, {ssh_app, []}}, {runtime_dependencies, [ "crypto-5.0", - "erts-11.0", - "kernel-6.0", + "erts-@OTP-17932@", + "kernel-@OTP-17932@", "public_key-1.6.1", - "stdlib-3.15", + "stdlib-@OTP-17932@", "runtime_tools-1.15.1" ]}]}. diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 13a44beea3c8..43237b6141cb 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -281,6 +281,10 @@ handle_msg({Group, get_unicode_state}, State) -> Group ! {self(), get_unicode_state, false}, {ok, State}; +handle_msg({Group, get_terminal_state}, State) -> + Group ! {self(), get_terminal_state, true}, + {ok, State}; + handle_msg({Group, tty_geometry}, #state{group = Group, pty = Pty } = State) -> @@ -447,7 +451,7 @@ io_request(tty_geometry, Buf, Tty, Group) -> io_request({put_chars_sync, Class, Cs, Reply}, Buf, Tty, Group) -> %% We handle these asynchronous for now, if we need output guarantees %% we have to handle these synchronously - Group ! {reply, Reply}, + Group ! {reply, Reply, ok}, io_request({put_chars, Class, Cs}, Buf, Tty, Group); io_request(_R, Buf, _Tty, _Group) -> diff --git a/lib/stdlib/doc/src/io.xml b/lib/stdlib/doc/src/io.xml index a400d2af2334..fd44dffb60ee 100644 --- a/lib/stdlib/doc/src/io.xml +++ b/lib/stdlib/doc/src/io.xml @@ -82,6 +82,9 @@ + + + @@ -779,9 +782,14 @@ enter>: alan : joe + {encoding,unicode}, + {terminal,true}]

This example is, as can be seen, run in an environment where the terminal supports Unicode input and output.

+

The terminal option is read only and indicates whether + the output stream is a terminal or not. + See setopts/1 for a description + of the other options.

diff --git a/lib/stdlib/doc/src/shell.xml b/lib/stdlib/doc/src/shell.xml index 928d2686b641..3d6f5065acf8 100644 --- a/lib/stdlib/doc/src/shell.xml +++ b/lib/stdlib/doc/src/shell.xml @@ -956,6 +956,60 @@ q - quit erlang + + + Start the interactive shell + +

Starts the interactive shell if it has not already been started. + It can be used to programatically start the shell from an escript + or when erl is started with the -noinput or -noshell flags.

+
+
+ + + + Start the interactive shell + +

Starts the interactive shell if it has not already been started. + It can be used to programatically start the shell from an + escript or when + erl is started with the + -noinput or + -noshell flags. + The following options are allowed:

+ + noshell + +

Starts the interactive shell as if + -noshell was given to erl. + This is only useful when erl is started with + -noinput and the + system want to read input data. +

+
+ mfa() + +

Starts the interactive shell using + mfa() + as the default shell.

+
+ {node(), + mfa()} + +

Starts the interactive shell using + mfa() on + node() as the default shell.

+
+ {remote, string()} + +

Starts the interactive shell using as if + -remsh + was given to erl.

+
+
+
+
+ Exit a normal shell and starts a restricted shell. diff --git a/lib/stdlib/doc/src/stdlib_app.xml b/lib/stdlib/doc/src/stdlib_app.xml index 243853140bd0..b4d0c81c96b4 100644 --- a/lib/stdlib/doc/src/stdlib_app.xml +++ b/lib/stdlib/doc/src/stdlib_app.xml @@ -76,6 +76,29 @@

Can be used to determine how many results are saved by the Erlang shell.

+ shell_session_slogan = string() | fun() -> string()) + +

The slogan printed when starting an Erlang shell. Example:

+ +$ erl -stdlib shell_session_slogan '"Test slogan"' +Erlang/OTP 26 [DEVELOPMENT] [erts-13.0.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns] + +Test slogan +1> + +
+ shell_slogan = string() | fun(() -> string()) + +

The slogan printed when starting the Erlang shell subsystem. Example:

+ +$ erl -stdlib shell_slogan '"Test slogan"' +Test slogan +Eshell V13.0.2 (abort with ^G) +1> + +

The default is the return value of + erlang:system_info(system_version).

+
shell_strings = boolean()

Can be used to determine how the Erlang shell outputs lists of diff --git a/lib/stdlib/src/edlin.erl b/lib/stdlib/src/edlin.erl index 6078c5e67bd1..97088dd36147 100644 --- a/lib/stdlib/src/edlin.erl +++ b/lib/stdlib/src/edlin.erl @@ -191,9 +191,9 @@ key_map($\^E, none) -> end_of_line; key_map($\^F, none) -> forward_char; key_map($\^H, none) -> backward_delete_char; key_map($\t, none) -> tab_expand; +key_map($\^K, none) -> kill_line; key_map($\^L, none) -> redraw_line; key_map($\n, none) -> new_line; -key_map($\^K, none) -> kill_line; key_map($\r, none) -> new_line; key_map($\^T, none) -> transpose_char; key_map($\^U, none) -> ctlu; @@ -320,9 +320,9 @@ do_op({search, backward_delete_char}, [_|Bef], Aft, Rs) -> {{Bef,NAft}, [{insert_chars, unicode, NAft}, {delete_chars,-Offset}|Rs], search}; -do_op({search, backward_delete_char}, [], _Aft, Rs) -> - Aft="': ", - {{[],Aft}, Rs, search}; +do_op({search, backward_delete_char}, [], Aft, Rs) -> + NAft="': ", + {{[],NAft}, [{insert_chars, unicode, NAft}, {delete_chars,-cp_len(Aft)}|Rs], search}; do_op({search, skip_up}, Bef, Aft, Rs) -> Offset= cp_len(Aft), NAft = "': ", @@ -621,148 +621,3 @@ cp_len(Str) -> cp_len([GC|R], Len) -> cp_len(R, Len + gc_len(GC)); cp_len([], Len) -> Len. - -%% %% expand(CurrentBefore) -> -%% %% {yes,Expansion} | no -%% %% Try to expand the word before as either a module name or a function -%% %% name. We can handle white space around the seperating ':' but the -%% %% function name must be on the same line. CurrentBefore is reversed -%% %% and over_word/3 reverses the characters it finds. In certain cases -%% %% possible expansions are printed. - -%% expand(Bef0) -> -%% {Bef1,Word,_} = over_word(Bef0, [], 0), -%% case over_white(Bef1, [], 0) of -%% {[$:|Bef2],_White,_Nwh} -> -%% {Bef3,_White1,_Nwh1} = over_white(Bef2, [], 0), -%% {_,Mod,_Nm} = over_word(Bef3, [], 0), -%% expand_function_name(Mod, Word); -%% {_,_,_} -> -%% expand_module_name(Word) -%% end. - -%% expand_module_name(Prefix) -> -%% match(Prefix, code:all_loaded(), ":"). - -%% expand_function_name(ModStr, FuncPrefix) -> -%% Mod = list_to_atom(ModStr), -%% case erlang:module_loaded(Mod) of -%% true -> -%% L = apply(Mod, module_info, []), -%% case lists:keyfind(exports, 1, L) of -%% {_, Exports} -> -%% match(FuncPrefix, Exports, "("); -%% _ -> -%% no -%% end; -%% false -> -%% no -%% end. - -%% match(Prefix, Alts, Extra) -> -%% Matches = match1(Prefix, Alts), -%% case longest_common_head([N || {N,_} <- Matches]) of -%% {partial, []} -> -%% print_matches(Matches), -%% no; -%% {partial, Str} -> -%% case lists:nthtail(length(Prefix), Str) of -%% [] -> -%% print_matches(Matches), -%% {yes, []}; -%% Remain -> -%% {yes, Remain} -%% end; -%% {complete, Str} -> -%% {yes, lists:nthtail(length(Prefix), Str) ++ Extra}; -%% no -> -%% no -%% end. - -%% %% Print the list of names L in multiple columns. -%% print_matches(L) -> -%% io:nl(), -%% col_print(lists:sort(L)), -%% ok. - -%% col_print([]) -> ok; -%% col_print(L) -> col_print(L, field_width(L), 0). - -%% col_print(X, Width, Len) when Width + Len > 79 -> -%% io:nl(), -%% col_print(X, Width, 0); -%% col_print([{H0,A}|T], Width, Len) -> -%% H = if -%% %% If the second element is an integer, we assume it's an -%% %% arity, and meant to be printed. -%% integer(A) -> -%% H0 ++ "/" ++ integer_to_list(A); -%% true -> -%% H0 -%% end, -%% io:format("~-*s",[Width,H]), -%% col_print(T, Width, Len+Width); -%% col_print([], _, _) -> -%% io:nl(). - -%% field_width([{H,_}|T]) -> field_width(T, length(H)). - -%% field_width([{H,_}|T], W) -> -%% case length(H) of -%% L when L > W -> field_width(T, L); -%% _ -> field_width(T, W) -%% end; -%% field_width([], W) when W < 40 -> -%% W + 4; -%% field_width([], _) -> -%% 40. - -%% match1(Prefix, Alts) -> -%% match1(Prefix, Alts, []). - -%% match1(Prefix, [{H,A}|T], L) -> -%% case prefix(Prefix, Str = atom_to_list(H)) of -%% true -> -%% match1(Prefix, T, [{Str,A}|L]); -%% false -> -%% match1(Prefix, T, L) -%% end; -%% match1(_, [], L) -> -%% L. - -%% longest_common_head([]) -> -%% no; -%% longest_common_head(LL) -> -%% longest_common_head(LL, []). - -%% longest_common_head([[]|_], L) -> -%% {partial, reverse(L)}; -%% longest_common_head(LL, L) -> -%% case same_head(LL) of -%% true -> -%% [[H|_]|_] = LL, -%% LL1 = all_tails(LL), -%% case all_nil(LL1) of -%% false -> -%% longest_common_head(LL1, [H|L]); -%% true -> -%% {complete, reverse([H|L])} -%% end; -%% false -> -%% {partial, reverse(L)} -%% end. - -%% same_head([[H|_]|T1]) -> same_head(H, T1). - -%% same_head(H, [[H|_]|T]) -> same_head(H, T); -%% same_head(_, []) -> true; -%% same_head(_, _) -> false. - -%% all_tails(LL) -> all_tails(LL, []). - -%% all_tails([[_|T]|T1], L) -> all_tails(T1, [T|L]); -%% all_tails([], L) -> L. - -%% all_nil([]) -> true; -%% all_nil([[] | Rest]) -> all_nil(Rest); -%% all_nil(_) -> false. diff --git a/lib/stdlib/src/io.erl b/lib/stdlib/src/io.erl index 18f6ef3ddee9..46d5cd4ae82f 100644 --- a/lib/stdlib/src/io.erl +++ b/lib/stdlib/src/io.erl @@ -213,15 +213,18 @@ get_password(Io) -> -type opt_pair() :: {'binary', boolean()} | {'echo', boolean()} | {'expand_fun', expand_fun()} - | {'encoding', encoding()}. + | {'encoding', encoding()} + | {atom(), term()}. +-type get_opt_pair() :: opt_pair() + | {'terminal', boolean()}. --spec getopts() -> [opt_pair()] | {'error', Reason} when +-spec getopts() -> [get_opt_pair()] | {'error', Reason} when Reason :: term(). getopts() -> getopts(default_input()). --spec getopts(IoDevice) -> [opt_pair()] | {'error', Reason} when +-spec getopts(IoDevice) -> [get_opt_pair()] | {'error', Reason} when IoDevice :: device(), Reason :: term(). diff --git a/lib/stdlib/src/shell.erl b/lib/stdlib/src/shell.erl index 7de78758b03b..8d48ff7b2a03 100644 --- a/lib/stdlib/src/shell.erl +++ b/lib/stdlib/src/shell.erl @@ -20,10 +20,10 @@ -module(shell). -export([start/0, start/1, start/2, server/1, server/2, history/1, results/1]). --export([whereis_evaluator/0, whereis_evaluator/1]). -export([start_restricted/1, stop_restricted/0]). -export([local_allowed/3, non_local_allowed/3]). -export([catch_exception/1, prompt_func/1, strings/1]). +-export([start_interactive/0, start_interactive/1]). -define(LINEMAX, 30). -define(CHAR_MAX, 60). @@ -48,6 +48,16 @@ non_local_allowed({init,stop},[],State) -> non_local_allowed(_,_,State) -> {false,State}. +-spec start_interactive() -> ok | {error, already_started | enottty}. +start_interactive() -> + user_drv:start_shell(). +-spec start_interactive(noshell | mfa() | {node(), mfa()} | {remote, string()}) -> + ok | {error, already_started | enottty}. +start_interactive({Node, {M, F, A}}) -> + user_drv:start_shell(#{ initial_shell => {Node, M, F ,A} }); +start_interactive(InitialShell) -> + user_drv:start_shell(#{ initial_shell => InitialShell }). + -spec start() -> pid(). start() -> @@ -60,60 +70,14 @@ start(NoCtrlG) -> start(NoCtrlG, StartSync) -> _ = code:ensure_loaded(user_default), - spawn(fun() -> server(NoCtrlG, StartSync) end). - -%% Find the pid of the current evaluator process. --spec whereis_evaluator() -> 'undefined' | pid(). - -whereis_evaluator() -> - %% locate top group leader, always registered as user - %% can be implemented by group (normally) or user - %% (if oldshell or noshell) - case whereis(user) of - undefined -> - undefined; - User -> - %% get user_drv pid from group, or shell pid from user - case group:interfaces(User) of - [] -> % old- or noshell - case user:interfaces(User) of - [] -> - undefined; - [{shell,Shell}] -> - whereis_evaluator(Shell) - end; - [{user_drv,UserDrv}] -> - %% get current group pid from user_drv - case user_drv:interfaces(UserDrv) of - [] -> - undefined; - [{current_group,Group}] -> - %% get shell pid from group - GrIfs = group:interfaces(Group), - case lists:keyfind(shell, 1, GrIfs) of - {shell, Shell} -> - whereis_evaluator(Shell); - false -> - undefined - end - end - end - end. - --spec whereis_evaluator(pid()) -> 'undefined' | pid(). - -whereis_evaluator(Shell) -> - case process_info(Shell, dictionary) of - {dictionary,Dict} -> - case lists:keyfind(evaluator, 1, Dict) of - {_, Eval} when is_pid(Eval) -> - Eval; - _ -> - undefined - end; - _ -> - undefined - end. + Ancestors = [self() | case get('$ancestors') of + undefined -> []; + Anc -> Anc + end], + spawn(fun() -> + put('$ancestors', Ancestors), + server(NoCtrlG, StartSync) + end). %% Call this function to start a user restricted shell %% from a normal shell session. @@ -201,12 +165,24 @@ server(StartSync) -> undefined end, - case get(no_control_g) of - true -> - io:fwrite(<<"Eshell V~s\n">>, [erlang:system_info(version)]); - _undefined_or_false -> - io:fwrite(<<"Eshell V~s (abort with ^G)\n">>, - [erlang:system_info(version)]) + JCL = + case get(no_control_g) of + true -> " (type help(). for help)"; + _ -> " (press Ctrl+G to abort, type help(). for help)" + end, + DefaultSessionSlogan = + io_lib:format(<<"Eshell V~s">>, [erlang:system_info(version)]), + SessionSlogan = + case application:get_env(stdlib, shell_session_slogan, DefaultSessionSlogan) of + SloganFun when is_function(SloganFun, 0) -> + SloganFun(); + Slogan -> + Slogan + end, + try + io:fwrite("~ts~ts\n",[unicode:characters_to_list(SessionSlogan),JCL]) + catch _:_ -> + io:fwrite("Warning! The slogan \"~p\" could not be printed.\n",[SessionSlogan]) end, erase(no_control_g), diff --git a/lib/stdlib/src/shell_docs.erl b/lib/stdlib/src/shell_docs.erl index e42b5bb5b864..201e842cb1e7 100644 --- a/lib/stdlib/src/shell_docs.erl +++ b/lib/stdlib/src/shell_docs.erl @@ -1005,18 +1005,18 @@ nl(Chars) -> init_ansi(#config{ ansi = undefined, io_opts = Opts }) -> %% We use this as our heuristic to see if we should print ansi or not case {application:get_env(kernel, shell_docs_ansi), + proplists:get_value(tty, Opts, false), proplists:is_defined(echo, Opts) andalso - proplists:is_defined(expand_fun, Opts), - os:type()} of + proplists:is_defined(expand_fun, Opts)} of {{ok,false}, _, _} -> put(ansi, noansi); {{ok,true}, _, _} -> put(ansi, []); - {_, _, {win32,_}} -> - put(ansi, noansi); - {_, true,_} -> + {_, true, _} -> + put(ansi, []); + {_, _, true} -> put(ansi, []); - {_, false,_} -> + {_, _, false} -> put(ansi, noansi) end; init_ansi(#config{ ansi = true }) -> 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"]} ]}. diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile index 0ee9ee6f6d79..0503db2c62ae 100644 --- a/lib/stdlib/test/Makefile +++ b/lib/stdlib/test/Makefile @@ -164,6 +164,7 @@ release_tests_spec: make_emakefile $(ERL_FILES) $(COVERFILE) $(EXTRA_FILES) "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" @tar cf - *_SUITE_data property_test | (cd "$(RELSYSDIR)"; tar xf -) + $(INSTALL_DIR) "$(RELSYSDIR)/stdlib_SUITE_data" $(INSTALL_DATA) $(ERL_TOP)/make/otp_version_tickets "$(RELSYSDIR)/stdlib_SUITE_data" release_docs_spec: diff --git a/lib/stdlib/test/escript_SUITE_data/arg_overflow b/lib/stdlib/test/escript_SUITE_data/arg_overflow index dd5accc05184..e3138cabbdaf 100755 --- a/lib/stdlib/test/escript_SUITE_data/arg_overflow +++ b/lib/stdlib/test/escript_SUITE_data/arg_overflow @@ -1,5 +1,5 @@ #! /usr/bin/env escript %% -*- erlang -*- -%%!x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x +%%!-x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x -x main(_) -> halt(0). diff --git a/lib/stdlib/test/escript_SUITE_data/linebuf_overflow b/lib/stdlib/test/escript_SUITE_data/linebuf_overflow index 33133c1ce903..018be1f26d0a 100755 --- a/lib/stdlib/test/escript_SUITE_data/linebuf_overflow +++ b/lib/stdlib/test/escript_SUITE_data/linebuf_overflow @@ -1,5 +1,5 @@ #! /usr/bin/env escript %% -*- erlang -*- -%%!xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +%%!-v xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx main(_) -> halt(0). diff --git a/lib/stdlib/test/io_proto_SUITE.erl b/lib/stdlib/test/io_proto_SUITE.erl index 482e233493b5..bc96992ce2a3 100644 --- a/lib/stdlib/test/io_proto_SUITE.erl +++ b/lib/stdlib/test/io_proto_SUITE.erl @@ -24,7 +24,8 @@ -export([setopts_getopts/1,unicode_options/1,unicode_options_gen/1, binary_options/1, read_modes_gl/1, - read_modes_ogl/1, broken_unicode/1,eof_on_pipe/1,unicode_prompt/1]). + read_modes_ogl/1, broken_unicode/1,eof_on_pipe/1, + unicode_prompt/1, shell_slogan/1]). -export([io_server_proxy/1,start_io_server_proxy/0, proxy_getall/1, @@ -32,7 +33,7 @@ %% For spawn -export([answering_machine1/3, answering_machine2/3]). --export([uprompt/1]). +-export([uprompt/1, slogan/0, session_slogan/0]). %%-define(debug, true). @@ -49,7 +50,8 @@ suite() -> all() -> [setopts_getopts, unicode_options, unicode_options_gen, binary_options, read_modes_gl, read_modes_ogl, - broken_unicode, eof_on_pipe, unicode_prompt]. + broken_unicode, eof_on_pipe, unicode_prompt, + shell_slogan]. groups() -> []. @@ -115,14 +117,42 @@ unicode_prompt(Config) when is_list(Config) -> {putline, "hej"}, {expect, "\\Q\"hej\\n\"\\E"}, {putline, "io:setopts([{binary,true}])."}, - {expect, "[\n ]ok"}, + {expect, "[\n ]\\?*ok"}, {putline, "io:get_line('')."}, {putline, "hej"}, - {expect,"[\n ]hej"}, + {expect,"[\n ]\\?*hej"}, {expect, "\\Q<<\"hej\\n\">>\\E"} ],[],"",["-oldshell","-pa",PA]), ok. +%% Test that an Unicode prompt does not crash the shell. +shell_slogan(Config) when is_list(Config) -> + PA = filename:dirname(code:which(?MODULE)), + case proplists:get_value(default_shell,Config) of + new -> + rtnode:run( + [{expect, "\\Q"++string:trim(erlang:system_info(system_version))++"\\E"}, + {expect, "\\Q"++io_lib:format("Eshell V~s (press Ctrl+G to abort, type help(). for help)",[erlang:system_info(version)])++"\\E"} + ],[],"",[]), + rtnode:run( + [{expect, "\nTest slogan"}, + {expect, "\nTest session slogan \\("} + ],[],"",["-stdlib","shell_slogan","\"Test slogan\"", + "-stdlib","shell_session_slogan","\"Test session slogan\""]), + rtnode:run( + [{expect, "\nTest slogan"}, + {expect, "\\Q\nTest session slogan (\\E"} + ],[],"",["-stdlib","shell_slogan","fun io_proto_SUITE:slogan/0", + "-stdlib","shell_session_slogan","fun io_proto_SUITE:session_slogan/0", + "-pa",PA]); + _ -> + ok + end. + +slogan() -> + "Test slogan". +session_slogan() -> + "Test session slogan". %% Check io:setopts and io:getopts functions. setopts_getopts(Config) when is_list(Config) -> diff --git a/lib/stdlib/test/shell_SUITE.erl b/lib/stdlib/test/shell_SUITE.erl index b38dee47e7e1..6aedfb94a272 100644 --- a/lib/stdlib/test/shell_SUITE.erl +++ b/lib/stdlib/test/shell_SUITE.erl @@ -36,6 +36,8 @@ -export([ start_restricted_from_shell/1, start_restricted_on_command_line/1,restricted_local/1]). +-export([ start_interactive/1 ]). + %% Internal export. -export([otp_5435_2/0, prompt1/1, prompt2/1, prompt3/1, prompt4/1, prompt5/1]). @@ -3014,6 +3016,57 @@ otp_14296(Config) when is_list(Config) -> {error, {_,_,"bad term"}} = TF("1, 2"), ok. +start_interactive(_Config) -> + rtnode:run( + [{expect, "test"}, + {putline, "test."}, + {eval, fun() -> shell:start_interactive() end}, + {expect, "1>"}, + {expect, "2>"} + ],[],"",["-noinput","-eval","io:format(\"test~n\")"]), + + rtnode:run( + [{expect, "test"}, + {putline, "test."}, + {eval, fun() -> shell:start_interactive({shell,start,[]}) end}, + {expect, "1>"}, + {expect, "2>"} + ],[],"",["-noinput","-eval","io:format(\"test~n\")"]), + + rtnode:run( + [{expect, "test"}, + {putline, "test."}, + {eval, fun() -> shell:start_interactive(noshell) end}, + {eval, fun() -> io:format(user,"~ts",[io:get_line(user, "")]) end}, + {expect, "test\\."}, + {putline, "test."}, + {eval, fun() -> shell:start_interactive() end}, + {expect, "1>"}, + {expect, "2>"} + ],[],"",["-noinput","-eval","io:format(\"test~n\")"]), + + {ok, RPeer, RNode} = ?CT_PEER(), + unlink(RPeer), + SRNode = atom_to_list(RNode), + rtnode:run( + [{expect, "test"}, + {putline, "test."}, + {eval, fun() -> shell:start_interactive({remote, SRNode}) end}, + {expect, "\\Q("++SRNode++")\\E2>"} + ],[],"",["-noinput","-eval","io:format(\"test~n\")"]), + + {ok, Peer, Node} = ?CT_PEER(), + unlink(Peer), + SNode = atom_to_list(Node), + rtnode:run( + [{expect, "test"}, + {putline, "test."}, + {eval, fun() -> shell:start_interactive({Node, {shell,start,[]}}) end}, + {expect, "\\Q("++SNode++")\\E2>"} + ],[],"",["-noinput","-eval","io:format(\"test~n\")"]), + + ok. + term_to_string(T) -> lists:flatten(io_lib:format("~w", [T])). diff --git a/lib/stdlib/test/shell_docs_SUITE.erl b/lib/stdlib/test/shell_docs_SUITE.erl index 028e2c0aba64..b7d85204d84b 100644 --- a/lib/stdlib/test/shell_docs_SUITE.erl +++ b/lib/stdlib/test/shell_docs_SUITE.erl @@ -255,14 +255,15 @@ render_non_native(_Config) -> beam_language = not_erlang, format = <<"text/asciidoc">>, module_doc = #{<<"en">> => <<"This is\n\npure text">>}, - docs= [] + docs = [] }, <<"\n\tnot_an_erlang_module\n\n" " This is\n" " \n" " pure text\n">> = - unicode:characters_to_binary(shell_docs:render(not_an_erlang_module, Docs, #{})), + unicode:characters_to_binary( + shell_docs:render(not_an_erlang_module, Docs, #{ ansi => false })), ok. diff --git a/make/configure.ac b/make/configure.ac index e0d4103b6edb..1276caecbcac 100644 --- a/make/configure.ac +++ b/make/configure.ac @@ -212,10 +212,6 @@ AS_HELP_STRING([--disable-parallel-configure], [disable parallel execution of co AC_ARG_ENABLE(dirty-schedulers, AS_HELP_STRING([--enable-dirty-schedulers], [enable dirty scheduler support])) -AC_ARG_ENABLE(plain-emulator, -AS_HELP_STRING([--enable-plain-emulator], [enable threaded non-smp emulator]) -AS_HELP_STRING([--disable-plain-emulator], [disable threaded non-smp emulator])) - AC_ARG_WITH(termcap, AS_HELP_STRING([--with-termcap], [use termcap (default)]) AS_HELP_STRING([--without-termcap], diff --git a/make/test_target_script.sh b/make/test_target_script.sh index a837533c7a86..7fc9d5b89d3d 100755 --- a/make/test_target_script.sh +++ b/make/test_target_script.sh @@ -316,7 +316,7 @@ then -pz "$ERL_TOP/lib/common_test/test_server" \ -pz "." \ -ct_test_vars "{net_dir,\"\"}" \ - -noshell \ + -noinput \ -sname test_server \ -rsh ssh \ ${ERL_ARGS} @@ -337,7 +337,7 @@ else -pz "$WIN_ERL_TOP/lib/common_test/test_server"\ -pz "."\ -ct_test_vars "{net_dir,\"\"}"\ - -noshell\ + -noinput\ -sname test_server\ -rsh ssh\ ${ERL_ARGS} diff --git a/otp_build b/otp_build index 7b35b39fc2c5..284da6389320 100755 --- a/otp_build +++ b/otp_build @@ -1001,8 +1001,7 @@ do_tests () do_debuginfo_win32 () { setup_make - (cd erts/emulator && $MAKE MAKE="$MAKE" TARGET=$TARGET FLAVOR=smp debug &&\ - $MAKE MAKE="$MAKE" TARGET=$TARGET FLAVOR=plain debug) || exit 1 + (cd erts/emulator && $MAKE MAKE="$MAKE" TARGET=$TARGET debug) || exit 1 if [ -z "$1" ]; then RELDIR="$ERL_TOP/release/$TARGET" else @@ -1010,7 +1009,7 @@ do_debuginfo_win32 () fi BINDIR="$ERL_TOP/bin/$TARGET" EVSN=`grep '^VSN' erts/vsn.mk | sed 's,^VSN.*=[^0-9]*\([0-9].*\)$,@\1,g;s,^[^@].*,,g;s,^@,,g'` - for f in beam.debug.smp.dll beam.smp.pdb beam.debug.smp.dll.pdb erl.pdb werl.pdb erlexec.pdb; do + for f in beam.debug.smp.dll beam.smp.pdb beam.debug.smp.dll.pdb erl.pdb erlexec.pdb; do if [ -f $BINDIR/$f ]; then rm -f $RELDIR/erts-$EVSN/bin/$f cp $BINDIR/$f $RELDIR/erts-$EVSN/bin/$f @@ -1218,7 +1217,7 @@ case "$1" in do_configure "$@";; opt) do_boot;; - plain|smp) + smp) if [ $minus_x_flag = false ]; then TYPE=opt fi;