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 <CompareAndSwap@gmail.com>
This commit is contained in:
Spencer Low 2015-11-07 17:34:39 -08:00
parent 53529ecacd
commit 2e02dc630f
3 changed files with 71 additions and 19 deletions

View File

@ -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<ShellProtocol>& 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<const HANDLE>(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

View File

@ -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

View File

@ -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