How Windows Terminal Handles Pseudoconsole (ConPTY): Architecture and Implementation
Windows Terminal uses a dedicated ConPTY subsystem to spawn headless console host processes, managing three asynchronous pipes (input, output, and signal) to isolate console applications from the UI while enabling real-time resize, clear, and re-parent operations.
Windows Terminal leverages the pseudoconsole (ConPTY) subsystem to embed traditional console applications within its modern UI framework. According to the microsoft/terminal source code, this architecture launches a hidden console host process and bridges it to the Terminal UI through a specialized C API exported from the winconpty library. The implementation ensures complete isolation between the console engine and the terminal frontend while maintaining full compatibility with existing command-line tools.
ConPTY Architecture Overview
Windows Terminal treats ConPTY as a separate console-host process that runs in the background. The system creates a headless conhost.exe (or OpenConsole.exe when packaged with Terminal) and establishes three communication channels: an input pipe, an output pipe, and a dedicated signal pipe for out-of-band control commands.
The Three-Handle Model
The pseudoconsole relies on three distinct handles to separate data flow from control operations:
- Input pipe: Receives keystrokes and mouse input from the Terminal UI and forwards them to the client application
- Output pipe: Carries rendered text and VT sequences from the console host back to the Terminal for display
- Signal pipe: Transmits asynchronous control messages including resize requests, clear commands, and window visibility changes
This design prevents control sequences from interfering with the primary data streams, allowing the Terminal to resize or clear the buffer without corrupting the application’s I/O.
Server Process Isolation
The PTY server runs in its own process space rather than inside the Terminal executable. In src/winconpty/winconpty.cpp (L 45‑L 101), the helper _ConsoleHostPath() resolves the path to either the system conhost.exe or the side-by-side OpenConsole.exe that ships with Windows Terminal. Keeping the console host external ensures that legacy applications receive a genuine console environment while the Terminal UI remains a separate HWND that can be styled, resized, and composed freely.
Core Implementation in winconpty
The low-level ConPTY logic lives in the winconpty static library, which exports a C-style API and manages the lifecycle of the hidden console host.
Host Resolution and Driver Loading
Before creating the PTY, the library ensures the console driver is available. The function _EnsureDriverIsLoaded() in src/winconpty/winconpty.cpp (L 101‑L 112) loads ntdll.dll and invokes NtSetSystemInformation with SystemConsoleInformation to force-load the ConDrv driver on builds where it might not be initialized. This guarantees that subsequent console operations succeed even on non-Insider builds of Windows.
Process Creation with Extended Attributes
Creating the headless console host requires careful handle inheritance setup. In src/winconpty/winconpty.cpp (L 124‑L 166), the code builds a PROC_THREAD_ATTRIBUTE_LIST containing the server handle, client input/output handles, and the signal pipe handle. This attribute list is passed to CreateProcessAsUserW (L 186‑L 227) along with a command line such as:
"<conhostPath>" --headless --width <cols> --height <rows> --signal <signalHandle> --server <serverHandle>
The --headless flag instructs the console host to run without a visible window, while the --signal and --server parameters wire the process to the Terminal’s pipes.
Exported C API Functions
The public interface declared in src/inc/conpty-static.h (L 26‑L 48) and implemented in src/winconpty/winconpty.cpp (L 460‑L 508) provides:
ConptyCreatePseudoConsole: Allocates thePseudoConsolestruct and launches the headless hostConptyResizePseudoConsole: SendsPTY_SIGNAL_RESIZE_WINDOWvia the signal pipeConptyClearPseudoConsole: SendsPTY_SIGNAL_CLEAR_WINDOWConptyShowHidePseudoConsole: SendsPTY_SIGNAL_SHOWHIDE_WINDOWConptyReparentPseudoConsole: SendsPTY_SIGNAL_REPARENT_WINDOWto attach the hidden console window to a different HWNDConptyClosePseudoConsole: Releases theHPCONhandle and closes all three pipes (L 624‑L 648)
These functions wrap the internal helpers _CreatePseudoConsole, _ResizePseudoConsole, and others, offering a RAII-friendly interface for C++ consumers.
Terminal Integration via ConptyConnection
While winconpty handles the raw PTY mechanics, the Cascadia UI layer consumes these primitives through ConptyConnection.
Pipe Creation and PTY Initialization
In src/cascadia/TerminalConnection/ConptyConnection.cpp (L 96‑L 124), the Start() method creates overlapped I/O pipes using Utils::CreateOverlappedPipe, then invokes ConptyCreatePseudoConsole to obtain an HPCON handle. The method stores this handle and optionally re-parents the PTY window (L 146‑L 162) so that the invisible console window behaves correctly with focus and IME integration. After setup, the code releases the initial reference via ConptyReleasePseudoConsole, keeping the PTY alive only through the active pipe handles.
Runtime Control Operations
All dynamic modifications to the pseudoconsole travel over the signal pipe, implemented in src/winconpty/winconpty.cpp:
- Resize (L 486‑L 525):
ConptyResizePseudoConsolewrites aPTY_SIGNAL_RESIZE_WINDOWstructure containing new width and height values - Clear (L 540‑L 564):
ConptyClearPseudoConsoleissuesPTY_SIGNAL_CLEAR_WINDOWto reset the scrollback buffer - Show/Hide (L 590‑L 620):
ConptyShowHidePseudoConsoletoggles visibility withPTY_SIGNAL_SHOWHIDE_WINDOW - Re-parent (L 660‑L 685):
ConptyReparentPseudoConsolesendsPTY_SIGNAL_REPARENT_WINDOWwith a target HWND, enabling the Terminal UI to own the PTY window for proper focus handling (see GitHub issue #2988)
Because these messages are asynchronous and travel over a dedicated pipe, the Terminal can resize or clear the buffer without blocking the data streams moving through the input and output pipes.
Practical ConPTY Implementation Example
The following minimal C++ snippet mirrors the logic found in ConptyConnection::Start() and demonstrates how to create a pseudoconsole, launch a child process, and resize it programmatically:
#include <windows.h>
#include <conpty-static.h> // ConPTY public API
#include <wil/resource.h> // for wil::unique_handle
int main()
{
// 1. Create overlapped pipes for bidirectional I/O
HANDLE hInRead, hInWrite;
HANDLE hOutRead, hOutWrite;
SECURITY_ATTRIBUTES sa{ sizeof(sa), nullptr, TRUE };
CreatePipe(&hInRead, &hInWrite, &sa, 0);
CreatePipe(&hOutRead, &hOutWrite, &sa, 0);
// 2. Create the pseudoconsole (80 columns x 24 rows)
HPCON hPC = nullptr;
COORD size{ 80, 24 };
HRESULT hr = ConptyCreatePseudoConsole(
size,
hInRead, // PTY input (data flowing to the client)
hOutWrite, // PTY output (data flowing from the client)
0, // Flags such as PSEUDOCONSOLE_INHERIT_CURSOR
&hPC);
if (FAILED(hr))
return 1;
// 3. Launch cmd.exe attached to the PTY
STARTUPINFOEXW siEx{ sizeof(STARTUPINFOEXW) };
PROCESS_INFORMATION pi{};
siEx.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
siEx.StartupInfo.hStdInput = hInWrite;
siEx.StartupInfo.hStdOutput = hOutRead;
siEx.StartupInfo.hStdError = hOutRead;
CreateProcessW(L"cmd.exe", nullptr, nullptr, nullptr, TRUE,
EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr,
&siEx.StartupInfo, &pi);
// 4. Resize the terminal to 100x30 via the signal pipe
COORD newSize{ 100, 30 };
ConptyResizePseudoConsole(hPC, newSize);
// 5. Cleanup on exit
ConptyClosePseudoConsole(hPC);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hInRead); CloseHandle(hInWrite);
CloseHandle(hOutRead); CloseHandle(hOutWrite);
}
This example reflects the production implementation found in src/winconpty/winconpty.cpp (L 460‑L 468 for creation, L 486‑L 494 for resize) and src/cascadia/TerminalConnection/ConptyConnection.cpp (L 96‑L 162 for integration).
Summary
- Windows Terminal spawns a headless
conhost.exeorOpenConsole.exeprocess to run the PTY server independently from the UI, isolating the console implementation from the terminal frontend. - The system maintains three asynchronous handles: an input pipe, an output pipe, and a dedicated signal pipe for out-of-band control messages such as resize and clear commands.
- The winconpty library exports a C API (
ConptyCreatePseudoConsole,ConptyResizePseudoConsole, etc.) that wraps low-level NT primitives and handle inheritance logic. ConptyConnectionintegrates these primitives into the Cascadia UI, managing pipe creation, process lifecycle, window re-parenting, and graceful shutdown.- All control operations travel over the signal pipe, ensuring that data streams remain uncontaminated while enabling real-time terminal modifications.
Frequently Asked Questions
What is the difference between ConPTY and a traditional console host?
A traditional console host renders text directly to a visible window owned by the operating system. ConPTY creates a pseudoconsole that captures all output into pipes, allowing Windows Terminal to receive the rendered buffer and display it inside a modern, hardware-accelerated UI while the actual console engine runs headlessly in the background.
How does Windows Terminal resize a pseudoconsole without disrupting the client process?
Windows Terminal calls ConptyResizePseudoConsole, which writes a PTY_SIGNAL_RESIZE_WINDOW packet to the signal pipe (implemented in src/winconpty/winconpty.cpp L 486‑L 525). The headless conhost.exe processes this message asynchronously, updating its internal buffer dimensions without interrupting the STDIN or STDOUT data flowing through the primary pipes.
Can third-party applications use the same ConPTY API as Windows Terminal?
Yes. The API is publicly exported in src/inc/conpty-static.h and demonstrated in the samples/ConPTY/EchoCon project. Any application can link against the winconpty static library and call ConptyCreatePseudoConsole to embed a full Windows console inside its own window, handling I/O through the same three-pipe model.
Why does Windows Terminal use a separate signal pipe instead of embedding control codes in the data stream?
The dedicated signal pipe isolates control-plane messages (resize, clear, show/hide, re-parent) from the data flow. This prevents escape-sequence conflicts, avoids the need to parse inline control codes from application output, and enables asynchronous operations that do not block or buffer within the primary I/O pipes, ensuring responsive terminal behavior even under heavy load.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →