Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 92 additions & 1 deletion .github/workflows/build-terminal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,96 @@ jobs:
- name: Validate Linux scripts and metadata
run: bash scripts/Test-LinuxPackagingMetadata.sh

macos-managed:
name: macOS managed and metadata tests
runs-on: macos-14
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Cache native libraries
uses: actions/cache@v4
with:
path: |
artifacts/tools
artifacts/ghostty-src
native/ghostty
native/linux-pty
native/noto-emoji/NotoColorEmoji.ttf
key: native-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/ghostty/ghostty-upstream.json', 'native/linux-pty/dt-pty-host.c', 'native/noto-emoji/noto-emoji.json') }}
restore-keys: |
native-${{ runner.os }}-${{ runner.arch }}-

- name: Set up .NET
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: 10.0.x

- name: Restore
run: dotnet restore Devolutions.Terminal.slnx

- name: Run managed tests
run: dotnet test Devolutions.Terminal.slnx -c Release --no-restore

- name: Validate macOS scripts and metadata
run: bash scripts/Test-MacOsPackagingMetadata.sh

macos-native-aot:
name: macOS NativeAOT osx-arm64
runs-on: macos-14
needs: macos-managed
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Cache native libraries
uses: actions/cache@v4
with:
path: |
artifacts/tools
artifacts/ghostty-src
native/ghostty
native/linux-pty
native/noto-emoji/NotoColorEmoji.ttf
key: native-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('native/ghostty/ghostty-upstream.json', 'native/linux-pty/dt-pty-host.c', 'native/noto-emoji/noto-emoji.json') }}
restore-keys: |
native-${{ runner.os }}-${{ runner.arch }}-

- name: Set up .NET
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: 10.0.x

- name: NativeAOT publish
run: >
dotnet publish src/Devolutions.Terminal/Devolutions.Terminal.csproj
-c Release
-r osx-arm64
--self-contained true
-p:DebugSymbols=false
-p:DebugType=None
-p:NativeDebugSymbols=false
-o artifacts/publish/osx-arm64

- name: Build app bundle and zip
env:
MACOS_PUBLISH_DIR: ${{ github.workspace }}/artifacts/publish/osx-arm64
SOURCE_DATE_EPOCH: "1704067200"
run: bash scripts/Build-MacOsPackage.sh osx-arm64 "0.1.${{ github.run_number }}" artifacts/macos-packages

- name: Validate package without launching UI
run: bash scripts/Test-MacOsPackage.sh osx-arm64 artifacts/macos-packages/*.zip

- name: Run native non-UI gates
run: bash scripts/Test-MacOsRuntime.sh artifacts/macos-packages

- name: Upload macOS package artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: DevolutionsTerminal-osx-arm64-packages
path: artifacts/macos-packages
if-no-files-found: error

linux-packages:
name: Linux packages ${{ matrix.rid }}
runs-on: ubuntu-24.04
Expand Down Expand Up @@ -166,7 +256,8 @@ jobs:
if: matrix.rid == 'linux-arm64'
run: |
sudo apt-get install --no-install-recommends \
binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu
binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross

- name: Fetch pinned AppImage runtime
env:
Expand Down
15 changes: 11 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Do not point that tool at this repository.

## Native helpers

- Linux/macOS PTY host: `native/linux-pty` (`dt-pty-host.c`, Zig `cc`)
- Linux/macOS PTY host: `native/linux-pty` (`dt-pty-host.c`; Zig `cc` on Linux, Apple clang on macOS)
- Ghostty VT library: `native/ghostty` (Zig build of pinned Ghostty)
- Windows Explorer/toast helpers: `native/windows-shell` (MSVC, gitignored `bin/`)

Expand All @@ -56,6 +56,13 @@ downloaded into `artifacts/tools` on first use). See

## macOS

macOS is partial. See [docs/macos.md](docs/macos.md). The Unix PTY host source
cross-targets `osx-arm64` / `osx-x64`; Ghostty dylibs and app-bundle packaging
are not in this tree yet.
See [docs/macos.md](docs/macos.md). On a Mac:

```bash
dotnet test Devolutions.Terminal.slnx
scripts/Build-MacOsPackage.sh osx-arm64 0.1.0 artifacts/packages
```

`dotnet build` restores Ghostty and `dt-pty-host` for `osx-arm64` / `osx-x64`.
The PTY host is compiled with Apple clang against the macOS 13 SDK. App-bundle
packaging is Darwin-only; notarization, DMG, and Homebrew are not included.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ 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):

```bash
scripts/Build-MacOsPackage.sh osx-arm64 0.1.0 artifacts/packages
bash scripts/Test-MacOsPackage.sh osx-arm64 artifacts/packages/*.zip
```

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

```bash
Expand Down Expand Up @@ -61,8 +68,9 @@ Development signing, trust, install, validation, and uninstall commands are in
## Settings and engines

Settings are stored at `%LOCALAPPDATA%\Devolutions\Terminal\settings.json` on
Windows and under `$XDG_CONFIG_HOME/devolutions-terminal` on Linux, with the
usual `~/.config` fallback.
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
Expand Down
30 changes: 22 additions & 8 deletions docs/macos.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,48 @@
# macOS support

macOS is a partial port. The managed app, built-in VT engine, settings, and
Unix PTY protocol are wired. NativeAOT publish and live UI have not been run
on a Mac in this tree.
macOS is a first-class host for the managed app, Unix PTY transport, built-in
and Ghostty engines, and NativeAOT `.app` packaging. Global hotkeys, default
terminal registration, notarization, DMG, and Homebrew remain out of scope.

## What should work
## What works

- Avalonia desktop host (`osx-arm64` / `osx-x64`)
- Local shells through `dt-pty-host` (`forkpty`, same framing as Linux)
- Selectable built-in and Ghostty engines (`libghostty-vt.dylib`)
- Settings at `~/Library/Application Support/Devolutions/Terminal/`
- Generated zsh/bash/fish/pwsh/sh profiles (`Devolutions.Terminal.macOS`)
- Hidden Windows inbox profiles that use `%SystemRoot%`
- Opening files and URIs with `open(1)`
- Notifications through `osascript` `display notification`
- `dterm:` URL scheme declared in `macos/Info.plist`
- NativeAOT `.app` + zip packaging on Darwin

## Not bundled yet

- App bundle / notarization / DMG / Homebrew cask
- Notarization / DMG / Homebrew cask
- Global hotkeys (broker / `dt -w` still work)
- Default-terminal registration

Ghostty dylibs and `dt-pty-host` are built on restore for `osx-arm64` /
`osx-x64` (macOS 13+).
`osx-x64` (macOS 13+). `dt-pty-host` is compiled with Apple clang so it can
link `libutil` from the SDK.

## Build on a Mac

```bash
dotnet test Devolutions.Terminal.slnx
dotnet publish src/Devolutions.Terminal -c Release -r osx-arm64 --self-contained
scripts/Build-MacOsPackage.sh osx-arm64 0.1.0 artifacts/packages
bash scripts/Test-MacOsPackage.sh osx-arm64 artifacts/packages/*.zip
bash scripts/Test-MacOsRuntime.sh artifacts/packages
```

`Build-MacOsPackage.sh` publishes NativeAOT unless `MACOS_PUBLISH_DIR` is set,
stages `Devolutions Terminal.app` with `macos/Info.plist`, generates
`DevolutionsTerminal.icns` from the hicolor PNGs, ad-hoc signs the bundle, and
writes a zip plus SHA-256 manifest.

```bash
open "artifacts/packages/Devolutions Terminal.app"
```

Copy `macos/Info.plist` into the `.app` bundle when packaging. Publish restores
`dt-pty-host` and `libghostty-vt.dylib` for the RID.
10 changes: 7 additions & 3 deletions docs/parity-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ and NativeAOT gates completed before the final live UI matrix.
| --- | --- |
| Windows process transport | ConPTY with input, resize, cancellation, restart, exit metadata, and x64/ARM64 NativeAOT |
| Linux process transport | Bundled `forkpty` relay with input, resize, cancellation, restart, exit metadata, and x64/ARM64 NativeAOT |
| macOS process transport | Same Unix `forkpty` host (`osx-arm64`/`osx-x64` Zig targets); binaries and live NativeAOT UI not produced in this tree |
| Terminal engines | Selectable built-in and pinned Ghostty engines on Windows and Linux; Ghostty dylib not bundled on macOS |
| macOS process transport | Same Unix `forkpty` host (`osx-arm64`/`osx-x64`); Apple clang `dt-pty-host` plus NativeAOT `.app` packaging on Darwin |
| Terminal engines | Selectable built-in and pinned Ghostty engines on Windows, Linux, and macOS |
| Application shell | Multi-window broker, tabs, panes, settings editor, palettes, tray behavior, accessibility, clipboard, and notifications |
| Settings | Layering, dynamic profiles, fragments, source-generated JSON, Windows paths, XDG paths, macOS Application Support, and state persistence |
| Distribution | Windows x64/ARM64 MSIX and bundle; reproducible Linux x64/ARM64 tar, DEB, RPM, and AppImage packages with canonical freedesktop assets, licenses, checksums, inventory/SPDX SBOM, and DESTDIR-aware helpers |
| Distribution | Windows x64/ARM64 MSIX and bundle; reproducible Linux x64/ARM64 tar, DEB, RPM, and AppImage packages with canonical freedesktop assets, licenses, checksums, inventory/SPDX SBOM, and DESTDIR-aware helpers; macOS NativeAOT `.app` and zip on Darwin |

## Actions

Expand Down Expand Up @@ -127,3 +127,7 @@ truecolor, Unicode/CJK/emoji, row rendition, images, and shader effects.
provide a visible file-manager handler.
- Native Windows ARM64 and Linux ARM64 remain non-UI hardware/package gates;
no ARM64 desktop was available in this session.
- macOS ARM64 NativeAOT `.app` packaging, `dt` parser, Ghostty ABI, real
`forkpty`, broker named-pipe length, and a live GUI-host process start were
validated on Darwin. Notarization, DMG, Homebrew, global hotkeys, and
default-terminal registration remain out of scope.
24 changes: 20 additions & 4 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,22 @@ Commands are relative to the repository root.

- Windows 10 version 2004 (build 19041) or later for Windows builds
- glibc-based x64 or ARM64 Linux for Linux builds
- macOS 13+ with Xcode CLT for macOS NativeAOT and `.app` packaging
- .NET SDK selected by `global.json`
- Visual Studio 2022/2026 Build Tools with Desktop C++ for NativeAOT linking
- WinApp CLI 0.6.0 for MSIX creation and validation
- A trusted code-signing certificate for distributable MSIX artifacts

Windows local sessions use ConPTY. Linux local sessions use the bundled
`forkpty` relay. The Avalonia shell, settings, renderer, and terminal engines
are shared.
Windows local sessions use ConPTY. Linux and macOS local sessions use the
bundled `forkpty` relay. The Avalonia shell, settings, renderer, and terminal
engines are shared.

CI workflows:

- `build-ghostty.yml` — compile `libghostty-vt` for every RID and upload
artifacts (optional cache; not required to develop).
- `build-terminal.yml` — restore natives from source, test, NativeAOT, Linux
packages, MSIX.
packages, macOS `.app`/zip, MSIX.

## Developer build

Expand Down Expand Up @@ -54,6 +55,21 @@ MSIX staging additionally builds `dt-shell-integration.exe` and
`Devolutions.Terminal.ShellExt.dll` for the package architecture with the installed
MSVC/Windows SDK toolchain.

macOS NativeAOT app bundles (Darwin only):

```bash
dotnet publish src/Devolutions.Terminal -c Release -r osx-arm64 --self-contained \
-o artifacts/native/osx-arm64
MACOS_PUBLISH_DIR="$PWD/artifacts/native/osx-arm64" \
bash scripts/Build-MacOsPackage.sh osx-arm64 0.1.0 artifacts/packages
bash scripts/Test-MacOsPackage.sh osx-arm64 artifacts/packages/*.zip
bash scripts/Test-MacOsRuntime.sh artifacts/packages
```

The staged `Devolutions Terminal.app` contains `Devolutions.Terminal`, `dt`,
`dt-pty-host`, `libghostty-vt.dylib`, Skia, HarfBuzz, `Info.plist`, and an
ad-hoc signed icon. Notarization, DMG, and Homebrew are not part of this gate.

Linux package formats:

```bash
Expand Down
4 changes: 4 additions & 0 deletions macos/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@
<string>Devolutions Terminal</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleIconFile</key>
<string>DevolutionsTerminal</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>LSMinimumSystemVersion</key>
<string>13.0</string>
<key>NSHighResolutionCapable</key>
Expand Down
15 changes: 15 additions & 0 deletions macos/package.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Canonical macOS package metadata. Keep versions out of this file; callers pass
# the version from Directory.Build.props or the release pipeline.
PACKAGE_NAME=devolutions-terminal
APP_ID=com.devolutions.Terminal
DISPLAY_NAME="Devolutions Terminal"
BUNDLE_NAME="Devolutions Terminal.app"
EXECUTABLE_NAME=Devolutions.Terminal
CLI_NAME=dt
PTY_HOST_NAME=dt-pty-host
GHOSTTY_LIBRARY=libghostty-vt.dylib
ICON_NAME=DevolutionsTerminal
MACOS_DEPLOYMENT_TARGET=13.0
LICENSE_ID="MIT AND OFL-1.1"
SBOM_LICENSE_ID="MIT AND OFL-1.1"
URL_SCHEME=dterm
8 changes: 7 additions & 1 deletion native/NativeLibraries.targets
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
Condition="'$(SkipNativeRestore)' != 'true' and '$(DesignTimeBuild)' != 'true' and '$(NativeRestore)' != 'none'">
<Exec
Command="$(NativeRestoreShell) -NoProfile -NonInteractive -ExecutionPolicy Bypass -File &quot;$(NativeRoot)Restore-NativeLibraries.ps1&quot; -Rid $(GhosttyRid) -Component $(NativeRestore)"
LogStandardErrorAsError="true" />
LogStandardErrorAsError="false" />
</Target>

<Target Name="CopyRestoredNativeLibraries"
Expand All @@ -35,6 +35,9 @@
DestinationFiles="$(TargetDir)dt-pty-host"
SkipUnchangedFiles="true"
Condition="'$(NativeRestore)' != 'ghostty' and '$(UnixPtyRid)' != '' and Exists('$(NativeRoot)linux-pty\$(UnixPtyRid)\dt-pty-host')" />
<Exec
Command="chmod +x &quot;$(TargetDir)dt-pty-host&quot;"
Condition="'$(NativeRestore)' != 'ghostty' and '$(UnixPtyRid)' != '' and Exists('$(TargetDir)dt-pty-host') and !$([MSBuild]::IsOSPlatform('Windows'))" />
</Target>

<Target Name="PublishRestoredNativeLibraries"
Expand All @@ -50,5 +53,8 @@
DestinationFiles="$(PublishDir)dt-pty-host"
SkipUnchangedFiles="true"
Condition="'$(NativeRestore)' != 'ghostty' and '$(UnixPtyRid)' != '' and Exists('$(NativeRoot)linux-pty\$(UnixPtyRid)\dt-pty-host')" />
<Exec
Command="chmod +x &quot;$(PublishDir)dt-pty-host&quot;"
Condition="'$(NativeRestore)' != 'ghostty' and '$(UnixPtyRid)' != '' and Exists('$(PublishDir)dt-pty-host') and !$([MSBuild]::IsOSPlatform('Windows'))" />
</Target>
</Project>
24 changes: 12 additions & 12 deletions native/Restore-NativeLibraries.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ try {
$fileName = [string]$manifest.targets.$Rid.file
$output = Join-Path $nativeRoot "ghostty\$Rid\$fileName"
if ($Force -or -not (Test-Path -LiteralPath $output)) {
$ghosttyArgs = @(
"-Rid", $Rid,
"-ZigPath", $ZigPath
)
$ghosttyArgs = @{
Rid = $Rid
ZigPath = $ZigPath
}
if ($Force) {
$ghosttyArgs += "-Force"
$ghosttyArgs.Force = $true
}

& (Join-Path $nativeRoot "ghostty\Build-Ghostty.ps1") @ghosttyArgs
& (Join-Path (Join-Path $nativeRoot "ghostty") "Build-Ghostty.ps1") @ghosttyArgs
if ($LASTEXITCODE -ne 0) {
throw "Build-Ghostty.ps1 failed for $Rid."
}
Expand All @@ -76,15 +76,15 @@ try {
if ($buildPty) {
$output = Join-Path $nativeRoot "linux-pty\$Rid\dt-pty-host"
if ($Force -or -not (Test-Path -LiteralPath $output)) {
$ptyArgs = @(
"-Rid", $Rid,
"-ZigPath", $ZigPath
)
$ptyArgs = @{
Rid = $Rid
ZigPath = $ZigPath
}
if ($Force) {
$ptyArgs += "-Force"
$ptyArgs.Force = $true
}

& (Join-Path $nativeRoot "linux-pty\Build-LinuxPtyHost.ps1") @ptyArgs
& (Join-Path (Join-Path $nativeRoot "linux-pty") "Build-LinuxPtyHost.ps1") @ptyArgs
if ($LASTEXITCODE -ne 0) {
throw "Build-LinuxPtyHost.ps1 failed for $Rid."
}
Expand Down
Loading
Loading