Merge branch 'ghostty-org:main' into main
3
.gitattributes
vendored
@ -1,3 +1,6 @@
|
||||
build.zig.zon.nix linguist-generated=true
|
||||
build.zig.zon.txt linguist-generated=true
|
||||
build.zig.zon2json-lock linguist-generated=true
|
||||
vendor/** linguist-vendored
|
||||
website/** linguist-documentation
|
||||
pkg/breakpad/vendor/** linguist-vendored
|
||||
|
4
.github/workflows/nix.yml
vendored
@ -50,5 +50,5 @@ jobs:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
useDaemon: false # sometimes fails on short jobs
|
||||
- name: Check Zig cache hash
|
||||
run: nix develop -c ./nix/build-support/check-zig-cache-hash.sh
|
||||
- name: Check Zig cache
|
||||
run: nix develop -c ./nix/build-support/check-zig-cache.sh
|
||||
|
2
.github/workflows/release-pr.yml
vendored
@ -68,7 +68,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.6.3
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
4
.github/workflows/release-tag.yml
vendored
@ -136,7 +136,7 @@ jobs:
|
||||
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.6.3
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@ -298,7 +298,7 @@ jobs:
|
||||
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.6.3
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
2
.github/workflows/release-tip.yml
vendored
@ -164,7 +164,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.6.3
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
113
.github/workflows/test.yml
vendored
@ -14,6 +14,7 @@ jobs:
|
||||
- build-bench
|
||||
- build-linux-libghostty
|
||||
- build-nix
|
||||
- build-snap
|
||||
- build-macos
|
||||
- build-macos-matrix
|
||||
- build-windows
|
||||
@ -25,6 +26,7 @@ jobs:
|
||||
- alejandra
|
||||
- typos
|
||||
- test-pkg-linux
|
||||
- test-debian-12
|
||||
steps:
|
||||
- id: status
|
||||
name: Determine status
|
||||
@ -202,10 +204,14 @@ jobs:
|
||||
- name: XCode Select
|
||||
run: sudo xcode-select -s /Applications/Xcode_16.0.app
|
||||
|
||||
- name: get the Zig deps
|
||||
id: deps
|
||||
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
|
||||
|
||||
# GhosttyKit is the framework that is built from Zig for our native
|
||||
# Mac app to access.
|
||||
- name: Build GhosttyKit
|
||||
run: nix develop -c zig build
|
||||
run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }}
|
||||
|
||||
# The native app is built with native XCode tooling. This also does
|
||||
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
|
||||
@ -238,35 +244,65 @@ jobs:
|
||||
- name: XCode Select
|
||||
run: sudo xcode-select -s /Applications/Xcode_16.0.app
|
||||
|
||||
- name: get the Zig deps
|
||||
id: deps
|
||||
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Test All
|
||||
run: |
|
||||
# OpenGL
|
||||
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
|
||||
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
|
||||
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
|
||||
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
|
||||
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
|
||||
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
|
||||
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
|
||||
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
|
||||
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
|
||||
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
|
||||
|
||||
# Metal
|
||||
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
|
||||
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
|
||||
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
|
||||
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
|
||||
nix develop -c zig build test -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
|
||||
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
|
||||
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
|
||||
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
|
||||
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
|
||||
nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
|
||||
|
||||
- name: Build All
|
||||
run: |
|
||||
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
|
||||
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
|
||||
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
|
||||
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
|
||||
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
|
||||
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype
|
||||
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext
|
||||
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype
|
||||
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz
|
||||
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape
|
||||
|
||||
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
|
||||
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
|
||||
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
|
||||
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
|
||||
nix develop -c zig build -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
|
||||
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype
|
||||
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext
|
||||
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype
|
||||
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz
|
||||
nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape
|
||||
|
||||
build-snap:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
[namespace-profile-ghostty-snap, namespace-profile-ghostty-snap-arm64]
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: test
|
||||
env:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.0
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- run: sudo apt install -y udev
|
||||
- run: sudo systemctl start systemd-udevd
|
||||
- uses: snapcore/action-build@v1
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-2022
|
||||
@ -366,7 +402,7 @@ jobs:
|
||||
run: nix develop -c zig build -Dapp-runtime=none test
|
||||
|
||||
- name: Test GTK Build
|
||||
run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true -Demit-docs
|
||||
run: nix develop -c zig build -Dapp-runtime=gtk -Demit-docs
|
||||
|
||||
- name: Test GLFW Build
|
||||
run: nix develop -c zig build -Dapp-runtime=glfw
|
||||
@ -379,10 +415,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
adwaita: ["true", "false"]
|
||||
x11: ["true", "false"]
|
||||
wayland: ["true", "false"]
|
||||
name: GTK adwaita=${{ matrix.adwaita }} x11=${{ matrix.x11 }} wayland=${{ matrix.wayland }}
|
||||
name: GTK x11=${{ matrix.x11 }} wayland=${{ matrix.wayland }}
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: test
|
||||
env:
|
||||
@ -413,7 +448,6 @@ jobs:
|
||||
nix develop -c \
|
||||
zig build \
|
||||
-Dapp-runtime=gtk \
|
||||
-Dgtk-adwaita=${{ matrix.adwaita }} \
|
||||
-Dgtk-x11=${{ matrix.x11 }} \
|
||||
-Dgtk-wayland=${{ matrix.wayland }}
|
||||
|
||||
@ -471,8 +505,12 @@ jobs:
|
||||
- name: XCode Select
|
||||
run: sudo xcode-select -s /Applications/Xcode_16.0.app
|
||||
|
||||
- name: get the Zig deps
|
||||
id: deps
|
||||
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: test
|
||||
run: nix develop -c zig build test
|
||||
run: nix develop -c zig build test --system ${{ steps.deps.outputs.deps }}
|
||||
|
||||
prettier:
|
||||
if: github.repository == 'ghostty-org/ghostty'
|
||||
@ -589,3 +627,26 @@ jobs:
|
||||
- name: Test ${{ matrix.pkg }} Build
|
||||
run: |
|
||||
nix develop -c sh -c "cd pkg/${{ matrix.pkg }} ; zig build test"
|
||||
|
||||
test-debian-12:
|
||||
name: Test build on Debian 12
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install and configure Namespace CLI
|
||||
uses: namespacelabs/nscloud-setup@v0
|
||||
|
||||
- name: Configure Namespace powered Buildx
|
||||
uses: namespacelabs/nscloud-setup-buildx-action@v0
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: src/build/docker/debian/Dockerfile
|
||||
build-args: |
|
||||
DISTRO_VERSION=12
|
||||
ZIG_VERSION=0.13.0
|
||||
|
10
.github/workflows/update-colorschemes.yml
vendored
@ -48,14 +48,14 @@ jobs:
|
||||
run: |
|
||||
# Only proceed if build.zig.zon has changed
|
||||
if ! git diff --exit-code build.zig.zon; then
|
||||
nix develop -c ./nix/build-support/check-zig-cache-hash.sh --update
|
||||
nix develop -c ./nix/build-support/check-zig-cache-hash.sh
|
||||
nix develop -c ./nix/build-support/check-zig-cache.sh --update
|
||||
nix develop -c ./nix/build-support/check-zig-cache.sh
|
||||
fi
|
||||
|
||||
# Verify the build still works. We choose an arbitrary build type
|
||||
# as a canary instead of testing all build types.
|
||||
- name: Test Build
|
||||
run: nix develop -c zig build -Dapp-runtime=gtk -Dgtk-adwaita=true
|
||||
run: nix build .#ghostty
|
||||
|
||||
- name: Create pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
@ -66,7 +66,9 @@ jobs:
|
||||
commit-message: "deps: Update iTerm2 color schemes"
|
||||
add-paths: |
|
||||
build.zig.zon
|
||||
nix/zigCacheHash.nix
|
||||
build.zig.zon2json-lock
|
||||
build.zig.zon.nix
|
||||
build.zig.zon.txt
|
||||
body: |
|
||||
Upstream revision: https://github.com/mbadolato/iTerm2-Color-Schemes/tree/${{ steps.zig_fetch.outputs.upstream_rev }}
|
||||
labels: dependencies
|
||||
|
14
PACKAGING.md
@ -23,13 +23,6 @@ https://release.files.ghostty.org/VERSION/ghostty-VERSION.tar.gz
|
||||
https://release.files.ghostty.org/VERSION/ghostty-VERSION.tar.gz.minisig
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> **Version 1.0.0 the filename is `ghostty-source.tar.gz`.** Future
|
||||
> versions will use the `ghostty-VERSION.tar.gz` format since it is more
|
||||
> typical for source tarballs. But for version 1.0.0, the filename is
|
||||
> `ghostty-source.tar.gz`.
|
||||
|
||||
Signature files are signed with
|
||||
[minisign](https://jedisct1.github.io/minisign/)
|
||||
using the following public key:
|
||||
@ -88,6 +81,13 @@ for system packages which separate a build and install step, since the
|
||||
install step can then be done with a `mv` or `cp` command (from `/tmp/ghostty`
|
||||
to wherever the package manager expects it).
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> **Version 1.1.1 and 1.1.2 are missing `fetch-zig-cache.sh`.** This was
|
||||
> an oversight on the release process. You can use the script from version
|
||||
> 1.1.0 to fetch the Zig cache for these versions. Future versions will
|
||||
> restore the script.
|
||||
|
||||
### Build Options
|
||||
|
||||
Ghostty uses the Zig build system. You can see all available build options by
|
||||
|
@ -1,32 +1,39 @@
|
||||
.{
|
||||
.name = "ghostty",
|
||||
.version = "1.1.1",
|
||||
.version = "1.1.3",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
// Zig libs
|
||||
|
||||
.libxev = .{
|
||||
.url = "https://github.com/mitchellh/libxev/archive/31eed4e337fed7b0149319e5cdbb62b848c24fbd.tar.gz",
|
||||
// mitchellh/libxev
|
||||
.url = "https://deps.files.ghostty.org/libxev-1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c.tar.gz",
|
||||
.hash = "1220ebf88622c4d502dc59e71347e4d28c47e033f11b59aff774ae5787565c40999c",
|
||||
},
|
||||
.mach_glfw = .{
|
||||
.url = "https://github.com/mitchellh/mach-glfw/archive/37c2995f31abcf7e8378fba68ddcf4a3faa02de0.tar.gz",
|
||||
// mitchellh/mach-glfw
|
||||
.url = "https://deps.files.ghostty.org/mach_glfw-12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62.tar.gz",
|
||||
.hash = "12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62",
|
||||
.lazy = true,
|
||||
},
|
||||
.vaxis = .{
|
||||
.url = "git+https://github.com/rockorager/libvaxis/?ref=main#6d729a2dc3b934818dffe06d2ba3ce02841ed74b",
|
||||
.hash = "12200df4ebeaed45de26cb2c9f3b6f3746d8013b604e035dae658f86f586c8c91d2f",
|
||||
// rockorager/libvaxis
|
||||
.url = "git+https://github.com/rockorager/libvaxis#2237a7059eae99e9f132dd5acd1555e49d6c7d93",
|
||||
.hash = "1220f5aec880d4f430cc1597ede88f1530da69e39a4986080e976b0c7b919c2ebfeb",
|
||||
},
|
||||
.z2d = .{
|
||||
.url = "git+https://github.com/vancluever/z2d?ref=v0.4.0#4638bb02a9dc41cc2fb811f092811f6a951c752a",
|
||||
// vancluever/z2d
|
||||
.url = "https://deps.files.ghostty.org/z2d-12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a.tar.gz",
|
||||
.hash = "12201f0d542e7541cf492a001d4d0d0155c92f58212fbcb0d224e95edeba06b5416a",
|
||||
},
|
||||
.zig_objc = .{
|
||||
.url = "https://github.com/mitchellh/zig-objc/archive/9b8ba849b0f58fe207ecd6ab7c147af55b17556e.tar.gz",
|
||||
// mitchellh/zig-objc
|
||||
.url = "https://deps.files.ghostty.org/zig_objc-1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634.tar.gz",
|
||||
.hash = "1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634",
|
||||
},
|
||||
.zig_js = .{
|
||||
.url = "https://github.com/mitchellh/zig-js/archive/d0b8b0a57c52fbc89f9d9fecba75ca29da7dd7d1.tar.gz",
|
||||
// mitchellh/zig-js
|
||||
.url = "https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz",
|
||||
.hash = "12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc",
|
||||
},
|
||||
.ziglyph = .{
|
||||
@ -34,13 +41,20 @@
|
||||
.hash = "12207831bce7d4abce57b5a98e8f3635811cfefd160bca022eb91fe905d36a02cf25",
|
||||
},
|
||||
.zig_wayland = .{
|
||||
.url = "https://codeberg.org/ifreund/zig-wayland/archive/fbfe3b4ac0b472a27b1f1a67405436c58cbee12d.tar.gz",
|
||||
// codeberg ifreund/zig-wayland
|
||||
.url = "https://deps.files.ghostty.org/zig-wayland-fbfe3b4ac0b472a27b1f1a67405436c58cbee12d.tar.gz",
|
||||
.hash = "12209ca054cb1919fa276e328967f10b253f7537c4136eb48f3332b0f7cf661cad38",
|
||||
},
|
||||
.zf = .{
|
||||
.url = "git+https://github.com/natecraddock/zf/?ref=main#ed99ca18b02dda052e20ba467e90b623c04690dd",
|
||||
// natecraddock/zf
|
||||
.url = "https://deps.files.ghostty.org/zf-1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8.tar.gz",
|
||||
.hash = "1220edc3b8d8bedbb50555947987e5e8e2f93871ca3c8e8d4cc8f1377c15b5dd35e8",
|
||||
},
|
||||
.gobject = .{
|
||||
// ianprime0509/zig-gobject
|
||||
.url = "https://deps.files.ghostty.org/gobject-12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d.tar.zst",
|
||||
.hash = "12208d70ee791d7ef7e16e1c3c9c1127b57f1ed066a24f87d57fc9f730c5dc394b9d",
|
||||
},
|
||||
|
||||
// C libs
|
||||
.cimgui = .{ .path = "./pkg/cimgui" },
|
||||
@ -72,15 +86,15 @@
|
||||
.hash = "12201a57c6ce0001aa034fa80fba3e1cd2253c560a45748f4f4dd21ff23b491cddef",
|
||||
},
|
||||
.plasma_wayland_protocols = .{
|
||||
.url = "git+https://github.com/KDE/plasma-wayland-protocols?ref=main#db525e8f9da548cffa2ac77618dd0fbe7f511b86",
|
||||
.url = "https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz",
|
||||
.hash = "12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566",
|
||||
},
|
||||
|
||||
// Other
|
||||
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
||||
.iterm2_themes = .{
|
||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/db227d159adc265818f2e898da0f70ef8d7b580e.tar.gz",
|
||||
.hash = "12203d2647e5daf36a9c85b969e03f422540786ce9ea624eb4c26d204fe1f46218f3",
|
||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/efb1bb1843500a751eb30afa58fe48a6bec8952c.tar.gz",
|
||||
.hash = "1220a1dbe41bc69aacf75026a7158812198ea265fb9cac64dcb91cd31f3b1b8c1f92",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
390
build.zig.zon.nix
generated
Normal 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
@ -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_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz
|
||||
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
|
||||
https://deps.files.ghostty.org/zig_objc-1220e17e64ef0ef561b3e4b9f3a96a2494285f2ec31c097721bf8c8677ec4415c634.tar.gz
|
||||
https://deps.files.ghostty.org/zig-wayland-fbfe3b4ac0b472a27b1f1a67405436c58cbee12d.tar.gz
|
||||
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
|
||||
https://github.com/getsentry/breakpad/archive/b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz
|
||||
https://github.com/GNOME/libxml2/archive/refs/tags/v2.11.5.tar.gz
|
||||
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/efb1bb1843500a751eb30afa58fe48a6bec8952c.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
@ -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="
|
||||
},
|
||||
"1220a1dbe41bc69aacf75026a7158812198ea265fb9cac64dcb91cd31f3b1b8c1f92": {
|
||||
"name": "iterm2_themes",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/efb1bb1843500a751eb30afa58fe48a6bec8952c.tar.gz",
|
||||
"hash": "sha256-ZdVc1mmLwF45PZiqL/j/l7MO2O6hZ11lqIToGFdHiEU="
|
||||
},
|
||||
"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="
|
||||
}
|
||||
}
|
62
flake.lock
generated
@ -3,11 +3,11 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"lastModified": 1733328505,
|
||||
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -21,11 +21,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705309234,
|
||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -36,11 +36,11 @@
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1733423277,
|
||||
"narHash": "sha256-TxabjxEgkNbCGFRHgM/b9yZWlBj60gUOUnRT/wbVQR8=",
|
||||
"lastModified": 1738255539,
|
||||
"narHash": "sha256-hP2eOqhIO/OILW+3moNWO4GtdJFYCqAe9yJZgvlCoDQ=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e36963a147267afc055f7cf65225958633e536bf",
|
||||
"rev": "c3511a3b53b482aa7547c9d1626fd7310c1de1c5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -52,11 +52,11 @@
|
||||
},
|
||||
"nixpkgs-unstable": {
|
||||
"locked": {
|
||||
"lastModified": 1733229606,
|
||||
"narHash": "sha256-FLYY5M0rpa5C2QAE3CKLYAM6TwbKicdRK6qNrSHlNrE=",
|
||||
"lastModified": 1738136902,
|
||||
"narHash": "sha256-pUvLijVGARw4u793APze3j6mU1Zwdtz7hGkGGkD87qw=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "566e53c2ad750c84f6d31f9ccb9d00f823165550",
|
||||
"rev": "9a5db3142ce450045840cc8d832b13b8a2018e0c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -69,9 +69,11 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs-stable": "nixpkgs-stable",
|
||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||
"zig": "zig"
|
||||
"zig": "zig",
|
||||
"zig2nix": "zig2nix"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
@ -92,17 +94,19 @@
|
||||
"zig": {
|
||||
"inputs": {
|
||||
"flake-compat": [],
|
||||
"flake-utils": "flake-utils",
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs-stable"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1717848532,
|
||||
"narHash": "sha256-d+xIUvSTreHl8pAmU1fnmkfDTGQYCn2Rb/zOwByxS2M=",
|
||||
"lastModified": 1738239110,
|
||||
"narHash": "sha256-Y5i9mQ++dyIQr+zEPNy+KIbc5wjPmfllBrag3cHZgcE=",
|
||||
"owner": "mitchellh",
|
||||
"repo": "zig-overlay",
|
||||
"rev": "02fc5cc555fc14fda40c42d7c3250efa43812b43",
|
||||
"rev": "1a8fb6f3a04724519436355564b95fce5e272504",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -110,6 +114,30 @@
|
||||
"repo": "zig-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"zig2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs-stable"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1738263917,
|
||||
"narHash": "sha256-j/3fwe2pEOquHabP/puljOKwAZFjIE9gXZqA91sC48M=",
|
||||
"owner": "jcollie",
|
||||
"repo": "zig2nix",
|
||||
"rev": "c311d8e77a6ee0d995f40a6e10a89a3a4ab04f9a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "jcollie",
|
||||
"ref": "c311d8e77a6ee0d995f40a6e10a89a3a4ab04f9a",
|
||||
"repo": "zig2nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
13
flake.nix
@ -8,6 +8,7 @@
|
||||
# glibc versions used by our dependencies from Nix are compatible with the
|
||||
# system glibc that the user is building for.
|
||||
nixpkgs-stable.url = "github:nixos/nixpkgs/release-24.11";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
# Used for shell.nix
|
||||
flake-compat = {
|
||||
@ -19,9 +20,18 @@
|
||||
url = "github:mitchellh/zig-overlay";
|
||||
inputs = {
|
||||
nixpkgs.follows = "nixpkgs-stable";
|
||||
flake-utils.follows = "flake-utils";
|
||||
flake-compat.follows = "";
|
||||
};
|
||||
};
|
||||
|
||||
zig2nix = {
|
||||
url = "github:jcollie/zig2nix?ref=c311d8e77a6ee0d995f40a6e10a89a3a4ab04f9a";
|
||||
inputs = {
|
||||
nixpkgs.follows = "nixpkgs-stable";
|
||||
flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
@ -29,6 +39,7 @@
|
||||
nixpkgs-unstable,
|
||||
nixpkgs-stable,
|
||||
zig,
|
||||
zig2nix,
|
||||
...
|
||||
}:
|
||||
builtins.foldl' nixpkgs-stable.lib.recursiveUpdate {} (
|
||||
@ -40,6 +51,7 @@
|
||||
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
|
||||
@ -49,6 +61,7 @@
|
||||
revision = self.shortRev or self.dirtyShortRev or "dirty";
|
||||
};
|
||||
in rec {
|
||||
deps = pkgs-stable.callPackage ./build.zig.zon.nix {};
|
||||
ghostty-debug = pkgs-stable.callPackage ./nix/package.nix (mkArgs "Debug");
|
||||
ghostty-releasesafe = pkgs-stable.callPackage ./nix/package.nix (mkArgs "ReleaseSafe");
|
||||
ghostty-releasefast = pkgs-stable.callPackage ./nix/package.nix (mkArgs "ReleaseFast");
|
||||
|
@ -412,6 +412,7 @@ typedef enum {
|
||||
GHOSTTY_FULLSCREEN_NATIVE,
|
||||
GHOSTTY_FULLSCREEN_NON_NATIVE,
|
||||
GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU,
|
||||
GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH,
|
||||
} ghostty_action_fullscreen_e;
|
||||
|
||||
// apprt.action.SecureInput
|
||||
@ -585,6 +586,7 @@ typedef enum {
|
||||
GHOSTTY_ACTION_RENDER_INSPECTOR,
|
||||
GHOSTTY_ACTION_DESKTOP_NOTIFICATION,
|
||||
GHOSTTY_ACTION_SET_TITLE,
|
||||
GHOSTTY_ACTION_PROMPT_TITLE,
|
||||
GHOSTTY_ACTION_PWD,
|
||||
GHOSTTY_ACTION_MOUSE_SHAPE,
|
||||
GHOSTTY_ACTION_MOUSE_VISIBILITY,
|
||||
@ -644,7 +646,7 @@ typedef void (*ghostty_runtime_write_clipboard_cb)(void*,
|
||||
ghostty_clipboard_e,
|
||||
bool);
|
||||
typedef void (*ghostty_runtime_close_surface_cb)(void*, bool);
|
||||
typedef void (*ghostty_runtime_action_cb)(ghostty_app_t,
|
||||
typedef bool (*ghostty_runtime_action_cb)(ghostty_app_t,
|
||||
ghostty_target_s,
|
||||
ghostty_action_s);
|
||||
|
||||
|
12
macos/Assets.xcassets/Alternate Icons/BlueprintImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "macOS-AppIcon-1024px.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
macos/Assets.xcassets/Alternate Icons/BlueprintImage.imageset/macOS-AppIcon-1024px.png
vendored
Normal file
After Width: | Height: | Size: 434 KiB |
12
macos/Assets.xcassets/Alternate Icons/ChalkboardImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "macOS-AppIcon-1024px.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
macos/Assets.xcassets/Alternate Icons/ChalkboardImage.imageset/macOS-AppIcon-1024px.png
vendored
Normal file
After Width: | Height: | Size: 576 KiB |
6
macos/Assets.xcassets/Alternate Icons/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
12
macos/Assets.xcassets/Alternate Icons/GlassImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "macOS-AppIcon-1024px.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
macos/Assets.xcassets/Alternate Icons/GlassImage.imageset/macOS-AppIcon-1024px.png
vendored
Normal file
After Width: | Height: | Size: 515 KiB |
12
macos/Assets.xcassets/Alternate Icons/HolographicImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "macOS-AppIcon-1024px.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
macos/Assets.xcassets/Alternate Icons/HolographicImage.imageset/macOS-AppIcon-1024px.png
vendored
Normal file
After Width: | Height: | Size: 588 KiB |
12
macos/Assets.xcassets/Alternate Icons/MicrochipImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "macOS-AppIcon-1024px.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
macos/Assets.xcassets/Alternate Icons/MicrochipImage.imageset/macOS-AppIcon-1024px.png
vendored
Normal file
After Width: | Height: | Size: 630 KiB |
12
macos/Assets.xcassets/Alternate Icons/PaperImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "macOS-AppIcon-1024px.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
macos/Assets.xcassets/Alternate Icons/PaperImage.imageset/macOS-AppIcon-1024px.png
vendored
Normal file
After Width: | Height: | Size: 335 KiB |
12
macos/Assets.xcassets/Alternate Icons/RetroImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "macOS-AppIcon-1024px.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
macos/Assets.xcassets/Alternate Icons/RetroImage.imageset/macOS-AppIcon-1024px.png
vendored
Normal file
After Width: | Height: | Size: 1.0 MiB |
12
macos/Assets.xcassets/Alternate Icons/XrayImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "macOS-AppIcon-1024px.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
macos/Assets.xcassets/Alternate Icons/XrayImage.imageset/macOS-AppIcon-1024px.png
vendored
Normal file
After Width: | Height: | Size: 443 KiB |
@ -72,6 +72,7 @@
|
||||
A5A2A3CA2D4445E30033CF96 /* Dock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A2A3C92D4445E20033CF96 /* Dock.swift */; };
|
||||
A5A2A3CC2D444ABB0033CF96 /* NSApplication+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A2A3CB2D444AB80033CF96 /* NSApplication+Extension.swift */; };
|
||||
A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A6F7292CC41B8700B232A5 /* Xcode.swift */; };
|
||||
A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */; };
|
||||
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
||||
A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */; };
|
||||
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378D2D31D6C100931030 /* Weak.swift */; };
|
||||
@ -168,6 +169,7 @@
|
||||
A5A2A3C92D4445E20033CF96 /* Dock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dock.swift; sourceTree = "<group>"; };
|
||||
A5A2A3CB2D444AB80033CF96 /* NSApplication+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSApplication+Extension.swift"; sourceTree = "<group>"; };
|
||||
A5A6F7292CC41B8700B232A5 /* Xcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xcode.swift; sourceTree = "<group>"; };
|
||||
A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastWindowPosition.swift; sourceTree = "<group>"; };
|
||||
A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
|
||||
@ -270,6 +272,7 @@
|
||||
A534263D2A7DCBB000EBB7A2 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */,
|
||||
A5A6F7292CC41B8700B232A5 /* Xcode.swift */,
|
||||
A5CEAFFE29C2410700646FDA /* Backport.swift */,
|
||||
A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */,
|
||||
@ -623,6 +626,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */,
|
||||
A59630A42AF059BB00D64628 /* Ghostty.SplitNode.swift in Sources */,
|
||||
A514C8D62B54A16400493A16 /* Ghostty.Config.swift in Sources */,
|
||||
A54B0CEB2D0CFB4C00CBEFF8 /* NSImage+Extension.swift in Sources */,
|
||||
|
@ -28,7 +28,9 @@ class AppDelegate: NSObject,
|
||||
@IBOutlet private var menuNewWindow: NSMenuItem?
|
||||
@IBOutlet private var menuNewTab: NSMenuItem?
|
||||
@IBOutlet private var menuSplitRight: NSMenuItem?
|
||||
@IBOutlet private var menuSplitLeft: NSMenuItem?
|
||||
@IBOutlet private var menuSplitDown: NSMenuItem?
|
||||
@IBOutlet private var menuSplitUp: NSMenuItem?
|
||||
@IBOutlet private var menuClose: NSMenuItem?
|
||||
@IBOutlet private var menuCloseTab: NSMenuItem?
|
||||
@IBOutlet private var menuCloseWindow: NSMenuItem?
|
||||
@ -41,6 +43,7 @@ class AppDelegate: NSObject,
|
||||
|
||||
@IBOutlet private var menuToggleVisibility: NSMenuItem?
|
||||
@IBOutlet private var menuToggleFullScreen: NSMenuItem?
|
||||
@IBOutlet private var menuBringAllToFront: NSMenuItem?
|
||||
@IBOutlet private var menuZoomSplit: NSMenuItem?
|
||||
@IBOutlet private var menuPreviousSplit: NSMenuItem?
|
||||
@IBOutlet private var menuNextSplit: NSMenuItem?
|
||||
@ -52,6 +55,7 @@ class AppDelegate: NSObject,
|
||||
@IBOutlet private var menuIncreaseFontSize: NSMenuItem?
|
||||
@IBOutlet private var menuDecreaseFontSize: NSMenuItem?
|
||||
@IBOutlet private var menuResetFontSize: NSMenuItem?
|
||||
@IBOutlet private var menuChangeTitle: NSMenuItem?
|
||||
@IBOutlet private var menuQuickTerminal: NSMenuItem?
|
||||
@IBOutlet private var menuTerminalInspector: NSMenuItem?
|
||||
|
||||
@ -93,7 +97,7 @@ class AppDelegate: NSObject,
|
||||
}
|
||||
|
||||
/// Tracks the windows that we hid for toggleVisibility.
|
||||
private var hiddenWindows: [Weak<NSWindow>] = []
|
||||
private var hiddenState: ToggleVisibilityState? = nil
|
||||
|
||||
/// The observer for the app appearance.
|
||||
private var appearanceObserver: NSKeyValueObservation? = nil
|
||||
@ -217,8 +221,8 @@ class AppDelegate: NSObject,
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ notification: Notification) {
|
||||
// If we're back then clear the hidden windows
|
||||
self.hiddenWindows = []
|
||||
// If we're back manually then clear the hidden state because macOS handles it.
|
||||
self.hiddenState = nil
|
||||
|
||||
// First launch stuff
|
||||
if (!applicationHasBecomeActive) {
|
||||
@ -245,7 +249,13 @@ class AppDelegate: NSObject,
|
||||
// This probably isn't fully safe. The isEmpty check above is aspirational, it doesn't
|
||||
// quite work with SwiftUI because windows are retained on close. So instead we check
|
||||
// if there are any that are visible. I'm guessing this breaks under certain scenarios.
|
||||
if (windows.allSatisfy { !$0.isVisible }) { return .terminateNow }
|
||||
//
|
||||
// NOTE(mitchellh): I don't think we need this check at all anymore. I'm keeping it
|
||||
// here because I don't want to remove it in a patch release cycle but we should
|
||||
// target removing it soon.
|
||||
if (self.quickController == nil && windows.allSatisfy { !$0.isVisible }) {
|
||||
return .terminateNow
|
||||
}
|
||||
|
||||
// If the user is shutting down, restarting, or logging out, we don't confirm quit.
|
||||
why: if let event = NSAppleEventManager.shared().currentAppleEvent {
|
||||
@ -355,7 +365,9 @@ class AppDelegate: NSObject,
|
||||
syncMenuShortcut(config, action: "close_window", menuItem: self.menuCloseWindow)
|
||||
syncMenuShortcut(config, action: "close_all_windows", menuItem: self.menuCloseAllWindows)
|
||||
syncMenuShortcut(config, action: "new_split:right", menuItem: self.menuSplitRight)
|
||||
syncMenuShortcut(config, action: "new_split:left", menuItem: self.menuSplitLeft)
|
||||
syncMenuShortcut(config, action: "new_split:down", menuItem: self.menuSplitDown)
|
||||
syncMenuShortcut(config, action: "new_split:up", menuItem: self.menuSplitUp)
|
||||
|
||||
syncMenuShortcut(config, action: "copy_to_clipboard", menuItem: self.menuCopy)
|
||||
syncMenuShortcut(config, action: "paste_from_clipboard", menuItem: self.menuPaste)
|
||||
@ -378,6 +390,7 @@ class AppDelegate: NSObject,
|
||||
syncMenuShortcut(config, action: "increase_font_size:1", menuItem: self.menuIncreaseFontSize)
|
||||
syncMenuShortcut(config, action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize)
|
||||
syncMenuShortcut(config, action: "reset_font_size", menuItem: self.menuResetFontSize)
|
||||
syncMenuShortcut(config, action: "change_title_prompt", menuItem: self.menuChangeTitle)
|
||||
syncMenuShortcut(config, action: "toggle_quick_terminal", menuItem: self.menuQuickTerminal)
|
||||
syncMenuShortcut(config, action: "toggle_visibility", menuItem: self.menuToggleVisibility)
|
||||
syncMenuShortcut(config, action: "inspector:toggle", menuItem: self.menuTerminalInspector)
|
||||
@ -524,6 +537,15 @@ class AppDelegate: NSObject,
|
||||
// AppKit mutex on the appearance.
|
||||
DispatchQueue.main.async { self.syncAppearance(config: config) }
|
||||
|
||||
// Decide whether to hide/unhide app from dock and app switcher
|
||||
switch (config.macosHidden) {
|
||||
case .never:
|
||||
NSApp.setActivationPolicy(.regular)
|
||||
|
||||
case .always:
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
}
|
||||
|
||||
// If we have configuration errors, we need to show them.
|
||||
let c = ConfigurationErrorsController.sharedInstance
|
||||
c.errors = config.errors
|
||||
@ -557,6 +579,30 @@ class AppDelegate: NSObject,
|
||||
self.appIcon = nil
|
||||
break
|
||||
|
||||
case .blueprint:
|
||||
self.appIcon = NSImage(named: "BlueprintImage")!
|
||||
|
||||
case .chalkboard:
|
||||
self.appIcon = NSImage(named: "ChalkboardImage")!
|
||||
|
||||
case .glass:
|
||||
self.appIcon = NSImage(named: "GlassImage")!
|
||||
|
||||
case .holographic:
|
||||
self.appIcon = NSImage(named: "HolographicImage")!
|
||||
|
||||
case .microchip:
|
||||
self.appIcon = NSImage(named: "MicrochipImage")!
|
||||
|
||||
case .paper:
|
||||
self.appIcon = NSImage(named: "PaperImage")!
|
||||
|
||||
case .retro:
|
||||
self.appIcon = NSImage(named: "RetroImage")!
|
||||
|
||||
case .xray:
|
||||
self.appIcon = NSImage(named: "XrayImage")!
|
||||
|
||||
case .customStyle:
|
||||
guard let ghostColor = config.macosIconGhostColor else { break }
|
||||
guard let screenColors = config.macosIconScreenColor else { break }
|
||||
@ -709,21 +755,15 @@ class AppDelegate: NSObject,
|
||||
|
||||
/// Toggles visibility of all Ghosty Terminal windows. When hidden, activates Ghostty as the frontmost application
|
||||
@IBAction func toggleVisibility(_ sender: Any) {
|
||||
// If we have focus, then we hide all windows.
|
||||
if NSApp.isActive {
|
||||
// Toggle visibility doesn't do anything if the focused window is native
|
||||
// fullscreen.
|
||||
// fullscreen. This is only relevant if Ghostty is active.
|
||||
guard let keyWindow = NSApp.keyWindow,
|
||||
!keyWindow.styleMask.contains(.fullScreen) else { return }
|
||||
|
||||
// If we have focus, then we hide all windows.
|
||||
if NSApp.isActive {
|
||||
// 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) }
|
||||
// Keep track of our hidden state to restore properly
|
||||
self.hiddenState = .init()
|
||||
NSApp.hide(nil)
|
||||
return
|
||||
}
|
||||
@ -734,8 +774,16 @@ class AppDelegate: NSObject,
|
||||
// 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.
|
||||
self.hiddenWindows.forEach { $0.value?.orderFrontRegardless() }
|
||||
self.hiddenWindows = []
|
||||
hiddenState?.restore()
|
||||
hiddenState = nil
|
||||
}
|
||||
|
||||
@IBAction func bringAllToFront(_ sender: Any) {
|
||||
if !NSApp.isActive {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
NSApplication.shared.arrangeInFront(sender)
|
||||
}
|
||||
|
||||
private struct DerivedConfig {
|
||||
@ -755,4 +803,33 @@ class AppDelegate: NSObject,
|
||||
self.quickTerminalPosition = config.quickTerminalPosition
|
||||
}
|
||||
}
|
||||
|
||||
private struct ToggleVisibilityState {
|
||||
let hiddenWindows: [Weak<NSWindow>]
|
||||
let keyWindow: Weak<NSWindow>?
|
||||
|
||||
init() {
|
||||
// We need to know the key window so that we can bring focus back to the
|
||||
// right window if it was hidden.
|
||||
self.keyWindow = if let keyWindow = NSApp.keyWindow {
|
||||
.init(keyWindow)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
|
||||
// We need to keep track of the windows that were visible because we only
|
||||
// want to bring back these windows if we remove the toggle.
|
||||
//
|
||||
// We also ignore fullscreen windows because they don't hide anyways.
|
||||
self.hiddenWindows = NSApp.windows.filter {
|
||||
$0.isVisible &&
|
||||
!$0.styleMask.contains(.fullScreen)
|
||||
}.map { Weak($0) }
|
||||
}
|
||||
|
||||
func restore() {
|
||||
hiddenWindows.forEach { $0.value?.orderFrontRegardless() }
|
||||
keyWindow?.value?.makeKey()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customObject id="bbz-4X-AYv" userLabel="AppDelegate" customClass="AppDelegate" customModule="Ghostty" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="menuBringAllToFront" destination="LE2-aR-0XJ" id="AP9-oK-60V"/>
|
||||
<outlet property="menuChangeTitle" destination="24I-xg-qIq" id="kg6-kT-jNL"/>
|
||||
<outlet property="menuCheckForUpdates" destination="GEA-5y-yzH" id="0nV-Tf-nJQ"/>
|
||||
<outlet property="menuClose" destination="DVo-aG-piG" id="R3t-0C-aSU"/>
|
||||
<outlet property="menuCloseAllWindows" destination="yKr-Vi-Yqw" id="Zet-Ir-zbm"/>
|
||||
@ -45,8 +47,10 @@
|
||||
<outlet property="menuSelectSplitLeft" destination="cTK-oy-KuV" id="Jpr-5q-dqz"/>
|
||||
<outlet property="menuSelectSplitRight" destination="upj-mc-L7X" id="nLY-o1-lky"/>
|
||||
<outlet property="menuServices" destination="aQe-vS-j8Q" id="uWQ-Wo-T1L"/>
|
||||
<outlet property="menuSplitDown" destination="UDZ-4y-6xL" id="fgZ-Wb-8OR"/>
|
||||
<outlet property="menuSplitDown" destination="UDZ-4y-6xL" id="ptr-mj-Azh"/>
|
||||
<outlet property="menuSplitLeft" destination="Ppv-GP-lQU" id="Xd5-Cd-Jut"/>
|
||||
<outlet property="menuSplitRight" destination="VUR-Ld-nLx" id="RxO-Zw-ovb"/>
|
||||
<outlet property="menuSplitUp" destination="Ggp-7N-GbX" id="YJF-uq-S4Y"/>
|
||||
<outlet property="menuTerminalInspector" destination="QwP-M5-fvh" id="wJi-Dh-S9f"/>
|
||||
<outlet property="menuToggleFullScreen" destination="8kY-Pi-KaY" id="yQg-6V-OO6"/>
|
||||
<outlet property="menuToggleVisibility" destination="DOX-wA-ilh" id="iBj-Bc-2bq"/>
|
||||
@ -143,10 +147,22 @@
|
||||
<action selector="splitRight:" target="-1" id="cv2-Xg-FR4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Split Left" id="Ppv-GP-lQU">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="splitLeft:" target="-1" id="Cey-Mf-bD2"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Split Down" id="UDZ-4y-6xL">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="splitDown:" target="-1" id="c6x-CF-u52"/>
|
||||
<action selector="splitDown:" target="-1" id="Zej-CF-6nO"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Split Up" id="Ggp-7N-GbX">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="splitUp:" target="-1" id="bbi-dK-pOS"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="sjq-M1-UGS"/>
|
||||
@ -232,6 +248,13 @@
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="L3L-I8-sqk"/>
|
||||
<menuItem title="Change Title..." id="24I-xg-qIq">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="changeTitle:" target="-1" id="XuL-QB-Q9l"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="Vkj-tP-dMZ"/>
|
||||
<menuItem title="Quick Terminal" id="1pv-LF-NBJ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
@ -270,12 +293,6 @@
|
||||
<action selector="toggleGhosttyFullScreen:" target="-1" id="QB9-7R-xyc"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Show/Hide All Terminals" id="DOX-wA-ilh">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
@ -370,6 +387,13 @@
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="dgt-Tx-d4e"/>
|
||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
|
@ -521,11 +521,21 @@ class BaseTerminalController: NSWindowController,
|
||||
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_RIGHT)
|
||||
}
|
||||
|
||||
@IBAction func splitLeft(_ sender: Any) {
|
||||
guard let surface = focusedSurface?.surface else { return }
|
||||
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_LEFT)
|
||||
}
|
||||
|
||||
@IBAction func splitDown(_ sender: Any) {
|
||||
guard let surface = focusedSurface?.surface else { return }
|
||||
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_DOWN)
|
||||
}
|
||||
|
||||
@IBAction func splitUp(_ sender: Any) {
|
||||
guard let surface = focusedSurface?.surface else { return }
|
||||
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_UP)
|
||||
}
|
||||
|
||||
@IBAction func splitZoom(_ sender: Any) {
|
||||
guard let surface = focusedSurface?.surface else { return }
|
||||
ghostty.splitToggleZoom(surface: surface)
|
||||
|
@ -283,9 +283,12 @@ class TerminalController: BaseTerminalController {
|
||||
private func setInitialWindowPosition(x: Int16?, y: Int16?, windowDecorations: Bool) {
|
||||
guard let window else { return }
|
||||
|
||||
// If we don't have both an X and Y we center.
|
||||
// If we don't have an X/Y then we try to use the previously saved window pos.
|
||||
guard let x, let y else {
|
||||
if (!LastWindowPosition.shared.restore(window)) {
|
||||
window.center()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -490,6 +493,20 @@ class TerminalController: BaseTerminalController {
|
||||
override func windowDidMove(_ notification: Notification) {
|
||||
super.windowDidMove(notification)
|
||||
self.fixTabBar()
|
||||
|
||||
// Whenever we move save our last position for the next start.
|
||||
if let window {
|
||||
LastWindowPosition.shared.save(window)
|
||||
}
|
||||
}
|
||||
|
||||
func windowDidBecomeMain(_ notification: Notification) {
|
||||
// Whenever we get focused, use that as our last window position for
|
||||
// restart. This differs from Terminal.app but matches iTerm2 behavior
|
||||
// and I think its sensible.
|
||||
if let window {
|
||||
LastWindowPosition.shared.save(window)
|
||||
}
|
||||
}
|
||||
|
||||
// Called when the window will be encoded. We handle the data encoding here in the
|
||||
@ -692,13 +709,21 @@ class TerminalController: BaseTerminalController {
|
||||
// If our index is the same we do nothing
|
||||
guard finalIndex != selectedIndex else { return }
|
||||
|
||||
// Get our parent
|
||||
let parent = tabbedWindows[finalIndex]
|
||||
// Get our target window
|
||||
let targetWindow = tabbedWindows[finalIndex]
|
||||
|
||||
// Move our current selected window to the proper index
|
||||
// Begin a group of window operations to minimize visual updates
|
||||
NSAnimationContext.beginGrouping()
|
||||
NSAnimationContext.current.duration = 0
|
||||
|
||||
// Remove and re-add the window in the correct position
|
||||
tabGroup.removeWindow(selectedWindow)
|
||||
parent.addTabbedWindow(selectedWindow, ordered: action.amount < 0 ? .below : .above)
|
||||
selectedWindow.makeKeyAndOrderFront(nil)
|
||||
targetWindow.addTabbedWindow(selectedWindow, ordered: action.amount < 0 ? .below : .above)
|
||||
|
||||
// Ensure our window remains selected
|
||||
selectedWindow.makeKey()
|
||||
|
||||
NSAnimationContext.endGrouping()
|
||||
}
|
||||
|
||||
@objc private func onGotoTab(notification: SwiftUI.Notification) {
|
||||
|
@ -86,7 +86,7 @@ class TerminalManager {
|
||||
// fullscreen for the logic later in this method.
|
||||
c.toggleFullscreen(mode: .native)
|
||||
|
||||
case .nonNative, .nonNativeVisibleMenu:
|
||||
case .nonNative, .nonNativeVisibleMenu, .nonNativePaddedNotch:
|
||||
// If we're non-native then we have to do it on a later loop
|
||||
// so that the content view is setup.
|
||||
DispatchQueue.main.async {
|
||||
|
@ -95,6 +95,23 @@ fileprivate class CenteredDynamicLabel: NSTextField {
|
||||
setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||
}
|
||||
|
||||
// Vertically center the text
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
guard let attributedString = self.attributedStringValue.mutableCopy() as? NSMutableAttributedString else {
|
||||
super.draw(dirtyRect)
|
||||
return
|
||||
}
|
||||
|
||||
let textSize = attributedString.size()
|
||||
|
||||
let yOffset = (self.bounds.height - textSize.height) / 2 - 1 // -1 to center it better
|
||||
|
||||
let centeredRect = NSRect(x: self.bounds.origin.x, y: self.bounds.origin.y + yOffset,
|
||||
width: self.bounds.width, height: textSize.height)
|
||||
|
||||
attributedString.draw(in: centeredRect)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSToolbarItem.Identifier {
|
||||
|
@ -13,6 +13,9 @@ extension FullscreenMode {
|
||||
case GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU:
|
||||
.nonNativeVisibleMenu
|
||||
|
||||
case GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH:
|
||||
.nonNativePaddedNotch
|
||||
|
||||
default:
|
||||
nil
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ extension Ghostty {
|
||||
// MARK: Ghostty Callbacks (iOS)
|
||||
|
||||
static func wakeup(_ userdata: UnsafeMutableRawPointer?) {}
|
||||
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) {}
|
||||
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) -> Bool { return false }
|
||||
static func readClipboard(
|
||||
_ userdata: UnsafeMutableRawPointer?,
|
||||
location: ghostty_clipboard_e,
|
||||
@ -423,7 +423,7 @@ extension Ghostty {
|
||||
|
||||
// MARK: Actions (macOS)
|
||||
|
||||
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) {
|
||||
static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) -> Bool {
|
||||
// Make sure it a target we understand so all our action handlers can assert
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP, GHOSTTY_TARGET_SURFACE:
|
||||
@ -431,7 +431,7 @@ extension Ghostty {
|
||||
|
||||
default:
|
||||
Ghostty.logger.warning("unknown action target=\(target.tag.rawValue)")
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
// Action dispatch
|
||||
@ -455,13 +455,13 @@ extension Ghostty {
|
||||
toggleFullscreen(app, target: target, mode: action.action.toggle_fullscreen)
|
||||
|
||||
case GHOSTTY_ACTION_MOVE_TAB:
|
||||
moveTab(app, target: target, move: action.action.move_tab)
|
||||
return moveTab(app, target: target, move: action.action.move_tab)
|
||||
|
||||
case GHOSTTY_ACTION_GOTO_TAB:
|
||||
gotoTab(app, target: target, tab: action.action.goto_tab)
|
||||
return gotoTab(app, target: target, tab: action.action.goto_tab)
|
||||
|
||||
case GHOSTTY_ACTION_GOTO_SPLIT:
|
||||
gotoSplit(app, target: target, direction: action.action.goto_split)
|
||||
return gotoSplit(app, target: target, direction: action.action.goto_split)
|
||||
|
||||
case GHOSTTY_ACTION_RESIZE_SPLIT:
|
||||
resizeSplit(app, target: target, resize: action.action.resize_split)
|
||||
@ -484,6 +484,9 @@ extension Ghostty {
|
||||
case GHOSTTY_ACTION_SET_TITLE:
|
||||
setTitle(app, target: target, v: action.action.set_title)
|
||||
|
||||
case GHOSTTY_ACTION_PROMPT_TITLE:
|
||||
return promptTitle(app, target: target)
|
||||
|
||||
case GHOSTTY_ACTION_PWD:
|
||||
pwdChanged(app, target: target, v: action.action.pwd)
|
||||
|
||||
@ -541,10 +544,15 @@ extension Ghostty {
|
||||
fallthrough
|
||||
case GHOSTTY_ACTION_QUIT_TIMER:
|
||||
Ghostty.logger.info("known but unimplemented action action=\(action.tag.rawValue)")
|
||||
|
||||
return false
|
||||
default:
|
||||
Ghostty.logger.warning("unknown action action=\(action.tag.rawValue)")
|
||||
return false
|
||||
}
|
||||
|
||||
// If we reached here then we assume performed since all unknown actions
|
||||
// are captured in the switch and return false.
|
||||
return true
|
||||
}
|
||||
|
||||
private static func quit(_ app: ghostty_app_t) {
|
||||
@ -716,15 +724,19 @@ extension Ghostty {
|
||||
private static func moveTab(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
move: ghostty_action_move_tab_s) {
|
||||
move: ghostty_action_move_tab_s) -> Bool {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("move tab does nothing with an app target")
|
||||
return
|
||||
return false
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
guard let surface = target.target.surface else { return false }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
||||
|
||||
// See gotoTab for notes on this check.
|
||||
guard (surfaceView.window?.tabGroup?.windows.count ?? 0) > 1 else { return false }
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: .ghosttyMoveTab,
|
||||
object: surfaceView,
|
||||
@ -736,20 +748,27 @@ extension Ghostty {
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private static func gotoTab(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
tab: ghostty_action_goto_tab_e) {
|
||||
tab: ghostty_action_goto_tab_e) -> Bool {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("goto tab does nothing with an app target")
|
||||
return
|
||||
return false
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
guard let surface = target.target.surface else { return false }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
||||
|
||||
// Similar to goto_split (see comment there) about our performability,
|
||||
// we should make this more accurate later.
|
||||
guard (surfaceView.window?.tabGroup?.windows.count ?? 0) > 1 else { return false }
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.ghosttyGotoTab,
|
||||
object: surfaceView,
|
||||
@ -761,20 +780,31 @@ extension Ghostty {
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private static func gotoSplit(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
direction: ghostty_action_goto_split_e) {
|
||||
direction: ghostty_action_goto_split_e) -> Bool {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("goto split does nothing with an app target")
|
||||
return
|
||||
return false
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
guard let surface = target.target.surface else { return false }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
||||
guard let controller = surfaceView.window?.windowController as? BaseTerminalController else { return false }
|
||||
|
||||
// For now, we return false if the window has no splits and we return
|
||||
// true if the window has ANY splits. This isn't strictly correct because
|
||||
// we should only be returning true if we actually performed the action,
|
||||
// but this handles the most common case of caring about goto_split performability
|
||||
// which is the no-split case.
|
||||
guard controller.surfaceTree?.isSplit ?? false else { return false }
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.ghosttyFocusSplit,
|
||||
object: surfaceView,
|
||||
@ -786,6 +816,8 @@ extension Ghostty {
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private static func resizeSplit(
|
||||
@ -978,6 +1010,26 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
private static func promptTitle(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s) -> Bool {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("set title prompt does nothing with an app target")
|
||||
return false
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return false }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
||||
surfaceView.promptTitle()
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private static func pwdChanged(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
|
@ -216,6 +216,8 @@ extension Ghostty {
|
||||
.nonNative
|
||||
case "visible-menu":
|
||||
.nonNativeVisibleMenu
|
||||
case "padded-notch":
|
||||
.nonNativePaddedNotch
|
||||
default:
|
||||
defaultValue
|
||||
}
|
||||
@ -300,6 +302,16 @@ extension Ghostty {
|
||||
return buffer.map { .init(ghostty: $0) }
|
||||
}
|
||||
|
||||
var macosHidden: MacHidden {
|
||||
guard let config = self.config else { return .never }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-hidden"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return .never }
|
||||
guard let ptr = v else { return .never }
|
||||
let str = String(cString: ptr)
|
||||
return MacHidden(rawValue: str) ?? .never
|
||||
}
|
||||
|
||||
var focusFollowsMouse : Bool {
|
||||
guard let config = self.config else { return false }
|
||||
var v = false;
|
||||
@ -514,6 +526,11 @@ extension Ghostty.Config {
|
||||
case download
|
||||
}
|
||||
|
||||
enum MacHidden : String {
|
||||
case never
|
||||
case always
|
||||
}
|
||||
|
||||
enum ResizeOverlay : String {
|
||||
case always
|
||||
case never
|
||||
|
@ -38,6 +38,15 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the tree is split.
|
||||
var isSplit: Bool {
|
||||
return if case .leaf = self {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
func topLeft() -> SurfaceView {
|
||||
switch (self) {
|
||||
case .leaf(let leaf):
|
||||
@ -120,14 +129,7 @@ extension Ghostty {
|
||||
|
||||
/// Returns true if the split tree contains the given view.
|
||||
func contains(view: SurfaceView) -> Bool {
|
||||
switch (self) {
|
||||
case .leaf(let leaf):
|
||||
return leaf.surface == view
|
||||
|
||||
case .split(let container):
|
||||
return container.topLeft.contains(view: view) ||
|
||||
container.bottomRight.contains(view: view)
|
||||
}
|
||||
return leaf(for: view) != nil
|
||||
}
|
||||
|
||||
/// Find a surface view by UUID.
|
||||
@ -164,6 +166,22 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the node for the given view if its in the tree.
|
||||
func leaf(for view: SurfaceView) -> Leaf? {
|
||||
switch (self) {
|
||||
case .leaf(let leaf):
|
||||
if leaf.surface == view {
|
||||
return leaf
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
case .split(let container):
|
||||
return container.topLeft.leaf(for: view) ??
|
||||
container.bottomRight.leaf(for: view)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sequence
|
||||
|
||||
func makeIterator() -> IndexingIterator<[Leaf]> {
|
||||
|
@ -198,6 +198,14 @@ extension Ghostty {
|
||||
/// macos-icon
|
||||
enum MacOSIcon: String {
|
||||
case official
|
||||
case blueprint
|
||||
case chalkboard
|
||||
case glass
|
||||
case holographic
|
||||
case microchip
|
||||
case paper
|
||||
case retro
|
||||
case xray
|
||||
case customStyle = "custom-style"
|
||||
}
|
||||
|
||||
|
@ -124,6 +124,11 @@ extension Ghostty {
|
||||
// A timer to fallback to ghost emoji if no title is set within the grace period
|
||||
private var titleFallbackTimer: Timer?
|
||||
|
||||
// This is the title from the terminal. This is nil if we're currently using
|
||||
// the terminal title as the main title property. If the title is set manually
|
||||
// by the user, this is set to the prior value (which may be empty, but non-nil).
|
||||
private var titleFromTerminal: String?
|
||||
|
||||
/// Event monitor (see individual events for why)
|
||||
private var eventMonitor: Any? = nil
|
||||
|
||||
@ -380,6 +385,45 @@ extension Ghostty {
|
||||
NSCursor.setHiddenUntilMouseMoves(!visible)
|
||||
}
|
||||
|
||||
/// Set the title by prompting the user.
|
||||
func promptTitle() {
|
||||
// Create an alert dialog
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Change Terminal Title"
|
||||
alert.informativeText = "Leave blank to restore the default."
|
||||
alert.alertStyle = .informational
|
||||
|
||||
// Add a text field to the alert
|
||||
let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 250, height: 24))
|
||||
textField.stringValue = title
|
||||
alert.accessoryView = textField
|
||||
|
||||
// Add buttons
|
||||
alert.addButton(withTitle: "OK")
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
|
||||
let response = alert.runModal()
|
||||
|
||||
// Check if the user clicked "OK"
|
||||
if response == .alertFirstButtonReturn {
|
||||
// Get the input text
|
||||
let newTitle = textField.stringValue
|
||||
|
||||
if newTitle.isEmpty {
|
||||
// Empty means that user wants the title to be set automatically
|
||||
// We also need to reload the config for the "title" property to be
|
||||
// used again by this tab.
|
||||
let prevTitle = titleFromTerminal ?? "👻"
|
||||
titleFromTerminal = nil
|
||||
setTitle(prevTitle)
|
||||
} else {
|
||||
// Set the title and prevent it from being changed automatically
|
||||
titleFromTerminal = title
|
||||
title = newTitle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setTitle(_ title: String) {
|
||||
// This fixes an issue where very quick changes to the title could
|
||||
// cause an unpleasant flickering. We set a timer so that we can
|
||||
@ -390,6 +434,11 @@ extension Ghostty {
|
||||
withTimeInterval: 0.075,
|
||||
repeats: false
|
||||
) { [weak self] _ in
|
||||
// Set the title if it wasn't manually set.
|
||||
guard self?.titleFromTerminal == nil else {
|
||||
self?.titleFromTerminal = title
|
||||
return
|
||||
}
|
||||
self?.title = title
|
||||
}
|
||||
}
|
||||
@ -849,28 +898,8 @@ extension Ghostty {
|
||||
var handled: Bool = false
|
||||
if let list = keyTextAccumulator, list.count > 0 {
|
||||
handled = true
|
||||
|
||||
// This is a hack. libghostty on macOS treats ctrl input as not having
|
||||
// text because some keyboard layouts generate bogus characters for
|
||||
// ctrl+key. libghostty can't tell this is from an IM keyboard giving
|
||||
// us direct values. So, we just remove control.
|
||||
var modifierFlags = event.modifierFlags
|
||||
modifierFlags.remove(.control)
|
||||
if let keyTextEvent = NSEvent.keyEvent(
|
||||
with: .keyDown,
|
||||
location: event.locationInWindow,
|
||||
modifierFlags: modifierFlags,
|
||||
timestamp: event.timestamp,
|
||||
windowNumber: event.windowNumber,
|
||||
context: nil,
|
||||
characters: event.characters ?? "",
|
||||
charactersIgnoringModifiers: event.charactersIgnoringModifiers ?? "",
|
||||
isARepeat: event.isARepeat,
|
||||
keyCode: event.keyCode
|
||||
) {
|
||||
for text in list {
|
||||
_ = keyAction(action, event: keyTextEvent, text: text)
|
||||
}
|
||||
_ = keyAction(action, event: event, text: text)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1132,11 +1161,15 @@ extension Ghostty {
|
||||
|
||||
menu.addItem(.separator())
|
||||
menu.addItem(withTitle: "Split Right", action: #selector(splitRight(_:)), keyEquivalent: "")
|
||||
menu.addItem(withTitle: "Split Left", action: #selector(splitLeft(_:)), keyEquivalent: "")
|
||||
menu.addItem(withTitle: "Split Down", action: #selector(splitDown(_:)), keyEquivalent: "")
|
||||
menu.addItem(withTitle: "Split Up", action: #selector(splitUp(_:)), keyEquivalent: "")
|
||||
|
||||
menu.addItem(.separator())
|
||||
menu.addItem(withTitle: "Reset Terminal", action: #selector(resetTerminal(_:)), keyEquivalent: "")
|
||||
menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(toggleTerminalInspector(_:)), keyEquivalent: "")
|
||||
menu.addItem(.separator())
|
||||
menu.addItem(withTitle: "Change Title...", action: #selector(changeTitle(_:)), keyEquivalent: "")
|
||||
|
||||
return menu
|
||||
}
|
||||
@ -1189,11 +1222,21 @@ extension Ghostty {
|
||||
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_RIGHT)
|
||||
}
|
||||
|
||||
@IBAction func splitLeft(_ sender: Any) {
|
||||
guard let surface = self.surface else { return }
|
||||
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_LEFT)
|
||||
}
|
||||
|
||||
@IBAction func splitDown(_ sender: Any) {
|
||||
guard let surface = self.surface else { return }
|
||||
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_DOWN)
|
||||
}
|
||||
|
||||
@IBAction func splitUp(_ sender: Any) {
|
||||
guard let surface = self.surface else { return }
|
||||
ghostty_surface_split(surface, GHOSTTY_SPLIT_DIRECTION_UP)
|
||||
}
|
||||
|
||||
@objc func resetTerminal(_ sender: Any) {
|
||||
guard let surface = self.surface else { return }
|
||||
let action = "reset"
|
||||
@ -1210,6 +1253,10 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func changeTitle(_ sender: Any) {
|
||||
promptTitle()
|
||||
}
|
||||
|
||||
/// Show a user notification and associate it with this surface
|
||||
func showUserNotification(title: String, body: String) {
|
||||
let content = UNMutableNotificationContent()
|
||||
|
@ -6,6 +6,7 @@ enum FullscreenMode {
|
||||
case native
|
||||
case nonNative
|
||||
case nonNativeVisibleMenu
|
||||
case nonNativePaddedNotch
|
||||
|
||||
/// Initializes the fullscreen style implementation for the mode. This will not toggle any
|
||||
/// fullscreen properties. This may fail if the window isn't configured properly for a given
|
||||
@ -20,6 +21,9 @@ enum FullscreenMode {
|
||||
|
||||
case .nonNativeVisibleMenu:
|
||||
return NonNativeFullscreenVisibleMenu(window)
|
||||
|
||||
case .nonNativePaddedNotch:
|
||||
return NonNativeFullscreenPaddedNotch(window)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,6 +145,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
||||
|
||||
struct Properties {
|
||||
var hideMenu: Bool = true
|
||||
var paddedNotch: Bool = false
|
||||
}
|
||||
|
||||
private var savedState: SavedState?
|
||||
@ -278,6 +283,9 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
||||
// put an #available check, but it was in a bug fix release so I think
|
||||
// if a bug is reported to Ghostty we can just advise the user to
|
||||
// update.
|
||||
} else if (properties.paddedNotch) {
|
||||
// We are hiding the menu, we may need to avoid the notch.
|
||||
frame.size.height -= screen.safeAreaInsets.top
|
||||
}
|
||||
|
||||
return frame
|
||||
@ -349,3 +357,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
||||
class NonNativeFullscreenVisibleMenu: NonNativeFullscreen {
|
||||
override var properties: Properties { Properties(hideMenu: false) }
|
||||
}
|
||||
|
||||
class NonNativeFullscreenPaddedNotch: NonNativeFullscreen {
|
||||
override var properties: Properties { Properties(paddedNotch: true) }
|
||||
}
|
||||
|
34
macos/Sources/Helpers/LastWindowPosition.swift
Normal 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
|
||||
}
|
||||
}
|
@ -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}"
|
78
nix/build-support/check-zig-cache.sh
Executable 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
|
||||
|
@ -1,32 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Because Zig does not fetch recursive dependencies when you run `zig build
|
||||
# --fetch` (see https://github.com/ziglang/zig/issues/20976) we need to do some
|
||||
# extra work to fetch everything that we actually need to build without Internet
|
||||
# access (such as when building a Nix package).
|
||||
# NOTE THIS IS A TEMPORARY SCRIPT TO SUPPORT PACKAGE MAINTAINERS.
|
||||
#
|
||||
# An example of this happening:
|
||||
# A future Zig version will hopefully fix the issue where
|
||||
# `zig build --fetch` doesn't fetch transitive dependencies[1]. When that
|
||||
# is resolved, we won't need any special machinery for the general use case
|
||||
# at all and packagers can just use `zig build --fetch`.
|
||||
#
|
||||
# error: builder for '/nix/store/cx8qcwrhjmjxik2547fw99v5j6np5san-ghostty-0.1.0.drv' failed with exit code 1;
|
||||
# la/build/tmp.xgHOheUF7V/p/12208cfdda4d5fdbc81b0c44b82e4d6dba2d4a86bff644a153e026fdfc80f8469133/build.zig.zon:7:20: error: unable to discover remote git server capabilities: TemporaryNameServerFailure
|
||||
# > .url = "git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e",
|
||||
# > ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# > /build/tmp.xgHOheUF7V/p/12208cfdda4d5fdbc81b0c44b82e4d6dba2d4a86bff644a153e026fdfc80f8469133/build.zig.zon:16:20: error: unable to discover remote git server capabilities: TemporaryNameServerFailure
|
||||
# > .url = "git+https://github.com/mitchellh/libxev#f6a672a78436d8efee1aa847a43a900ad773618b",
|
||||
# > ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# >
|
||||
# For full logs, run 'nix log /nix/store/cx8qcwrhjmjxik2547fw99v5j6np5san-ghostty-0.1.0.drv'.
|
||||
#
|
||||
# To update this script, add any failing URLs with a line like this:
|
||||
#
|
||||
# zig fetch <url>
|
||||
#
|
||||
# Periodically old URLs may need to be cleaned out.
|
||||
#
|
||||
# Hopefully when the Zig issue is fixed this script can be eliminated in favor
|
||||
# of a plain `zig build --fetch`.
|
||||
# [1]: https://github.com/ziglang/zig/issues/20976
|
||||
|
||||
if [ -z ${ZIG_GLOBAL_CACHE_DIR+x} ]
|
||||
then
|
||||
@ -34,6 +15,13 @@ then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
zig build --fetch
|
||||
zig fetch git+https://github.com/zigimg/zigimg#3a667bdb3d7f0955a5a51c8468eac83210c1439e
|
||||
zig fetch git+https://github.com/mitchellh/libxev#f6a672a78436d8efee1aa847a43a900ad773618b
|
||||
# Go through each line of our build.zig.zon.txt and fetch it.
|
||||
SCRIPT_PATH="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
ZON_TXT_FILE="$SCRIPT_PATH/../../build.zig.zon.txt"
|
||||
while IFS= read -r url; do
|
||||
echo "Fetching: $url"
|
||||
zig fetch "$url" >/dev/null 2>&1 || {
|
||||
echo "Failed to fetch: $url" >&2
|
||||
exit 1
|
||||
}
|
||||
done < "$ZON_TXT_FILE"
|
||||
|
30
nix/build-support/update-mirror.sh
Executable 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
|
@ -14,6 +14,7 @@
|
||||
python3,
|
||||
qemu,
|
||||
scdoc,
|
||||
snapcraft,
|
||||
valgrind,
|
||||
#, vulkan-loader # unused
|
||||
vttest,
|
||||
@ -30,7 +31,9 @@
|
||||
glib,
|
||||
glslang,
|
||||
gtk4,
|
||||
gobject-introspection,
|
||||
libadwaita,
|
||||
blueprint-compiler,
|
||||
adwaita-icon-theme,
|
||||
hicolor-icon-theme,
|
||||
harfbuzz,
|
||||
@ -47,6 +50,7 @@
|
||||
simdutf,
|
||||
zlib,
|
||||
alejandra,
|
||||
jq,
|
||||
minisign,
|
||||
pandoc,
|
||||
hyperfine,
|
||||
@ -54,6 +58,8 @@
|
||||
wayland,
|
||||
wayland-scanner,
|
||||
wayland-protocols,
|
||||
zig2nix,
|
||||
system,
|
||||
}: let
|
||||
# See package.nix. Keep in sync.
|
||||
rpathLibs =
|
||||
@ -83,6 +89,7 @@
|
||||
libadwaita
|
||||
gtk4
|
||||
glib
|
||||
gobject-introspection
|
||||
wayland
|
||||
];
|
||||
in
|
||||
@ -92,6 +99,7 @@ in
|
||||
packages =
|
||||
[
|
||||
# For builds
|
||||
jq
|
||||
llvmPackages_latest.llvm
|
||||
minisign
|
||||
ncurses
|
||||
@ -100,6 +108,7 @@ in
|
||||
scdoc
|
||||
zig
|
||||
zip
|
||||
zig2nix.packages.${system}.zon2nix
|
||||
|
||||
# For web and wasm stuff
|
||||
nodejs
|
||||
@ -129,6 +138,7 @@ in
|
||||
qemu
|
||||
|
||||
gdb
|
||||
snapcraft
|
||||
valgrind
|
||||
wraptest
|
||||
|
||||
@ -154,9 +164,11 @@ in
|
||||
libXrandr
|
||||
|
||||
# Only needed for GTK builds
|
||||
blueprint-compiler
|
||||
libadwaita
|
||||
gtk4
|
||||
glib
|
||||
gobject-introspection
|
||||
wayland
|
||||
wayland-scanner
|
||||
wayland-protocols
|
||||
|
@ -2,6 +2,7 @@
|
||||
lib,
|
||||
stdenv,
|
||||
bzip2,
|
||||
callPackage,
|
||||
expat,
|
||||
fontconfig,
|
||||
freetype,
|
||||
@ -12,7 +13,9 @@
|
||||
libGL,
|
||||
glib,
|
||||
gtk4,
|
||||
gobject-introspection,
|
||||
libadwaita,
|
||||
blueprint-compiler,
|
||||
wrapGAppsHook4,
|
||||
gsettings-desktop-schemas,
|
||||
git,
|
||||
@ -42,6 +45,10 @@
|
||||
zig_hook = zig_0_13.hook.overrideAttrs {
|
||||
zig_default_flags = "-Dcpu=baseline -Doptimize=${optimize} --color off";
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "ghostty";
|
||||
version = "1.1.3";
|
||||
|
||||
# We limit source like this to try and reduce the amount of rebuilds as possible
|
||||
# thus we only provide the source that is needed for the build
|
||||
@ -60,62 +67,12 @@
|
||||
../vendor
|
||||
../build.zig
|
||||
../build.zig.zon
|
||||
./build-support/fetch-zig-cache.sh
|
||||
../build.zig.zon.nix
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
# 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
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "ghostty";
|
||||
version = "1.1.1";
|
||||
inherit src;
|
||||
deps = callPackage ../build.zig.zon.nix {name = "ghostty-cache-${finalAttrs.version}";};
|
||||
|
||||
nativeBuildInputs =
|
||||
[
|
||||
@ -124,7 +81,9 @@ in
|
||||
pandoc
|
||||
pkg-config
|
||||
zig_hook
|
||||
gobject-introspection
|
||||
wrapGAppsHook4
|
||||
blueprint-compiler
|
||||
]
|
||||
++ lib.optionals enableWayland [
|
||||
wayland-scanner
|
||||
@ -164,7 +123,7 @@ in
|
||||
|
||||
zigBuildFlags = [
|
||||
"--system"
|
||||
"${zigCache}/p"
|
||||
"${finalAttrs.deps}"
|
||||
"-Dversion-string=${finalAttrs.version}-${revision}-nix"
|
||||
"-Dgtk-x11=${lib.boolToString enableX11}"
|
||||
"-Dgtk-wayland=${lib.boolToString enableWayland}"
|
||||
|
@ -1,3 +1,3 @@
|
||||
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
|
||||
# more details.
|
||||
"sha256-I7uuv0MkaW3gWAw6NHci+II42OfM7NdtKh2Npw2pTis="
|
||||
"sha256-S8kS+gO17dl9LJGKL1+kgDUre+vPTmdTvXzgc585Fl8="
|
||||
|
@ -6,7 +6,8 @@
|
||||
// This should be kept in sync with the submodule in the cimgui source
|
||||
// code in ./vendor/ to be safe that they're compatible.
|
||||
.imgui = .{
|
||||
.url = "https://github.com/ocornut/imgui/archive/e391fe2e66eb1c96b1624ae8444dc64c23146ef4.tar.gz",
|
||||
// ocornut/imgui
|
||||
.url = "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
|
||||
.hash = "1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402",
|
||||
},
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
.version = "2.13.2",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
// freetype/freetype
|
||||
.freetype = .{
|
||||
.url = "https://github.com/freetype/freetype/archive/refs/tags/VER-2-13-2.tar.gz",
|
||||
.url = "https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz",
|
||||
.hash = "1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d",
|
||||
},
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
.version = "14.2.0",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
// KhronosGroup/glslang
|
||||
.glslang = .{
|
||||
.url = "https://github.com/KhronosGroup/glslang/archive/refs/tags/14.2.0.tar.gz",
|
||||
.url = "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
|
||||
.hash = "12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1",
|
||||
},
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
.version = "8.4.0",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
// harfbuzz/harfbuzz
|
||||
.harfbuzz = .{
|
||||
.url = "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/8.4.0.tar.gz",
|
||||
.url = "https://deps.files.ghostty.org/harfbuzz-1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122.tar.gz",
|
||||
.hash = "1220b8588f106c996af10249bfa092c6fb2f35fbacb1505ef477a0b04a7dd1063122",
|
||||
},
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
.version = "1.1.0",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
// google/highway
|
||||
.highway = .{
|
||||
.url = "https://github.com/google/highway/archive/refs/tags/1.1.0.tar.gz",
|
||||
.url = "https://deps.files.ghostty.org/highway-12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b.tar.gz",
|
||||
.hash = "12205c83b8311a24b1d5ae6d21640df04f4b0726e314337c043cde1432758cbe165b",
|
||||
},
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
.version = "1.6.43",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
// glennrp/libpng
|
||||
.libpng = .{
|
||||
.url = "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.43.tar.gz",
|
||||
.url = "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz",
|
||||
.hash = "1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66",
|
||||
},
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
.version = "6.9.9",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
// kkos/oniguruma
|
||||
.oniguruma = .{
|
||||
.url = "https://github.com/kkos/oniguruma/archive/refs/tags/v6.9.9.tar.gz",
|
||||
.url = "https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz",
|
||||
.hash = "1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb",
|
||||
},
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
.version = "0.7.8",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
// getsentry/sentry-native
|
||||
.sentry = .{
|
||||
.url = "https://github.com/getsentry/sentry-native/archive/refs/tags/0.7.8.tar.gz",
|
||||
.url = "https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz",
|
||||
.hash = "1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e",
|
||||
},
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
.version = "13.1.1",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
// KhronosGroup/SPIRV-Cross
|
||||
.spirv_cross = .{
|
||||
.url = "https://github.com/KhronosGroup/SPIRV-Cross/archive/476f384eb7d9e48613c45179e502a15ab95b6b49.tar.gz",
|
||||
.url = "https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz",
|
||||
.hash = "1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da",
|
||||
},
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
.version = "4.0.5",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
// nemtrif/utfcpp
|
||||
.utfcpp = .{
|
||||
.url = "https://github.com/nemtrif/utfcpp/archive/refs/tags/v4.0.5.tar.gz",
|
||||
.url = "https://deps.files.ghostty.org/utfcpp-1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641.tar.gz",
|
||||
.hash = "1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641",
|
||||
},
|
||||
|
||||
|
@ -2,13 +2,15 @@
|
||||
.name = "wuffs",
|
||||
.version = "0.0.0",
|
||||
.dependencies = .{
|
||||
// google/wuffs
|
||||
.wuffs = .{
|
||||
.url = "https://github.com/google/wuffs/archive/refs/tags/v0.4.0-alpha.9.tar.gz",
|
||||
.url = "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
|
||||
.hash = "122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd",
|
||||
},
|
||||
|
||||
// make-github-pseudonymous-again/pixels
|
||||
.pixels = .{
|
||||
.url = "git+https://github.com/make-github-pseudonymous-again/pixels?ref=main#d843c2714d32e15b48b8d7eeb480295af537f877",
|
||||
.url = "https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz",
|
||||
.hash = "12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806",
|
||||
},
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
.version = "1.3.1",
|
||||
.paths = .{""},
|
||||
.dependencies = .{
|
||||
// madler/zlib
|
||||
.zlib = .{
|
||||
.url = "https://github.com/madler/zlib/archive/refs/tags/v1.3.1.tar.gz",
|
||||
.url = "https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz",
|
||||
.hash = "1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb",
|
||||
},
|
||||
|
||||
|
51
snap/local/launcher
Executable file
@ -0,0 +1,51 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Set these to reasonable defaults if not already set
|
||||
if [ -z "$XDG_CONFIG_HOME" ]; then
|
||||
export XDG_CONFIG_HOME="$SNAP_REAL_HOME/.config"
|
||||
fi
|
||||
|
||||
if [ -z "$XDG_CACHE_HOME" ]; then
|
||||
export XDG_CACHE_HOME="$SNAP_REAL_HOME/.cache"
|
||||
fi
|
||||
|
||||
if [ -z "$XDG_DATA_HOME" ]; then
|
||||
export XDG_DATA_HOME="$SNAP_REAL_HOME/.local/share"
|
||||
fi
|
||||
|
||||
export HOME="$SNAP_REAL_HOME"
|
||||
|
||||
if [ "$SNAP_ARCH" = "amd64" ]; then
|
||||
ARCH="x86_64-linux-gnu"
|
||||
elif [ "$SNAP_ARCH" = "arm64" ]; then
|
||||
ARCH="aarch64-linux-gnu"
|
||||
else
|
||||
ARCH="$SNAP_ARCH-linux-gnu"
|
||||
fi
|
||||
|
||||
export LD_LIBRARY_PATH=${SNAP}/usr/lib/${ARCH}:${SNAP}/usr/lib/${ARCH}/vdpau:${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}
|
||||
export LIBGL_DRIVERS_PATH=${LIBGL_DRIVERS_PATH:+$LIBGL_DRIVERS_PATH:}${SNAP}/usr/lib/${ARCH}/dri/
|
||||
export LIBVA_DRIVERS_PATH=${LIBVA_DRIVERS_PATH:+$LIBVA_DRIVERS_PATH:}${SNAP}/usr/lib/${ARCH}/dri/
|
||||
export __EGL_VENDOR_LIBRARY_DIRS=${__EGL_VENDOR_LIBRARY_DIRS:+$__EGL_VENDOR_LIBRARY_DIRS:}${SNAP}/usr/share/glvnd/egl_vendor.d
|
||||
export __EGL_EXTERNAL_PLATFORM_CONFIG_DIRS=${__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS:+$__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS:}${SNAP}/usr/share/egl/egl_external_platform.d
|
||||
export DRIRC_CONFIGDIR=${SNAP}/usr/share/drirc.d
|
||||
export VK_LAYER_PATH=${VK_LAYER_PATH:+$VK_LAYER_PATH:}${SNAP}/usr/share/vulkan/implicit_layer.d/:${SNAP}/usr/share/vulkan/explicit_layer.d/
|
||||
export XDG_DATA_DIRS=${XDG_DATA_DIRS:+$XDG_DATA_DIRS:}${SNAP}/usr/share
|
||||
export XLOCALEDIR="${SNAP}/usr/share/X11/locale"
|
||||
export GDK_PIXBUF_MODULEDIR="$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/2.10.0/loaders"
|
||||
export GTK_PATH="$SNAP/usr/lib/$ARCH/gtk-4.0"
|
||||
|
||||
if [ "${__NV_PRIME_RENDER_OFFLOAD:-}" != 1 ]; then
|
||||
# Prevent picking VA-API (Intel/AMD) over NVIDIA VDPAU
|
||||
# https://download.nvidia.com/XFree86/Linux-x86_64/510.54/README/primerenderoffload.html#configureapplications
|
||||
export LIBVA_DRIVERS_PATH
|
||||
fi
|
||||
|
||||
# Unset all SNAP specific environment variables to keep them from leaking
|
||||
# into other snaps that might get executed from within the shell
|
||||
for var in $(printenv | grep SNAP_ | cut -d= -f1); do
|
||||
unset $var
|
||||
done
|
||||
|
||||
exec "$@"
|
139
snap/snapcraft.yaml
Normal file
@ -0,0 +1,139 @@
|
||||
name: ghostty
|
||||
base: core24
|
||||
summary: A terminal emulator
|
||||
description: |
|
||||
Ghostty is a fast, feature-rich, and cross-platform terminal emulator that
|
||||
uses platform-native UI and GPU acceleration.
|
||||
grade: stable
|
||||
confinement: classic
|
||||
contact: https://github.com/ghostty-org/ghostty/discussions
|
||||
issues: https://github.com/ghostty-org/ghostty/issues
|
||||
website: https://ghostty.org
|
||||
license: MIT
|
||||
icon: images/icons/icon_512.png
|
||||
adopt-info: ghostty
|
||||
|
||||
platforms:
|
||||
amd64:
|
||||
arm64:
|
||||
|
||||
apps:
|
||||
ghostty:
|
||||
command: bin/ghostty
|
||||
command-chain: [bin/launcher]
|
||||
completer: share/bash-completion/completions/ghostty.bash
|
||||
desktop: share/applications/com.mitchellh.ghostty.desktop
|
||||
#refresh-mode: ignore-running # Store rejects this, needs fix in review-tools
|
||||
environment:
|
||||
PATH: /snap/ghostty/current/bin:/snap/ghostty/current/usr/bin:$PATH
|
||||
LC_ALL: C.UTF-8
|
||||
GHOSTTY_RESOURCES_DIR: /snap/ghostty/current/share/ghostty
|
||||
|
||||
parts:
|
||||
launcher:
|
||||
plugin: dump
|
||||
source: snap/local
|
||||
source-type: local
|
||||
organize:
|
||||
launcher: bin/
|
||||
|
||||
zig:
|
||||
plugin: nil
|
||||
build-packages:
|
||||
- curl
|
||||
override-pull: |
|
||||
set -ex
|
||||
case "$CRAFT_ARCH_BUILD_FOR" in
|
||||
amd64) arch=x86_64 ;;
|
||||
arm64) arch=aarch64 ;;
|
||||
*) arch="" ;;
|
||||
esac
|
||||
|
||||
rm -rf $CRAFT_PART_SRC/*
|
||||
|
||||
if [[ -n $arch ]]; then
|
||||
curl -LO --retry-connrefused --retry 10 https://ziglang.org/download/0.13.0/zig-linux-$arch-0.13.0.tar.xz
|
||||
else
|
||||
echo "Unsupported arch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tar xf zig-lin*xz
|
||||
rm -f *xz
|
||||
mv zig-linux*/* .
|
||||
prime:
|
||||
- -*
|
||||
|
||||
ghostty:
|
||||
source: .
|
||||
after: [zig]
|
||||
plugin: nil
|
||||
build-attributes: [enable-patchelf]
|
||||
build-packages:
|
||||
- libgtk-4-dev
|
||||
- libadwaita-1-dev
|
||||
- git
|
||||
- patchelf
|
||||
override-build: |
|
||||
craftctl set version=$(git describe --abbrev=8)
|
||||
$CRAFT_PART_SRC/../../zig/src/zig build -Dpatch-rpath=\$ORIGIN/../usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/core24/current/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR -Doptimize=ReleaseFast
|
||||
cp -rp zig-out/* $CRAFT_PART_INSTALL/
|
||||
sed -i 's|Icon=com.mitchellh.ghostty|Icon=/snap/ghostty/current/share/icons/hicolor/512x512/apps/com.mitchellh.ghostty.png|g' $CRAFT_PART_INSTALL/share/applications/com.mitchellh.ghostty.desktop
|
||||
|
||||
libs:
|
||||
plugin: nil
|
||||
build-attributes: [enable-patchelf]
|
||||
stage-packages:
|
||||
- libadwaita-1-0
|
||||
- libglib2.0-0t64
|
||||
- libgtk-4-1
|
||||
- libgtk-4-media-gstreamer
|
||||
- ibus-gtk4
|
||||
- libpciaccess0
|
||||
- libtinfo6
|
||||
- libedit2
|
||||
- libelf1t64
|
||||
- libsensors5
|
||||
- libllvm17
|
||||
- libunistring5
|
||||
- librsvg2-2
|
||||
- on amd64:
|
||||
[
|
||||
i965-va-driver,
|
||||
libdrm-intel1,
|
||||
libdrm-nouveau2,
|
||||
libdrm-amdgpu1,
|
||||
libdrm-radeon1,
|
||||
]
|
||||
stage:
|
||||
# The libraries in dri need no-patchelf, so they come from the mesa-unpatched part
|
||||
- -usr/lib/*/dri
|
||||
|
||||
mesa:
|
||||
plugin: nil
|
||||
build-attributes: [enable-patchelf]
|
||||
stage-packages:
|
||||
- libglu1-mesa
|
||||
- libgl1-mesa-dri
|
||||
- libegl-mesa0
|
||||
- libegl1
|
||||
- libglx-mesa0
|
||||
- mesa-libgallium
|
||||
stage:
|
||||
# The libraries in dri need no-patchelf, so they come from the mesa-unpatched part
|
||||
- usr/lib/*/*.so*
|
||||
- usr/lib/*/dri/libdril_dri.so
|
||||
- -usr/lib/*/libgallium*so
|
||||
- -usr/lib/*/dri
|
||||
|
||||
mesa-gl1-dri:
|
||||
plugin: nil
|
||||
stage-packages:
|
||||
- libgl1-mesa-dri
|
||||
build-attributes: [no-patchelf]
|
||||
stage:
|
||||
# Only the libraries in dri need to not be patched, the rest come from the mesa part
|
||||
# Otherwise snapcraft may strip the build ID and cause the driver to crash
|
||||
- usr/lib/*/libgallium*so
|
||||
- -usr/lib/*/dri/libdril_dri.so
|
||||
- usr/lib/*/dri
|
24
src/App.zig
@ -161,7 +161,7 @@ pub fn updateConfig(self: *App, rt_app: *apprt.App, config: *const Config) !void
|
||||
const applied: *const configpkg.Config = if (applied_) |*c| c else config;
|
||||
|
||||
// Notify the apprt that the app has changed configuration.
|
||||
try rt_app.performAction(
|
||||
_ = try rt_app.performAction(
|
||||
.app,
|
||||
.config_change,
|
||||
.{ .config = applied },
|
||||
@ -180,7 +180,7 @@ pub fn addSurface(
|
||||
// Since we have non-zero surfaces, we can cancel the quit timer.
|
||||
// It is up to the apprt if there is a quit timer at all and if it
|
||||
// should be canceled.
|
||||
rt_surface.app.performAction(
|
||||
_ = rt_surface.app.performAction(
|
||||
.app,
|
||||
.quit_timer,
|
||||
.stop,
|
||||
@ -214,7 +214,7 @@ pub fn deleteSurface(self: *App, rt_surface: *apprt.Surface) void {
|
||||
|
||||
// If we have no surfaces, we can start the quit timer. It is up to the
|
||||
// apprt to determine if this is necessary.
|
||||
if (self.surfaces.items.len == 0) rt_surface.app.performAction(
|
||||
if (self.surfaces.items.len == 0) _ = rt_surface.app.performAction(
|
||||
.app,
|
||||
.quit_timer,
|
||||
.start,
|
||||
@ -294,7 +294,7 @@ pub fn newWindow(self: *App, rt_app: *apprt.App, msg: Message.NewWindow) !void {
|
||||
break :target .app;
|
||||
};
|
||||
|
||||
try rt_app.performAction(
|
||||
_ = try rt_app.performAction(
|
||||
target,
|
||||
.new_window,
|
||||
{},
|
||||
@ -419,7 +419,7 @@ pub fn colorSchemeEvent(
|
||||
|
||||
// Request our configuration be reloaded because the new scheme may
|
||||
// impact the colors of the app.
|
||||
try rt_app.performAction(
|
||||
_ = try rt_app.performAction(
|
||||
.app,
|
||||
.reload_config,
|
||||
.{ .soft = true },
|
||||
@ -437,13 +437,13 @@ pub fn performAction(
|
||||
switch (action) {
|
||||
.unbind => unreachable,
|
||||
.ignore => {},
|
||||
.quit => try rt_app.performAction(.app, .quit, {}),
|
||||
.new_window => try self.newWindow(rt_app, .{ .parent = null }),
|
||||
.open_config => try rt_app.performAction(.app, .open_config, {}),
|
||||
.reload_config => try rt_app.performAction(.app, .reload_config, .{}),
|
||||
.close_all_windows => try rt_app.performAction(.app, .close_all_windows, {}),
|
||||
.toggle_quick_terminal => try rt_app.performAction(.app, .toggle_quick_terminal, {}),
|
||||
.toggle_visibility => try rt_app.performAction(.app, .toggle_visibility, {}),
|
||||
.quit => _ = try rt_app.performAction(.app, .quit, {}),
|
||||
.new_window => _ = try self.newWindow(rt_app, .{ .parent = null }),
|
||||
.open_config => _ = try rt_app.performAction(.app, .open_config, {}),
|
||||
.reload_config => _ = try rt_app.performAction(.app, .reload_config, .{}),
|
||||
.close_all_windows => _ = try rt_app.performAction(.app, .close_all_windows, {}),
|
||||
.toggle_quick_terminal => _ = try rt_app.performAction(.app, .toggle_quick_terminal, {}),
|
||||
.toggle_visibility => _ = try rt_app.performAction(.app, .toggle_visibility, {}),
|
||||
}
|
||||
}
|
||||
|
||||
|
109
src/Surface.zig
@ -519,9 +519,19 @@ pub fn init(
|
||||
// This separate block ({}) is important because our errdefers must
|
||||
// be scoped here to be valid.
|
||||
{
|
||||
var env = rt_surface.defaultTermioEnv() catch |err| env: {
|
||||
// If an error occurs, we don't want to block surface startup.
|
||||
log.warn("error getting env map for surface err={}", .{err});
|
||||
break :env internal_os.getEnvMap(alloc) catch
|
||||
std.process.EnvMap.init(alloc);
|
||||
};
|
||||
errdefer env.deinit();
|
||||
|
||||
// Initialize our IO backend
|
||||
var io_exec = try termio.Exec.init(alloc, .{
|
||||
.command = command,
|
||||
.env = env,
|
||||
.env_override = config.env,
|
||||
.shell_integration = config.@"shell-integration",
|
||||
.shell_integration_features = config.@"shell-integration-features",
|
||||
.working_directory = config.@"working-directory",
|
||||
@ -561,7 +571,7 @@ pub fn init(
|
||||
errdefer self.io.deinit();
|
||||
|
||||
// Report initial cell size on surface creation
|
||||
try rt_app.performAction(
|
||||
_ = try rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.cell_size,
|
||||
.{ .width = size.cell.width, .height = size.cell.height },
|
||||
@ -573,7 +583,7 @@ pub fn init(
|
||||
const min_window_width_cells: u32 = 10;
|
||||
const min_window_height_cells: u32 = 4;
|
||||
|
||||
try rt_app.performAction(
|
||||
_ = try rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.size_limit,
|
||||
.{
|
||||
@ -637,7 +647,7 @@ pub fn init(
|
||||
size.padding.top +
|
||||
size.padding.bottom;
|
||||
|
||||
rt_app.performAction(
|
||||
_ = rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.initial_size,
|
||||
.{ .width = final_width, .height = final_height },
|
||||
@ -649,7 +659,7 @@ pub fn init(
|
||||
}
|
||||
|
||||
if (config.title) |title| {
|
||||
try rt_app.performAction(
|
||||
_ = try rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.set_title,
|
||||
.{ .title = title },
|
||||
@ -670,7 +680,7 @@ pub fn init(
|
||||
break :xdg;
|
||||
};
|
||||
defer alloc.free(title);
|
||||
try rt_app.performAction(
|
||||
_ = try rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.set_title,
|
||||
.{ .title = title },
|
||||
@ -823,7 +833,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
||||
// We know that our title should end in 0.
|
||||
const slice = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(v)), 0);
|
||||
log.debug("changing title \"{s}\"", .{slice});
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.set_title,
|
||||
.{ .title = slice },
|
||||
@ -859,7 +869,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
||||
.color_change => |change| {
|
||||
// Notify our apprt, but don't send a mode 2031 DSR report
|
||||
// because VT sequences were used to change the color.
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.color_change,
|
||||
.{
|
||||
@ -878,7 +888,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
||||
|
||||
.set_mouse_shape => |shape| {
|
||||
log.debug("changing mouse shape: {}", .{shape});
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_shape,
|
||||
shape,
|
||||
@ -910,7 +920,7 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
||||
const str = try self.alloc.dupeZ(u8, w.slice());
|
||||
defer self.alloc.free(str);
|
||||
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.pwd,
|
||||
.{ .pwd = str },
|
||||
@ -961,7 +971,7 @@ fn passwordInput(self: *Surface, v: bool) !void {
|
||||
}
|
||||
|
||||
// Notify our apprt so it can do whatever it wants.
|
||||
self.rt_app.performAction(
|
||||
_ = self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.secure_input,
|
||||
if (v) .on else .off,
|
||||
@ -1050,7 +1060,7 @@ fn mouseRefreshLinks(
|
||||
self.renderer_state.mouse.point = pos_vp;
|
||||
self.mouse.over_link = true;
|
||||
self.renderer_state.terminal.screen.dirty.hyperlink_hover = true;
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_shape,
|
||||
.pointer,
|
||||
@ -1063,7 +1073,7 @@ fn mouseRefreshLinks(
|
||||
.trim = false,
|
||||
});
|
||||
defer self.alloc.free(str);
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_over_link,
|
||||
.{ .url = str },
|
||||
@ -1077,7 +1087,7 @@ fn mouseRefreshLinks(
|
||||
log.warn("failed to get URI for OSC8 hyperlink", .{});
|
||||
break :link;
|
||||
};
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_over_link,
|
||||
.{ .url = uri },
|
||||
@ -1087,12 +1097,12 @@ fn mouseRefreshLinks(
|
||||
|
||||
try self.queueRender();
|
||||
} else if (over_link) {
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_shape,
|
||||
self.io.terminal.mouse_shape,
|
||||
);
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_over_link,
|
||||
.{ .url = "" },
|
||||
@ -1104,7 +1114,7 @@ fn mouseRefreshLinks(
|
||||
/// Called when our renderer health state changes.
|
||||
fn updateRendererHealth(self: *Surface, health: renderer.Health) void {
|
||||
log.warn("renderer health status change status={}", .{health});
|
||||
self.rt_app.performAction(
|
||||
_ = self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.renderer_health,
|
||||
health,
|
||||
@ -1116,7 +1126,7 @@ fn updateRendererHealth(self: *Surface, health: renderer.Health) void {
|
||||
/// This should be called anytime `config_conditional_state` changes
|
||||
/// so that the apprt can reload the configuration.
|
||||
fn notifyConfigConditionalState(self: *Surface) void {
|
||||
self.rt_app.performAction(
|
||||
_ = self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.reload_config,
|
||||
.{ .soft = true },
|
||||
@ -1196,14 +1206,14 @@ pub fn updateConfig(
|
||||
|
||||
// If we have a title set then we update our window to have the
|
||||
// newly configured title.
|
||||
if (config.title) |title| try self.rt_app.performAction(
|
||||
if (config.title) |title| _ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.set_title,
|
||||
.{ .title = title },
|
||||
);
|
||||
|
||||
// Notify the window
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.config_change,
|
||||
.{ .config = config },
|
||||
@ -1470,7 +1480,7 @@ fn setCellSize(self: *Surface, size: renderer.CellSize) !void {
|
||||
self.io.queueMessage(.{ .resize = self.size }, .unlocked);
|
||||
|
||||
// Notify the window
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.cell_size,
|
||||
.{ .width = size.width, .height = size.height },
|
||||
@ -1766,12 +1776,12 @@ pub fn keyCallback(
|
||||
};
|
||||
} else if (self.io.terminal.flags.mouse_event != .none and !self.mouse.mods.shift) {
|
||||
// If we have mouse reports on and we don't have shift pressed, we reset state
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_shape,
|
||||
self.io.terminal.mouse_shape,
|
||||
);
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_over_link,
|
||||
.{ .url = "" },
|
||||
@ -1789,7 +1799,7 @@ pub fn keyCallback(
|
||||
.mods = self.mouse.mods,
|
||||
.over_link = self.mouse.over_link,
|
||||
.hidden = self.mouse.hidden,
|
||||
}).keyToMouseShape()) |shape| try self.rt_app.performAction(
|
||||
}).keyToMouseShape()) |shape| _ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_shape,
|
||||
shape,
|
||||
@ -1914,7 +1924,7 @@ fn maybeHandleBinding(
|
||||
}
|
||||
|
||||
// Start or continue our key sequence
|
||||
self.rt_app.performAction(
|
||||
_ = self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.key_sequence,
|
||||
.{ .trigger = entry.key_ptr.* },
|
||||
@ -2023,7 +2033,7 @@ fn endKeySequence(
|
||||
mem: KeySequenceMemory,
|
||||
) void {
|
||||
// Notify apprt key sequence ended
|
||||
self.rt_app.performAction(
|
||||
_ = self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.key_sequence,
|
||||
.end,
|
||||
@ -3359,12 +3369,12 @@ pub fn cursorPosCallback(
|
||||
self.mouse.link_point = null;
|
||||
if (self.mouse.over_link) {
|
||||
self.mouse.over_link = false;
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_shape,
|
||||
self.io.terminal.mouse_shape,
|
||||
);
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_over_link,
|
||||
.{ .url = "" },
|
||||
@ -3790,7 +3800,7 @@ fn scrollToBottom(self: *Surface) !void {
|
||||
fn hideMouse(self: *Surface) void {
|
||||
if (self.mouse.hidden) return;
|
||||
self.mouse.hidden = true;
|
||||
self.rt_app.performAction(
|
||||
_ = self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_visibility,
|
||||
.hidden,
|
||||
@ -3802,7 +3812,7 @@ fn hideMouse(self: *Surface) void {
|
||||
fn showMouse(self: *Surface) void {
|
||||
if (!self.mouse.hidden) return;
|
||||
self.mouse.hidden = false;
|
||||
self.rt_app.performAction(
|
||||
_ = self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_visibility,
|
||||
.visible,
|
||||
@ -4015,6 +4025,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
try self.setFontSize(size);
|
||||
},
|
||||
|
||||
.prompt_surface_title => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.prompt_title,
|
||||
{},
|
||||
),
|
||||
|
||||
.clear_screen => {
|
||||
// This is a duplicate of some of the logic in termio.clearScreen
|
||||
// but we need to do this here so we can know the answer before
|
||||
@ -4093,13 +4109,13 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
v,
|
||||
),
|
||||
|
||||
.new_tab => try self.rt_app.performAction(
|
||||
.new_tab => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.new_tab,
|
||||
{},
|
||||
),
|
||||
|
||||
.close_tab => try self.rt_app.performAction(
|
||||
.close_tab => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.close_tab,
|
||||
{},
|
||||
@ -4109,7 +4125,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
.next_tab,
|
||||
.last_tab,
|
||||
.goto_tab,
|
||||
=> |v, tag| try self.rt_app.performAction(
|
||||
=> |v, tag| return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.goto_tab,
|
||||
switch (tag) {
|
||||
@ -4121,13 +4137,13 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
},
|
||||
),
|
||||
|
||||
.move_tab => |position| try self.rt_app.performAction(
|
||||
.move_tab => |position| return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.move_tab,
|
||||
.{ .amount = position },
|
||||
),
|
||||
|
||||
.new_split => |direction| try self.rt_app.performAction(
|
||||
.new_split => |direction| return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.new_split,
|
||||
switch (direction) {
|
||||
@ -4142,7 +4158,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
},
|
||||
),
|
||||
|
||||
.goto_split => |direction| try self.rt_app.performAction(
|
||||
.goto_split => |direction| return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.goto_split,
|
||||
switch (direction) {
|
||||
@ -4153,7 +4169,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
},
|
||||
),
|
||||
|
||||
.resize_split => |value| try self.rt_app.performAction(
|
||||
.resize_split => |value| return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.resize_split,
|
||||
.{
|
||||
@ -4167,47 +4183,48 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
},
|
||||
),
|
||||
|
||||
.equalize_splits => try self.rt_app.performAction(
|
||||
.equalize_splits => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.equalize_splits,
|
||||
{},
|
||||
),
|
||||
|
||||
.toggle_split_zoom => try self.rt_app.performAction(
|
||||
.toggle_split_zoom => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.toggle_split_zoom,
|
||||
{},
|
||||
),
|
||||
|
||||
.toggle_maximize => try self.rt_app.performAction(
|
||||
.toggle_maximize => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.toggle_maximize,
|
||||
{},
|
||||
),
|
||||
|
||||
.toggle_fullscreen => try self.rt_app.performAction(
|
||||
.toggle_fullscreen => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.toggle_fullscreen,
|
||||
switch (self.config.macos_non_native_fullscreen) {
|
||||
.false => .native,
|
||||
.true => .macos_non_native,
|
||||
.@"visible-menu" => .macos_non_native_visible_menu,
|
||||
.@"padded-notch" => .macos_non_native_padded_notch,
|
||||
},
|
||||
),
|
||||
|
||||
.toggle_window_decorations => try self.rt_app.performAction(
|
||||
.toggle_window_decorations => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.toggle_window_decorations,
|
||||
{},
|
||||
),
|
||||
|
||||
.toggle_tab_overview => try self.rt_app.performAction(
|
||||
.toggle_tab_overview => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.toggle_tab_overview,
|
||||
{},
|
||||
),
|
||||
|
||||
.toggle_secure_input => try self.rt_app.performAction(
|
||||
.toggle_secure_input => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.secure_input,
|
||||
.toggle,
|
||||
@ -4221,7 +4238,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
}
|
||||
},
|
||||
|
||||
.inspector => |mode| try self.rt_app.performAction(
|
||||
.inspector => |mode| return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.inspector,
|
||||
switch (mode) {
|
||||
@ -4668,7 +4685,7 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const
|
||||
|
||||
self.app.last_notification_time = now;
|
||||
self.app.last_notification_digest = new_digest;
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.desktop_notification,
|
||||
.{
|
||||
@ -4688,7 +4705,7 @@ fn crashThreadState(self: *Surface) crash.sentry.ThreadState {
|
||||
/// Tell the surface to present itself to the user. This may involve raising the
|
||||
/// window and switching tabs.
|
||||
fn presentSurface(self: *Surface) !void {
|
||||
try self.rt_app.performAction(
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.present_terminal,
|
||||
{},
|
||||
|
@ -158,9 +158,13 @@ pub const Action = union(Key) {
|
||||
/// Show a desktop notification.
|
||||
desktop_notification: DesktopNotification,
|
||||
|
||||
/// Set the title of the target.
|
||||
/// Set the title of the target to the requested value.
|
||||
set_title: SetTitle,
|
||||
|
||||
/// Set the title of the target to a prompted value. It is up to
|
||||
/// the apprt to prompt.
|
||||
prompt_title,
|
||||
|
||||
/// The current working directory has changed for the target terminal.
|
||||
pwd: Pwd,
|
||||
|
||||
@ -254,6 +258,7 @@ pub const Action = union(Key) {
|
||||
render_inspector,
|
||||
desktop_notification,
|
||||
set_title,
|
||||
prompt_title,
|
||||
pwd,
|
||||
mouse_shape,
|
||||
mouse_visibility,
|
||||
@ -385,6 +390,7 @@ pub const Fullscreen = enum(c_int) {
|
||||
/// window. This is much faster to enter and exit than the native mode.
|
||||
macos_non_native,
|
||||
macos_non_native_visible_menu,
|
||||
macos_non_native_padded_notch,
|
||||
};
|
||||
|
||||
pub const SecureInput = enum(c_int) {
|
||||
|
@ -12,6 +12,7 @@ const objc = @import("objc");
|
||||
const apprt = @import("../apprt.zig");
|
||||
const font = @import("../font/main.zig");
|
||||
const input = @import("../input.zig");
|
||||
const internal_os = @import("../os/main.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
const terminal = @import("../terminal/main.zig");
|
||||
const CoreApp = @import("../App.zig");
|
||||
@ -45,7 +46,7 @@ pub const App = struct {
|
||||
wakeup: *const fn (AppUD) callconv(.C) void,
|
||||
|
||||
/// Callback called to handle an action.
|
||||
action: *const fn (*App, apprt.Target.C, apprt.Action.C) callconv(.C) void,
|
||||
action: *const fn (*App, apprt.Target.C, apprt.Action.C) callconv(.C) bool,
|
||||
|
||||
/// Read the clipboard value. The return value must be preserved
|
||||
/// by the host until the next call. If there is no valid clipboard
|
||||
@ -181,14 +182,9 @@ pub const App = struct {
|
||||
if (strip) translate_mods.alt = false;
|
||||
}
|
||||
|
||||
// On macOS we strip ctrl because UCKeyTranslate
|
||||
// converts to the masked values (i.e. ctrl+c becomes 3)
|
||||
// and we don't want that behavior.
|
||||
//
|
||||
// We also strip super because its not used for translation
|
||||
// on macos and it results in a bad translation.
|
||||
// We strip super on macOS because its not used for translation
|
||||
// it results in a bad translation.
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
translate_mods.ctrl = false;
|
||||
translate_mods.super = false;
|
||||
}
|
||||
|
||||
@ -228,6 +224,7 @@ pub const App = struct {
|
||||
const result: input.Keymap.Translation = if (event_text) |text| .{
|
||||
.text = text,
|
||||
.composing = event.composing,
|
||||
.mods = translate_mods,
|
||||
} else try self.keymap.translate(
|
||||
&buf,
|
||||
switch (target) {
|
||||
@ -272,16 +269,12 @@ pub const App = struct {
|
||||
// then we clear the text. We handle non-printables in the
|
||||
// key encoder manual (such as tab, ctrl+c, etc.)
|
||||
if (result.text.len == 1 and result.text[0] < 0x20) {
|
||||
break :translate .{ .composing = false, .text = "" };
|
||||
break :translate .{};
|
||||
}
|
||||
}
|
||||
|
||||
break :translate result;
|
||||
} else .{ .composing = false, .text = "" };
|
||||
|
||||
// UCKeyTranslate always consumes all mods, so if we have any output
|
||||
// then we've consumed our translate mods.
|
||||
const consumed_mods: input.Mods = if (result.text.len > 0) translate_mods else .{};
|
||||
} else .{};
|
||||
|
||||
// We need to always do a translation with no modifiers at all in
|
||||
// order to get the "unshifted_codepoint" for the key event.
|
||||
@ -353,7 +346,7 @@ pub const App = struct {
|
||||
.key = key,
|
||||
.physical_key = physical_key,
|
||||
.mods = mods,
|
||||
.consumed_mods = consumed_mods,
|
||||
.consumed_mods = result.mods,
|
||||
.composing = result.composing,
|
||||
.utf8 = result.text,
|
||||
.unshifted_codepoint = unshifted_codepoint,
|
||||
@ -477,13 +470,14 @@ pub const App = struct {
|
||||
surface.queueInspectorRender();
|
||||
}
|
||||
|
||||
/// Perform a given action.
|
||||
/// Perform a given action. Returns `true` if the action was able to be
|
||||
/// performed, `false` otherwise.
|
||||
pub fn performAction(
|
||||
self: *App,
|
||||
target: apprt.Target,
|
||||
comptime action: apprt.Action.Key,
|
||||
value: apprt.Action.Value(action),
|
||||
) !void {
|
||||
) !bool {
|
||||
// Special case certain actions before they are sent to the
|
||||
// embedded apprt.
|
||||
self.performPreAction(target, action, value);
|
||||
@ -493,7 +487,7 @@ pub const App = struct {
|
||||
action,
|
||||
value,
|
||||
});
|
||||
self.opts.action(
|
||||
return self.opts.action(
|
||||
self,
|
||||
target.cval(),
|
||||
@unionInit(apprt.Action, @tagName(action), value).cval(),
|
||||
@ -1005,7 +999,7 @@ pub const Surface = struct {
|
||||
}
|
||||
|
||||
fn queueInspectorRender(self: *Surface) void {
|
||||
self.app.performAction(
|
||||
_ = self.app.performAction(
|
||||
.{ .surface = &self.core_surface },
|
||||
.render_inspector,
|
||||
{},
|
||||
@ -1026,6 +1020,30 @@ pub const Surface = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn defaultTermioEnv(self: *const Surface) !std.process.EnvMap {
|
||||
const alloc = self.app.core_app.alloc;
|
||||
var env = try internal_os.getEnvMap(alloc);
|
||||
errdefer env.deinit();
|
||||
|
||||
if (comptime builtin.target.isDarwin()) {
|
||||
if (env.get("__XCODE_BUILT_PRODUCTS_DIR_PATHS") != null) {
|
||||
env.remove("__XCODE_BUILT_PRODUCTS_DIR_PATHS");
|
||||
env.remove("__XPC_DYLD_LIBRARY_PATH");
|
||||
env.remove("DYLD_FRAMEWORK_PATH");
|
||||
env.remove("DYLD_INSERT_LIBRARIES");
|
||||
env.remove("DYLD_LIBRARY_PATH");
|
||||
env.remove("LD_LIBRARY_PATH");
|
||||
env.remove("SECURITYSESSIONID");
|
||||
env.remove("XPC_SERVICE_NAME");
|
||||
}
|
||||
|
||||
// Remove this so that running `ghostty` within Ghostty works.
|
||||
env.remove("GHOSTTY_MAC_APP");
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
/// The cursor position from the host directly is in screen coordinates but
|
||||
/// all our interface works in pixels.
|
||||
fn cursorPosToPixels(self: *const Surface, pos: apprt.CursorPos) !apprt.CursorPos {
|
||||
@ -1432,7 +1450,7 @@ pub const CAPI = struct {
|
||||
|
||||
/// Open the configuration.
|
||||
export fn ghostty_app_open_config(v: *App) void {
|
||||
v.performAction(.app, .open_config, {}) catch |err| {
|
||||
_ = v.performAction(.app, .open_config, {}) catch |err| {
|
||||
log.err("error reloading config err={}", .{err});
|
||||
return;
|
||||
};
|
||||
@ -1775,7 +1793,7 @@ pub const CAPI = struct {
|
||||
|
||||
/// Request that the surface split in the given direction.
|
||||
export fn ghostty_surface_split(ptr: *Surface, direction: apprt.action.SplitDirection) void {
|
||||
ptr.app.performAction(
|
||||
_ = ptr.app.performAction(
|
||||
.{ .surface = &ptr.core_surface },
|
||||
.new_split,
|
||||
direction,
|
||||
@ -1790,7 +1808,7 @@ pub const CAPI = struct {
|
||||
ptr: *Surface,
|
||||
direction: apprt.action.GotoSplit,
|
||||
) void {
|
||||
ptr.app.performAction(
|
||||
_ = ptr.app.performAction(
|
||||
.{ .surface = &ptr.core_surface },
|
||||
.goto_split,
|
||||
direction,
|
||||
@ -1809,7 +1827,7 @@ pub const CAPI = struct {
|
||||
direction: apprt.action.ResizeSplit.Direction,
|
||||
amount: u16,
|
||||
) void {
|
||||
ptr.app.performAction(
|
||||
_ = ptr.app.performAction(
|
||||
.{ .surface = &ptr.core_surface },
|
||||
.resize_split,
|
||||
.{ .direction = direction, .amount = amount },
|
||||
@ -1821,7 +1839,7 @@ pub const CAPI = struct {
|
||||
|
||||
/// Equalize the size of all splits in the current window.
|
||||
export fn ghostty_surface_split_equalize(ptr: *Surface) void {
|
||||
ptr.app.performAction(
|
||||
_ = ptr.app.performAction(
|
||||
.{ .surface = &ptr.core_surface },
|
||||
.equalize_splits,
|
||||
{},
|
||||
|
@ -147,13 +147,14 @@ pub const App = struct {
|
||||
glfw.postEmptyEvent();
|
||||
}
|
||||
|
||||
/// Perform a given action.
|
||||
/// Perform a given action. Returns `true` if the action was able to be
|
||||
/// performed, `false` otherwise.
|
||||
pub fn performAction(
|
||||
self: *App,
|
||||
target: apprt.Target,
|
||||
comptime action: apprt.Action.Key,
|
||||
value: apprt.Action.Value(action),
|
||||
) !void {
|
||||
) !bool {
|
||||
switch (action) {
|
||||
.quit => self.quit = true,
|
||||
|
||||
@ -238,8 +239,14 @@ pub const App = struct {
|
||||
.pwd,
|
||||
.config_change,
|
||||
.toggle_maximize,
|
||||
=> log.info("unimplemented action={}", .{action}),
|
||||
.prompt_title,
|
||||
=> {
|
||||
log.info("unimplemented action={}", .{action});
|
||||
return false;
|
||||
},
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Reload the configuration. This should return the new configuration.
|
||||
@ -874,6 +881,10 @@ pub const Surface = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn defaultTermioEnv(self: *Surface) !std.process.EnvMap {
|
||||
return try internal_os.getEnvMap(self.app.app.alloc);
|
||||
}
|
||||
|
||||
fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
|
||||
_ = width;
|
||||
_ = height;
|
||||
|
@ -25,7 +25,6 @@ const Config = configpkg.Config;
|
||||
const CoreApp = @import("../../App.zig");
|
||||
const CoreSurface = @import("../../Surface.zig");
|
||||
|
||||
const adwaita = @import("adwaita.zig");
|
||||
const cgroup = @import("cgroup.zig");
|
||||
const Surface = @import("Surface.zig");
|
||||
const Window = @import("Window.zig");
|
||||
@ -109,6 +108,14 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||
c.gtk_get_micro_version(),
|
||||
});
|
||||
|
||||
// log the adwaita version
|
||||
log.info("libadwaita version build={s} runtime={}.{}.{}", .{
|
||||
c.ADW_VERSION_S,
|
||||
c.adw_get_major_version(),
|
||||
c.adw_get_minor_version(),
|
||||
c.adw_get_micro_version(),
|
||||
});
|
||||
|
||||
// Load our configuration
|
||||
var config = try Config.load(core_app.alloc);
|
||||
errdefer config.deinit();
|
||||
@ -236,7 +243,8 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||
}
|
||||
}
|
||||
|
||||
c.gtk_init();
|
||||
c.adw_init();
|
||||
|
||||
const display: *c.GdkDisplay = c.gdk_display_get_default() orelse {
|
||||
// I'm unsure of any scenario where this happens. Because we don't
|
||||
// want to litter null checks everywhere, we just exit here.
|
||||
@ -244,16 +252,6 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||
std.posix.exit(1);
|
||||
};
|
||||
|
||||
// If we're using libadwaita, log the version
|
||||
if (adwaita.enabled(&config)) {
|
||||
log.info("libadwaita version build={s} runtime={}.{}.{}", .{
|
||||
c.ADW_VERSION_S,
|
||||
c.adw_get_major_version(),
|
||||
c.adw_get_minor_version(),
|
||||
c.adw_get_micro_version(),
|
||||
});
|
||||
}
|
||||
|
||||
// The "none" cursor is used for hiding the cursor
|
||||
const cursor_none = c.gdk_cursor_new_from_name("none", null);
|
||||
errdefer if (cursor_none) |cursor| c.g_object_unref(cursor);
|
||||
@ -288,80 +286,18 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||
};
|
||||
|
||||
// Create our GTK Application which encapsulates our process.
|
||||
const app: *c.GtkApplication = app: {
|
||||
log.debug("creating GTK application id={s} single-instance={} adwaita={}", .{
|
||||
log.debug("creating GTK application id={s} single-instance={}", .{
|
||||
app_id,
|
||||
single_instance,
|
||||
adwaita,
|
||||
});
|
||||
|
||||
// If not libadwaita, create a standard GTK application.
|
||||
if ((comptime !adwaita.versionAtLeast(0, 0, 0)) or
|
||||
!adwaita.enabled(&config))
|
||||
{
|
||||
{
|
||||
const provider = c.gtk_css_provider_new();
|
||||
defer c.g_object_unref(provider);
|
||||
switch (config.@"window-theme") {
|
||||
.system, .light => {},
|
||||
.dark => {
|
||||
const settings = c.gtk_settings_get_default();
|
||||
c.g_object_set(@ptrCast(@alignCast(settings)), "gtk-application-prefer-dark-theme", true, @as([*c]const u8, null));
|
||||
|
||||
c.gtk_css_provider_load_from_resource(
|
||||
provider,
|
||||
"/com/mitchellh/ghostty/style-dark.css",
|
||||
);
|
||||
c.gtk_style_context_add_provider_for_display(
|
||||
display,
|
||||
@ptrCast(provider),
|
||||
c.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 2,
|
||||
);
|
||||
},
|
||||
.auto, .ghostty => {
|
||||
const lum = config.background.toTerminalRGB().perceivedLuminance();
|
||||
if (lum <= 0.5) {
|
||||
const settings = c.gtk_settings_get_default();
|
||||
c.g_object_set(@ptrCast(@alignCast(settings)), "gtk-application-prefer-dark-theme", true, @as([*c]const u8, null));
|
||||
|
||||
c.gtk_css_provider_load_from_resource(
|
||||
provider,
|
||||
"/com/mitchellh/ghostty/style-dark.css",
|
||||
);
|
||||
c.gtk_style_context_add_provider_for_display(
|
||||
display,
|
||||
@ptrCast(provider),
|
||||
c.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 2,
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const provider = c.gtk_css_provider_new();
|
||||
defer c.g_object_unref(provider);
|
||||
|
||||
c.gtk_css_provider_load_from_resource(provider, "/com/mitchellh/ghostty/style.css");
|
||||
c.gtk_style_context_add_provider_for_display(
|
||||
display,
|
||||
@ptrCast(provider),
|
||||
c.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 1,
|
||||
);
|
||||
}
|
||||
|
||||
break :app @as(?*c.GtkApplication, @ptrCast(c.gtk_application_new(
|
||||
app_id.ptr,
|
||||
app_flags,
|
||||
))) orelse return error.GtkInitFailed;
|
||||
}
|
||||
|
||||
// Use libadwaita if requested. Using an AdwApplication lets us use
|
||||
// Adwaita widgets and access things such as the color scheme.
|
||||
// Using an AdwApplication lets us use Adwaita widgets and access things
|
||||
// such as the color scheme.
|
||||
const adw_app = @as(?*c.AdwApplication, @ptrCast(c.adw_application_new(
|
||||
app_id.ptr,
|
||||
app_flags,
|
||||
))) orelse return error.GtkInitFailed;
|
||||
errdefer c.g_object_unref(adw_app);
|
||||
|
||||
const style_manager = c.adw_application_get_style_manager(adw_app);
|
||||
c.adw_style_manager_set_color_scheme(
|
||||
@ -374,17 +310,14 @@ pub fn init(core_app: *CoreApp, opts: Options) !App {
|
||||
else
|
||||
c.ADW_COLOR_SCHEME_PREFER_DARK;
|
||||
},
|
||||
|
||||
.system => c.ADW_COLOR_SCHEME_PREFER_LIGHT,
|
||||
.dark => c.ADW_COLOR_SCHEME_FORCE_DARK,
|
||||
.light => c.ADW_COLOR_SCHEME_FORCE_LIGHT,
|
||||
},
|
||||
);
|
||||
|
||||
break :app @ptrCast(adw_app);
|
||||
};
|
||||
errdefer c.g_object_unref(app);
|
||||
const gapp = @as(*c.GApplication, @ptrCast(app));
|
||||
const app: *c.GtkApplication = @ptrCast(adw_app);
|
||||
const gapp: *c.GApplication = @ptrCast(app);
|
||||
|
||||
// force the resource path to a known value so that it doesn't depend on
|
||||
// the app id and load in compiled resources
|
||||
@ -507,13 +440,14 @@ pub fn terminate(self: *App) void {
|
||||
self.config.deinit();
|
||||
}
|
||||
|
||||
/// Perform a given action.
|
||||
/// Perform a given action. Returns `true` if the action was able to be
|
||||
/// performed, `false` otherwise.
|
||||
pub fn performAction(
|
||||
self: *App,
|
||||
target: apprt.Target,
|
||||
comptime action: apprt.Action.Key,
|
||||
value: apprt.Action.Value(action),
|
||||
) !void {
|
||||
) !bool {
|
||||
switch (action) {
|
||||
.quit => self.quit(),
|
||||
.new_window => _ = try self.newWindow(switch (target) {
|
||||
@ -525,12 +459,12 @@ pub fn performAction(
|
||||
|
||||
.new_tab => try self.newTab(target),
|
||||
.close_tab => try self.closeTab(target),
|
||||
.goto_tab => self.gotoTab(target, value),
|
||||
.goto_tab => return self.gotoTab(target, value),
|
||||
.move_tab => self.moveTab(target, value),
|
||||
.new_split => try self.newSplit(target, value),
|
||||
.resize_split => self.resizeSplit(target, value),
|
||||
.equalize_splits => self.equalizeSplits(target),
|
||||
.goto_split => self.gotoSplit(target, value),
|
||||
.goto_split => return self.gotoSplit(target, value),
|
||||
.open_config => try configpkg.edit.open(self.core_app.alloc),
|
||||
.config_change => self.configChange(target, value.config),
|
||||
.reload_config => try self.reloadConfig(target, value),
|
||||
@ -559,8 +493,16 @@ pub fn performAction(
|
||||
.render_inspector,
|
||||
.renderer_health,
|
||||
.color_change,
|
||||
=> log.warn("unimplemented action={}", .{action}),
|
||||
.prompt_title,
|
||||
=> {
|
||||
log.warn("unimplemented action={}", .{action});
|
||||
return false;
|
||||
},
|
||||
}
|
||||
|
||||
// We can assume it was handled because all unknown/unimplemented actions
|
||||
// are caught above.
|
||||
return true;
|
||||
}
|
||||
|
||||
fn newTab(_: *App, target: apprt.Target) !void {
|
||||
@ -597,24 +539,24 @@ fn closeTab(_: *App, target: apprt.Target) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn gotoTab(_: *App, target: apprt.Target, tab: apprt.action.GotoTab) void {
|
||||
fn gotoTab(_: *App, target: apprt.Target, tab: apprt.action.GotoTab) bool {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.app => return false,
|
||||
.surface => |v| {
|
||||
const window = v.rt_surface.container.window() orelse {
|
||||
log.info(
|
||||
"gotoTab invalid for container={s}",
|
||||
.{@tagName(v.rt_surface.container)},
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
};
|
||||
|
||||
switch (tab) {
|
||||
return switch (tab) {
|
||||
.previous => window.gotoPreviousTab(v.rt_surface),
|
||||
.next => window.gotoNextTab(v.rt_surface),
|
||||
.last => window.gotoLastTab(),
|
||||
else => window.gotoTab(@intCast(@intFromEnum(tab))),
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -668,18 +610,22 @@ fn gotoSplit(
|
||||
_: *const App,
|
||||
target: apprt.Target,
|
||||
direction: apprt.action.GotoSplit,
|
||||
) void {
|
||||
) bool {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.app => return false,
|
||||
.surface => |v| {
|
||||
const s = v.rt_surface.container.split() orelse return;
|
||||
const s = v.rt_surface.container.split() orelse return false;
|
||||
const map = s.directionMap(switch (v.rt_surface.container) {
|
||||
.split_tl => .top_left,
|
||||
.split_br => .bottom_right,
|
||||
.none, .tab_ => unreachable,
|
||||
});
|
||||
const surface_ = map.get(direction) orelse return;
|
||||
if (surface_) |surface| surface.grabFocus();
|
||||
const surface_ = map.get(direction) orelse return false;
|
||||
if (surface_) |surface| {
|
||||
surface.grabFocus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -967,12 +913,10 @@ fn configChange(
|
||||
|
||||
// App changes needs to show a toast that our configuration
|
||||
// has reloaded.
|
||||
if (adwaita.enabled(&self.config)) {
|
||||
if (self.core_app.focusedSurface()) |core_surface| {
|
||||
const surface = core_surface.rt_surface;
|
||||
if (surface.container.window()) |window| window.onConfigReloaded();
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
72
src/apprt/gtk/Builder.zig
Normal file
@ -0,0 +1,72 @@
|
||||
/// Wrapper around GTK's builder APIs that perform some comptime checks.
|
||||
const Builder = @This();
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const gtk = @import("gtk");
|
||||
const gobject = @import("gobject");
|
||||
|
||||
resource_name: [:0]const u8,
|
||||
builder: ?*gtk.Builder,
|
||||
|
||||
pub fn init(comptime name: []const u8, comptime kind: enum { blp, ui }) Builder {
|
||||
comptime {
|
||||
switch (kind) {
|
||||
.blp => {
|
||||
// Use @embedFile to make sure that the file exists at compile
|
||||
// time. Zig _should_ discard the data so that it doesn't end
|
||||
// up in the final executable. At runtime we will load the data
|
||||
// from a GResource.
|
||||
_ = @embedFile("ui/" ++ name ++ ".blp");
|
||||
|
||||
// Check to make sure that our file is listed as a
|
||||
// `blueprint_file` in `gresource.zig`. If it isn't Ghostty
|
||||
// could crash at runtime when we try and load a nonexistent
|
||||
// GResource.
|
||||
const gresource = @import("gresource.zig");
|
||||
for (gresource.blueprint_files) |blueprint_file| {
|
||||
if (std.mem.eql(u8, blueprint_file, name)) break;
|
||||
} else @compileError("missing blueprint file '" ++ name ++ "' in gresource.zig");
|
||||
},
|
||||
.ui => {
|
||||
// Use @embedFile to make sure that the file exists at compile
|
||||
// time. Zig _should_ discard the data so that it doesn't end
|
||||
// up in the final executable. At runtime we will load the data
|
||||
// from a GResource.
|
||||
_ = @embedFile("ui/" ++ name ++ ".ui");
|
||||
|
||||
// Check to make sure that our file is listed as a `ui_file` in
|
||||
// `gresource.zig`. If it isn't Ghostty could crash at runtime
|
||||
// when we try and load a nonexistent GResource.
|
||||
const gresource = @import("gresource.zig");
|
||||
for (gresource.ui_files) |ui_file| {
|
||||
if (std.mem.eql(u8, ui_file, name)) break;
|
||||
} else @compileError("missing ui file '" ++ name ++ "' in gresource.zig");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return .{
|
||||
.resource_name = "/com/mitchellh/ghostty/ui/" ++ name ++ ".ui",
|
||||
.builder = null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setWidgetClassTemplate(self: *const Builder, class: *gtk.WidgetClass) void {
|
||||
class.setTemplateFromResource(self.resource_name);
|
||||
}
|
||||
|
||||
pub fn getObject(self: *Builder, name: [:0]const u8) ?*gobject.Object {
|
||||
const builder = builder: {
|
||||
if (self.builder) |builder| break :builder builder;
|
||||
const builder = gtk.Builder.newFromResource(self.resource_name);
|
||||
self.builder = builder;
|
||||
break :builder builder;
|
||||
};
|
||||
|
||||
return builder.getObject(name);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Builder) void {
|
||||
if (self.builder) |builder| builder.unref();
|
||||
}
|
@ -313,11 +313,7 @@ pub fn directionMap(self: *const Split, from: Side) DirectionMap {
|
||||
if (self.directionPrevious(from)) |prev| {
|
||||
result.put(.previous, prev.surface);
|
||||
if (!prev.wrapped) {
|
||||
// This behavior matches the behavior of macOS at the time of writing
|
||||
// this. There is an open issue (#524) to make this depend on the
|
||||
// actual physical location of the current split.
|
||||
result.put(.up, prev.surface);
|
||||
result.put(.left, prev.surface);
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,13 +321,57 @@ pub fn directionMap(self: *const Split, from: Side) DirectionMap {
|
||||
result.put(.next, next.surface);
|
||||
if (!next.wrapped) {
|
||||
result.put(.down, next.surface);
|
||||
result.put(.right, next.surface);
|
||||
}
|
||||
}
|
||||
|
||||
if (self.directionLeft(from)) |left| {
|
||||
result.put(.left, left);
|
||||
}
|
||||
|
||||
if (self.directionRight(from)) |right| {
|
||||
result.put(.right, right);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fn directionLeft(self: *const Split, from: Side) ?*Surface {
|
||||
switch (from) {
|
||||
.bottom_right => {
|
||||
switch (self.orientation) {
|
||||
.horizontal => return self.top_left.deepestSurface(.bottom_right),
|
||||
.vertical => return directionLeft(
|
||||
self.container.split() orelse return null,
|
||||
.bottom_right,
|
||||
),
|
||||
}
|
||||
},
|
||||
.top_left => return directionLeft(
|
||||
self.container.split() orelse return null,
|
||||
.bottom_right,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn directionRight(self: *const Split, from: Side) ?*Surface {
|
||||
switch (from) {
|
||||
.top_left => {
|
||||
switch (self.orientation) {
|
||||
.horizontal => return self.bottom_right.deepestSurface(.top_left),
|
||||
.vertical => return directionRight(
|
||||
self.container.split() orelse return null,
|
||||
.top_left,
|
||||
),
|
||||
}
|
||||
},
|
||||
.bottom_right => return directionRight(
|
||||
self.container.split() orelse return null,
|
||||
.top_left,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn directionPrevious(self: *const Split, from: Side) ?struct {
|
||||
surface: *Surface,
|
||||
wrapped: bool,
|
||||
|
@ -1270,10 +1270,12 @@ fn showContextMenu(self: *Surface, x: f32, y: f32) void {
|
||||
return;
|
||||
};
|
||||
|
||||
// Convert surface coordinate into coordinate space of the
|
||||
// context menu's parent
|
||||
var point: c.graphene_point_t = .{ .x = x, .y = y };
|
||||
if (c.gtk_widget_compute_point(
|
||||
self.primaryWidget(),
|
||||
@ptrCast(window.window),
|
||||
c.gtk_widget_get_parent(@ptrCast(window.context_menu)),
|
||||
&c.GRAPHENE_POINT_INIT(point.x, point.y),
|
||||
@ptrCast(&point),
|
||||
) == 0) {
|
||||
@ -2124,7 +2126,7 @@ pub fn present(self: *Surface) void {
|
||||
if (self.container.window()) |window| {
|
||||
if (self.container.tab()) |tab| {
|
||||
if (window.notebook.getTabPosition(tab)) |position|
|
||||
window.notebook.gotoNthTab(position);
|
||||
_ = window.notebook.gotoNthTab(position);
|
||||
}
|
||||
c.gtk_window_present(window.window);
|
||||
}
|
||||
@ -2252,6 +2254,41 @@ fn doPaste(self: *Surface, data: [:0]const u8) void {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn defaultTermioEnv(self: *Surface) !std.process.EnvMap {
|
||||
const alloc = self.app.core_app.alloc;
|
||||
var env = try internal_os.getEnvMap(alloc);
|
||||
errdefer env.deinit();
|
||||
|
||||
// Don't leak these GTK environment variables to child processes.
|
||||
env.remove("GDK_DEBUG");
|
||||
env.remove("GDK_DISABLE");
|
||||
env.remove("GSK_RENDERER");
|
||||
|
||||
// Unset environment varies set by snaps if we're running in a snap.
|
||||
// This allows Ghostty to further launch additional snaps.
|
||||
if (env.get("SNAP")) |_| {
|
||||
env.remove("SNAP");
|
||||
env.remove("DRIRC_CONFIGDIR");
|
||||
env.remove("__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS");
|
||||
env.remove("__EGL_VENDOR_LIBRARY_DIRS");
|
||||
env.remove("LD_LIBRARY_PATH");
|
||||
env.remove("LIBGL_DRIVERS_PATH");
|
||||
env.remove("LIBVA_DRIVERS_PATH");
|
||||
env.remove("VK_LAYER_PATH");
|
||||
env.remove("XLOCALEDIR");
|
||||
env.remove("GDK_PIXBUF_MODULEDIR");
|
||||
env.remove("GTK_PATH");
|
||||
}
|
||||
|
||||
if (self.container.window()) |window| {
|
||||
// On some window protocols we might want to add specific
|
||||
// environment variables to subprocesses, such as WINDOWID on X11.
|
||||
try window.winproto.addSubprocessEnv(&env);
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
/// Check a GValue to see what's type its wrapping. This is equivalent to GTK's
|
||||
/// `G_VALUE_HOLDS` macro but Zig's C translator does not like it.
|
||||
fn g_value_holds(value_: ?*c.GValue, g_type: c.GType) bool {
|
||||
|
275
src/apprt/gtk/TabView.zig
Normal file
@ -0,0 +1,275 @@
|
||||
/// An abstraction over the Adwaita tab view to manage all the terminal tabs in
|
||||
/// a window.
|
||||
const TabView = @This();
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const gtk = @import("gtk");
|
||||
const adw = @import("adw");
|
||||
const gobject = @import("gobject");
|
||||
|
||||
const Window = @import("Window.zig");
|
||||
const Tab = @import("Tab.zig");
|
||||
const adwaita = @import("adwaita.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
/// our window
|
||||
window: *Window,
|
||||
|
||||
/// the tab view
|
||||
tab_view: *adw.TabView,
|
||||
|
||||
/// Set to true so that the adw close-page handler knows we're forcing
|
||||
/// and to allow a close to happen with no confirm. This is a bit of a hack
|
||||
/// because we currently use GTK alerts to confirm tab close and they
|
||||
/// don't carry with them the ADW state that we are confirming or not.
|
||||
/// Long term we should move to ADW alerts so we can know if we are
|
||||
/// confirming or not.
|
||||
forcing_close: bool = false,
|
||||
|
||||
pub fn init(self: *TabView, window: *Window) void {
|
||||
self.* = .{
|
||||
.window = window,
|
||||
.tab_view = adw.TabView.new(),
|
||||
};
|
||||
self.tab_view.as(gtk.Widget).addCssClass("notebook");
|
||||
|
||||
if (adwaita.versionAtLeast(1, 2, 0)) {
|
||||
// Adwaita enables all of the shortcuts by default.
|
||||
// We want to manage keybindings ourselves.
|
||||
self.tab_view.removeShortcuts(.{
|
||||
.alt_digits = true,
|
||||
.alt_zero = true,
|
||||
.control_end = true,
|
||||
.control_home = true,
|
||||
.control_page_down = true,
|
||||
.control_page_up = true,
|
||||
.control_shift_end = true,
|
||||
.control_shift_home = true,
|
||||
.control_shift_page_down = true,
|
||||
.control_shift_page_up = true,
|
||||
.control_shift_tab = true,
|
||||
.control_tab = true,
|
||||
});
|
||||
}
|
||||
|
||||
_ = adw.TabView.signals.page_attached.connect(
|
||||
self.tab_view,
|
||||
*TabView,
|
||||
adwPageAttached,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
_ = adw.TabView.signals.close_page.connect(
|
||||
self.tab_view,
|
||||
*TabView,
|
||||
adwClosePage,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
_ = adw.TabView.signals.create_window.connect(
|
||||
self.tab_view,
|
||||
*TabView,
|
||||
adwTabViewCreateWindow,
|
||||
self,
|
||||
.{},
|
||||
);
|
||||
_ = gobject.Object.signals.notify.connect(
|
||||
self.tab_view,
|
||||
*TabView,
|
||||
adwSelectPage,
|
||||
self,
|
||||
.{
|
||||
.detail = "selected-page",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn asWidget(self: *TabView) *gtk.Widget {
|
||||
return self.tab_view.as(gtk.Widget);
|
||||
}
|
||||
|
||||
pub fn nPages(self: *TabView) c_int {
|
||||
return self.tab_view.getNPages();
|
||||
}
|
||||
|
||||
/// Returns the index of the currently selected page.
|
||||
/// Returns null if the notebook has no pages.
|
||||
fn currentPage(self: *TabView) ?c_int {
|
||||
const page = self.tab_view.getSelectedPage() orelse return null;
|
||||
return self.tab_view.getPagePosition(page);
|
||||
}
|
||||
|
||||
/// Returns the currently selected tab or null if there are none.
|
||||
pub fn currentTab(self: *TabView) ?*Tab {
|
||||
const page = self.tab_view.getSelectedPage() orelse return null;
|
||||
const child = page.getChild().as(gobject.Object);
|
||||
return @ptrCast(@alignCast(child.getData(Tab.GHOSTTY_TAB) orelse return null));
|
||||
}
|
||||
|
||||
pub fn gotoNthTab(self: *TabView, position: c_int) bool {
|
||||
const page_to_select = self.tab_view.getNthPage(position);
|
||||
self.tab_view.setSelectedPage(page_to_select);
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn getTabPosition(self: *TabView, tab: *Tab) ?c_int {
|
||||
const page = self.tab_view.getPage(@ptrCast(tab.box));
|
||||
return self.tab_view.getPagePosition(page);
|
||||
}
|
||||
|
||||
pub fn gotoPreviousTab(self: *TabView, tab: *Tab) bool {
|
||||
const page_idx = self.getTabPosition(tab) orelse return false;
|
||||
|
||||
// The next index is the previous or we wrap around.
|
||||
const next_idx = if (page_idx > 0) page_idx - 1 else next_idx: {
|
||||
const max = self.nPages();
|
||||
break :next_idx max -| 1;
|
||||
};
|
||||
|
||||
// Do nothing if we have one tab
|
||||
if (next_idx == page_idx) return false;
|
||||
|
||||
return self.gotoNthTab(next_idx);
|
||||
}
|
||||
|
||||
pub fn gotoNextTab(self: *TabView, tab: *Tab) bool {
|
||||
const page_idx = self.getTabPosition(tab) orelse return false;
|
||||
|
||||
const max = self.nPages() -| 1;
|
||||
const next_idx = if (page_idx < max) page_idx + 1 else 0;
|
||||
if (next_idx == page_idx) return false;
|
||||
|
||||
return self.gotoNthTab(next_idx);
|
||||
}
|
||||
|
||||
pub fn moveTab(self: *TabView, tab: *Tab, position: c_int) void {
|
||||
const page_idx = self.getTabPosition(tab) orelse return;
|
||||
|
||||
const max = self.nPages() -| 1;
|
||||
var new_position: c_int = page_idx + position;
|
||||
|
||||
if (new_position < 0) {
|
||||
new_position = max + new_position + 1;
|
||||
} else if (new_position > max) {
|
||||
new_position = new_position - max - 1;
|
||||
}
|
||||
|
||||
if (new_position == page_idx) return;
|
||||
self.reorderPage(tab, new_position);
|
||||
}
|
||||
|
||||
pub fn reorderPage(self: *TabView, tab: *Tab, position: c_int) void {
|
||||
const page = self.tab_view.getPage(@ptrCast(tab.box));
|
||||
_ = self.tab_view.reorderPage(page, position);
|
||||
}
|
||||
|
||||
pub fn setTabLabel(self: *TabView, tab: *Tab, title: [:0]const u8) void {
|
||||
const page = self.tab_view.getPage(@ptrCast(tab.box));
|
||||
page.setTitle(title.ptr);
|
||||
}
|
||||
|
||||
pub fn setTabTooltip(self: *TabView, tab: *Tab, tooltip: [:0]const u8) void {
|
||||
const page = self.tab_view.getPage(@ptrCast(tab.box));
|
||||
page.setTooltip(tooltip.ptr);
|
||||
}
|
||||
|
||||
fn newTabInsertPosition(self: *TabView, tab: *Tab) c_int {
|
||||
const numPages = self.nPages();
|
||||
return switch (tab.window.app.config.@"window-new-tab-position") {
|
||||
.current => if (self.currentPage()) |page| page + 1 else numPages,
|
||||
.end => numPages,
|
||||
};
|
||||
}
|
||||
|
||||
/// Adds a new tab with the given title to the notebook.
|
||||
pub fn addTab(self: *TabView, tab: *Tab, title: [:0]const u8) void {
|
||||
const position = self.newTabInsertPosition(tab);
|
||||
const box_widget: *gtk.Widget = @ptrCast(tab.box);
|
||||
const page = self.tab_view.insert(box_widget, position);
|
||||
self.setTabLabel(tab, title);
|
||||
self.tab_view.setSelectedPage(page);
|
||||
}
|
||||
|
||||
pub fn closeTab(self: *TabView, tab: *Tab) void {
|
||||
// Save a pointer to the GTK window in case we need it later. It may be
|
||||
// impossible to access later due to how resources are cleaned up.
|
||||
const window: *gtk.Window = @ptrCast(@alignCast(self.window.window));
|
||||
|
||||
// closeTab always expects to close unconditionally so we mark this
|
||||
// as true so that the close_page call below doesn't request
|
||||
// confirmation.
|
||||
self.forcing_close = true;
|
||||
const n = self.nPages();
|
||||
defer {
|
||||
// self becomes invalid if we close the last page because we close
|
||||
// the whole window
|
||||
if (n > 1) self.forcing_close = false;
|
||||
}
|
||||
|
||||
const page = self.tab_view.getPage(@ptrCast(tab.box));
|
||||
self.tab_view.closePage(page);
|
||||
|
||||
// If we have no more tabs we close the window
|
||||
if (self.nPages() == 0) {
|
||||
// libadw versions <= 1.3.x leak the final page view
|
||||
// which causes our surface to not properly cleanup. We
|
||||
// unref to force the cleanup. This will trigger a critical
|
||||
// warning from GTK, but I don't know any other workaround.
|
||||
// Note: I'm not actually sure if 1.4.0 contains the fix,
|
||||
// I just know that 1.3.x is broken and 1.5.1 is fixed.
|
||||
// If we know that 1.4.0 is fixed, we can change this.
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) {
|
||||
const box: *gtk.Box = @ptrCast(@alignCast(tab.box));
|
||||
box.as(gobject.Object).unref();
|
||||
}
|
||||
|
||||
window.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn createWindow(currentWindow: *Window) !*Window {
|
||||
const alloc = currentWindow.app.core_app.alloc;
|
||||
const app = currentWindow.app;
|
||||
|
||||
// Create a new window
|
||||
return Window.create(alloc, app);
|
||||
}
|
||||
|
||||
fn adwPageAttached(_: *adw.TabView, page: *adw.TabPage, _: c_int, self: *TabView) callconv(.C) void {
|
||||
const child = page.getChild().as(gobject.Object);
|
||||
const tab: *Tab = @ptrCast(@alignCast(child.getData(Tab.GHOSTTY_TAB) orelse return));
|
||||
tab.window = self.window;
|
||||
|
||||
self.window.focusCurrentTab();
|
||||
}
|
||||
|
||||
fn adwClosePage(
|
||||
_: *adw.TabView,
|
||||
page: *adw.TabPage,
|
||||
self: *TabView,
|
||||
) callconv(.C) c_int {
|
||||
const child = page.getChild().as(gobject.Object);
|
||||
const tab: *Tab = @ptrCast(@alignCast(child.getData(Tab.GHOSTTY_TAB) orelse return 0));
|
||||
self.tab_view.closePageFinish(page, @intFromBool(self.forcing_close));
|
||||
if (!self.forcing_close) tab.closeWithConfirmation();
|
||||
return 1;
|
||||
}
|
||||
|
||||
fn adwTabViewCreateWindow(
|
||||
_: *adw.TabView,
|
||||
self: *TabView,
|
||||
) callconv(.C) ?*adw.TabView {
|
||||
const window = createWindow(self.window) catch |err| {
|
||||
log.warn("error creating new window error={}", .{err});
|
||||
return null;
|
||||
};
|
||||
return window.notebook.tab_view;
|
||||
}
|
||||
|
||||
fn adwSelectPage(_: *adw.TabView, _: *gobject.ParamSpec, self: *TabView) callconv(.C) void {
|
||||
const page = self.tab_view.getSelectedPage() orelse return;
|
||||
const title = page.getTitle();
|
||||
self.window.setTitle(std.mem.span(title));
|
||||
}
|
@ -22,8 +22,8 @@ const Tab = @import("Tab.zig");
|
||||
const c = @import("c.zig").c;
|
||||
const adwaita = @import("adwaita.zig");
|
||||
const gtk_key = @import("key.zig");
|
||||
const Notebook = @import("notebook.zig").Notebook;
|
||||
const HeaderBar = @import("headerbar.zig").HeaderBar;
|
||||
const TabView = @import("TabView.zig");
|
||||
const HeaderBar = @import("headerbar.zig");
|
||||
const version = @import("version.zig");
|
||||
const winproto = @import("winproto.zig");
|
||||
|
||||
@ -34,9 +34,7 @@ app: *App,
|
||||
/// Our window
|
||||
window: *c.GtkWindow,
|
||||
|
||||
/// The header bar for the window. This is possibly null since it can be
|
||||
/// disabled using gtk-titlebar. This is either an AdwHeaderBar or
|
||||
/// GtkHeaderBar depending on if adw is enabled and linked.
|
||||
/// The header bar for the window.
|
||||
headerbar: HeaderBar,
|
||||
|
||||
/// The tab overview for the window. This is possibly null since there is no
|
||||
@ -44,14 +42,12 @@ headerbar: HeaderBar,
|
||||
tab_overview: ?*c.GtkWidget,
|
||||
|
||||
/// The notebook (tab grouping) for this window.
|
||||
/// can be either c.GtkNotebook or c.AdwTabView.
|
||||
notebook: Notebook,
|
||||
notebook: TabView,
|
||||
|
||||
context_menu: *c.GtkWidget,
|
||||
|
||||
/// The libadwaita widget for receiving toast send requests. If libadwaita is
|
||||
/// not used, this is null and unused.
|
||||
toast_overlay: ?*c.GtkWidget,
|
||||
/// The libadwaita widget for receiving toast send requests.
|
||||
toast_overlay: *c.GtkWidget,
|
||||
|
||||
/// See adwTabOverviewOpen for why we have this.
|
||||
adw_tab_overview_focus_timer: ?c.guint = null,
|
||||
@ -87,49 +83,39 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
};
|
||||
|
||||
// Create the window
|
||||
const window: *c.GtkWidget = window: {
|
||||
if ((comptime adwaita.versionAtLeast(0, 0, 0)) and adwaita.enabled(&self.app.config)) {
|
||||
const window = c.adw_application_window_new(app.app);
|
||||
c.gtk_widget_add_css_class(@ptrCast(window), "adw");
|
||||
break :window window;
|
||||
} else {
|
||||
const window = c.gtk_application_window_new(app.app);
|
||||
c.gtk_widget_add_css_class(@ptrCast(window), "gtk");
|
||||
break :window window;
|
||||
}
|
||||
};
|
||||
errdefer c.gtk_window_destroy(@ptrCast(window));
|
||||
const gtk_widget = c.adw_application_window_new(app.app);
|
||||
errdefer c.gtk_window_destroy(@ptrCast(gtk_widget));
|
||||
|
||||
const gtk_window: *c.GtkWindow = @ptrCast(window);
|
||||
self.window = gtk_window;
|
||||
c.gtk_window_set_title(gtk_window, "Ghostty");
|
||||
c.gtk_window_set_default_size(gtk_window, 1000, 600);
|
||||
c.gtk_widget_add_css_class(@ptrCast(gtk_window), "window");
|
||||
c.gtk_widget_add_css_class(@ptrCast(gtk_window), "terminal-window");
|
||||
self.window = @ptrCast(@alignCast(gtk_widget));
|
||||
|
||||
c.gtk_window_set_title(self.window, "Ghostty");
|
||||
c.gtk_window_set_default_size(self.window, 1000, 600);
|
||||
c.gtk_widget_add_css_class(gtk_widget, "window");
|
||||
c.gtk_widget_add_css_class(gtk_widget, "terminal-window");
|
||||
|
||||
// GTK4 grabs F10 input by default to focus the menubar icon. We want
|
||||
// to disable this so that terminal programs can capture F10 (such as htop)
|
||||
c.gtk_window_set_handle_menubar_accel(gtk_window, 0);
|
||||
c.gtk_window_set_handle_menubar_accel(self.window, 0);
|
||||
|
||||
c.gtk_window_set_icon_name(gtk_window, build_config.bundle_id);
|
||||
c.gtk_window_set_icon_name(self.window, build_config.bundle_id);
|
||||
|
||||
// Apply class to color headerbar if window-theme is set to `ghostty` and
|
||||
// GTK version is before 4.16. The conditional is because above 4.16
|
||||
// we use GTK CSS color variables.
|
||||
if (!version.atLeast(4, 16, 0) and app.config.@"window-theme" == .ghostty) {
|
||||
c.gtk_widget_add_css_class(@ptrCast(gtk_window), "window-theme-ghostty");
|
||||
c.gtk_widget_add_css_class(gtk_widget, "window-theme-ghostty");
|
||||
}
|
||||
|
||||
// Create our box which will hold our widgets in the main content area.
|
||||
const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
||||
|
||||
// Setup our notebook
|
||||
self.notebook.init();
|
||||
self.notebook.init(self);
|
||||
|
||||
// If we are using Adwaita, then we can support the tab overview.
|
||||
self.tab_overview = if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.enabled(&self.app.config) and adwaita.versionAtLeast(1, 4, 0)) overview: {
|
||||
self.tab_overview = if (adwaita.versionAtLeast(1, 4, 0)) overview: {
|
||||
const tab_overview = c.adw_tab_overview_new();
|
||||
c.adw_tab_overview_set_view(@ptrCast(tab_overview), self.notebook.adw.tab_view);
|
||||
c.adw_tab_overview_set_view(@ptrCast(tab_overview), @ptrCast(@alignCast(self.notebook.tab_view)));
|
||||
c.adw_tab_overview_set_enable_new_tab(@ptrCast(tab_overview), 1);
|
||||
_ = c.g_signal_connect_data(
|
||||
tab_overview,
|
||||
@ -166,10 +152,9 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
|
||||
// If we're using an AdwWindow then we can support the tab overview.
|
||||
if (self.tab_overview) |tab_overview| {
|
||||
if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
assert(self.app.config.@"gtk-adwaita" and adwaita.versionAtLeast(1, 4, 0));
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
const btn = switch (app.config.@"gtk-tabs-location") {
|
||||
.top, .bottom, .left, .right => btn: {
|
||||
.top, .bottom => btn: {
|
||||
const btn = c.gtk_toggle_button_new();
|
||||
c.gtk_widget_set_tooltip_text(btn, "View Open Tabs");
|
||||
c.gtk_button_set_icon_name(@ptrCast(btn), "view-grid-symbolic");
|
||||
@ -186,7 +171,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
|
||||
.hidden => btn: {
|
||||
const btn = c.adw_tab_button_new();
|
||||
c.adw_tab_button_set_view(@ptrCast(btn), self.notebook.adw.tab_view);
|
||||
c.adw_tab_button_set_view(@ptrCast(btn), @ptrCast(@alignCast(self.notebook.tab_view)));
|
||||
c.gtk_actionable_set_action_name(@ptrCast(btn), "overview.open");
|
||||
break :btn btn;
|
||||
},
|
||||
@ -203,13 +188,13 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
self.headerbar.packStart(btn);
|
||||
}
|
||||
|
||||
_ = c.g_signal_connect_data(gtk_window, "notify::decorated", c.G_CALLBACK(>kWindowNotifyDecorated), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gtk_window, "notify::maximized", c.G_CALLBACK(>kWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gtk_window, "notify::fullscreened", c.G_CALLBACK(>kWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(self.window, "notify::decorated", c.G_CALLBACK(>kWindowNotifyDecorated), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(self.window, "notify::maximized", c.G_CALLBACK(>kWindowNotifyMaximized), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(self.window, "notify::fullscreened", c.G_CALLBACK(>kWindowNotifyFullscreened), self, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
// If Adwaita is enabled and is older than 1.4.0 we don't have the tab overview and so we
|
||||
// need to stick the headerbar into the content box.
|
||||
if (!adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config)) {
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) {
|
||||
c.gtk_box_append(@ptrCast(box), self.headerbar.asWidget());
|
||||
}
|
||||
|
||||
@ -218,10 +203,7 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
const warning_box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0);
|
||||
const warning_text = "⚠️ You're running a debug build of Ghostty! Performance will be degraded.";
|
||||
if ((comptime adwaita.versionAtLeast(1, 3, 0)) and
|
||||
adwaita.enabled(&app.config) and
|
||||
adwaita.versionAtLeast(1, 3, 0))
|
||||
{
|
||||
if (adwaita.versionAtLeast(1, 3, 0)) {
|
||||
const banner = c.adw_banner_new(warning_text);
|
||||
c.adw_banner_set_revealed(@ptrCast(banner), 1);
|
||||
c.gtk_box_append(@ptrCast(warning_box), @ptrCast(banner));
|
||||
@ -231,30 +213,23 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
c.gtk_widget_set_margin_bottom(warning, 10);
|
||||
c.gtk_box_append(@ptrCast(warning_box), warning);
|
||||
}
|
||||
c.gtk_widget_add_css_class(@ptrCast(gtk_window), "devel");
|
||||
c.gtk_widget_add_css_class(gtk_widget, "devel");
|
||||
c.gtk_widget_add_css_class(@ptrCast(warning_box), "background");
|
||||
c.gtk_box_append(@ptrCast(box), warning_box);
|
||||
}
|
||||
|
||||
// Setup our toast overlay if we have one
|
||||
self.toast_overlay = if (adwaita.enabled(&self.app.config)) toast: {
|
||||
const toast_overlay = c.adw_toast_overlay_new();
|
||||
self.toast_overlay = c.adw_toast_overlay_new();
|
||||
c.adw_toast_overlay_set_child(
|
||||
@ptrCast(toast_overlay),
|
||||
@ptrCast(self.toast_overlay),
|
||||
@ptrCast(@alignCast(self.notebook.asWidget())),
|
||||
);
|
||||
c.gtk_box_append(@ptrCast(box), toast_overlay);
|
||||
break :toast toast_overlay;
|
||||
} else toast: {
|
||||
c.gtk_box_append(@ptrCast(box), self.notebook.asWidget());
|
||||
break :toast null;
|
||||
};
|
||||
c.gtk_box_append(@ptrCast(box), self.toast_overlay);
|
||||
|
||||
// If we have a tab overview then we can set it on our notebook.
|
||||
if (self.tab_overview) |tab_overview| {
|
||||
if (comptime !adwaita.versionAtLeast(1, 3, 0)) unreachable;
|
||||
assert(self.notebook == .adw);
|
||||
c.adw_tab_overview_set_view(@ptrCast(tab_overview), self.notebook.adw.tab_view);
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
c.adw_tab_overview_set_view(@ptrCast(tab_overview), @ptrCast(@alignCast(self.notebook.tab_view)));
|
||||
}
|
||||
|
||||
self.context_menu = c.gtk_popover_menu_new_from_model(@ptrCast(@alignCast(self.app.context_menu)));
|
||||
@ -273,40 +248,39 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
// focused (i.e. when the libadw tab overview is shown).
|
||||
const ec_key_press = c.gtk_event_controller_key_new();
|
||||
errdefer c.g_object_unref(ec_key_press);
|
||||
c.gtk_widget_add_controller(window, ec_key_press);
|
||||
c.gtk_widget_add_controller(gtk_widget, ec_key_press);
|
||||
|
||||
// All of our events
|
||||
_ = c.g_signal_connect_data(self.context_menu, "closed", c.G_CALLBACK(>kRefocusTerm), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(window, "realize", c.G_CALLBACK(>kRealize), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(window, "close-request", c.G_CALLBACK(>kCloseRequest), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(self.window, "realize", c.G_CALLBACK(>kRealize), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(self.window, "close-request", c.G_CALLBACK(>kCloseRequest), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(self.window, "destroy", c.G_CALLBACK(>kDestroy), self, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(ec_key_press, "key-pressed", c.G_CALLBACK(>kKeyPressed), self, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
// Our actions for the menu
|
||||
initActions(self);
|
||||
|
||||
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config)) {
|
||||
if (adwaita.versionAtLeast(1, 4, 0)) {
|
||||
const toolbar_view: *c.AdwToolbarView = @ptrCast(c.adw_toolbar_view_new());
|
||||
|
||||
c.adw_toolbar_view_add_top_bar(toolbar_view, self.headerbar.asWidget());
|
||||
|
||||
if (self.app.config.@"gtk-tabs-location" != .hidden) {
|
||||
const tab_bar = c.adw_tab_bar_new();
|
||||
c.adw_tab_bar_set_view(tab_bar, self.notebook.adw.tab_view);
|
||||
c.adw_tab_bar_set_view(tab_bar, @ptrCast(@alignCast(self.notebook.tab_view)));
|
||||
|
||||
if (!app.config.@"gtk-wide-tabs") c.adw_tab_bar_set_expand_tabs(tab_bar, 0);
|
||||
|
||||
const tab_bar_widget: *c.GtkWidget = @ptrCast(@alignCast(tab_bar));
|
||||
switch (self.app.config.@"gtk-tabs-location") {
|
||||
// left and right are not supported in libadwaita.
|
||||
.top, .left, .right => c.adw_toolbar_view_add_top_bar(toolbar_view, tab_bar_widget),
|
||||
.top => c.adw_toolbar_view_add_top_bar(toolbar_view, tab_bar_widget),
|
||||
.bottom => c.adw_toolbar_view_add_bottom_bar(toolbar_view, tab_bar_widget),
|
||||
.hidden => unreachable,
|
||||
}
|
||||
}
|
||||
c.adw_toolbar_view_set_content(toolbar_view, box);
|
||||
|
||||
const toolbar_style: c.AdwToolbarStyle = switch (self.app.config.@"adw-toolbar-style") {
|
||||
const toolbar_style: c.AdwToolbarStyle = switch (self.app.config.@"gtk-toolbar-style") {
|
||||
.flat => c.ADW_TOOLBAR_FLAT,
|
||||
.raised => c.ADW_TOOLBAR_RAISED,
|
||||
.@"raised-border" => c.ADW_TOOLBAR_RAISED_BORDER,
|
||||
@ -320,51 +294,34 @@ pub fn init(self: *Window, app: *App) !void {
|
||||
@ptrCast(@alignCast(toolbar_view)),
|
||||
);
|
||||
c.adw_application_window_set_content(
|
||||
@ptrCast(gtk_window),
|
||||
@ptrCast(gtk_widget),
|
||||
@ptrCast(@alignCast(self.tab_overview)),
|
||||
);
|
||||
} else tab_bar: {
|
||||
switch (self.notebook) {
|
||||
.adw => |*adw| if (comptime adwaita.versionAtLeast(0, 0, 0)) {
|
||||
if (app.config.@"gtk-tabs-location" == .hidden) break :tab_bar;
|
||||
// In earlier adwaita versions, we need to add the tabbar manually since we do not use
|
||||
// an AdwToolbarView.
|
||||
const tab_bar: *c.AdwTabBar = c.adw_tab_bar_new().?;
|
||||
c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_bar)), "inline");
|
||||
switch (app.config.@"gtk-tabs-location") {
|
||||
.top,
|
||||
.left,
|
||||
.right,
|
||||
=> c.gtk_box_insert_child_after(@ptrCast(box), @ptrCast(@alignCast(tab_bar)), @ptrCast(@alignCast(self.headerbar.asWidget()))),
|
||||
|
||||
.top => c.gtk_box_insert_child_after(
|
||||
@ptrCast(box),
|
||||
@ptrCast(@alignCast(tab_bar)),
|
||||
@ptrCast(@alignCast(self.headerbar.asWidget())),
|
||||
),
|
||||
.bottom => c.gtk_box_append(
|
||||
@ptrCast(box),
|
||||
@ptrCast(@alignCast(tab_bar)),
|
||||
),
|
||||
.hidden => unreachable,
|
||||
}
|
||||
c.adw_tab_bar_set_view(tab_bar, adw.tab_view);
|
||||
c.adw_tab_bar_set_view(tab_bar, @ptrCast(@alignCast(self.notebook.tab_view)));
|
||||
|
||||
if (!app.config.@"gtk-wide-tabs") c.adw_tab_bar_set_expand_tabs(tab_bar, 0);
|
||||
},
|
||||
|
||||
.gtk => {},
|
||||
}
|
||||
|
||||
// The box is our main child
|
||||
if (!adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config)) {
|
||||
c.adw_application_window_set_content(
|
||||
@ptrCast(gtk_window),
|
||||
box,
|
||||
);
|
||||
} else {
|
||||
c.gtk_window_set_titlebar(gtk_window, self.headerbar.asWidget());
|
||||
c.gtk_window_set_child(gtk_window, box);
|
||||
}
|
||||
}
|
||||
|
||||
// Show the window
|
||||
c.gtk_widget_show(window);
|
||||
c.gtk_widget_show(gtk_widget);
|
||||
}
|
||||
|
||||
pub fn updateConfig(
|
||||
@ -407,10 +364,8 @@ pub fn syncAppearance(self: *Window, config: *const configpkg.Config) !void {
|
||||
// Disable the title buttons (close, maximize, minimize, ...)
|
||||
// *inside* the tab overview if CSDs are disabled.
|
||||
// We do spare the search button, though.
|
||||
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and
|
||||
adwaita.enabled(&self.app.config))
|
||||
{
|
||||
if (self.tab_overview) |tab_overview| {
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
c.adw_tab_overview_set_show_start_title_buttons(
|
||||
@ptrCast(tab_overview),
|
||||
@intFromBool(csd_enabled),
|
||||
@ -421,7 +376,6 @@ pub fn syncAppearance(self: *Window, config: *const configpkg.Config) !void {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn toggleCssClass(
|
||||
widget: *c.GtkWidget,
|
||||
@ -506,23 +460,25 @@ pub fn closeTab(self: *Window, tab: *Tab) void {
|
||||
}
|
||||
|
||||
/// Go to the previous tab for a surface.
|
||||
pub fn gotoPreviousTab(self: *Window, surface: *Surface) void {
|
||||
pub fn gotoPreviousTab(self: *Window, surface: *Surface) bool {
|
||||
const tab = surface.container.tab() orelse {
|
||||
log.info("surface is not attached to a tab bar, cannot navigate", .{});
|
||||
return;
|
||||
return false;
|
||||
};
|
||||
self.notebook.gotoPreviousTab(tab);
|
||||
if (!self.notebook.gotoPreviousTab(tab)) return false;
|
||||
self.focusCurrentTab();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Go to the next tab for a surface.
|
||||
pub fn gotoNextTab(self: *Window, surface: *Surface) void {
|
||||
pub fn gotoNextTab(self: *Window, surface: *Surface) bool {
|
||||
const tab = surface.container.tab() orelse {
|
||||
log.info("surface is not attached to a tab bar, cannot navigate", .{});
|
||||
return;
|
||||
return false;
|
||||
};
|
||||
self.notebook.gotoNextTab(tab);
|
||||
if (!self.notebook.gotoNextTab(tab)) return false;
|
||||
self.focusCurrentTab();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Move the current tab for a surface.
|
||||
@ -535,25 +491,26 @@ pub fn moveTab(self: *Window, surface: *Surface, position: c_int) void {
|
||||
}
|
||||
|
||||
/// Go to the last tab for a surface.
|
||||
pub fn gotoLastTab(self: *Window) void {
|
||||
pub fn gotoLastTab(self: *Window) bool {
|
||||
const max = self.notebook.nPages();
|
||||
self.gotoTab(@intCast(max));
|
||||
return self.gotoTab(@intCast(max));
|
||||
}
|
||||
|
||||
/// Go to the specific tab index.
|
||||
pub fn gotoTab(self: *Window, n: usize) void {
|
||||
if (n == 0) return;
|
||||
pub fn gotoTab(self: *Window, n: usize) bool {
|
||||
if (n == 0) return false;
|
||||
const max = self.notebook.nPages();
|
||||
if (max == 0) return;
|
||||
const page_idx = std.math.cast(c_int, n - 1) orelse return;
|
||||
self.notebook.gotoNthTab(@min(page_idx, max - 1));
|
||||
if (max == 0) return false;
|
||||
const page_idx = std.math.cast(c_int, n - 1) orelse return false;
|
||||
if (!self.notebook.gotoNthTab(@min(page_idx, max - 1))) return false;
|
||||
self.focusCurrentTab();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Toggle tab overview (if present)
|
||||
pub fn toggleTabOverview(self: *Window) void {
|
||||
if (self.tab_overview) |tab_overview_widget| {
|
||||
if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(tab_overview_widget));
|
||||
c.adw_tab_overview_set_open(tab_overview, 1 - c.adw_tab_overview_get_open(tab_overview));
|
||||
}
|
||||
@ -600,11 +557,9 @@ pub fn onConfigReloaded(self: *Window) void {
|
||||
}
|
||||
|
||||
pub fn sendToast(self: *Window, title: [:0]const u8) void {
|
||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) return;
|
||||
const toast_overlay = self.toast_overlay orelse return;
|
||||
const toast = c.adw_toast_new(title);
|
||||
c.adw_toast_set_timeout(toast, 3);
|
||||
c.adw_toast_overlay_add_toast(@ptrCast(toast_overlay), toast);
|
||||
c.adw_toast_overlay_add_toast(@ptrCast(self.toast_overlay), toast);
|
||||
}
|
||||
|
||||
fn gtkRealize(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool {
|
||||
@ -707,13 +662,13 @@ fn gtkTabNewClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
||||
/// Create a new tab from the AdwTabOverview. We can't copy gtkTabNewClick
|
||||
/// because we need to return an AdwTabPage from this function.
|
||||
fn gtkNewTabFromOverview(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) ?*c.AdwTabPage {
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) unreachable;
|
||||
const self: *Window = userdataSelf(ud.?);
|
||||
assert((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.versionAtLeast(1, 4, 0) and adwaita.enabled(&self.app.config));
|
||||
|
||||
const alloc = self.app.core_app.alloc;
|
||||
const surface = self.actionSurface();
|
||||
const tab = Tab.create(alloc, self, surface) catch return null;
|
||||
return c.adw_tab_view_get_page(self.notebook.adw.tab_view, @ptrCast(@alignCast(tab.box)));
|
||||
return c.adw_tab_view_get_page(@ptrCast(@alignCast(self.notebook.tab_view)), @ptrCast(@alignCast(tab.box)));
|
||||
}
|
||||
|
||||
fn adwTabOverviewOpen(
|
||||
@ -860,7 +815,7 @@ fn gtkKeyPressed(
|
||||
//
|
||||
// If someone can confidently show or explain that this is not
|
||||
// necessary, please remove this check.
|
||||
if (comptime adwaita.versionAtLeast(1, 4, 0)) {
|
||||
if (adwaita.versionAtLeast(1, 4, 0)) {
|
||||
if (self.tab_overview) |tab_overview_widget| {
|
||||
const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(tab_overview_widget));
|
||||
if (c.adw_tab_overview_get_open(tab_overview) == 0) return 0;
|
||||
@ -888,10 +843,7 @@ fn gtkActionAbout(
|
||||
const icon = "com.mitchellh.ghostty";
|
||||
const website = "https://ghostty.org";
|
||||
|
||||
if ((comptime adwaita.versionAtLeast(1, 5, 0)) and
|
||||
adwaita.versionAtLeast(1, 5, 0) and
|
||||
adwaita.enabled(&self.app.config))
|
||||
{
|
||||
if (adwaita.versionAtLeast(1, 5, 0)) {
|
||||
c.adw_show_about_dialog(
|
||||
@ptrCast(self.window),
|
||||
"application-name",
|
||||
|
@ -1,20 +1,5 @@
|
||||
const std = @import("std");
|
||||
const c = @import("c.zig").c;
|
||||
const build_options = @import("build_options");
|
||||
const Config = @import("../../config.zig").Config;
|
||||
|
||||
/// Returns true if Ghostty is configured to build with libadwaita and
|
||||
/// the configuration has enabled adwaita.
|
||||
///
|
||||
/// For a comptime version of this function, use `versionAtLeast` in
|
||||
/// a comptime context with all the version numbers set to 0.
|
||||
///
|
||||
/// This must be `inline` so that the comptime check noops conditional
|
||||
/// paths that are not enabled.
|
||||
pub inline fn enabled(config: *const Config) bool {
|
||||
return build_options.adwaita and
|
||||
config.@"gtk-adwaita";
|
||||
}
|
||||
|
||||
/// Verifies that the running libadwaita version is at least the given
|
||||
/// version. This will return false if Ghostty is configured to
|
||||
@ -33,8 +18,6 @@ pub inline fn versionAtLeast(
|
||||
comptime minor: u16,
|
||||
comptime micro: u16,
|
||||
) bool {
|
||||
if (comptime !build_options.adwaita) return false;
|
||||
|
||||
// If our header has lower versions than the given version,
|
||||
// we can return false immediately. This prevents us from
|
||||
// compiling against unknown symbols and makes runtime checks
|
||||
|
32
src/apprt/gtk/builder_check.zig
Normal file
@ -0,0 +1,32 @@
|
||||
const std = @import("std");
|
||||
const build_options = @import("build_options");
|
||||
|
||||
const gtk = @import("gtk");
|
||||
const adw = @import("adw");
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const alloc = gpa.allocator();
|
||||
|
||||
const filename = filename: {
|
||||
var it = try std.process.argsWithAllocator(alloc);
|
||||
defer it.deinit();
|
||||
|
||||
_ = it.next() orelse return error.NoFilename;
|
||||
break :filename try alloc.dupeZ(u8, it.next() orelse return error.NoFilename);
|
||||
};
|
||||
defer alloc.free(filename);
|
||||
|
||||
const data = try std.fs.cwd().readFileAllocOptions(alloc, filename, std.math.maxInt(u16), null, 1, 0);
|
||||
defer alloc.free(data);
|
||||
|
||||
if (gtk.initCheck() == 0) {
|
||||
std.debug.print("{s}: skipping builder check because we can't connect to display!\n", .{filename});
|
||||
return;
|
||||
}
|
||||
|
||||
adw.init();
|
||||
|
||||
const builder = gtk.Builder.newFromString(data.ptr, @intCast(data.len));
|
||||
defer builder.unref();
|
||||
}
|
@ -3,9 +3,7 @@ const build_options = @import("build_options");
|
||||
/// Imported C API directly from header files
|
||||
pub const c = @cImport({
|
||||
@cInclude("gtk/gtk.h");
|
||||
if (build_options.adwaita) {
|
||||
@cInclude("libadwaita-1/adwaita.h");
|
||||
}
|
||||
@cInclude("adwaita.h");
|
||||
|
||||
if (build_options.x11) {
|
||||
// Add in X11-specific GDK backend which we use for specific things
|
||||
|
@ -53,29 +53,33 @@ const icons = [_]struct {
|
||||
},
|
||||
};
|
||||
|
||||
pub const gresource_xml = comptimeGenerateGResourceXML();
|
||||
pub const ui_files = [_][]const u8{};
|
||||
pub const blueprint_files = [_][]const u8{};
|
||||
|
||||
fn comptimeGenerateGResourceXML() []const u8 {
|
||||
comptime {
|
||||
@setEvalBranchQuota(13000);
|
||||
var counter = std.io.countingWriter(std.io.null_writer);
|
||||
try writeGResourceXML(&counter.writer());
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const alloc = gpa.allocator();
|
||||
|
||||
var buf: [counter.bytes_written]u8 = undefined;
|
||||
var stream = std.io.fixedBufferStream(&buf);
|
||||
try writeGResourceXML(stream.writer());
|
||||
const final = buf;
|
||||
return final[0..stream.getWritten().len];
|
||||
var extra_ui_files = std.ArrayList([]const u8).init(alloc);
|
||||
defer {
|
||||
for (extra_ui_files.items) |item| alloc.free(item);
|
||||
extra_ui_files.deinit();
|
||||
}
|
||||
|
||||
var it = try std.process.argsWithAllocator(alloc);
|
||||
defer it.deinit();
|
||||
|
||||
while (it.next()) |filename| {
|
||||
if (std.mem.eql(u8, std.fs.path.extension(filename), ".ui")) {
|
||||
try extra_ui_files.append(try alloc.dupe(u8, filename));
|
||||
}
|
||||
}
|
||||
|
||||
fn writeGResourceXML(writer: anytype) !void {
|
||||
const writer = std.io.getStdOut().writer();
|
||||
|
||||
try writer.writeAll(
|
||||
\\<?xml version="1.0" encoding="UTF-8"?>
|
||||
\\<gresources>
|
||||
\\
|
||||
);
|
||||
try writer.writeAll(
|
||||
\\ <gresource prefix="/com/mitchellh/ghostty">
|
||||
\\
|
||||
);
|
||||
@ -87,9 +91,6 @@ fn writeGResourceXML(writer: anytype) !void {
|
||||
}
|
||||
try writer.writeAll(
|
||||
\\ </gresource>
|
||||
\\
|
||||
);
|
||||
try writer.writeAll(
|
||||
\\ <gresource prefix="/com/mitchellh/ghostty/icons">
|
||||
\\
|
||||
);
|
||||
@ -99,6 +100,23 @@ fn writeGResourceXML(writer: anytype) !void {
|
||||
.{ icon.alias, icon.source },
|
||||
);
|
||||
}
|
||||
try writer.writeAll(
|
||||
\\ </gresource>
|
||||
\\ <gresource prefix="/com/mitchellh/ghostty/ui">
|
||||
\\
|
||||
);
|
||||
for (ui_files) |ui_file| {
|
||||
try writer.print(
|
||||
" <file compressed=\"true\" preprocess=\"xml-stripblanks\" alias=\"{0s}.ui\">src/apprt/gtk/ui/{0s}.ui</file>\n",
|
||||
.{ui_file},
|
||||
);
|
||||
}
|
||||
for (extra_ui_files.items) |ui_file| {
|
||||
try writer.print(
|
||||
" <file compressed=\"true\" preprocess=\"xml-stripblanks\" alias=\"{s}\">{s}</file>\n",
|
||||
.{ std.fs.path.basename(ui_file), ui_file },
|
||||
);
|
||||
}
|
||||
try writer.writeAll(
|
||||
\\ </gresource>
|
||||
\\</gresources>
|
||||
@ -107,12 +125,24 @@ fn writeGResourceXML(writer: anytype) !void {
|
||||
}
|
||||
|
||||
pub const dependencies = deps: {
|
||||
var deps: [css_files.len + icons.len][]const u8 = undefined;
|
||||
for (css_files, 0..) |css_file, i| {
|
||||
deps[i] = std.fmt.comptimePrint("src/apprt/gtk/{s}", .{css_file});
|
||||
const total = css_files.len + icons.len + ui_files.len + blueprint_files.len;
|
||||
var deps: [total][]const u8 = undefined;
|
||||
var index: usize = 0;
|
||||
for (css_files) |css_file| {
|
||||
deps[index] = std.fmt.comptimePrint("src/apprt/gtk/{s}", .{css_file});
|
||||
index += 1;
|
||||
}
|
||||
for (icons, css_files.len..) |icon, i| {
|
||||
deps[i] = std.fmt.comptimePrint("images/icons/icon_{s}.png", .{icon.source});
|
||||
for (icons) |icon| {
|
||||
deps[index] = std.fmt.comptimePrint("images/icons/icon_{s}.png", .{icon.source});
|
||||
index += 1;
|
||||
}
|
||||
for (ui_files) |ui_file| {
|
||||
deps[index] = std.fmt.comptimePrint("src/apprt/gtk/ui/{s}.ui", .{ui_file});
|
||||
index += 1;
|
||||
}
|
||||
for (blueprint_files) |blueprint_file| {
|
||||
deps[index] = std.fmt.comptimePrint("src/apprt/gtk/ui/{s}.blp", .{blueprint_file});
|
||||
index += 1;
|
||||
}
|
||||
break :deps deps;
|
||||
};
|
||||
|
@ -1,58 +1,59 @@
|
||||
const HeaderBar = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
const Window = @import("Window.zig");
|
||||
const adwaita = @import("adwaita.zig");
|
||||
|
||||
const HeaderBarAdw = @import("headerbar_adw.zig");
|
||||
const HeaderBarGtk = @import("headerbar_gtk.zig");
|
||||
/// the Adwaita headerbar widget
|
||||
headerbar: *c.AdwHeaderBar,
|
||||
|
||||
pub const HeaderBar = union(enum) {
|
||||
adw: HeaderBarAdw,
|
||||
gtk: HeaderBarGtk,
|
||||
/// the Adwaita window title widget
|
||||
title: *c.AdwWindowTitle,
|
||||
|
||||
pub fn init(self: *HeaderBar) void {
|
||||
const window: *Window = @fieldParentPtr("headerbar", self);
|
||||
if ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.enabled(&window.app.config)) {
|
||||
HeaderBarAdw.init(self);
|
||||
} else {
|
||||
HeaderBarGtk.init(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setVisible(self: HeaderBar, visible: bool) void {
|
||||
switch (self) {
|
||||
inline else => |v| v.setVisible(visible),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn asWidget(self: HeaderBar) *c.GtkWidget {
|
||||
return switch (self) {
|
||||
inline else => |v| v.asWidget(),
|
||||
self.* = .{
|
||||
.headerbar = @ptrCast(@alignCast(c.adw_header_bar_new())),
|
||||
.title = @ptrCast(@alignCast(c.adw_window_title_new(
|
||||
c.gtk_window_get_title(window.window) orelse "Ghostty",
|
||||
null,
|
||||
))),
|
||||
};
|
||||
c.adw_header_bar_set_title_widget(
|
||||
self.headerbar,
|
||||
@ptrCast(@alignCast(self.title)),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn packEnd(self: HeaderBar, widget: *c.GtkWidget) void {
|
||||
switch (self) {
|
||||
inline else => |v| v.packEnd(widget),
|
||||
}
|
||||
pub fn setVisible(self: *const HeaderBar, visible: bool) void {
|
||||
c.gtk_widget_set_visible(self.asWidget(), @intFromBool(visible));
|
||||
}
|
||||
|
||||
pub fn packStart(self: HeaderBar, widget: *c.GtkWidget) void {
|
||||
switch (self) {
|
||||
inline else => |v| v.packStart(widget),
|
||||
}
|
||||
pub fn asWidget(self: *const HeaderBar) *c.GtkWidget {
|
||||
return @ptrCast(@alignCast(self.headerbar));
|
||||
}
|
||||
|
||||
pub fn setTitle(self: HeaderBar, title: [:0]const u8) void {
|
||||
switch (self) {
|
||||
inline else => |v| v.setTitle(title),
|
||||
}
|
||||
pub fn packEnd(self: *const HeaderBar, widget: *c.GtkWidget) void {
|
||||
c.adw_header_bar_pack_end(
|
||||
@ptrCast(@alignCast(self.headerbar)),
|
||||
widget,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn setSubtitle(self: HeaderBar, subtitle: [:0]const u8) void {
|
||||
switch (self) {
|
||||
inline else => |v| v.setSubtitle(subtitle),
|
||||
pub fn packStart(self: *const HeaderBar, widget: *c.GtkWidget) void {
|
||||
c.adw_header_bar_pack_start(
|
||||
@ptrCast(@alignCast(self.headerbar)),
|
||||
widget,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn setTitle(self: *const HeaderBar, title: [:0]const u8) void {
|
||||
const window: *const Window = @fieldParentPtr("headerbar", self);
|
||||
c.gtk_window_set_title(window.window, title);
|
||||
c.adw_window_title_set_title(self.title, title);
|
||||
}
|
||||
|
||||
pub fn setSubtitle(self: *const HeaderBar, subtitle: [:0]const u8) void {
|
||||
c.adw_window_title_set_subtitle(self.title, subtitle);
|
||||
}
|
||||
};
|
||||
|
@ -1,78 +0,0 @@
|
||||
const HeaderBarAdw = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
const Window = @import("Window.zig");
|
||||
const adwaita = @import("adwaita.zig");
|
||||
|
||||
const HeaderBar = @import("headerbar.zig").HeaderBar;
|
||||
|
||||
const AdwHeaderBar = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwHeaderBar else anyopaque;
|
||||
const AdwWindowTitle = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwWindowTitle else anyopaque;
|
||||
|
||||
/// the window that this headerbar is attached to
|
||||
window: *Window,
|
||||
/// the Adwaita headerbar widget
|
||||
headerbar: *AdwHeaderBar,
|
||||
/// the Adwaita window title widget
|
||||
title: *AdwWindowTitle,
|
||||
|
||||
pub fn init(headerbar: *HeaderBar) void {
|
||||
if (!adwaita.versionAtLeast(0, 0, 0)) return;
|
||||
|
||||
const window: *Window = @fieldParentPtr("headerbar", headerbar);
|
||||
headerbar.* = .{
|
||||
.adw = .{
|
||||
.window = window,
|
||||
.headerbar = @ptrCast(@alignCast(c.adw_header_bar_new())),
|
||||
.title = @ptrCast(@alignCast(c.adw_window_title_new(
|
||||
c.gtk_window_get_title(window.window) orelse "Ghostty",
|
||||
null,
|
||||
))),
|
||||
},
|
||||
};
|
||||
c.adw_header_bar_set_title_widget(
|
||||
headerbar.adw.headerbar,
|
||||
@ptrCast(@alignCast(headerbar.adw.title)),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn setVisible(self: HeaderBarAdw, visible: bool) void {
|
||||
c.gtk_widget_set_visible(self.asWidget(), @intFromBool(visible));
|
||||
}
|
||||
|
||||
pub fn asWidget(self: HeaderBarAdw) *c.GtkWidget {
|
||||
return @ptrCast(@alignCast(self.headerbar));
|
||||
}
|
||||
|
||||
pub fn packEnd(self: HeaderBarAdw, widget: *c.GtkWidget) void {
|
||||
if (comptime adwaita.versionAtLeast(0, 0, 0)) {
|
||||
c.adw_header_bar_pack_end(
|
||||
@ptrCast(@alignCast(self.headerbar)),
|
||||
widget,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn packStart(self: HeaderBarAdw, widget: *c.GtkWidget) void {
|
||||
if (comptime adwaita.versionAtLeast(0, 0, 0)) {
|
||||
c.adw_header_bar_pack_start(
|
||||
@ptrCast(@alignCast(self.headerbar)),
|
||||
widget,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setTitle(self: HeaderBarAdw, title: [:0]const u8) void {
|
||||
c.gtk_window_set_title(self.window.window, title);
|
||||
if (comptime adwaita.versionAtLeast(0, 0, 0)) {
|
||||
c.adw_window_title_set_title(self.title, title);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setSubtitle(self: HeaderBarAdw, subtitle: [:0]const u8) void {
|
||||
if (comptime adwaita.versionAtLeast(0, 0, 0)) {
|
||||
c.adw_window_title_set_subtitle(self.title, subtitle);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
const HeaderBarGtk = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
const Window = @import("Window.zig");
|
||||
const adwaita = @import("adwaita.zig");
|
||||
|
||||
const HeaderBar = @import("headerbar.zig").HeaderBar;
|
||||
|
||||
/// the window that this headarbar is attached to
|
||||
window: *Window,
|
||||
/// the GTK headerbar widget
|
||||
headerbar: *c.GtkHeaderBar,
|
||||
|
||||
pub fn init(headerbar: *HeaderBar) void {
|
||||
const window: *Window = @fieldParentPtr("headerbar", headerbar);
|
||||
headerbar.* = .{
|
||||
.gtk = .{
|
||||
.window = window,
|
||||
.headerbar = @ptrCast(c.gtk_header_bar_new()),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setVisible(self: HeaderBarGtk, visible: bool) void {
|
||||
c.gtk_widget_set_visible(self.asWidget(), @intFromBool(visible));
|
||||
}
|
||||
|
||||
pub fn asWidget(self: HeaderBarGtk) *c.GtkWidget {
|
||||
return @ptrCast(@alignCast(self.headerbar));
|
||||
}
|
||||
|
||||
pub fn packEnd(self: HeaderBarGtk, widget: *c.GtkWidget) void {
|
||||
c.gtk_header_bar_pack_end(
|
||||
@ptrCast(@alignCast(self.headerbar)),
|
||||
widget,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn packStart(self: HeaderBarGtk, widget: *c.GtkWidget) void {
|
||||
c.gtk_header_bar_pack_start(
|
||||
@ptrCast(@alignCast(self.headerbar)),
|
||||
widget,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn setTitle(self: HeaderBarGtk, title: [:0]const u8) void {
|
||||
c.gtk_window_set_title(self.window.window, title);
|
||||
}
|
||||
|
||||
pub fn setSubtitle(_: HeaderBarGtk, _: [:0]const u8) void {}
|
@ -1,169 +0,0 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
const Window = @import("Window.zig");
|
||||
const Tab = @import("Tab.zig");
|
||||
const NotebookAdw = @import("notebook_adw.zig").NotebookAdw;
|
||||
const NotebookGtk = @import("notebook_gtk.zig").NotebookGtk;
|
||||
const adwaita = @import("adwaita.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
const AdwTabView = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwTabView else anyopaque;
|
||||
|
||||
/// An abstraction over the GTK notebook and Adwaita tab view to manage
|
||||
/// all the terminal tabs in a window.
|
||||
/// An abstraction over the GTK notebook and Adwaita tab view to manage
|
||||
/// all the terminal tabs in a window.
|
||||
pub const Notebook = union(enum) {
|
||||
adw: NotebookAdw,
|
||||
gtk: NotebookGtk,
|
||||
|
||||
pub fn init(self: *Notebook) void {
|
||||
const window: *Window = @fieldParentPtr("notebook", self);
|
||||
const app = window.app;
|
||||
if (adwaita.enabled(&app.config)) return NotebookAdw.init(self);
|
||||
|
||||
return NotebookGtk.init(self);
|
||||
}
|
||||
|
||||
pub fn asWidget(self: *Notebook) *c.GtkWidget {
|
||||
return switch (self.*) {
|
||||
.adw => |*adw| adw.asWidget(),
|
||||
.gtk => |*gtk| gtk.asWidget(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn nPages(self: *Notebook) c_int {
|
||||
return switch (self.*) {
|
||||
.adw => |*adw| adw.nPages(),
|
||||
.gtk => |*gtk| gtk.nPages(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the index of the currently selected page.
|
||||
/// Returns null if the notebook has no pages.
|
||||
fn currentPage(self: *Notebook) ?c_int {
|
||||
return switch (self.*) {
|
||||
.adw => |*adw| adw.currentPage(),
|
||||
.gtk => |*gtk| gtk.currentPage(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the currently selected tab or null if there are none.
|
||||
pub fn currentTab(self: *Notebook) ?*Tab {
|
||||
return switch (self.*) {
|
||||
.adw => |*adw| adw.currentTab(),
|
||||
.gtk => |*gtk| gtk.currentTab(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn gotoNthTab(self: *Notebook, position: c_int) void {
|
||||
switch (self.*) {
|
||||
.adw => |*adw| adw.gotoNthTab(position),
|
||||
.gtk => |*gtk| gtk.gotoNthTab(position),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getTabPosition(self: *Notebook, tab: *Tab) ?c_int {
|
||||
return switch (self.*) {
|
||||
.adw => |*adw| adw.getTabPosition(tab),
|
||||
.gtk => |*gtk| gtk.getTabPosition(tab),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn gotoPreviousTab(self: *Notebook, tab: *Tab) void {
|
||||
const page_idx = self.getTabPosition(tab) orelse return;
|
||||
|
||||
// The next index is the previous or we wrap around.
|
||||
const next_idx = if (page_idx > 0) page_idx - 1 else next_idx: {
|
||||
const max = self.nPages();
|
||||
break :next_idx max -| 1;
|
||||
};
|
||||
|
||||
// Do nothing if we have one tab
|
||||
if (next_idx == page_idx) return;
|
||||
|
||||
self.gotoNthTab(next_idx);
|
||||
}
|
||||
|
||||
pub fn gotoNextTab(self: *Notebook, tab: *Tab) void {
|
||||
const page_idx = self.getTabPosition(tab) orelse return;
|
||||
|
||||
const max = self.nPages() -| 1;
|
||||
const next_idx = if (page_idx < max) page_idx + 1 else 0;
|
||||
if (next_idx == page_idx) return;
|
||||
|
||||
self.gotoNthTab(next_idx);
|
||||
}
|
||||
|
||||
pub fn moveTab(self: *Notebook, tab: *Tab, position: c_int) void {
|
||||
const page_idx = self.getTabPosition(tab) orelse return;
|
||||
|
||||
const max = self.nPages() -| 1;
|
||||
var new_position: c_int = page_idx + position;
|
||||
|
||||
if (new_position < 0) {
|
||||
new_position = max + new_position + 1;
|
||||
} else if (new_position > max) {
|
||||
new_position = new_position - max - 1;
|
||||
}
|
||||
|
||||
if (new_position == page_idx) return;
|
||||
self.reorderPage(tab, new_position);
|
||||
}
|
||||
|
||||
pub fn reorderPage(self: *Notebook, tab: *Tab, position: c_int) void {
|
||||
switch (self.*) {
|
||||
.adw => |*adw| adw.reorderPage(tab, position),
|
||||
.gtk => |*gtk| gtk.reorderPage(tab, position),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setTabLabel(self: *Notebook, tab: *Tab, title: [:0]const u8) void {
|
||||
switch (self.*) {
|
||||
.adw => |*adw| adw.setTabLabel(tab, title),
|
||||
.gtk => |*gtk| gtk.setTabLabel(tab, title),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setTabTooltip(self: *Notebook, tab: *Tab, tooltip: [:0]const u8) void {
|
||||
switch (self.*) {
|
||||
.adw => |*adw| adw.setTabTooltip(tab, tooltip),
|
||||
.gtk => |*gtk| gtk.setTabTooltip(tab, tooltip),
|
||||
}
|
||||
}
|
||||
|
||||
fn newTabInsertPosition(self: *Notebook, tab: *Tab) c_int {
|
||||
const numPages = self.nPages();
|
||||
return switch (tab.window.app.config.@"window-new-tab-position") {
|
||||
.current => if (self.currentPage()) |page| page + 1 else numPages,
|
||||
.end => numPages,
|
||||
};
|
||||
}
|
||||
|
||||
/// Adds a new tab with the given title to the notebook.
|
||||
pub fn addTab(self: *Notebook, tab: *Tab, title: [:0]const u8) void {
|
||||
const position = self.newTabInsertPosition(tab);
|
||||
switch (self.*) {
|
||||
.adw => |*adw| adw.addTab(tab, position, title),
|
||||
.gtk => |*gtk| gtk.addTab(tab, position, title),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn closeTab(self: *Notebook, tab: *Tab) void {
|
||||
switch (self.*) {
|
||||
.adw => |*adw| adw.closeTab(tab),
|
||||
.gtk => |*gtk| gtk.closeTab(tab),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn createWindow(currentWindow: *Window) !*Window {
|
||||
const alloc = currentWindow.app.core_app.alloc;
|
||||
const app = currentWindow.app;
|
||||
|
||||
// Create a new window
|
||||
return Window.create(alloc, app);
|
||||
}
|
@ -1,209 +0,0 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
const Window = @import("Window.zig");
|
||||
const Tab = @import("Tab.zig");
|
||||
const Notebook = @import("notebook.zig").Notebook;
|
||||
const createWindow = @import("notebook.zig").createWindow;
|
||||
const adwaita = @import("adwaita.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
const AdwTabView = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwTabView else anyopaque;
|
||||
const AdwTabPage = if (adwaita.versionAtLeast(0, 0, 0)) c.AdwTabPage else anyopaque;
|
||||
|
||||
pub const NotebookAdw = struct {
|
||||
/// the tab view
|
||||
tab_view: *AdwTabView,
|
||||
|
||||
/// Set to true so that the adw close-page handler knows we're forcing
|
||||
/// and to allow a close to happen with no confirm. This is a bit of a hack
|
||||
/// because we currently use GTK alerts to confirm tab close and they
|
||||
/// don't carry with them the ADW state that we are confirming or not.
|
||||
/// Long term we should move to ADW alerts so we can know if we are
|
||||
/// confirming or not.
|
||||
forcing_close: bool = false,
|
||||
|
||||
pub fn init(notebook: *Notebook) void {
|
||||
const window: *Window = @fieldParentPtr("notebook", notebook);
|
||||
const app = window.app;
|
||||
assert(adwaita.enabled(&app.config));
|
||||
|
||||
const tab_view: *c.AdwTabView = c.adw_tab_view_new().?;
|
||||
c.gtk_widget_add_css_class(@ptrCast(@alignCast(tab_view)), "notebook");
|
||||
|
||||
if (comptime adwaita.versionAtLeast(1, 2, 0) and adwaita.versionAtLeast(1, 2, 0)) {
|
||||
// Adwaita enables all of the shortcuts by default.
|
||||
// We want to manage keybindings ourselves.
|
||||
c.adw_tab_view_remove_shortcuts(tab_view, c.ADW_TAB_VIEW_SHORTCUT_ALL_SHORTCUTS);
|
||||
}
|
||||
|
||||
notebook.* = .{
|
||||
.adw = .{
|
||||
.tab_view = tab_view,
|
||||
},
|
||||
};
|
||||
|
||||
_ = c.g_signal_connect_data(tab_view, "page-attached", c.G_CALLBACK(&adwPageAttached), window, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(tab_view, "close-page", c.G_CALLBACK(&adwClosePage), window, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(tab_view, "create-window", c.G_CALLBACK(&adwTabViewCreateWindow), window, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(tab_view, "notify::selected-page", c.G_CALLBACK(&adwSelectPage), window, null, c.G_CONNECT_DEFAULT);
|
||||
}
|
||||
|
||||
pub fn asWidget(self: *NotebookAdw) *c.GtkWidget {
|
||||
return @ptrCast(@alignCast(self.tab_view));
|
||||
}
|
||||
|
||||
pub fn nPages(self: *NotebookAdw) c_int {
|
||||
if (comptime adwaita.versionAtLeast(0, 0, 0))
|
||||
return c.adw_tab_view_get_n_pages(self.tab_view)
|
||||
else
|
||||
unreachable;
|
||||
}
|
||||
|
||||
/// Returns the index of the currently selected page.
|
||||
/// Returns null if the notebook has no pages.
|
||||
pub fn currentPage(self: *NotebookAdw) ?c_int {
|
||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
||||
const page = c.adw_tab_view_get_selected_page(self.tab_view) orelse return null;
|
||||
return c.adw_tab_view_get_page_position(self.tab_view, page);
|
||||
}
|
||||
|
||||
/// Returns the currently selected tab or null if there are none.
|
||||
pub fn currentTab(self: *NotebookAdw) ?*Tab {
|
||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
||||
const page = c.adw_tab_view_get_selected_page(self.tab_view) orelse return null;
|
||||
const child = c.adw_tab_page_get_child(page);
|
||||
return @ptrCast(@alignCast(
|
||||
c.g_object_get_data(@ptrCast(child), Tab.GHOSTTY_TAB) orelse return null,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn gotoNthTab(self: *NotebookAdw, position: c_int) void {
|
||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
||||
const page_to_select = c.adw_tab_view_get_nth_page(self.tab_view, position);
|
||||
c.adw_tab_view_set_selected_page(self.tab_view, page_to_select);
|
||||
}
|
||||
|
||||
pub fn getTabPosition(self: *NotebookAdw, tab: *Tab) ?c_int {
|
||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
||||
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box)) orelse return null;
|
||||
return c.adw_tab_view_get_page_position(self.tab_view, page);
|
||||
}
|
||||
|
||||
pub fn reorderPage(self: *NotebookAdw, tab: *Tab, position: c_int) void {
|
||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
||||
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
|
||||
_ = c.adw_tab_view_reorder_page(self.tab_view, page, position);
|
||||
}
|
||||
|
||||
pub fn setTabLabel(self: *NotebookAdw, tab: *Tab, title: [:0]const u8) void {
|
||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
||||
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
|
||||
c.adw_tab_page_set_title(page, title.ptr);
|
||||
}
|
||||
|
||||
pub fn setTabTooltip(self: *NotebookAdw, tab: *Tab, tooltip: [:0]const u8) void {
|
||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
||||
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box));
|
||||
c.adw_tab_page_set_tooltip(page, tooltip.ptr);
|
||||
}
|
||||
|
||||
pub fn addTab(self: *NotebookAdw, tab: *Tab, position: c_int, title: [:0]const u8) void {
|
||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
||||
const box_widget: *c.GtkWidget = @ptrCast(tab.box);
|
||||
const page = c.adw_tab_view_insert(self.tab_view, box_widget, position);
|
||||
c.adw_tab_page_set_title(page, title.ptr);
|
||||
c.adw_tab_view_set_selected_page(self.tab_view, page);
|
||||
}
|
||||
|
||||
pub fn closeTab(self: *NotebookAdw, tab: *Tab) void {
|
||||
if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable;
|
||||
|
||||
// closeTab always expects to close unconditionally so we mark this
|
||||
// as true so that the close_page call below doesn't request
|
||||
// confirmation.
|
||||
self.forcing_close = true;
|
||||
const n = self.nPages();
|
||||
defer {
|
||||
// self becomes invalid if we close the last page because we close
|
||||
// the whole window
|
||||
if (n > 1) self.forcing_close = false;
|
||||
}
|
||||
|
||||
const page = c.adw_tab_view_get_page(self.tab_view, @ptrCast(tab.box)) orelse return;
|
||||
c.adw_tab_view_close_page(self.tab_view, page);
|
||||
|
||||
// If we have no more tabs we close the window
|
||||
if (self.nPages() == 0) {
|
||||
const window = tab.window.window;
|
||||
|
||||
// libadw versions <= 1.3.x leak the final page view
|
||||
// which causes our surface to not properly cleanup. We
|
||||
// unref to force the cleanup. This will trigger a critical
|
||||
// warning from GTK, but I don't know any other workaround.
|
||||
// Note: I'm not actually sure if 1.4.0 contains the fix,
|
||||
// I just know that 1.3.x is broken and 1.5.1 is fixed.
|
||||
// If we know that 1.4.0 is fixed, we can change this.
|
||||
if (!adwaita.versionAtLeast(1, 4, 0)) {
|
||||
c.g_object_unref(tab.box);
|
||||
}
|
||||
|
||||
// `self` will become invalid after this call because it will have
|
||||
// been freed up as part of the process of closing the window.
|
||||
c.gtk_window_destroy(window);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn adwPageAttached(_: *AdwTabView, page: *c.AdwTabPage, _: c_int, ud: ?*anyopaque) callconv(.C) void {
|
||||
const window: *Window = @ptrCast(@alignCast(ud.?));
|
||||
|
||||
const child = c.adw_tab_page_get_child(page);
|
||||
const tab: *Tab = @ptrCast(@alignCast(c.g_object_get_data(@ptrCast(child), Tab.GHOSTTY_TAB) orelse return));
|
||||
tab.window = window;
|
||||
|
||||
window.focusCurrentTab();
|
||||
}
|
||||
|
||||
fn adwClosePage(
|
||||
_: *AdwTabView,
|
||||
page: *c.AdwTabPage,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) c.gboolean {
|
||||
const child = c.adw_tab_page_get_child(page);
|
||||
const tab: *Tab = @ptrCast(@alignCast(c.g_object_get_data(
|
||||
@ptrCast(child),
|
||||
Tab.GHOSTTY_TAB,
|
||||
) orelse return 0));
|
||||
|
||||
const window: *Window = @ptrCast(@alignCast(ud.?));
|
||||
const notebook = window.notebook.adw;
|
||||
c.adw_tab_view_close_page_finish(
|
||||
notebook.tab_view,
|
||||
page,
|
||||
@intFromBool(notebook.forcing_close),
|
||||
);
|
||||
if (!notebook.forcing_close) tab.closeWithConfirmation();
|
||||
return 1;
|
||||
}
|
||||
|
||||
fn adwTabViewCreateWindow(
|
||||
_: *AdwTabView,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) ?*AdwTabView {
|
||||
const currentWindow: *Window = @ptrCast(@alignCast(ud.?));
|
||||
const window = createWindow(currentWindow) catch |err| {
|
||||
log.warn("error creating new window error={}", .{err});
|
||||
return null;
|
||||
};
|
||||
return window.notebook.adw.tab_view;
|
||||
}
|
||||
|
||||
fn adwSelectPage(_: *c.GObject, _: *c.GParamSpec, ud: ?*anyopaque) void {
|
||||
const window: *Window = @ptrCast(@alignCast(ud.?));
|
||||
const page = c.adw_tab_view_get_selected_page(window.notebook.adw.tab_view) orelse return;
|
||||
const title = c.adw_tab_page_get_title(page);
|
||||
window.setTitle(std.mem.span(title));
|
||||
}
|
@ -1,304 +0,0 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const c = @import("c.zig").c;
|
||||
|
||||
const Window = @import("Window.zig");
|
||||
const Tab = @import("Tab.zig");
|
||||
const Notebook = @import("notebook.zig").Notebook;
|
||||
const createWindow = @import("notebook.zig").createWindow;
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
/// An abstraction over the GTK notebook and Adwaita tab view to manage
|
||||
/// all the terminal tabs in a window.
|
||||
pub const NotebookGtk = struct {
|
||||
notebook: *c.GtkNotebook,
|
||||
|
||||
pub fn init(notebook: *Notebook) void {
|
||||
const window: *Window = @fieldParentPtr("notebook", notebook);
|
||||
const app = window.app;
|
||||
|
||||
// Create a notebook to hold our tabs.
|
||||
const notebook_widget: *c.GtkWidget = c.gtk_notebook_new();
|
||||
c.gtk_widget_add_css_class(notebook_widget, "notebook");
|
||||
|
||||
const gtk_notebook: *c.GtkNotebook = @ptrCast(notebook_widget);
|
||||
const notebook_tab_pos: c_uint = switch (app.config.@"gtk-tabs-location") {
|
||||
.top, .hidden => c.GTK_POS_TOP,
|
||||
.bottom => c.GTK_POS_BOTTOM,
|
||||
.left => c.GTK_POS_LEFT,
|
||||
.right => c.GTK_POS_RIGHT,
|
||||
};
|
||||
c.gtk_notebook_set_tab_pos(gtk_notebook, notebook_tab_pos);
|
||||
c.gtk_notebook_set_scrollable(gtk_notebook, 1);
|
||||
c.gtk_notebook_set_show_tabs(gtk_notebook, 0);
|
||||
c.gtk_notebook_set_show_border(gtk_notebook, 0);
|
||||
|
||||
// This enables all Ghostty terminal tabs to be exchanged across windows.
|
||||
c.gtk_notebook_set_group_name(gtk_notebook, "ghostty-terminal-tabs");
|
||||
|
||||
// This is important so the notebook expands to fit available space.
|
||||
// Otherwise, it will be zero/zero in the box below.
|
||||
c.gtk_widget_set_vexpand(notebook_widget, 1);
|
||||
c.gtk_widget_set_hexpand(notebook_widget, 1);
|
||||
|
||||
// Remove the background from the stack widget
|
||||
const stack = c.gtk_widget_get_last_child(notebook_widget);
|
||||
c.gtk_widget_add_css_class(stack, "transparent");
|
||||
|
||||
notebook.* = .{
|
||||
.gtk = .{
|
||||
.notebook = gtk_notebook,
|
||||
},
|
||||
};
|
||||
|
||||
// All of our events
|
||||
_ = c.g_signal_connect_data(gtk_notebook, "page-added", c.G_CALLBACK(>kPageAdded), window, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gtk_notebook, "page-removed", c.G_CALLBACK(>kPageRemoved), window, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gtk_notebook, "switch-page", c.G_CALLBACK(>kSwitchPage), window, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gtk_notebook, "create-window", c.G_CALLBACK(>kNotebookCreateWindow), window, null, c.G_CONNECT_DEFAULT);
|
||||
}
|
||||
|
||||
/// return the underlying widget as a generic GtkWidget
|
||||
pub fn asWidget(self: *NotebookGtk) *c.GtkWidget {
|
||||
return @ptrCast(@alignCast(self.notebook));
|
||||
}
|
||||
|
||||
/// returns the number of pages in the notebook
|
||||
pub fn nPages(self: *NotebookGtk) c_int {
|
||||
return c.gtk_notebook_get_n_pages(self.notebook);
|
||||
}
|
||||
|
||||
/// Returns the index of the currently selected page.
|
||||
/// Returns null if the notebook has no pages.
|
||||
pub fn currentPage(self: *NotebookGtk) ?c_int {
|
||||
const current = c.gtk_notebook_get_current_page(self.notebook);
|
||||
return if (current == -1) null else current;
|
||||
}
|
||||
|
||||
/// Returns the currently selected tab or null if there are none.
|
||||
pub fn currentTab(self: *NotebookGtk) ?*Tab {
|
||||
log.warn("currentTab", .{});
|
||||
const page = self.currentPage() orelse return null;
|
||||
const child = c.gtk_notebook_get_nth_page(self.notebook, page);
|
||||
return @ptrCast(@alignCast(
|
||||
c.g_object_get_data(@ptrCast(child), Tab.GHOSTTY_TAB) orelse return null,
|
||||
));
|
||||
}
|
||||
|
||||
/// focus the nth tab
|
||||
pub fn gotoNthTab(self: *NotebookGtk, position: c_int) void {
|
||||
c.gtk_notebook_set_current_page(self.notebook, position);
|
||||
}
|
||||
|
||||
/// get the position of the current tab
|
||||
pub fn getTabPosition(self: *NotebookGtk, tab: *Tab) ?c_int {
|
||||
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(tab.box)) orelse return null;
|
||||
return getNotebookPageIndex(page);
|
||||
}
|
||||
|
||||
pub fn reorderPage(self: *NotebookGtk, tab: *Tab, position: c_int) void {
|
||||
c.gtk_notebook_reorder_child(self.notebook, @ptrCast(tab.box), position);
|
||||
}
|
||||
|
||||
pub fn setTabLabel(_: *NotebookGtk, tab: *Tab, title: [:0]const u8) void {
|
||||
c.gtk_label_set_text(tab.label_text, title.ptr);
|
||||
}
|
||||
|
||||
pub fn setTabTooltip(_: *NotebookGtk, tab: *Tab, tooltip: [:0]const u8) void {
|
||||
c.gtk_widget_set_tooltip_text(@ptrCast(@alignCast(tab.label_text)), tooltip.ptr);
|
||||
}
|
||||
|
||||
/// Adds a new tab with the given title to the notebook.
|
||||
pub fn addTab(self: *NotebookGtk, tab: *Tab, position: c_int, title: [:0]const u8) void {
|
||||
const box_widget: *c.GtkWidget = @ptrCast(tab.box);
|
||||
|
||||
// Build the tab label
|
||||
const label_box_widget = c.gtk_box_new(c.GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
const label_box = @as(*c.GtkBox, @ptrCast(label_box_widget));
|
||||
const label_text_widget = c.gtk_label_new(title.ptr);
|
||||
const label_text: *c.GtkLabel = @ptrCast(label_text_widget);
|
||||
c.gtk_box_append(label_box, label_text_widget);
|
||||
tab.label_text = label_text;
|
||||
|
||||
const window = tab.window;
|
||||
if (window.app.config.@"gtk-wide-tabs") {
|
||||
c.gtk_widget_set_hexpand(label_box_widget, 1);
|
||||
c.gtk_widget_set_halign(label_box_widget, c.GTK_ALIGN_FILL);
|
||||
c.gtk_widget_set_hexpand(label_text_widget, 1);
|
||||
c.gtk_widget_set_halign(label_text_widget, c.GTK_ALIGN_FILL);
|
||||
|
||||
// This ensures that tabs are always equal width. If they're too
|
||||
// long, they'll be truncated with an ellipsis.
|
||||
c.gtk_label_set_max_width_chars(label_text, 1);
|
||||
c.gtk_label_set_ellipsize(label_text, c.PANGO_ELLIPSIZE_END);
|
||||
|
||||
// We need to set a minimum width so that at a certain point
|
||||
// the notebook will have an arrow button rather than shrinking tabs
|
||||
// to an unreadably small size.
|
||||
c.gtk_widget_set_size_request(label_text_widget, 100, 1);
|
||||
}
|
||||
|
||||
// Build the close button for the tab
|
||||
const label_close_widget = c.gtk_button_new_from_icon_name("window-close-symbolic");
|
||||
const label_close: *c.GtkButton = @ptrCast(label_close_widget);
|
||||
c.gtk_button_set_has_frame(label_close, 0);
|
||||
c.gtk_box_append(label_box, label_close_widget);
|
||||
|
||||
const page_idx = c.gtk_notebook_insert_page(
|
||||
self.notebook,
|
||||
box_widget,
|
||||
label_box_widget,
|
||||
position,
|
||||
);
|
||||
|
||||
// Clicks
|
||||
const gesture_tab_click = c.gtk_gesture_click_new();
|
||||
c.gtk_gesture_single_set_button(@ptrCast(gesture_tab_click), 0);
|
||||
c.gtk_widget_add_controller(label_box_widget, @ptrCast(gesture_tab_click));
|
||||
|
||||
_ = c.g_signal_connect_data(label_close, "clicked", c.G_CALLBACK(>kTabCloseClick), tab, null, c.G_CONNECT_DEFAULT);
|
||||
_ = c.g_signal_connect_data(gesture_tab_click, "pressed", c.G_CALLBACK(>kTabClick), tab, null, c.G_CONNECT_DEFAULT);
|
||||
|
||||
// Tab settings
|
||||
c.gtk_notebook_set_tab_reorderable(self.notebook, box_widget, 1);
|
||||
c.gtk_notebook_set_tab_detachable(self.notebook, box_widget, 1);
|
||||
|
||||
if (self.nPages() > 1) {
|
||||
c.gtk_notebook_set_show_tabs(self.notebook, 1);
|
||||
}
|
||||
|
||||
// Switch to the new tab
|
||||
c.gtk_notebook_set_current_page(self.notebook, page_idx);
|
||||
}
|
||||
|
||||
pub fn closeTab(self: *NotebookGtk, tab: *Tab) void {
|
||||
const page = c.gtk_notebook_get_page(self.notebook, @ptrCast(tab.box)) orelse return;
|
||||
|
||||
// Find page and tab which we're closing
|
||||
const page_idx = getNotebookPageIndex(page);
|
||||
|
||||
// Remove the page. This will destroy the GTK widgets in the page which
|
||||
// will trigger Tab cleanup. The `tab` variable is therefore unusable past that point.
|
||||
c.gtk_notebook_remove_page(self.notebook, page_idx);
|
||||
|
||||
const remaining = self.nPages();
|
||||
switch (remaining) {
|
||||
// If we have no more tabs we close the window
|
||||
0 => c.gtk_window_destroy(tab.window.window),
|
||||
|
||||
// If we have one more tab we hide the tab bar
|
||||
1 => c.gtk_notebook_set_show_tabs(self.notebook, 0),
|
||||
|
||||
else => {},
|
||||
}
|
||||
|
||||
// If we have remaining tabs, we need to make sure we grab focus.
|
||||
if (remaining > 0)
|
||||
(self.currentTab() orelse return).window.focusCurrentTab();
|
||||
}
|
||||
};
|
||||
|
||||
fn getNotebookPageIndex(page: *c.GtkNotebookPage) c_int {
|
||||
var value: c.GValue = std.mem.zeroes(c.GValue);
|
||||
defer c.g_value_unset(&value);
|
||||
_ = c.g_value_init(&value, c.G_TYPE_INT);
|
||||
c.g_object_get_property(
|
||||
@ptrCast(@alignCast(page)),
|
||||
"position",
|
||||
&value,
|
||||
);
|
||||
|
||||
return c.g_value_get_int(&value);
|
||||
}
|
||||
|
||||
fn gtkPageAdded(
|
||||
notebook: *c.GtkNotebook,
|
||||
_: *c.GtkWidget,
|
||||
page_idx: c.guint,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *Window = @ptrCast(@alignCast(ud.?));
|
||||
|
||||
// The added page can come from another window with drag and drop, thus we migrate the tab
|
||||
// window to be self.
|
||||
const page = c.gtk_notebook_get_nth_page(notebook, @intCast(page_idx));
|
||||
const tab: *Tab = @ptrCast(@alignCast(
|
||||
c.g_object_get_data(@ptrCast(page), Tab.GHOSTTY_TAB) orelse return,
|
||||
));
|
||||
tab.window = self;
|
||||
|
||||
// Whenever a new page is added, we always grab focus of the
|
||||
// currently selected page. This was added specifically so that when
|
||||
// we drag a tab out to create a new window ("create-window" event)
|
||||
// we grab focus in the new window. Without this, the terminal didn't
|
||||
// have focus.
|
||||
self.focusCurrentTab();
|
||||
}
|
||||
|
||||
fn gtkPageRemoved(
|
||||
_: *c.GtkNotebook,
|
||||
_: *c.GtkWidget,
|
||||
_: c.guint,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
log.warn("gtkPageRemoved", .{});
|
||||
const window: *Window = @ptrCast(@alignCast(ud.?));
|
||||
|
||||
// Hide the tab bar if we only have one tab after removal
|
||||
const remaining = c.gtk_notebook_get_n_pages(window.notebook.gtk.notebook);
|
||||
|
||||
if (remaining == 1) {
|
||||
c.gtk_notebook_set_show_tabs(window.notebook.gtk.notebook, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn gtkSwitchPage(_: *c.GtkNotebook, page: *c.GtkWidget, _: usize, ud: ?*anyopaque) callconv(.C) void {
|
||||
const window: *Window = @ptrCast(@alignCast(ud.?));
|
||||
const self = &window.notebook.gtk;
|
||||
const gtk_label_box = @as(*c.GtkWidget, @ptrCast(c.gtk_notebook_get_tab_label(self.notebook, page)));
|
||||
const gtk_label = @as(*c.GtkLabel, @ptrCast(c.gtk_widget_get_first_child(gtk_label_box)));
|
||||
const label_text = c.gtk_label_get_text(gtk_label);
|
||||
window.setTitle(std.mem.span(label_text));
|
||||
}
|
||||
|
||||
fn gtkNotebookCreateWindow(
|
||||
_: *c.GtkNotebook,
|
||||
page: *c.GtkWidget,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) ?*c.GtkNotebook {
|
||||
// The tab for the page is stored in the widget data.
|
||||
const tab: *Tab = @ptrCast(@alignCast(
|
||||
c.g_object_get_data(@ptrCast(page), Tab.GHOSTTY_TAB) orelse return null,
|
||||
));
|
||||
|
||||
const currentWindow: *Window = @ptrCast(@alignCast(ud.?));
|
||||
const newWindow = createWindow(currentWindow) catch |err| {
|
||||
log.warn("error creating new window error={}", .{err});
|
||||
return null;
|
||||
};
|
||||
|
||||
// And add it to the new window.
|
||||
tab.window = newWindow;
|
||||
|
||||
return newWindow.notebook.gtk.notebook;
|
||||
}
|
||||
|
||||
fn gtkTabCloseClick(_: *c.GtkButton, ud: ?*anyopaque) callconv(.C) void {
|
||||
const tab: *Tab = @ptrCast(@alignCast(ud));
|
||||
tab.closeWithConfirmation();
|
||||
}
|
||||
|
||||
fn gtkTabClick(
|
||||
gesture: *c.GtkGestureClick,
|
||||
_: c.gint,
|
||||
_: c.gdouble,
|
||||
_: c.gdouble,
|
||||
ud: ?*anyopaque,
|
||||
) callconv(.C) void {
|
||||
const self: *Tab = @ptrCast(@alignCast(ud));
|
||||
const gtk_button = c.gtk_gesture_single_get_current_button(@ptrCast(gesture));
|
||||
if (gtk_button == c.GDK_BUTTON_MIDDLE) {
|
||||
self.closeWithConfirmation();
|
||||
}
|
||||
}
|
@ -131,4 +131,10 @@ pub const Window = union(Protocol) {
|
||||
inline else => |v| v.clientSideDecorationEnabled(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void {
|
||||
switch (self.*) {
|
||||
inline else => |*v| try v.addSubprocessEnv(env),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -61,4 +61,6 @@ pub const Window = struct {
|
||||
_ = self;
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn addSubprocessEnv(_: *Window, _: *std.process.EnvMap) !void {}
|
||||
};
|
||||
|
@ -262,6 +262,11 @@ pub const Window = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void {
|
||||
_ = self;
|
||||
_ = env;
|
||||
}
|
||||
|
||||
/// Update the blur state of the window.
|
||||
fn syncBlur(self: *Window) !void {
|
||||
const manager = self.app_context.kde_blur_manager orelse return;
|
||||
|
@ -314,6 +314,13 @@ pub const Window = struct {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void {
|
||||
var buf: [64]u8 = undefined;
|
||||
const window_id = try std.fmt.bufPrint(&buf, "{}", .{self.window});
|
||||
|
||||
try env.put("WINDOWID", window_id);
|
||||
}
|
||||
|
||||
fn getWindowProperty(
|
||||
self: *Window,
|
||||
comptime T: type,
|
||||
|
@ -19,7 +19,7 @@ const GitVersion = @import("GitVersion.zig");
|
||||
/// TODO: When Zig 0.14 is released, derive this from build.zig.zon directly.
|
||||
/// Until then this MUST match build.zig.zon and should always be the
|
||||
/// _next_ version to release.
|
||||
const app_version: std.SemanticVersion = .{ .major = 1, .minor = 1, .patch = 1 };
|
||||
const app_version: std.SemanticVersion = .{ .major = 1, .minor = 1, .patch = 3 };
|
||||
|
||||
/// Standard build configuration options.
|
||||
optimize: std.builtin.OptimizeMode,
|
||||
@ -32,7 +32,6 @@ renderer: renderer.Impl = .opengl,
|
||||
font_backend: font.Backend = .freetype,
|
||||
|
||||
/// Feature flags
|
||||
adwaita: bool = false,
|
||||
x11: bool = false,
|
||||
wayland: bool = false,
|
||||
sentry: bool = true,
|
||||
@ -132,12 +131,6 @@ pub fn init(b: *std.Build) !Config {
|
||||
//---------------------------------------------------------------
|
||||
// Feature Flags
|
||||
|
||||
config.adwaita = b.option(
|
||||
bool,
|
||||
"gtk-adwaita",
|
||||
"Enables the use of Adwaita when using the GTK rendering backend.",
|
||||
) orelse true;
|
||||
|
||||
config.flatpak = b.option(
|
||||
bool,
|
||||
"flatpak",
|
||||
@ -397,7 +390,6 @@ pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void {
|
||||
// We need to break these down individual because addOption doesn't
|
||||
// support all types.
|
||||
step.addOption(bool, "flatpak", self.flatpak);
|
||||
step.addOption(bool, "adwaita", self.adwaita);
|
||||
step.addOption(bool, "x11", self.x11);
|
||||
step.addOption(bool, "wayland", self.wayland);
|
||||
step.addOption(bool, "sentry", self.sentry);
|
||||
@ -442,7 +434,6 @@ pub fn fromOptions() Config {
|
||||
|
||||
.version = options.app_version,
|
||||
.flatpak = options.flatpak,
|
||||
.adwaita = options.adwaita,
|
||||
.app_runtime = std.meta.stringToEnum(apprt.Runtime, @tagName(options.app_runtime)).?,
|
||||
.font_backend = std.meta.stringToEnum(font.Backend, @tagName(options.font_backend)).?,
|
||||
.renderer = std.meta.stringToEnum(renderer.Impl, @tagName(options.renderer)).?,
|
||||
|
37
src/build/GhosttyFrameData.zig
Normal file
@ -0,0 +1,37 @@
|
||||
//! GhosttyFrameData generates a compressed file and zig module which contains (and exposes) the
|
||||
//! Ghostty animation frames for use in `ghostty +boo`
|
||||
const GhosttyFrameData = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const Config = @import("Config.zig");
|
||||
const SharedDeps = @import("SharedDeps.zig");
|
||||
|
||||
/// The exe.
|
||||
exe: *std.Build.Step.Compile,
|
||||
|
||||
/// The output path for the compressed framedata zig file
|
||||
output: std.Build.LazyPath,
|
||||
|
||||
pub fn init(b: *std.Build) !GhosttyFrameData {
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "framegen",
|
||||
.root_source_file = b.path("src/build/framegen/main.zig"),
|
||||
.target = b.host,
|
||||
});
|
||||
|
||||
const run = b.addRunArtifact(exe);
|
||||
|
||||
_ = run.addOutputFileArg("framedata.compressed");
|
||||
return .{
|
||||
.exe = exe,
|
||||
.output = run.captureStdOut(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Add the "framedata" import.
|
||||
pub fn addImport(self: *const GhosttyFrameData, step: *std.Build.Step.Compile) void {
|
||||
self.output.addStepDependencies(&step.step);
|
||||
step.root_module.addAnonymousImport("framedata", .{
|
||||
.root_source_file = self.output,
|
||||
});
|
||||
}
|
@ -6,6 +6,7 @@ const Config = @import("Config.zig");
|
||||
const HelpStrings = @import("HelpStrings.zig");
|
||||
const MetallibStep = @import("MetallibStep.zig");
|
||||
const UnicodeTables = @import("UnicodeTables.zig");
|
||||
const GhosttyFrameData = @import("GhosttyFrameData.zig");
|
||||
|
||||
config: *const Config,
|
||||
|
||||
@ -13,6 +14,7 @@ options: *std.Build.Step.Options,
|
||||
help_strings: HelpStrings,
|
||||
metallib: ?*MetallibStep,
|
||||
unicode_tables: UnicodeTables,
|
||||
framedata: GhosttyFrameData,
|
||||
|
||||
/// Used to keep track of a list of file sources.
|
||||
pub const LazyPathList = std.ArrayList(std.Build.LazyPath);
|
||||
@ -22,6 +24,7 @@ pub fn init(b: *std.Build, cfg: *const Config) !SharedDeps {
|
||||
.config = cfg,
|
||||
.help_strings = try HelpStrings.init(b, cfg),
|
||||
.unicode_tables = try UnicodeTables.init(b),
|
||||
.framedata = try GhosttyFrameData.init(b),
|
||||
|
||||
// Setup by retarget
|
||||
.options = undefined,
|
||||
@ -430,9 +433,30 @@ pub fn add(
|
||||
},
|
||||
|
||||
.gtk => {
|
||||
const gobject = b.dependency("gobject", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
const gobject_imports = .{
|
||||
.{ "gobject", "gobject2" },
|
||||
.{ "gio", "gio2" },
|
||||
.{ "glib", "glib2" },
|
||||
.{ "gtk", "gtk4" },
|
||||
.{ "gdk", "gdk4" },
|
||||
};
|
||||
inline for (gobject_imports) |import| {
|
||||
const name, const module = import;
|
||||
step.root_module.addImport(name, gobject.module(module));
|
||||
}
|
||||
|
||||
step.linkSystemLibrary2("gtk4", dynamic_link_opts);
|
||||
if (self.config.adwaita) step.linkSystemLibrary2("libadwaita-1", dynamic_link_opts);
|
||||
if (self.config.x11) step.linkSystemLibrary2("X11", dynamic_link_opts);
|
||||
step.linkSystemLibrary2("libadwaita-1", dynamic_link_opts);
|
||||
step.root_module.addImport("adw", gobject.module("adw1"));
|
||||
|
||||
if (self.config.x11) {
|
||||
step.linkSystemLibrary2("X11", dynamic_link_opts);
|
||||
step.root_module.addImport("gdk_x11", gobject.module("gdkx114"));
|
||||
}
|
||||
|
||||
if (self.config.wayland) {
|
||||
const scanner = Scanner.create(b.dependency("zig_wayland", .{}), .{
|
||||
@ -460,14 +484,54 @@ pub fn add(
|
||||
scanner.generate("org_kde_kwin_server_decoration_manager", 1);
|
||||
|
||||
step.root_module.addImport("wayland", wayland);
|
||||
step.root_module.addImport("gdk_wayland", gobject.module("gdkwayland4"));
|
||||
step.linkSystemLibrary2("wayland-client", dynamic_link_opts);
|
||||
}
|
||||
|
||||
{
|
||||
const gresource = @import("../apprt/gtk/gresource.zig");
|
||||
|
||||
const wf = b.addWriteFiles();
|
||||
const gresource_xml = wf.add("gresource.xml", gresource.gresource_xml);
|
||||
const gresource_xml = gresource_xml: {
|
||||
const generate_gresource_xml = b.addExecutable(.{
|
||||
.name = "generate_gresource_xml",
|
||||
.root_source_file = b.path("src/apprt/gtk/gresource.zig"),
|
||||
.target = b.host,
|
||||
});
|
||||
|
||||
const generate = b.addRunArtifact(generate_gresource_xml);
|
||||
|
||||
for (gresource.blueprint_files) |blueprint_file| {
|
||||
const blueprint_compiler = b.addSystemCommand(&.{
|
||||
"blueprint-compiler",
|
||||
"compile",
|
||||
"--output",
|
||||
});
|
||||
const ui_file = blueprint_compiler.addOutputFileArg(b.fmt("{s}.ui", .{blueprint_file}));
|
||||
blueprint_compiler.addFileArg(b.path(b.fmt("src/apprt/gtk/ui/{s}.blp", .{blueprint_file})));
|
||||
generate.addFileArg(ui_file);
|
||||
}
|
||||
|
||||
break :gresource_xml generate.captureStdOut();
|
||||
};
|
||||
|
||||
{
|
||||
const gtk_builder_check = b.addExecutable(.{
|
||||
.name = "gtk_builder_check",
|
||||
.root_source_file = b.path("src/apprt/gtk/builder_check.zig"),
|
||||
.target = b.host,
|
||||
});
|
||||
gtk_builder_check.root_module.addOptions("build_options", self.options);
|
||||
gtk_builder_check.root_module.addImport("gtk", gobject.module("gtk4"));
|
||||
gtk_builder_check.root_module.addImport("adw", gobject.module("adw1"));
|
||||
|
||||
for (gresource.dependencies) |pathname| {
|
||||
const extension = std.fs.path.extension(pathname);
|
||||
if (!std.mem.eql(u8, extension, ".ui")) continue;
|
||||
const check = b.addRunArtifact(gtk_builder_check);
|
||||
check.addFileArg(b.path(pathname));
|
||||
step.step.dependOn(&check.step);
|
||||
}
|
||||
}
|
||||
|
||||
const generate_resources_c = b.addSystemCommand(&.{
|
||||
"glib-compile-resources",
|
||||
@ -499,6 +563,7 @@ pub fn add(
|
||||
|
||||
self.help_strings.addImport(step);
|
||||
self.unicode_tables.addImport(step);
|
||||
self.framedata.addImport(step);
|
||||
|
||||
return static_libs;
|
||||
}
|
||||
|
49
src/build/docker/debian/Dockerfile
Normal file
@ -0,0 +1,49 @@
|
||||
ARG DISTRO_VERSION="12"
|
||||
FROM docker.io/library/debian:${DISTRO_VERSION}
|
||||
|
||||
# Install Dependencies
|
||||
RUN DEBIAN_FRONTEND="noninteractive" apt-get -qq update && \
|
||||
apt-get -qq -y --no-install-recommends install \
|
||||
# Build Tools
|
||||
build-essential \
|
||||
libbz2-dev \
|
||||
libonig-dev \
|
||||
lintian \
|
||||
lsb-release \
|
||||
pandoc \
|
||||
wget \
|
||||
# Ghostty Dependencies
|
||||
libadwaita-1-dev \
|
||||
libgtk-4-dev && \
|
||||
# Clean up for better caching
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# work around the fact that Debian 12 doesn't ship a pkg-config file for bzip2
|
||||
RUN . /etc/os-release; if [ $VERSION_ID -le 12 ]; then ln -s libbz2.so /usr/lib/$(gcc -dumpmachine)/libbzip2.so; fi
|
||||
|
||||
# Install zig
|
||||
# https://ziglang.org/download/
|
||||
ARG ZIG_VERSION="0.13.0"
|
||||
RUN wget -q "https://ziglang.org/download/$ZIG_VERSION/zig-linux-$(uname -m)-$ZIG_VERSION.tar.xz" && \
|
||||
tar -xf "zig-linux-$(uname -m)-$ZIG_VERSION.tar.xz" -C /opt && \
|
||||
rm zig-linux-* && \
|
||||
ln -s "/opt/zig-linux-$(uname -m)-$ZIG_VERSION/zig" /usr/local/bin/zig
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY ./dist/linux /src/dist/linux
|
||||
COPY ./images /src/images
|
||||
COPY ./include /src/include
|
||||
COPY ./pkg /src/pkg
|
||||
COPY ./nix /src/nix
|
||||
COPY ./vendor /src/vendor
|
||||
COPY ./build.zig /src/build.zig
|
||||
COPY ./build.zig.zon /src/build.zig.zon
|
||||
COPY ./build.zig.zon.txt /src/build.zig.zon.txt
|
||||
|
||||
RUN ZIG_GLOBAL_CACHE_DIR=/zig/global-cache ./nix/build-support/fetch-zig-cache.sh
|
||||
|
||||
COPY ./src /src/src
|
||||
|
||||
RUN zig build -Doptimize=Debug -Dcpu=baseline -Dapp-runtime=gtk --system /zig/global-cache/p
|
||||
|
41
src/build/framegen/frames/frame_001.txt
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
|
||||
<span class="b">+++==*%%%%%%%%%%%%*==+++</span>
|
||||
<span class="b">++****++</span> <span class="b">++****++</span>
|
||||
<span class="b">++**++</span> <span class="b">++**++</span>
|
||||
<span class="b">xx**+=</span> o+*%$@@@@@@$%*+o <span class="b">=+**xx</span>
|
||||
<span class="b">xx**oo</span> ·=$@@@@@@@$$$$$$$$@@@@@@@$=· <span class="b">oo**xx</span>
|
||||
<span class="b">xx**</span> x$@@@$$$$$$$$$$$$$$$$$$$$$$$$@@@$x <span class="b">**xx</span>
|
||||
<span class="b">ox**</span> ·$@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@@$· <span class="b">**xo</span>
|
||||
<span class="b">==+~</span> ~@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@@@~ <span class="b">~+==</span>
|
||||
<span class="b">x+++</span> $@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@$ <span class="b">+++x</span>
|
||||
<span class="b">==</span> ·@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@@· <span class="b">==</span>
|
||||
<span class="b">ox++</span> ~@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@~ <span class="b">++xo</span>
|
||||
<span class="b">+++~</span> @$$$$$@@@@@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@ <span class="b">~+++</span>
|
||||
<span class="b">==</span> $$$$$@@%%%%%%$$$$$$$@@@@@$$$@@@@@@@@@@@@@@@@@@@@$$$$$$ <span class="b">==</span>
|
||||
<span class="b">==</span> @$$$$* $$$$% =$$$$$@ <span class="b">==</span>
|
||||
<span class="b">==</span> ·$$$$@ x@$@ @$$$$$· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$% ·$$$$% *$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$@@$%%$$$$$$@@@@@@@@$$$$@@@@@@@@@@@@@@@@@@@@$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ~$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$~ <span class="b">==</span>
|
||||
<span class="b">==</span> @@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@@ <span class="b">==</span>
|
||||
<span class="b">==x·</span> $@@$$$$$$$$$@@@@@@@@@@$$$$$$$$@@@@@@@@@@$$$$$$$$$@@$ <span class="b">·+==</span>
|
||||
<span class="b">++++</span> =@@@@@@@@@* x$@@@@@@@@$x *@@@@@@@@@= <span class="b">++++</span>
|
||||
<span class="b">xx==++</span> <span class="b">++==oo</span>
|
||||
<span class="b">++===+</span> <span class="b">++%%+o</span> <span class="b">o+%%++</span> <span class="b">+===++</span>
|
||||
<span class="b">++=====%+=++++*=*========***++++***========*=*++++=+%=====++</span>
|
||||
<span class="b">xx++==******====++</span> <span class="b">++==********==++</span> <span class="b">++====******==++xx</span>
|
||||
<span class="b">++++</span> <span class="b">++++</span> <span class="b">++++</span>
|
||||
|
41
src/build/framegen/frames/frame_002.txt
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
<span class="b">++++++++++++</span>
|
||||
<span class="b">++==*%%%**=++++++=**%%%*==++</span>
|
||||
<span class="b">++**=*</span> <span class="b">*=**++</span>
|
||||
<span class="b">x+**+=</span> <span class="b">=+**+x</span>
|
||||
<span class="b">++==</span> o=%@@@@@@@@@@@@@@@@%=o <span class="b">==++</span>
|
||||
<span class="b">===+</span> +$@@@@@$$$$$$$$$$$$$$$$@@@@@$+ <span class="b">+===</span>
|
||||
<span class="b">++=+</span> =@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$@@@@= <span class="b">+=++</span>
|
||||
<span class="b">++==</span> $@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@@$ <span class="b">==++</span>
|
||||
<span class="b">**</span> $@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@@$ <span class="b">**</span>
|
||||
<span class="b">+++o</span> +@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@@+ <span class="b">o+++</span>
|
||||
<span class="b">==</span> %@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@% <span class="b">==</span>
|
||||
<span class="b">xx++</span> %@$$$$@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@% <span class="b">++xx</span>
|
||||
<span class="b">==+·</span> x@$$$@@%=*%$$@@@@@@@@@@@$$$$$@@@@@@@@@@@@@@@@@@$$$$$@x <span class="b">·+==</span>
|
||||
<span class="b">==</span> @$$$$% ~$@$$$@= x@$$$$$@ <span class="b">==</span>
|
||||
<span class="b">==</span> $$$$@* %$$@· @$$$$$ <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$@* $$$$+ $$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$~ ~ox+=%@@$$$@@*==============*$@$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$@@@@@@@@@@@@@@@@@$$$$$$$@@@@@@@@@@@@@@@@$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ~$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$~ <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@ <span class="b">==</span>
|
||||
<span class="b">==</span> $@$$$$$$$$$$$$$$@@@@$$$$$$$$$$$$$$@@@@$$$$$$$$$$$$$$@$ <span class="b">==</span>
|
||||
<span class="b">==+o</span> +@@@$$$$$$$@@@$***%@@@@$$$$$$@@@@%***$@@@$$$$$$$@@@+ <span class="b">o+==</span>
|
||||
<span class="b">++==</span> ·=$@@@@@$*~ +%@@@@@@%+ ~*$@@@@@$=· <span class="b">==++</span>
|
||||
<span class="b">===+</span> <span class="b">+===</span>
|
||||
<span class="b">++====x+</span> <span class="b">*=**==</span> <span class="b">==**=*</span> <span class="b">+x====++</span>
|
||||
<span class="b">++=====**%******=========**%****%**=========******%**=====++</span>
|
||||
<span class="b">++++========++xx</span> <span class="b">++++========++++</span> <span class="b">xx++========++++</span>
|
||||
|
||||
|
41
src/build/framegen/frames/frame_003.txt
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
<span class="b">++====****====++</span>
|
||||
<span class="b">==***%==xo</span> <span class="b">ox==%***==</span>
|
||||
<span class="b">===*++</span> <span class="b">++*===</span>
|
||||
<span class="b">++**x+</span> ·oxx++xxo· <span class="b">+x**++</span>
|
||||
<span class="b">===+</span> o=$@@@@@@@@@@@@@@@@@@$=o <span class="b">+===</span>
|
||||
<span class="b">**+x</span> o%@@@@$$$$$$$$$$$$$$$$$$$$@@@@%o <span class="b">x+**</span>
|
||||
<span class="b">==+o</span> ~$@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$@@@$~ <span class="b">o+==</span>
|
||||
<span class="b">++++</span> +@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@@@+ <span class="b">++++</span>
|
||||
<span class="b">ox==</span> o@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@@o <span class="b">==xo</span>
|
||||
<span class="b">==+~</span> %@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@% <span class="b">~+==</span>
|
||||
<span class="b">ox==</span> @@$$$@@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@@ <span class="b">==xo</span>
|
||||
<span class="b">+++x</span> @$$$$@+ ·+*$@@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@ <span class="b">x+++</span>
|
||||
<span class="b">==+</span> *@$$$@ o=%@@@@$$$$@@@@@@@@@@@@@@@@@@@@$$$$@* <span class="b">+==</span>
|
||||
<span class="b">==</span> @$$$$$o %$$$$$ *$$$$$@ <span class="b">==</span>
|
||||
<span class="b">==</span> $$$$$$@@=· @$$@ @$$$$$ <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$x %$$$$% =$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$@ ·x*$@@@$$$$@@@@@@@@@@@@@@@@@@@@$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$@x o=%@@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$@@@@@@@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ·@$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@· <span class="b">==</span>
|
||||
<span class="b">==</span> ~$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$~ <span class="b">==</span>
|
||||
<span class="b">==</span> @$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$@ <span class="b">==</span>
|
||||
<span class="b">==</span> %@$$$$$$$$$$$$$@@@@@@$$$$$$$$$$$$@@@@@@$$$$$$$$$$$$$@% <span class="b">==</span>
|
||||
<span class="b">==+x</span> ~$@@@@$$$$@@@@%+xx=$@@@@$$$$@@@@$=xx+*@@@@$$$$@@@@$~ <span class="b">x+==</span>
|
||||
<span class="b">++==</span> x*$@@@$*x ·=%$@@$%=· x*$@@@$*x <span class="b">==++</span>
|
||||
<span class="b">====</span> <span class="b">=+==</span>
|
||||
<span class="b">x+====+=</span> <span class="b">x+*===+=</span> <span class="b">======+x</span> <span class="b">=+====xx</span>
|
||||
<span class="b">++====*%%%%***====++====*%*%%*%*====++=====**%%%%*====++</span>
|
||||
<span class="b">xx++++====++++</span> <span class="b">++++====++++</span> <span class="b">++++====++++xx</span>
|
||||
|
||||
|