Cygwin: pty: Inherit typeahead data between two input pipes.

- PTY has a problem that the key input, which is typed during
  windows native app is running, disappear when it returns to shell.
  (Problem 3 in https://cygwin.com/ml/cygwin/2020-02/msg00007.html)
  This is beacuse pty has two input pipes, one is for cygwin apps
  and the other one is for native windows apps. The key input during
  windows native program is running is sent to the second input pipe
  while cygwin shell reads input from the first input pipe.
  This patch realize transfering input data between these two pipes.
This commit is contained in:
Takashi Yano
2020-02-09 23:46:59 +09:00
committed by Corinna Vinschen
parent a4ca6c4861
commit 29431fcb5b
5 changed files with 331 additions and 93 deletions

View File

@ -440,6 +440,10 @@ fhandler_pty_master::flush_to_slave ()
{
if (get_readahead_valid () && !(get_ttyp ()->ti.c_lflag & ICANON))
accept_input ();
WaitForSingleObject (input_mutex, INFINITE);
if (!get_ttyp ()->pcon_in_empty && !(get_ttyp ()->ti.c_lflag & ICANON))
SetEvent (input_available_event);
ReleaseMutex (input_mutex);
}
DWORD
@ -519,7 +523,7 @@ fhandler_pty_master::accept_input ()
termios_printf ("sending EOF to slave");
get_ttyp ()->read_retval = 0;
}
else
else if (!to_be_read_from_pcon ())
{
char *p = rabuf ();
DWORD rc;
@ -986,7 +990,6 @@ fhandler_pty_slave::close ()
termios_printf ("CloseHandle (output_mutex<%p>), %E", output_mutex);
if (pcon_attached_to == get_minor ())
get_ttyp ()->num_pcon_attached_slaves --;
get_ttyp ()->mask_switch_to_pcon_in = false;
return 0;
}
@ -1076,6 +1079,157 @@ fhandler_pty_slave::restore_reattach_pcon (void)
pid_restore = 0;
}
/* This function requests transfering the input data from the input
pipe for cygwin apps to the other input pipe for native apps. */
void
fhandler_pty_slave::pull_pcon_input (void)
{
get_ttyp ()->req_transfer_input_to_pcon = true;
while (get_ttyp ()->req_transfer_input_to_pcon)
Sleep (1);
}
void
fhandler_pty_slave::update_pcon_input_state (bool need_lock)
{
if (need_lock)
WaitForSingleObject (input_mutex, INFINITE);
/* Flush input buffer if it is requested by master.
This happens if ^C is pressed in pseudo console side. */
if (get_ttyp ()->req_flush_pcon_input)
{
FlushConsoleInputBuffer (get_handle ());
get_ttyp ()->req_flush_pcon_input = false;
}
/* Peek console input buffer and update state. */
INPUT_RECORD inp[INREC_SIZE];
DWORD n;
BOOL (WINAPI *PeekFunc)
(HANDLE, PINPUT_RECORD, DWORD, LPDWORD);
PeekFunc =
PeekConsoleInputA_Orig ? : PeekConsoleInput;
PeekFunc (get_handle (), inp, INREC_SIZE, &n);
bool saw_accept = false;
bool pipe_empty = true;
while (n-- > 0)
if (inp[n].EventType == KEY_EVENT && inp[n].Event.KeyEvent.bKeyDown &&
inp[n].Event.KeyEvent.uChar.AsciiChar)
{
pipe_empty = false;
char c = inp[n].Event.KeyEvent.uChar.AsciiChar;
const char sigs[] = {
(char) get_ttyp ()->ti.c_cc[VINTR],
(char) get_ttyp ()->ti.c_cc[VQUIT],
(char) get_ttyp ()->ti.c_cc[VSUSP],
};
const char eols[] = {
(char) get_ttyp ()->ti.c_cc[VEOF],
(char) get_ttyp ()->ti.c_cc[VEOL],
(char) get_ttyp ()->ti.c_cc[VEOL2],
'\n',
'\r'
};
if (is_line_input () && memchr (eols, c, sizeof (eols)))
saw_accept = true;
if ((get_ttyp ()->ti.c_lflag & ISIG) &&
memchr (sigs, c, sizeof (sigs)))
saw_accept = true;
}
get_ttyp ()->pcon_in_empty = pipe_empty && !(ralen () > raixget ());
if (!get_readahead_valid () &&
(pipe_empty || (is_line_input () && !saw_accept)) &&
get_ttyp ()->read_retval == 1 && bytes_available (n) && n == 0)
ResetEvent (input_available_event);
if (need_lock)
ReleaseMutex (input_mutex);
}
int
fhandler_pty_slave::eat_readahead (int n)
{
int oralen = ralen () - raixget ();
if (n < 0)
n = ralen () - raixget ();
if (n > 0 && ralen () > raixget ())
{
const char eols[] = {
(char) get_ttyp ()->ti.c_cc[VEOF],
(char) get_ttyp ()->ti.c_cc[VEOL],
(char) get_ttyp ()->ti.c_cc[VEOL2],
'\n'
};
while (n > 0 && ralen () > raixget ())
{
if (memchr (eols, rabuf ()[ralen ()-1], sizeof (eols)))
break;
-- n;
-- ralen ();
}
/* If IUTF8 is set, the terminal is in UTF-8 mode. If so, we erase
a complete UTF-8 multibyte sequence on VERASE/VWERASE. Otherwise,
if we only erase a single byte, invalid unicode chars are left in
the input. */
if (get_ttyp ()->ti.c_iflag & IUTF8)
while (ralen () > 0 &&
((unsigned char) rabuf ()[ralen ()] & 0xc0) == 0x80)
--ralen ();
if (raixget () >= ralen ())
raixget () = raixput () = ralen () = 0;
else if (raixput () > ralen ())
raixput () = ralen ();
}
return oralen;
}
int
fhandler_pty_slave::get_readahead_into_buffer (char *buf, size_t buflen)
{
int ch;
int copied_chars = 0;
while (buflen)
if ((ch = get_readahead ()) < 0)
break;
else
{
const char eols[] = {
(char) get_ttyp ()->ti.c_cc[VEOF],
(char) get_ttyp ()->ti.c_cc[VEOL],
(char) get_ttyp ()->ti.c_cc[VEOL2],
'\n'
};
buf[copied_chars++] = (unsigned char)(ch & 0xff);
buflen--;
if (is_line_input () && memchr (eols, ch & 0xff, sizeof (eols)))
break;
}
return copied_chars;
}
bool
fhandler_pty_slave::get_readahead_valid (void)
{
if (is_line_input ())
{
const char eols[] = {
(char) get_ttyp ()->ti.c_cc[VEOF],
(char) get_ttyp ()->ti.c_cc[VEOL],
(char) get_ttyp ()->ti.c_cc[VEOL2],
'\n'
};
for (size_t i=raixget (); i<ralen (); i++)
if (memchr (eols, rabuf ()[i], sizeof (eols)))
return true;
return false;
}
else
return ralen () > raixget ();
}
void
fhandler_pty_slave::set_switch_to_pcon (int fd_set)
{
@ -1083,13 +1237,7 @@ fhandler_pty_slave::set_switch_to_pcon (int fd_set)
fd = fd_set;
if (fd == 0 && !get_ttyp ()->switch_to_pcon_in)
{
pid_restore = 0;
if (pcon_attached_to != get_minor ())
if (!try_reattach_pcon ())
goto skip_console_setting;
FlushConsoleInputBuffer (get_handle ());
skip_console_setting:
restore_reattach_pcon ();
pull_pcon_input ();
if (get_ttyp ()->pcon_pid == 0 ||
!pinfo (get_ttyp ()->pcon_pid))
get_ttyp ()->pcon_pid = myself->pid;
@ -1121,16 +1269,7 @@ fhandler_pty_slave::reset_switch_to_pcon (void)
/* There is a process which is grabbing pseudo console. */
return;
if (isHybrid)
{
DWORD bytes_in_pipe;
WaitForSingleObject (input_mutex, INFINITE);
if (bytes_available (bytes_in_pipe) && !bytes_in_pipe)
ResetEvent (input_available_event);
FlushConsoleInputBuffer (get_handle ());
ReleaseMutex (input_mutex);
init_console_handler (true);
return;
}
return;
if (do_not_reset_switch_to_pcon)
return;
if (get_ttyp ()->switch_to_pcon_out)
@ -1333,6 +1472,14 @@ fhandler_pty_slave::write (const void *ptr, size_t len)
if (!try_reattach_pcon ())
fallback = true;
if (get_ttyp ()->switch_to_pcon_out && !fallback &&
(memmem (buf, nlen, "\033[6n", 4) || memmem (buf, nlen, "\033[0c", 4)))
{
get_ttyp ()->pcon_in_empty = false;
if (!is_line_input ())
SetEvent (input_available_event);
}
DWORD dwMode, flags;
flags = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (!(get_ttyp ()->ti.c_oflag & OPOST) ||
@ -1382,18 +1529,14 @@ fhandler_pty_slave::write (const void *ptr, size_t len)
void
fhandler_pty_slave::mask_switch_to_pcon_in (bool mask)
{
if (!mask && get_ttyp ()->pcon_pid &&
get_ttyp ()->pcon_pid != myself->pid &&
!!pinfo (get_ttyp ()->pcon_pid))
return;
get_ttyp ()->mask_switch_to_pcon_in = mask;
}
bool
fhandler_pty_common::to_be_read_from_pcon (void)
{
return get_ttyp ()->switch_to_pcon_in &&
!get_ttyp ()->mask_switch_to_pcon_in;
return !get_ttyp ()->pcon_in_empty ||
(get_ttyp ()->switch_to_pcon_in && !get_ttyp ()->mask_switch_to_pcon_in);
}
void __reg3
@ -1424,6 +1567,8 @@ fhandler_pty_slave::read (void *ptr, size_t& len)
mask_switch_to_pcon_in (true);
reset_switch_to_pcon ();
}
if (to_be_read_from_pcon ())
update_pcon_input_state (true);
if (is_nonblocking () || !ptr) /* Indicating tcflush(). */
time_to_wait = 0;
@ -1523,60 +1668,81 @@ fhandler_pty_slave::read (void *ptr, size_t& len)
}
goto out;
}
if (to_be_read_from_pcon ())
if (ptr && to_be_read_from_pcon ())
{
if (!try_reattach_pcon ())
if (get_readahead_valid ())
{
restore_reattach_pcon ();
goto do_read_cyg;
}
DWORD dwMode;
GetConsoleMode (get_handle (), &dwMode);
DWORD flags = ENABLE_VIRTUAL_TERMINAL_INPUT;
if (get_ttyp ()->ti.c_lflag & ECHO)
flags |= ENABLE_ECHO_INPUT;
if (get_ttyp ()->ti.c_lflag & ICANON)
flags |= ENABLE_LINE_INPUT;
if (flags & ENABLE_ECHO_INPUT && !(flags & ENABLE_LINE_INPUT))
flags &= ~ENABLE_ECHO_INPUT;
if ((get_ttyp ()->ti.c_lflag & ISIG) &&
!(get_ttyp ()->ti.c_iflag & IGNBRK))
flags |= ENABLE_PROCESSED_INPUT;
if (dwMode != flags)
SetConsoleMode (get_handle (), flags);
/* Read get_handle() instad of get_handle_cyg() */
BOOL (WINAPI *ReadFunc)
(HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED);
ReadFunc = ReadFile_Orig ? ReadFile_Orig : ReadFile;
DWORD rlen;
if (!ReadFunc (get_handle (), ptr, len, &rlen, NULL))
{
termios_printf ("read failed, %E");
ReleaseMutex (input_mutex);
set_errno (EIO);
totalread = -1;
goto out;
totalread = get_readahead_into_buffer ((char *) ptr, len);
}
INPUT_RECORD inp[128];
DWORD n;
BOOL (WINAPI *PeekFunc)
(HANDLE, PINPUT_RECORD, DWORD, LPDWORD);
PeekFunc =
PeekConsoleInputA_Orig ? PeekConsoleInputA_Orig : PeekConsoleInput;
PeekFunc (get_handle (), inp, 128, &n);
bool pipe_empty = true;
while (n-- > 0)
if (inp[n].EventType == KEY_EVENT && inp[n].Event.KeyEvent.bKeyDown)
pipe_empty = false;
if (pipe_empty)
ResetEvent (input_available_event);
ReleaseMutex (input_mutex);
len = rlen;
else
{
if (!try_reattach_pcon ())
{
restore_reattach_pcon ();
goto do_read_cyg;
}
restore_reattach_pcon ();
DWORD dwMode;
GetConsoleMode (get_handle (), &dwMode);
DWORD flags = ENABLE_VIRTUAL_TERMINAL_INPUT;
if (dwMode != flags)
SetConsoleMode (get_handle (), flags);
/* Read get_handle() instad of get_handle_cyg() */
BOOL (WINAPI *ReadFunc)
(HANDLE, LPVOID, DWORD, LPDWORD, LPVOID);
ReadFunc = ReadConsoleA_Orig ? ReadConsoleA_Orig : ReadConsoleA;
DWORD rlen;
readlen = MIN (len, sizeof (buf));
if (!ReadFunc (get_handle (), buf, readlen, &rlen, NULL))
{
termios_printf ("read failed, %E");
SetConsoleMode (get_handle (), dwMode);
restore_reattach_pcon ();
ReleaseMutex (input_mutex);
set_errno (EIO);
totalread = -1;
goto out;
}
SetConsoleMode (get_handle (), dwMode);
restore_reattach_pcon ();
ReleaseMutex (input_mutex);
ssize_t nlen;
char *nbuf = convert_mb_str (get_ttyp ()->term_code_page,
(size_t *) &nlen, GetConsoleCP (), buf, rlen);
ssize_t ret;
line_edit_status res =
line_edit (nbuf, nlen, get_ttyp ()->ti, &ret);
mb_str_free (nbuf);
if (res == line_edit_input_done || res == line_edit_ok)
totalread = get_readahead_into_buffer ((char *) ptr, len);
else if (res > line_edit_signalled)
{
set_sig_errno (EINTR);
totalread = -1;
}
else
{
update_pcon_input_state (true);
continue;
}
}
update_pcon_input_state (true);
mask_switch_to_pcon_in (false);
return;
goto out;
}
if (!ptr && len == UINT_MAX && !get_ttyp ()->pcon_in_empty)
{
FlushConsoleInputBuffer (get_handle ());
get_ttyp ()->pcon_in_empty = true;
DWORD n;
if (bytes_available (n) && n == 0)
ResetEvent (input_available_event);
}
do_read_cyg:
@ -1697,7 +1863,8 @@ out:
termios_printf ("%d = read(%p, %lu)", totalread, ptr, len);
len = (size_t) totalread;
/* Push slave read as echo to pseudo console screen buffer. */
if (get_pseudo_console () && ptr0 && (get_ttyp ()->ti.c_lflag & ECHO))
if (get_pseudo_console () && ptr0 && totalread > 0 &&
(get_ttyp ()->ti.c_lflag & ECHO))
{
acquire_output_mutex (INFINITE);
push_to_pcon_screenbuffer (ptr0, len, true);
@ -2307,19 +2474,41 @@ fhandler_pty_master::write (const void *ptr, size_t len)
WaitForSingleObject (input_mutex, INFINITE);
if (memchr (buf, '\003', nlen)) /* ^C intr key in pcon */
get_ttyp ()->req_flush_pcon_input = true;
DWORD wLen;
WriteFile (to_slave, buf, nlen, &wLen, NULL);
if (ti.c_lflag & ICANON)
{
if (memchr (buf, '\r', nlen))
SetEvent (input_available_event);
}
else
SetEvent (input_available_event);
get_ttyp ()->pcon_in_empty = false;
ReleaseMutex (input_mutex);
/* Use line_edit () in order to set input_available_event. */
bool is_echo = tc ()->ti.c_lflag & ECHO;
if (!get_ttyp ()->mask_switch_to_pcon_in)
{
tc ()->ti.c_lflag &= ~ECHO;
ti.c_lflag &= ~ECHO;
}
ti.c_lflag &= ~ISIG;
line_edit (buf, nlen, ti, &ret);
if (is_echo)
tc ()->ti.c_lflag |= ECHO;
get_ttyp ()->read_retval = 1;
const char sigs[] = {
(char) ti.c_cc[VINTR],
(char) ti.c_cc[VQUIT],
(char) ti.c_cc[VSUSP],
};
if (tc ()->ti.c_lflag & ISIG)
for (size_t i=0; i<sizeof (sigs); i++)
if (memchr (buf, sigs[i], nlen))
{
eat_readahead (-1);
SetEvent (input_available_event);
}
mb_str_free (buf);
return len;
}
@ -2716,11 +2905,17 @@ fhandler_pty_slave::fixup_after_attach (bool native_maybe, int fd_set)
get_ttyp ()->num_pcon_attached_slaves ++;
}
#if 0 /* This is for debug only. */
isHybrid = true;
get_ttyp ()->switch_to_pcon_in = true;
get_ttyp ()->switch_to_pcon_out = true;
#endif
if (pcon_attached_to == get_minor () && native_maybe)
{
if (fd == 0)
{
FlushConsoleInputBuffer (get_handle ());
pull_pcon_input ();
DWORD mode =
ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT;
SetConsoleMode (get_handle (), mode);
@ -2764,13 +2959,6 @@ fhandler_pty_slave::fixup_after_fork (HANDLE parent)
void
fhandler_pty_slave::fixup_after_exec ()
{
/* Native windows program does not reset event on read.
Therefore, reset here if no input is available. */
DWORD bytes_in_pipe;
if (!to_be_read_from_pcon () &&
bytes_available (bytes_in_pipe) && !bytes_in_pipe)
ResetEvent (input_available_event);
reset_switch_to_pcon ();
if (!close_on_exec ())
@ -2988,6 +3176,33 @@ reply:
return 0;
}
void
fhandler_pty_master::transfer_input_to_pcon (void)
{
WaitForSingleObject (input_mutex, INFINITE);
DWORD n;
size_t transfered = 0;
while (::bytes_available (n, from_master_cyg) && n)
{
char buf[1024];
ReadFile (from_master_cyg, buf, sizeof (buf), &n, 0);
char *p = buf;
while ((p = (char *) memchr (p, '\n', n - (p - buf))))
*p = '\r';
if (WriteFile (to_slave, buf, n, &n, 0))
transfered += n;
}
DWORD bytes_left = eat_readahead (-1);
if (bytes_left)
{
if (WriteFile (to_slave, rabuf (), bytes_left, &n, NULL))
transfered += n;
}
if (transfered)
get_ttyp ()->pcon_in_empty = false;
ReleaseMutex (input_mutex);
}
static DWORD WINAPI
pty_master_thread (VOID *arg)
{
@ -3018,6 +3233,15 @@ fhandler_pty_master::pty_master_fwd_thread ()
if (GetTickCount () - get_ttyp ()->pcon_last_time > time_to_wait)
SetEvent (get_ttyp ()->fwd_done);
release_output_mutex ();
/* Forcibly transfer input if it is requested by slave.
This happens when input data should be transfered
from the input pipe for cygwin apps to the input pipe
for native apps. */
if (get_ttyp ()->req_transfer_input_to_pcon)
{
transfer_input_to_pcon ();
get_ttyp ()->req_transfer_input_to_pcon = false;
}
Sleep (1);
}
}