Repository navigation
Add process.stdin.setRawMode
#2046
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d53bfc5
00a1d32
483291d
40f5cfc
38ae1f8
b957928
933626a
b88debc
6eb4e8f
fe57824
23c9aac
2e2a7a1
b9b53bb
f210357
46a3825
48f7b0f
4927e07
d2aa68b
c692ba2
102188f
2859820
5101e58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| #include "JSTTYHelper.h" | ||
|
|
||
| #include "../bindings/ZigGlobalObject.h" | ||
| #include "TTYHelper.h" | ||
|
|
||
| namespace WebCore { | ||
| using namespace Zig; | ||
|
|
||
| JSC_DEFINE_HOST_FUNCTION(jsFunctionInternalTty_isRaw, | ||
| (JSGlobalObject * globalObject, | ||
| CallFrame* callFrame)) | ||
| { | ||
| VM& vm = globalObject->vm(); | ||
| if (callFrame->argumentCount() < 1) { | ||
| return JSValue::encode(jsBoolean(false)); | ||
| } | ||
|
|
||
| auto scope = DECLARE_CATCH_SCOPE(vm); | ||
| int fd = callFrame->argument(0).toInt32(globalObject); | ||
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); | ||
|
|
||
| auto isRaw = tty__is_raw(fd); | ||
| if (isRaw == -3) { | ||
| return JSValue::encode(jsUndefined()); | ||
| } | ||
| return JSValue::encode(jsBoolean(isRaw)); | ||
| } | ||
|
|
||
| JSC_DEFINE_HOST_FUNCTION(jsFunctionInternalTty_setRawMode, | ||
| (JSGlobalObject * globalObject, | ||
| CallFrame* callFrame)) | ||
| { | ||
| VM& vm = globalObject->vm(); | ||
| if (callFrame->argumentCount() < 2) { | ||
| return JSValue::encode(jsBoolean(false)); | ||
| } | ||
|
|
||
| auto scope = DECLARE_CATCH_SCOPE(vm); | ||
| int fd = callFrame->argument(0).toInt32(globalObject); | ||
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); | ||
| bool shouldBeRaw = callFrame->argument(1).toBoolean(globalObject); | ||
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); | ||
|
|
||
| auto throwScope = DECLARE_THROW_SCOPE(vm); | ||
| auto result = tty__set_mode(fd, shouldBeRaw ? TTY_MODE_RAW : TTY_MODE_NORMAL); | ||
| if (result < 0) { | ||
| JSC::throwException( | ||
| globalObject, throwScope, | ||
| JSC::createError(globalObject, "Failed to set tty mode. Error code: "_s + WTFMove(std::to_string(result).c_str()))); | ||
| return JSValue::encode(jsUndefined()); | ||
| } | ||
| return JSValue::encode(jsBoolean(true)); | ||
| } | ||
|
|
||
| JSC_DEFINE_HOST_FUNCTION(jsFunctionInternalTty_setAsyncIoMode, | ||
| (JSGlobalObject * globalObject, | ||
| CallFrame* callFrame)) | ||
| { | ||
| VM& vm = globalObject->vm(); | ||
| if (callFrame->argumentCount() < 2) { | ||
| return JSValue::encode(jsBoolean(false)); | ||
| } | ||
|
|
||
| auto scope = DECLARE_CATCH_SCOPE(vm); | ||
| int fd = callFrame->argument(0).toInt32(globalObject); | ||
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); | ||
| bool shouldBeIoMode = callFrame->argument(1).toBoolean(globalObject); | ||
| RETURN_IF_EXCEPTION(scope, encodedJSValue()); | ||
|
|
||
| auto throwScope = DECLARE_THROW_SCOPE(vm); | ||
| auto result = tty__set_async_io_mode(fd, shouldBeIoMode); | ||
| if (result < 0) { | ||
| JSC::throwException( | ||
| globalObject, throwScope, | ||
| JSC::createError(globalObject, "Failed to set tty mode. Error code: "_s + WTFMove(std::to_string(result).c_str()))); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
| return JSValue::encode(jsUndefined()); | ||
| } | ||
| return JSValue::encode(jsBoolean(true)); | ||
| } | ||
|
|
||
| } // namespace WebCore | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #pragma once | ||
|
|
||
| #include "root.h" | ||
|
|
||
| namespace WebCore { | ||
|
|
||
| JSC_DECLARE_HOST_FUNCTION(jsFunctionInternalTty_isRaw); | ||
| JSC_DECLARE_HOST_FUNCTION(jsFunctionInternalTty_setRawMode); | ||
| JSC_DECLARE_HOST_FUNCTION(jsFunctionInternalTty_setAsyncIoMode); | ||
|
|
||
| } // namespace WebCore |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,222 @@ | ||
| #include <termios.h> | ||
| #include <unistd.h> | ||
| #include <cstdio> | ||
|
|
||
| #include "TTYHelper.h" | ||
|
|
||
| namespace Zig { | ||
|
|
||
| static bool tty__orig_set = false; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will break when we have multiple instances of Bun's runtime Sort of need this to be part of JSTTYHelper as an object with the relevant fields |
||
| static bool tty__raw_mode_set = false; | ||
| static tty_mode_t tty__cached_mode = TTY_MODE_UNSET; | ||
| static struct termios tty__orig_termios; | ||
| static struct termios tty__raw_mode_termios; | ||
| static struct termios tty__termios_tmp; | ||
| static int32_t tty__is_tty_val = -1; | ||
|
|
||
| int32_t tty__is_tty(int32_t fd) | ||
| { | ||
| // Only cache is_tty for stdin | ||
| // NOTE: This caching behavior assumes that stdin doesn't change for the duration of the program | ||
| // This may be a dangerous assumption but it's probably fine in 99% of cases as far as I can tell | ||
| if (UNLIKELY(fd != STDIN_FILENO)) { | ||
| return isatty(fd); | ||
| } | ||
|
|
||
| if (tty__is_tty_val == -1) | ||
| tty__is_tty_val = isatty(fd); | ||
|
|
||
| return tty__is_tty_val; | ||
| } | ||
|
|
||
| int32_t tty__get_termios(int32_t fd, termios* termios_p) | ||
| { | ||
| if (UNLIKELY(!tty__is_tty(fd))) | ||
| return -3; | ||
|
|
||
| if (termios_p == NULL && fd == STDIN_FILENO) | ||
| termios_p = &tty__orig_termios; | ||
|
|
||
| if (UNLIKELY(tcgetattr(fd, termios_p))) | ||
| return -1; | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| int32_t tty__is_raw(int32_t fd) | ||
| { | ||
| if (UNLIKELY(!tty__is_tty(fd))) | ||
| return -3; | ||
|
|
||
| if (UNLIKELY(tty__get_termios(fd, &tty__termios_tmp))) | ||
| return -4; | ||
|
|
||
| return (tty__termios_tmp.c_lflag & (ECHO | ICANON)) == 0; | ||
| } | ||
|
|
||
| static inline tty_mode_t tty__internal_get_mode(termios* termios_p) | ||
| { | ||
| // If ICANON and ECHO are unset, we're in (one of the) raw mode(s) | ||
| if ((termios_p->c_lflag & (ECHO | ICANON)) == 0) { | ||
| // If OPOST is unset, we're in raw async I/O mode | ||
| if ((termios_p->c_oflag & OPOST) == 0) | ||
| return TTY_MODE_RAW_ASYNC_IO; | ||
| else // Otherwise, we're in normal raw mode | ||
| return TTY_MODE_RAW; | ||
| } | ||
|
|
||
| return TTY_MODE_NORMAL; | ||
| } | ||
|
|
||
| tty_mode_t tty__get_mode(int32_t fd) | ||
| { | ||
| if (UNLIKELY(!tty__is_tty(fd))) | ||
| return TTY_MODE_UNSET; | ||
|
|
||
| if (UNLIKELY(tty__get_termios(fd, &tty__termios_tmp))) | ||
| return TTY_MODE_UNSET; | ||
|
|
||
| return tty__internal_get_mode(&tty__termios_tmp); | ||
| } | ||
|
|
||
| int32_t tty__set_mode(int32_t fd, tty_mode_t mode) | ||
| { | ||
| if (UNLIKELY(!tty__is_tty(fd))) | ||
| return -3; | ||
|
|
||
| bool orig_just_set_now = false; | ||
|
|
||
| switch (mode) { | ||
| case TTY_MODE_NORMAL: | ||
| if (UNLIKELY(!tty__is_raw(fd))) | ||
| break; | ||
|
|
||
| if (UNLIKELY(tcsetattr(fd, TCSADRAIN, &tty__orig_termios))) | ||
| return -6; | ||
|
|
||
| break; | ||
|
|
||
| // NOTE: This is based on the code from libuv for TTY_MODE_RAW | ||
| case TTY_MODE_RAW: | ||
| if (!tty__orig_set) { | ||
| if (UNLIKELY(tty__get_termios(fd, nullptr))) | ||
| return -4; | ||
| tty__orig_set = true; | ||
| orig_just_set_now = true; | ||
| } | ||
|
|
||
| // If we set original this time, then we can make a copy of that for our new struct | ||
| if (orig_just_set_now) { | ||
| tty__termios_tmp = tty__orig_termios; | ||
| } else if (UNLIKELY(tcgetattr(fd, &tty__termios_tmp))) | ||
| return -5; | ||
|
|
||
| // Check if we are already in raw mode | ||
| // if so, then break | ||
| if (UNLIKELY(tty__internal_get_mode(&tty__termios_tmp) == TTY_MODE_RAW)) | ||
| break; | ||
|
|
||
| // If we already have a raw mode struct, use that | ||
| if (tty__raw_mode_set) { | ||
| if (UNLIKELY(tcsetattr(fd, TCSADRAIN, &tty__raw_mode_termios))) | ||
| return -6; | ||
| break; | ||
| } | ||
|
|
||
| tty__termios_tmp.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); | ||
| tty__termios_tmp.c_oflag |= (ONLCR); | ||
| tty__termios_tmp.c_cflag |= (CS8); | ||
| tty__termios_tmp.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); | ||
| tty__termios_tmp.c_cc[VMIN] = 1; | ||
| tty__termios_tmp.c_cc[VTIME] = 0; | ||
|
|
||
| tty__raw_mode_termios = tty__termios_tmp; | ||
| tty__raw_mode_set = true; | ||
|
|
||
| if (UNLIKELY(tcsetattr(fd, TCSADRAIN, &tty__termios_tmp))) | ||
| return -6; | ||
|
|
||
| break; | ||
|
|
||
| case TTY_MODE_RAW_ASYNC_IO: | ||
|
|
||
| if (!tty__orig_set) { | ||
| if (UNLIKELY(tty__get_termios(fd, nullptr))) | ||
| return -4; | ||
| tty__orig_set = true; | ||
| orig_just_set_now = true; | ||
| } | ||
|
|
||
| // If we set original this time, then we can make a copy of that for our new struct | ||
| if (orig_just_set_now) { | ||
| tty__termios_tmp = tty__orig_termios; | ||
| } else if (UNLIKELY(tcgetattr(fd, &tty__termios_tmp))) | ||
| return -5; | ||
|
|
||
| // Check if we are already in raw mode | ||
| // if so, then break | ||
| if (UNLIKELY(tty__internal_get_mode(&tty__termios_tmp) == TTY_MODE_RAW_ASYNC_IO)) | ||
| break; | ||
|
|
||
| // Here are the main difference between this (TTY_MODE_RAW_ASYNC_IO) | ||
| // and what Node and libuv consider "raw mode", as well as what we call "raw mode" (TTY_MODE_RAW): | ||
|
|
||
| // - We unset INLCR, which means we don't convert newlines to carriage returns | ||
| // - We unset IGNCR, which means we don't ignore carriage returns | ||
| // - We unset IGNBRK, which means we don't ignore break conditions | ||
| // - We unset PARMRK, which means we don't mark parity errors | ||
| // - We unset OPOST, which means we don't do any output processing... This means thinks like LF -> CRLF won't happen, | ||
| // but also that we won't do any other output processing | ||
| // - We unset ECHONL, which means we don't echo newlines | ||
| // - We unset CSIZE, which means we don't set the character size | ||
| // - We unset PARENB, which means we don't enable parity generation on output and parity checking for input | ||
|
|
||
| // - We don't unset INPCK, which means we *would* check parity... except actually we don't because we unset PARENB | ||
|
|
||
| // Some parts of this config may be redundant, but it's likely just so that things are more explicit semantically | ||
| // I basically pulled out the `cfmakeraw()` config to make it more explicit what we're doing here | ||
| // But the config below *should* be equivalent to `cfmakeraw()` (on most systems) | ||
| tty__termios_tmp.c_iflag &= ~(BRKINT | ICRNL | ISTRIP | IXON | INLCR | IGNCR | IGNBRK | PARMRK); | ||
| tty__termios_tmp.c_oflag &= ~OPOST; | ||
| tty__termios_tmp.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG | ECHONL); | ||
| tty__termios_tmp.c_cflag &= ~(CSIZE | PARENB); | ||
| tty__termios_tmp.c_cflag |= CS8; | ||
|
|
||
| if (UNLIKELY(tcsetattr(fd, TCSADRAIN, &tty__termios_tmp))) | ||
| return -6; | ||
|
|
||
| break; | ||
| case TTY_MODE_UNSET: | ||
| // User should really never call this with TTY_MODE_UNSET | ||
| return -10; | ||
| default: | ||
| return -1; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| int tty__set_async_io_mode(int fd, bool enabled) | ||
| { | ||
| tty_mode_t current_mode = tty__get_mode(fd); | ||
| bool is_async_io = current_mode == TTY_MODE_RAW_ASYNC_IO; | ||
|
|
||
| // If already in desired state, then return 0 | ||
| if (is_async_io == enabled) | ||
| return 0; | ||
|
|
||
| if (enabled) { | ||
| // If enabling, cache the current mode so we can restore it later | ||
| tty__cached_mode = current_mode; | ||
| // Set the mode to raw async io | ||
| return tty__set_mode(fd, TTY_MODE_RAW_ASYNC_IO); | ||
| } else { | ||
| // If disabling, restore the cached mode and reset cached mode | ||
| // If unset, then default to normal. Though this should never happen under normal circumstances | ||
| auto result = tty__set_mode(fd, tty__cached_mode != TTY_MODE_UNSET ? tty__cached_mode : TTY_MODE_NORMAL); | ||
| tty__cached_mode = TTY_MODE_UNSET; | ||
| return result; | ||
| } | ||
| } | ||
|
|
||
| } // namespace Zig | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| #pragma once | ||
|
|
||
| #include <termios.h> | ||
|
|
||
| #ifndef STDIN_FILENO | ||
| #define STDIN_FILENO 0 | ||
| #endif | ||
|
|
||
| #ifndef UNLIKELY | ||
| #define UNLIKELY(x) __builtin_expect(!!(x), 0) | ||
| #endif | ||
|
|
||
| #ifndef LIKELY | ||
| #define LIKELY(x) __builtin_expect(!!(x), 1) | ||
| #endif | ||
|
|
||
| namespace Zig { | ||
|
|
||
| extern "C" typedef enum { | ||
| TTY_MODE_NORMAL, | ||
| TTY_MODE_RAW, | ||
| TTY_MODE_RAW_ASYNC_IO, | ||
| TTY_MODE_UNSET = -1, | ||
| } tty_mode_t; | ||
|
|
||
| extern "C" int32_t tty__is_tty(int32_t fd); | ||
| extern "C" int32_t tty__get_termios(int32_t fd, termios* termios_p); | ||
| extern "C" int32_t tty__is_raw(int32_t fd); | ||
| extern "C" int32_t tty__set_mode(int32_t fd, tty_mode_t mode); | ||
| extern "C" tty_mode_t tty__get_mode(int32_t fd); | ||
|
|
||
| int tty__set_async_io_mode(int fd, bool enabled); | ||
|
|
||
| } // namespace Zig |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use
makeStringinstead of+operator