Skip to content

feat: add asciicast session recording and playback (#469)#19960

Open
SemperFu wants to merge 7 commits intomicrosoft:mainfrom
SemperFu:feature/asciicast
Open

feat: add asciicast session recording and playback (#469)#19960
SemperFu wants to merge 7 commits intomicrosoft:mainfrom
SemperFu:feature/asciicast

Conversation

@SemperFu
Copy link

Hey! I've wanted built-in terminal recording for a while and wasn't happy with the existing options, so I built this for my own use. It's been working well, and since #469 has been open for years I figured I'd contribute it upstream. I'm sure it's not exactly how people want it but, just wanted to get the ball rolling. Happy to iterate on feedback

Summary of the Pull Request

Adds built-in terminal session recording and playback using the asciicast format. Records in asciicast v3 (interval-based timing) and plays back both v2 and v3 files, compatible with the asciinema ecosystem (asciinema play, agg, asciinema.org).

Watch an interactive demo of a WT recorded .cast file via asciinema-player.

Demo.mp4

Screen recording of the full workflow: starting a recording, saving, and playing back a cast file. (2x speed)

References and Relevant Issues

Detailed Description of the Pull Request / Additional comments

Recording

Right-click a tab > "Record session" (or Ctrl+Shift+R) to open a Save As dialog with a timestamped default filename. A red dot appears in the tab header while recording. The current prompt line is captured with VT attributes as the first event so recordings don't start blank. All subsequent terminal output is written as timestamped VT data. Toggle the shortcut or menu item again to stop.

Recording works with any shell or connection type (PowerShell, cmd, WSL, SSH) since it captures raw VT output from the TerminalOutput event.

Playback

Open cast file from the command palette or Ctrl+Shift+O. The selected .cast file replays in a new tab with original timing. After playback, the tab shows "Playback complete. Press any key to close." Auto-detects v2 vs v3 from the header.

UI integration

Feature Details
Ctrl+Shift+R Toggle recording (Save dialog on start)
Ctrl+Shift+O Open cast file for playback
Tab context menu "Record session" / "Stop recording"
Command palette Both actions listed
Tab header Red dot indicator during recording

Architecture

AsciicastRecorder (plain C++ class):

  • Subscribes to ITerminalConnection::TerminalOutput to capture raw VT data
  • Writes asciicast v3 JSON (header + interval-timed events + exit event)
  • Thread-safe via std::mutex; managed by ControlCore

AsciicastConnection (new ITerminalConnection implementation):

  • Parses .cast files, auto-detects v2 vs v3 from header
  • Replays output events as a fire_and_forget coroutine with timed delays
  • Skips # comment lines (v3 feature)

New files

Validation Steps Performed

  • Deployed CascadiaPackage, recorded sessions with PowerShell
  • Confirmed recording works across shells (PowerShell, cmd, WSL)
  • Verified prompt line appears with colors at start of recording
  • Verified playback timing matches original session
  • Verified tab header indicator and context menu toggle correctly
  • Verified playback completion message appears cleanly after last event
  • Verified v3 .cast files play correctly in asciinema-player 3.15.1 (web)
  • Verified v2 backward compatibility (plays v2 files from asciinema.org)
  • Generated GIF from recorded .cast file using agg with Campbell theme

PR Checklist

@microsoft-github-policy-service microsoft-github-policy-service bot added Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Area-Extensibility A feature that would ideally be fulfilled by us having an extension model. Area-Output Related to output processing (inserting text into buffer, retrieving buffer text, etc.) Product-Terminal The new Windows Terminal. labels Mar 11, 2026
@SemperFu
Copy link
Author

@microsoft-github-policy-service agree

SemperFu pushed a commit to SemperFu/terminal that referenced this pull request Mar 11, 2026
Add 4 GitHub Actions workflows for automated building and publishing:
- sync-upstream.yml: Polls MS releases every 6hrs, syncs fork, rebases feature branch
- build.yml: Builds x64 + ARM64 unpackaged zip distributions
- release.yml: Creates GitHub Releases with versioned artifacts
- winget-publish.yml: Auto-publishes SemperFu.TerminalCast to winget

These live on main (required for scheduled/release triggers) and don't
affect PR microsoft#19960 since that PR is from feature/asciicast.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@SemperFu
Copy link
Author

SemperFu commented Mar 15, 2026

Switched the recorder to a dedicated writer thread so the rendering thread doesn't stall during recording.
Added resize ("r") and marker ("m") events per the v3 spec, as well as terminal type, theme, and title.
The player reads idle_time_limit now and does error diffusion timing to avoid drift on long recordings.
Also grouped the record/open actions into a "Recording" submenu in the tab context menu.
Auto-resize only works when the playback tab is the only tab in the window. Going to try and have playback open a new window which may work better anyways.

image

@SemperFu
Copy link
Author

Playback now opens in a dedicated window instead of a tab in the current window. This fixes the auto-resize issue. There is a setting "autoResizeForPlayback" to use new window or tab. Made OpenCastFile a serializable action with a path parameter, which enables future command-line support and file association.

Added a video-player-style transport bar that appears at the bottom of the terminal during cast file playback. Supports play/pause, restart, seek via timeline slider, and speed control (0.5x/1x/2x/4x). The bar auto-hides and reappears on mouse hover and can restart after playback finishes.

DemoNew.mp4

Tried focus mode for the playback window to hide tabs and titlebar, but you can't move the window. also tried hiding just the tab row, but tabs-in-titlebar mode doesn't render a separate title, so the titlebar was blank. These need more thought, possibly a custom playback window mode?

Blank titlebar
TitleBar

Good stopping point for now

SemperFu and others added 4 commits March 15, 2026 21:44
- Green play icon in tab header for playback tabs
- Renamed Asciicast submenu to Recording in context menu
- Added autoResizeForPlayback global setting (default: false)
- Parse recording dimensions from cast file header
- Auto-resize on playback to match recording size (WIP, only works when tab is the only tab)
- Replaced mutex recording with lock-free til::spsc channel and writer thread to prevent rendering stalls during recording
- Added OpenCastFileArgs with String Path for serializable action
- File picker opens in current window, playback launches in new window
- New window has 1 tab + 1 pane so XTWINOPS auto-resize works
- Handler skips file picker when path is provided (for deserialization)
- Fixed spell check issues (nonexistent, unicode escapes, expect list)
- Playback engine: pause/resume, seek, speed control, position tracking
- PlaybackBar UserControl with hover show/hide at bottom of terminal
- Timeline slider, play/pause, restart, speed dropdown, time display
- Bar auto-hides and reappears on mouse hover
@SemperFu SemperFu force-pushed the feature/asciicast branch from 08dfac7 to 7f37240 Compare March 16, 2026 01:45
  - Added a generation counter so restarting playback doesn't leave a stale coroutine running alongside the new one
  - Switched the replay loop from for to while to fix an unsigned underflow when seeking to position 0
  - Replaced std::stod for speed parsing with direct string matching since it breaks on non-English locales
  - Added missing _writeError check to WriteInitialSnapshot to match the other write methods
  - Capped the yield spin in StopRecording so it doesn't burn CPU forever on a busy system
  - Documented what the three hardcoded delays are actually waiting for
  - Fixed temp file leak in the test helper when ofstream fails to open
…the terminal:

  - Screen wasn't being cleared before replaying, so restarting drew on top of old content
  - After a seek, the loop re-emitted the event that _emitAllOutputUpTo already played
  - Seeking to a point with a different terminal size ignored resize events — only applied the header dimensions. Now it finds the last resize event before the seek position and applies that size
@github-actions

This comment has been minimized.

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

Labels

Area-Extensibility A feature that would ideally be fulfilled by us having an extension model. Area-Output Related to output processing (inserting text into buffer, retrieving buffer text, etc.) Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Product-Terminal The new Windows Terminal.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Recording

1 participant