diff --git a/adb/commandline.cpp b/adb/commandline.cpp index f88669808..1b5836388 100644 --- a/adb/commandline.cpp +++ b/adb/commandline.cpp @@ -472,18 +472,46 @@ static bool GetFeatureSet(TransportType transport_type, const char* serial, Feat } static void send_window_size_change(int fd, std::unique_ptr& shell) { -#if !defined(_WIN32) // Old devices can't handle window size changes. if (shell == nullptr) return; +#if defined(_WIN32) + struct winsize { + unsigned short ws_row; + unsigned short ws_col; + unsigned short ws_xpixel; + unsigned short ws_ypixel; + }; +#endif + winsize ws; + +#if defined(_WIN32) + // If stdout is redirected to a non-console, we won't be able to get the + // console size, but that makes sense. + const intptr_t intptr_handle = _get_osfhandle(STDOUT_FILENO); + if (intptr_handle == -1) return; + + const HANDLE handle = reinterpret_cast(intptr_handle); + + CONSOLE_SCREEN_BUFFER_INFO info; + memset(&info, 0, sizeof(info)); + if (!GetConsoleScreenBufferInfo(handle, &info)) return; + + memset(&ws, 0, sizeof(ws)); + // The number of visible rows, excluding offscreen scroll-back rows which are in info.dwSize.Y. + ws.ws_row = info.srWindow.Bottom - info.srWindow.Top + 1; + // If the user has disabled "Wrap text output on resize", they can make the screen buffer wider + // than the window, in which case we should use the width of the buffer. + ws.ws_col = info.dwSize.X; +#else if (ioctl(fd, TIOCGWINSZ, &ws) == -1) return; +#endif // Send the new window size as human-readable ASCII for debugging convenience. size_t l = snprintf(shell->data(), shell->data_capacity(), "%dx%d,%dx%d", ws.ws_row, ws.ws_col, ws.ws_xpixel, ws.ws_ypixel); shell->Write(ShellProtocol::kIdWindowSizeChange, l + 1); -#endif } // Used to pass multiple values to the stdin read thread. @@ -508,7 +536,10 @@ static void* stdin_read_thread_loop(void* x) { pthread_sigmask(SIG_BLOCK, &sigset, nullptr); #endif -#if !defined(_WIN32) +#if defined(_WIN32) + // _get_interesting_input_record_uncached() causes unix_read_interruptible() + // to return -1 with errno == EINTR if the window size changes. +#else // Unblock SIGWINCH for this thread, so our read(2) below will be // interrupted if the window size changes. sigset_t mask; @@ -537,20 +568,15 @@ static void* stdin_read_thread_loop(void* x) { EscapeState state = kStartOfLine; while (true) { - // Use unix_read() rather than adb_read() for stdin. - D("stdin_read_thread_loop(): pre unix_read(fdi=%d,...)", args->stdin_fd); -#if !defined(_WIN32) -#undef read - int r = read(args->stdin_fd, buffer_ptr, buffer_size); + // Use unix_read_interruptible() rather than adb_read() for stdin. + D("stdin_read_thread_loop(): pre unix_read_interruptible(fdi=%d,...)", args->stdin_fd); + int r = unix_read_interruptible(args->stdin_fd, buffer_ptr, + buffer_size); if (r == -1 && errno == EINTR) { send_window_size_change(args->stdin_fd, args->protocol); continue; } -#define read ___xxx_read -#else - int r = unix_read(args->stdin_fd, buffer_ptr, buffer_size); -#endif - D("stdin_read_thread_loop(): post unix_read(fdi=%d,...)", args->stdin_fd); + D("stdin_read_thread_loop(): post unix_read_interruptible(fdi=%d,...)", args->stdin_fd); if (r <= 0) { // Only devices using the shell protocol know to close subprocess // stdin. For older devices we want to just leave the connection diff --git a/adb/sysdeps.h b/adb/sysdeps.h index 2190c61f7..0abade46f 100644 --- a/adb/sysdeps.h +++ b/adb/sysdeps.h @@ -164,8 +164,13 @@ static __inline__ int unix_close(int fd) #undef close #define close ____xxx_close +// Like unix_read(), but may return EINTR. +extern int unix_read_interruptible(int fd, void* buf, size_t len); + // See the comments for the !defined(_WIN32) version of unix_read(). -extern int unix_read(int fd, void* buf, size_t len); +static __inline__ int unix_read(int fd, void* buf, size_t len) { + return TEMP_FAILURE_RETRY(unix_read_interruptible(fd, buf, len)); +} #undef read #define read ___xxx_read @@ -517,6 +522,11 @@ static __inline__ int adb_read(int fd, void* buf, size_t len) return TEMP_FAILURE_RETRY( read( fd, buf, len ) ); } +// Like unix_read(), but does not handle EINTR. +static __inline__ int unix_read_interruptible(int fd, void* buf, size_t len) { + return read(fd, buf, len); +} + #undef read #define read ___xxx_read diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp index c3889b686..bea47a217 100644 --- a/adb/sysdeps_win32.cpp +++ b/adb/sysdeps_win32.cpp @@ -2588,6 +2588,18 @@ static bool _get_key_event_record(const HANDLE console, INPUT_RECORD* const inpu fatal("ReadConsoleInputA did not return one input record"); } + // If the console window is resized, emulate SIGWINCH by breaking out + // of read() with errno == EINTR. Note that there is no event on + // vertical resize because we don't give the console our own custom + // screen buffer (with CreateConsoleScreenBuffer() + + // SetConsoleActiveScreenBuffer()). Instead, we use the default which + // supports scrollback, but doesn't seem to raise an event for vertical + // window resize. + if (input_record->EventType == WINDOW_BUFFER_SIZE_EVENT) { + errno = EINTR; + return false; + } + if ((input_record->EventType == KEY_EVENT) && (input_record->Event.KeyEvent.bKeyDown)) { if (input_record->Event.KeyEvent.wRepeatCount == 0) { @@ -3323,9 +3335,13 @@ void stdin_raw_init() { // Disable ENABLE_LINE_INPUT so that input is immediately sent. // Disable ENABLE_ECHO_INPUT to disable local echo. Disabling this // flag also seems necessary to have proper line-ending processing. - if (!SetConsoleMode(in, _old_console_mode & ~(ENABLE_PROCESSED_INPUT | - ENABLE_LINE_INPUT | - ENABLE_ECHO_INPUT))) { + DWORD new_console_mode = _old_console_mode & ~(ENABLE_PROCESSED_INPUT | + ENABLE_LINE_INPUT | + ENABLE_ECHO_INPUT); + // Enable ENABLE_WINDOW_INPUT to get window resizes. + new_console_mode |= ENABLE_WINDOW_INPUT; + + if (!SetConsoleMode(in, new_console_mode)) { // This really should not fail. D("stdin_raw_init: SetConsoleMode() failed: %s", SystemErrorCodeToString(GetLastError()).c_str()); @@ -3353,8 +3369,8 @@ void stdin_raw_restore() { } } -// Called by 'adb shell' and 'adb exec-in' to read from stdin. -int unix_read(int fd, void* buf, size_t len) { +// Called by 'adb shell' and 'adb exec-in' (via unix_read()) to read from stdin. +int unix_read_interruptible(int fd, void* buf, size_t len) { if ((fd == STDIN_FILENO) && (_console_handle != NULL)) { // If it is a request to read from stdin, and stdin_raw_init() has been // called, and it successfully configured the console, then read from