summaryrefslogtreecommitdiff
path: root/erts/emulator/nifs/common/prim_tty_nif.c
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/nifs/common/prim_tty_nif.c')
-rw-r--r--erts/emulator/nifs/common/prim_tty_nif.c1036
1 files changed, 1036 insertions, 0 deletions
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 0000000000..7328ec894e
--- /dev/null
+++ b/erts/emulator/nifs/common/prim_tty_nif.c
@@ -0,0 +1,1036 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson 2015-2023. 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 <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <wchar.h>
+#include <stdio.h>
+#include <signal.h>
+#include <locale.h>
+#ifdef HAVE_TERMCAP
+ #include <termios.h>
+ #include <curses.h>
+ #include <term.h>
+#endif
+#ifndef __WIN32__
+ #include <unistd.h>
+ #include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_UIO_H
+ #include <sys/uio.h>
+#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 <langinfo.h>
+#endif
+
+#ifdef VALGRIND
+# include <valgrind/memcheck.h>
+#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;
+}