(I initially accidentally wrote an "Answer" that was intended to be a reply to an AI "Answer".)
^C sent to the stdin of a program running under a pseudo console does not generate break
I've written a program that launches a console application within a pseudoconsole. Following the documentation, I:
- Create two anonymous pipes.
- Call
CreatePseudoConsole, passing in the appropriate end of each of the pipes for standard input & standard output. - Create a process thread attribute list and stash the
hPCvalue in it. - Link to the process thread attribute list from a
STARTUPINFOEXstructure. - Call
CreateProcessW, passing in thatSTARTUPINFOEX. - Start reading bytes from the parent end of the standard output pipe and interpreting escape sequences. This seems to be working well. :-)
- Receive keyboard input events from my front-end, translate them to byte sequences and send them to the parent end of the standard input pipe. This is also mostly working well, arrow keys and such functioning just fine.
Everything I've read indicates that if a byte 0x03 ("ETX"?) is sent to the child process' standard input, this will effectively become a ^C console control event. But, when I do this, no break results.
If the child process is cmd and I'm at a prompt, then when I send byte 0x03, it cancels the input and goes to the next line, looking exactly like cmd does in a regular console window. But, if I run e.g. ping and it is waiting between pings, in a regular console window, pressing ^C results in the process being interrupted, and in fact writing the strings Control-C and ^C to its output before terminating. When my code sends byte 0x03 to its child ping process, though, it doesn't appear to have any effect at all. This happens whether ping is a direct child process of my application or I run cmd as a child process of my application and then launch ping from the command-line.
Searches tell me that what's missing is the console mode flag ENABLE_PROCESSED_INPUT, but as far as I can tell, that can only be set with a direct call to the SetConsoleMode API with the process attached to the console. It doesn't seem to be applicable in the context of interacting with the child process via ConPTY.
I must be missing something, but I haven't been able to figure out the next step to take to uncover it. Does anybody know what's going on?
ETA:
I've been doing further investigation on my end, and I'm pretty sure the AI response is just 100% factually incorrect. It states that the stdin pipe is just a raw character sequence for the target application to process, but with ConPTY, the stdin stream is processed by the console host to translate terminal escape sequences representing input keys into KEY_INPUT_RECORD structures to be written to the traditional console input queue. I haven't found the source of my problem yet, but I have found loads of code within the Microsoft Terminal codebase specifically oriented to the handling of non-textual keys, including control keys and including special handling for Ctrl+C specifically. This handling runs when ETX, or byte 3, is read from the pipe -- and on the other end, in the Terminal GUI app which acts as a client, Ctrl-letter key combinations are translated to (letter - 0x40) bytes, so that Ctrl-C produces byte (0x43 - 0x40) == 3.
Here's the ConPTY code that does, in fact, "synthesize control events from characters", following the sequence of calls down the call stack:
- https://github.com/microsoft/terminal/blob/0f5d883c59201007e8a7cd4c576477d2b5157cf7/src/host/VtInputThread.cpp#L114
- https://github.com/microsoft/terminal/blob/0f5d883c59201007e8a7cd4c576477d2b5157cf7/src/terminal/parser/stateMachine.cpp#L1998
- https://github.com/microsoft/terminal/blob/0f5d883c59201007e8a7cd4c576477d2b5157cf7/src/terminal/parser/stateMachine.cpp#L2024
- https://github.com/microsoft/terminal/blob/0f5d883c59201007e8a7cd4c576477d2b5157cf7/src/terminal/parser/stateMachine.cpp#L1881
- https://github.com/microsoft/terminal/blob/0f5d883c59201007e8a7cd4c576477d2b5157cf7/src/terminal/parser/stateMachine.cpp#L1062-L1064
- https://github.com/microsoft/terminal/blob/0f5d883c59201007e8a7cd4c576477d2b5157cf7/src/terminal/parser/stateMachine.cpp#L387
- https://github.com/microsoft/terminal/blob/0f5d883c59201007e8a7cd4c576477d2b5157cf7/src/terminal/parser/InputStateMachineEngine.cpp#L146
- https://github.com/microsoft/terminal/blob/0f5d883c59201007e8a7cd4c576477d2b5157cf7/src/terminal/parser/InputStateMachineEngine.cpp#L158-L165
- https://github.com/microsoft/terminal/blob/0f5d883c59201007e8a7cd4c576477d2b5157cf7/src/terminal/adapter/InteractDispatch.cpp#L51-L54
- https://github.com/microsoft/terminal/blob/0f5d883c59201007e8a7cd4c576477d2b5157cf7/src/host/input.cpp#L118-L121
Interestingly, as can be seen at that final point in the code, the ConPTY code does explicitly read & check the ENABLE_PROCESSED_INPUT flag in the traditional Win32 consoles world, as is normally exposed via SetConsoleMode/GetConsoleMode (that's what IsInProcessedInputMode returns). If ENABLE_PROCESSED_INPUT isn't set, then it doesn't call HandleCtrlEvent.
So, one possible explanation for the symptoms I'm seeing is that, for some reason, when my process launches a cmd.exe or ping.exe child process, it is ending up with ENABLE_PROCESSED_INPUT off. I'm not sure how that would happen, though, or why it'd be different from, say, Start->Run cmd.exe or ping.exe.