microsoft/terminal
github.com/microsoft/terminal ↗ · 2428 files · 23.2 MB
Welcome to the Windows Terminal, Console and Command-Line repo Table of Contents - Installing and running Windows Terminal - [Microsoft Store \[Recommended\]](#microsoft-store-recommended) - Other install methods - Via GitHub - Via Windows Package Manager CLI
Overview
This is the microsoft/terminal monorepo: it builds the Windows Terminal application (a WinUI 3 / C++/WinRT tabbed terminal emulator), the classic Windows console host (conhost.exe), and the shared components between them (VT parser, text buffer, renderers, types). Windows Terminal spawns client command-line apps through ConPTY pseudoconsoles, parses their VT output through a state machine into a text buffer, and renders with a Direct2D/Direct3D 'Atlas' engine. Distinctive aspects include a single-instance 'WindowEmperor' process model with WM_COPYDATA command-line handoff, a heavily layered JSON settings model with dynamic profile generators and third-party fragments, and an X-macro-driven action/keybinding system.
System map
The repo is one MSBuild workspace (OpenConsole.slnx) producing several binaries: WindowsTerminal.exe (the packaged terminal app, entrypoint in src/cascadia/WindowsTerminal/main.cpp), conhost.exe/OpenConsole.exe (src/host/exe), a wt.exe CLI shim, a shell extension, and many DLL/LIB components under src/cascadia and src/terminal. The terminal app is a Win32 process hosting WinUI 3 XAML islands; a single-instance 'WindowEmperor' owns all windows. Terminal functionality is layered: TerminalConnection (process/pipe IO) -> TerminalControl/ControlCore (XAML + renderer glue) -> TerminalCore (buffer + VT state machine) -> renderer/atlas (D3D text rendering). The conhost side lives under src/host and src/server and talks to the ConDrv driver. Build is MSBuild via tools/razzle.cmd + bcz.cmd or the OpenConsole.psm1 module; CI is Azure Pipelines (build/pipelines), with GitHub Actions only for release publishing and spell-checking.
- The repo builds three products: Windows Terminal, Windows Terminal Preview, and the Windows console host (conhost.exe), plus shared components, ColorTool, and Console API samples.
- WindowsTerminal.exe entrypoint is wWinMain in src/cascadia/WindowsTerminal/main.cpp: it registers the 'Microsoft.Windows.Terminal.Win32Host' TraceLogging provider, resets Ctrl handlers, calls EnsureNativeArchitecture() to block wrong-arch launches, enables WM_POINTER, initializes the thread as STA, then constructs a WindowEmperor and calls HandleCommandlineArgs(nCmdShow).
- WindowEmperor is the single-instance owner of all terminal windows: it builds a branding/elevation/SID-scoped window class name (e.g. 'Windows Terminal Admin <hash> <hash>'), acquires a named mutex or hands the serialized command line to the existing instance via WM_COPYDATA, then runs the process-wide GetMessageW loop that pumps every AppHost window.
- Each visible window is an AppHost wrapping a XAML-island HWND (IslandWindow/NonClientIslandWindow); windows are created by WindowEmperor::CreateNewWindow which constructs AppHost with WindowRequestedArgs and tracks them in _windows.
- The terminal app's code is split into projects under src/cascadia: TerminalApp (window/page/tab/pane UI + actions), TerminalControl (XAML control + ControlCore), TerminalCore (UI-agnostic terminal: buffer, VT parser wiring), TerminalSettingsModel (settings.json model), TerminalConnection (ConPTY/Azure/Echo connection types), TerminalSettingsEditor (Settings UI), plus helpers (ElevateShim, ShellExtension, wt shim, WpfTerminalControl).
- Client processes are spawned through ConPTY: ConptyConnection::Start calls ConptyCreatePseudoConsole (statically linked from src/inc/conpty-static.h / src/winconpty) then _LaunchAttachedClient uses CreateProcessW with PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE.
- conhost.exe is built from src/host/exe (Host.EXE.vcxproj) with wWinMain in exemain.cpp; it registers COM out-of-proc class objects (including CConsoleHandoff for incoming ConPTY handoff), honors the HKCU\Console ForceV2 registry value for the legacy console, and initializes the server side under src/server (ConDrvDeviceComm, IoDispatchers, ApiDispatchers).
- wt.exe is a ~40-line shim (src/cascadia/wt/shim.cpp) that replaces its own filename with WindowsTerminal.exe in the same directory and CreateProcessW's it, prepending its own name (wt/wtd) to the command line.
- Packaging is MSIX via src/cascadia/CascadiaPackage/CascadiaPackage.wapproj with four branding manifests (Package.appxmanifest, Package-Pre.appxmanifest, Package-Can.appxmanifest, Package-Dev.appxmanifest) selected by WT_BRANDING_* macros.
- Third-party dependencies are managed by vcpkg (vcpkg.json): fmt 12.1.0, ms-gsl 4.2.2, jsoncpp 1.9.6, cli11 2.6.1, cmark 0.31.1, with overlay ports/triplets under dep/vcpkg-overlay-*.
- The master build is OpenConsole.slnx (XML solution listing ~all vcxproj/csproj/wapproj with AuditMode/Debug/Fuzzing/Release configs on x86/x64/arm64); conhost.slnf is a filtered solution for conhost-only work, and Scratch.sln exists for experiments.
- Developer builds run through tools/: razzle.cmd sets up the environment, bcz.cmd/bx.cmd wrap MSBuild Clean;Build, and tools/OpenConsole.psm1 provides Invoke-OpenConsoleBuild/Set-MsBuildDevEnvironment; tests run via tools/runut.cmd, runft.cmd, runuia.cmd.
Feature catalog
Windows Terminal's user-facing surface is a tabbed, split-pane terminal with profiles, color schemes, themes, keybindings ('actions'), a command palette, search, a GUI settings editor, a wt command line, session persistence, quake-mode summon, global hotkeys, and deep shell/OS integration (default-terminal handoff, 'Open in Terminal' explorer extension, jump lists, elevated tabs). The settings model supports dynamic profile generators (WSL, PowerShell, Azure Cloud Shell, Visual Studio, SSH hosts) and third-party JSON fragments/app-extensions. VT features include Sixel graphics, scroll marks, and custom HLSL pixel shaders in the Atlas renderer.
- The wt CLI supports subcommands parsed with CLI11 in AppCommandlineArgs: new-tab/nt, split-pane/sp (-H/-V/--size/-D), move-pane/mp, focus-tab, and more; an implicit 'new-tab' parse is attempted when no subcommand is given.
- There are ~70 bindable 'shortcut actions' (CopyText, PasteText, NewTab, SplitPane, SendInput, ScrollToMark, ToggleShaderEffects, ToggleFocusMode, ...) enumerated by the ALL_SHORTCUT_ACTIONS X-macro and dispatched by ShortcutActionDispatch::DoAction which raises a per-action event.
- Settings live in settings.json layered over inbox defaults.json: keys include profiles/defaults+list, schemes, themes (builtin names system/light/dark plus legacy* variants), actions, and newTabMenu; DEFAULT_WINDOWS_POWERSHELL_GUID and DEFAULT_COMMAND_PROMPT_GUID are hardcoded defaults.
- Dynamic profile generators ship for WSL distros, PowerShell Core, Azure Cloud Shell, Visual Studio developer shells, and SSH hosts; they run during settings load via SettingsLoader::GenerateProfiles().
- Third-party apps can inject settings 'fragments' by dropping JSON under '...\Microsoft\Windows Terminal\Fragments' in a package's LocalState or by registering a 'com.microsoft.windows.terminal.settings' app extension; FindFragmentsAndMergeIntoUserSettings merges them during load.
- Windows Terminal can be registered as the default terminal: CTerminalHandoff registers a COM local server to receive PTY handles from the OS, and ConptyConnection::StartInboundListener/NewConnection feed inbound connections into a new tab (TerminalPage::CreateTabFromConnection with Elevate forced false).
- Connection types are pluggable: ConptyConnection (local shells), AzureConnection (Azure Cloud Shell), EchoConnection (loopback test), and DebugTapConnection (tap another connection's output); TerminalPage::_CreateConnectionFromSettings picks by profile.ConnectionType().
- Tab/pane UX includes: a customizable new-tab dropdown built from NewTabMenuEntry types (Profile, Folder, Separator, RemainingProfiles, MatchProfiles, Action), a fuzzy-filtered Command Palette (CommandPalette.cpp + vendored fzf in src/cascadia/fzf), search box, and a tab switcher.
- A full GUI settings editor lives in src/cascadia/TerminalSettingsEditor (XAML pages for profiles, color schemes, actions/keybindings, appearance, extensions, compatibility) and can also launch settings.json in the user's editor; Jumplist.cpp builds taskbar jump-list profiles.
- Window/session features: persisted window layouts restored at launch (ApplicationState::SharedInstance().PersistedWindowLayouts -> synthetic 'wt -w new -s <i>' dispatches), named windows ('wt -w <name>'), quake-style summon/dropdown, and global hotkeys registered from ActionMap::GlobalHotkeys.
- Elevation is handled by spawning an elevated sibling: ElevateShim/elevate-shim.cpp performs the ShellExecute runas-style launch, and elevated instances are isolated by the ' Admin' suffix on the window class name/mutex.
- Shell/OS integrations include the 'Open in Terminal' Explorer context-menu extension (ShellExtension/OpenTerminalHere.cpp), taskbar AUMID management with deferred .lnk stamping for pinned icons (GH#20053 workaround in _setupAumid), and a notification-area icon for hidden/quake windows.
Data flow
The core data path is bidirectional ConPTY pipes. Outbound: keystrokes travel XAML -> TermControl/ControlCore -> ITerminalConnection::WriteInput (UTF-16 to UTF-8, overlapped WriteFile on the ConPTY input pipe). Inbound: ConptyConnection's dedicated _OutputThread ReadFile's the output pipe and raises the TerminalOutput event; ControlCore::_connectionOutputHandler takes the terminal write lock and calls Terminal::Write -> StateMachine::ProcessString -> OutputStateMachineEngine -> ITermDispatch handlers that mutate TextBuffer rows; the Atlas renderer then paints the buffer. Settings flow from settings.json + inbox defaults.json + dynamic generators + fragments through SettingsLoader's layered ParsedSettings into CascadiaSettings, with a directory watcher triggering throttled reloads. Command lines flow through CLI11 parsing into ActionAndArgs vectors executed by ShortcutActionDispatch; between processes, whole invocations are serialized into WM_COPYDATA blobs.
- Input path: key presses reach the WindowEmperor message loop (which pre-filters F7, Alt keyup, and Alt+Space because XAML cannot suppress them), then XAML -> TermControl key handlers -> ControlCore::_sendInputToConnection -> ITerminalConnection::WriteInput.
- ConptyConnection::WriteInput serializes writers with a first-come-first-served ticket lock, drains any pending overlapped write via GetOverlappedResult, converts UTF-16 to UTF-8 into _writeBuffer, and issues an overlapped WriteFile on the ConPTY pipe; ERROR_BROKEN_PIPE tears the connection down.
- Output path: ConptyConnection::Start creates an overlapped 128KB pipe pair and ConPTY, then spawns 'ConptyConnection Output Thread' running _OutputThread which ReadFile's the pipe and raises the TerminalOutput event; ConPTY size/show/reparent changes go through ConptyResizePseudoConsole/ConptyShowHidePseudoConsole/ConptyReparentPseudoConsole.
- ControlCore::_connectionOutputHandler receives the connection's TerminalOutput string, takes _terminal->LockForWriting() (a til::recursive_ticket_lock in TerminalCore), calls Terminal::Write, then flushes any _pendingResponses (e.g. DA/DSR replies) back to the connection.
- Terminal::Write feeds the VT StateMachine::ProcessString; OutputStateMachineEngine translates C0/C1/ESC/CSI/OSC sequences into ITermDispatch calls (Print, PrintString, LineFeed, CursorBackward, DeviceAttributes...) that land on the TextBuffer (src/buffer/out/textBuffer.cpp) via the adapter layer.
- Rendering: ControlCore owns an AtlasEngine (Direct2D/DirectWrite text rasterization with optional D3D backend and custom shaders) created lazily in TermControl; EnablePainting/size changes flow TermControl::_InitializeTerminal -> _core.Initialize/SizeOrScaleChanged.
- Settings flow: CascadiaSettings::LoadAll reads settings.json via til::io::read_file_as_utf8_string_if_exists, falls back to a Release-channel copy on Preview/Canary first-run, seeds from embedded resources IDR_USER_DEFAULTS + IDR_DEFAULTS, then runs SettingsLoader steps in fixed order: GenerateProfiles -> ApplyRuntimeInitialSettings -> MergeInboxIntoUserSettings -> FindFragmentsAndMergeIntoUserSettings -> FinalizeLayering -> DisableDeletedProfiles/FixupUserSettings/AddDynamicProfileFolders.
- Settings location: GetBaseSettingsPath returns the packaged LocalState folder, or %LOCALAPPDATA%\Microsoft\Windows Terminal for unpackaged builds, or the exe directory when a '.portable' marker file exists (portable mode).
- Write-back: LoadAll only writes settings.json when it was a first-time setup or when fixups/new dynamic profiles require persistence; otherwise it just caches a hash of file content+timestamp used by AppLogic::_ProcessLazySettingsChanges to skip redundant Jumplist rebuilds.
- Command-line data flow: GetCommandLineW -> commandlineToArgArray -> CommandlineArgs -> AppCommandlineArgs (CLI11) producing std::vector<ActionAndArgs> -> TerminalPage::ProcessStartupActions which feeds each entry to _actionDispatch->DoAction, suspending to the dispatcher between panes/tabs so WinUI can lay out controls (GH#13136).
- Cross-process handoff serializes GetCommandLineW + full environment block + CWD + nCmdShow into a length-prefixed byte blob delivered by WM_COPYDATA with TERMINAL_HANDOFF_MAGIC ('TERMINAL'), retried with exponential backoff for ~30s until the owner finishes creating its message window.
- Environment synthesis for each session: ConptyConnection injects WT_SESSION (plain GUID) and WT_PROFILE_ID (braced GUID), merges user 'environment' overrides, and maintains WSLENV so those variables propagate into WSL; initial env comes from til::env::from_current_environment or a serialized block for handoffs.
Behavior traces
Three load-bearing flows were traced end-to-end. (1) Process startup: wWinMain -> WindowEmperor::HandleCommandlineArgs does mutex-or-WM_COPYDATA handoff, loads settings via AppLogic::ReloadSettings -> CascadiaSettings::LoadAll, restores persisted windows, dispatches the command line into AppHost windows, then pumps a single message loop. (2) New tab: an action (keybinding/menu/CLI) reaches ShortcutActionDispatch::DoAction -> TerminalPage handlers -> _MakePane -> _CreateConnectionFromSettings (ConptyConnection) -> TermControl::_InitializeTerminal -> connection.Start() -> ConPTY + CreateProcessW -> _OutputThread -> TerminalOutput -> Terminal::Write. (3) Settings persistence: a wil::FolderChangeReader on the settings directory triggers ReloadSettingsThrottled -> _TryLoadSettings -> SettingsChanged event, which WindowEmperor and TerminalPage subscribe to for hot-reload of hotkeys, themes, and profiles.
- Startup step 1 (pre-XAML): wWinMain registers TraceLogging, calls SetConsoleCtrlHandler(NULL,FALSE) to unbreak inherited ^C state, EnsureNativeArchitecture() to refuse wrong-arch binaries, EnableMouseInPointer(true), then winrt::init_apartment(single_threaded) — explicitly load-bearing for UIAutomation correctness per the comment.
- Startup step 2 (instance arbitration): WindowEmperor::HandleCommandlineArgs builds a branding+elevation+SID-scoped window class name, sets an AUMID for unpackaged runs (_setupAumid with deferred taskbar-pin stamping), then acquireMutexOrAttemptHandoff either wins the mutex or WM_COPYDATA's the serialized invocation to the running instance and TerminateProcess(0)s itself.
- Startup step 3 (app + settings): the emperor creates winrt::TerminalApp::App, calls App.Logic().ReloadSettings() (AppLogic::_TryLoadSettings -> CascadiaSettings::LoadAll -> SettingsLoader pipeline), pre-warms FOLDERID_LocalAppData to avoid an Nvidia loader-lock stall (GH#20348), creates the hidden message window, registers global hotkeys and notification icon, and subscribes to SettingsChanged.
- Startup step 4 (window materialization): persisted layouts are replayed as synthetic 'wt -w new -s <idx>' command lines; then the real argv is dispatched via _dispatchCommandlineCommon -> CreateNewWindow -> AppHost::Initialize; '-Embedding' mode instead arms a 5s handoff timeout; finally a single GetMessageW loop pumps all windows, then _finalizeSessionPersistence runs at exit.
- New-tab step 1 (invocation): keybindings go AppKeyBindings -> ActionMap lookup -> ShortcutActionDispatch::DoAction raising the NewTab event; the same dispatch is reused for command-line startup actions, the new-tab flyout items (which embed NewTabArgs with a profile index), and command palette entries.
- New-tab step 2 (pane + connection): the NewTab handler builds a pane via _MakePane -> _CreateConnectionFromSettings, which selects ConptyConnection or AzureConnection by profile.ConnectionType(), resolves StartingDirectory against the virtual CWD (for 'wt -w 0 -d .' correctness), packs a ValueSet (commandline, dirs, rows/cols, sessionId, textMeasurement, ambiguousIsWide, inheritCursor, environment) and calls connection.Initialize.
- New-tab step 3 (terminal bring-up): TermControl::_InitializeTerminal waits for a nonzero SwapChainPanel size, calls ControlCore::Initialize (creates Terminal + AtlasEngine), then _core.Connection().Start() — or _restoreInBackground for restored buffer contents — followed by EnablePainting, scrollbar setup, appearance application, and raising Initialized.
- New-tab step 4 (process + IO): ConptyConnection::Start transitions to Connecting, creates overlapped pipes, ConptyCreatePseudoConsole, _LaunchAttachedClient (CreateProcessW with PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, WSLENV-augmented env, WT_SESSION/WT_PROFILE_ID), ConptyReleasePseudoConsole, then spawns _OutputThread and transitions to Connected; failures emit localized ProcessFailedToLaunch/BadPathText/ElevationRequired text as TerminalOutput and transition to Failed.
- New-tab step 5 (steady state): _OutputThread pipe reads -> TerminalOutput event -> ControlCore::_connectionOutputHandler -> LockForWriting -> Terminal::Write -> StateMachine::ProcessString -> OutputStateMachineEngine Action* -> dispatch -> TextBuffer; scrollback/viewport changes invalidate the AtlasEngine for the next frame.
- Settings-reload trace: AppLogic::_RegisterSettingsChange creates a wil::FolderChangeReader on the settings directory watching FileName+LastWriteTime; when settings.json changes, ReloadSettingsThrottled debounces 100ms then _TryLoadSettings -> CascadiaSettings::LoadAll; success raises SettingsChanged (WindowEmperor re-registers global hotkeys/notification icon/persistence; TerminalPage re-binds keybindings), failure keeps the old settings and surfaces _settingsLoadExceptionText.
- First-run fixup trace: when settings.json is absent/empty, LoadAll resets ApplicationState, loads embedded IDR_USER_DEFAULTS, runs ApplyRuntimeInitialSettings + dynamic generators, and WriteSettingsToDisk persists the materialized file; Preview/Canary first try migrating the Release-channel settings.json instead.
- Teardown trace: ConptyConnection::Close sets state Closing, resets the HPCON (sends CTRL_CLOSE_EVENT to clients), then loops CancelIoEx + WaitForSingleObject(1000) until the output thread exits — required so TerminalOutput.raise can't hit a dead ControlCore (GH#13880); the code documents three termination 'EXIT POINT's that must not flap each other.
Testing signals
Tests are TAEF-based C++ unit tests (TEST_CLASS/TEST_METHOD) plus WexTaef te.exe for the settings model, feature tests, a fuzzing harness, and a small C# WinAppDriver UIA suite. tools/runut.cmd enumerates the unit test DLLs: Conhost.Unit.Tests, TextBuffer.Unit.Tests, Terminal.Core.Unit.Tests, ConParser/ConAdapter.Unit.Tests, Types, til.unit.tests, Terminal.App.Unit.Tests, Control.Unit.Tests, TerminalApp.LocalTests, and SettingsModel.Unit.Tests. CI (Azure Pipelines) gates on code formatting and runs tests per-platform. Coverage is strong for settings serialization, the parser/adapter, buffer, and control core; the Win32 windowing layer (WindowEmperor/AppHost/IslandWindow) and remoting have no dedicated unit tests.
- Unit tests run under TAEF via tools/runut.cmd, which invokes %TAEF% over ten test DLLs and then te.exe for SettingsModel.Unit.Tests; the comment says to keep the list in sync with tools/tests.xml.
- Settings model tests (src/cascadia/UnitTests_SettingsModel) cover deserialization, serialization round-trips, keybindings, profiles, color schemes, themes, new-tab menu, application state, and media resources — e.g. SerializationTests.cpp TEST_METHODs GlobalSettings, Profile, ColorScheme, Actions, CascadiaSettings, RoundtripReloadEnvVars.
- TerminalCore unit tests cover TerminalApiTest, selection, scrolling, screen-size limits, buffer tests, and input; Control tests cover ControlCore and ControlInteractivity with MockConnection/MockControlSettings fakes.
- Conhost-side tests: src/host/ut_host exercises screen buffer, input buffer, history, aliases, VtIo, console arguments; src/til/ut_til covers the til utility library; adapter/parser have ut_adapter and ConParser.Unit.Tests.
- TerminalApp has 'LocalTests' that run inside a TestHostApp cppwinrt harness (LocalTests_TerminalApp/TestHostApp) rather than the real packaged app, which is why AppLogic::Current()/CurrentAppSettings() have try/catch guards for the non-App case.
- UI automation tests exist as a C# WinAppDriver suite (src/cascadia/WindowsTerminal_UIATests, SmokeTests.cs, WindowsTerminal.UIA.Tests.csproj) run via tools/runuia.cmd; WinAppDriver itself is vendored under dep/WinAppDriver.
- Fuzzing is first-class: src/host/ft_fuzzer and src/terminal/parser/ft_fuzzer/ft_fuzzwrapper provide harnesses, 'Fuzzing' is a solution build type in OpenConsole.slnx, and build/pipelines/fuzz.yml + doc/fuzzing.md document the pipeline.
- Feature/integration tests live in ft_* dirs (src/host/ft_host, ft_integrity, ft_uia) run by tools/runft.cmd; additional manual test tools exist under src/tools (vttests, texttests, RenderingTests, echokey).
- CI runs a dedicated code-format job (templates-v2/job-check-code-format.yml) plus AuditMode and per-platform builds; doc/ changes are path-excluded from CI triggers.
- No unit test project exists for the WindowsTerminal.exe windowing layer (WindowEmperor, AppHost, IslandWindow, handoff serialization) — coverage there relies on the UIA smoke suite; this is an inference from the test-DLL list in runut.cmd. (inferred)
Risk map
The riskiest areas are cross-process trust boundaries and lifecycle races. The WM_COPYDATA handoff sends the entire environment block (potentially containing secrets) to an existing instance and parses a remote buffer — bounds-checked, but a real IPC attack surface. ConPTY spawn uses raw CreateProcessW with expanded environment and user-controlled profile commandlines. The defterm COM handoff accepts duplicated handles from other processes. The codebase repeatedly documents fragile threading invariants (STA-before-XAML, recursive ticket lock, output-thread join on Close, XAML islands quirks) — these are blast-radius hotspots. Telemetry (TraceLogging with Microsoft telemetry options) is pervasive and warrants privacy review on changes.
- WM_COPYDATA handoff serializes the caller's full environment block (GetEnvironmentStringsW) into the message to the existing instance — any spawned wt.exe inherits and forwards env secrets; the receiving side parses remote memory via deserializeHandoffPayload/deserializeString with bounds checks.
- Process launch surface: profile 'commandline' strings from settings.json go through wil::ExpandEnvironmentStringsW and CreateProcessW with a mutable buffer and custom environment block; startingDirectory is WSL-mangled. Command lines are attacker-influenceable if a profile/fragment is malicious.
- Settings fragments: JSON files under other packages' 'Microsoft\Windows Terminal\Fragments' folders and registered app extensions are merged into the user's settings (profiles, schemes) during every load — a content injection path gated only by package identity. (inferred)
- COM defterm handoff: CTerminalHandoff registers a CLSCTX_LOCAL_SERVER class object that accepts in/out/signal/reference/process handles from arbitrary callers to adopt a PTY — trust boundary across processes.
- Elevation boundary is only the window-class-name/mutex namespacing (' Admin' suffix) plus forcing Elevate(false) on defterm tabs; the ElevateShim does the actual elevated spawn. Mixing elevated and non-elevated commandlines in one instance would be a security bug, so the class-name isolation is load-bearing.
- Termination is documented as fragile: ConptyConnection has an 'EXIT POINT' protocol for the three ways a connection dies (Close, spawn failure, pipe EOF) to avoid reentrant state flapping; Close() busy-loops CancelIoEx/WaitForSingleObject to guarantee no async TerminalOutput.raise after ControlCore teardown (GH#13880).
- Threading invariants are subtle and enforced by comments, not types: STA must precede all XAML objects (main.cpp '!!! LOAD BEARING !!!'), TerminalCore guards the buffer with a til::recursive_ticket_lock (LockForWriting) with suspend support, and TermControl warns about deadlocking when calling into ControlCore from event handlers.
- The single message loop hand-patches XAML deficiencies: F7 caret-browsing, Alt-keyup, and Alt+Space are intercepted before TranslateMessage/DispatchMessage — behavioral coupling that could break on WinUI updates.
- Process exit after command-line handoff uses TerminateProcess(0) 'primarily to avoid WinUI shutdown issues' — abrupt termination means destructors/finalizers never run; anything added after the handoff point must tolerate that.
- Settings watcher auto-reloads on ANY rename/write of a file named settings.json in the watched dir; malformed JSON keeps old settings but posts errors, and the code warns against holding a static ApplicationState ref in the callback because teardown ordering would crash (a real past bug pattern).
- conhost still carries the legacy console path: HKCU\Console\ForceV2=0 reverts to v1 console, and interactivity/win32 maintains classic window/menu/clipboard code — a large legacy surface kept alive for inbox compatibility.
- Registry/file probing at startup includes scanning the user's pinned-taskbar .lnk files (IShellLink/IPersistFile) to decide AUMID stamping — benign but a privacy-sensitive file enumeration on every unpackaged launch.
Extension points
The codebase is built around explicit extension surfaces: the ALL_SHORTCUT_ACTIONS X-macro generates dispatch for every bindable action; ACTION_ARGS_STRUCT macros synthesize args classes with JSON keys; NewTabMenuEntry variants compose the new-tab menu; IDynamicProfileGenerator feeds discovered profiles; ITerminalConnection abstracts transports; IStateMachineEngine/ITermDispatch decouple the VT parser from its consumer; renderers are swappable behind the renderer/base interface; settings fragments + app extensions let third parties inject configuration; and features.xml adds staged feature flags. HwndTerminal/WpfTerminalControl expose the terminal as an embeddable control.
- Adding a new keybindable action means adding one line to ALL_SHORTCUT_ACTIONS in AllShortcutActions.h — the X-macro auto-generates the ShortcutAction enum entry, the ShortcutActionDispatch case that raises the event, and handler declarations consumed by TerminalPage/AppActionHandlers.
- Action argument types are generated by macro: ACTION_ARGS_STRUCT(MyArgs, MY_ARGS) plus a per-action args X-macro produces the class, its JSON keys, and (de)serialization hooks — documented inline in ActionArgsMagic.h including a godbolt -E tip for debugging expansion.
- The new-tab dropdown is data-driven from settings via NewTabMenuEntry variants — Profile, Folder, Separator, RemainingProfiles, MatchProfiles, Action — recursively materialized by _CreateNewTabFlyoutItems.
- New dynamic profile sources implement IDynamicProfileGenerator and are enumerated during CascadiaSettings::LoadAll -> GenerateProfiles; existing examples are WslDistroGenerator, PowershellCoreProfileGenerator, AzureCloudShellGenerator, VisualStudioGenerator, SshHostGenerator.
- Third-party settings injection: JSON 'fragments' dropped in '<LocalState>\Microsoft\Windows Terminal\Fragments' or delivered via the 'com.microsoft.windows.terminal.settings' app-extension host are merged by FindFragmentsAndMergeIntoUserSettings and surfaced as ExtensionPackages in the Settings UI.
- New connection transports implement ITerminalConnection (idl: Initialize(ValueSet), Start, WriteInput, Resize, Close; events TerminalOutput/StateChanged) — ConptyConnection, AzureConnection, EchoConnection are the built-ins, and _CreateConnectionFromSettings selects by profile.ConnectionType().
- The VT parser is engine-agnostic: StateMachine owns an IStateMachineEngine (OutputStateMachineEngine for output, InputStateMachineEngine for input), which forwards to an ITermDispatch/ITerminalApi implementation — letting conhost (AdaptDispatch) and TerminalCore share the same parser.
- Settings layering is tag-based: ParsedSettings entries carry an OriginTag (InBox, User, Generated, Dynamic, Fragment) and SettingsLoader merges them in a fixed order — new settings sources plug in as additional origins without changing the merge engine.
- New feature flags are added declaratively in src/features.xml with a name/description/stage and per-branding alwaysEnabled/alwaysDisabled tokens, then materialized as TIL_FEATURE_* macros by tools/Generate-FeatureStagingHeader.ps1 (schema: tools/FeatureStagingSchema.xsd).
- The terminal control is embeddable: HwndTerminal hosts it as a raw HWND and WpfTerminalControl wraps it for WPF apps — the intended surface for third-party embedding.
- wt subcommands extend through CLI11: _addNewTerminalArgs adds the shared option set to each subcommand and each subcommand registers a callback that pushes ActionAndArgs onto _startupActions — new subcommands follow the new-tab/split-pane pattern.
- Adding a new setting is a documented recipe (doc/AddASetting.md): model class + .idl property + JSON key in the serialization layer + defaults.json + Settings UI page — the specs folder contains design docs for past additions like marks, unfocused appearance, and quake mode.
Contributor handoff
This is a Windows-only C++/WinRT + WinUI 3 codebase; you cannot build or run it on Linux/macOS. Read AGENTS.md first: the project has a strict AI-usage policy that bans most AI-authored public contributions and Co-authored-by AI credits. Build with tools/razzle.cmd + bcz (or OpenConsole.psm1 Invoke-OpenConsoleBuild), test with tools/runut.cmd (TAEF). Conventions: wil RAII + THROW_IF_*/RETURN_IF_* macros, til:: types, fmt, .idl files generate .g.cpp via cppwinrt (never edit generated files), settings changes follow doc/AddASetting.md, new actions go through the ALL_SHORTCUT_ACTIONS X-macro. Key traps: STA must precede XAML creation, don't hold static refs to ApplicationState in callbacks, first tab must be created synchronously, and WindowsTerminal.exe can't just be double-clicked when packaged.
- AGENTS.md is an AI usage POLICY, not a build guide: it permits only small localized AI fixes and disclosed+maintainer-approved contributions, bans AI-authored public interactions and Co-authored-by AI credits, and instructs agents to STOP and explain the restriction if the work is prohibited. Read it before producing any PR-bound change.
- Build requires Windows with VS 2026 18.6+, Windows 11 SDK 10.0.26100.8249+, PowerShell 7+, .NET Framework 4.7.2 targeting pack (for test projects), Developer Mode enabled; quickest setup is `winget configure .config/configuration.winget`.
- Build commands: `Import-Module ./tools/OpenConsole.psm1; Set-MsBuildDevEnvironment; Invoke-OpenConsoleBuild` in PowerShell, or `tools\razzle.cmd` then `bcz` in cmd; bcz supports dbg/rel/no_clean/exclusive args. Debug via the CascadiaPackage project with 'Native Only' debugging, x64 or x86 (not Any CPU).
- Tests: tools/runut.cmd runs the TAEF unit suite (must match tools/tests.xml), tools/runft.cmd runs feature tests, tools/runuia.cmd runs the WinAppDriver UIA suite; repo convention is a ut_ folder per project and ft_ for functional tests (doc/ORGANIZATION.md).
- Coding conventions: wil::unique_* RAII wrappers and THROW_IF_WIN32_BOOL_FALSE/RETURN_IF_FAILED/CATCH_LOG/CATCH_RETURN error macros, til:: geometry/string types, fmt::format with FMT_COMPILE, gsl::narrow for casts, winrt namespaces with .idl-defined runtimeclasses; see doc/STYLE.md, doc/WIL.md, doc/EXCEPTIONS.md.
- cppwinrt generates *.g.cpp/*.g.h from .idl files at build time (cppwinrt.build.*.props) — edit the .idl, never the generated files; many projects pair a source dir with a dll/ subdir holding the .vcxproj and .def.
- settings.json lives at CascadiaSettings::SettingsPath(): packaged LocalState (8wekyb3d8bbwe), %LOCALAPPDATA%\Microsoft\Windows Terminal for unpackaged, or beside the exe when a '.portable' file exists; first-run on Preview/Canary migrates the Release channel's file.
- Known traps documented in-code: init_apartment(STA) must precede any Xaml object (main.cpp '!!! LOAD BEARING !!!'); the first tab must be created synchronously because WinUI won't size hidden content and ControlCore needs a size (TerminalPage ProcessStartupActions GH#13136); don't keep a static ref to ApplicationState in the folder-watcher callback or teardown crashes (AppLogic.cpp).
- You cannot launch a packaged build by running WindowsTerminal.exe directly (GH#926/GH#4043) — deploy/debug through CascadiaPackage; the wt.exe shim resolves WindowsTerminal.exe from its own directory.
- Files to read before editing a feature: src/cascadia/TerminalApp/TerminalPage.cpp (action handlers, tab/pane lifecycle), src/cascadia/TerminalSettingsModel/AllShortcutActions.h + ActionArgsMagic.h (action plumbing), src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp (settings layering), src/cascadia/TerminalControl/ControlCore.cpp + TermControl.cpp (control/renderer/connection glue), src/cascadia/WindowsTerminal/WindowEmperor.cpp (process model).
- Safe-edit boundaries: TerminalCore/parser/adapter changes are UI-agnostic and unit-testable via UnitTests_TerminalCore + ConParser/ConAdapter tests; settings-model changes should round-trip through UnitTests_SettingsModel; UI changes in TerminalApp/TerminalControl can't be validated without a Windows desktop — prefer unit-testable layers when possible. (inferred)
- Localization: user-facing strings go through Resources.resw per project and RS_/USES_RESOURCE/GetStringResource helpers; WindowsTerminal.exe borrows TerminalApp's resources via UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE("TerminalApp/Resources") marked '!! BODGY !!' — add new strings there, not in WindowsTerminal.
Languages
| Language | Files | Share of source |
|---|---|---|
| C++ | 677 | 56% |
| C | 429 | 35% |
| Markdown | 125 | docs/config |
| C# | 97 | 8% |
| JSON | 21 | docs/config |
| YAML | 9 | docs/config |
| Python | 5 | 0% |
| HTML | 2 | docs/config |
Start reading here
Probable source entrypoint: src/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
src/ (2002 files)
Repository structure
| Top-level path | Files |
|---|---|
src/ | 2002 |
doc/ | 101 |
scratch/ | 73 |
samples/ | 63 |
tools/ | 41 |
oss/ | 37 |
.github/ | 33 |
dep/ | 31 |
Docs found: AGENTS.mdCLAUDE.mdCONTRIBUTING.mdREADME.md
CI & config entrypoints
README.md— Repository readmeAGENTS.md— Agent instructionsCLAUDE.md— Claude instructions.github/workflows/addToProject.yml— GitHub Actions workflow.github/workflows/winget.yml— GitHub Actions workflow.github/workflows/spelling2.yml— GitHub Actions workflow
Structural health signals
Average score 7.8/10 across 96 analyzed source files; 18 hotspots surfaced.
src/cascadia/WindowsTerminal_UIATests/Common/NativeMethods.cs— 861 lines, score 5.5 (Large file, Deep nesting)src/host/ft_uia/Common/NativeMethods.cs— 861 lines, score 5.5 (Large file, Deep nesting)src/host/ft_uia/VirtualTerminalTests.cs— 775 lines, score 5.5 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
7c92ecd037472026-09-17 — Localization Updates - Settings Expanders (#20690)077f9cf5ecab2026-09-17 — build: add our ServiceTree ID to our loc submissions (#20695)9694946ae2242026-09-15 — build: use the latest compiler *of the same version* (#20689)c0c72e95f39e2026-09-15 — Update profile subpages (#20336)e4171cc2d8f02026-09-15 — chore: Update to CppWinRT 3.0.260818 (#20679)