Merge remote-tracking branch 'origin/main' into gh-1972-clickable-file-paths

This commit is contained in:
Mustaque Ahmed
2025-02-16 18:39:22 +05:30
452 changed files with 20921 additions and 3456 deletions

3
.gitattributes vendored
View File

@ -1,3 +1,6 @@
build.zig.zon.nix linguist-generated=true
build.zig.zon.txt linguist-generated=true
build.zig.zon2json-lock linguist-generated=true
vendor/** linguist-vendored
website/** linguist-documentation
pkg/breakpad/vendor/** linguist-vendored

View File

@ -1,6 +1,31 @@
on: [push, pull_request]
name: Nix
jobs:
required:
name: "Required Checks: Nix"
runs-on: namespace-profile-ghostty-sm
needs:
- check-zig-cache-hash
steps:
- id: status
name: Determine status
run: |
results=$(tr -d '\n' <<< '${{ toJSON(needs.*.result) }}')
if ! grep -q -v -E '(failure|cancelled)' <<< "$results"; then
result="failed"
else
result="success"
fi
{
echo "result=${result}"
echo "results=${results}"
} | tee -a "$GITHUB_OUTPUT"
- if: always() && steps.status.outputs.result != 'success'
name: Check for failed status
run: |
echo "One or more required build workflows failed: ${{ steps.status.outputs.results }}"
exit 1
check-zig-cache-hash:
if: github.repository == 'ghostty-org/ghostty'
runs-on: namespace-profile-ghostty-sm
@ -25,5 +50,5 @@ jobs:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
useDaemon: false # sometimes fails on short jobs
- name: Check Zig cache hash
run: nix develop -c ./nix/build-support/check-zig-cache-hash.sh
- name: Check Zig cache
run: nix develop -c ./nix/build-support/check-zig-cache.sh

74
.github/workflows/publish-tag.yml vendored Normal file
View File

@ -0,0 +1,74 @@
on:
workflow_dispatch:
inputs:
version:
description: "Version to deploy (format: vX.Y.Z)"
required: true
name: Publish Tagged Release
# We must only run one release workflow at a time to prevent corrupting
# our release artifacts.
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
setup:
runs-on: namespace-profile-ghostty-sm
outputs:
version: ${{ steps.extract_version.outputs.version }}
steps:
- name: Validate Version Input
run: |
if [[ ! "${{ github.event.inputs.version }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: Version must follow the format vX.Y.Z (e.g., v1.0.0)."
exit 1
fi
echo "Version is valid: ${{ github.event.inputs.version }}"
- name: Exract the Version
id: extract_version
run: |
VERSION=${{ github.event.inputs.version }}
VERSION=${VERSION#v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
upload:
needs: [setup]
runs-on: namespace-profile-ghostty-sm
env:
GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
steps:
- name: Validate Release Files
run: |
BASE="https://release.files.ghostty.org/${GHOSTTY_VERSION}"
curl -I -s -o /dev/null -w "%{http_code}" "${BASE}/ghostty-${GHOSTTY_VERSION}.tar.gz" | grep -q "^200$" || exit 1
curl -I -s -o /dev/null -w "%{http_code}" "${BASE}/ghostty-${GHOSTTY_VERSION}.tar.gz.minisig" | grep -q "^200$" || exit 1
curl -I -s -o /dev/null -w "%{http_code}" "${BASE}/ghostty-source.tar.gz" | grep -q "^200$" || exit 1
curl -I -s -o /dev/null -w "%{http_code}" "${BASE}/ghostty-source.tar.gz.minisig" | grep -q "^200$" || exit 1
curl -I -s -o /dev/null -w "%{http_code}" "${BASE}/ghostty-macos-universal.zip" | grep -q "^200$" || exit 1
curl -I -s -o /dev/null -w "%{http_code}" "${BASE}/ghostty-macos-universal-dsym.zip" | grep -q "^200$" || exit 1
curl -I -s -o /dev/null -w "%{http_code}" "${BASE}/Ghostty.dmg" | grep -q "^200$" || exit 1
curl -I -s -o /dev/null -w "%{http_code}" "${BASE}/appcast-staged.xml" | grep -q "^200$" || exit 1
- name: Download Staged Appcast
run: |
curl -L https://release.files.ghostty.org/${GHOSTTY_VERSION}/appcast-staged.xml > appcast-staged.xml
mv appcast-staged.xml appcast.xml
- name: Upload Appcast
run: |
rm -rf blob
mkdir blob
mv appcast.xml blob/appcast.xml
- name: Upload Appcast to R2
uses: ryand56/r2-upload-action@latest
with:
r2-account-id: ${{ secrets.CF_R2_RELEASE_ACCOUNT_ID }}
r2-access-key-id: ${{ secrets.CF_R2_RELEASE_AWS_KEY }}
r2-secret-access-key: ${{ secrets.CF_R2_RELEASE_SECRET_KEY }}
r2-bucket: ghostty-release
source-dir: blob
destination-dir: ./

View File

@ -68,7 +68,7 @@ jobs:
# Setup Sparkle
- name: Setup Sparkle
env:
SPARKLE_VERSION: 2.6.3
SPARKLE_VERSION: 2.6.4
run: |
mkdir -p .action/sparkle
cd .action/sparkle

View File

@ -7,6 +7,7 @@ on:
upload:
description: "Upload final artifacts to R2"
default: false
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
@ -135,7 +136,7 @@ jobs:
- name: Setup Sparkle
env:
SPARKLE_VERSION: 2.6.3
SPARKLE_VERSION: 2.6.4
run: |
mkdir -p .action/sparkle
cd .action/sparkle
@ -297,7 +298,7 @@ jobs:
- name: Setup Sparkle
env:
SPARKLE_VERSION: 2.6.3
SPARKLE_VERSION: 2.6.4
run: |
mkdir -p .action/sparkle
cd .action/sparkle
@ -367,6 +368,7 @@ jobs:
mv ghostty-macos-universal.zip blob/${GHOSTTY_VERSION}/ghostty-macos-universal.zip
mv ghostty-macos-universal-dsym.zip blob/${GHOSTTY_VERSION}/ghostty-macos-universal-dsym.zip
mv Ghostty.dmg blob/${GHOSTTY_VERSION}/Ghostty.dmg
mv appcast.xml blob/${GHOSTTY_VERSION}/appcast-staged.xml
- name: Upload to R2
uses: ryand56/r2-upload-action@latest
with:
@ -376,18 +378,3 @@ jobs:
r2-bucket: ghostty-release
source-dir: blob
destination-dir: ./
- name: Prep Appcast
run: |
rm -rf blob
mkdir blob
mv appcast.xml blob/appcast.xml
- name: Upload Appcast to R2
uses: ryand56/r2-upload-action@latest
with:
r2-account-id: ${{ secrets.CF_R2_RELEASE_ACCOUNT_ID }}
r2-access-key-id: ${{ secrets.CF_R2_RELEASE_AWS_KEY }}
r2-secret-access-key: ${{ secrets.CF_R2_RELEASE_SECRET_KEY }}
r2-bucket: ghostty-release
source-dir: blob
destination-dir: ./

View File

@ -164,7 +164,7 @@ jobs:
# Setup Sparkle
- name: Setup Sparkle
env:
SPARKLE_VERSION: 2.6.3
SPARKLE_VERSION: 2.6.4
run: |
mkdir -p .action/sparkle
cd .action/sparkle

View File

@ -6,6 +6,47 @@ on:
name: Test
jobs:
required:
name: "Required Checks: Test"
runs-on: namespace-profile-ghostty-sm
needs:
- build
- build-bench
- build-linux-libghostty
- build-nix
- build-snap
- build-macos
- build-macos-matrix
- build-windows
- test
- test-gtk
- test-sentry-linux
- test-macos
- prettier
- alejandra
- typos
- test-pkg-linux
- test-debian-12
steps:
- id: status
name: Determine status
run: |
results=$(tr -d '\n' <<< '${{ toJSON(needs.*.result) }}')
if ! grep -q -v -E '(failure|cancelled)' <<< "$results"; then
result="failed"
else
result="success"
fi
{
echo "result=${result}"
echo "results=${results}"
} | tee -a "$GITHUB_OUTPUT"
- if: always() && steps.status.outputs.result != 'success'
name: Check for failed status
run: |
echo "One or more required build workflows failed: ${{ steps.status.outputs.results }}"
exit 1
build:
strategy:
fail-fast: false
@ -163,10 +204,14 @@ jobs:
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
- name: get the Zig deps
id: deps
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
# GhosttyKit is the framework that is built from Zig for our native
# Mac app to access.
- name: Build GhosttyKit
run: nix develop -c zig build
run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }}
# The native app is built with native XCode tooling. This also does
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
@ -199,35 +244,65 @@ jobs:
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
- name: get the Zig deps
id: deps
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
- name: Test All
run: |
# OpenGL
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
# Metal
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
- name: Build All
run: |
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
build-snap:
strategy:
fail-fast: false
matrix:
os:
[namespace-profile-ghostty-snap, namespace-profile-ghostty-snap-arm64]
runs-on: ${{ matrix.os }}
needs: test
env:
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Setup Cache
uses: namespacelabs/nscloud-cache-action@v1.2.0
with:
path: |
/nix
/zig
- run: sudo apt install -y udev
- run: sudo systemctl start systemd-udevd
- uses: snapcore/action-build@v1
build-windows:
runs-on: windows-2022
@ -247,10 +322,10 @@ jobs:
run: |
# Get the zig version from build.zig so that it only needs to be updated
$fileContent = Get-Content -Path "build.zig" -Raw
$pattern = 'const required_zig = "(.*?)";'
$pattern = 'buildpkg\.requireZig\("(.*?)"\);'
$zigVersion = [regex]::Match($fileContent, $pattern).Groups[1].Value
Write-Output $version
$version = "zig-windows-x86_64-$zigVersion"
Write-Output $version
$uri = "https://ziglang.org/download/$zigVersion/$version.zip"
Invoke-WebRequest -Uri "$uri" -OutFile ".\zig-windows.zip"
Expand-Archive -Path ".\zig-windows.zip" -DestinationPath ".\" -Force
@ -327,7 +402,7 @@ jobs:
run: nix develop -c zig build -Dapp-runtime=none test
- name: Test GTK Build
run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true -Demit-docs
run: nix develop -c zig build -Dapp-runtime=gtk -Demit-docs
- name: Test GLFW Build
run: nix develop -c zig build -Dapp-runtime=glfw
@ -340,9 +415,9 @@ jobs:
strategy:
fail-fast: false
matrix:
adwaita: ["true", "false"]
x11: ["true", "false"]
name: GTK adwaita=${{ matrix.adwaita }} x11=${{ matrix.x11 }}
wayland: ["true", "false"]
name: GTK x11=${{ matrix.x11 }} wayland=${{ matrix.wayland }}
runs-on: namespace-profile-ghostty-sm
needs: test
env:
@ -373,8 +448,8 @@ jobs:
nix develop -c \
zig build \
-Dapp-runtime=gtk \
-Dgtk-adwaita=${{ matrix.adwaita }} \
-Dgtk-x11=${{ matrix.x11 }}
-Dgtk-x11=${{ matrix.x11 }} \
-Dgtk-wayland=${{ matrix.wayland }}
test-sentry-linux:
strategy:
@ -430,8 +505,12 @@ jobs:
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
- name: get the Zig deps
id: deps
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
- name: test
run: nix develop -c zig build test
run: nix develop -c zig build test --system ${{ steps.deps.outputs.deps }}
prettier:
if: github.repository == 'ghostty-org/ghostty'
@ -548,3 +627,26 @@ jobs:
- name: Test ${{ matrix.pkg }} Build
run: |
nix develop -c sh -c "cd pkg/${{ matrix.pkg }} ; zig build test"
test-debian-12:
name: Test build on Debian 12
runs-on: namespace-profile-ghostty-sm
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install and configure Namespace CLI
uses: namespacelabs/nscloud-setup@v0
- name: Configure Namespace powered Buildx
uses: namespacelabs/nscloud-setup-buildx-action@v0
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: src/build/docker/debian/Dockerfile
build-args: |
DISTRO_VERSION=12
ZIG_VERSION=0.13.0

View File

@ -48,14 +48,14 @@ jobs:
run: |
# Only proceed if build.zig.zon has changed
if ! git diff --exit-code build.zig.zon; then
nix develop -c ./nix/build-support/check-zig-cache-hash.sh --update
nix develop -c ./nix/build-support/check-zig-cache-hash.sh
nix develop -c ./nix/build-support/check-zig-cache.sh --update
nix develop -c ./nix/build-support/check-zig-cache.sh
fi
# Verify the build still works. We choose an arbitrary build type
# as a canary instead of testing all build types.
- name: Test Build
run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true
run: nix build .#ghostty
- name: Create pull request
uses: peter-evans/create-pull-request@v7
@ -66,7 +66,7 @@ jobs:
commit-message: "deps: Update iTerm2 color schemes"
add-paths: |
build.zig.zon
nix/zigCacheHash.nix
build.zig.zon.nix
body: |
Upstream revision: https://github.com/mbadolato/iTerm2-Color-Schemes/tree/${{ steps.zig_fetch.outputs.upstream_rev }}
labels: dependencies

1
.gitignore vendored
View File

@ -17,3 +17,4 @@ test/cases/**/*.actual.png
glad.zip
/Box_test.ppm
/Box_test_diff.ppm
/ghostty.qcow2

149
CODEOWNERS Normal file
View File

@ -0,0 +1,149 @@
# This file documents the subsystem maintainers of the Ghostty project
# along with the responsibilities of a maintainer and how one can become
# a maintainer.
#
# Ghostty follows a subsystem maintainer model where distinguished
# contributors (with mutual agreement) are designated as maintainers of a
# specific subset of the project. A subsystem maintainer has more privileges
# and authority over a specific part of the project than a regular
# contributor and deference is given to them when making decisions about
# their subsystem.
#
# Ultimately Ghostty has a BDFL (Benevolent Dictator For Life) model
# currently with @mitchellh as the BDFL. The BDFL has the final say in all
# decisions and may override a maintainer's decision if necessary. I like to
# say its a BDFLFN (Benevolent Dictator For Life "For Now") model because
# long term I'd like to see the project be more community driven. But for
# now, early in its life, we're going with this model.
#
# ## Privileges
#
# - Authority to approve or reject pull requests in their subsystem.
# - Authority to moderate issues and discussions in their subsystem.
# - Authority to make roadmap and design decisions about their subsystem
# with input only from other subsystem maintainers.
#
# In all scenarios, the BDFL doesn't need to be consulted for decisions
# but may revert or override decisions if necessary. The expectation is
# that maintainers will be trusted to make the right decisions for their
# subsystem and this will be rare.
#
# ## Responsibilities
#
# Subsystem maintainership is a voluntary role and maintainers are not
# expected to dedicate any amount of time to the project. However, if a
# maintainer is inactive for a long period of time, they may be removed from
# the maintainers list to avoid bitrot or outdated information.
#
# Maintainers are expected to be exemplary members of the community and
# should be respectful, helpful, and professional in all interactions.
# This is both in regards to the community at large as well as other
# subsystem maintainers as well as @mitchellh.
#
# As technical leaders, maintainers are expected to be mindful about
# breaking changes, performance, user impact, and other technical
# considerations in their subsystem. They should be considerate of large
# changes and should be able to justify their decisions.
#
# Notably, maintainers have NO OBLIGATION to review pull requests or issues
# in their subsystem. They have full discretion to review or not review
# anything they want. This isn't a job! It is a role of trust and authority
# and the expectation is that maintainers will use their best judgement.
#
# ## Becoming a Maintainer
#
# Maintainer candidates are noticed and proposed by the community. Anyone
# may propose themselves or someone else as a maintainer. The BDFL along
# with existing maintainers will discuss and decide.
#
# Generally, we want to see consistent high quality contributions to a
# specific subsystem before considering someone as a maintainer. There isn't
# an exact number of contributions or time period required but generally
# we're looking for an order of a dozen or more contributions over a period of
# months, at least.
#
# # Subsystem List
#
# The subsystems don't fully cover the entirety of the Ghostty project but
# are created organically as experts in certain areas emerge. If you feel
# you are an expert in a certain area and would like to be a maintainer,
# please reach out to @mitchellh on Discord.
#
# (Alphabetical order)
#
# - @ghostty-org/font - All things font related including discovery,
# rasterization, shaping, coloring, etc.
#
# - @ghostty-org/gtk - Anything GTK-related in the project, primarily
# the GTK apprt. Also includes X11/Wayland integrations and general
# Linux support.
#
# - @ghostty-org/macos - The Ghostty macOS app and any macOS-specific
# features, configurations, etc.
#
# - @ghostty-org/renderer - Ghostty rendering subsystem, including the
# rendering abstractions as well as specific renderers like OpenGL
# and Metal.
#
# - @ghostty-org/shell - Ghostty shell integration, including shell
# completions, shell detection, and any other shell interactions.
#
# - @ghostty-org/terminal - The terminal emulator subsystem, including
# subprocess management and pty handling, escape sequence parsing,
# key encoding, etc.
#
# ## Outside of Ghostty
#
# Other "subsystems" exist outside of Ghostty and will not be represented
# in this CODEOWNERS file:
#
# - @ghostty-org/discord-bot - Maintainers of the Ghostty Discord bot.
#
# - @ghostty-org/website - Maintainers of the Ghostty website.
# Font
/src/font/ @ghostty-org/font
/pkg/fontconfig/ @ghostty-org/font
/pkg/freetype/ @ghostty-org/font
/pkg/harfbuzz/ @ghostty-org/font
# GTK
/src/apprt/gtk/ @ghostty-org/gtk
/src/os/cgroup.zig @ghostty-org/gtk
/src/os/flatpak.zig @ghostty-org/gtk
/dist/linux/ @ghostty-org/gtk
# macOS
#
# This includes libghostty because the macOS apprt is built on top of
# libghostty and often requires or is impacted by changes to libghostty.
# macOS subsystem maintainers are expected to only work on libghostty
# insofar as it impacts the macOS apprt.
/include/ghostty.h @ghostty-org/macos
/src/apprt/embedded.zig @ghostty-org/macos
/src/os/cf_release_thread.zig @ghostty-org/macos
/src/os/macos.zig @ghostty-org/macos
/macos/ @ghostty-org/macos
/dist/macos/ @ghostty-org/macos
/pkg/apple-sdk/ @ghostty-org/macos
/pkg/macos/ @ghostty-org/macos
# Renderer
/src/renderer.zig @ghostty-org/renderer
/src/renderer/ @ghostty-org/renderer
/pkg/glslang/ @ghostty-org/renderer
/pkg/opengl/ @ghostty-org/renderer
/pkg/spirv-cross/ @ghostty-org/renderer
/pkg/wuffs/ @ghostty-org/renderer
# Shell
/src/shell-integration/ @ghostty-org/shell
/src/termio/shell-integration.zig @ghostty-org/shell
# Terminal
/src/simd/ @ghostty-org/terminal
/src/terminal/ @ghostty-org/terminal
/src/terminfo/ @ghostty-org/terminal
/src/unicode/ @ghostty-org/terminal
/src/Surface.zig @ghostty-org/terminal
/src/surface_mouse.zig @ghostty-org/terminal

View File

@ -77,3 +77,183 @@ pull request will be accepted with a high degree of certainty.
> **Pull requests are NOT a place to discuss feature design.** Please do
> not open a WIP pull request to discuss a feature. Instead, use a discussion
> and link to your branch.
# Developer Guide
> [!NOTE]
>
> **The remainder of this file is dedicated to developers actively
> working on Ghostty.** If you're a user reporting an issue, you can
> ignore the rest of this document.
## Input Stack Testing
The input stack is the part of the codebase that starts with a
key event and ends with text encoding being sent to the pty (it
does not include _rendering_ the text, which is part of the
font or rendering stack).
If you modify any part of the input stack, you must manually verify
all the following input cases work properly. We unfortunately do
not automate this in any way, but if we can do that one day that'd
save a LOT of grief and time.
Note: this list may not be exhaustive, I'm still working on it.
### Linux IME
IME (Input Method Editors) are a common source of bugs in the input stack,
especially on Linux since there are multiple different IME systems
interacting with different windowing systems and application frameworks
all written by different organizations.
The following matrix should be tested to ensure that all IME input works
properly:
1. Wayland, X11
2. ibus, fcitx, none
3. Dead key input (e.g. Spanish), CJK (e.g. Japanese), Emoji, Unicode Hex
4. ibus versions: 1.5.29, 1.5.30, 1.5.31 (each exhibit slightly different behaviors)
> [!NOTE]
>
> This is a **work in progress**. I'm still working on this list and it
> is not complete. As I find more test cases, I will add them here.
#### Dead Key Input
Set your keyboard layout to "Spanish" (or another layout that uses dead keys).
1. Launch Ghostty
2. Press `'`
3. Press `a`
4. Verify that `á` is displayed
Note that the dead key may or may not show a preedit state visually.
For ibus and fcitx it does but for the "none" case it does not. Importantly,
the text should be correct when it is sent to the pty.
We should also test canceling dead key input:
1. Launch Ghostty
2. Press `'`
3. Press escape
4. Press `a`
5. Verify that `a` is displayed (no diacritic)
#### CJK Input
Configure fcitx or ibus with a keyboard layout like Japanese or Mozc. The
exact layout doesn't matter.
1. Launch Ghostty
2. Press `Ctrl+Shift` to switch to "Hiragana"
3. On a US physical layout, type: `konn`, you should see `こん` in preedit.
4. Press `Enter`
5. Verify that `こん` is displayed in the terminal.
We should also test switching input methods while preedit is active, which
should commit the text:
1. Launch Ghostty
2. Press `Ctrl+Shift` to switch to "Hiragana"
3. On a US physical layout, type: `konn`, you should see `こん` in preedit.
4. Press `Ctrl+Shift` to switch to another layout (any)
5. Verify that `こん` is displayed in the terminal as committed text.
## Nix Virtual Machines
Several Nix virtual machine definitions are provided by the project for testing
and developing Ghostty against multiple different Linux desktop environments.
Running these requires a working Nix installation, either Nix on your
favorite Linux distribution, NixOS, or macOS with nix-darwin installed. Further
requirements for macOS are detailed below.
VMs should only be run on your local desktop and then powered off when not in
use, which will discard any changes to the VM.
The VM definitions provide minimal software "out of the box" but additional
software can be installed by using standard Nix mechanisms like `nix run nixpkgs#<package>`.
### Linux
1. Check out the Ghostty source and change to the directory.
2. Run `nix run .#<vmtype>`. `<vmtype>` can be any of the VMs defined in the
`nix/vm` directory (without the `.nix` suffix) excluding any file prefixed
with `common` or `create`.
3. The VM will build and then launch. Depending on the speed of your system, this
can take a while, but eventually you should get a new VM window.
4. The Ghostty source directory should be mounted to `/tmp/shared` in the VM. Depending
on what UID and GID of the user that you launched the VM as, `/tmp/shared` _may_ be
writable by the VM user, so be careful!
### macOS
1. To run the VMs on macOS you will need to enable the Linux builder in your `nix-darwin`
config. This _should_ be as simple as adding `nix.linux-builder.enable=true` to your
configuration and then rebuilding. See [this](https://nixcademy.com/posts/macos-linux-builder/)
blog post for more information about the Linux builder and how to tune the performance.
2. Once the Linux builder has been enabled, you should be able to follow the Linux instructions
above to launch a VM.
### Custom VMs
To easily create a custom VM without modifying the Ghostty source, create a new
directory, then create a file called `flake.nix` with the following text in the
new directory.
```
{
inputs = {
nixpkgs.url = "nixpkgs/nixpkgs-unstable";
ghostty.url = "github:ghostty-org/ghostty";
};
outputs = {
nixpkgs,
ghostty,
...
}: {
nixosConfigurations.custom-vm = ghostty.create-gnome-vm {
nixpkgs = nixpkgs;
system = "x86_64-linux";
overlay = ghostty.overlays.releasefast;
# module = ./configuration.nix # also works
module = {pkgs, ...}: {
environment.systemPackages = [
pkgs.btop
];
};
};
};
}
```
The custom VM can then be run with a command like this:
```
nix run .#nixosConfigurations.custom-vm.config.system.build.vm
```
A file named `ghostty.qcow2` will be created that is used to persist any changes
made in the VM. To "reset" the VM to default delete the file and it will be
recreated the next time you run the VM.
### Contributing new VM definitions
#### VM Acceptance Criteria
We welcome the contribution of new VM definitions, as long as they meet the following criteria:
1. The should be different enough from existing VM definitions that they represent a distinct
user (and developer) experience.
2. There's a significant Ghostty user population that uses a similar environment.
3. The VMs can be built using only packages from the current stable NixOS release.
#### VM Definition Criteria
1. VMs should be as minimal as possible so that they build and launch quickly.
Additional software can be added at runtime with a command like `nix run nixpkgs#<package name>`.
2. VMs should not expose any services to the network, or run any remote access
software like SSH daemons, VNC or RDP.
3. VMs should auto-login using the "ghostty" user.

View File

@ -23,13 +23,6 @@ https://release.files.ghostty.org/VERSION/ghostty-VERSION.tar.gz
https://release.files.ghostty.org/VERSION/ghostty-VERSION.tar.gz.minisig
```
> [!NOTE]
>
> **Version 1.0.0 the filename is `ghostty-source.tar.gz`.** Future
> versions will use the `ghostty-VERSION.tar.gz` format since it is more
> typical for source tarballs. But for version 1.0.0, the filename is
> `ghostty-source.tar.gz`.
Signature files are signed with
[minisign](https://jedisct1.github.io/minisign/)
using the following public key:
@ -88,6 +81,13 @@ for system packages which separate a build and install step, since the
install step can then be done with a `mv` or `cp` command (from `/tmp/ghostty`
to wherever the package manager expects it).
> [!NOTE]
>
> **Version 1.1.1 and 1.1.2 are missing `fetch-zig-cache.sh`.** This was
> an oversight on the release process. You can use the script from version
> 1.1.0 to fetch the Zig cache for these versions. Future versions will
> restore the script.
### Build Options
Ghostty uses the Zig build system. You can see all available build options by

View File

@ -3,21 +3,7 @@ const builtin = @import("builtin");
const buildpkg = @import("src/build/main.zig");
comptime {
// This is the required Zig version for building this project. We allow
// any patch version but the major and minor must match exactly.
const required_zig = "0.13.0";
// Fail compilation if the current Zig version doesn't meet requirements.
const current_vsn = builtin.zig_version;
const required_vsn = std.SemanticVersion.parse(required_zig) catch unreachable;
if (current_vsn.major != required_vsn.major or
current_vsn.minor != required_vsn.minor)
{
@compileError(std.fmt.comptimePrint(
"Your Zig version v{} does not meet the required build version of v{}",
.{ current_vsn, required_vsn },
));
}
buildpkg.requireZig("0.13.0");
}
pub fn build(b: *std.Build) !void {

View File

@ -1,32 +1,39 @@
.{
.name = "ghostty",
.version = "1.0.2",
.version = "1.1.3",
.paths = .{""},
.dependencies = .{
// Zig libs
.libxev = .{
.url = "https://github.com/mitchellh/libxev/archive/db6a52bafadf00360e675fefa7926e8e6c0e9931.tar.gz",
.hash = "12206029de146b685739f69b10a6f08baee86b3d0a5f9a659fa2b2b66c9602078bbf",
// mitchellh/libxev
.url = "https://deps.files.ghostty.org/libxev-1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c.tar.gz",
.hash = "1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c",
},
.mach_glfw = .{
.url = "https://github.com/mitchellh/mach-glfw/archive/37c2995f31abcf7e8378fba68ddcf4a3faa02de0.tar.gz",
// mitchellh/mach-glfw
.url = "https://deps.files.ghostty.org/mach_glfw-12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62.tar.gz",
.hash = "12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62",
.lazy = true,
},
.vaxis = .{
.url = "git+https://github.com/rockorager/libvaxis/?ref=main#6d729a2dc3b934818dffe06d2ba3ce02841ed74b",
.hash = "12200df4ebeaed45de26cb2c9f3b6f3746d8013b604e035dae658f86f586c8c91d2f",
// rockorager/libvaxis
.url = "git+https://github.com/rockorager/libvaxis#2237a7059eae99e9f132dd5acd1555e49d6c7d93",
.hash = "1220f5aec880d4f430cc1597ede88f1530da69e39a4986080e976b0c7b919c2ebfeb",
},
.z2d = .{
.url = "git+https://github.com/vancluever/z2d?ref=v0.4.0#4638bb02a9dc41cc2fb811f092811f6a951c752a",
// vancluever/z2d
.url = "https://deps.files.ghostty.org/z2d-12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a.tar.gz",
.hash = "12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a",
},
.zig_objc = .{
.url = "https://github.com/mitchellh/zig-objc/archive/9b8ba849b0f58fe207ecd6ab7c147af55b17556e.tar.gz",
// mitchellh/zig-objc
.url = "https://deps.files.ghostty.org/zig_objc-1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634.tar.gz",
.hash = "1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634",
},
.zig_js = .{
.url = "https://github.com/mitchellh/zig-js/archive/d0b8b0a57c52fbc89f9d9fecba75ca29da7dd7d1.tar.gz",
// mitchellh/zig-js
.url = "https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz",
.hash = "12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc",
},
.ziglyph = .{
@ -34,13 +41,20 @@
.hash = "12207831bce7d4abce57b5a98e8f3635811cfefd160bca022eb91fe905d36a02cf25",
},
.zig_wayland = .{
.url = "https://codeberg.org/ifreund/zig-wayland/archive/a5e2e9b6a6d7fba638ace4d4b24a3b576a02685b.tar.gz",
.hash = "1220d41b23ae70e93355bb29dac1c07aa6aeb92427a2dffc4375e94b4de18111248c",
// codeberg ifreund/zig-wayland
.url = "https://deps.files.ghostty.org/zig-wayland-fbfe3b4ac0b472a27b1f1a67405436c58cbee12d.tar.gz",
.hash = "12209ca054cb1919fa276e328967f10b253f7537c4136eb48f3332b0f7cf661cad38",
},
.zf = .{
.url = "git+https://github.com/natecraddock/zf/?ref=main#ed99ca18b02dda052e20ba467e90b623c04690dd",
// natecraddock/zf
.url = "https://deps.files.ghostty.org/zf-1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8.tar.gz",
.hash = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8",
},
.gobject = .{
// ianprime0509/zig-gobject
.url = "https://deps.files.ghostty.org/gobject-12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d.tar.zst",
.hash = "12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d",
},
// C libs
.cimgui = .{ .path = "./pkg/cimgui" },
@ -72,15 +86,15 @@
.hash = "12201a57c6ce0001aa034fa80fba3e1cd2253c560a45748f4f4dd21ff23b491cddef",
},
.plasma_wayland_protocols = .{
.url = "git+https://github.com/KDE/plasma-wayland-protocols?ref=main#db525e8f9da548cffa2ac77618dd0fbe7f511b86",
.url = "https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz",
.hash = "12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566",
},
// Other
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
.iterm2_themes = .{
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/4762ad5bd6d3906e28babdc2bda8a967d63a63be.tar.gz",
.hash = "1220a263b22113273d01bd33e3c06b8119cb2f63b4e5d414a85d88e3aa95bb68a2de",
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/efb1bb1843500a751eb30afa58fe48a6bec8952c.tar.gz",
.hash = "1220a1dbe41bc69aacf75026a7158812198ea265fb9cac64dcb91cd31f3b1b8c1f92",
},
},
}

390
build.zig.zon.nix generated Normal file
View File

@ -0,0 +1,390 @@
# generated by zon2nix (https://github.com/Cloudef/zig2nix)
{
lib,
linkFarm,
fetchurl,
fetchgit,
runCommandLocal,
zig,
name ? "zig-packages",
}:
with builtins;
with lib; let
unpackZigArtifact = {
name,
artifact,
}:
runCommandLocal name
{
nativeBuildInputs = [zig];
}
''
hash="$(zig fetch --global-cache-dir "$TMPDIR" ${artifact})"
mv "$TMPDIR/p/$hash" "$out"
chmod 755 "$out"
'';
fetchZig = {
name,
url,
hash,
}: let
artifact = fetchurl {inherit url hash;};
in
unpackZigArtifact {inherit name artifact;};
fetchGitZig = {
name,
url,
hash,
}: let
parts = splitString "#" url;
url_base = elemAt parts 0;
url_without_query = elemAt (splitString "?" url_base) 0;
rev_base = elemAt parts 1;
rev =
if match "^[a-fA-F0-9]{40}$" rev_base != null
then rev_base
else "refs/heads/${rev_base}";
in
fetchgit {
inherit name rev hash;
url = url_without_query;
deepClone = false;
};
fetchZigArtifact = {
name,
url,
hash,
}: let
parts = splitString "://" url;
proto = elemAt parts 0;
path = elemAt parts 1;
fetcher = {
"git+http" = fetchGitZig {
inherit name hash;
url = "http://${path}";
};
"git+https" = fetchGitZig {
inherit name hash;
url = "https://${path}";
};
http = fetchZig {
inherit name hash;
url = "http://${path}";
};
https = fetchZig {
inherit name hash;
url = "https://${path}";
};
};
in
fetcher.${proto};
in
linkFarm name [
{
name = "1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c";
path = fetchZigArtifact {
name = "libxev";
url = "https://deps.files.ghostty.org/libxev-1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c.tar.gz";
hash = "sha256-VHP90NTytIZ8UZsYRKOOxN490/I6yv6ec40sP8y5MJ8=";
};
}
{
name = "12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62";
path = fetchZigArtifact {
name = "mach_glfw";
url = "https://deps.files.ghostty.org/mach_glfw-12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62.tar.gz";
hash = "sha256-HhXIvWUS8/CHWY4VXPG2ZEo+we8XOn3o5rYJCQ1n8Nk=";
};
}
{
name = "1220736fa4ba211162c7a0e46cc8fe04d95921927688bff64ab5da7420d098a7272d";
path = fetchZigArtifact {
name = "glfw";
url = "https://github.com/mitchellh/glfw/archive/b552c6ec47326b94015feddb36058ea567b87159.tar.gz";
hash = "sha256-IeBVAOQmtyFqVxzuXPek1onuPwIamcOyYtxqKpPEQjU=";
};
}
{
name = "12202adbfecdad671d585c9a5bfcbd5cdf821726779430047742ce1bf94ad67d19cb";
path = fetchZigArtifact {
name = "xcode_frameworks";
url = "https://github.com/mitchellh/xcode-frameworks/archive/69801c154c39d7ae6129ea1ba8fe1afe00585fc8.tar.gz";
hash = "sha256-mP/I2coL57UJm/3+4Q8sPAgQwk8V4zM+S4VBBTrX2To=";
};
}
{
name = "122004bfd4c519dadfb8e6281a42fc34fd1aa15aea654ea8a492839046f9894fa2cf";
path = fetchZigArtifact {
name = "vulkan_headers";
url = "https://github.com/mitchellh/vulkan-headers/archive/04c8a0389d5a0236a96312988017cd4ce27d8041.tar.gz";
hash = "sha256-K+zrRudgHFukOM6En1StRYRMNYkeRk+qHTXvrXaG+FU=";
};
}
{
name = "1220b3164434d2ec9db146a40bf3a30f490590d68fa8529776a3138074f0da2c11ca";
path = fetchZigArtifact {
name = "wayland_headers";
url = "https://github.com/mitchellh/wayland-headers/archive/5f991515a29f994d87b908115a2ab0b899474bd1.tar.gz";
hash = "sha256-uFilLZinKkZt6RdVTV3lUmJpzpswDdFva22FvwU/XQI=";
};
}
{
name = "122089c326186c84aa2fd034b16abc38f3ebf4862d9ae106dc1847ac44f557b36465";
path = fetchZigArtifact {
name = "x11_headers";
url = "https://github.com/mitchellh/x11-headers/archive/2ffbd62d82ff73ec929dd8de802bc95effa0ef88.tar.gz";
hash = "sha256-EhV2bmTY/OMYN1wEul35gD0hQgS/Al262jO3pVr0O+c=";
};
}
{
name = "1220f5aec880d4f430cc1597ede88f1530da69e39a4986080e976b0c7b919c2ebfeb";
path = fetchZigArtifact {
name = "vaxis";
url = "git+https://github.com/rockorager/libvaxis#2237a7059eae99e9f132dd5acd1555e49d6c7d93";
hash = "sha256-ZzLNJOsXzyBhkdUhbET30RoU2T9xKYsBUQz2NAjK/G8=";
};
}
{
name = "1220dd654ef941fc76fd96f9ec6adadf83f69b9887a0d3f4ee5ac0a1a3e11be35cf5";
path = fetchZigArtifact {
name = "zigimg";
url = "git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e";
hash = "sha256-oLf3YH3yeg4ikVO/GahMCDRMTU31AHkfSnF4rt7xTKo=";
};
}
{
name = "122055beff332830a391e9895c044d33b15ea21063779557024b46169fb1984c6e40";
path = fetchZigArtifact {
name = "zg";
url = "https://codeberg.org/atman/zg/archive/v0.13.2.tar.gz";
hash = "sha256-2x9hT7bYq9KJYWLVOf21a+QvTG/F7HWT+YK15IMRzNY=";
};
}
{
name = "12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a";
path = fetchZigArtifact {
name = "z2d";
url = "https://deps.files.ghostty.org/z2d-12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a.tar.gz";
hash = "sha256-P0UJ54RO/vVyDa+UkBl+QEOjzoMMEFSOTexQP/uBXfc=";
};
}
{
name = "1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634";
path = fetchZigArtifact {
name = "zig_objc";
url = "https://deps.files.ghostty.org/zig_objc-1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634.tar.gz";
hash = "sha256-H+HIbh2T23uzrsg9/1/vl9Ir1HCAa2pzeTx6zktJH9Q=";
};
}
{
name = "12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc";
path = fetchZigArtifact {
name = "zig_js";
url = "https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz";
hash = "sha256-fyNeCVbC9UAaKJY6JhAZlT0A479M/AKYMPIWEZbDWD0=";
};
}
{
name = "12207831bce7d4abce57b5a98e8f3635811cfefd160bca022eb91fe905d36a02cf25";
path = fetchZigArtifact {
name = "ziglyph";
url = "https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz";
hash = "sha256-cse98+Ft8QUjX+P88yyYfaxJOJGQ9M7Ymw7jFxDz89k=";
};
}
{
name = "12209ca054cb1919fa276e328967f10b253f7537c4136eb48f3332b0f7cf661cad38";
path = fetchZigArtifact {
name = "zig_wayland";
url = "https://deps.files.ghostty.org/zig-wayland-fbfe3b4ac0b472a27b1f1a67405436c58cbee12d.tar.gz";
hash = "sha256-RtAystqK/GRYIquTK1KfD7rRSCrfuzAvCD1Z9DE1ldc=";
};
}
{
name = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8";
path = fetchZigArtifact {
name = "zf";
url = "https://deps.files.ghostty.org/zf-1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8.tar.gz";
hash = "sha256-/oLryY3VQfjbtQi+UP+n6FJTVA/YxIetjO+6Ovrh6/E=";
};
}
{
name = "1220c72c1697dd9008461ead702997a15d8a1c5810247f02e7983b9f74c6c6e4c087";
path = fetchZigArtifact {
name = "vaxis";
url = "git+https://github.com/rockorager/libvaxis/?ref=main#dc0a228a5544988d4a920cfb40be9cd28db41423";
hash = "sha256-QWN4jOrA91KlbqmeEHHJ4HTnCC9nmfxt8DHUXJpAzLI=";
};
}
{
name = "12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d";
path = fetchZigArtifact {
name = "gobject";
url = "https://deps.files.ghostty.org/gobject-12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d.tar.zst";
hash = "sha256-UU97kNv/bZzQPKz1djhEDLapLguvfBpFfWVb6FthtcI=";
};
}
{
name = "12202cdac858abc52413a6c6711d5026d2d3c8e13f95ca2c327eade0736298bb021f";
path = fetchZigArtifact {
name = "wayland";
url = "https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz";
hash = "sha256-6kGR1o5DdnflHzqs3ieCmBAUTpMdOXoyfcYDXiw5xQ0=";
};
}
{
name = "12201a57c6ce0001aa034fa80fba3e1cd2253c560a45748f4f4dd21ff23b491cddef";
path = fetchZigArtifact {
name = "wayland_protocols";
url = "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz";
hash = "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg=";
};
}
{
name = "12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566";
path = fetchZigArtifact {
name = "plasma_wayland_protocols";
url = "https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz";
hash = "sha256-XFi6IUrNjmvKNCbcCLAixGqN2Zeymhs+KLrfccIN9EE=";
};
}
{
name = "1220a1dbe41bc69aacf75026a7158812198ea265fb9cac64dcb91cd31f3b1b8c1f92";
path = fetchZigArtifact {
name = "iterm2_themes";
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/efb1bb1843500a751eb30afa58fe48a6bec8952c.tar.gz";
hash = "sha256-ZdVc1mmLwF45PZiqL/j/l7MO2O6hZ11lqIToGFdHiEU=";
};
}
{
name = "1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402";
path = fetchZigArtifact {
name = "imgui";
url = "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz";
hash = "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA=";
};
}
{
name = "1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d";
path = fetchZigArtifact {
name = "freetype";
url = "https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz";
hash = "sha256-QnIB9dUVFnDQXB9bRb713aHy592XHvVPD+qqf/0quQw=";
};
}
{
name = "1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66";
path = fetchZigArtifact {
name = "libpng";
url = "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz";
hash = "sha256-/syVtGzwXo4/yKQUdQ4LparQDYnp/fF16U/wQcrxoDo=";
};
}
{
name = "1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb";
path = fetchZigArtifact {
name = "zlib";
url = "https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz";
hash = "sha256-F+iIY/NgBnKrSRgvIXKBtvxNPHYr3jYZNeQ2qVIU0Fw=";
};
}
{
name = "12201149afb3326c56c05bb0a577f54f76ac20deece63aa2f5cd6ff31a4fa4fcb3b7";
path = fetchZigArtifact {
name = "fontconfig";
url = "https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz";
hash = "sha256-O6LdkhWHGKzsXKrxpxYEO1qgVcJ7CB2RSvPMtA3OilU=";
};
}
{
name = "122032442d95c3b428ae8e526017fad881e7dc78eab4d558e9a58a80bfbd65a64f7d";
path = fetchZigArtifact {
name = "libxml2";
url = "https://github.com/GNOME/libxml2/archive/refs/tags/v2.11.5.tar.gz";
hash = "sha256-bCgFni4+60K1tLFkieORamNGwQladP7jvGXNxdiaYhU=";
};
}
{
name = "1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122";
path = fetchZigArtifact {
name = "harfbuzz";
url = "https://deps.files.ghostty.org/harfbuzz-1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122.tar.gz";
hash = "sha256-nxygiYE7BZRK0c6MfgGCEwJtNdybq0gKIeuHaDg5ZVY=";
};
}
{
name = "12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b";
path = fetchZigArtifact {
name = "highway";
url = "https://deps.files.ghostty.org/highway-12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b.tar.gz";
hash = "sha256-NUqLRTm1iOcLmOxwhEJz4/J0EwLEw3e8xOgbPRhm98k=";
};
}
{
name = "1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb";
path = fetchZigArtifact {
name = "oniguruma";
url = "https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz";
hash = "sha256-ABqhIC54RI9MC/GkjHblVodrNvFtks4yB+zP1h2Z8qA=";
};
}
{
name = "1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e";
path = fetchZigArtifact {
name = "sentry";
url = "https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz";
hash = "sha256-KsZJfMjWGo0xCT5HrduMmyxFsWsHBbszSoNbZCPDGN8=";
};
}
{
name = "12207fd37bb8251919c112dcdd8f616a491857b34a451f7e4486490077206dc2a1ea";
path = fetchZigArtifact {
name = "breakpad";
url = "https://github.com/getsentry/breakpad/archive/b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz";
hash = "sha256-bMqYlD0amQdmzvYQd8Ca/1k4Bj/heh7+EijlQSttatk=";
};
}
{
name = "1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641";
path = fetchZigArtifact {
name = "utfcpp";
url = "https://deps.files.ghostty.org/utfcpp-1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641.tar.gz";
hash = "sha256-/8ZooxDndgfTk/PBizJxXyI9oerExNbgV5oR345rWc8=";
};
}
{
name = "122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd";
path = fetchZigArtifact {
name = "wuffs";
url = "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz";
hash = "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM=";
};
}
{
name = "12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806";
path = fetchZigArtifact {
name = "pixels";
url = "https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz";
hash = "sha256-Veg7FtCRCCUCvxSb9FfzH0IJLFmCZQ4/+657SIcb8Ro=";
};
}
{
name = "12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1";
path = fetchZigArtifact {
name = "glslang";
url = "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz";
hash = "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U=";
};
}
{
name = "1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da";
path = fetchZigArtifact {
name = "spirv_cross";
url = "https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz";
hash = "sha256-tStvz8Ref6abHwahNiwVVHNETizAmZVVaxVsU7pmV+M=";
};
}
]

38
build.zig.zon.txt generated Normal file
View File

@ -0,0 +1,38 @@
git+https://github.com/rockorager/libvaxis#2237a7059eae99e9f132dd5acd1555e49d6c7d93
git+https://github.com/rockorager/libvaxis/?ref=main#dc0a228a5544988d4a920cfb40be9cd28db41423
git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e
https://codeberg.org/atman/zg/archive/v0.13.2.tar.gz
https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz
https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz
https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz
https://deps.files.ghostty.org/gobject-12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d.tar.zst
https://deps.files.ghostty.org/harfbuzz-1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122.tar.gz
https://deps.files.ghostty.org/highway-12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b.tar.gz
https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz
https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz
https://deps.files.ghostty.org/libxev-1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c.tar.gz
https://deps.files.ghostty.org/mach_glfw-12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62.tar.gz
https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz
https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz
https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz
https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz
https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz
https://deps.files.ghostty.org/utfcpp-1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641.tar.gz
https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz
https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz
https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz
https://deps.files.ghostty.org/z2d-12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a.tar.gz
https://deps.files.ghostty.org/zf-1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8.tar.gz
https://deps.files.ghostty.org/zig-wayland-fbfe3b4ac0b472a27b1f1a67405436c58cbee12d.tar.gz
https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz
https://deps.files.ghostty.org/zig_objc-1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634.tar.gz
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
https://github.com/GNOME/libxml2/archive/refs/tags/v2.11.5.tar.gz
https://github.com/getsentry/breakpad/archive/b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/db227d159adc265818f2e898da0f70ef8d7b580e.tar.gz
https://github.com/mitchellh/glfw/archive/b552c6ec47326b94015feddb36058ea567b87159.tar.gz
https://github.com/mitchellh/vulkan-headers/archive/04c8a0389d5a0236a96312988017cd4ce27d8041.tar.gz
https://github.com/mitchellh/wayland-headers/archive/5f991515a29f994d87b908115a2ab0b899474bd1.tar.gz
https://github.com/mitchellh/x11-headers/archive/2ffbd62d82ff73ec929dd8de802bc95effa0ef88.tar.gz
https://github.com/mitchellh/xcode-frameworks/archive/69801c154c39d7ae6129ea1ba8fe1afe00585fc8.tar.gz

192
build.zig.zon2json-lock generated Normal file
View File

@ -0,0 +1,192 @@
{
"1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c": {
"name": "libxev",
"url": "https://deps.files.ghostty.org/libxev-1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c.tar.gz",
"hash": "sha256-VHP90NTytIZ8UZsYRKOOxN490/I6yv6ec40sP8y5MJ8="
},
"12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62": {
"name": "mach_glfw",
"url": "https://deps.files.ghostty.org/mach_glfw-12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62.tar.gz",
"hash": "sha256-HhXIvWUS8/CHWY4VXPG2ZEo+we8XOn3o5rYJCQ1n8Nk="
},
"1220736fa4ba211162c7a0e46cc8fe04d95921927688bff64ab5da7420d098a7272d": {
"name": "glfw",
"url": "https://github.com/mitchellh/glfw/archive/b552c6ec47326b94015feddb36058ea567b87159.tar.gz",
"hash": "sha256-IeBVAOQmtyFqVxzuXPek1onuPwIamcOyYtxqKpPEQjU="
},
"12202adbfecdad671d585c9a5bfcbd5cdf821726779430047742ce1bf94ad67d19cb": {
"name": "xcode_frameworks",
"url": "https://github.com/mitchellh/xcode-frameworks/archive/69801c154c39d7ae6129ea1ba8fe1afe00585fc8.tar.gz",
"hash": "sha256-mP/I2coL57UJm/3+4Q8sPAgQwk8V4zM+S4VBBTrX2To="
},
"122004bfd4c519dadfb8e6281a42fc34fd1aa15aea654ea8a492839046f9894fa2cf": {
"name": "vulkan_headers",
"url": "https://github.com/mitchellh/vulkan-headers/archive/04c8a0389d5a0236a96312988017cd4ce27d8041.tar.gz",
"hash": "sha256-K+zrRudgHFukOM6En1StRYRMNYkeRk+qHTXvrXaG+FU="
},
"1220b3164434d2ec9db146a40bf3a30f490590d68fa8529776a3138074f0da2c11ca": {
"name": "wayland_headers",
"url": "https://github.com/mitchellh/wayland-headers/archive/5f991515a29f994d87b908115a2ab0b899474bd1.tar.gz",
"hash": "sha256-uFilLZinKkZt6RdVTV3lUmJpzpswDdFva22FvwU/XQI="
},
"122089c326186c84aa2fd034b16abc38f3ebf4862d9ae106dc1847ac44f557b36465": {
"name": "x11_headers",
"url": "https://github.com/mitchellh/x11-headers/archive/2ffbd62d82ff73ec929dd8de802bc95effa0ef88.tar.gz",
"hash": "sha256-EhV2bmTY/OMYN1wEul35gD0hQgS/Al262jO3pVr0O+c="
},
"1220f5aec880d4f430cc1597ede88f1530da69e39a4986080e976b0c7b919c2ebfeb": {
"name": "vaxis",
"url": "git+https://github.com/rockorager/libvaxis#2237a7059eae99e9f132dd5acd1555e49d6c7d93",
"hash": "sha256-ZzLNJOsXzyBhkdUhbET30RoU2T9xKYsBUQz2NAjK/G8="
},
"1220dd654ef941fc76fd96f9ec6adadf83f69b9887a0d3f4ee5ac0a1a3e11be35cf5": {
"name": "zigimg",
"url": "git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e",
"hash": "sha256-oLf3YH3yeg4ikVO/GahMCDRMTU31AHkfSnF4rt7xTKo="
},
"122055beff332830a391e9895c044d33b15ea21063779557024b46169fb1984c6e40": {
"name": "zg",
"url": "https://codeberg.org/atman/zg/archive/v0.13.2.tar.gz",
"hash": "sha256-2x9hT7bYq9KJYWLVOf21a+QvTG/F7HWT+YK15IMRzNY="
},
"12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a": {
"name": "z2d",
"url": "https://deps.files.ghostty.org/z2d-12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a.tar.gz",
"hash": "sha256-P0UJ54RO/vVyDa+UkBl+QEOjzoMMEFSOTexQP/uBXfc="
},
"1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634": {
"name": "zig_objc",
"url": "https://deps.files.ghostty.org/zig_objc-1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634.tar.gz",
"hash": "sha256-H+HIbh2T23uzrsg9/1/vl9Ir1HCAa2pzeTx6zktJH9Q="
},
"12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc": {
"name": "zig_js",
"url": "https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz",
"hash": "sha256-fyNeCVbC9UAaKJY6JhAZlT0A479M/AKYMPIWEZbDWD0="
},
"12207831bce7d4abce57b5a98e8f3635811cfefd160bca022eb91fe905d36a02cf25": {
"name": "ziglyph",
"url": "https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz",
"hash": "sha256-cse98+Ft8QUjX+P88yyYfaxJOJGQ9M7Ymw7jFxDz89k="
},
"12209ca054cb1919fa276e328967f10b253f7537c4136eb48f3332b0f7cf661cad38": {
"name": "zig_wayland",
"url": "https://deps.files.ghostty.org/zig-wayland-fbfe3b4ac0b472a27b1f1a67405436c58cbee12d.tar.gz",
"hash": "sha256-RtAystqK/GRYIquTK1KfD7rRSCrfuzAvCD1Z9DE1ldc="
},
"1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8": {
"name": "zf",
"url": "https://deps.files.ghostty.org/zf-1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8.tar.gz",
"hash": "sha256-/oLryY3VQfjbtQi+UP+n6FJTVA/YxIetjO+6Ovrh6/E="
},
"1220c72c1697dd9008461ead702997a15d8a1c5810247f02e7983b9f74c6c6e4c087": {
"name": "vaxis",
"url": "git+https://github.com/rockorager/libvaxis/?ref=main#dc0a228a5544988d4a920cfb40be9cd28db41423",
"hash": "sha256-QWN4jOrA91KlbqmeEHHJ4HTnCC9nmfxt8DHUXJpAzLI="
},
"12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d": {
"name": "gobject",
"url": "https://deps.files.ghostty.org/gobject-12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d.tar.zst",
"hash": "sha256-UU97kNv/bZzQPKz1djhEDLapLguvfBpFfWVb6FthtcI="
},
"12202cdac858abc52413a6c6711d5026d2d3c8e13f95ca2c327eade0736298bb021f": {
"name": "wayland",
"url": "https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz",
"hash": "sha256-6kGR1o5DdnflHzqs3ieCmBAUTpMdOXoyfcYDXiw5xQ0="
},
"12201a57c6ce0001aa034fa80fba3e1cd2253c560a45748f4f4dd21ff23b491cddef": {
"name": "wayland_protocols",
"url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz",
"hash": "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg="
},
"12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566": {
"name": "plasma_wayland_protocols",
"url": "https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz",
"hash": "sha256-XFi6IUrNjmvKNCbcCLAixGqN2Zeymhs+KLrfccIN9EE="
},
"12203d2647e5daf36a9c85b969e03f422540786ce9ea624eb4c26d204fe1f46218f3": {
"name": "iterm2_themes",
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/db227d159adc265818f2e898da0f70ef8d7b580e.tar.gz",
"hash": "sha256-Iyf7U4rpvNkPX4AOEbYSYGte5+SjRwsWD2luOn1Hz8U="
},
"1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402": {
"name": "imgui",
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
},
"1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d": {
"name": "freetype",
"url": "https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz",
"hash": "sha256-QnIB9dUVFnDQXB9bRb713aHy592XHvVPD+qqf/0quQw="
},
"1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66": {
"name": "libpng",
"url": "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz",
"hash": "sha256-/syVtGzwXo4/yKQUdQ4LparQDYnp/fF16U/wQcrxoDo="
},
"1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb": {
"name": "zlib",
"url": "https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz",
"hash": "sha256-F+iIY/NgBnKrSRgvIXKBtvxNPHYr3jYZNeQ2qVIU0Fw="
},
"12201149afb3326c56c05bb0a577f54f76ac20deece63aa2f5cd6ff31a4fa4fcb3b7": {
"name": "fontconfig",
"url": "https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz",
"hash": "sha256-O6LdkhWHGKzsXKrxpxYEO1qgVcJ7CB2RSvPMtA3OilU="
},
"122032442d95c3b428ae8e526017fad881e7dc78eab4d558e9a58a80bfbd65a64f7d": {
"name": "libxml2",
"url": "https://github.com/GNOME/libxml2/archive/refs/tags/v2.11.5.tar.gz",
"hash": "sha256-bCgFni4+60K1tLFkieORamNGwQladP7jvGXNxdiaYhU="
},
"1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122": {
"name": "harfbuzz",
"url": "https://deps.files.ghostty.org/harfbuzz-1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122.tar.gz",
"hash": "sha256-nxygiYE7BZRK0c6MfgGCEwJtNdybq0gKIeuHaDg5ZVY="
},
"12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b": {
"name": "highway",
"url": "https://deps.files.ghostty.org/highway-12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b.tar.gz",
"hash": "sha256-NUqLRTm1iOcLmOxwhEJz4/J0EwLEw3e8xOgbPRhm98k="
},
"1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb": {
"name": "oniguruma",
"url": "https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz",
"hash": "sha256-ABqhIC54RI9MC/GkjHblVodrNvFtks4yB+zP1h2Z8qA="
},
"1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e": {
"name": "sentry",
"url": "https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz",
"hash": "sha256-KsZJfMjWGo0xCT5HrduMmyxFsWsHBbszSoNbZCPDGN8="
},
"12207fd37bb8251919c112dcdd8f616a491857b34a451f7e4486490077206dc2a1ea": {
"name": "breakpad",
"url": "https://github.com/getsentry/breakpad/archive/b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz",
"hash": "sha256-bMqYlD0amQdmzvYQd8Ca/1k4Bj/heh7+EijlQSttatk="
},
"1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641": {
"name": "utfcpp",
"url": "https://deps.files.ghostty.org/utfcpp-1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641.tar.gz",
"hash": "sha256-/8ZooxDndgfTk/PBizJxXyI9oerExNbgV5oR345rWc8="
},
"122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd": {
"name": "wuffs",
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
"hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM="
},
"12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806": {
"name": "pixels",
"url": "https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz",
"hash": "sha256-Veg7FtCRCCUCvxSb9FfzH0IJLFmCZQ4/+657SIcb8Ro="
},
"12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1": {
"name": "glslang",
"url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
"hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U="
},
"1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da": {
"name": "spirv_cross",
"url": "https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz",
"hash": "sha256-tStvz8Ref6abHwahNiwVVHNETizAmZVVaxVsU7pmV+M="
}
}

View File

@ -7,6 +7,7 @@ Icon=com.mitchellh.ghostty
Categories=System;TerminalEmulator;
Keywords=terminal;tty;pty;
StartupNotify=true
StartupWMClass=com.mitchellh.ghostty
Terminal=false
Actions=new-window;
X-GNOME-UsesNotifications=true

0
dist/linux/ghostty_dolphin.desktop vendored Normal file → Executable file
View File

97
dist/linux/ghostty_nautilus.py vendored Normal file
View File

@ -0,0 +1,97 @@
# Adapted from wezterm: https://github.com/wez/wezterm/blob/main/assets/wezterm-nautilus.py
# original copyright notice:
#
# Copyright (C) 2022 Sebastian Wiesner <sebastian@swsnr.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from os.path import isdir
from gi import require_version
from gi.repository import Nautilus, GObject, Gio, GLib
class OpenInGhosttyAction(GObject.GObject, Nautilus.MenuProvider):
def __init__(self):
super().__init__()
session = Gio.bus_get_sync(Gio.BusType.SESSION, None)
self._systemd = None
# Check if the this system runs under systemd, per sd_booted(3)
if isdir('/run/systemd/system/'):
self._systemd = Gio.DBusProxy.new_sync(session,
Gio.DBusProxyFlags.NONE,
None,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager", None)
def _open_terminal(self, path):
cmd = ['ghostty', f'--working-directory={path}', '--gtk-single-instance=false']
child = Gio.Subprocess.new(cmd, Gio.SubprocessFlags.NONE)
if self._systemd:
# Move new terminal into a dedicated systemd scope to make systemd
# track the terminal separately; in particular this makes systemd
# keep a separate CPU and memory account for the terminal which in turn
# ensures that oomd doesn't take nautilus down if a process in
# ghostty consumes a lot of memory.
pid = int(child.get_identifier())
props = [("PIDs", GLib.Variant('au', [pid])),
('CollectMode', GLib.Variant('s', 'inactive-or-failed'))]
name = 'app-nautilus-com.mitchellh.ghostty-{}.scope'.format(pid)
args = GLib.Variant('(ssa(sv)a(sa(sv)))', (name, 'fail', props, []))
self._systemd.call_sync('StartTransientUnit', args,
Gio.DBusCallFlags.NO_AUTO_START, 500, None)
def _menu_item_activated(self, _menu, paths):
for path in paths:
self._open_terminal(path)
def _make_item(self, name, paths):
item = Nautilus.MenuItem(name=name, label='Open in Ghostty',
icon='com.mitchellh.ghostty')
item.connect('activate', self._menu_item_activated, paths)
return item
def _paths_to_open(self, files):
paths = []
for file in files:
location = file.get_location() if file.is_directory() else file.get_parent_location()
path = location.get_path()
if path and path not in paths:
paths.append(path)
if 10 < len(paths):
# Let's not open anything if the user selected a lot of directories,
# to avoid accidentally spamming their desktop with dozends of
# new windows or tabs. Ten is a totally arbitrary limit :)
return []
else:
return paths
def get_file_items(self, *args):
# Nautilus 3.0 API passes args (window, files), 4.0 API just passes files
files = args[0] if len(args) == 1 else args[1]
paths = self._paths_to_open(files)
if paths:
return [self._make_item(name='GhosttyNautilus::open_in_ghostty', paths=paths)]
else:
return []
def get_background_items(self, *args):
# Nautilus 3.0 API passes args (window, file), 4.0 API just passes file
file = args[0] if len(args) == 1 else args[1]
paths = self._paths_to_open([file])
if paths:
return [self._make_item(name='GhosttyNautilus::open_folder_in_ghostty', paths=paths)]
else:
return []

View File

@ -21,6 +21,7 @@ from datetime import datetime, timezone
now = datetime.now(timezone.utc)
version = os.environ["GHOSTTY_VERSION"]
version_dash = version.replace('.', '-')
build = os.environ["GHOSTTY_BUILD"]
commit = os.environ["GHOSTTY_COMMIT"]
commit_long = os.environ["GHOSTTY_COMMIT_LONG"]
@ -82,6 +83,8 @@ elem = ET.SubElement(item, "sparkle:shortVersionString")
elem.text = f"{version}"
elem = ET.SubElement(item, "sparkle:minimumSystemVersion")
elem.text = "13.0.0"
elem = ET.SubElement(item, "sparkle:fullReleaseNotesLink")
elem.text = f"https://ghostty.org/docs/install/release-notes/{version_dash}"
elem = ET.SubElement(item, "description")
elem.text = f"""
<h1>Ghostty v{version}</h1>
@ -91,8 +94,8 @@ on {now.strftime('%Y-%m-%d')}.
</p>
<p>
We don't currently generate release notes for auto-updates.
You can view the complete changelog and release notes on
the <a href="https://ghostty.org">Ghostty website</a>.
You can view the complete changelog and release notes
at <a href="https://ghostty.org/docs/install/release-notes/{version_dash}">ghostty.org/docs/install/release-notes/{version_dash}</a>.
</p>
"""
elem = ET.SubElement(item, "enclosure")

62
flake.lock generated
View File

@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
@ -21,11 +21,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@ -36,11 +36,11 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1733423277,
"narHash": "sha256-TxabjxEgkNbCGFRHgM/b9yZWlBj60gUOUnRT/wbVQR8=",
"lastModified": 1738255539,
"narHash": "sha256-hP2eOqhIO/OILW+3moNWO4GtdJFYCqAe9yJZgvlCoDQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e36963a147267afc055f7cf65225958633e536bf",
"rev": "c3511a3b53b482aa7547c9d1626fd7310c1de1c5",
"type": "github"
},
"original": {
@ -52,11 +52,11 @@
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1733229606,
"narHash": "sha256-FLYY5M0rpa5C2QAE3CKLYAM6TwbKicdRK6qNrSHlNrE=",
"lastModified": 1738136902,
"narHash": "sha256-pUvLijVGARw4u793APze3j6mU1Zwdtz7hGkGGkD87qw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "566e53c2ad750c84f6d31f9ccb9d00f823165550",
"rev": "9a5db3142ce450045840cc8d832b13b8a2018e0c",
"type": "github"
},
"original": {
@ -69,9 +69,11 @@
"root": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nixpkgs-stable": "nixpkgs-stable",
"nixpkgs-unstable": "nixpkgs-unstable",
"zig": "zig"
"zig": "zig",
"zig2nix": "zig2nix"
}
},
"systems": {
@ -92,17 +94,19 @@
"zig": {
"inputs": {
"flake-compat": [],
"flake-utils": "flake-utils",
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs-stable"
]
},
"locked": {
"lastModified": 1717848532,
"narHash": "sha256-d+xIUvSTreHl8pAmU1fnmkfDTGQYCn2Rb/zOwByxS2M=",
"lastModified": 1738239110,
"narHash": "sha256-Y5i9mQ++dyIQr+zEPNy+KIbc5wjPmfllBrag3cHZgcE=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "02fc5cc555fc14fda40c42d7c3250efa43812b43",
"rev": "1a8fb6f3a04724519436355564b95fce5e272504",
"type": "github"
},
"original": {
@ -110,6 +114,30 @@
"repo": "zig-overlay",
"type": "github"
}
},
"zig2nix": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs-stable"
]
},
"locked": {
"lastModified": 1738263917,
"narHash": "sha256-j/3fwe2pEOquHabP/puljOKwAZFjIE9gXZqA91sC48M=",
"owner": "jcollie",
"repo": "zig2nix",
"rev": "c311d8e77a6ee0d995f40a6e10a89a3a4ab04f9a",
"type": "github"
},
"original": {
"owner": "jcollie",
"ref": "c311d8e77a6ee0d995f40a6e10a89a3a4ab04f9a",
"repo": "zig2nix",
"type": "github"
}
}
},
"root": "root",

106
flake.nix
View File

@ -8,6 +8,7 @@
# glibc versions used by our dependencies from Nix are compatible with the
# system glibc that the user is building for.
nixpkgs-stable.url = "github:nixos/nixpkgs/release-24.11";
flake-utils.url = "github:numtide/flake-utils";
# Used for shell.nix
flake-compat = {
@ -19,9 +20,18 @@
url = "github:mitchellh/zig-overlay";
inputs = {
nixpkgs.follows = "nixpkgs-stable";
flake-utils.follows = "flake-utils";
flake-compat.follows = "";
};
};
zig2nix = {
url = "github:jcollie/zig2nix?ref=c311d8e77a6ee0d995f40a6e10a89a3a4ab04f9a";
inputs = {
nixpkgs.follows = "nixpkgs-stable";
flake-utils.follows = "flake-utils";
};
};
};
outputs = {
@ -29,40 +39,86 @@
nixpkgs-unstable,
nixpkgs-stable,
zig,
zig2nix,
...
}:
builtins.foldl' nixpkgs-stable.lib.recursiveUpdate {} (builtins.map (system: let
pkgs-stable = nixpkgs-stable.legacyPackages.${system};
pkgs-unstable = nixpkgs-unstable.legacyPackages.${system};
in {
devShell.${system} = pkgs-stable.callPackage ./nix/devShell.nix {
zig = zig.packages.${system}."0.13.0";
wraptest = pkgs-stable.callPackage ./nix/wraptest.nix {};
};
builtins.foldl' nixpkgs-stable.lib.recursiveUpdate {} (
builtins.map (
system: let
pkgs-stable = nixpkgs-stable.legacyPackages.${system};
pkgs-unstable = nixpkgs-unstable.legacyPackages.${system};
in {
devShell.${system} = pkgs-stable.callPackage ./nix/devShell.nix {
zig = zig.packages.${system}."0.13.0";
wraptest = pkgs-stable.callPackage ./nix/wraptest.nix {};
zig2nix = zig2nix;
};
packages.${system} = let
mkArgs = optimize: {
inherit optimize;
packages.${system} = let
mkArgs = optimize: {
inherit optimize;
revision = self.shortRev or self.dirtyShortRev or "dirty";
};
in rec {
ghostty-debug = pkgs-stable.callPackage ./nix/package.nix (mkArgs "Debug");
ghostty-releasesafe = pkgs-stable.callPackage ./nix/package.nix (mkArgs "ReleaseSafe");
ghostty-releasefast = pkgs-stable.callPackage ./nix/package.nix (mkArgs "ReleaseFast");
revision = self.shortRev or self.dirtyShortRev or "dirty";
};
in rec {
deps = pkgs-stable.callPackage ./build.zig.zon.nix {};
ghostty-debug = pkgs-stable.callPackage ./nix/package.nix (mkArgs "Debug");
ghostty-releasesafe = pkgs-stable.callPackage ./nix/package.nix (mkArgs "ReleaseSafe");
ghostty-releasefast = pkgs-stable.callPackage ./nix/package.nix (mkArgs "ReleaseFast");
ghostty = ghostty-releasefast;
default = ghostty;
};
ghostty = ghostty-releasefast;
default = ghostty;
};
formatter.${system} = pkgs-stable.alejandra;
formatter.${system} = pkgs-stable.alejandra;
# Our supported systems are the same supported systems as the Zig binaries.
}) (builtins.attrNames zig.packages))
apps.${system} = let
runVM = (
module: let
vm = import ./nix/vm/create.nix {
inherit system module;
nixpkgs = nixpkgs-stable;
overlay = self.overlays.debug;
};
program = pkgs-stable.writeShellScript "run-ghostty-vm" ''
SHARED_DIR=$(pwd)
export SHARED_DIR
${pkgs-stable.lib.getExe vm.config.system.build.vm} "$@"
'';
in {
type = "app";
program = "${program}";
}
);
in {
wayland-cinnamon = runVM ./nix/vm/wayland-cinnamon.nix;
wayland-gnome = runVM ./nix/vm/wayland-gnome.nix;
wayland-plasma6 = runVM ./nix/vm/wayland-plasma6.nix;
x11-cinnamon = runVM ./nix/vm/x11-cinnamon.nix;
x11-gnome = runVM ./nix/vm/x11-gnome.nix;
x11-plasma6 = runVM ./nix/vm/x11-plasma6.nix;
x11-xfce = runVM ./nix/vm/x11-xfce.nix;
};
}
# Our supported systems are the same supported systems as the Zig binaries.
) (builtins.attrNames zig.packages)
)
// {
overlays.default = final: prev: {
ghostty = self.packages.${prev.system}.default;
overlays = {
default = self.overlays.releasefast;
releasefast = final: prev: {
ghostty = self.packages.${prev.system}.ghostty-releasefast;
};
debug = final: prev: {
ghostty = self.packages.${prev.system}.ghostty-debug;
};
};
create-vm = import ./nix/vm/create.nix;
create-cinnamon-vm = import ./nix/vm/create-cinnamon.nix;
create-gnome-vm = import ./nix/vm/create-gnome.nix;
create-plasma6-vm = import ./nix/vm/create-plasma6.nix;
create-xfce-vm = import ./nix/vm/create-xfce.nix;
};
nixConfig = {

View File

@ -159,7 +159,7 @@ typedef enum {
GHOSTTY_KEY_EQUAL,
GHOSTTY_KEY_LEFT_BRACKET, // [
GHOSTTY_KEY_RIGHT_BRACKET, // ]
GHOSTTY_KEY_BACKSLASH, // /
GHOSTTY_KEY_BACKSLASH, // \
// control
GHOSTTY_KEY_UP,
@ -412,6 +412,7 @@ typedef enum {
GHOSTTY_FULLSCREEN_NATIVE,
GHOSTTY_FULLSCREEN_NON_NATIVE,
GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU,
GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH,
} ghostty_action_fullscreen_e;
// apprt.action.SecureInput
@ -562,8 +563,10 @@ typedef enum {
GHOSTTY_ACTION_QUIT,
GHOSTTY_ACTION_NEW_WINDOW,
GHOSTTY_ACTION_NEW_TAB,
GHOSTTY_ACTION_CLOSE_TAB,
GHOSTTY_ACTION_NEW_SPLIT,
GHOSTTY_ACTION_CLOSE_ALL_WINDOWS,
GHOSTTY_ACTION_TOGGLE_MAXIMIZE,
GHOSTTY_ACTION_TOGGLE_FULLSCREEN,
GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW,
GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS,
@ -583,6 +586,7 @@ typedef enum {
GHOSTTY_ACTION_RENDER_INSPECTOR,
GHOSTTY_ACTION_DESKTOP_NOTIFICATION,
GHOSTTY_ACTION_SET_TITLE,
GHOSTTY_ACTION_PROMPT_TITLE,
GHOSTTY_ACTION_PWD,
GHOSTTY_ACTION_MOUSE_SHAPE,
GHOSTTY_ACTION_MOUSE_VISIBILITY,
@ -642,7 +646,7 @@ typedef void (*ghostty_runtime_write_clipboard_cb)(void*,
ghostty_clipboard_e,
bool);
typedef void (*ghostty_runtime_close_surface_cb)(void*, bool);
typedef void (*ghostty_runtime_action_cb)(ghostty_app_t,
typedef bool (*ghostty_runtime_action_cb)(ghostty_app_t,
ghostty_target_s,
ghostty_action_s);

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 KiB

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "macOS-AppIcon-1024px.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

View File

@ -69,9 +69,13 @@
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */; };
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5D02AE0DEA7009128F3 /* MetalView.swift */; };
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; };
A5A2A3CA2D4445E30033CF96 /* Dock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A2A3C92D4445E20033CF96 /* Dock.swift */; };
A5A2A3CC2D444ABB0033CF96 /* NSApplication+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A2A3CB2D444AB80033CF96 /* NSApplication+Extension.swift */; };
A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A6F7292CC41B8700B232A5 /* Xcode.swift */; };
A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */; };
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */; };
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378D2D31D6C100931030 /* Weak.swift */; };
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */; };
A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0572C9F30860017A1AE /* Cursor.swift */; };
A5CBD0592C9F37B10017A1AE /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; };
@ -102,6 +106,7 @@
C159E89D2B69A2EF00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; };
C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F26EA62B738B9900404083 /* NSView+Extension.swift */; };
C1F26EE92B76CBFC00404083 /* VibrantLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = C1F26EE82B76CBFC00404083 /* VibrantLayer.m */; };
CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFBB5FE92D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift */; };
FC5218FA2D10FFCE004C93E0 /* zsh in Resources */ = {isa = PBXBuildFile; fileRef = FC5218F92D10FFC7004C93E0 /* zsh */; };
FC9ABA9C2D0F53F80020D4C8 /* bash-completion in Resources */ = {isa = PBXBuildFile; fileRef = FC9ABA9B2D0F538D0020D4C8 /* bash-completion */; };
/* End PBXBuildFile section */
@ -161,11 +166,15 @@
A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorView.swift; sourceTree = "<group>"; };
A59FB5D02AE0DEA7009128F3 /* MetalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalView.swift; sourceTree = "<group>"; };
A5A1F8842A489D6800D1E8BC /* terminfo */ = {isa = PBXFileReference; lastKnownFileType = folder; name = terminfo; path = "../zig-out/share/terminfo"; sourceTree = "<group>"; };
A5A2A3C92D4445E20033CF96 /* Dock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dock.swift; sourceTree = "<group>"; };
A5A2A3CB2D444AB80033CF96 /* NSApplication+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSApplication+Extension.swift"; sourceTree = "<group>"; };
A5A6F7292CC41B8700B232A5 /* Xcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xcode.swift; sourceTree = "<group>"; };
A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastWindowPosition.swift; sourceTree = "<group>"; };
A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; };
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardLayout.swift; sourceTree = "<group>"; };
A5CA378D2D31D6C100931030 /* Weak.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weak.swift; sourceTree = "<group>"; };
A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableWindowView.swift; sourceTree = "<group>"; };
A5CBD0572C9F30860017A1AE /* Cursor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cursor.swift; sourceTree = "<group>"; };
A5CBD05B2CA0C5C70017A1AE /* QuickTerminal.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QuickTerminal.xib; sourceTree = "<group>"; };
@ -198,6 +207,7 @@
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VibrantLayer.h; sourceTree = "<group>"; };
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VibrantLayer.m; sourceTree = "<group>"; };
C1F26EEA2B76CC2400404083 /* ghostty-bridging-header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ghostty-bridging-header.h"; sourceTree = "<group>"; };
CFBB5FE92D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalSpaceBehavior.swift; sourceTree = "<group>"; };
FC5218F92D10FFC7004C93E0 /* zsh */ = {isa = PBXFileReference; lastKnownFileType = folder; name = zsh; path = "../zig-out/share/zsh"; sourceTree = "<group>"; };
FC9ABA9B2D0F538D0020D4C8 /* bash-completion */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "bash-completion"; path = "../zig-out/share/bash-completion"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -262,11 +272,13 @@
A534263D2A7DCBB000EBB7A2 /* Helpers */ = {
isa = PBXGroup;
children = (
A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */,
A5A6F7292CC41B8700B232A5 /* Xcode.swift */,
A5CEAFFE29C2410700646FDA /* Backport.swift */,
A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */,
A5CBD0572C9F30860017A1AE /* Cursor.swift */,
A5D0AF3C2B37804400D21823 /* CodableBridge.swift */,
A5A2A3C92D4445E20033CF96 /* Dock.swift */,
A52FFF582CAA4FF1000C6A5B /* Fullscreen.swift */,
A59630962AEE163600D64628 /* HostingWindow.swift */,
A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */,
@ -274,12 +286,14 @@
A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */,
C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */,
A599CDAF2CF103F20049FA26 /* NSAppearance+Extension.swift */,
A5A2A3CB2D444AB80033CF96 /* NSApplication+Extension.swift */,
A54B0CEA2D0CFB4A00CBEFF8 /* NSImage+Extension.swift */,
A52FFF5C2CAB4D05000C6A5B /* NSScreen+Extension.swift */,
C1F26EA62B738B9900404083 /* NSView+Extension.swift */,
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */,
A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
A5CC36142C9CDA03004D6760 /* View+Extension.swift */,
A5CA378D2D31D6C100931030 /* Weak.swift */,
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */,
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */,
A5CEAFDA29B8005900646FDA /* SplitView */,
@ -448,6 +462,7 @@
children = (
A5CBD05B2CA0C5C70017A1AE /* QuickTerminal.xib */,
A5CBD05D2CA0C5E70017A1AE /* QuickTerminalController.swift */,
CFBB5FE92D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift */,
A5CBD0632CA122E70017A1AE /* QuickTerminalPosition.swift */,
A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */,
A5CBD05F2CA0C9080017A1AE /* QuickTerminalWindow.swift */,
@ -611,11 +626,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */,
A59630A42AF059BB00D64628 /* Ghostty.SplitNode.swift in Sources */,
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */,
A54B0CEB2D0CFB4C00CBEFF8 /* NSImage+Extension.swift in Sources */,
A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */,
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */,
A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */,
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */,
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
@ -628,6 +645,7 @@
A5CBD0602CA0C90A0017A1AE /* QuickTerminalWindow.swift in Sources */,
A5CBD05E2CA0C5EC0017A1AE /* QuickTerminalController.swift in Sources */,
A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */,
A5A2A3CA2D4445E30033CF96 /* Dock.swift in Sources */,
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */,
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */,
@ -643,12 +661,14 @@
A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */,
A52FFF5B2CAA54B1000C6A5B /* FullscreenMode+Extension.swift in Sources */,
A5333E222B5A2128008AEFF7 /* SurfaceView_AppKit.swift in Sources */,
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */,
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */,
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */,
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
A56D58862ACDDB4100508D2C /* Ghostty.Shell.swift in Sources */,
A5985CD72C320C4500C57AD3 /* String+Extension.swift in Sources */,
A5A2A3CC2D444ABB0033CF96 /* NSApplication+Extension.swift in Sources */,
A59630A22AF0415000D64628 /* Ghostty.TerminalSplit.swift in Sources */,
A5FEB3002ABB69450068369E /* main.swift in Sources */,
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,

View File

@ -6,8 +6,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/sparkle-project/Sparkle",
"state" : {
"revision" : "b456fd404954a9e13f55aa0c88cd5a40b8399638",
"version" : "2.6.3"
"revision" : "0ef1ee0220239b3776f433314515fd849025673f",
"version" : "2.6.4"
}
}
],

View File

@ -30,15 +30,18 @@ class AppDelegate: NSObject,
@IBOutlet private var menuSplitRight: NSMenuItem?
@IBOutlet private var menuSplitDown: NSMenuItem?
@IBOutlet private var menuClose: NSMenuItem?
@IBOutlet private var menuCloseTab: NSMenuItem?
@IBOutlet private var menuCloseWindow: NSMenuItem?
@IBOutlet private var menuCloseAllWindows: NSMenuItem?
@IBOutlet private var menuCopy: NSMenuItem?
@IBOutlet private var menuPaste: NSMenuItem?
@IBOutlet private var menuPasteSelection: NSMenuItem?
@IBOutlet private var menuSelectAll: NSMenuItem?
@IBOutlet private var menuToggleVisibility: NSMenuItem?
@IBOutlet private var menuToggleFullScreen: NSMenuItem?
@IBOutlet private var menuBringAllToFront: NSMenuItem?
@IBOutlet private var menuZoomSplit: NSMenuItem?
@IBOutlet private var menuPreviousSplit: NSMenuItem?
@IBOutlet private var menuNextSplit: NSMenuItem?
@ -50,6 +53,7 @@ class AppDelegate: NSObject,
@IBOutlet private var menuIncreaseFontSize: NSMenuItem?
@IBOutlet private var menuDecreaseFontSize: NSMenuItem?
@IBOutlet private var menuResetFontSize: NSMenuItem?
@IBOutlet private var menuChangeTitle: NSMenuItem?
@IBOutlet private var menuQuickTerminal: NSMenuItem?
@IBOutlet private var menuTerminalInspector: NSMenuItem?
@ -90,10 +94,8 @@ class AppDelegate: NSObject,
return ProcessInfo.processInfo.systemUptime - applicationLaunchTime
}
/// Tracks whether the application is currently visible. This can be gamed, i.e. if a user manually
/// brings each window one by one to the front. But at worst its off by one set of toggles and this
/// makes our logic very easy.
private var isVisible: Bool = true
/// Tracks the windows that we hid for toggleVisibility.
private var hiddenState: ToggleVisibilityState? = nil
/// The observer for the app appearance.
private var appearanceObserver: NSKeyValueObservation? = nil
@ -217,15 +219,20 @@ class AppDelegate: NSObject,
}
func applicationDidBecomeActive(_ notification: Notification) {
guard !applicationHasBecomeActive else { return }
applicationHasBecomeActive = true
// If we're back manually then clear the hidden state because macOS handles it.
self.hiddenState = nil
// Let's launch our first window. We only do this if we have no other windows. It
// is possible to have other windows in a few scenarios:
// - if we're opening a URL since `application(_:openFile:)` is called before this.
// - if we're restoring from persisted state
if terminalManager.windows.count == 0 && derivedConfig.initialWindow {
terminalManager.newWindow()
// First launch stuff
if (!applicationHasBecomeActive) {
applicationHasBecomeActive = true
// Let's launch our first window. We only do this if we have no other windows. It
// is possible to have other windows in a few scenarios:
// - if we're opening a URL since `application(_:openFile:)` is called before this.
// - if we're restoring from persisted state
if terminalManager.windows.count == 0 && derivedConfig.initialWindow {
terminalManager.newWindow()
}
}
}
@ -240,7 +247,13 @@ class AppDelegate: NSObject,
// This probably isn't fully safe. The isEmpty check above is aspirational, it doesn't
// quite work with SwiftUI because windows are retained on close. So instead we check
// if there are any that are visible. I'm guessing this breaks under certain scenarios.
if (windows.allSatisfy { !$0.isVisible }) { return .terminateNow }
//
// NOTE(mitchellh): I don't think we need this check at all anymore. I'm keeping it
// here because I don't want to remove it in a patch release cycle but we should
// target removing it soon.
if (self.quickController == nil && windows.allSatisfy { !$0.isVisible }) {
return .terminateNow
}
// If the user is shutting down, restarting, or logging out, we don't confirm quit.
why: if let event = NSAppleEventManager.shared().currentAppleEvent {
@ -346,6 +359,7 @@ class AppDelegate: NSObject,
syncMenuShortcut(config, action: "new_window", menuItem: self.menuNewWindow)
syncMenuShortcut(config, action: "new_tab", menuItem: self.menuNewTab)
syncMenuShortcut(config, action: "close_surface", menuItem: self.menuClose)
syncMenuShortcut(config, action: "close_tab", menuItem: self.menuCloseTab)
syncMenuShortcut(config, action: "close_window", menuItem: self.menuCloseWindow)
syncMenuShortcut(config, action: "close_all_windows", menuItem: self.menuCloseAllWindows)
syncMenuShortcut(config, action: "new_split:right", menuItem: self.menuSplitRight)
@ -353,6 +367,7 @@ class AppDelegate: NSObject,
syncMenuShortcut(config, action: "copy_to_clipboard", menuItem: self.menuCopy)
syncMenuShortcut(config, action: "paste_from_clipboard", menuItem: self.menuPaste)
syncMenuShortcut(config, action: "paste_from_selection", menuItem: self.menuPasteSelection)
syncMenuShortcut(config, action: "select_all", menuItem: self.menuSelectAll)
syncMenuShortcut(config, action: "toggle_split_zoom", menuItem: self.menuZoomSplit)
@ -371,6 +386,7 @@ class AppDelegate: NSObject,
syncMenuShortcut(config, action: "increase_font_size:1", menuItem: self.menuIncreaseFontSize)
syncMenuShortcut(config, action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize)
syncMenuShortcut(config, action: "reset_font_size", menuItem: self.menuResetFontSize)
syncMenuShortcut(config, action: "change_title_prompt", menuItem: self.menuChangeTitle)
syncMenuShortcut(config, action: "toggle_quick_terminal", menuItem: self.menuQuickTerminal)
syncMenuShortcut(config, action: "toggle_visibility", menuItem: self.menuToggleVisibility)
syncMenuShortcut(config, action: "inspector:toggle", menuItem: self.menuTerminalInspector)
@ -424,7 +440,7 @@ class AppDelegate: NSObject,
// If we have a main window then we don't process any of the keys
// because we let it capture and propagate.
guard NSApp.mainWindow == nil else { return event }
// If this event as-is would result in a key binding then we send it.
if let app = ghostty.app,
ghostty_app_key_is_binding(
@ -440,26 +456,26 @@ class AppDelegate: NSObject,
return nil
}
}
// If this event would be handled by our menu then we do nothing.
if let mainMenu = NSApp.mainMenu,
mainMenu.performKeyEquivalent(with: event) {
return nil
}
// If we reach this point then we try to process the key event
// through the Ghostty key mechanism.
// Ghostty must be loaded
guard let ghostty = self.ghostty.app else { return event }
// Build our event input and call ghostty
if (ghostty_app_key(ghostty, event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS))) {
// The key was used so we want to stop it from going to our Mac app
Ghostty.logger.debug("local key event handled event=\(event)")
return nil
}
return event
}
@ -517,6 +533,15 @@ class AppDelegate: NSObject,
// AppKit mutex on the appearance.
DispatchQueue.main.async { self.syncAppearance(config: config) }
// Decide whether to hide/unhide app from dock and app switcher
switch (config.macosHidden) {
case .never:
NSApp.setActivationPolicy(.regular)
case .always:
NSApp.setActivationPolicy(.accessory)
}
// If we have configuration errors, we need to show them.
let c = ConfigurationErrorsController.sharedInstance
c.errors = config.errors
@ -550,6 +575,30 @@ class AppDelegate: NSObject,
self.appIcon = nil
break
case .blueprint:
self.appIcon = NSImage(named: "BlueprintImage")!
case .chalkboard:
self.appIcon = NSImage(named: "ChalkboardImage")!
case .glass:
self.appIcon = NSImage(named: "GlassImage")!
case .holographic:
self.appIcon = NSImage(named: "HolographicImage")!
case .microchip:
self.appIcon = NSImage(named: "MicrochipImage")!
case .paper:
self.appIcon = NSImage(named: "PaperImage")!
case .retro:
self.appIcon = NSImage(named: "RetroImage")!
case .xray:
self.appIcon = NSImage(named: "XrayImage")!
case .customStyle:
guard let ghostColor = config.macosIconGhostColor else { break }
guard let screenColors = config.macosIconScreenColor else { break }
@ -702,21 +751,35 @@ class AppDelegate: NSObject,
/// Toggles visibility of all Ghosty Terminal windows. When hidden, activates Ghostty as the frontmost application
@IBAction func toggleVisibility(_ sender: Any) {
// We only care about terminal windows.
for window in NSApp.windows.filter({ $0.windowController is BaseTerminalController }) {
if isVisible {
window.orderOut(nil)
} else {
window.makeKeyAndOrderFront(nil)
}
// If we have focus, then we hide all windows.
if NSApp.isActive {
// Toggle visibility doesn't do anything if the focused window is native
// fullscreen. This is only relevant if Ghostty is active.
guard let keyWindow = NSApp.keyWindow,
!keyWindow.styleMask.contains(.fullScreen) else { return }
// Keep track of our hidden state to restore properly
self.hiddenState = .init()
NSApp.hide(nil)
return
}
// After bringing them all to front we make sure our app is active too.
if !isVisible {
// If we're not active, we want to become active
NSApp.activate(ignoringOtherApps: true)
// Bring all windows to the front. Note: we don't use NSApp.unhide because
// that will unhide ALL hidden windows. We want to only bring forward the
// ones that we hid.
hiddenState?.restore()
hiddenState = nil
}
@IBAction func bringAllToFront(_ sender: Any) {
if !NSApp.isActive {
NSApp.activate(ignoringOtherApps: true)
}
isVisible.toggle()
NSApplication.shared.arrangeInFront(sender)
}
private struct DerivedConfig {
@ -736,4 +799,33 @@ class AppDelegate: NSObject,
self.quickTerminalPosition = config.quickTerminalPosition
}
}
private struct ToggleVisibilityState {
let hiddenWindows: [Weak<NSWindow>]
let keyWindow: Weak<NSWindow>?
init() {
// We need to know the key window so that we can bring focus back to the
// right window if it was hidden.
self.keyWindow = if let keyWindow = NSApp.keyWindow {
.init(keyWindow)
} else {
nil
}
// We need to keep track of the windows that were visible because we only
// want to bring back these windows if we remove the toggle.
//
// We also ignore fullscreen windows because they don't hide anyways.
self.hiddenWindows = NSApp.windows.filter {
$0.isVisible &&
!$0.styleMask.contains(.fullScreen)
}.map { Weak($0) }
}
func restore() {
hiddenWindows.forEach { $0.value?.orderFrontRegardless() }
keyWindow?.value?.makeKey()
}
}
}

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23094" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23504" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23094"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23504"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -14,9 +14,12 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="bbz-4X-AYv" userLabel="AppDelegate" customClass="AppDelegate" customModule="Ghostty" customModuleProvider="target">
<connections>
<outlet property="menuBringAllToFront" destination="LE2-aR-0XJ" id="AP9-oK-60V"/>
<outlet property="menuChangeTitle" destination="24I-xg-qIq" id="kg6-kT-jNL"/>
<outlet property="menuCheckForUpdates" destination="GEA-5y-yzH" id="0nV-Tf-nJQ"/>
<outlet property="menuClose" destination="DVo-aG-piG" id="R3t-0C-aSU"/>
<outlet property="menuCloseAllWindows" destination="yKr-Vi-Yqw" id="Zet-Ir-zbm"/>
<outlet property="menuCloseTab" destination="Obb-Mk-j8J" id="Gda-L0-gdz"/>
<outlet property="menuCloseWindow" destination="W5w-UZ-crk" id="6ff-BT-ENV"/>
<outlet property="menuCopy" destination="Jqf-pv-Zcu" id="bKd-1C-oy9"/>
<outlet property="menuDecreaseFontSize" destination="kzb-SZ-dOA" id="Y1B-Vh-6Z2"/>
@ -31,6 +34,7 @@
<outlet property="menuNextSplit" destination="bD7-ei-wKU" id="LeT-xw-eh4"/>
<outlet property="menuOpenConfig" destination="BOF-NM-1cW" id="Nze-Go-glw"/>
<outlet property="menuPaste" destination="i27-pK-umN" id="ICc-X2-gV3"/>
<outlet property="menuPasteSelection" destination="akq-ov-Jjh" id="GS8-aQ-hVw"/>
<outlet property="menuPreviousSplit" destination="Lic-px-1wg" id="Rto-CG-yRe"/>
<outlet property="menuQuickTerminal" destination="1pv-LF-NBJ" id="glN-5B-IGi"/>
<outlet property="menuQuit" destination="4sb-4s-VLi" id="qYN-S1-6UW"/>
@ -154,6 +158,12 @@
<action selector="close:" target="-1" id="tTZ-2b-Mbm"/>
</connections>
</menuItem>
<menuItem title="Close Tab" id="Obb-Mk-j8J">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="closeTab:" target="-1" id="UBb-Bd-nkj"/>
</connections>
</menuItem>
<menuItem title="Close Window" id="W5w-UZ-crk">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
@ -185,6 +195,12 @@
<action selector="paste:" target="-1" id="ZKe-2B-mel"/>
</connections>
</menuItem>
<menuItem title="Paste Selection" id="akq-ov-Jjh">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="pasteSelection:" target="-1" id="vo3-Rf-Udb"/>
</connections>
</menuItem>
<menuItem title="Select All" id="q2h-lq-e4r">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
@ -218,6 +234,13 @@
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="L3L-I8-sqk"/>
<menuItem title="Change Title..." id="24I-xg-qIq">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="changeTitle:" target="-1" id="XuL-QB-Q9l"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="Vkj-tP-dMZ"/>
<menuItem title="Quick Terminal" id="1pv-LF-NBJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
@ -256,12 +279,6 @@
<action selector="toggleGhosttyFullScreen:" target="-1" id="QB9-7R-xyc"/>
</connections>
</menuItem>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
<menuItem title="Show/Hide All Terminals" id="DOX-wA-ilh">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
@ -356,6 +373,13 @@
</items>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="dgt-Tx-d4e"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>

View File

@ -3,6 +3,12 @@ import Cocoa
import SwiftUI
import GhosttyKit
// This is a Apple's private function that we need to call to get the active space.
@_silgen_name("CGSGetActiveSpace")
func CGSGetActiveSpace(_ cid: Int) -> size_t
@_silgen_name("CGSMainConnectionID")
func CGSMainConnectionID() -> Int
/// Controller for the "quick" terminal.
class QuickTerminalController: BaseTerminalController {
override var windowNibName: NSNib.Name? { "QuickTerminal" }
@ -18,6 +24,12 @@ class QuickTerminalController: BaseTerminalController {
/// application to the front.
private var previousApp: NSRunningApplication? = nil
// The active space when the quick terminal was last shown.
private var previousActiveSpace: size_t = 0
/// Non-nil if we have hidden dock state.
private var hiddenDock: HiddenDock? = nil
/// The configuration derived from the Ghostty config so we don't need to rely on references.
private var derivedConfig: DerivedConfig
@ -32,6 +44,11 @@ class QuickTerminalController: BaseTerminalController {
// Setup our notifications for behaviors
let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(applicationWillTerminate(_:)),
name: NSApplication.willTerminateNotification,
object: nil)
center.addObserver(
self,
selector: #selector(onToggleFullscreen),
@ -52,6 +69,9 @@ class QuickTerminalController: BaseTerminalController {
// Remove all of our notificationcenter subscriptions
let center = NotificationCenter.default
center.removeObserver(self)
// Make sure we restore our hidden dock
hiddenDock = nil
}
// MARK: NSWindowController
@ -87,6 +107,17 @@ class QuickTerminalController: BaseTerminalController {
// MARK: NSWindowDelegate
override func windowDidBecomeKey(_ notification: Notification) {
super.windowDidBecomeKey(notification)
// If we're not visible we don't care to run the logic below. It only
// applies if we can be seen.
guard visible else { return }
// Re-hide the dock if we were hiding it before.
hiddenDock?.hide()
}
override func windowDidResignKey(_ notification: Notification) {
super.windowDidResignKey(notification)
@ -107,8 +138,32 @@ class QuickTerminalController: BaseTerminalController {
self.previousApp = nil
}
if (derivedConfig.quickTerminalAutoHide) {
animateOut()
// Regardless of autohide, we always want to bring the dock back
// when we lose focus.
hiddenDock?.restore()
if derivedConfig.quickTerminalAutoHide {
switch derivedConfig.quickTerminalSpaceBehavior {
case .remain:
// If we lose focus on the active space, then we can animate out
animateOut()
case .move:
let currentActiveSpace = CGSGetActiveSpace(CGSMainConnectionID())
if previousActiveSpace == currentActiveSpace {
// We haven't moved spaces. We lost focus to another app on the
// current space. Animate out.
animateOut()
} else {
// We've moved to a different space. Bring the quick terminal back
// into view.
DispatchQueue.main.async {
self.window?.makeKeyAndOrderFront(nil)
}
self.previousActiveSpace = currentActiveSpace
}
}
}
}
@ -163,6 +218,9 @@ class QuickTerminalController: BaseTerminalController {
}
}
// Set previous active space
self.previousActiveSpace = CGSGetActiveSpace(CGSMainConnectionID())
// Animate the window in
animateWindowIn(window: window, from: position)
@ -198,8 +256,29 @@ class QuickTerminalController: BaseTerminalController {
// Move our window off screen to the top
position.setInitial(in: window, on: screen)
// We need to set our window level to a high value. In testing, only
// popUpMenu and above do what we want. This gets it above the menu bar
// and lets us render off screen.
window.level = .popUpMenu
// Move it to the visible position since animation requires this
window.makeKeyAndOrderFront(nil)
DispatchQueue.main.async {
window.makeKeyAndOrderFront(nil)
}
// If our dock position would conflict with our target location then
// we autohide the dock.
if position.conflictsWithDock(on: screen) {
if (hiddenDock == nil) {
hiddenDock = .init()
}
hiddenDock?.hide()
} else {
// Ensure we don't have any hidden dock if we don't conflict.
// The deinit will restore.
hiddenDock = nil
}
// Run the animation that moves our window into the proper place and makes
// it visible.
@ -211,8 +290,16 @@ class QuickTerminalController: BaseTerminalController {
// There is a very minor delay here so waiting at least an event loop tick
// keeps us safe from the view not being on the window.
DispatchQueue.main.async {
// If we canceled our animation in we do nothing
guard self.visible else { return }
// If we canceled our animation clean up some state.
guard self.visible else {
self.hiddenDock = nil
return
}
// After animating in, we reset the window level to a value that
// is above other windows but not as high as popUpMenu. This allows
// things like IME dropdowns to appear properly.
window.level = .floating
// Now that the window is visible, sync our appearance. This function
// requires the window is visible.
@ -276,6 +363,17 @@ class QuickTerminalController: BaseTerminalController {
}
private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) {
// If we hid the dock then we unhide it.
hiddenDock = nil
// If the window isn't on our active space then we don't animate, we just
// hide it.
if !window.isOnActiveSpace {
self.previousApp = nil
window.orderOut(self)
return
}
// We always animate out to whatever screen the window is actually on.
guard let screen = window.screen ?? NSScreen.main else { return }
@ -297,6 +395,11 @@ class QuickTerminalController: BaseTerminalController {
}
}
// We need to set our window level to a high value. In testing, only
// popUpMenu and above do what we want. This gets it above the menu bar
// and lets us render off screen.
window.level = .popUpMenu
NSAnimationContext.runAnimationGroup({ context in
context.duration = derivedConfig.quickTerminalAnimationDuration
context.timingFunction = .init(name: .easeIn)
@ -311,23 +414,13 @@ class QuickTerminalController: BaseTerminalController {
private func syncAppearance() {
guard let window else { return }
// Change the collection behavior of the window depending on the configuration.
window.collectionBehavior = derivedConfig.quickTerminalSpaceBehavior.collectionBehavior
// If our window is not visible, then no need to sync the appearance yet.
// Some APIs such as window blur have no effect unless the window is visible.
guard window.isVisible else { return }
// Terminals typically operate in sRGB color space and macOS defaults
// to "native" which is typically P3. There is a lot more resources
// covered in this GitHub issue: https://github.com/mitchellh/ghostty/pull/376
// Ghostty defaults to sRGB but this can be overridden.
switch (self.derivedConfig.windowColorspace) {
case "display-p3":
window.colorSpace = .displayP3
case "srgb":
fallthrough
default:
window.colorSpace = .sRGB
}
// If we have window transparency then set it transparent. Otherwise set it opaque.
if (self.derivedConfig.backgroundOpacity < 1) {
window.isOpaque = false
@ -368,6 +461,13 @@ class QuickTerminalController: BaseTerminalController {
// MARK: Notifications
@objc private func applicationWillTerminate(_ notification: Notification) {
// If the application is going to terminate we want to make sure we
// restore any global dock state. I think deinit should be called which
// would call this anyways but I can't be sure so I will do this too.
hiddenDock = nil
}
@objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard target == self.focusedSurface else { return }
@ -396,14 +496,14 @@ class QuickTerminalController: BaseTerminalController {
let quickTerminalScreen: QuickTerminalScreen
let quickTerminalAnimationDuration: Double
let quickTerminalAutoHide: Bool
let windowColorspace: String
let quickTerminalSpaceBehavior: QuickTerminalSpaceBehavior
let backgroundOpacity: Double
init() {
self.quickTerminalScreen = .main
self.quickTerminalAnimationDuration = 0.2
self.quickTerminalAutoHide = true
self.windowColorspace = ""
self.quickTerminalSpaceBehavior = .move
self.backgroundOpacity = 1.0
}
@ -411,10 +511,39 @@ class QuickTerminalController: BaseTerminalController {
self.quickTerminalScreen = config.quickTerminalScreen
self.quickTerminalAnimationDuration = config.quickTerminalAnimationDuration
self.quickTerminalAutoHide = config.quickTerminalAutoHide
self.windowColorspace = config.windowColorspace
self.quickTerminalSpaceBehavior = config.quickTerminalSpaceBehavior
self.backgroundOpacity = config.backgroundOpacity
}
}
/// Hides the dock globally (not just NSApp). This is only used if the quick terminal is
/// in a conflicting position with the dock.
private class HiddenDock {
let previousAutoHide: Bool
private var hidden: Bool = false
init() {
previousAutoHide = Dock.autoHideEnabled
}
deinit {
restore()
}
func hide() {
guard !hidden else { return }
NSApp.acquirePresentationOption(.autoHideDock)
Dock.autoHideEnabled = true
hidden = true
}
func restore() {
guard hidden else { return }
NSApp.releasePresentationOption(.autoHideDock)
Dock.autoHideEnabled = previousAutoHide
hidden = false
}
}
}
extension Notification.Name {

View File

@ -69,7 +69,7 @@ enum QuickTerminalPosition : String {
finalSize.width = screen.frame.width
case .left, .right:
finalSize.height = screen.frame.height
finalSize.height = screen.visibleFrame.height
case .center:
finalSize.width = screen.frame.width / 2
@ -118,4 +118,22 @@ enum QuickTerminalPosition : String {
return .init(x: screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2, y: screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
}
}
func conflictsWithDock(on screen: NSScreen) -> Bool {
// Screen must have a dock for it to conflict
guard screen.hasDock else { return false }
// Get the dock orientation for this screen
guard let orientation = Dock.orientation else { return false }
// Depending on the orientation of the dock, we conflict if our quick terminal
// would potentially "hit" the dock. In the future we should probably consider
// the frame of the quick terminal.
return switch (orientation) {
case .top: self == .top || self == .left || self == .right
case .bottom: self == .bottom || self == .left || self == .right
case .left: self == .top || self == .bottom
case .right: self == .top || self == .bottom
}
}
}

View File

@ -0,0 +1,36 @@
import Foundation
import Cocoa
enum QuickTerminalSpaceBehavior {
case remain
case move
init?(fromGhosttyConfig string: String) {
switch (string) {
case "move":
self = .move
case "remain":
self = .remain
default:
return nil
}
}
var collectionBehavior: NSWindow.CollectionBehavior {
let commonBehavior: [NSWindow.CollectionBehavior] = [
.ignoresCycle,
.fullScreenAuxiliary
]
switch (self) {
case .move:
// We want this to move the window to the active space.
return NSWindow.CollectionBehavior([.canJoinAllSpaces] + commonBehavior)
case .remain:
// We want this to remain the window in the current space.
return NSWindow.CollectionBehavior([.moveToActiveSpace] + commonBehavior)
}
}
}

View File

@ -1,6 +1,6 @@
import Cocoa
class QuickTerminalWindow: NSWindow {
class QuickTerminalWindow: NSPanel {
// Both of these must be true for windows without decorations to be able to
// still become key/main and receive events.
override var canBecomeKey: Bool { return true }
@ -26,22 +26,7 @@ class QuickTerminalWindow: NSWindow {
// window remains resizable.
self.styleMask.remove(.titled)
// We need to set our window level to a high value. In testing, only
// popUpMenu and above do what we want. This gets it above the menu bar
// and lets us render off screen.
self.level = .popUpMenu
// This plus the level above was what was needed for the animation to work,
// because it gets the window off screen properly. Plus we add some fields
// we just want the behavior of.
self.collectionBehavior = [
// We want this to be part of every space because it is a singleton.
.canJoinAllSpaces,
// We don't want to be part of command-tilde
.ignoresCycle,
// We never support fullscreen
.fullScreenNone]
// We don't want to activate the owning app when quick terminal is triggered.
self.styleMask.insert(.nonactivatingPanel)
}
}

View File

@ -389,9 +389,9 @@ class BaseTerminalController: NSWindowController,
}
switch (request) {
case .osc_52_write:
case let .osc_52_write(pasteboard):
guard case .confirm = action else { break }
let pb = NSPasteboard.general
let pb = pasteboard ?? NSPasteboard.general
pb.declareTypes([.string], owner: nil)
pb.setString(cc.contents, forType: .string)
case .osc_52_read, .paste:
@ -452,6 +452,7 @@ class BaseTerminalController: NSWindowController,
self.alert = nil
switch (response) {
case .alertFirstButtonReturn:
alert.window.orderOut(nil)
window.close()
default:

View File

@ -22,7 +22,7 @@ class TerminalController: BaseTerminalController {
private var restorable: Bool = true
/// The configuration derived from the Ghostty config so we don't need to rely on references.
private var derivedConfig: DerivedConfig
private(set) var derivedConfig: DerivedConfig
/// The notification cancellable for focused surface property changes.
private var surfaceAppearanceCancellables: Set<AnyCancellable> = []
@ -60,6 +60,11 @@ class TerminalController: BaseTerminalController {
selector: #selector(onGotoTab),
name: Ghostty.Notification.ghosttyGotoTab,
object: nil)
center.addObserver(
self,
selector: #selector(onCloseTab),
name: .ghosttyCloseTab,
object: nil)
center.addObserver(
self,
selector: #selector(ghosttyConfigDidChange(_:)),
@ -278,9 +283,12 @@ class TerminalController: BaseTerminalController {
private func setInitialWindowPosition(x: Int16?, y: Int16?, windowDecorations: Bool) {
guard let window else { return }
// If we don't have both an X and Y we center.
// If we don't have an X/Y then we try to use the previously saved window pos.
guard let x, let y else {
window.center()
if (!LastWindowPosition.shared.restore(window)) {
window.center()
}
return
}
@ -310,28 +318,28 @@ class TerminalController: BaseTerminalController {
window.styleMask = [
// We need `titled` in the mask to get the normal window frame
.titled,
// Full size content view so we can extend
// content in to the hidden titlebar's area
.fullSizeContentView,
.resizable,
.fullSizeContentView,
.resizable,
.closable,
.miniaturizable,
]
// Hide the title
window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true
// Hide the traffic lights (window control buttons)
window.standardWindowButton(.closeButton)?.isHidden = true
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true
// Disallow tabbing if the titlebar is hidden, since that will (should) also hide the tab bar.
window.tabbingMode = .disallowed
// Nuke it from orbit -- hide the titlebar container entirely, just in case. There are
// some operations that appear to bring back the titlebar visibility so this ensures
// it is gone forever.
@ -340,7 +348,7 @@ class TerminalController: BaseTerminalController {
titleBarContainer.isHidden = true
}
}
override func windowDidLoad() {
super.windowDidLoad()
guard let window = window as? TerminalWindow else { return }
@ -361,33 +369,31 @@ class TerminalController: BaseTerminalController {
// If window decorations are disabled, remove our title
if (!config.windowDecorations) { window.styleMask.remove(.titled) }
// Terminals typically operate in sRGB color space and macOS defaults
// to "native" which is typically P3. There is a lot more resources
// covered in this GitHub issue: https://github.com/mitchellh/ghostty/pull/376
// Ghostty defaults to sRGB but this can be overridden.
switch (config.windowColorspace) {
case "display-p3":
window.colorSpace = .displayP3
case "srgb":
fallthrough
default:
window.colorSpace = .sRGB
}
// If we have only a single surface (no splits) and that surface requested
// an initial size then we set it here now.
if case let .leaf(leaf) = surfaceTree {
if let initialSize = leaf.surface.initialSize,
let screen = window.screen ?? NSScreen.main {
// Setup our frame. We need to first subtract the views frame so that we can
// just get the chrome frame so that we only affect the surface view size.
// Get the current frame of the window
var frame = window.frame
frame.size.width -= leaf.surface.frame.size.width
frame.size.height -= leaf.surface.frame.size.height
frame.size.width += min(initialSize.width, screen.frame.width)
frame.size.height += min(initialSize.height, screen.frame.height)
// We have no tabs and we are not a split, so set the initial size of the window.
// Calculate the chrome size (window size minus view size)
let chromeWidth = frame.size.width - leaf.surface.frame.size.width
let chromeHeight = frame.size.height - leaf.surface.frame.size.height
// Calculate the new width and height, clamping to the screen's size
let newWidth = min(initialSize.width + chromeWidth, screen.visibleFrame.width)
let newHeight = min(initialSize.height + chromeHeight, screen.visibleFrame.height)
// Update the frame size while keeping the window's position intact
frame.size.width = newWidth
frame.size.height = newHeight
// Ensure the window doesn't go outside the screen boundaries
frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth))
frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight))
// Set the updated frame to the window
window.setFrame(frame, display: true)
}
}
@ -487,6 +493,20 @@ class TerminalController: BaseTerminalController {
override func windowDidMove(_ notification: Notification) {
super.windowDidMove(notification)
self.fixTabBar()
// Whenever we move save our last position for the next start.
if let window {
LastWindowPosition.shared.save(window)
}
}
func windowDidBecomeMain(_ notification: Notification) {
// Whenever we get focused, use that as our last window position for
// restart. This differs from Terminal.app but matches iTerm2 behavior
// and I think its sensible.
if let window {
LastWindowPosition.shared.save(window)
}
}
// Called when the window will be encoded. We handle the data encoding here in the
@ -508,7 +528,50 @@ class TerminalController: BaseTerminalController {
ghostty.newTab(surface: surface)
}
@IBAction override func closeWindow(_ sender: Any) {
private func confirmClose(
window: NSWindow,
messageText: String,
informativeText: String,
completion: @escaping () -> Void
) {
// If we need confirmation by any, show one confirmation for all windows
// in the tab group.
let alert = NSAlert()
alert.messageText = messageText
alert.informativeText = informativeText
alert.addButton(withTitle: "Close")
alert.addButton(withTitle: "Cancel")
alert.alertStyle = .warning
alert.beginSheetModal(for: window) { response in
if response == .alertFirstButtonReturn {
completion()
}
}
}
@IBAction func closeTab(_ sender: Any?) {
guard let window = window else { return }
guard window.tabGroup != nil else {
// No tabs, no tab group, just perform a normal close.
window.performClose(sender)
return
}
if surfaceTree?.needsConfirmQuit() ?? false {
confirmClose(
window: window,
messageText: "Close Tab?",
informativeText: "The terminal still has a running process. If you close the tab the process will be killed."
) {
window.close()
}
return
}
window.close()
}
@IBAction override func closeWindow(_ sender: Any?) {
guard let window = window else { return }
guard let tabGroup = window.tabGroup else {
// No tabs, no tab group, just perform a normal close.
@ -523,47 +586,34 @@ class TerminalController: BaseTerminalController {
}
// Check if any windows require close confirmation.
var needsConfirm: Bool = false
for tabWindow in tabGroup.windows {
guard let c = tabWindow.windowController as? TerminalController else { continue }
if (c.surfaceTree?.needsConfirmQuit() ?? false) {
needsConfirm = true
break
let needsConfirm = tabGroup.windows.contains { tabWindow in
guard let controller = tabWindow.windowController as? TerminalController else {
return false
}
return controller.surfaceTree?.needsConfirmQuit() ?? false
}
// If none need confirmation then we can just close all the windows.
if (!needsConfirm) {
for tabWindow in tabGroup.windows {
tabWindow.close()
}
if !needsConfirm {
tabGroup.windows.forEach { $0.close() }
return
}
// If we need confirmation by any, show one confirmation for all windows
// in the tab group.
let alert = NSAlert()
alert.messageText = "Close Window?"
alert.informativeText = "All terminal sessions in this window will be terminated."
alert.addButton(withTitle: "Close Window")
alert.addButton(withTitle: "Cancel")
alert.alertStyle = .warning
alert.beginSheetModal(for: window, completionHandler: { response in
if (response == .alertFirstButtonReturn) {
for tabWindow in tabGroup.windows {
tabWindow.close()
}
}
})
confirmClose(
window: window,
messageText: "Close Window?",
informativeText: "All terminal sessions in this window will be terminated."
) {
tabGroup.windows.forEach { $0.close() }
}
}
@IBAction func toggleGhosttyFullScreen(_ sender: Any) {
@IBAction func toggleGhosttyFullScreen(_ sender: Any?) {
guard let surface = focusedSurface?.surface else { return }
ghostty.toggleFullscreen(surface: surface)
}
@IBAction func toggleTerminalInspector(_ sender: Any) {
@IBAction func toggleTerminalInspector(_ sender: Any?) {
guard let surface = focusedSurface?.surface else { return }
ghostty.toggleTerminalInspector(surface: surface)
}
@ -659,13 +709,21 @@ class TerminalController: BaseTerminalController {
// If our index is the same we do nothing
guard finalIndex != selectedIndex else { return }
// Get our parent
let parent = tabbedWindows[finalIndex]
// Get our target window
let targetWindow = tabbedWindows[finalIndex]
// Move our current selected window to the proper index
// Begin a group of window operations to minimize visual updates
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = 0
// Remove and re-add the window in the correct position
tabGroup.removeWindow(selectedWindow)
parent.addTabbedWindow(selectedWindow, ordered: action.amount < 0 ? .below : .above)
selectedWindow.makeKeyAndOrderFront(nil)
targetWindow.addTabbedWindow(selectedWindow, ordered: action.amount < 0 ? .below : .above)
// Ensure our window remains selected
selectedWindow.makeKey()
NSAnimationContext.endGrouping()
}
@objc private func onGotoTab(notification: SwiftUI.Notification) {
@ -720,6 +778,12 @@ class TerminalController: BaseTerminalController {
targetWindow.makeKeyAndOrderFront(nil)
}
@objc private func onCloseTab(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard surfaceTree?.contains(view: target) ?? false else { return }
closeTab(self)
}
@objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard target == self.focusedSurface else { return }
@ -737,7 +801,7 @@ class TerminalController: BaseTerminalController {
toggleFullscreen(mode: fullscreenMode)
}
private struct DerivedConfig {
struct DerivedConfig {
let backgroundColor: Color
let macosTitlebarStyle: String

View File

@ -86,7 +86,7 @@ class TerminalManager {
// fullscreen for the logic later in this method.
c.toggleFullscreen(mode: .native)
case .nonNative, .nonNativeVisibleMenu:
case .nonNative, .nonNativeVisibleMenu, .nonNativePaddedNotch:
// If we're non-native then we have to do it on a later loop
// so that the content view is setup.
DispatchQueue.main.async {

View File

@ -10,7 +10,7 @@ protocol TerminalViewDelegate: AnyObject {
/// The title of the terminal should change.
func titleDidChange(to: String)
/// The URL of the pwd should change.
func pwdDidChange(to: URL?)
@ -56,15 +56,10 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
// The title for our window
private var title: String {
var title = "👻"
if let surfaceTitle = surfaceTitle {
if (surfaceTitle.count > 0) {
title = surfaceTitle
}
if let surfaceTitle, !surfaceTitle.isEmpty {
return surfaceTitle
}
return title
return "👻"
}
// The pwd of the focused surface as a URL
@ -72,7 +67,7 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
guard let surfacePwd, surfacePwd != "" else { return nil }
return URL(fileURLWithPath: surfacePwd)
}
var body: some View {
switch ghostty.readiness {
case .loading:

View File

@ -115,6 +115,21 @@ class TerminalWindow: NSWindow {
}
}
// We override this so that with the hidden titlebar style the titlebar
// area is not draggable.
override var contentLayoutRect: CGRect {
var rect = super.contentLayoutRect
// If we are using a hidden titlebar style, the content layout is the
// full frame making it so that it is not draggable.
if let controller = windowController as? TerminalController,
controller.derivedConfig.macosTitlebarStyle == "hidden" {
rect.origin.y = 0
rect.size.height = self.frame.height
}
return rect
}
// The window theme configuration from Ghostty. This is used to control some
// behaviors that don't look quite right in certain situations.
var windowTheme: TerminalWindowTheme?

View File

@ -13,6 +13,9 @@ extension FullscreenMode {
case GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU:
.nonNativeVisibleMenu
case GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH:
.nonNativePaddedNotch
default:
nil
}

View File

@ -62,7 +62,7 @@ extension Ghostty {
// uses to interface with the application runtime environment.
var runtime_cfg = ghostty_runtime_config_s(
userdata: Unmanaged.passUnretained(self).toOpaque(),
supports_selection_clipboard: false,
supports_selection_clipboard: true,
wakeup_cb: { userdata in App.wakeup(userdata) },
action_cb: { app, target, action in App.action(app!, target: target, action: action) },
read_clipboard_cb: { userdata, loc, state in App.readClipboard(userdata, location: loc, state: state) },
@ -257,7 +257,7 @@ extension Ghostty {
// MARK: Ghostty Callbacks (iOS)
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {}
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) {}
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) -> Bool { return false }
static func readClipboard(
_ userdata: UnsafeMutableRawPointer?,
location: ghostty_clipboard_e,
@ -320,13 +320,13 @@ extension Ghostty {
let surfaceView = self.surfaceUserdata(from: userdata)
guard let surface = surfaceView.surface else { return }
// We only support the standard clipboard
if (location != GHOSTTY_CLIPBOARD_STANDARD) {
// Get our pasteboard
guard let pasteboard = NSPasteboard.ghostty(location) else {
return completeClipboardRequest(surface, data: "", state: state)
}
// Get our string
let str = NSPasteboard.general.getOpinionatedStringContents() ?? ""
let str = pasteboard.getOpinionatedStringContents() ?? ""
completeClipboardRequest(surface, data: str, state: state)
}
@ -364,14 +364,12 @@ extension Ghostty {
static func writeClipboard(_ userdata: UnsafeMutableRawPointer?, string: UnsafePointer<CChar>?, location: ghostty_clipboard_e, confirm: Bool) {
let surface = self.surfaceUserdata(from: userdata)
// We only support the standard clipboard
if (location != GHOSTTY_CLIPBOARD_STANDARD) { return }
guard let pasteboard = NSPasteboard.ghostty(location) else { return }
guard let valueStr = String(cString: string!, encoding: .utf8) else { return }
if !confirm {
let pb = NSPasteboard.general
pb.declareTypes([.string], owner: nil)
pb.setString(valueStr, forType: .string)
pasteboard.declareTypes([.string], owner: nil)
pasteboard.setString(valueStr, forType: .string)
return
}
@ -380,7 +378,7 @@ extension Ghostty {
object: surface,
userInfo: [
Notification.ConfirmClipboardStrKey: valueStr,
Notification.ConfirmClipboardRequestKey: Ghostty.ClipboardRequest.osc_52_write,
Notification.ConfirmClipboardRequestKey: Ghostty.ClipboardRequest.osc_52_write(pasteboard),
]
)
}
@ -425,7 +423,7 @@ extension Ghostty {
// MARK: Actions (macOS)
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) {
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) -> Bool {
// Make sure it a target we understand so all our action handlers can assert
switch (target.tag) {
case GHOSTTY_TARGET_APP, GHOSTTY_TARGET_SURFACE:
@ -433,7 +431,7 @@ extension Ghostty {
default:
Ghostty.logger.warning("unknown action target=\(target.tag.rawValue)")
return
return false
}
// Action dispatch
@ -450,17 +448,20 @@ extension Ghostty {
case GHOSTTY_ACTION_NEW_SPLIT:
newSplit(app, target: target, direction: action.action.new_split)
case GHOSTTY_ACTION_CLOSE_TAB:
closeTab(app, target: target)
case GHOSTTY_ACTION_TOGGLE_FULLSCREEN:
toggleFullscreen(app, target: target, mode: action.action.toggle_fullscreen)
case GHOSTTY_ACTION_MOVE_TAB:
moveTab(app, target: target, move: action.action.move_tab)
return moveTab(app, target: target, move: action.action.move_tab)
case GHOSTTY_ACTION_GOTO_TAB:
gotoTab(app, target: target, tab: action.action.goto_tab)
return gotoTab(app, target: target, tab: action.action.goto_tab)
case GHOSTTY_ACTION_GOTO_SPLIT:
gotoSplit(app, target: target, direction: action.action.goto_split)
return gotoSplit(app, target: target, direction: action.action.goto_split)
case GHOSTTY_ACTION_RESIZE_SPLIT:
resizeSplit(app, target: target, resize: action.action.resize_split)
@ -483,6 +484,9 @@ extension Ghostty {
case GHOSTTY_ACTION_SET_TITLE:
setTitle(app, target: target, v: action.action.set_title)
case GHOSTTY_ACTION_PROMPT_TITLE:
return promptTitle(app, target: target)
case GHOSTTY_ACTION_PWD:
pwdChanged(app, target: target, v: action.action.pwd)
@ -540,10 +544,15 @@ extension Ghostty {
fallthrough
case GHOSTTY_ACTION_QUIT_TIMER:
Ghostty.logger.info("known but unimplemented action action=\(action.tag.rawValue)")
return false
default:
Ghostty.logger.warning("unknown action action=\(action.tag.rawValue)")
return false
}
// If we reached here then we assume performed since all unknown actions
// are captured in the switch and return false.
return true
}
private static func quit(_ app: ghostty_app_t) {
@ -653,6 +662,27 @@ extension Ghostty {
}
}
private static func closeTab(_ app: ghostty_app_t, target: ghostty_target_s) {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("close tab does nothing with an app target")
return
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
NotificationCenter.default.post(
name: .ghosttyCloseTab,
object: surfaceView
)
default:
assertionFailure()
}
}
private static func toggleFullscreen(
_ app: ghostty_app_t,
target: ghostty_target_s,
@ -694,15 +724,19 @@ extension Ghostty {
private static func moveTab(
_ app: ghostty_app_t,
target: ghostty_target_s,
move: ghostty_action_move_tab_s) {
move: ghostty_action_move_tab_s) -> Bool {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("move tab does nothing with an app target")
return
return false
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return false }
// See gotoTab for notes on this check.
guard (surfaceView.window?.tabGroup?.windows.count ?? 0) > 1 else { return false }
NotificationCenter.default.post(
name: .ghosttyMoveTab,
object: surfaceView,
@ -714,20 +748,27 @@ extension Ghostty {
default:
assertionFailure()
}
return true
}
private static func gotoTab(
_ app: ghostty_app_t,
target: ghostty_target_s,
tab: ghostty_action_goto_tab_e) {
tab: ghostty_action_goto_tab_e) -> Bool {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("goto tab does nothing with an app target")
return
return false
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return false }
// Similar to goto_split (see comment there) about our performability,
// we should make this more accurate later.
guard (surfaceView.window?.tabGroup?.windows.count ?? 0) > 1 else { return false }
NotificationCenter.default.post(
name: Notification.ghosttyGotoTab,
object: surfaceView,
@ -739,20 +780,31 @@ extension Ghostty {
default:
assertionFailure()
}
return true
}
private static func gotoSplit(
_ app: ghostty_app_t,
target: ghostty_target_s,
direction: ghostty_action_goto_split_e) {
direction: ghostty_action_goto_split_e) -> Bool {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("goto split does nothing with an app target")
return
return false
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return false }
guard let controller = surfaceView.window?.windowController as? BaseTerminalController else { return false }
// For now, we return false if the window has no splits and we return
// true if the window has ANY splits. This isn't strictly correct because
// we should only be returning true if we actually performed the action,
// but this handles the most common case of caring about goto_split performability
// which is the no-split case.
guard controller.surfaceTree?.isSplit ?? false else { return false }
NotificationCenter.default.post(
name: Notification.ghosttyFocusSplit,
object: surfaceView,
@ -764,6 +816,8 @@ extension Ghostty {
default:
assertionFailure()
}
return true
}
private static func resizeSplit(
@ -956,6 +1010,26 @@ extension Ghostty {
}
}
private static func promptTitle(
_ app: ghostty_app_t,
target: ghostty_target_s) -> Bool {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("set title prompt does nothing with an app target")
return false
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return false }
surfaceView.promptTitle()
default:
assertionFailure()
}
return true
}
private static func pwdChanged(
_ app: ghostty_app_t,
target: ghostty_target_s,

View File

@ -132,15 +132,6 @@ extension Ghostty {
return v
}
var windowColorspace: String {
guard let config = self.config else { return "" }
var v: UnsafePointer<Int8>? = nil
let key = "window-colorspace"
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return "" }
guard let ptr = v else { return "" }
return String(cString: ptr)
}
var windowSaveState: String {
guard let config = self.config else { return "" }
var v: UnsafePointer<Int8>? = nil
@ -174,11 +165,14 @@ extension Ghostty {
}
var windowDecorations: Bool {
guard let config = self.config else { return true }
var v = false;
let defaultValue = true
guard let config = self.config else { return defaultValue }
var v: UnsafePointer<Int8>? = nil
let key = "window-decoration"
_ = ghostty_config_get(config, &v, key, UInt(key.count))
return v;
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
guard let ptr = v else { return defaultValue }
let str = String(cString: ptr)
return WindowDecoration(rawValue: str)?.enabled() ?? defaultValue
}
var windowTheme: String? {
@ -222,6 +216,8 @@ extension Ghostty {
.nonNative
case "visible-menu":
.nonNativeVisibleMenu
case "padded-notch":
.nonNativePaddedNotch
default:
defaultValue
}
@ -306,6 +302,16 @@ extension Ghostty {
return buffer.map { .init(ghostty: $0) }
}
var macosHidden: MacHidden {
guard let config = self.config else { return .never }
var v: UnsafePointer<Int8>? = nil
let key = "macos-hidden"
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .never }
guard let ptr = v else { return .never }
let str = String(cString: ptr)
return MacHidden(rawValue: str) ?? .never
}
var focusFollowsMouse : Bool {
guard let config = self.config else { return false }
var v = false;
@ -345,7 +351,7 @@ extension Ghostty {
var backgroundBlurRadius: Int {
guard let config = self.config else { return 1 }
var v: Int = 0
let key = "background-blur-radius"
let key = "background-blur"
_ = ghostty_config_get(config, &v, key, UInt(key.count))
return v;
}
@ -431,6 +437,16 @@ extension Ghostty {
_ = ghostty_config_get(config, &v, key, UInt(key.count))
return v
}
var quickTerminalSpaceBehavior: QuickTerminalSpaceBehavior {
guard let config = self.config else { return .move }
var v: UnsafePointer<Int8>? = nil
let key = "quick-terminal-space-behavior"
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .move }
guard let ptr = v else { return .move }
let str = String(cString: ptr)
return QuickTerminalSpaceBehavior(fromGhosttyConfig: str) ?? .move
}
#endif
var resizeOverlay: ResizeOverlay {
@ -510,6 +526,11 @@ extension Ghostty.Config {
case download
}
enum MacHidden : String {
case never
case always
}
enum ResizeOverlay : String {
case always
case never
@ -553,4 +574,18 @@ extension Ghostty.Config {
}
}
}
enum WindowDecoration: String {
case none
case client
case server
case auto
func enabled() -> Bool {
switch self {
case .client, .server, .auto: return true
case .none: return false
}
}
}
}

View File

@ -38,6 +38,15 @@ extension Ghostty {
}
}
/// Returns true if the tree is split.
var isSplit: Bool {
return if case .leaf = self {
false
} else {
true
}
}
func topLeft() -> SurfaceView {
switch (self) {
case .leaf(let leaf):
@ -120,14 +129,7 @@ extension Ghostty {
/// Returns true if the split tree contains the given view.
func contains(view: SurfaceView) -> Bool {
switch (self) {
case .leaf(let leaf):
return leaf.surface == view
case .split(let container):
return container.topLeft.contains(view: view) ||
container.bottomRight.contains(view: view)
}
return leaf(for: view) != nil
}
/// Find a surface view by UUID.
@ -164,6 +166,22 @@ extension Ghostty {
}
}
/// Return the node for the given view if its in the tree.
func leaf(for view: SurfaceView) -> Leaf? {
switch (self) {
case .leaf(let leaf):
if leaf.surface == view {
return leaf
} else {
return nil
}
case .split(let container):
return container.topLeft.leaf(for: view) ??
container.bottomRight.leaf(for: view)
}
}
// MARK: - Sequence
func makeIterator() -> IndexingIterator<[Leaf]> {

View File

@ -205,6 +205,7 @@ extension Ghostty {
alert.beginSheetModal(for: window, completionHandler: { response in
switch (response) {
case .alertFirstButtonReturn:
alert.window.orderOut(nil)
node = nil
default:

View File

@ -159,7 +159,7 @@ extension Ghostty {
case osc_52_read
/// An application is attempting to write to the clipboard using OSC 52
case osc_52_write
case osc_52_write(OSPasteboard?)
/// The text to show in the clipboard confirmation prompt for a given request type
func text() -> String {
@ -188,7 +188,7 @@ extension Ghostty {
case GHOSTTY_CLIPBOARD_REQUEST_OSC_52_READ:
return .osc_52_read
case GHOSTTY_CLIPBOARD_REQUEST_OSC_52_WRITE:
return .osc_52_write
return .osc_52_write(nil)
default:
return nil
}
@ -198,6 +198,14 @@ extension Ghostty {
/// macos-icon
enum MacOSIcon: String {
case official
case blueprint
case chalkboard
case glass
case holographic
case microchip
case paper
case retro
case xray
case customStyle = "custom-style"
}
@ -236,6 +244,9 @@ extension Notification.Name {
/// Goto tab. Has tab index in the userinfo.
static let ghosttyMoveTab = Notification.Name("com.mitchellh.ghostty.moveTab")
static let GhosttyMoveTabKey = ghosttyMoveTab.rawValue
/// Close tab
static let ghosttyCloseTab = Notification.Name("com.mitchellh.ghostty.closeTab")
}
// NOTE: I am moving all of these to Notification.Name extensions over time. This

View File

@ -92,22 +92,6 @@ extension Ghostty {
windowFocus = false
}
}
.onDrop(of: [.fileURL], isTargeted: nil) { providers in
providers.forEach { provider in
_ = provider.loadObject(ofClass: URL.self) { url, _ in
guard let url = url else { return }
let path = Shell.escape(url.path)
DispatchQueue.main.async {
surfaceView.insertText(
path,
replacementRange: NSMakeRange(0, 0)
)
}
}
}
return true
}
#endif
// If our geo size changed then we show the resize overlay as configured.

View File

@ -1,3 +1,4 @@
import AppKit
import SwiftUI
import CoreText
import UserNotifications
@ -12,7 +13,14 @@ extension Ghostty {
// The current title of the surface as defined by the pty. This can be
// changed with escape codes. This is public because the callbacks go
// to the app level and it is set from there.
@Published private(set) var title: String = "👻"
@Published private(set) var title: String = "" {
didSet {
if !title.isEmpty {
titleFallbackTimer?.invalidate()
titleFallbackTimer = nil
}
}
}
// The current pwd of the surface as defined by the pty. This can be
// changed with escape codes.
@ -113,6 +121,14 @@ extension Ghostty {
// A small delay that is introduced before a title change to avoid flickers
private var titleChangeTimer: Timer?
// A timer to fallback to ghost emoji if no title is set within the grace period
private var titleFallbackTimer: Timer?
// This is the title from the terminal. This is nil if we're currently using
// the terminal title as the main title property. If the title is set manually
// by the user, this is set to the prior value (which may be empty, but non-nil).
private var titleFromTerminal: String?
/// Event monitor (see individual events for why)
private var eventMonitor: Any? = nil
@ -139,6 +155,13 @@ extension Ghostty {
// can do SOMETHING.
super.init(frame: NSMakeRect(0, 0, 800, 600))
// Set a timer to show the ghost emoji after 500ms if no title is set
titleFallbackTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in
if let self = self, self.title.isEmpty {
self.title = "👻"
}
}
// Before we initialize the surface we want to register our notifications
// so there is no window where we can't receive them.
let center = NotificationCenter.default
@ -213,6 +236,9 @@ extension Ghostty {
ghostty_surface_set_color_scheme(surface, scheme)
}
// The UTTypes that can be dragged onto this view.
registerForDraggedTypes(Array(Self.dropTypes))
}
required init?(coder: NSCoder) {
@ -359,6 +385,45 @@ extension Ghostty {
NSCursor.setHiddenUntilMouseMoves(!visible)
}
/// Set the title by prompting the user.
func promptTitle() {
// Create an alert dialog
let alert = NSAlert()
alert.messageText = "Change Terminal Title"
alert.informativeText = "Leave blank to restore the default."
alert.alertStyle = .informational
// Add a text field to the alert
let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 250, height: 24))
textField.stringValue = title
alert.accessoryView = textField
// Add buttons
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Cancel")
let response = alert.runModal()
// Check if the user clicked "OK"
if response == .alertFirstButtonReturn {
// Get the input text
let newTitle = textField.stringValue
if newTitle.isEmpty {
// Empty means that user wants the title to be set automatically
// We also need to reload the config for the "title" property to be
// used again by this tab.
let prevTitle = titleFromTerminal ?? "👻"
titleFromTerminal = nil
setTitle(prevTitle)
} else {
// Set the title and prevent it from being changed automatically
titleFromTerminal = title
title = newTitle
}
}
}
func setTitle(_ title: String) {
// This fixes an issue where very quick changes to the title could
// cause an unpleasant flickering. We set a timer so that we can
@ -369,6 +434,11 @@ extension Ghostty {
withTimeInterval: 0.075,
repeats: false
) { [weak self] _ in
// Set the title if it wasn't manually set.
guard self?.titleFromTerminal == nil else {
self?.titleFromTerminal = title
return
}
self?.title = title
}
}
@ -1096,6 +1166,8 @@ extension Ghostty {
menu.addItem(.separator())
menu.addItem(withTitle: "Reset Terminal", action: #selector(resetTerminal(_:)), keyEquivalent: "")
menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(toggleTerminalInspector(_:)), keyEquivalent: "")
menu.addItem(.separator())
menu.addItem(withTitle: "Change Title...", action: #selector(changeTitle(_:)), keyEquivalent: "")
return menu
}
@ -1127,6 +1199,14 @@ extension Ghostty {
}
}
@IBAction func pasteSelection(_ sender: Any?) {
guard let surface = self.surface else { return }
let action = "paste_from_selection"
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
AppDelegate.logger.warning("action failed action=\(action)")
}
}
@IBAction override func selectAll(_ sender: Any?) {
guard let surface = self.surface else { return }
let action = "select_all"
@ -1160,6 +1240,10 @@ extension Ghostty {
AppDelegate.logger.warning("action failed action=\(action)")
}
}
@IBAction func changeTitle(_ sender: Any) {
promptTitle()
}
/// Show a user notification and associate it with this surface
func showUserNotification(title: String, body: String) {
@ -1448,3 +1532,78 @@ extension Ghostty.SurfaceView: NSServicesMenuRequestor {
return true
}
}
// MARK: NSMenuItemValidation
extension Ghostty.SurfaceView: NSMenuItemValidation {
func validateMenuItem(_ item: NSMenuItem) -> Bool {
switch item.action {
case #selector(pasteSelection):
let pb = NSPasteboard.ghosttySelection
guard let str = pb.getOpinionatedStringContents() else { return false }
return !str.isEmpty
default:
return true
}
}
}
// MARK: NSDraggingDestination
extension Ghostty.SurfaceView {
static let dropTypes: Set<NSPasteboard.PasteboardType> = [
.string,
.fileURL,
.URL
]
override func draggingEntered(_ sender: any NSDraggingInfo) -> NSDragOperation {
guard let types = sender.draggingPasteboard.types else { return [] }
// If the dragging object contains none of our types then we return none.
// This shouldn't happen because AppKit should guarantee that we only
// receive types we registered for but its good to check.
if Set(types).isDisjoint(with: Self.dropTypes) {
return []
}
// We use copy to get the proper icon
return .copy
}
override func performDragOperation(_ sender: any NSDraggingInfo) -> Bool {
let pb = sender.draggingPasteboard
let content: String?
if let url = pb.string(forType: .URL) {
// URLs first, they get escaped as-is.
content = Ghostty.Shell.escape(url)
} else if let urls = pb.readObjects(forClasses: [NSURL.self]) as? [URL],
urls.count > 0 {
// File URLs next. They get escaped individually and then joined by a
// space if there are multiple.
content = urls
.map { Ghostty.Shell.escape($0.path) }
.joined(separator: " ")
} else if let str = pb.string(forType: .string) {
// Strings are not escaped because they may be copy/pasting a
// command they want to execute.
content = str
} else {
content = nil
}
if let content {
DispatchQueue.main.async {
self.insertText(
content,
replacementRange: NSMakeRange(0, 0)
)
}
return true
}
return false
}
}

View File

@ -10,6 +10,7 @@ import AppKit
typealias OSView = NSView
typealias OSColor = NSColor
typealias OSSize = NSSize
typealias OSPasteboard = NSPasteboard
protocol OSViewRepresentable: NSViewRepresentable where NSViewType == OSViewType {
associatedtype OSViewType: NSView
@ -34,6 +35,7 @@ import UIKit
typealias OSView = UIView
typealias OSColor = UIColor
typealias OSSize = CGSize
typealias OSPasteboard = UIPasteboard
protocol OSViewRepresentable: UIViewRepresentable {
associatedtype OSViewType: UIView

View File

@ -0,0 +1,38 @@
import Cocoa
// Private API to get Dock location
@_silgen_name("CoreDockGetOrientationAndPinning")
func CoreDockGetOrientationAndPinning(
_ outOrientation: UnsafeMutablePointer<Int32>,
_ outPinning: UnsafeMutablePointer<Int32>)
// Private API to get the current Dock auto-hide state
@_silgen_name("CoreDockGetAutoHideEnabled")
func CoreDockGetAutoHideEnabled() -> Bool
// Toggles the Dock's auto-hide state
@_silgen_name("CoreDockSetAutoHideEnabled")
func CoreDockSetAutoHideEnabled(_ flag: Bool)
enum DockOrientation: Int {
case top = 1
case bottom = 2
case left = 3
case right = 4
}
class Dock {
/// Returns the orientation of the dock or nil if it can't be determined.
static var orientation: DockOrientation? {
var orientation: Int32 = 0
var pinning: Int32 = 0
CoreDockGetOrientationAndPinning(&orientation, &pinning)
return .init(rawValue: Int(orientation)) ?? nil
}
/// Set the dock autohide.
static var autoHideEnabled: Bool {
get { return CoreDockGetAutoHideEnabled() }
set { CoreDockSetAutoHideEnabled(newValue) }
}
}

View File

@ -6,6 +6,7 @@ enum FullscreenMode {
case native
case nonNative
case nonNativeVisibleMenu
case nonNativePaddedNotch
/// Initializes the fullscreen style implementation for the mode. This will not toggle any
/// fullscreen properties. This may fail if the window isn't configured properly for a given
@ -20,6 +21,9 @@ enum FullscreenMode {
case .nonNativeVisibleMenu:
return NonNativeFullscreenVisibleMenu(window)
case .nonNativePaddedNotch:
return NonNativeFullscreenPaddedNotch(window)
}
}
}
@ -141,6 +145,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
struct Properties {
var hideMenu: Bool = true
var paddedNotch: Bool = false
}
private var savedState: SavedState?
@ -278,6 +283,9 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
// put an #available check, but it was in a bug fix release so I think
// if a bug is reported to Ghostty we can just advise the user to
// update.
} else if (properties.paddedNotch) {
// We are hiding the menu, we may need to avoid the notch.
frame.size.height -= screen.safeAreaInsets.top
}
return frame
@ -307,21 +315,21 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
// MARK: Dock
private func hideDock() {
NSApp.presentationOptions.insert(.autoHideDock)
NSApp.acquirePresentationOption(.autoHideDock)
}
private func unhideDock() {
NSApp.presentationOptions.remove(.autoHideDock)
NSApp.releasePresentationOption(.autoHideDock)
}
// MARK: Menu
func hideMenu() {
NSApp.presentationOptions.insert(.autoHideMenuBar)
NSApp.acquirePresentationOption(.autoHideMenuBar)
}
func unhideMenu() {
NSApp.presentationOptions.remove(.autoHideMenuBar)
NSApp.releasePresentationOption(.autoHideMenuBar)
}
/// The state that must be saved for non-native fullscreen to exit fullscreen.
@ -349,3 +357,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
class NonNativeFullscreenVisibleMenu: NonNativeFullscreen {
override var properties: Properties { Properties(hideMenu: false) }
}
class NonNativeFullscreenPaddedNotch: NonNativeFullscreen {
override var properties: Properties { Properties(paddedNotch: true) }
}

View File

@ -0,0 +1,34 @@
import Cocoa
/// Manages the persistence and restoration of window positions across app launches.
class LastWindowPosition {
static let shared = LastWindowPosition()
private let positionKey = "NSWindowLastPosition"
func save(_ window: NSWindow) {
let origin = window.frame.origin
let point = [origin.x, origin.y]
UserDefaults.standard.set(point, forKey: positionKey)
}
func restore(_ window: NSWindow) -> Bool {
guard let points = UserDefaults.standard.array(forKey: positionKey) as? [Double],
points.count == 2 else { return false }
let lastPosition = CGPoint(x: points[0], y: points[1])
guard let screen = window.screen ?? NSScreen.main else { return false }
let visibleFrame = screen.visibleFrame
var newFrame = window.frame
newFrame.origin = lastPosition
if !visibleFrame.contains(newFrame.origin) {
newFrame.origin.x = max(visibleFrame.minX, min(visibleFrame.maxX - newFrame.width, newFrame.origin.x))
newFrame.origin.y = max(visibleFrame.minY, min(visibleFrame.maxY - newFrame.height, newFrame.origin.y))
}
window.setFrame(newFrame, display: true)
return true
}
}

View File

@ -0,0 +1,31 @@
import Cocoa
extension NSApplication {
private static var presentationOptionCounts: [NSApplication.PresentationOptions.Element: UInt] = [:]
/// Add a presentation option to the application and main a reference count so that and equal
/// number of pops is required to disable it. This is useful so that multiple classes can affect global
/// app state without overriding others.
func acquirePresentationOption(_ option: NSApplication.PresentationOptions.Element) {
Self.presentationOptionCounts[option, default: 0] += 1
presentationOptions.insert(option)
}
/// See acquirePresentationOption
func releasePresentationOption(_ option: NSApplication.PresentationOptions.Element) {
guard let value = Self.presentationOptionCounts[option] else { return }
guard value > 0 else { return }
if (value == 1) {
presentationOptions.remove(option)
Self.presentationOptionCounts.removeValue(forKey: option)
} else {
Self.presentationOptionCounts[option] = value - 1
}
}
}
extension NSApplication.PresentationOptions.Element: @retroactive Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(rawValue)
}
}

View File

@ -1,17 +1,39 @@
import AppKit
import GhosttyKit
extension NSPasteboard {
/// The pasteboard to used for Ghostty selection.
static var ghosttySelection: NSPasteboard = {
NSPasteboard(name: .init("com.mitchellh.ghostty.selection"))
}()
/// Gets the contents of the pasteboard as a string following a specific set of semantics.
/// Does these things in order:
/// - Tries to get the absolute filesystem path of the file in the pasteboard if there is one.
/// - Tries to get the absolute filesystem path of the file in the pasteboard if there is one and ensures the file path is properly escaped.
/// - Tries to get any string from the pasteboard.
/// If all of the above fail, returns None.
func getOpinionatedStringContents() -> String? {
if let file = self.string(forType: .fileURL) {
if let path = NSURL(string: file)?.path {
return path
}
if let urls = readObjects(forClasses: [NSURL.self]) as? [URL],
urls.count > 0 {
return urls
.map { $0.isFileURL ? Ghostty.Shell.escape($0.path) : $0.absoluteString }
.joined(separator: " ")
}
return self.string(forType: .string)
}
/// The pasteboard for the Ghostty enum type.
static func ghostty(_ clipboard: ghostty_clipboard_e) -> NSPasteboard? {
switch (clipboard) {
case GHOSTTY_CLIPBOARD_STANDARD:
return Self.general
case GHOSTTY_CLIPBOARD_SELECTION:
return Self.ghosttySelection
default:
return nil
}
}
}

View File

@ -0,0 +1,9 @@
/// A wrapper that holds a weak reference to an object. This lets us create native containers
/// of weak references.
class Weak<T: AnyObject> {
weak var value: T?
init(_ value: T) {
self.value = value
}
}

View File

@ -1,63 +0,0 @@
#!/usr/bin/env bash
# Nothing in this script should fail.
set -e
CACHE_HASH_FILE="$(realpath "$(dirname "$0")/../zigCacheHash.nix")"
help() {
echo ""
echo "To fix, please (manually) re-run the script from the repository root,"
echo "commit, and push the update:"
echo ""
echo " ./nix/build-support/check-zig-cache-hash.sh --update"
echo " git add nix/zigCacheHash.nix"
echo " git commit -m \"nix: update Zig cache hash\""
echo " git push"
echo ""
}
if [ -f "${CACHE_HASH_FILE}" ]; then
OLD_CACHE_HASH="$(nix eval --raw --file "${CACHE_HASH_FILE}")"
elif [ "$1" != "--update" ]; then
echo -e "\nERROR: Zig cache hash file missing."
help
exit 1
fi
ZIG_GLOBAL_CACHE_DIR="$(mktemp --directory --suffix nix-zig-cache)"
export ZIG_GLOBAL_CACHE_DIR
# This is not 100% necessary in CI but is helpful when running locally to keep
# a local workstation clean.
trap 'rm -rf "${ZIG_GLOBAL_CACHE_DIR}"' EXIT
# Run Zig and download the cache to the temporary directory.
sh ./nix/build-support/fetch-zig-cache.sh
# Now, calculate the hash.
ZIG_CACHE_HASH="sha256-$(nix-hash --type sha256 --to-base64 "$(nix-hash --type sha256 "${ZIG_GLOBAL_CACHE_DIR}")")"
if [ "${OLD_CACHE_HASH}" == "${ZIG_CACHE_HASH}" ]; then
echo -e "\nOK: Zig cache store hash unchanged."
exit 0
elif [ "$1" != "--update" ]; then
echo -e "\nERROR: The Zig cache store hash has updated."
echo ""
echo " * Old hash: ${OLD_CACHE_HASH}"
echo " * New hash: ${ZIG_CACHE_HASH}"
help
exit 1
else
echo -e "\nNew Zig cache store hash: ${ZIG_CACHE_HASH}"
fi
# Write out the cache file
cat > "${CACHE_HASH_FILE}" <<EOS
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
# more details.
"${ZIG_CACHE_HASH}"
EOS
echo -e "\nOK: Wrote new hash to file: ${CACHE_HASH_FILE}"

View File

@ -0,0 +1,78 @@
#!/usr/bin/env bash
#
# This script checks if the build.zig.zon.nix file is up-to-date.
# If the `--update` flag is passed, it will update all necessary
# files to be up to date.
#
# The files owned by this are:
#
# - build.zig.zon.nix
# - build.zig.zon.txt
# - build.zig.zon2json-lock
#
# All of these are auto-generated and should not be edited manually.
# Nothing in this script should fail.
set -e
WORK_DIR=$(mktemp -d)
if [[ ! "$WORK_DIR" || ! -d "$WORK_DIR" ]]; then
echo "could not create temp dir"
exit 1
fi
function cleanup {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
help() {
echo ""
echo "To fix, please (manually) re-run the script from the repository root,"
echo "commit, and submit a PR with the update:"
echo ""
echo " ./nix/build-support/check-zig-cache-hash.sh --update"
echo " git add build.zig.zon.nix"
echo " git commit -m \"nix: update build.zig.zon.nix\""
echo ""
}
ROOT="$(realpath "$(dirname "$0")/../../")"
BUILD_ZIG_ZON="$ROOT/build.zig.zon"
BUILD_ZIG_ZON_LOCK="$ROOT/build.zig.zon2json-lock"
BUILD_ZIG_ZON_NIX="$ROOT/build.zig.zon.nix"
BUILD_ZIG_ZON_TXT="$ROOT/build.zig.zon.txt"
if [ -f "${BUILD_ZIG_ZON_NIX}" ]; then
OLD_HASH=$(sha512sum "${BUILD_ZIG_ZON_NIX}" | awk '{print $1}')
elif [ "$1" != "--update" ]; then
echo -e "\nERROR: build.zig.zon.nix missing."
help
exit 1
fi
rm -f "$BUILD_ZIG_ZON_LOCK"
zon2nix "$BUILD_ZIG_ZON" > "$WORK_DIR/build.zig.zon.nix"
alejandra --quiet "$WORK_DIR/build.zig.zon.nix"
NEW_HASH=$(sha512sum "$WORK_DIR/build.zig.zon.nix" | awk '{print $1}')
if [ "${OLD_HASH}" == "${NEW_HASH}" ]; then
echo -e "\nOK: build.zig.zon.nix unchanged."
exit 0
elif [ "$1" != "--update" ]; then
echo -e "\nERROR: build.zig.zon.nix needs to be updated."
echo ""
echo " * Old hash: ${OLD_HASH}"
echo " * New hash: ${NEW_HASH}"
help
exit 1
else
jq -r '.[] .url' "$BUILD_ZIG_ZON_LOCK" | sort > "$BUILD_ZIG_ZON_TXT"
mv "$WORK_DIR/build.zig.zon.nix" "$BUILD_ZIG_ZON_NIX"
echo -e "\nOK: build.zig.zon.nix updated."
exit 0
fi

View File

@ -1,32 +1,13 @@
#!/bin/sh
set -e
# Because Zig does not fetch recursive dependencies when you run `zig build
# --fetch` (see https://github.com/ziglang/zig/issues/20976) we need to do some
# extra work to fetch everything that we actually need to build without Internet
# access (such as when building a Nix package).
# NOTE THIS IS A TEMPORARY SCRIPT TO SUPPORT PACKAGE MAINTAINERS.
#
# An example of this happening:
# A future Zig version will hopefully fix the issue where
# `zig build --fetch` doesn't fetch transitive dependencies[1]. When that
# is resolved, we won't need any special machinery for the general use case
# at all and packagers can just use `zig build --fetch`.
#
# error: builder for '/nix/store/cx8qcwrhjmjxik2547fw99v5j6np5san-ghostty-0.1.0.drv' failed with exit code 1;
# la/build/tmp.xgHOheUF7V/p/12208cfdda4d5fdbc81b0c44b82e4d6dba2d4a86bff644a153e026fdfc80f8469133/build.zig.zon:7:20: error: unable to discover remote git server capabilities: TemporaryNameServerFailure
# > .url = "git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e",
# > ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# > /build/tmp.xgHOheUF7V/p/12208cfdda4d5fdbc81b0c44b82e4d6dba2d4a86bff644a153e026fdfc80f8469133/build.zig.zon:16:20: error: unable to discover remote git server capabilities: TemporaryNameServerFailure
# > .url = "git+https://github.com/mitchellh/libxev#f6a672a78436d8efee1aa847a43a900ad773618b",
# > ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# >
# For full logs, run 'nix log /nix/store/cx8qcwrhjmjxik2547fw99v5j6np5san-ghostty-0.1.0.drv'.
#
# To update this script, add any failing URLs with a line like this:
#
# zig fetch <url>
#
# Periodically old URLs may need to be cleaned out.
#
# Hopefully when the Zig issue is fixed this script can be eliminated in favor
# of a plain `zig build --fetch`.
# [1]: https://github.com/ziglang/zig/issues/20976
if [ -z ${ZIG_GLOBAL_CACHE_DIR+x} ]
then
@ -34,6 +15,13 @@ then
exit 1
fi
zig build --fetch
zig fetch git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e
zig fetch git+https://github.com/mitchellh/libxev#f6a672a78436d8efee1aa847a43a900ad773618b
# Go through each line of our build.zig.zon.txt and fetch it.
SCRIPT_PATH="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
ZON_TXT_FILE="$SCRIPT_PATH/../../build.zig.zon.txt"
while IFS= read -r url; do
echo "Fetching: $url"
zig fetch "$url" >/dev/null 2>&1 || {
echo "Failed to fetch: $url" >&2
exit 1
}
done < "$ZON_TXT_FILE"

View File

@ -0,0 +1,30 @@
#!/bin/sh
#
# This script generates a directory that can be uploaded to blob
# storage to mirror our dependencies. The dependencies are unmodified
# so their checksum and content hashes will match.
set -e # Exit immediately if a command exits with a non-zero status
SCRIPT_PATH="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
INPUT_FILE="$SCRIPT_PATH/../../build.zig.zon2json-lock"
OUTPUT_DIR="blob"
# Ensure the output directory exists
mkdir -p "$OUTPUT_DIR"
# Use jq to iterate over the JSON and download files
jq -r 'to_entries[] | "\(.key) \(.value.name) \(.value.url)"' "$INPUT_FILE" | while read -r key name url; do
# Skip URLs that don't start with http(s). They aren't necessary for
# our mirror.
if ! echo "$url" | grep -Eq "^https?://"; then
continue
fi
# Extract the file extension from the URL
extension=$(echo "$url" | grep -oE '\.[a-z0-9]+(\.[a-z0-9]+)?$')
filename="${name}-${key}${extension}"
echo "$url -> $filename"
curl -L -o "$OUTPUT_DIR/$filename" "$url"
done

View File

@ -14,6 +14,7 @@
python3,
qemu,
scdoc,
snapcraft,
valgrind,
#, vulkan-loader # unused
vttest,
@ -30,7 +31,9 @@
glib,
glslang,
gtk4,
gobject-introspection,
libadwaita,
blueprint-compiler,
adwaita-icon-theme,
hicolor-icon-theme,
harfbuzz,
@ -47,6 +50,7 @@
simdutf,
zlib,
alejandra,
jq,
minisign,
pandoc,
hyperfine,
@ -54,6 +58,8 @@
wayland,
wayland-scanner,
wayland-protocols,
zig2nix,
system,
}: let
# See package.nix. Keep in sync.
rpathLibs =
@ -83,6 +89,7 @@
libadwaita
gtk4
glib
gobject-introspection
wayland
];
in
@ -92,6 +99,7 @@ in
packages =
[
# For builds
jq
llvmPackages_latest.llvm
minisign
ncurses
@ -100,6 +108,7 @@ in
scdoc
zig
zip
zig2nix.packages.${system}.zon2nix
# For web and wasm stuff
nodejs
@ -129,6 +138,7 @@ in
qemu
gdb
snapcraft
valgrind
wraptest
@ -154,9 +164,11 @@ in
libXrandr
# Only needed for GTK builds
blueprint-compiler
libadwaita
gtk4
glib
gobject-introspection
wayland
wayland-scanner
wayland-protocols

View File

@ -2,6 +2,7 @@
lib,
stdenv,
bzip2,
callPackage,
expat,
fontconfig,
freetype,
@ -12,7 +13,9 @@
libGL,
glib,
gtk4,
gobject-introspection,
libadwaita,
blueprint-compiler,
wrapGAppsHook4,
gsettings-desktop-schemas,
git,
@ -40,82 +43,36 @@
# ultimately acted on and has made its way to a nixpkgs implementation, this
# can probably be removed in favor of that.
zig_hook = zig_0_13.hook.overrideAttrs {
zig_default_flags = "-Dcpu=baseline -Doptimize=${optimize}";
};
# We limit source like this to try and reduce the amount of rebuilds as possible
# thus we only provide the source that is needed for the build
#
# NOTE: as of the current moment only linux files are provided,
# since darwin support is not finished
src = lib.fileset.toSource {
root = ../.;
fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ../.)) (
lib.fileset.unions [
../dist/linux
../images
../include
../pkg
../src
../vendor
../build.zig
../build.zig.zon
./build-support/fetch-zig-cache.sh
]
);
};
# This hash is the computation of the zigCache fixed-output derivation. This
# allows us to use remote package dependencies without breaking the sandbox.
#
# This will need updating whenever dependencies get updated (e.g. changes are
# made to zig.build.zon). If you see that the main build is trying to reach
# out to the internet and failing, this is likely the cause. Change this
# value back to lib.fakeHash, and re-run. The build failure should emit the
# updated hash, which of course, should be validated before updating here.
#
# (It's also possible that you might see a hash mismatch - without the
# network errors - if you don't have a previous instance of the cache
# derivation in your store already. If so, just update the value as above.)
zigCacheHash = import ./zigCacheHash.nix;
zigCache = stdenv.mkDerivation {
inherit src;
name = "ghostty-cache";
nativeBuildInputs = [
git
zig_hook
];
dontConfigure = true;
dontUseZigBuild = true;
dontUseZigInstall = true;
dontFixup = true;
buildPhase = ''
runHook preBuild
sh ./nix/build-support/fetch-zig-cache.sh
runHook postBuild
'';
installPhase = ''
runHook preInstall
cp -r --reflink=auto $ZIG_GLOBAL_CACHE_DIR $out
runHook postInstall
'';
outputHashMode = "recursive";
outputHash = zigCacheHash;
zig_default_flags = "-Dcpu=baseline -Doptimize=${optimize} --color off";
};
in
stdenv.mkDerivation (finalAttrs: {
pname = "ghostty";
version = "1.0.2";
inherit src;
version = "1.1.3";
# We limit source like this to try and reduce the amount of rebuilds as possible
# thus we only provide the source that is needed for the build
#
# NOTE: as of the current moment only linux files are provided,
# since darwin support is not finished
src = lib.fileset.toSource {
root = ../.;
fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ../.)) (
lib.fileset.unions [
../dist/linux
../images
../include
../pkg
../src
../vendor
../build.zig
../build.zig.zon
../build.zig.zon.nix
]
);
};
deps = callPackage ../build.zig.zon.nix {name = "ghostty-cache-${finalAttrs.version}";};
nativeBuildInputs =
[
@ -124,7 +81,9 @@ in
pandoc
pkg-config
zig_hook
gobject-introspection
wrapGAppsHook4
blueprint-compiler
]
++ lib.optionals enableWayland [
wayland-scanner
@ -162,13 +121,13 @@ in
dontConfigure = true;
zigBuildFlags = "-Dversion-string=${finalAttrs.version}-${revision}-nix -Dgtk-x11=${lib.boolToString enableX11} -Dgtk-wayland=${lib.boolToString enableWayland}";
preBuild = ''
rm -rf $ZIG_GLOBAL_CACHE_DIR
cp -r --reflink=auto ${zigCache} $ZIG_GLOBAL_CACHE_DIR
chmod u+rwX -R $ZIG_GLOBAL_CACHE_DIR
'';
zigBuildFlags = [
"--system"
"${finalAttrs.deps}"
"-Dversion-string=${finalAttrs.version}-${revision}-nix"
"-Dgtk-x11=${lib.boolToString enableX11}"
"-Dgtk-wayland=${lib.boolToString enableWayland}"
];
outputs = [
"out"
@ -202,7 +161,7 @@ in
'';
meta = {
homepage = "https://github.com/ghostty-org/ghostty";
homepage = "https://ghostty.org";
license = lib.licenses.mit;
platforms = [
"x86_64-linux"

View File

@ -0,0 +1,18 @@
{...}: {
imports = [
./common.nix
];
services.xserver = {
displayManager = {
lightdm = {
enable = true;
};
};
desktopManager = {
cinnamon = {
enable = true;
};
};
};
}

136
nix/vm/common-gnome.nix Normal file
View File

@ -0,0 +1,136 @@
{
config,
lib,
pkgs,
...
}: {
imports = [
./common.nix
];
services.xserver = {
displayManager = {
gdm = {
enable = true;
autoSuspend = false;
};
};
desktopManager = {
gnome = {
enable = true;
};
};
};
environment.systemPackages = [
pkgs.gnomeExtensions.no-overview
];
environment.gnome.excludePackages = with pkgs; [
atomix
baobab
cheese
epiphany
evince
file-roller
geary
gnome-backgrounds
gnome-calculator
gnome-calendar
gnome-clocks
gnome-connections
gnome-contacts
gnome-disk-utility
gnome-extension-manager
gnome-logs
gnome-maps
gnome-music
gnome-photos
gnome-software
gnome-system-monitor
gnome-text-editor
gnome-themes-extra
gnome-tour
gnome-user-docs
gnome-weather
hitori
iagno
loupe
nautilus
orca
seahorse
simple-scan
snapshot
sushi
tali
totem
yelp
];
programs.dconf = {
enable = true;
profiles.user.databases = [
{
settings = with lib.gvariant; {
"org/gnome/desktop/background" = {
picture-uri = "file://${pkgs.ghostty}/share/icons/hicolor/512x512/apps/com.mitchellh.ghostty.png";
picture-uri-dark = "file://${pkgs.ghostty}/share/icons/hicolor/512x512/apps/com.mitchellh.ghostty.png";
picture-options = "centered";
primary-color = "#000000000000";
secondary-color = "#000000000000";
};
"org/gnome/desktop/interface" = {
color-scheme = "prefer-dark";
};
"org/gnome/desktop/notifications" = {
show-in-lock-screen = false;
};
"org/gnome/desktop/screensaver" = {
lock-enabled = false;
picture-uri = "file://${pkgs.ghostty}/share/icons/hicolor/512x512/apps/com.mitchellh.ghostty.png";
picture-options = "centered";
primary-color = "#000000000000";
secondary-color = "#000000000000";
};
"org/gnome/desktop/session" = {
idle-delay = mkUint32 0;
};
"org/gnome/shell" = {
disable-user-extensions = false;
enabled-extensions = builtins.map (x: x.extensionUuid) (
lib.filter (p: p ? extensionUuid) config.environment.systemPackages
);
};
};
}
];
};
programs.geary.enable = false;
services.gnome = {
gnome-browser-connector.enable = false;
gnome-initial-setup.enable = false;
gnome-online-accounts.enable = false;
gnome-remote-desktop.enable = false;
rygel.enable = false;
};
system.activationScripts = {
face = {
text = ''
mkdir -p /var/lib/AccountsService/{icons,users}
cp ${pkgs.ghostty}/share/icons/hicolor/1024x1024/apps/com.mitchellh.ghostty.png /var/lib/AccountsService/icons/ghostty
echo -e "[User]\nIcon=/var/lib/AccountsService/icons/ghostty\n" > /var/lib/AccountsService/users/ghostty
chown root:root /var/lib/AccountsService/users/ghostty
chmod 0600 /var/lib/AccountsService/users/ghostty
chown root:root /var/lib/AccountsService/icons/ghostty
chmod 0444 /var/lib/AccountsService/icons/ghostty
'';
};
};
}

21
nix/vm/common-plasma6.nix Normal file
View File

@ -0,0 +1,21 @@
{...}: {
imports = [
./common.nix
];
services = {
displayManager = {
sddm = {
enable = true;
wayland = {
enable = true;
};
};
};
desktopManager = {
plasma6 = {
enable = true;
};
};
};
}

18
nix/vm/common-xfce.nix Normal file
View File

@ -0,0 +1,18 @@
{...}: {
imports = [
./common.nix
];
services.xserver = {
displayManager = {
lightdm = {
enable = true;
};
};
desktopManager = {
xfce = {
enable = true;
};
};
};
}

83
nix/vm/common.nix Normal file
View File

@ -0,0 +1,83 @@
{pkgs, ...}: {
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
documentation.nixos.enable = false;
networking.hostName = "ghostty";
networking.domain = "mitchellh.com";
virtualisation.vmVariant = {
virtualisation.memorySize = 2048;
};
nix = {
settings = {
trusted-users = [
"root"
"ghostty"
];
};
extraOptions = ''
experimental-features = nix-command flakes
'';
};
users.mutableUsers = false;
users.groups.ghostty = {};
users.users.ghostty = {
description = "Ghostty";
group = "ghostty";
extraGroups = ["wheel"];
isNormalUser = true;
initialPassword = "ghostty";
};
environment.etc = {
"xdg/autostart/com.mitchellh.ghostty.desktop" = {
source = "${pkgs.ghostty}/share/applications/com.mitchellh.ghostty.desktop";
};
};
environment.systemPackages = [
pkgs.kitty
pkgs.fish
pkgs.ghostty
pkgs.helix
pkgs.neovim
pkgs.xterm
pkgs.zsh
];
security.polkit = {
enable = true;
};
services.dbus = {
enable = true;
};
services.displayManager = {
autoLogin = {
user = "ghostty";
};
};
services.libinput = {
enable = true;
};
services.qemuGuest = {
enable = true;
};
services.spice-vdagentd = {
enable = true;
};
services.xserver = {
enable = true;
};
}

View File

@ -0,0 +1,12 @@
{
system,
nixpkgs,
overlay,
module,
uid ? 1000,
gid ? 1000,
}:
import ./create.nix {
inherit system nixpkgs overlay module uid gid;
common = ./common-cinnamon.nix;
}

12
nix/vm/create-gnome.nix Normal file
View File

@ -0,0 +1,12 @@
{
system,
nixpkgs,
overlay,
module,
uid ? 1000,
gid ? 1000,
}:
import ./create.nix {
inherit system nixpkgs overlay module uid gid;
common = ./common-gnome.nix;
}

12
nix/vm/create-plasma6.nix Normal file
View File

@ -0,0 +1,12 @@
{
system,
nixpkgs,
overlay,
module,
uid ? 1000,
gid ? 1000,
}:
import ./create.nix {
inherit system nixpkgs overlay module uid gid;
common = ./common-plasma6.nix;
}

12
nix/vm/create-xfce.nix Normal file
View File

@ -0,0 +1,12 @@
{
system,
nixpkgs,
overlay,
module,
uid ? 1000,
gid ? 1000,
}:
import ./create.nix {
inherit system nixpkgs overlay module uid gid;
common = ./common-xfce.nix;
}

42
nix/vm/create.nix Normal file
View File

@ -0,0 +1,42 @@
{
system,
nixpkgs,
overlay,
module,
common ? ./common.nix,
uid ? 1000,
gid ? 1000,
}: let
pkgs = import nixpkgs {
inherit system;
overlays = [
overlay
];
};
in
nixpkgs.lib.nixosSystem {
system = builtins.replaceStrings ["darwin"] ["linux"] system;
modules = [
{
virtualisation.vmVariant = {
virtualisation.host.pkgs = pkgs;
};
nixpkgs.overlays = [
overlay
];
users.groups.ghostty = {
gid = gid;
};
users.users.ghostty = {
uid = uid;
};
system.stateVersion = nixpkgs.lib.trivial.release;
}
common
module
];
}

View File

@ -0,0 +1,7 @@
{...}: {
imports = [
./common-cinnamon.nix
];
services.displayManager.defaultSession = "cinnamon-wayland";
}

9
nix/vm/wayland-gnome.nix Normal file
View File

@ -0,0 +1,9 @@
{...}: {
imports = [
./common-gnome.nix
];
services.displayManager = {
defaultSession = "gnome";
};
}

View File

@ -0,0 +1,6 @@
{...}: {
imports = [
./common-plasma6.nix
];
services.displayManager.defaultSession = "plasma";
}

7
nix/vm/x11-cinnamon.nix Normal file
View File

@ -0,0 +1,7 @@
{...}: {
imports = [
./common-cinnamon.nix
];
services.displayManager.defaultSession = "cinnamon";
}

9
nix/vm/x11-gnome.nix Normal file
View File

@ -0,0 +1,9 @@
{...}: {
imports = [
./common-gnome.nix
];
services.displayManager = {
defaultSession = "gnome-xorg";
};
}

6
nix/vm/x11-plasma6.nix Normal file
View File

@ -0,0 +1,6 @@
{...}: {
imports = [
./common-plasma6.nix
];
services.displayManager.defaultSession = "plasmax11";
}

7
nix/vm/x11-xfce.nix Normal file
View File

@ -0,0 +1,7 @@
{...}: {
imports = [
./common-xfce.nix
];
services.displayManager.defaultSession = "xfce";
}

View File

@ -1,3 +1,3 @@
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
# more details.
"sha256-PnfSy793kcVt85q47kWR0xkivXoMOZAAmuUyKO9vqAI="
"sha256-S8kS+gO17dl9LJGKL1+kgDUre+vPTmdTvXzgc585Fl8="

View File

@ -6,7 +6,8 @@
// This should be kept in sync with the submodule in the cimgui source
// code in ./vendor/ to be safe that they're compatible.
.imgui = .{
.url = "https://github.com/ocornut/imgui/archive/e391fe2e66eb1c96b1624ae8444dc64c23146ef4.tar.gz",
// ocornut/imgui
.url = "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
.hash = "1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402",
},

View File

@ -3,8 +3,9 @@
.version = "2.13.2",
.paths = .{""},
.dependencies = .{
// freetype/freetype
.freetype = .{
.url = "https://github.com/freetype/freetype/archive/refs/tags/VER-2-13-2.tar.gz",
.url = "https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz",
.hash = "1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d",
},

View File

@ -3,8 +3,9 @@
.version = "14.2.0",
.paths = .{""},
.dependencies = .{
// KhronosGroup/glslang
.glslang = .{
.url = "https://github.com/KhronosGroup/glslang/archive/refs/tags/14.2.0.tar.gz",
.url = "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
.hash = "12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1",
},

View File

@ -3,8 +3,9 @@
.version = "8.4.0",
.paths = .{""},
.dependencies = .{
// harfbuzz/harfbuzz
.harfbuzz = .{
.url = "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/8.4.0.tar.gz",
.url = "https://deps.files.ghostty.org/harfbuzz-1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122.tar.gz",
.hash = "1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122",
},

View File

@ -3,8 +3,9 @@
.version = "1.1.0",
.paths = .{""},
.dependencies = .{
// google/highway
.highway = .{
.url = "https://github.com/google/highway/archive/refs/tags/1.1.0.tar.gz",
.url = "https://deps.files.ghostty.org/highway-12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b.tar.gz",
.hash = "12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b",
},

View File

@ -3,8 +3,9 @@
.version = "1.6.43",
.paths = .{""},
.dependencies = .{
// glennrp/libpng
.libpng = .{
.url = "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.43.tar.gz",
.url = "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz",
.hash = "1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66",
},

View File

@ -18,9 +18,72 @@ pub const ColorSpace = opaque {
) orelse Allocator.Error.OutOfMemory;
}
pub fn createNamed(name: Name) Allocator.Error!*ColorSpace {
return @as(
?*ColorSpace,
@ptrFromInt(@intFromPtr(c.CGColorSpaceCreateWithName(name.cfstring()))),
) orelse Allocator.Error.OutOfMemory;
}
pub fn release(self: *ColorSpace) void {
c.CGColorSpaceRelease(@ptrCast(self));
}
pub const Name = enum {
/// This color space uses the DCI P3 primaries, a D65 white point, and
/// the sRGB transfer function.
displayP3,
/// The Display P3 color space with a linear transfer function and
/// extended-range values.
extendedLinearDisplayP3,
/// The sRGB colorimetry and non-linear transfer function are specified
/// in IEC 61966-2-1.
sRGB,
/// This color space has the same colorimetry as `sRGB`, but uses a
/// linear transfer function.
linearSRGB,
/// This color space has the same colorimetry as `sRGB`, but you can
/// encode component values below `0.0` and above `1.0`. Negative values
/// are encoded as the signed reflection of the original encoding
/// function, as shown in the formula below:
/// ```
/// extendedTransferFunction(x) = sign(x) * sRGBTransferFunction(abs(x))
/// ```
extendedSRGB,
/// This color space has the same colorimetry as `sRGB`; in addition,
/// you may encode component values below `0.0` and above `1.0`.
extendedLinearSRGB,
/// ...
genericGrayGamma2_2,
/// ...
linearGray,
/// This color space has the same colorimetry as `genericGrayGamma2_2`,
/// but you can encode component values below `0.0` and above `1.0`.
/// Negative values are encoded as the signed reflection of the
/// original encoding function, as shown in the formula below:
/// ```
/// extendedGrayTransferFunction(x) = sign(x) * gamma22Function(abs(x))
/// ```
extendedGray,
/// This color space has the same colorimetry as `linearGray`; in
/// addition, you may encode component values below `0.0` and above `1.0`.
extendedLinearGray,
fn cfstring(self: Name) c.CFStringRef {
return switch (self) {
.displayP3 => c.kCGColorSpaceDisplayP3,
.extendedLinearDisplayP3 => c.kCGColorSpaceExtendedLinearDisplayP3,
.sRGB => c.kCGColorSpaceSRGB,
.extendedSRGB => c.kCGColorSpaceExtendedSRGB,
.linearSRGB => c.kCGColorSpaceLinearSRGB,
.extendedLinearSRGB => c.kCGColorSpaceExtendedLinearSRGB,
.genericGrayGamma2_2 => c.kCGColorSpaceGenericGrayGamma2_2,
.extendedGray => c.kCGColorSpaceExtendedGray,
.linearGray => c.kCGColorSpaceLinearGray,
.extendedLinearGray => c.kCGColorSpaceExtendedLinearGray,
};
}
};
};
test {

Some files were not shown because too many files have changed in this diff Show More