cons: fix Windows console VT mode save and restore (#6169)

* cons: fix windows console VT restore
* restore state in rzcons and own to instance for state
This commit is contained in:
Alok Kumar Mishra
2026-04-11 10:47:05 +08:00
committed by GitHub
parent 66ccde55dd
commit f1e918d528
4 changed files with 83 additions and 35 deletions
+51 -18
View File
@@ -21,6 +21,12 @@ static RzConsContext rz_cons_context_default = { { { { 0 } } } };
static RzCons rz_cons_instance = { 0 };
#define I rz_cons_instance
#if __WINDOWS__
// restore only the console flags rizin owns instead of replaying the whole host mode word
#define RZ_CONS_OUTPUT_MODE_MASK (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
#define RZ_CONS_INPUT_MODE_MASK (ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_MOUSE_INPUT | ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS | ENABLE_VIRTUAL_TERMINAL_INPUT)
#endif
// this structure goes into cons_stack when rz_cons_push/pop
typedef struct {
char *buf;
@@ -535,7 +541,9 @@ RZ_API bool rz_cons_enable_mouse(const bool enable) {
HANDLE h;
bool enabled = I.mouse;
h = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(h, &mode);
if (!GetConsoleMode(h, &mode)) {
return enabled;
}
mode |= ENABLE_EXTENDED_FLAGS;
mode = enable
? (mode | ENABLE_MOUSE_INPUT) & ~ENABLE_QUICK_EDIT_MODE
@@ -564,34 +572,46 @@ static void set_console_codepage_to_utf8(void) {
}
static void save_console_state(void) {
if (rz_cons_isatty()) {
// Snapshot the exact std handles and their current console modes before any probing mutates them.
I.saved_input_handle = GetStdHandle(STD_INPUT_HANDLE);
I.saved_output_handle = GetStdHandle(STD_OUTPUT_HANDLE);
I.saved_input_console = GetConsoleMode((HANDLE)I.saved_input_handle, &I.old_input_mode);
I.saved_output_console = GetConsoleMode((HANDLE)I.saved_output_handle, &I.old_output_mode);
if (I.saved_output_console) {
if (!(I.old_ocp = GetConsoleOutputCP())) {
rz_sys_perror("GetConsoleOutputCP");
}
}
if (I.saved_input_console) {
if (!(I.old_cp = GetConsoleCP())) {
rz_sys_perror("GetConsoleCP");
}
if (!GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &I.old_output_mode)) {
rz_sys_perror("GetConsoleMode");
}
if (!GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &I.old_input_mode)) {
rz_sys_perror("GetConsoleCP");
}
}
}
static inline DWORD restore_console_mode_bits(DWORD mode, DWORD saved_mode, DWORD mask) {
return (mode & ~mask) | (saved_mode & mask);
}
static void restore_console_state(void) {
if (rz_cons_isatty()) {
if (!SetConsoleCP(I.old_cp)) {
rz_sys_perror("SetConsoleCP");
}
DWORD mode;
if (I.saved_output_console && GetConsoleMode((HANDLE)I.saved_output_handle, &mode)) {
if (!SetConsoleOutputCP(I.old_ocp)) {
rz_sys_perror("SetConsoleOutputCP");
}
if (!SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), I.old_output_mode)) {
// Keep unrelated host-owned bits intact and restore only the output flags we changed.
mode = restore_console_mode_bits(mode, I.old_output_mode, RZ_CONS_OUTPUT_MODE_MASK);
if (!SetConsoleMode((HANDLE)I.saved_output_handle, mode)) {
rz_sys_perror("SetConsoleMode");
}
if (!SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), I.old_input_mode)) {
}
if (I.saved_input_console && GetConsoleMode((HANDLE)I.saved_input_handle, &mode)) {
if (!SetConsoleCP(I.old_cp)) {
rz_sys_perror("SetConsoleCP");
}
// Input teardown mirrors output teardown for the subset of flags Rizin manages.
mode = restore_console_mode_bits(mode, I.old_input_mode, RZ_CONS_INPUT_MODE_MASK);
if (!SetConsoleMode((HANDLE)I.saved_input_handle, mode)) {
rz_sys_perror("SetConsoleMode");
}
}
@@ -605,6 +625,10 @@ RZ_API RzCons *rz_cons_new(void) {
if (I.refcnt != 1) {
return &I;
}
#if __WINDOWS__
// Save the console state before rz_line_new() runs VT detection on Windows.
save_console_state();
#endif
I.rgbstr = rz_cons_rgb_str_off;
I.line = rz_line_new();
I.enable_highlight = true;
@@ -635,8 +659,9 @@ RZ_API RzCons *rz_cons_new(void) {
I.num = NULL;
I.null = 0;
#if __WINDOWS__
save_console_state();
I.vtmode = rz_cons_detect_vt_mode();
// Keep line editing on the same VT mode that the main console instance selected.
I.line->vtmode = I.vtmode;
set_console_codepage_to_utf8();
#else
I.vtmode = RZ_VIRT_TERM_MODE_COMPLETE;
@@ -653,8 +678,11 @@ RZ_API RzCons *rz_cons_new(void) {
I.term_raw.c_cc[VMIN] = 1; // Solaris stuff hehe
rz_sys_signal(SIGWINCH, resize);
#elif __WINDOWS__
I.term_buf = I.old_input_mode | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT;
I.term_raw = ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT);
if (I.saved_input_console) {
// Raw/buffered mode masks must be derived from the saved console input mode.
I.term_buf = I.old_input_mode | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT;
I.term_raw = ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT);
}
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)__w32_control, TRUE)) {
eprintf("rz_cons: Cannot set control console handler\n");
}
@@ -1698,7 +1726,12 @@ RZ_API void rz_cons_set_raw(bool is_raw) {
#elif __WINDOWS__
DWORD mode;
HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(h, &mode);
if (!GetConsoleMode(h, &mode)) {
// non console stdin is valid in batch mode.. just track state and leave the handle untouched
fflush(stdout);
I.oldraw = is_raw;
return;
}
if (is_raw) {
if (I.term_pty) {
rz_sys_xsystem("stty raw -echo");
+9 -4
View File
@@ -397,13 +397,15 @@ static int __cons_readchar_w32(ut32 usec) {
void *bed;
I->mouse_event = MOUSE_NONE;
h = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(h, &mode);
const bool has_input_console = GetConsoleMode(h, &mode);
DWORD newmode = ENABLE_WINDOW_INPUT;
if (I->vtmode == RZ_VIRT_TERM_MODE_COMPLETE) {
newmode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
}
newmode |= mode;
SetConsoleMode(h, newmode);
if (has_input_console) {
// Only toggle temporary read modes when stdin is a real console handle.
SetConsoleMode(h, newmode | mode);
}
do {
bed = rz_cons_sleep_begin();
if (usec) {
@@ -545,7 +547,10 @@ static int __cons_readchar_w32(ut32 usec) {
resizeWin();
}
} while (ch == 0 && !do_break);
SetConsoleMode(h, mode);
if (has_input_console) {
// Restore the temporary read mode only when we successfully captured one.
SetConsoleMode(h, mode);
}
return ch;
}
#endif
+19 -13
View File
@@ -2029,27 +2029,33 @@ static bool scr_vtmode(void *user, void *data) {
DWORD mode;
HANDLE input = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(input, &mode);
if (node->i_value == RZ_VIRT_TERM_MODE_COMPLETE) {
SetConsoleMode(input, mode & ENABLE_VIRTUAL_TERMINAL_INPUT);
cons->term_raw |= ENABLE_VIRTUAL_TERMINAL_INPUT;
} else {
SetConsoleMode(input, mode & ~ENABLE_VIRTUAL_TERMINAL_INPUT);
cons->term_raw &= ~ENABLE_VIRTUAL_TERMINAL_INPUT;
if (GetConsoleMode(input, &mode)) {
if (node->i_value == RZ_VIRT_TERM_MODE_COMPLETE) {
// Enabling VT input must preserve the rest of the console input flags.
SetConsoleMode(input, mode | ENABLE_VIRTUAL_TERMINAL_INPUT);
cons->term_raw |= ENABLE_VIRTUAL_TERMINAL_INPUT;
} else {
SetConsoleMode(input, mode & ~ENABLE_VIRTUAL_TERMINAL_INPUT);
cons->term_raw &= ~ENABLE_VIRTUAL_TERMINAL_INPUT;
}
}
HANDLE streams[] = { GetStdHandle(STD_OUTPUT_HANDLE), GetStdHandle(STD_ERROR_HANDLE) };
int i;
if (node->i_value > RZ_VIRT_TERM_MODE_DISABLE) {
for (i = 0; i < RZ_ARRAY_SIZE(streams); i++) {
GetConsoleMode(streams[i], &mode);
SetConsoleMode(streams[i],
mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
if (GetConsoleMode(streams[i], &mode)) {
// Output VT mode is an additive toggle on the current console output state.
SetConsoleMode(streams[i],
mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
}
} else {
for (i = 0; i < RZ_ARRAY_SIZE(streams); i++) {
GetConsoleMode(streams[i], &mode);
SetConsoleMode(streams[i],
mode & ~ENABLE_VIRTUAL_TERMINAL_PROCESSING & ~ENABLE_WRAP_AT_EOL_OUTPUT);
if (GetConsoleMode(streams[i], &mode)) {
// Disabling VT output should only clear the bits enabled by this callback.
SetConsoleMode(streams[i],
mode & ~(ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_WRAP_AT_EOL_OUTPUT));
}
}
}
return true;
+4
View File
@@ -558,6 +558,10 @@ typedef struct rz_cons_t {
struct termios term_raw, term_buf;
#elif __WINDOWS__
unsigned long term_raw, term_buf, term_pty;
void *saved_input_handle;
void *saved_output_handle;
bool saved_input_console;
bool saved_output_console;
unsigned long old_input_mode, old_output_mode;
ut32 old_cp;
ut32 old_ocp;