Hi,
When I'm using some programs such as bash and python from cygwin under
Windows Terminal, Ctrl+C is sometimes ignored.
https://github.com/microsoft/terminal
Normally holding 'Ctrl' and pressing 'C' will make new line.
But, sometimes it does not and unholding 'Ctrl' makes new line under
Windows Terminal.
bash from msys2 does also reproduce this issue.
I dug into this issue and found that this is related to
readline and Windows 10's pseudo console (ConPTY).
I made simple programs to reproduce this issue.
- EchoCon.cpp
- modification of ConPty sample code provided by Microsoft.
This program execute bash on pseudo console.
to be compiled with MSVC.
- getkey.cpp
- simple program to check Ctrl+C is passed to Cygwin program.
to be compiled with Cygwin gcc.
- rltest.cpp
- simple program to check SIGINT handling.
This program reproduces the issue.
If you replace readline("> ") with gets(buf),
then the issue does not happen.
to be compiled with Cygwin gcc.
- The machine and OS that it is running on
- OS: Windows 10 Pro 19043.1288
- Windows Terminal: 1.11.2921.0
Regards,
Naoto Aoki
// original: https://blog.goo.ne.jp/lm324/e/16629a8aadaa0de77fc05611390cf15b
// modified by aont 2021/10/15
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
int getkey(void)
{
struct termios oldt, newt;
int ch0,ch1,ch2,ch3,ch4;
int ret;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_iflag = ~( BRKINT | ISTRIP | IXON );
newt.c_lflag = ~( ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHONL );
newt.c_cc[VTIME] = 0;
newt.c_cc[VMIN] = 1;
newt.c_cc[VINTR] = 1;
if(tcsetattr(STDIN_FILENO, TCSANOW, &newt)==-1) {
fprintf(stderr,"error tcsetattr\n");
exit(EXIT_FAILURE);
}
ch0 = getchar();
if(ch0==0x1B) {
ch1 = getchar();
ch2 = getchar();
if(ch2==0x32) {
ch3 = getchar();
if(ch3==0x7e) {
ret = (ch0<<24) | (ch1<<16) | (ch2<<8) | ch3;
} else {
ch4 = getchar();
ret = (ch1<<24) | (ch2<<16) | (ch3<<8) | ch4;
}
} else if(ch2==0x31) {
ch3 = getchar();
ch4 = getchar();
ret = (ch1<<24) | (ch2<<16) | (ch3<<8) | ch4;
} else if((ch2==0x33)||(ch2==0x35)||(ch2==0x36)) {
ch3 = getchar();
ret = (ch0<<24) | (ch1<<16) | (ch2<<8) | ch3;
} else {
ret = (ch0<<16) | (ch1<<8) | ch2;
}
} else if(ch0 != EOF) {
ret = ch0;
} else {
ret = 0;
}
if(tcsetattr(STDIN_FILENO, TCSANOW, &oldt)==-1) {
fprintf(stderr,"error tcsetattr\n");
exit(EXIT_FAILURE);
}
return ret;
}
int main(int argc,char *argv[])
{
int key;
while (1) {
key = getkey();
if (key!=0) {
printf("key code 0x%x\n",key);
}else{
;
usleep(1000);
}
if(key==0x71 /* q */ ) {
break;
}
}
return 0;
}
#include <setjmp.h>
#include <stdio.h>
#include <signal.h>
#include <readline/readline.h>
sigjmp_buf ctrlc_buf;
void handle_signals(int signo) {
if (signo == SIGINT) {
siglongjmp(ctrlc_buf, 1);
}
}
int main(int argc, char **argv)
{
char * input;
if (signal(SIGINT, handle_signals) == SIG_ERR) {
fprintf(stderr, "installing signal handler failed\n");
}
char buf[128];
while (1)
{
while ( sigsetjmp( ctrlc_buf, 1 ) != 0 ) {
fprintf(stderr, "Ctrl+C\n");
}
input = readline("> ");
if (!input)
break;
}
return 0;
}
// EchoCon.cpp : Entry point for the EchoCon Pseudo-Console sample application.
// Copyright © 2018, Microsoft
// Modified by aont 2021/10/15
// #include "stdafx.h"
#include <Windows.h>
#include <process.h>
#include <cstdio>
// Forward declarations
HRESULT CreatePseudoConsoleAndPipes(HPCON*, HANDLE*, HANDLE*);
HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX*, HPCON);
void __cdecl PipeListener(LPVOID);
int main()
{
wchar_t szCommand[]{ L"C:\\cygwin64\\bin\\bash.exe --login -i" };
HRESULT hr{ E_UNEXPECTED };
HANDLE hConsole = { GetStdHandle(STD_OUTPUT_HANDLE) };
HANDLE hin = GetStdHandle(STD_INPUT_HANDLE);
// Enable Console VT Processing
DWORD consoleMode{};
GetConsoleMode(hin, &consoleMode);
hr = SetConsoleMode(hin, consoleMode ^ ENABLE_PROCESSED_INPUT ^
ENABLE_LINE_INPUT)
? S_OK
: GetLastError();
GetConsoleMode(hConsole, &consoleMode);
hr = SetConsoleMode(hConsole, consoleMode |
ENABLE_VIRTUAL_TERMINAL_PROCESSING)
? S_OK
: GetLastError();
if (S_OK == hr)
{
HPCON hPC{ INVALID_HANDLE_VALUE };
// Create the Pseudo Console and pipes to it
HANDLE hPipeIn{ INVALID_HANDLE_VALUE };
HANDLE hPipeOut{ INVALID_HANDLE_VALUE };
hr = CreatePseudoConsoleAndPipes(&hPC, &hPipeIn, &hPipeOut);
if (S_OK == hr)
{
// Create & start thread to listen to the incoming pipe
// Note: Using CRT-safe _beginthread() rather than CreateThread()
HANDLE hPipeListenerThread{
reinterpret_cast<HANDLE>(_beginthread(PipeListener, 0, hPipeIn)) };
// Initialize the necessary startup info struct
STARTUPINFOEX startupInfo{};
if (S_OK ==
InitializeStartupInfoAttachedToPseudoConsole(&startupInfo, hPC))
{
// Launch ping to emit some text back via the pipe
PROCESS_INFORMATION piClient{};
hr = CreateProcess(
NULL, // No module name - use
Command Line
szCommand, // Command Line
NULL, // Process handle not
inheritable
NULL, // Thread handle not
inheritable
FALSE, // Inherit handles
EXTENDED_STARTUPINFO_PRESENT, // Creation flags
NULL, // Use parent's environment
block
NULL, // Use parent's starting
directory
&startupInfo.StartupInfo, // Pointer to STARTUPINFO
&piClient) // Pointer to
PROCESS_INFORMATION
? S_OK
: GetLastError();
char input_key;
DWORD num_events;
while (true) {
bool ret;
ret = ReadFile(hin, &input_key, 1, &num_events, NULL);
if (ret) {
fprintf(stderr, "input: 0x%x\n", input_key);
WriteFile(hPipeOut, &input_key, 1, &num_events, NULL);
}
};
if (S_OK == hr)
{
// Wait up to 10s for ping process to complete
WaitForSingleObject(piClient.hThread, 10 * 1000);
// Allow listening thread to catch-up with final output!
Sleep(500);
}
// --- CLOSEDOWN ---
// Now safe to clean-up client app's process-info & thread
CloseHandle(piClient.hThread);
CloseHandle(piClient.hProcess);
// Cleanup attribute list
DeleteProcThreadAttributeList(startupInfo.lpAttributeList);
free(startupInfo.lpAttributeList);
}
// Close ConPTY - this will terminate client process if running
ClosePseudoConsole(hPC);
// Clean-up the pipes
if (INVALID_HANDLE_VALUE != hPipeOut) CloseHandle(hPipeOut);
if (INVALID_HANDLE_VALUE != hPipeIn) CloseHandle(hPipeIn);
}
}
return S_OK == hr ? EXIT_SUCCESS : EXIT_FAILURE;
}
HRESULT CreatePseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE*
phPipeOut)
{
HRESULT hr{ E_UNEXPECTED };
HANDLE hPipePTYIn{ INVALID_HANDLE_VALUE };
HANDLE hPipePTYOut{ INVALID_HANDLE_VALUE };
// Create the pipes to which the ConPTY will connect
if (CreatePipe(&hPipePTYIn, phPipeOut, NULL, 0) &&
CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0))
{
// Determine required size of Pseudo Console
COORD consoleSize{};
CONSOLE_SCREEN_BUFFER_INFO csbi{};
HANDLE hConsole{ GetStdHandle(STD_OUTPUT_HANDLE) };
if (GetConsoleScreenBufferInfo(hConsole, &csbi))
{
consoleSize.X = csbi.srWindow.Right - csbi.srWindow.Left + 1;
consoleSize.Y = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
}
// Create the Pseudo Console of the required size, attached to the
PTY-end of the pipes
hr = CreatePseudoConsole(consoleSize, hPipePTYIn, hPipePTYOut, 0, phPC);
// Note: We can close the handles to the PTY-end of the pipes here
// because the handles are dup'ed into the ConHost and will be released
// when the ConPTY is destroyed.
if (INVALID_HANDLE_VALUE != hPipePTYOut) CloseHandle(hPipePTYOut);
if (INVALID_HANDLE_VALUE != hPipePTYIn) CloseHandle(hPipePTYIn);
}
return hr;
}
// Initializes the specified startup info struct with the required properties
and
// updates its thread attribute list with the specified ConPTY handle
HRESULT InitializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX*
pStartupInfo, HPCON hPC)
{
HRESULT hr{ E_UNEXPECTED };
if (pStartupInfo)
{
SIZE_T attrListSize{};
pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX);
// Get the size of the thread attribute list.
InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize);
// Allocate a thread attribute list of the correct size
pStartupInfo->lpAttributeList =
reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attrListSize));
// Initialize thread attribute list
if (pStartupInfo->lpAttributeList
&& InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList,
1, 0, &attrListSize))
{
// Set Pseudo Console attribute
hr = UpdateProcThreadAttribute(
pStartupInfo->lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
hPC,
sizeof(HPCON),
NULL,
NULL)
? S_OK
: HRESULT_FROM_WIN32(GetLastError());
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
return hr;
}
void __cdecl PipeListener(LPVOID pipe)
{
HANDLE hPipe{ pipe };
HANDLE hConsole{ GetStdHandle(STD_OUTPUT_HANDLE) };
const DWORD BUFF_SIZE{ 512 };
char szBuffer[BUFF_SIZE]{};
DWORD dwBytesWritten{};
DWORD dwBytesRead{};
BOOL fRead{ FALSE };
do
{
// Read from the pipe
fRead = ReadFile(hPipe, szBuffer, BUFF_SIZE, &dwBytesRead, NULL);
// Write received text to the Console
// Note: Write to the Console using WriteFile(hConsole...), not
printf()/puts() to
// prevent partially-read VT sequences from corrupting output
WriteFile(hConsole, szBuffer, dwBytesRead, &dwBytesWritten, NULL);
} while (fRead && dwBytesRead >= 0);
}
--
Problem reports: https://cygwin.com/problems.html
FAQ: https://cygwin.com/faq/
Documentation: https://cygwin.com/docs.html
Unsubscribe info: https://cygwin.com/ml/#unsubscribe-simple