From 2e02dc630f6449f2a79d9130a6346de7761e2be2 Mon Sep 17 00:00:00 2001 From: Spencer Low Date: Sat, 7 Nov 2015 17:34:39 -0800 Subject: [PATCH] adb: SIGWINCH support for Windows - Introduces unix_read_interruptible() which is like unix_read() except that it can return EINTR. - The big idea is that the Windows ReadConsoleInput() API will return an event on window resize and then we return EINTR from unix_read_interruptible() just like Unix. - Only handles horizontal resize since Windows doesn't seem to give an event for vertical resize when no special screen buffer is used. This should be sufficient for the primary use case of adb on Windows (people are not running vi in the first place). Change-Id: Id8d1710b559834c8098f2d7fbecedf2d0ade4b88 Signed-off-by: Spencer Low --- adb/commandline.cpp | 52 ++++++++++++++++++++++++++++++++----------- adb/sysdeps.h | 12 +++++++++- adb/sysdeps_win32.cpp | 26 +++++++++++++++++----- 3 files changed, 71 insertions(+), 19 deletions(-) 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