Terminal Component Interaction Flow in Windows Terminal: The WPF-Native Bridge Architecture
The Terminal component interaction flow in Windows Terminal operates through a bidirectional bridge where a thin WPF managed UI forwards input events to a native C++ core via a C API, while the core processes VT100/ANSI sequences and renders output directly to an HWND hosted within the WPF control.
The Terminal component interaction flow in Windows Terminal represents a sophisticated architectural pattern that separates presentation from processing. In the microsoft/terminal repository, this flow connects the managed WPF frontend with the native terminal engine through a well-defined C API boundary, enabling high-performance text rendering while maintaining flexible UI capabilities.
How the Terminal Component Interaction Flow Works
The architecture divides responsibilities into three distinct stages: input capture, core processing, and rendering output. Each stage crosses the managed-native boundary through specific exported functions defined in src/cascadia/TerminalControl/HwndTerminal.hpp.
Stage 1: Input Capture from WPF to Native
User interaction begins in the managed WPF layer within src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs. The TerminalControl hosts a TerminalContainer (an HwndHost subclass) defined in src/cascadia/WpfTerminalControl/TerminalContainer.cs.
The TerminalContainer installs a message hook (TerminalContainer_MessageHook) that intercepts Windows messages destined for the native HWND. Relevant messages are immediately forwarded across the C API boundary:
WM_KEYDOWN/WM_SYSKEYDOWN→TerminalSendKeyEvent(with vkey, scan code, flags, keyDown = true)WM_KEYUP/WM_SYSKEYUP→TerminalSendKeyEvent(with keyDown = false)WM_CHAR→TerminalSendCharEventWM_MOUSEWHEEL→TerminalUserScrollWM_SETFOCUS/WM_KILLFOCUS→TerminalSetFocusedWM_WINDOWPOSCHANGED(size change) →TerminalTriggerResizeorTerminalCalculateResizedepending onAutoResize
Stage 2: Core Processing in the Terminal Engine
Once input crosses the boundary, src/cascadia/TerminalControl/HwndTerminal.cpp unwraps the terminal instance pointer and forwards calls to the core Microsoft::Terminal::Core::Terminal class defined in src/cascadia/TerminalCore/Terminal.hpp.
The core implements the VT100/ANSI state machine (src/terminal/parser/StateMachine.hpp) and the ITerminalApi interface (src/terminal/adapter/ITerminalApi.hpp). Input methods such as SendKeyEvent, SendCharEvent, UserResize, and UserScroll perform the following:
- Update the text buffer and scrollback
- Modify selection state
- Toggle terminal modes (e.g., application cursor keys)
- Generate CSI responses (e.g., cursor position reports)
After processing, the core may request a render update or produce output data destined for the PTY.
Stage 3: Rendering and Output Communication
The terminal core communicates back to the WPF UI through two registered callbacks established during initialization in TerminalContainer.BuildWindowCore:
- Write Callback (
TerminalRegisterWriteCallback) – handles data flowing from the core to the PTY - Scroll Callback (
TerminalRegisterScrollCallback) – notifies the UI of viewport changes
When the core generates output (e.g., echoing user input or sending control sequence responses), it invokes the write callback (OnWrite in TerminalContainer), which forwards the data to the attached ITerminalConnection via Connection.WriteInput.
For rendering, the core holds a Renderer instance (src/renderer/base/renderer.cpp) that uses back-ends such as AtlasEngine or UiaEngine. The renderer draws directly onto the native HWND created by CreateTerminal. The WPF TerminalContainer simply hosts this HWND; it does not participate in the paint loop.
When the viewport scrolls, the core calls the scroll callback (OnScroll), which raises the TerminalScrolled event. TerminalControl handles this to update the WPF scrollbar position.
Key Source Files in the Terminal Architecture
Understanding the Terminal component interaction flow requires familiarity with these specific files in the microsoft/terminal repository:
src/cascadia/WpfTerminalControl/TerminalControl.xaml.cs– WPF control that forwards UI events to the native hostsrc/cascadia/WpfTerminalControl/TerminalContainer.cs–HwndHostimplementation that creates the native HWND and registers callbackssrc/cascadia/TerminalControl/HwndTerminal.hpp– Exported C API (CreateTerminal,TerminalSendKeyEvent, etc.) used by the managed hostsrc/cascadia/TerminalControl/HwndTerminal.cpp– Implements the C API and forwards calls to the coreTerminalobjectsrc/cascadia/TerminalCore/Terminal.hpp– Core VT100/ANSI parser, text buffer, and selection logicsrc/renderer/base/renderer.cpp– Rendering infrastructure that paints to the native HWNDsrc/terminal/adapter/ITerminalApi.hpp– Interface implemented by the core for UI integrationsrc/terminal/input/terminalInput.hpp– Key and character event handling inside the coresrc/terminal/parser/StateMachine.hpp– VT100 state machine that interprets escape sequences
Implementation Examples
Hosting the Terminal in XAML
<!-- MainWindow.xaml -->
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:Microsoft.Terminal.Wpf"
Title="My Terminal">
<local:TerminalControl x:Name="TermCtrl" />
</Window>
Wiring a PTY Connection in C#
using Microsoft.Terminal.Wpf;
using Microsoft.Terminal.Settings.Models;
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
// Create a ConPTY connection
ITerminalConnection conn = new ConPtyConnection(@"cmd.exe");
// Assign it to the control
TermCtrl.Connection = conn;
// Configure theme and font
var theme = new TerminalTheme
{
DefaultBackground = 0x000000,
DefaultForeground = 0xFFFFFF
};
TermCtrl.SetTheme(theme, "Cascadia Mono", 10);
}
}
Handling Scroll Events
TermCtrl.TerminalScrolled += (s, e) =>
{
// e.viewTop, e.viewHeight, e.bufferSize
Console.WriteLine($"Viewport scrolled to line: {e.viewTop}");
};
Programmatic Resizing
await TermCtrl.ResizeAsync(rows: 40, columns: 120, CancellationToken.None);
Summary
- The Terminal component interaction flow follows a strict managed-to-native bridge pattern, separating the WPF UI from the high-performance C++ core.
- Input travels from
TerminalControl→TerminalContainer→HwndTerminalC API →Terminalcore, where the VT100 state machine processes sequences. - Output flows back through registered callbacks (
TerminalRegisterWriteCallback,TerminalRegisterScrollCallback) to update the PTY connection and WPF scrollbar. - Rendering bypasses WPF's visual tree; the native
Rendererdraws directly onto the HWND created byCreateTerminal, ensuring optimal performance for text-intensive workloads.
Frequently Asked Questions
How does keyboard input cross from the WPF layer to the native terminal core?
Keyboard input is captured by the TerminalContainer_MessageHook in TerminalContainer.cs, which intercepts WM_KEYDOWN, WM_KEYUP, and WM_CHAR messages. These are forwarded across the C API boundary via TerminalSendKeyEvent and TerminalSendCharEvent defined in HwndTerminal.hpp. The C++ implementation in HwndTerminal.cpp unwraps the terminal pointer and calls SendKeyEvent or SendCharEvent on the core Microsoft::Terminal::Core::Terminal instance.
Why does Windows Terminal use an HWND hosted inside WPF rather than pure WPF rendering?
The terminal requires high-performance text rendering capable of handling rapid VT100/ANSI sequence processing, large scrollback buffers, and complex glyph shaping without the overhead of WPF's retained-mode graphics system. By hosting a native HWND via HwndHost in TerminalContainer.cs, the application leverages DirectWrite and Atlas rendering engines (src/renderer/base/renderer.cpp) that draw directly to the HWND surface, achieving the necessary throughput for terminal workloads while the WPF layer handles chrome, theming, and window management.
How does the terminal core notify the WPF UI of scroll position changes?
When the viewport changes due to new output or user scrolling, the core invokes the scroll callback registered via TerminalRegisterScrollCallback during initialization in BuildWindowCore. This callback maps to OnScroll in TerminalContainer.cs, which raises the TerminalScrolled event. The TerminalControl subscribes to this event and updates the WPF scrollbar's position and extent based on the viewTop, viewHeight, and bufferSize parameters passed through the event args.
What happens when the terminal window is resized?
Resize events originate in WPF's OnRenderSizeChanged handler in TerminalControl.xaml.cs, which calls termContainer.Resize. This invokes TerminalTriggerResize or TerminalCalculateResize in HwndTerminal.cpp, depending on the AutoResize setting. The native method calls UserResize on the core Terminal instance, which recalculates visible rows and columns, updates the text buffer dimensions, and triggers a redraw via the Renderer. The new dimensions are then propagated back to the PTY connection to ensure the shell receives a SIGWINCH equivalent notification.
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 →