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 vendor/** linguist-vendored
website/** linguist-documentation website/** linguist-documentation
pkg/breakpad/vendor/** linguist-vendored pkg/breakpad/vendor/** linguist-vendored

View File

@ -1,6 +1,31 @@
on: [push, pull_request] on: [push, pull_request]
name: Nix name: Nix
jobs: 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: check-zig-cache-hash:
if: github.repository == 'ghostty-org/ghostty' if: github.repository == 'ghostty-org/ghostty'
runs-on: namespace-profile-ghostty-sm runs-on: namespace-profile-ghostty-sm
@ -25,5 +50,5 @@ jobs:
name: ghostty name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
useDaemon: false # sometimes fails on short jobs useDaemon: false # sometimes fails on short jobs
- name: Check Zig cache hash - name: Check Zig cache
run: nix develop -c ./nix/build-support/check-zig-cache-hash.sh 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 # Setup Sparkle
- name: Setup Sparkle - name: Setup Sparkle
env: env:
SPARKLE_VERSION: 2.6.3 SPARKLE_VERSION: 2.6.4
run: | run: |
mkdir -p .action/sparkle mkdir -p .action/sparkle
cd .action/sparkle cd .action/sparkle

View File

@ -7,6 +7,7 @@ on:
upload: upload:
description: "Upload final artifacts to R2" description: "Upload final artifacts to R2"
default: false default: false
push: push:
tags: tags:
- "v[0-9]+.[0-9]+.[0-9]+" - "v[0-9]+.[0-9]+.[0-9]+"
@ -135,7 +136,7 @@ jobs:
- name: Setup Sparkle - name: Setup Sparkle
env: env:
SPARKLE_VERSION: 2.6.3 SPARKLE_VERSION: 2.6.4
run: | run: |
mkdir -p .action/sparkle mkdir -p .action/sparkle
cd .action/sparkle cd .action/sparkle
@ -297,7 +298,7 @@ jobs:
- name: Setup Sparkle - name: Setup Sparkle
env: env:
SPARKLE_VERSION: 2.6.3 SPARKLE_VERSION: 2.6.4
run: | run: |
mkdir -p .action/sparkle mkdir -p .action/sparkle
cd .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.zip blob/${GHOSTTY_VERSION}/ghostty-macos-universal.zip
mv ghostty-macos-universal-dsym.zip blob/${GHOSTTY_VERSION}/ghostty-macos-universal-dsym.zip mv ghostty-macos-universal-dsym.zip blob/${GHOSTTY_VERSION}/ghostty-macos-universal-dsym.zip
mv Ghostty.dmg blob/${GHOSTTY_VERSION}/Ghostty.dmg mv Ghostty.dmg blob/${GHOSTTY_VERSION}/Ghostty.dmg
mv appcast.xml blob/${GHOSTTY_VERSION}/appcast-staged.xml
- name: Upload to R2 - name: Upload to R2
uses: ryand56/r2-upload-action@latest uses: ryand56/r2-upload-action@latest
with: with:
@ -376,18 +378,3 @@ jobs:
r2-bucket: ghostty-release r2-bucket: ghostty-release
source-dir: blob source-dir: blob
destination-dir: ./ 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 # Setup Sparkle
- name: Setup Sparkle - name: Setup Sparkle
env: env:
SPARKLE_VERSION: 2.6.3 SPARKLE_VERSION: 2.6.4
run: | run: |
mkdir -p .action/sparkle mkdir -p .action/sparkle
cd .action/sparkle cd .action/sparkle

View File

@ -6,6 +6,47 @@ on:
name: Test name: Test
jobs: 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: build:
strategy: strategy:
fail-fast: false fail-fast: false
@ -163,10 +204,14 @@ jobs:
- name: XCode Select - name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app 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 # GhosttyKit is the framework that is built from Zig for our native
# Mac app to access. # Mac app to access.
- name: Build GhosttyKit - 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 # The native app is built with native XCode tooling. This also does
# codesigning. IMPORTANT: this must NOT run in a Nix environment. # codesigning. IMPORTANT: this must NOT run in a Nix environment.
@ -199,35 +244,65 @@ jobs:
- name: XCode Select - name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app 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 - name: Test All
run: | run: |
# OpenGL # OpenGL
nix develop -c zig build test -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=freetype
nix develop -c zig build test -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
nix develop -c zig build test -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_freetype
nix develop -c zig build test -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_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=coretext_noshape
# Metal # Metal
nix develop -c zig build test -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=freetype
nix develop -c zig build test -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
nix develop -c zig build test -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_freetype
nix develop -c zig build test -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_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=coretext_noshape
- name: Build All - name: Build All
run: | run: |
nix develop -c zig build -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=freetype
nix develop -c zig build -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
nix develop -c zig build -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_freetype
nix develop -c zig build -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_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=coretext_noshape
nix develop -c zig build -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=freetype
nix develop -c zig build -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
nix develop -c zig build -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_freetype
nix develop -c zig build -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_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=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: build-windows:
runs-on: windows-2022 runs-on: windows-2022
@ -247,10 +322,10 @@ jobs:
run: | run: |
# Get the zig version from build.zig so that it only needs to be updated # Get the zig version from build.zig so that it only needs to be updated
$fileContent = Get-Content -Path "build.zig" -Raw $fileContent = Get-Content -Path "build.zig" -Raw
$pattern = 'const required_zig = "(.*?)";' $pattern = 'buildpkg\.requireZig\("(.*?)"\);'
$zigVersion = [regex]::Match($fileContent, $pattern).Groups[1].Value $zigVersion = [regex]::Match($fileContent, $pattern).Groups[1].Value
Write-Output $version
$version = "zig-windows-x86_64-$zigVersion" $version = "zig-windows-x86_64-$zigVersion"
Write-Output $version
$uri = "https://ziglang.org/download/$zigVersion/$version.zip" $uri = "https://ziglang.org/download/$zigVersion/$version.zip"
Invoke-WebRequest -Uri "$uri" -OutFile ".\zig-windows.zip" Invoke-WebRequest -Uri "$uri" -OutFile ".\zig-windows.zip"
Expand-Archive -Path ".\zig-windows.zip" -DestinationPath ".\" -Force Expand-Archive -Path ".\zig-windows.zip" -DestinationPath ".\" -Force
@ -327,7 +402,7 @@ jobs:
run: nix develop -c zig build -Dapp-runtime=none test run: nix develop -c zig build -Dapp-runtime=none test
- name: Test GTK Build - 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 - name: Test GLFW Build
run: nix develop -c zig build -Dapp-runtime=glfw run: nix develop -c zig build -Dapp-runtime=glfw
@ -340,9 +415,9 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
adwaita: ["true", "false"]
x11: ["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 runs-on: namespace-profile-ghostty-sm
needs: test needs: test
env: env:
@ -373,8 +448,8 @@ jobs:
nix develop -c \ nix develop -c \
zig build \ zig build \
-Dapp-runtime=gtk \ -Dapp-runtime=gtk \
-Dgtk-adwaita=${{ matrix.adwaita }} \ -Dgtk-x11=${{ matrix.x11 }} \
-Dgtk-x11=${{ matrix.x11 }} -Dgtk-wayland=${{ matrix.wayland }}
test-sentry-linux: test-sentry-linux:
strategy: strategy:
@ -430,8 +505,12 @@ jobs:
- name: XCode Select - name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app 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 - name: test
run: nix develop -c zig build test run: nix develop -c zig build test --system ${{ steps.deps.outputs.deps }}
prettier: prettier:
if: github.repository == 'ghostty-org/ghostty' if: github.repository == 'ghostty-org/ghostty'
@ -548,3 +627,26 @@ jobs:
- name: Test ${{ matrix.pkg }} Build - name: Test ${{ matrix.pkg }} Build
run: | run: |
nix develop -c sh -c "cd pkg/${{ matrix.pkg }} ; zig build test" 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: | run: |
# Only proceed if build.zig.zon has changed # Only proceed if build.zig.zon has changed
if ! git diff --exit-code build.zig.zon; then 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.sh --update
nix develop -c ./nix/build-support/check-zig-cache-hash.sh nix develop -c ./nix/build-support/check-zig-cache.sh
fi fi
# Verify the build still works. We choose an arbitrary build type # Verify the build still works. We choose an arbitrary build type
# as a canary instead of testing all build types. # as a canary instead of testing all build types.
- name: Test Build - name: Test Build
run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true run: nix build .#ghostty
- name: Create pull request - name: Create pull request
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v7
@ -66,7 +66,7 @@ jobs:
commit-message: "deps: Update iTerm2 color schemes" commit-message: "deps: Update iTerm2 color schemes"
add-paths: | add-paths: |
build.zig.zon build.zig.zon
nix/zigCacheHash.nix build.zig.zon.nix
body: | body: |
Upstream revision: https://github.com/mbadolato/iTerm2-Color-Schemes/tree/${{ steps.zig_fetch.outputs.upstream_rev }} Upstream revision: https://github.com/mbadolato/iTerm2-Color-Schemes/tree/${{ steps.zig_fetch.outputs.upstream_rev }}
labels: dependencies labels: dependencies

1
.gitignore vendored
View File

@ -17,3 +17,4 @@ test/cases/**/*.actual.png
glad.zip glad.zip
/Box_test.ppm /Box_test.ppm
/Box_test_diff.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 > **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 > not open a WIP pull request to discuss a feature. Instead, use a discussion
> and link to your branch. > 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 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 Signature files are signed with
[minisign](https://jedisct1.github.io/minisign/) [minisign](https://jedisct1.github.io/minisign/)
using the following public key: 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` install step can then be done with a `mv` or `cp` command (from `/tmp/ghostty`
to wherever the package manager expects it). 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 ### Build Options
Ghostty uses the Zig build system. You can see all available build options by 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"); const buildpkg = @import("src/build/main.zig");
comptime { comptime {
// This is the required Zig version for building this project. We allow buildpkg.requireZig("0.13.0");
// 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 },
));
}
} }
pub fn build(b: *std.Build) !void { pub fn build(b: *std.Build) !void {

View File

@ -1,32 +1,39 @@
.{ .{
.name = "ghostty", .name = "ghostty",
.version = "1.0.2", .version = "1.1.3",
.paths = .{""}, .paths = .{""},
.dependencies = .{ .dependencies = .{
// Zig libs // Zig libs
.libxev = .{ .libxev = .{
.url = "https://github.com/mitchellh/libxev/archive/db6a52bafadf00360e675fefa7926e8e6c0e9931.tar.gz", // mitchellh/libxev
.hash = "12206029de146b685739f69b10a6f08baee86b3d0a5f9a659fa2b2b66c9602078bbf", .url = "https://deps.files.ghostty.org/libxev-1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c.tar.gz",
.hash = "1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c",
}, },
.mach_glfw = .{ .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", .hash = "12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62",
.lazy = true, .lazy = true,
}, },
.vaxis = .{ .vaxis = .{
.url = "git+https://github.com/rockorager/libvaxis/?ref=main#6d729a2dc3b934818dffe06d2ba3ce02841ed74b", // rockorager/libvaxis
.hash = "12200df4ebeaed45de26cb2c9f3b6f3746d8013b604e035dae658f86f586c8c91d2f", .url = "git+https://github.com/rockorager/libvaxis#2237a7059eae99e9f132dd5acd1555e49d6c7d93",
.hash = "1220f5aec880d4f430cc1597ede88f1530da69e39a4986080e976b0c7b919c2ebfeb",
}, },
.z2d = .{ .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", .hash = "12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a",
}, },
.zig_objc = .{ .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", .hash = "1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634",
}, },
.zig_js = .{ .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", .hash = "12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc",
}, },
.ziglyph = .{ .ziglyph = .{
@ -34,13 +41,20 @@
.hash = "12207831bce7d4abce57b5a98e8f3635811cfefd160bca022eb91fe905d36a02cf25", .hash = "12207831bce7d4abce57b5a98e8f3635811cfefd160bca022eb91fe905d36a02cf25",
}, },
.zig_wayland = .{ .zig_wayland = .{
.url = "https://codeberg.org/ifreund/zig-wayland/archive/a5e2e9b6a6d7fba638ace4d4b24a3b576a02685b.tar.gz", // codeberg ifreund/zig-wayland
.hash = "1220d41b23ae70e93355bb29dac1c07aa6aeb92427a2dffc4375e94b4de18111248c", .url = "https://deps.files.ghostty.org/zig-wayland-fbfe3b4ac0b472a27b1f1a67405436c58cbee12d.tar.gz",
.hash = "12209ca054cb1919fa276e328967f10b253f7537c4136eb48f3332b0f7cf661cad38",
}, },
.zf = .{ .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", .hash = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8",
}, },
.gobject = .{
// ianprime0509/zig-gobject
.url = "https://deps.files.ghostty.org/gobject-12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d.tar.zst",
.hash = "12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d",
},
// C libs // C libs
.cimgui = .{ .path = "./pkg/cimgui" }, .cimgui = .{ .path = "./pkg/cimgui" },
@ -72,15 +86,15 @@
.hash = "12201a57c6ce0001aa034fa80fba3e1cd2253c560a45748f4f4dd21ff23b491cddef", .hash = "12201a57c6ce0001aa034fa80fba3e1cd2253c560a45748f4f4dd21ff23b491cddef",
}, },
.plasma_wayland_protocols = .{ .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", .hash = "12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566",
}, },
// Other // Other
.apple_sdk = .{ .path = "./pkg/apple-sdk" }, .apple_sdk = .{ .path = "./pkg/apple-sdk" },
.iterm2_themes = .{ .iterm2_themes = .{
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/4762ad5bd6d3906e28babdc2bda8a967d63a63be.tar.gz", .url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/efb1bb1843500a751eb30afa58fe48a6bec8952c.tar.gz",
.hash = "1220a263b22113273d01bd33e3c06b8119cb2f63b4e5d414a85d88e3aa95bb68a2de", .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; Categories=System;TerminalEmulator;
Keywords=terminal;tty;pty; Keywords=terminal;tty;pty;
StartupNotify=true StartupNotify=true
StartupWMClass=com.mitchellh.ghostty
Terminal=false Terminal=false
Actions=new-window; Actions=new-window;
X-GNOME-UsesNotifications=true 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) now = datetime.now(timezone.utc)
version = os.environ["GHOSTTY_VERSION"] version = os.environ["GHOSTTY_VERSION"]
version_dash = version.replace('.', '-')
build = os.environ["GHOSTTY_BUILD"] build = os.environ["GHOSTTY_BUILD"]
commit = os.environ["GHOSTTY_COMMIT"] commit = os.environ["GHOSTTY_COMMIT"]
commit_long = os.environ["GHOSTTY_COMMIT_LONG"] commit_long = os.environ["GHOSTTY_COMMIT_LONG"]
@ -82,6 +83,8 @@ elem = ET.SubElement(item, "sparkle:shortVersionString")
elem.text = f"{version}" elem.text = f"{version}"
elem = ET.SubElement(item, "sparkle:minimumSystemVersion") elem = ET.SubElement(item, "sparkle:minimumSystemVersion")
elem.text = "13.0.0" 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 = ET.SubElement(item, "description")
elem.text = f""" elem.text = f"""
<h1>Ghostty v{version}</h1> <h1>Ghostty v{version}</h1>
@ -91,8 +94,8 @@ on {now.strftime('%Y-%m-%d')}.
</p> </p>
<p> <p>
We don't currently generate release notes for auto-updates. We don't currently generate release notes for auto-updates.
You can view the complete changelog and release notes on You can view the complete changelog and release notes
the <a href="https://ghostty.org">Ghostty website</a>. at <a href="https://ghostty.org/docs/install/release-notes/{version_dash}">ghostty.org/docs/install/release-notes/{version_dash}</a>.
</p> </p>
""" """
elem = ET.SubElement(item, "enclosure") elem = ET.SubElement(item, "enclosure")

62
flake.lock generated
View File

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

106
flake.nix
View File

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

View File

@ -159,7 +159,7 @@ typedef enum {
GHOSTTY_KEY_EQUAL, GHOSTTY_KEY_EQUAL,
GHOSTTY_KEY_LEFT_BRACKET, // [ GHOSTTY_KEY_LEFT_BRACKET, // [
GHOSTTY_KEY_RIGHT_BRACKET, // ] GHOSTTY_KEY_RIGHT_BRACKET, // ]
GHOSTTY_KEY_BACKSLASH, // / GHOSTTY_KEY_BACKSLASH, // \
// control // control
GHOSTTY_KEY_UP, GHOSTTY_KEY_UP,
@ -412,6 +412,7 @@ typedef enum {
GHOSTTY_FULLSCREEN_NATIVE, GHOSTTY_FULLSCREEN_NATIVE,
GHOSTTY_FULLSCREEN_NON_NATIVE, GHOSTTY_FULLSCREEN_NON_NATIVE,
GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU, GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU,
GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH,
} ghostty_action_fullscreen_e; } ghostty_action_fullscreen_e;
// apprt.action.SecureInput // apprt.action.SecureInput
@ -562,8 +563,10 @@ typedef enum {
GHOSTTY_ACTION_QUIT, GHOSTTY_ACTION_QUIT,
GHOSTTY_ACTION_NEW_WINDOW, GHOSTTY_ACTION_NEW_WINDOW,
GHOSTTY_ACTION_NEW_TAB, GHOSTTY_ACTION_NEW_TAB,
GHOSTTY_ACTION_CLOSE_TAB,
GHOSTTY_ACTION_NEW_SPLIT, GHOSTTY_ACTION_NEW_SPLIT,
GHOSTTY_ACTION_CLOSE_ALL_WINDOWS, GHOSTTY_ACTION_CLOSE_ALL_WINDOWS,
GHOSTTY_ACTION_TOGGLE_MAXIMIZE,
GHOSTTY_ACTION_TOGGLE_FULLSCREEN, GHOSTTY_ACTION_TOGGLE_FULLSCREEN,
GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW, GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW,
GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS, GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS,
@ -583,6 +586,7 @@ typedef enum {
GHOSTTY_ACTION_RENDER_INSPECTOR, GHOSTTY_ACTION_RENDER_INSPECTOR,
GHOSTTY_ACTION_DESKTOP_NOTIFICATION, GHOSTTY_ACTION_DESKTOP_NOTIFICATION,
GHOSTTY_ACTION_SET_TITLE, GHOSTTY_ACTION_SET_TITLE,
GHOSTTY_ACTION_PROMPT_TITLE,
GHOSTTY_ACTION_PWD, GHOSTTY_ACTION_PWD,
GHOSTTY_ACTION_MOUSE_SHAPE, GHOSTTY_ACTION_MOUSE_SHAPE,
GHOSTTY_ACTION_MOUSE_VISIBILITY, GHOSTTY_ACTION_MOUSE_VISIBILITY,
@ -642,7 +646,7 @@ typedef void (*ghostty_runtime_write_clipboard_cb)(void*,
ghostty_clipboard_e, ghostty_clipboard_e,
bool); bool);
typedef void (*ghostty_runtime_close_surface_cb)(void*, 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_target_s,
ghostty_action_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 */; }; A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */; };
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5D02AE0DEA7009128F3 /* MetalView.swift */; }; A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59FB5D02AE0DEA7009128F3 /* MetalView.swift */; };
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; }; 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 */; }; 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 */; }; A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */; }; 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 */; }; A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */; };
A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0572C9F30860017A1AE /* Cursor.swift */; }; A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0572C9F30860017A1AE /* Cursor.swift */; };
A5CBD0592C9F37B10017A1AE /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.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 */; }; 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 */; }; C1F26EA72B738B9900404083 /* NSView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F26EA62B738B9900404083 /* NSView+Extension.swift */; };
C1F26EE92B76CBFC00404083 /* VibrantLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = C1F26EE82B76CBFC00404083 /* VibrantLayer.m */; }; 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 */; }; FC5218FA2D10FFCE004C93E0 /* zsh in Resources */ = {isa = PBXBuildFile; fileRef = FC5218F92D10FFC7004C93E0 /* zsh */; };
FC9ABA9C2D0F53F80020D4C8 /* bash-completion in Resources */ = {isa = PBXBuildFile; fileRef = FC9ABA9B2D0F538D0020D4C8 /* bash-completion */; }; FC9ABA9C2D0F53F80020D4C8 /* bash-completion in Resources */ = {isa = PBXBuildFile; fileRef = FC9ABA9B2D0F538D0020D4C8 /* bash-completion */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -161,11 +166,15 @@
A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorView.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; FC9ABA9B2D0F538D0020D4C8 /* bash-completion */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "bash-completion"; path = "../zig-out/share/bash-completion"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -262,11 +272,13 @@
A534263D2A7DCBB000EBB7A2 /* Helpers */ = { A534263D2A7DCBB000EBB7A2 /* Helpers */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */,
A5A6F7292CC41B8700B232A5 /* Xcode.swift */, A5A6F7292CC41B8700B232A5 /* Xcode.swift */,
A5CEAFFE29C2410700646FDA /* Backport.swift */, A5CEAFFE29C2410700646FDA /* Backport.swift */,
A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */, A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */,
A5CBD0572C9F30860017A1AE /* Cursor.swift */, A5CBD0572C9F30860017A1AE /* Cursor.swift */,
A5D0AF3C2B37804400D21823 /* CodableBridge.swift */, A5D0AF3C2B37804400D21823 /* CodableBridge.swift */,
A5A2A3C92D4445E20033CF96 /* Dock.swift */,
A52FFF582CAA4FF1000C6A5B /* Fullscreen.swift */, A52FFF582CAA4FF1000C6A5B /* Fullscreen.swift */,
A59630962AEE163600D64628 /* HostingWindow.swift */, A59630962AEE163600D64628 /* HostingWindow.swift */,
A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */, A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */,
@ -274,12 +286,14 @@
A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */, A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */,
C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */, C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */,
A599CDAF2CF103F20049FA26 /* NSAppearance+Extension.swift */, A599CDAF2CF103F20049FA26 /* NSAppearance+Extension.swift */,
A5A2A3CB2D444AB80033CF96 /* NSApplication+Extension.swift */,
A54B0CEA2D0CFB4A00CBEFF8 /* NSImage+Extension.swift */, A54B0CEA2D0CFB4A00CBEFF8 /* NSImage+Extension.swift */,
A52FFF5C2CAB4D05000C6A5B /* NSScreen+Extension.swift */, A52FFF5C2CAB4D05000C6A5B /* NSScreen+Extension.swift */,
C1F26EA62B738B9900404083 /* NSView+Extension.swift */, C1F26EA62B738B9900404083 /* NSView+Extension.swift */,
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */, AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */,
A5985CD62C320C4500C57AD3 /* String+Extension.swift */, A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
A5CC36142C9CDA03004D6760 /* View+Extension.swift */, A5CC36142C9CDA03004D6760 /* View+Extension.swift */,
A5CA378D2D31D6C100931030 /* Weak.swift */,
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */, C1F26EE72B76CBFC00404083 /* VibrantLayer.h */,
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */, C1F26EE82B76CBFC00404083 /* VibrantLayer.m */,
A5CEAFDA29B8005900646FDA /* SplitView */, A5CEAFDA29B8005900646FDA /* SplitView */,
@ -448,6 +462,7 @@
children = ( children = (
A5CBD05B2CA0C5C70017A1AE /* QuickTerminal.xib */, A5CBD05B2CA0C5C70017A1AE /* QuickTerminal.xib */,
A5CBD05D2CA0C5E70017A1AE /* QuickTerminalController.swift */, A5CBD05D2CA0C5E70017A1AE /* QuickTerminalController.swift */,
CFBB5FE92D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift */,
A5CBD0632CA122E70017A1AE /* QuickTerminalPosition.swift */, A5CBD0632CA122E70017A1AE /* QuickTerminalPosition.swift */,
A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */, A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */,
A5CBD05F2CA0C9080017A1AE /* QuickTerminalWindow.swift */, A5CBD05F2CA0C9080017A1AE /* QuickTerminalWindow.swift */,
@ -611,11 +626,13 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */,
A59630A42AF059BB00D64628 /* Ghostty.SplitNode.swift in Sources */, A59630A42AF059BB00D64628 /* Ghostty.SplitNode.swift in Sources */,
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */, A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */,
A54B0CEB2D0CFB4C00CBEFF8 /* NSImage+Extension.swift in Sources */, A54B0CEB2D0CFB4C00CBEFF8 /* NSImage+Extension.swift in Sources */,
A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */, A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */,
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */, A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */,
A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */, A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */,
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */, A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */,
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */, A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */,
@ -628,6 +645,7 @@
A5CBD0602CA0C90A0017A1AE /* QuickTerminalWindow.swift in Sources */, A5CBD0602CA0C90A0017A1AE /* QuickTerminalWindow.swift in Sources */,
A5CBD05E2CA0C5EC0017A1AE /* QuickTerminalController.swift in Sources */, A5CBD05E2CA0C5EC0017A1AE /* QuickTerminalController.swift in Sources */,
A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */, A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */,
A5A2A3CA2D4445E30033CF96 /* Dock.swift in Sources */,
A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */, A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */,
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */, A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */, A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */,
@ -643,12 +661,14 @@
A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */, A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */,
A52FFF5B2CAA54B1000C6A5B /* FullscreenMode+Extension.swift in Sources */, A52FFF5B2CAA54B1000C6A5B /* FullscreenMode+Extension.swift in Sources */,
A5333E222B5A2128008AEFF7 /* SurfaceView_AppKit.swift in Sources */, A5333E222B5A2128008AEFF7 /* SurfaceView_AppKit.swift in Sources */,
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */,
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */, A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */,
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */, A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */, A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */,
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */, A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
A56D58862ACDDB4100508D2C /* Ghostty.Shell.swift in Sources */, A56D58862ACDDB4100508D2C /* Ghostty.Shell.swift in Sources */,
A5985CD72C320C4500C57AD3 /* String+Extension.swift in Sources */, A5985CD72C320C4500C57AD3 /* String+Extension.swift in Sources */,
A5A2A3CC2D444ABB0033CF96 /* NSApplication+Extension.swift in Sources */,
A59630A22AF0415000D64628 /* Ghostty.TerminalSplit.swift in Sources */, A59630A22AF0415000D64628 /* Ghostty.TerminalSplit.swift in Sources */,
A5FEB3002ABB69450068369E /* main.swift in Sources */, A5FEB3002ABB69450068369E /* main.swift in Sources */,
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */, A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,

View File

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

View File

@ -30,15 +30,18 @@ class AppDelegate: NSObject,
@IBOutlet private var menuSplitRight: NSMenuItem? @IBOutlet private var menuSplitRight: NSMenuItem?
@IBOutlet private var menuSplitDown: NSMenuItem? @IBOutlet private var menuSplitDown: NSMenuItem?
@IBOutlet private var menuClose: NSMenuItem? @IBOutlet private var menuClose: NSMenuItem?
@IBOutlet private var menuCloseTab: NSMenuItem?
@IBOutlet private var menuCloseWindow: NSMenuItem? @IBOutlet private var menuCloseWindow: NSMenuItem?
@IBOutlet private var menuCloseAllWindows: NSMenuItem? @IBOutlet private var menuCloseAllWindows: NSMenuItem?
@IBOutlet private var menuCopy: NSMenuItem? @IBOutlet private var menuCopy: NSMenuItem?
@IBOutlet private var menuPaste: NSMenuItem? @IBOutlet private var menuPaste: NSMenuItem?
@IBOutlet private var menuPasteSelection: NSMenuItem?
@IBOutlet private var menuSelectAll: NSMenuItem? @IBOutlet private var menuSelectAll: NSMenuItem?
@IBOutlet private var menuToggleVisibility: NSMenuItem? @IBOutlet private var menuToggleVisibility: NSMenuItem?
@IBOutlet private var menuToggleFullScreen: NSMenuItem? @IBOutlet private var menuToggleFullScreen: NSMenuItem?
@IBOutlet private var menuBringAllToFront: NSMenuItem?
@IBOutlet private var menuZoomSplit: NSMenuItem? @IBOutlet private var menuZoomSplit: NSMenuItem?
@IBOutlet private var menuPreviousSplit: NSMenuItem? @IBOutlet private var menuPreviousSplit: NSMenuItem?
@IBOutlet private var menuNextSplit: NSMenuItem? @IBOutlet private var menuNextSplit: NSMenuItem?
@ -50,6 +53,7 @@ class AppDelegate: NSObject,
@IBOutlet private var menuIncreaseFontSize: NSMenuItem? @IBOutlet private var menuIncreaseFontSize: NSMenuItem?
@IBOutlet private var menuDecreaseFontSize: NSMenuItem? @IBOutlet private var menuDecreaseFontSize: NSMenuItem?
@IBOutlet private var menuResetFontSize: NSMenuItem? @IBOutlet private var menuResetFontSize: NSMenuItem?
@IBOutlet private var menuChangeTitle: NSMenuItem?
@IBOutlet private var menuQuickTerminal: NSMenuItem? @IBOutlet private var menuQuickTerminal: NSMenuItem?
@IBOutlet private var menuTerminalInspector: NSMenuItem? @IBOutlet private var menuTerminalInspector: NSMenuItem?
@ -90,10 +94,8 @@ class AppDelegate: NSObject,
return ProcessInfo.processInfo.systemUptime - applicationLaunchTime return ProcessInfo.processInfo.systemUptime - applicationLaunchTime
} }
/// Tracks whether the application is currently visible. This can be gamed, i.e. if a user manually /// Tracks the windows that we hid for toggleVisibility.
/// brings each window one by one to the front. But at worst its off by one set of toggles and this private var hiddenState: ToggleVisibilityState? = nil
/// makes our logic very easy.
private var isVisible: Bool = true
/// The observer for the app appearance. /// The observer for the app appearance.
private var appearanceObserver: NSKeyValueObservation? = nil private var appearanceObserver: NSKeyValueObservation? = nil
@ -217,15 +219,20 @@ class AppDelegate: NSObject,
} }
func applicationDidBecomeActive(_ notification: Notification) { func applicationDidBecomeActive(_ notification: Notification) {
guard !applicationHasBecomeActive else { return } // If we're back manually then clear the hidden state because macOS handles it.
applicationHasBecomeActive = true self.hiddenState = nil
// Let's launch our first window. We only do this if we have no other windows. It // First launch stuff
// is possible to have other windows in a few scenarios: if (!applicationHasBecomeActive) {
// - if we're opening a URL since `application(_:openFile:)` is called before this. applicationHasBecomeActive = true
// - if we're restoring from persisted state
if terminalManager.windows.count == 0 && derivedConfig.initialWindow { // Let's launch our first window. We only do this if we have no other windows. It
terminalManager.newWindow() // 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 // 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 // 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 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. // If the user is shutting down, restarting, or logging out, we don't confirm quit.
why: if let event = NSAppleEventManager.shared().currentAppleEvent { 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_window", menuItem: self.menuNewWindow)
syncMenuShortcut(config, action: "new_tab", menuItem: self.menuNewTab) syncMenuShortcut(config, action: "new_tab", menuItem: self.menuNewTab)
syncMenuShortcut(config, action: "close_surface", menuItem: self.menuClose) 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_window", menuItem: self.menuCloseWindow)
syncMenuShortcut(config, action: "close_all_windows", menuItem: self.menuCloseAllWindows) syncMenuShortcut(config, action: "close_all_windows", menuItem: self.menuCloseAllWindows)
syncMenuShortcut(config, action: "new_split:right", menuItem: self.menuSplitRight) 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: "copy_to_clipboard", menuItem: self.menuCopy)
syncMenuShortcut(config, action: "paste_from_clipboard", menuItem: self.menuPaste) 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: "select_all", menuItem: self.menuSelectAll)
syncMenuShortcut(config, action: "toggle_split_zoom", menuItem: self.menuZoomSplit) 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: "increase_font_size:1", menuItem: self.menuIncreaseFontSize)
syncMenuShortcut(config, action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize) syncMenuShortcut(config, action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize)
syncMenuShortcut(config, action: "reset_font_size", menuItem: self.menuResetFontSize) 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_quick_terminal", menuItem: self.menuQuickTerminal)
syncMenuShortcut(config, action: "toggle_visibility", menuItem: self.menuToggleVisibility) syncMenuShortcut(config, action: "toggle_visibility", menuItem: self.menuToggleVisibility)
syncMenuShortcut(config, action: "inspector:toggle", menuItem: self.menuTerminalInspector) 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 // If we have a main window then we don't process any of the keys
// because we let it capture and propagate. // because we let it capture and propagate.
guard NSApp.mainWindow == nil else { return event } guard NSApp.mainWindow == nil else { return event }
// If this event as-is would result in a key binding then we send it. // If this event as-is would result in a key binding then we send it.
if let app = ghostty.app, if let app = ghostty.app,
ghostty_app_key_is_binding( ghostty_app_key_is_binding(
@ -440,26 +456,26 @@ class AppDelegate: NSObject,
return nil return nil
} }
} }
// If this event would be handled by our menu then we do nothing. // If this event would be handled by our menu then we do nothing.
if let mainMenu = NSApp.mainMenu, if let mainMenu = NSApp.mainMenu,
mainMenu.performKeyEquivalent(with: event) { mainMenu.performKeyEquivalent(with: event) {
return nil return nil
} }
// If we reach this point then we try to process the key event // If we reach this point then we try to process the key event
// through the Ghostty key mechanism. // through the Ghostty key mechanism.
// Ghostty must be loaded // Ghostty must be loaded
guard let ghostty = self.ghostty.app else { return event } guard let ghostty = self.ghostty.app else { return event }
// Build our event input and call ghostty // Build our event input and call ghostty
if (ghostty_app_key(ghostty, event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS))) { 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 // 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)") Ghostty.logger.debug("local key event handled event=\(event)")
return nil return nil
} }
return event return event
} }
@ -517,6 +533,15 @@ class AppDelegate: NSObject,
// AppKit mutex on the appearance. // AppKit mutex on the appearance.
DispatchQueue.main.async { self.syncAppearance(config: config) } 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. // If we have configuration errors, we need to show them.
let c = ConfigurationErrorsController.sharedInstance let c = ConfigurationErrorsController.sharedInstance
c.errors = config.errors c.errors = config.errors
@ -550,6 +575,30 @@ class AppDelegate: NSObject,
self.appIcon = nil self.appIcon = nil
break 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: case .customStyle:
guard let ghostColor = config.macosIconGhostColor else { break } guard let ghostColor = config.macosIconGhostColor else { break }
guard let screenColors = config.macosIconScreenColor 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 /// Toggles visibility of all Ghosty Terminal windows. When hidden, activates Ghostty as the frontmost application
@IBAction func toggleVisibility(_ sender: Any) { @IBAction func toggleVisibility(_ sender: Any) {
// We only care about terminal windows. // If we have focus, then we hide all windows.
for window in NSApp.windows.filter({ $0.windowController is BaseTerminalController }) { if NSApp.isActive {
if isVisible { // Toggle visibility doesn't do anything if the focused window is native
window.orderOut(nil) // fullscreen. This is only relevant if Ghostty is active.
} else { guard let keyWindow = NSApp.keyWindow,
window.makeKeyAndOrderFront(nil) !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 we're not active, we want to become active
if !isVisible { 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) NSApp.activate(ignoringOtherApps: true)
} }
isVisible.toggle() NSApplication.shared.arrangeInFront(sender)
} }
private struct DerivedConfig { private struct DerivedConfig {
@ -736,4 +799,33 @@ class AppDelegate: NSObject,
self.quickTerminalPosition = config.quickTerminalPosition 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"?> <?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> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23094"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23504"/>
</dependencies> </dependencies>
<objects> <objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -14,9 +14,12 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/> <customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="bbz-4X-AYv" userLabel="AppDelegate" customClass="AppDelegate" customModule="Ghostty" customModuleProvider="target"> <customObject id="bbz-4X-AYv" userLabel="AppDelegate" customClass="AppDelegate" customModule="Ghostty" customModuleProvider="target">
<connections> <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="menuCheckForUpdates" destination="GEA-5y-yzH" id="0nV-Tf-nJQ"/>
<outlet property="menuClose" destination="DVo-aG-piG" id="R3t-0C-aSU"/> <outlet property="menuClose" destination="DVo-aG-piG" id="R3t-0C-aSU"/>
<outlet property="menuCloseAllWindows" destination="yKr-Vi-Yqw" id="Zet-Ir-zbm"/> <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="menuCloseWindow" destination="W5w-UZ-crk" id="6ff-BT-ENV"/>
<outlet property="menuCopy" destination="Jqf-pv-Zcu" id="bKd-1C-oy9"/> <outlet property="menuCopy" destination="Jqf-pv-Zcu" id="bKd-1C-oy9"/>
<outlet property="menuDecreaseFontSize" destination="kzb-SZ-dOA" id="Y1B-Vh-6Z2"/> <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="menuNextSplit" destination="bD7-ei-wKU" id="LeT-xw-eh4"/>
<outlet property="menuOpenConfig" destination="BOF-NM-1cW" id="Nze-Go-glw"/> <outlet property="menuOpenConfig" destination="BOF-NM-1cW" id="Nze-Go-glw"/>
<outlet property="menuPaste" destination="i27-pK-umN" id="ICc-X2-gV3"/> <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="menuPreviousSplit" destination="Lic-px-1wg" id="Rto-CG-yRe"/>
<outlet property="menuQuickTerminal" destination="1pv-LF-NBJ" id="glN-5B-IGi"/> <outlet property="menuQuickTerminal" destination="1pv-LF-NBJ" id="glN-5B-IGi"/>
<outlet property="menuQuit" destination="4sb-4s-VLi" id="qYN-S1-6UW"/> <outlet property="menuQuit" destination="4sb-4s-VLi" id="qYN-S1-6UW"/>
@ -154,6 +158,12 @@
<action selector="close:" target="-1" id="tTZ-2b-Mbm"/> <action selector="close:" target="-1" id="tTZ-2b-Mbm"/>
</connections> </connections>
</menuItem> </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"> <menuItem title="Close Window" id="W5w-UZ-crk">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
@ -185,6 +195,12 @@
<action selector="paste:" target="-1" id="ZKe-2B-mel"/> <action selector="paste:" target="-1" id="ZKe-2B-mel"/>
</connections> </connections>
</menuItem> </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"> <menuItem title="Select All" id="q2h-lq-e4r">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
@ -218,6 +234,13 @@
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="L3L-I8-sqk"/> <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"> <menuItem title="Quick Terminal" id="1pv-LF-NBJ">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
@ -256,12 +279,6 @@
<action selector="toggleGhosttyFullScreen:" target="-1" id="QB9-7R-xyc"/> <action selector="toggleGhosttyFullScreen:" target="-1" id="QB9-7R-xyc"/>
</connections> </connections>
</menuItem> </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"> <menuItem title="Show/Hide All Terminals" id="DOX-wA-ilh">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
<connections> <connections>
@ -356,6 +373,13 @@
</items> </items>
</menu> </menu>
</menuItem> </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> </items>
</menu> </menu>
</menuItem> </menuItem>

View File

@ -3,6 +3,12 @@ import Cocoa
import SwiftUI import SwiftUI
import GhosttyKit 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. /// Controller for the "quick" terminal.
class QuickTerminalController: BaseTerminalController { class QuickTerminalController: BaseTerminalController {
override var windowNibName: NSNib.Name? { "QuickTerminal" } override var windowNibName: NSNib.Name? { "QuickTerminal" }
@ -18,6 +24,12 @@ class QuickTerminalController: BaseTerminalController {
/// application to the front. /// application to the front.
private var previousApp: NSRunningApplication? = nil 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. /// The configuration derived from the Ghostty config so we don't need to rely on references.
private var derivedConfig: DerivedConfig private var derivedConfig: DerivedConfig
@ -32,6 +44,11 @@ class QuickTerminalController: BaseTerminalController {
// Setup our notifications for behaviors // Setup our notifications for behaviors
let center = NotificationCenter.default let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(applicationWillTerminate(_:)),
name: NSApplication.willTerminateNotification,
object: nil)
center.addObserver( center.addObserver(
self, self,
selector: #selector(onToggleFullscreen), selector: #selector(onToggleFullscreen),
@ -52,6 +69,9 @@ class QuickTerminalController: BaseTerminalController {
// Remove all of our notificationcenter subscriptions // Remove all of our notificationcenter subscriptions
let center = NotificationCenter.default let center = NotificationCenter.default
center.removeObserver(self) center.removeObserver(self)
// Make sure we restore our hidden dock
hiddenDock = nil
} }
// MARK: NSWindowController // MARK: NSWindowController
@ -87,6 +107,17 @@ class QuickTerminalController: BaseTerminalController {
// MARK: NSWindowDelegate // 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) { override func windowDidResignKey(_ notification: Notification) {
super.windowDidResignKey(notification) super.windowDidResignKey(notification)
@ -107,8 +138,32 @@ class QuickTerminalController: BaseTerminalController {
self.previousApp = nil self.previousApp = nil
} }
if (derivedConfig.quickTerminalAutoHide) { // Regardless of autohide, we always want to bring the dock back
animateOut() // 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 // Animate the window in
animateWindowIn(window: window, from: position) animateWindowIn(window: window, from: position)
@ -198,8 +256,29 @@ class QuickTerminalController: BaseTerminalController {
// Move our window off screen to the top // Move our window off screen to the top
position.setInitial(in: window, on: screen) 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 // 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 // Run the animation that moves our window into the proper place and makes
// it visible. // it visible.
@ -211,8 +290,16 @@ class QuickTerminalController: BaseTerminalController {
// There is a very minor delay here so waiting at least an event loop tick // 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. // keeps us safe from the view not being on the window.
DispatchQueue.main.async { DispatchQueue.main.async {
// If we canceled our animation in we do nothing // If we canceled our animation clean up some state.
guard self.visible else { return } 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 // Now that the window is visible, sync our appearance. This function
// requires the window is visible. // requires the window is visible.
@ -276,6 +363,17 @@ class QuickTerminalController: BaseTerminalController {
} }
private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) { 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. // We always animate out to whatever screen the window is actually on.
guard let screen = window.screen ?? NSScreen.main else { return } 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 NSAnimationContext.runAnimationGroup({ context in
context.duration = derivedConfig.quickTerminalAnimationDuration context.duration = derivedConfig.quickTerminalAnimationDuration
context.timingFunction = .init(name: .easeIn) context.timingFunction = .init(name: .easeIn)
@ -311,23 +414,13 @@ class QuickTerminalController: BaseTerminalController {
private func syncAppearance() { private func syncAppearance() {
guard let window else { return } 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. // 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. // Some APIs such as window blur have no effect unless the window is visible.
guard window.isVisible else { return } 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 we have window transparency then set it transparent. Otherwise set it opaque.
if (self.derivedConfig.backgroundOpacity < 1) { if (self.derivedConfig.backgroundOpacity < 1) {
window.isOpaque = false window.isOpaque = false
@ -368,6 +461,13 @@ class QuickTerminalController: BaseTerminalController {
// MARK: Notifications // 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) { @objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return } guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard target == self.focusedSurface else { return } guard target == self.focusedSurface else { return }
@ -396,14 +496,14 @@ class QuickTerminalController: BaseTerminalController {
let quickTerminalScreen: QuickTerminalScreen let quickTerminalScreen: QuickTerminalScreen
let quickTerminalAnimationDuration: Double let quickTerminalAnimationDuration: Double
let quickTerminalAutoHide: Bool let quickTerminalAutoHide: Bool
let windowColorspace: String let quickTerminalSpaceBehavior: QuickTerminalSpaceBehavior
let backgroundOpacity: Double let backgroundOpacity: Double
init() { init() {
self.quickTerminalScreen = .main self.quickTerminalScreen = .main
self.quickTerminalAnimationDuration = 0.2 self.quickTerminalAnimationDuration = 0.2
self.quickTerminalAutoHide = true self.quickTerminalAutoHide = true
self.windowColorspace = "" self.quickTerminalSpaceBehavior = .move
self.backgroundOpacity = 1.0 self.backgroundOpacity = 1.0
} }
@ -411,10 +511,39 @@ class QuickTerminalController: BaseTerminalController {
self.quickTerminalScreen = config.quickTerminalScreen self.quickTerminalScreen = config.quickTerminalScreen
self.quickTerminalAnimationDuration = config.quickTerminalAnimationDuration self.quickTerminalAnimationDuration = config.quickTerminalAnimationDuration
self.quickTerminalAutoHide = config.quickTerminalAutoHide self.quickTerminalAutoHide = config.quickTerminalAutoHide
self.windowColorspace = config.windowColorspace self.quickTerminalSpaceBehavior = config.quickTerminalSpaceBehavior
self.backgroundOpacity = config.backgroundOpacity 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 { extension Notification.Name {

View File

@ -69,7 +69,7 @@ enum QuickTerminalPosition : String {
finalSize.width = screen.frame.width finalSize.width = screen.frame.width
case .left, .right: case .left, .right:
finalSize.height = screen.frame.height finalSize.height = screen.visibleFrame.height
case .center: case .center:
finalSize.width = screen.frame.width / 2 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) 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 import Cocoa
class QuickTerminalWindow: NSWindow { class QuickTerminalWindow: NSPanel {
// Both of these must be true for windows without decorations to be able to // Both of these must be true for windows without decorations to be able to
// still become key/main and receive events. // still become key/main and receive events.
override var canBecomeKey: Bool { return true } override var canBecomeKey: Bool { return true }
@ -26,22 +26,7 @@ class QuickTerminalWindow: NSWindow {
// window remains resizable. // window remains resizable.
self.styleMask.remove(.titled) self.styleMask.remove(.titled)
// We need to set our window level to a high value. In testing, only // We don't want to activate the owning app when quick terminal is triggered.
// popUpMenu and above do what we want. This gets it above the menu bar self.styleMask.insert(.nonactivatingPanel)
// 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]
} }
} }

View File

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

View File

@ -22,7 +22,7 @@ class TerminalController: BaseTerminalController {
private var restorable: Bool = true private var restorable: Bool = true
/// The configuration derived from the Ghostty config so we don't need to rely on references. /// 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. /// The notification cancellable for focused surface property changes.
private var surfaceAppearanceCancellables: Set<AnyCancellable> = [] private var surfaceAppearanceCancellables: Set<AnyCancellable> = []
@ -60,6 +60,11 @@ class TerminalController: BaseTerminalController {
selector: #selector(onGotoTab), selector: #selector(onGotoTab),
name: Ghostty.Notification.ghosttyGotoTab, name: Ghostty.Notification.ghosttyGotoTab,
object: nil) object: nil)
center.addObserver(
self,
selector: #selector(onCloseTab),
name: .ghosttyCloseTab,
object: nil)
center.addObserver( center.addObserver(
self, self,
selector: #selector(ghosttyConfigDidChange(_:)), selector: #selector(ghosttyConfigDidChange(_:)),
@ -278,9 +283,12 @@ class TerminalController: BaseTerminalController {
private func setInitialWindowPosition(x: Int16?, y: Int16?, windowDecorations: Bool) { private func setInitialWindowPosition(x: Int16?, y: Int16?, windowDecorations: Bool) {
guard let window else { return } 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 { guard let x, let y else {
window.center() if (!LastWindowPosition.shared.restore(window)) {
window.center()
}
return return
} }
@ -310,28 +318,28 @@ class TerminalController: BaseTerminalController {
window.styleMask = [ window.styleMask = [
// We need `titled` in the mask to get the normal window frame // We need `titled` in the mask to get the normal window frame
.titled, .titled,
// Full size content view so we can extend // Full size content view so we can extend
// content in to the hidden titlebar's area // content in to the hidden titlebar's area
.fullSizeContentView, .fullSizeContentView,
.resizable, .resizable,
.closable, .closable,
.miniaturizable, .miniaturizable,
] ]
// Hide the title // Hide the title
window.titleVisibility = .hidden window.titleVisibility = .hidden
window.titlebarAppearsTransparent = true window.titlebarAppearsTransparent = true
// Hide the traffic lights (window control buttons) // Hide the traffic lights (window control buttons)
window.standardWindowButton(.closeButton)?.isHidden = true window.standardWindowButton(.closeButton)?.isHidden = true
window.standardWindowButton(.miniaturizeButton)?.isHidden = true window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true window.standardWindowButton(.zoomButton)?.isHidden = true
// Disallow tabbing if the titlebar is hidden, since that will (should) also hide the tab bar. // Disallow tabbing if the titlebar is hidden, since that will (should) also hide the tab bar.
window.tabbingMode = .disallowed window.tabbingMode = .disallowed
// Nuke it from orbit -- hide the titlebar container entirely, just in case. There are // 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 // some operations that appear to bring back the titlebar visibility so this ensures
// it is gone forever. // it is gone forever.
@ -340,7 +348,7 @@ class TerminalController: BaseTerminalController {
titleBarContainer.isHidden = true titleBarContainer.isHidden = true
} }
} }
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad() super.windowDidLoad()
guard let window = window as? TerminalWindow else { return } guard let window = window as? TerminalWindow else { return }
@ -361,33 +369,31 @@ class TerminalController: BaseTerminalController {
// If window decorations are disabled, remove our title // If window decorations are disabled, remove our title
if (!config.windowDecorations) { window.styleMask.remove(.titled) } 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 // If we have only a single surface (no splits) and that surface requested
// an initial size then we set it here now. // an initial size then we set it here now.
if case let .leaf(leaf) = surfaceTree { if case let .leaf(leaf) = surfaceTree {
if let initialSize = leaf.surface.initialSize, if let initialSize = leaf.surface.initialSize,
let screen = window.screen ?? NSScreen.main { let screen = window.screen ?? NSScreen.main {
// Setup our frame. We need to first subtract the views frame so that we can // Get the current frame of the window
// just get the chrome frame so that we only affect the surface view size.
var frame = window.frame 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) window.setFrame(frame, display: true)
} }
} }
@ -487,6 +493,20 @@ class TerminalController: BaseTerminalController {
override func windowDidMove(_ notification: Notification) { override func windowDidMove(_ notification: Notification) {
super.windowDidMove(notification) super.windowDidMove(notification)
self.fixTabBar() 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 // 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) 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 window = window else { return }
guard let tabGroup = window.tabGroup else { guard let tabGroup = window.tabGroup else {
// No tabs, no tab group, just perform a normal close. // No tabs, no tab group, just perform a normal close.
@ -523,47 +586,34 @@ class TerminalController: BaseTerminalController {
} }
// Check if any windows require close confirmation. // Check if any windows require close confirmation.
var needsConfirm: Bool = false let needsConfirm = tabGroup.windows.contains { tabWindow in
for tabWindow in tabGroup.windows { guard let controller = tabWindow.windowController as? TerminalController else {
guard let c = tabWindow.windowController as? TerminalController else { continue } return false
if (c.surfaceTree?.needsConfirmQuit() ?? false) {
needsConfirm = true
break
} }
return controller.surfaceTree?.needsConfirmQuit() ?? false
} }
// If none need confirmation then we can just close all the windows. // If none need confirmation then we can just close all the windows.
if (!needsConfirm) { if !needsConfirm {
for tabWindow in tabGroup.windows { tabGroup.windows.forEach { $0.close() }
tabWindow.close()
}
return return
} }
// If we need confirmation by any, show one confirmation for all windows confirmClose(
// in the tab group. window: window,
let alert = NSAlert() messageText: "Close Window?",
alert.messageText = "Close Window?" informativeText: "All terminal sessions in this window will be terminated."
alert.informativeText = "All terminal sessions in this window will be terminated." ) {
alert.addButton(withTitle: "Close Window") tabGroup.windows.forEach { $0.close() }
alert.addButton(withTitle: "Cancel") }
alert.alertStyle = .warning
alert.beginSheetModal(for: window, completionHandler: { response in
if (response == .alertFirstButtonReturn) {
for tabWindow in tabGroup.windows {
tabWindow.close()
}
}
})
} }
@IBAction func toggleGhosttyFullScreen(_ sender: Any) { @IBAction func toggleGhosttyFullScreen(_ sender: Any?) {
guard let surface = focusedSurface?.surface else { return } guard let surface = focusedSurface?.surface else { return }
ghostty.toggleFullscreen(surface: surface) ghostty.toggleFullscreen(surface: surface)
} }
@IBAction func toggleTerminalInspector(_ sender: Any) { @IBAction func toggleTerminalInspector(_ sender: Any?) {
guard let surface = focusedSurface?.surface else { return } guard let surface = focusedSurface?.surface else { return }
ghostty.toggleTerminalInspector(surface: surface) ghostty.toggleTerminalInspector(surface: surface)
} }
@ -659,13 +709,21 @@ class TerminalController: BaseTerminalController {
// If our index is the same we do nothing // If our index is the same we do nothing
guard finalIndex != selectedIndex else { return } guard finalIndex != selectedIndex else { return }
// Get our parent // Get our target window
let parent = tabbedWindows[finalIndex] 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) tabGroup.removeWindow(selectedWindow)
parent.addTabbedWindow(selectedWindow, ordered: action.amount < 0 ? .below : .above) targetWindow.addTabbedWindow(selectedWindow, ordered: action.amount < 0 ? .below : .above)
selectedWindow.makeKeyAndOrderFront(nil)
// Ensure our window remains selected
selectedWindow.makeKey()
NSAnimationContext.endGrouping()
} }
@objc private func onGotoTab(notification: SwiftUI.Notification) { @objc private func onGotoTab(notification: SwiftUI.Notification) {
@ -720,6 +778,12 @@ class TerminalController: BaseTerminalController {
targetWindow.makeKeyAndOrderFront(nil) 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) { @objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return } guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard target == self.focusedSurface else { return } guard target == self.focusedSurface else { return }
@ -737,7 +801,7 @@ class TerminalController: BaseTerminalController {
toggleFullscreen(mode: fullscreenMode) toggleFullscreen(mode: fullscreenMode)
} }
private struct DerivedConfig { struct DerivedConfig {
let backgroundColor: Color let backgroundColor: Color
let macosTitlebarStyle: String let macosTitlebarStyle: String

View File

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

View File

@ -10,7 +10,7 @@ protocol TerminalViewDelegate: AnyObject {
/// The title of the terminal should change. /// The title of the terminal should change.
func titleDidChange(to: String) func titleDidChange(to: String)
/// The URL of the pwd should change. /// The URL of the pwd should change.
func pwdDidChange(to: URL?) func pwdDidChange(to: URL?)
@ -56,15 +56,10 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
// The title for our window // The title for our window
private var title: String { private var title: String {
var title = "👻" if let surfaceTitle, !surfaceTitle.isEmpty {
return surfaceTitle
if let surfaceTitle = surfaceTitle {
if (surfaceTitle.count > 0) {
title = surfaceTitle
}
} }
return "👻"
return title
} }
// The pwd of the focused surface as a URL // 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 } guard let surfacePwd, surfacePwd != "" else { return nil }
return URL(fileURLWithPath: surfacePwd) return URL(fileURLWithPath: surfacePwd)
} }
var body: some View { var body: some View {
switch ghostty.readiness { switch ghostty.readiness {
case .loading: 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 // The window theme configuration from Ghostty. This is used to control some
// behaviors that don't look quite right in certain situations. // behaviors that don't look quite right in certain situations.
var windowTheme: TerminalWindowTheme? var windowTheme: TerminalWindowTheme?

View File

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

View File

@ -62,7 +62,7 @@ extension Ghostty {
// uses to interface with the application runtime environment. // uses to interface with the application runtime environment.
var runtime_cfg = ghostty_runtime_config_s( var runtime_cfg = ghostty_runtime_config_s(
userdata: Unmanaged.passUnretained(self).toOpaque(), userdata: Unmanaged.passUnretained(self).toOpaque(),
supports_selection_clipboard: false, supports_selection_clipboard: true,
wakeup_cb: { userdata in App.wakeup(userdata) }, wakeup_cb: { userdata in App.wakeup(userdata) },
action_cb: { app, target, action in App.action(app!, target: target, action: action) }, 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) }, read_clipboard_cb: { userdata, loc, state in App.readClipboard(userdata, location: loc, state: state) },
@ -257,7 +257,7 @@ extension Ghostty {
// MARK: Ghostty Callbacks (iOS) // MARK: Ghostty Callbacks (iOS)
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {} 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( static func readClipboard(
_ userdata: UnsafeMutableRawPointer?, _ userdata: UnsafeMutableRawPointer?,
location: ghostty_clipboard_e, location: ghostty_clipboard_e,
@ -320,13 +320,13 @@ extension Ghostty {
let surfaceView = self.surfaceUserdata(from: userdata) let surfaceView = self.surfaceUserdata(from: userdata)
guard let surface = surfaceView.surface else { return } guard let surface = surfaceView.surface else { return }
// We only support the standard clipboard // Get our pasteboard
if (location != GHOSTTY_CLIPBOARD_STANDARD) { guard let pasteboard = NSPasteboard.ghostty(location) else {
return completeClipboardRequest(surface, data: "", state: state) return completeClipboardRequest(surface, data: "", state: state)
} }
// Get our string // Get our string
let str = NSPasteboard.general.getOpinionatedStringContents() ?? "" let str = pasteboard.getOpinionatedStringContents() ?? ""
completeClipboardRequest(surface, data: str, state: state) 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) { static func writeClipboard(_ userdata: UnsafeMutableRawPointer?, string: UnsafePointer<CChar>?, location: ghostty_clipboard_e, confirm: Bool) {
let surface = self.surfaceUserdata(from: userdata) 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 } guard let valueStr = String(cString: string!, encoding: .utf8) else { return }
if !confirm { if !confirm {
let pb = NSPasteboard.general pasteboard.declareTypes([.string], owner: nil)
pb.declareTypes([.string], owner: nil) pasteboard.setString(valueStr, forType: .string)
pb.setString(valueStr, forType: .string)
return return
} }
@ -380,7 +378,7 @@ extension Ghostty {
object: surface, object: surface,
userInfo: [ userInfo: [
Notification.ConfirmClipboardStrKey: valueStr, 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) // 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 // Make sure it a target we understand so all our action handlers can assert
switch (target.tag) { switch (target.tag) {
case GHOSTTY_TARGET_APP, GHOSTTY_TARGET_SURFACE: case GHOSTTY_TARGET_APP, GHOSTTY_TARGET_SURFACE:
@ -433,7 +431,7 @@ extension Ghostty {
default: default:
Ghostty.logger.warning("unknown action target=\(target.tag.rawValue)") Ghostty.logger.warning("unknown action target=\(target.tag.rawValue)")
return return false
} }
// Action dispatch // Action dispatch
@ -450,17 +448,20 @@ extension Ghostty {
case GHOSTTY_ACTION_NEW_SPLIT: case GHOSTTY_ACTION_NEW_SPLIT:
newSplit(app, target: target, direction: action.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: case GHOSTTY_ACTION_TOGGLE_FULLSCREEN:
toggleFullscreen(app, target: target, mode: action.action.toggle_fullscreen) toggleFullscreen(app, target: target, mode: action.action.toggle_fullscreen)
case GHOSTTY_ACTION_MOVE_TAB: 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: 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: 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: case GHOSTTY_ACTION_RESIZE_SPLIT:
resizeSplit(app, target: target, resize: action.action.resize_split) resizeSplit(app, target: target, resize: action.action.resize_split)
@ -483,6 +484,9 @@ extension Ghostty {
case GHOSTTY_ACTION_SET_TITLE: case GHOSTTY_ACTION_SET_TITLE:
setTitle(app, target: target, v: action.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: case GHOSTTY_ACTION_PWD:
pwdChanged(app, target: target, v: action.action.pwd) pwdChanged(app, target: target, v: action.action.pwd)
@ -540,10 +544,15 @@ extension Ghostty {
fallthrough fallthrough
case GHOSTTY_ACTION_QUIT_TIMER: case GHOSTTY_ACTION_QUIT_TIMER:
Ghostty.logger.info("known but unimplemented action action=\(action.tag.rawValue)") Ghostty.logger.info("known but unimplemented action action=\(action.tag.rawValue)")
return false
default: default:
Ghostty.logger.warning("unknown action action=\(action.tag.rawValue)") 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) { 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( private static func toggleFullscreen(
_ app: ghostty_app_t, _ app: ghostty_app_t,
target: ghostty_target_s, target: ghostty_target_s,
@ -694,15 +724,19 @@ extension Ghostty {
private static func moveTab( private static func moveTab(
_ app: ghostty_app_t, _ app: ghostty_app_t,
target: ghostty_target_s, target: ghostty_target_s,
move: ghostty_action_move_tab_s) { move: ghostty_action_move_tab_s) -> Bool {
switch (target.tag) { switch (target.tag) {
case GHOSTTY_TARGET_APP: case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("move tab does nothing with an app target") Ghostty.logger.warning("move tab does nothing with an app target")
return return false
case GHOSTTY_TARGET_SURFACE: case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return } guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return } 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( NotificationCenter.default.post(
name: .ghosttyMoveTab, name: .ghosttyMoveTab,
object: surfaceView, object: surfaceView,
@ -714,20 +748,27 @@ extension Ghostty {
default: default:
assertionFailure() assertionFailure()
} }
return true
} }
private static func gotoTab( private static func gotoTab(
_ app: ghostty_app_t, _ app: ghostty_app_t,
target: ghostty_target_s, target: ghostty_target_s,
tab: ghostty_action_goto_tab_e) { tab: ghostty_action_goto_tab_e) -> Bool {
switch (target.tag) { switch (target.tag) {
case GHOSTTY_TARGET_APP: case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("goto tab does nothing with an app target") Ghostty.logger.warning("goto tab does nothing with an app target")
return return false
case GHOSTTY_TARGET_SURFACE: case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return } guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return } 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( NotificationCenter.default.post(
name: Notification.ghosttyGotoTab, name: Notification.ghosttyGotoTab,
object: surfaceView, object: surfaceView,
@ -739,20 +780,31 @@ extension Ghostty {
default: default:
assertionFailure() assertionFailure()
} }
return true
} }
private static func gotoSplit( private static func gotoSplit(
_ app: ghostty_app_t, _ app: ghostty_app_t,
target: ghostty_target_s, target: ghostty_target_s,
direction: ghostty_action_goto_split_e) { direction: ghostty_action_goto_split_e) -> Bool {
switch (target.tag) { switch (target.tag) {
case GHOSTTY_TARGET_APP: case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("goto split does nothing with an app target") Ghostty.logger.warning("goto split does nothing with an app target")
return return false
case GHOSTTY_TARGET_SURFACE: case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return } guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return } 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( NotificationCenter.default.post(
name: Notification.ghosttyFocusSplit, name: Notification.ghosttyFocusSplit,
object: surfaceView, object: surfaceView,
@ -764,6 +816,8 @@ extension Ghostty {
default: default:
assertionFailure() assertionFailure()
} }
return true
} }
private static func resizeSplit( 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( private static func pwdChanged(
_ app: ghostty_app_t, _ app: ghostty_app_t,
target: ghostty_target_s, target: ghostty_target_s,

View File

@ -132,15 +132,6 @@ extension Ghostty {
return v 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 { var windowSaveState: String {
guard let config = self.config else { return "" } guard let config = self.config else { return "" }
var v: UnsafePointer<Int8>? = nil var v: UnsafePointer<Int8>? = nil
@ -174,11 +165,14 @@ extension Ghostty {
} }
var windowDecorations: Bool { var windowDecorations: Bool {
guard let config = self.config else { return true } let defaultValue = true
var v = false; guard let config = self.config else { return defaultValue }
var v: UnsafePointer<Int8>? = nil
let key = "window-decoration" let key = "window-decoration"
_ = ghostty_config_get(config, &v, key, UInt(key.count)) guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
return v; guard let ptr = v else { return defaultValue }
let str = String(cString: ptr)
return WindowDecoration(rawValue: str)?.enabled() ?? defaultValue
} }
var windowTheme: String? { var windowTheme: String? {
@ -222,6 +216,8 @@ extension Ghostty {
.nonNative .nonNative
case "visible-menu": case "visible-menu":
.nonNativeVisibleMenu .nonNativeVisibleMenu
case "padded-notch":
.nonNativePaddedNotch
default: default:
defaultValue defaultValue
} }
@ -306,6 +302,16 @@ extension Ghostty {
return buffer.map { .init(ghostty: $0) } 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 { var focusFollowsMouse : Bool {
guard let config = self.config else { return false } guard let config = self.config else { return false }
var v = false; var v = false;
@ -345,7 +351,7 @@ extension Ghostty {
var backgroundBlurRadius: Int { var backgroundBlurRadius: Int {
guard let config = self.config else { return 1 } guard let config = self.config else { return 1 }
var v: Int = 0 var v: Int = 0
let key = "background-blur-radius" let key = "background-blur"
_ = ghostty_config_get(config, &v, key, UInt(key.count)) _ = ghostty_config_get(config, &v, key, UInt(key.count))
return v; return v;
} }
@ -431,6 +437,16 @@ extension Ghostty {
_ = ghostty_config_get(config, &v, key, UInt(key.count)) _ = ghostty_config_get(config, &v, key, UInt(key.count))
return v 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 #endif
var resizeOverlay: ResizeOverlay { var resizeOverlay: ResizeOverlay {
@ -510,6 +526,11 @@ extension Ghostty.Config {
case download case download
} }
enum MacHidden : String {
case never
case always
}
enum ResizeOverlay : String { enum ResizeOverlay : String {
case always case always
case never 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 { func topLeft() -> SurfaceView {
switch (self) { switch (self) {
case .leaf(let leaf): case .leaf(let leaf):
@ -120,14 +129,7 @@ extension Ghostty {
/// Returns true if the split tree contains the given view. /// Returns true if the split tree contains the given view.
func contains(view: SurfaceView) -> Bool { func contains(view: SurfaceView) -> Bool {
switch (self) { return leaf(for: view) != nil
case .leaf(let leaf):
return leaf.surface == view
case .split(let container):
return container.topLeft.contains(view: view) ||
container.bottomRight.contains(view: view)
}
} }
/// Find a surface view by UUID. /// 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 // MARK: - Sequence
func makeIterator() -> IndexingIterator<[Leaf]> { func makeIterator() -> IndexingIterator<[Leaf]> {

View File

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

View File

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

View File

@ -92,22 +92,6 @@ extension Ghostty {
windowFocus = false 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 #endif
// If our geo size changed then we show the resize overlay as configured. // If our geo size changed then we show the resize overlay as configured.

View File

@ -1,3 +1,4 @@
import AppKit
import SwiftUI import SwiftUI
import CoreText import CoreText
import UserNotifications import UserNotifications
@ -12,7 +13,14 @@ extension Ghostty {
// The current title of the surface as defined by the pty. This can be // 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 // changed with escape codes. This is public because the callbacks go
// to the app level and it is set from there. // 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 // The current pwd of the surface as defined by the pty. This can be
// changed with escape codes. // changed with escape codes.
@ -113,6 +121,14 @@ extension Ghostty {
// A small delay that is introduced before a title change to avoid flickers // A small delay that is introduced before a title change to avoid flickers
private var titleChangeTimer: Timer? 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) /// Event monitor (see individual events for why)
private var eventMonitor: Any? = nil private var eventMonitor: Any? = nil
@ -139,6 +155,13 @@ extension Ghostty {
// can do SOMETHING. // can do SOMETHING.
super.init(frame: NSMakeRect(0, 0, 800, 600)) 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 // Before we initialize the surface we want to register our notifications
// so there is no window where we can't receive them. // so there is no window where we can't receive them.
let center = NotificationCenter.default let center = NotificationCenter.default
@ -213,6 +236,9 @@ extension Ghostty {
ghostty_surface_set_color_scheme(surface, scheme) ghostty_surface_set_color_scheme(surface, scheme)
} }
// The UTTypes that can be dragged onto this view.
registerForDraggedTypes(Array(Self.dropTypes))
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -359,6 +385,45 @@ extension Ghostty {
NSCursor.setHiddenUntilMouseMoves(!visible) 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) { func setTitle(_ title: String) {
// This fixes an issue where very quick changes to the title could // This fixes an issue where very quick changes to the title could
// cause an unpleasant flickering. We set a timer so that we can // cause an unpleasant flickering. We set a timer so that we can
@ -369,6 +434,11 @@ extension Ghostty {
withTimeInterval: 0.075, withTimeInterval: 0.075,
repeats: false repeats: false
) { [weak self] _ in ) { [weak self] _ in
// Set the title if it wasn't manually set.
guard self?.titleFromTerminal == nil else {
self?.titleFromTerminal = title
return
}
self?.title = title self?.title = title
} }
} }
@ -1096,6 +1166,8 @@ extension Ghostty {
menu.addItem(.separator()) menu.addItem(.separator())
menu.addItem(withTitle: "Reset Terminal", action: #selector(resetTerminal(_:)), keyEquivalent: "") menu.addItem(withTitle: "Reset Terminal", action: #selector(resetTerminal(_:)), keyEquivalent: "")
menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(toggleTerminalInspector(_:)), 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 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?) { @IBAction override func selectAll(_ sender: Any?) {
guard let surface = self.surface else { return } guard let surface = self.surface else { return }
let action = "select_all" let action = "select_all"
@ -1160,6 +1240,10 @@ extension Ghostty {
AppDelegate.logger.warning("action failed action=\(action)") AppDelegate.logger.warning("action failed action=\(action)")
} }
} }
@IBAction func changeTitle(_ sender: Any) {
promptTitle()
}
/// Show a user notification and associate it with this surface /// Show a user notification and associate it with this surface
func showUserNotification(title: String, body: String) { func showUserNotification(title: String, body: String) {
@ -1448,3 +1532,78 @@ extension Ghostty.SurfaceView: NSServicesMenuRequestor {
return true 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 OSView = NSView
typealias OSColor = NSColor typealias OSColor = NSColor
typealias OSSize = NSSize typealias OSSize = NSSize
typealias OSPasteboard = NSPasteboard
protocol OSViewRepresentable: NSViewRepresentable where NSViewType == OSViewType { protocol OSViewRepresentable: NSViewRepresentable where NSViewType == OSViewType {
associatedtype OSViewType: NSView associatedtype OSViewType: NSView
@ -34,6 +35,7 @@ import UIKit
typealias OSView = UIView typealias OSView = UIView
typealias OSColor = UIColor typealias OSColor = UIColor
typealias OSSize = CGSize typealias OSSize = CGSize
typealias OSPasteboard = UIPasteboard
protocol OSViewRepresentable: UIViewRepresentable { protocol OSViewRepresentable: UIViewRepresentable {
associatedtype OSViewType: UIView 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 native
case nonNative case nonNative
case nonNativeVisibleMenu case nonNativeVisibleMenu
case nonNativePaddedNotch
/// Initializes the fullscreen style implementation for the mode. This will not toggle any /// 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 /// fullscreen properties. This may fail if the window isn't configured properly for a given
@ -20,6 +21,9 @@ enum FullscreenMode {
case .nonNativeVisibleMenu: case .nonNativeVisibleMenu:
return NonNativeFullscreenVisibleMenu(window) return NonNativeFullscreenVisibleMenu(window)
case .nonNativePaddedNotch:
return NonNativeFullscreenPaddedNotch(window)
} }
} }
} }
@ -141,6 +145,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
struct Properties { struct Properties {
var hideMenu: Bool = true var hideMenu: Bool = true
var paddedNotch: Bool = false
} }
private var savedState: SavedState? 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 // 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 // if a bug is reported to Ghostty we can just advise the user to
// update. // 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 return frame
@ -307,21 +315,21 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
// MARK: Dock // MARK: Dock
private func hideDock() { private func hideDock() {
NSApp.presentationOptions.insert(.autoHideDock) NSApp.acquirePresentationOption(.autoHideDock)
} }
private func unhideDock() { private func unhideDock() {
NSApp.presentationOptions.remove(.autoHideDock) NSApp.releasePresentationOption(.autoHideDock)
} }
// MARK: Menu // MARK: Menu
func hideMenu() { func hideMenu() {
NSApp.presentationOptions.insert(.autoHideMenuBar) NSApp.acquirePresentationOption(.autoHideMenuBar)
} }
func unhideMenu() { func unhideMenu() {
NSApp.presentationOptions.remove(.autoHideMenuBar) NSApp.releasePresentationOption(.autoHideMenuBar)
} }
/// The state that must be saved for non-native fullscreen to exit fullscreen. /// The state that must be saved for non-native fullscreen to exit fullscreen.
@ -349,3 +357,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
class NonNativeFullscreenVisibleMenu: NonNativeFullscreen { class NonNativeFullscreenVisibleMenu: NonNativeFullscreen {
override var properties: Properties { Properties(hideMenu: false) } 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 AppKit
import GhosttyKit
extension NSPasteboard { 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. /// Gets the contents of the pasteboard as a string following a specific set of semantics.
/// Does these things in order: /// 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. /// - Tries to get any string from the pasteboard.
/// If all of the above fail, returns None. /// If all of the above fail, returns None.
func getOpinionatedStringContents() -> String? { func getOpinionatedStringContents() -> String? {
if let file = self.string(forType: .fileURL) { if let urls = readObjects(forClasses: [NSURL.self]) as? [URL],
if let path = NSURL(string: file)?.path { urls.count > 0 {
return path return urls
} .map { $0.isFileURL ? Ghostty.Shell.escape($0.path) : $0.absoluteString }
.joined(separator: " ")
} }
return self.string(forType: .string) 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 #!/bin/sh
set -e # NOTE THIS IS A TEMPORARY SCRIPT TO SUPPORT PACKAGE MAINTAINERS.
# 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).
# #
# 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; # [1]: https://github.com/ziglang/zig/issues/20976
# 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`.
if [ -z ${ZIG_GLOBAL_CACHE_DIR+x} ] if [ -z ${ZIG_GLOBAL_CACHE_DIR+x} ]
then then
@ -34,6 +15,13 @@ then
exit 1 exit 1
fi fi
zig build --fetch # Go through each line of our build.zig.zon.txt and fetch it.
zig fetch git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e SCRIPT_PATH="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
zig fetch git+https://github.com/mitchellh/libxev#f6a672a78436d8efee1aa847a43a900ad773618b 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, python3,
qemu, qemu,
scdoc, scdoc,
snapcraft,
valgrind, valgrind,
#, vulkan-loader # unused #, vulkan-loader # unused
vttest, vttest,
@ -30,7 +31,9 @@
glib, glib,
glslang, glslang,
gtk4, gtk4,
gobject-introspection,
libadwaita, libadwaita,
blueprint-compiler,
adwaita-icon-theme, adwaita-icon-theme,
hicolor-icon-theme, hicolor-icon-theme,
harfbuzz, harfbuzz,
@ -47,6 +50,7 @@
simdutf, simdutf,
zlib, zlib,
alejandra, alejandra,
jq,
minisign, minisign,
pandoc, pandoc,
hyperfine, hyperfine,
@ -54,6 +58,8 @@
wayland, wayland,
wayland-scanner, wayland-scanner,
wayland-protocols, wayland-protocols,
zig2nix,
system,
}: let }: let
# See package.nix. Keep in sync. # See package.nix. Keep in sync.
rpathLibs = rpathLibs =
@ -83,6 +89,7 @@
libadwaita libadwaita
gtk4 gtk4
glib glib
gobject-introspection
wayland wayland
]; ];
in in
@ -92,6 +99,7 @@ in
packages = packages =
[ [
# For builds # For builds
jq
llvmPackages_latest.llvm llvmPackages_latest.llvm
minisign minisign
ncurses ncurses
@ -100,6 +108,7 @@ in
scdoc scdoc
zig zig
zip zip
zig2nix.packages.${system}.zon2nix
# For web and wasm stuff # For web and wasm stuff
nodejs nodejs
@ -129,6 +138,7 @@ in
qemu qemu
gdb gdb
snapcraft
valgrind valgrind
wraptest wraptest
@ -154,9 +164,11 @@ in
libXrandr libXrandr
# Only needed for GTK builds # Only needed for GTK builds
blueprint-compiler
libadwaita libadwaita
gtk4 gtk4
glib glib
gobject-introspection
wayland wayland
wayland-scanner wayland-scanner
wayland-protocols wayland-protocols

View File

@ -2,6 +2,7 @@
lib, lib,
stdenv, stdenv,
bzip2, bzip2,
callPackage,
expat, expat,
fontconfig, fontconfig,
freetype, freetype,
@ -12,7 +13,9 @@
libGL, libGL,
glib, glib,
gtk4, gtk4,
gobject-introspection,
libadwaita, libadwaita,
blueprint-compiler,
wrapGAppsHook4, wrapGAppsHook4,
gsettings-desktop-schemas, gsettings-desktop-schemas,
git, git,
@ -40,82 +43,36 @@
# ultimately acted on and has made its way to a nixpkgs implementation, this # ultimately acted on and has made its way to a nixpkgs implementation, this
# can probably be removed in favor of that. # can probably be removed in favor of that.
zig_hook = zig_0_13.hook.overrideAttrs { zig_hook = zig_0_13.hook.overrideAttrs {
zig_default_flags = "-Dcpu=baseline -Doptimize=${optimize}"; zig_default_flags = "-Dcpu=baseline -Doptimize=${optimize} --color off";
};
# 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;
}; };
in in
stdenv.mkDerivation (finalAttrs: { stdenv.mkDerivation (finalAttrs: {
pname = "ghostty"; pname = "ghostty";
version = "1.0.2"; version = "1.1.3";
inherit src;
# 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 = nativeBuildInputs =
[ [
@ -124,7 +81,9 @@ in
pandoc pandoc
pkg-config pkg-config
zig_hook zig_hook
gobject-introspection
wrapGAppsHook4 wrapGAppsHook4
blueprint-compiler
] ]
++ lib.optionals enableWayland [ ++ lib.optionals enableWayland [
wayland-scanner wayland-scanner
@ -162,13 +121,13 @@ in
dontConfigure = true; dontConfigure = true;
zigBuildFlags = "-Dversion-string=${finalAttrs.version}-${revision}-nix -Dgtk-x11=${lib.boolToString enableX11} -Dgtk-wayland=${lib.boolToString enableWayland}"; zigBuildFlags = [
"--system"
preBuild = '' "${finalAttrs.deps}"
rm -rf $ZIG_GLOBAL_CACHE_DIR "-Dversion-string=${finalAttrs.version}-${revision}-nix"
cp -r --reflink=auto ${zigCache} $ZIG_GLOBAL_CACHE_DIR "-Dgtk-x11=${lib.boolToString enableX11}"
chmod u+rwX -R $ZIG_GLOBAL_CACHE_DIR "-Dgtk-wayland=${lib.boolToString enableWayland}"
''; ];
outputs = [ outputs = [
"out" "out"
@ -202,7 +161,7 @@ in
''; '';
meta = { meta = {
homepage = "https://github.com/ghostty-org/ghostty"; homepage = "https://ghostty.org";
license = lib.licenses.mit; license = lib.licenses.mit;
platforms = [ platforms = [
"x86_64-linux" "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 # This file is auto-generated! check build-support/check-zig-cache-hash.sh for
# more details. # 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 // This should be kept in sync with the submodule in the cimgui source
// code in ./vendor/ to be safe that they're compatible. // code in ./vendor/ to be safe that they're compatible.
.imgui = .{ .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", .hash = "1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402",
}, },

View File

@ -3,8 +3,9 @@
.version = "2.13.2", .version = "2.13.2",
.paths = .{""}, .paths = .{""},
.dependencies = .{ .dependencies = .{
// freetype/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", .hash = "1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d",
}, },

View File

@ -3,8 +3,9 @@
.version = "14.2.0", .version = "14.2.0",
.paths = .{""}, .paths = .{""},
.dependencies = .{ .dependencies = .{
// KhronosGroup/glslang
.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", .hash = "12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1",
}, },

View File

@ -3,8 +3,9 @@
.version = "8.4.0", .version = "8.4.0",
.paths = .{""}, .paths = .{""},
.dependencies = .{ .dependencies = .{
// harfbuzz/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", .hash = "1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122",
}, },

View File

@ -3,8 +3,9 @@
.version = "1.1.0", .version = "1.1.0",
.paths = .{""}, .paths = .{""},
.dependencies = .{ .dependencies = .{
// google/highway
.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", .hash = "12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b",
}, },

View File

@ -3,8 +3,9 @@
.version = "1.6.43", .version = "1.6.43",
.paths = .{""}, .paths = .{""},
.dependencies = .{ .dependencies = .{
// glennrp/libpng
.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", .hash = "1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66",
}, },

View File

@ -18,9 +18,72 @@ pub const ColorSpace = opaque {
) orelse Allocator.Error.OutOfMemory; ) 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 { pub fn release(self: *ColorSpace) void {
c.CGColorSpaceRelease(@ptrCast(self)); 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 { test {

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