Skip to content

Latest commit

 

History

51 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Devolutions Terminal

A cross-platform terminal emulator implemented in C# on .NET 10, published with NativeAOT, and rendered with Avalonia 12 / Skia:

  • ConPTY on Windows and a real forkpty transport on Linux and macOS
  • Azure Cloud Shell for remote Azure sessions
  • selectable built-in or Ghostty VT engine
  • Windows Terminal-compatible settings.json, actions, keybindings, and dt CLI

This is the Devolutions Terminal source tree. Projects, namespaces, and the GUI host use Devolutions.Terminal.*. The CLI executable is dt.

Build and run

dotnet test Devolutions.Terminal.slnx
dotnet run --project src/Devolutions.Terminal

On Windows, Shift-click a profile in the new-tab dropdown to open it in an elevated tab without elevating the Terminal window. Elevated tabs show a shield, including in compact view; Show admin shield in Settings controls the badge. Install gsudo and ensure gsudo.exe is on PATH before starting Terminal. To always elevate a profile, enable Run this profile as Administrator in its settings (elevate: true). The newTab/splitPane action's elevate argument can override that default. Elevation is only available for local Windows sessions; UAC approval is still required, and cancelling it does not launch an unelevated shell.

NativeAOT publish

dotnet publish src/Devolutions.Terminal -c Release -r win-x64 --self-contained

The native executable is written to src/Devolutions.Terminal/bin/Release/net10.0/win-x64/publish/Devolutions.Terminal.exe.

macOS NativeAOT app bundles (Darwin only):

pwsh scripts/Build-MacOsPackage.ps1 osx-arm64 2026.3.0 artifacts/packages
pwsh scripts/Test-MacOsPackage.ps1 osx-arm64 artifacts/packages/*.zip

Linux x64 and ARM64 NativeAOT packages are built on Linux with:

scripts/Build-LinuxPackage.sh linux-x64 2026.3.0 artifacts/packages all
scripts/Build-LinuxPackage.sh linux-arm64 2026.3.0 artifacts/packages all
bash scripts/Test-LinuxPackage.sh linux-x64 artifacts/packages/*-linux-x64.*

The builder emits .tar.gz, .deb, .rpm, .AppImage, and .sha256 files. Every format consumes the same normalized /opt/devolutions-terminal payload and /usr desktop integration layout. SOURCE_DATE_EPOCH controls package timestamps.

After extracting a tarball at the filesystem root:

sudo /opt/devolutions-terminal/linux/Install-LinuxDesktopIntegration.sh install
/opt/devolutions-terminal/linux/Install-LinuxDesktopIntegration.sh register-protocol
/opt/devolutions-terminal/linux/Install-LinuxDesktopIntegration.sh set-default-terminal

MSIX packages

.\src\Devolutions.Terminal.Package\Scripts\Build-Packages.ps1

Development signing, trust, install, validation, and uninstall commands are in src/Devolutions.Terminal.Package/README.md.

Settings and engines

Settings are stored at %LOCALAPPDATA%\Devolutions\Terminal\settings.json on Windows, under $XDG_CONFIG_HOME/devolutions-terminal on Linux (usual ~/.config fallback), and at ~/Library/Application Support/Devolutions/Terminal/ on macOS. Set WT_BASE_SETTINGS_PATH to use a directory for settings.json and state.json (same contract as Devolutions' Windows Terminal distribution). Set DTERM_SETTINGS_PATH (or WT_DOTNET_SETTINGS_PATH) to load a specific settings file. On Windows, WT_PARENT_WINDOW_HANDLE embeds the window as a child of that HWND. alwaysShowTabs: false hides the tab row when only one tab is open.

dt --isolated (or Devolutions.Terminal.exe --isolated) runs the launch in its own process: it neither forwards to a running instance nor becomes the broker for later launches, so every window keeps its own environment, WT_PARENT_WINDOW_HANDLE, and settings directory. -w targeting of other windows is unavailable, and persisted layouts are neither restored nor saved. It is the per-launch equivalent of Windows Terminal's compatibility.isolatedMode setting, which Windows Terminal 1.23 removed.

Set "experimental.terminalEngine": "ghostty" to use the pinned libghostty-vt engine globally. A profile can override it with "builtin" or "ghostty". ConPTY remains the Windows process transport for both engines.

The compiled-XAML settings editor is documented in docs/settings-editor.md.

Architecture

Devolutions.Terminal.Core         VT parser + text buffer + terminal engine
Devolutions.Terminal.Ghostty      NativeAOT-safe libghostty-vt engine adapter
Devolutions.Terminal.Render       Immutable plans + HarfBuzz/Skia glyph renderer
Devolutions.Terminal.Connection   ConPTY + Linux PTY + Azure Cloud Shell + browser shell
Devolutions.Terminal.Settings     Layered Windows Terminal-compatible JSON settings
Devolutions.Terminal.Control      Avalonia TermControl renderer
Devolutions.Terminal.App   Tabs, title bar, panes, actions, window behavior
Devolutions.Terminal       NativeAOT executable and composition root
Devolutions.Terminal.Browser      Avalonia WebAssembly host (in-process dt-wasm shell)
Devolutions.Terminal.Browser.Host Loopback server for profile tabs and a real PTY

The measured remaining parity contract is in docs/parity-status.md. Architecture decisions are recorded in docs/decisions. Renderer contracts are documented in docs/renderer.md. Control, clipboard, IME, and accessibility contracts are documented in docs/control-accessibility.md. Advanced VT protocols are documented in docs/advanced-vt-protocols.md. Azure Cloud Shell is documented in docs/azure-cloud-shell.md. The browser WASM host is documented in docs/browser.md. Build and release gates are documented in docs/release.md.

Devolutions.Terminal.Control is also published to nuget.org as a reusable, self-contained Avalonia terminal control package (it bundles Core/Render/Connection/Settings internally) for embedding in other Avalonia applications. See the "NuGet package" section of docs/release.md for packaging and consumption details.

Per-tab asciicast recording

Recording actions operate on the active terminal pane. startRecording captures future PTY output, stopRecording retains the completed recording, saveRecording opens a native Save As dialog and exports an asciicast .cast file, and replayRecording replays the active pane's latest capture in a separate read-only tab with its original timing. The recorder writes v2 by default and the core API can also export v3 with recording.ToJson(AsciicastFormat.V3). openRecording detects and replays both v2 and v3 files, even when the current pane has no recording. Pickers start in the recordings directory beside settings.json.

All path-taking actions also accept an explicit path for keybindings, command-line activation, and automation. A path on startRecording enables an auto-save workflow: stopping the recording writes directly to that path. Exported v2 files are compatible with v2 players such as asciinema play; imported v3 files retain their relative event timing during replay.

To stream the active tab, open the command palette and choose Start streaming..., paste the producer ws:// or wss:// URL, and choose Asciinema v3 (recommended), Asciinema v2, or DVLS v2. The recording bar changes to Live while connected. Choose Stop streaming from the palette (or use the bar's Stop button) to flush and close the stream. DT remembers the URL only for the current process because producer URLs may contain access tokens.

NuGet consumers can also stream an active recording to an asciicast-compatible WebSocket push endpoint:

await terminal.StartRecordingAsync(
    new Uri("wss://gateway.example/jet/jrec/push/session?token=signed-token"));
// Use the terminal normally.
await terminal.StopRecordingAsync();

The control preserves authentication query parameters, adds fileType=asciicast, and sends the visible starting screen plus subsequent output as asciicast v2 JSONL text messages. Endpoint discovery and token acquisition stay in the embedding application.

Official asciinema servers are also supported. Create a live stream through the server API, then pass its ws_producer_url and select v2 or v3:

await terminal.StartRecordingAsync(
    new Uri(streamResponse.WsProducerUrl),
    AsciicastFormat.V3);

This negotiates the matching v2.asciicast or v3.asciicast WebSocket subprotocol. V3 output uses relative event deltas; the legacy URI-only overload remains compatible with DVLS push URLs and server-side first-message detection.

While a recording is active, its tab shows a red recording indicator and the active pane displays elapsed time, format, destination, and Stop/Save controls. Stopped captures remain available from the same bar for saving or replay. A yellow tab indicator marks an unsaved capture, and closing its pane, tab, or window asks for confirmation even when normal close confirmations are disabled.

Replay tabs include pause/resume, restart, progress, and 0.5x-2x playback speed controls. Replay remains read-only and can be closed without affecting the original terminal session or recording.

The actions can be invoked from the command palette or assigned in settings.json; for example:

{
  "command": {
    "action": "startRecording",
    "path": "%USERPROFILE%\\Desktop\\demo.cast",
    "format": "v3"
  },
  "keys": "ctrl+shift+r"
}

Compatibility inventory

Safety and compatibility settings

Large or multi-line pastes requiring confirmation are cancelled unless explicitly approved. The application prompts asynchronously; closing the prompt cancels the paste. Embedded controls without a confirmation handler also cancel warned pastes.

warning.confirmOnClose applies to user-initiated window, tab, pane, and bulk close actions. never skips confirmation; always confirms closing any running session; automatic confirms when an action closes more than one running session (the legacy confirmCloseAllTabs behavior). Already-exited sessions and automatic process-exit cleanup never prompt.

PTY input is queued in order off the UI thread, with limits of 256 pending writes and 4 MiB (including framing on Unix). Overflow rejects the entire new write and reports an error rather than blocking or silently dropping input. Async writes complete after transport delivery; caller cancellation skips writes not yet started. Cancelling an in-flight write terminates its session because input may have been partially delivered and Unix framing cannot safely resume. Closing a blocked Unix session has a one-second grace period before host termination; undelivered input is reported.

The editor disables options that are currently retained only for settings-file compatibility: compatibility.textMeasurement, compatibility.ambiguousWidth, and disableAnimations. The terminal engine determines text measurement and character widths. experimental.detectURLs detects http, https, ftp, and www URLs in terminal text; explicit OSC 8 hyperlinks remain supported. Window/pane animation effects are not configurable through disableAnimations.

Broker retries share active requests and retain completed responses for at least five seconds after completion. Admission is bounded at 128 active requests and 1024 total retained requests. When full, new requests receive an explicit unavailable response without executing their action; existing retries still join their original operation.

The port tracks Windows Terminal settings, actions, VT dispatch, command line, and settings-page surfaces in compat/windows-terminal.json. Tests use that checked-in snapshot. Regenerating it requires a separate Microsoft Windows Terminal C++ checkout:

dotnet run --project tools/Devolutions.Terminal.PortInventory -- <windows-terminal-checkout> compat/windows-terminal.json

About

Devolutions Terminal - cross-platform Windows Terminal in C# and Avalonia

Topics

Resources

Contributing

Security policy

Stars

23 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages