Skip to content

Add native tmux control mode and HTM integration - #20639

Open
Jason Gauci (MisterTea) wants to merge 6 commits into
microsoft:mainfrom
MisterTea:htm-integration
Open

Add native tmux control mode and HTM integration#20639
Jason Gauci (MisterTea) wants to merge 6 commits into
microsoft:mainfrom
MisterTea:htm-integration

Conversation

@MisterTea

@MisterTea Jason Gauci (MisterTea) commented Sep 3, 2026

Copy link
Copy Markdown

Hey folks! I'm the creator & maintainer of Eternal Terminal, and I'm working on getting all terminal emulators to support the tmux -CC protocol, as part of releasing our own tmux -CC alternative, HTM. HTM uses the same protocol as tmux -CC but it's built around control mode and has a rigorous battery of tests involving >5 terminal emulators.

Summary

This change adds native tmux control mode (tmux -CC) integration to Windows Terminal and uses it to support HTM as a terminal multiplexer.

It:

  • recognizes the tmux -CC DCS/ST lifecycle emitted by htm;
  • parses tmux control-mode reply blocks and asynchronous notifications;
  • maps tmux session/window/pane IDs to Windows Terminal windows, tabs, and panes;
  • routes pane output, keyboard input, resize, split, new-tab, close, detach, and shutdown operations through the control-mode connection;
  • synchronizes pane assignment across the ConPTY output and UI threads to avoid races during asynchronous layout updates;
  • adds protocol-focused unit coverage and an HTM integration specification;
  • keeps the integration behind Feature_HtmIntegration (enabled in Dev builds, disabled for Release and WindowsInbox).

Why tmux -CC

Normal tmux renders a complete terminal UI inside one PTY. In control mode, tmux instead exposes a line-oriented protocol intended for terminal emulators. The emulator sends ordinary tmux commands such as split-window, new-window, send-keys, and refresh-client; tmux returns command transactions delimited by %begin and %end/%error, and publishes asynchronous events such as %output, %window-add, and %layout-change.

With -CC, tmux also wraps the control session in terminal-facing DCS/ST markers. That lets Windows Terminal detect that the foreground process has entered control mode, stop treating its output as a single rendered terminal surface, and represent the remote tmux windows and panes using native Windows Terminal tabs and splits.

The protocol retains tmux's native identity model:

  • $session identifies a session;
  • @window identifies a window, represented as a Windows Terminal tab;
  • %pane identifies a pane, represented as a Windows Terminal pane.

This avoids inventing a Windows-Terminal-specific multiplexer API and makes the implementation applicable to compatible control-mode servers.

HTM compatibility

HTM is a headless multiplexer from EternalTerminal. Its current protocol deliberately speaks tmux control mode on the terminal PTY rather than a private framed protocol. The companion EternalTerminal work was merged in MisterTea/EternalTerminal#806.

The authoritative HTM integration references are:

HTM implements the subset needed by graphical terminal clients, including command/reply transactions, pane output, layout snapshots and changes, client sizing, and pause/resume flow control. It advertises a compatible tmux version so existing GUI-client behavior can be reused.

Implementation notes

A leader connection initially wraps the normal ConPTY connection. On detecting the ESC P 1000 p control-mode marker, Windows Terminal promotes the connection into an HTM session. Follower connections then back native panes without launching redundant local shells.

The parser handles control markers and protocol lines across arbitrary read boundaries, tmux octal escaping, reply correlation, pane output routing, and checksummed tmux layout trees. UI actions are translated back to tmux-compatible commands. Shared leader/follower state is synchronized because ConPTY output callbacks and XAML action handlers run on different threads.

Non-HTM profiles continue to use the existing ConPTY path.

Related requests

This implements the long-standing request in microsoft/terminal#3656 — Add support for tmux Control Mode, which grew out of the tmux and pane discussions in #532 and #1000. It also addresses the use case raised again in discussion #15613, which was closed as a duplicate of #3656.

Demo videos

Windows Terminal HTM end-to-end recordings from the EternalTerminal GUI driver (layout, stress, corners). Hosted as GitHub attachments (not in the Terminal tree).

Layout

windows-terminal-htm-layout-win01.mp4

Stress

windows-terminal-htm-stress-win01.mp4

Corners (native window 1)

windows-terminal-htm-corners-win01.mp4

Corners (native window 2)

windows-terminal-htm-corners-win02.mp4

Testing

  • Added HtmProtocolTests.cpp coverage for protocol parsing, escaping, control markers, replies, output, layouts, and related helpers.
  • Added a live concurrent HTM/htmd stress path covering attachment, refresh-client, splits, queries, output routing, and clean shutdown.
  • Validated the integration against the EternalTerminal Windows Terminal end-to-end driver; the companion EternalTerminal PR reports the full Windows test verification.

Windows Terminal has no Hyper-style plugin API, so JSON fragments cannot
intercept splits, new tabs, or pane close. Wrap every ConPTY in
HtmLeaderConnection (the DebugTapConnection pattern) so typing `htm` in
an existing profile can steal that PTY, apply INIT_STATE as follower
panes with no local process, and map WT split/new-tab/close onto htmd.

The wire protocol is unchanged from EternalTerminal and hyper-htm
(ESC[###q / ESC[$$$q, 1-byte header + 8-char b64 length, SESSION_END is
one byte). Feature_HtmIntegration is AlwaysEnabled in Dev and disabled
for Release and WindowsInbox so the diff stays reviewable upstream.

Optional profile env HTM_BIN_DIR is prepended to PATH so a local
htm.exe/htmd.exe build is found without changing the default commandline.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace the legacy framed HTM protocol handling with tmux -CC control-mode parsing, command replies, pane output routing, and DCS lifecycle handling.

Route Windows Terminal split, tab, resize, keyboard, detach, and shutdown actions through HTM followers while preserving normal ConPTY behavior for non-HTM profiles. Synchronize pending follower assignment so asynchronous pane notifications cannot race tab creation.

Add protocol helper coverage and a live concurrent htm/htmd stress test that exercises refresh-client, split-window, display-message, and clean server shutdown.
Synchronize leader and follower state shared between ConPTY output and UI action threads. Resolve new-window followers from the authoritative single-pane layout notification when needed, and route session-scoped new-tab actions without requiring a transient focused pane ID. This prevents split and tab actions from intermittently falling through or missing their initial resize during tmux control-mode operation.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check-spelling found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@github-actions

This comment has been minimized.

@MisterTea

Copy link
Copy Markdown
Author

Jason Gauci (Jason Gauci (@MisterTea)) please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"

Contributor License Agreement

@microsoft-github-policy-service agree [company="default"]

@MisterTea

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Fixes check-spelling failures on PR 20639:
- HtmProtocol.h Base64Decode valb renamed to valueBits
- Add htmd, htmtst, Toolhelp, SNAPPROCESS, PROCESSENTRY, wcsicmp,
  wdupenv, daemonizes to expected spelling dictionary
@MisterTea

Copy link
Copy Markdown
Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Commenter does not have sufficient privileges for PR 20639 in repo microsoft/terminal

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is curious how you have made line ending changes to only certain lines. It appears you have not run the code formatter.

Did you consult the existing open pull request that adds support for this feature to glean any information from it?

Do you have a video of it working properly?

@microsoft-github-policy-service microsoft-github-policy-service Bot added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Sep 3, 2026
@MisterTea

Copy link
Copy Markdown
Author

It is curious how you have made line ending changes to only certain lines. It appears you have not run the code formatter.

Did you consult the existing open pull request that adds support for this feature to glean any information from it?

Do you have a video of it working properly?

I have end to end tests in the eternal terminal repo that test all of the features and also do stress-tests (e.g. spamming input & output across several panes/tabs simultaneously). This is how I was able to fix the issues in the Wezterm tmux implementation. I'll run the code formatter and post a video from my desktop ASAP.

@microsoft-github-policy-service microsoft-github-policy-service Bot added Needs-Attention The core contributors need to come back around and look at this ASAP. and removed Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something labels Sep 3, 2026
@zadjii-msft

Copy link
Copy Markdown
Member

Jason Gauci (@MisterTea) What's different about your approach here, from the already open #18928?

Close native HTM panes via ForceCloseUi plus hosting-page teardown, debounce resize storms, map new-tab/new-window correctly, fix UTF-16 surrogate input, and always tear down HTM followers even when closeOnExit is never.

Co-authored-by: Cursor <cursoragent@cursor.com>
{
if (_htmMode)
{
// Stateful conversion: KEYEVENTF_UNICODE may deliver one surrogate
if (_htmMode)
{
// Stateful conversion: KEYEVENTF_UNICODE may deliver one surrogate
// per WriteInput; til::u16u8 without state would emit CESU-8.
{
return;
}
// Stateful conversion: KEYEVENTF_UNICODE may deliver one surrogate
return;
}
// Stateful conversion: KEYEVENTF_UNICODE may deliver one surrogate
// per WriteInput; til::u16u8 without state would emit CESU-8.
std::string _paneId;
std::mutex _writeMutex;
bool _closed{ false };
// SendInput KEYEVENTF_UNICODE delivers one UTF-16 code unit per call;
bool _suppressClosePacket{ false };
bool _closed{ false };
std::string _pendingOutput;
// SendInput KEYEVENTF_UNICODE delivers one UTF-16 code unit per call;
// bytes after DCS are ordinary newline-delimited tmux control records.
inline constexpr std::string_view TmuxControlDcs{ "\x1bP1000p" };
inline constexpr std::string_view TmuxControlSt{ "\x1b\\" };
// iTerm2's tmux -CC gateway banner. WezTerm prints the same text.
}
}

// KEYEVENTF_UNICODE may deliver one UTF-16 code unit per win32-input-mode
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, 👼 SARIF report, or 📝 job summary for details.

Unrecognized words (3)

CESU
KEYEVENTF
Wez

These words are not needed and should be removed ADDREF

To accept these unrecognized words as correct and remove the previously acknowledged and now absent words, you could run the following commands

... in a clone of the git@github.com:MisterTea/terminal.git repository
on the htm-integration branch (ℹ️ how do I use this?):

curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c/apply.pl' |
perl - 'https://github.com/microsoft/terminal/actions/runs/33892985100/attempts/1' &&
git commit -m 'Update check-spelling metadata'
Available 📚 dictionaries could cover words (expected and unrecognized) not in the 📘 dictionary

This includes both expected items (2035) from .github/actions/spelling/expect/alphabet.txt .github/actions/spelling/expect/expect.txt .github/actions/spelling/expect/web.txt and unrecognized words (3)

Dictionary Entries Covers Uniquely
cspell:cpp/src/lang-keywords.txt 44 3 3
cspell:csharp/csharp.txt 32 2 2
cspell:cpp/src/compiler-clang-attributes.txt 46 2 2
cspell:aws/aws.txt 218 2 2
cspell:python/src/python/python.txt 392 2 2

Consider adding to the extra_dictionaries array (in the .github/actions/spelling/config.json file):

    "cspell:cpp/src/lang-keywords.txt",
    "cspell:csharp/csharp.txt",
    "cspell:cpp/src/compiler-clang-attributes.txt",
    "cspell:aws/aws.txt",
    "cspell:python/src/python/python.txt",

To stop checking additional dictionaries, put (in the .github/actions/spelling/config.json file):

"check_extra_dictionaries": []
✏️ Contributor please read this

By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.

If the listed items are:

  • ... misspelled, then please correct them instead of using the command.
  • ... names, please add them to .github/actions/spelling/allow/names.txt.
  • ... APIs, you can add them to a file in .github/actions/spelling/allow/.
  • ... just things you're using, please add them to an appropriate file in .github/actions/spelling/expect/.
  • ... tokens you only need in one place and shouldn't generally be used, you can add an item in an appropriate file in .github/actions/spelling/patterns/.

See the README.md in each directory for more information.

🔬 You can test your commits without appending to a PR by creating a new branch with that extra change and pushing it to your fork. The check-spelling action will run in response to your push -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. 😉

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

@MisterTea

Jason Gauci (MisterTea) commented Sep 4, 2026

Copy link
Copy Markdown
Author

Dustin L. Howett (@DHowett) Posted the videos

Mike Griese (@zadjii-msft) To be honest, I didn't know there was a parallel effort so I can't comment on the differences. What I can say is that the wezterm tmux implementation had many issues, because multiplexing is fraught with race conditions and other challenges (see wezterm/wezterm#8125 ). My approach was to take iterm2 + tmux -CC as ground truth, create a ton of tests, record the console and mp4 videos of the windows for each tests, then use this ground truth to validate the other terminals. This allowed me to make https://github.com/MisterTea/hyper-htm robust enough that I could suggest people to use it, and allowed me to make this PR as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs-Attention The core contributors need to come back around and look at this ASAP.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants