mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-07 03:48:21 +00:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b0573a40e7 |
@@ -1,64 +0,0 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
# A command to generate an agent prompt to diagnose and formulate
|
||||
# a plan for resolving a GitHub issue.
|
||||
#
|
||||
# IMPORTANT: This command is prompted to NOT write any code and to ONLY
|
||||
# produce a plan. You should still be vigilant when running this but that
|
||||
# is the expected behavior.
|
||||
#
|
||||
# The `<issue>` parameter can be either an issue number or a full GitHub
|
||||
# issue URL.
|
||||
def main [
|
||||
issue: any, # Ghostty issue number or URL
|
||||
--repo: string = "ghostty-org/ghostty" # GitHub repository in the format "owner/repo"
|
||||
] {
|
||||
# TODO: This whole script doesn't handle errors very well. I actually
|
||||
# don't know Nu well enough to know the proper way to handle it all.
|
||||
|
||||
let issueData = gh issue view $issue --json author,title,number,body,comments | from json
|
||||
let comments = $issueData.comments | each { |comment|
|
||||
$"
|
||||
### Comment by ($comment.author.login)
|
||||
($comment.body)
|
||||
" | str trim
|
||||
} | str join "\n\n"
|
||||
|
||||
$"
|
||||
Deep-dive on this GitHub issue. Find the problem and generate a plan.
|
||||
Do not write code. Explain the problem clearly and propose a comprehensive plan
|
||||
to solve it.
|
||||
|
||||
# ($issueData.title) \(($issueData.number)\)
|
||||
|
||||
## Description
|
||||
($issueData.body)
|
||||
|
||||
## Comments
|
||||
($comments)
|
||||
|
||||
## Your Tasks
|
||||
|
||||
You are an experienced software developer tasked with diagnosing issues.
|
||||
|
||||
1. Review the issue context and details.
|
||||
2. Examine the relevant parts of the codebase. Analyze the code thoroughly
|
||||
until you have a solid understanding of how it works.
|
||||
3. Explain the issue in detail, including the problem and its root cause.
|
||||
4. Create a comprehensive plan to solve the issue. The plan should include:
|
||||
- Required code changes
|
||||
- Potential impacts on other parts of the system
|
||||
- Necessary tests to be written or updated
|
||||
- Documentation updates
|
||||
- Performance considerations
|
||||
- Security implications
|
||||
- Backwards compatibility \(if applicable\)
|
||||
- Include the reference link to the source issue and any related discussions
|
||||
4. Think deeply about all aspects of the task. Consider edge cases, potential
|
||||
challenges, and best practices for addressing the issue. Review the plan
|
||||
with the oracle and adjust it based on its feedback.
|
||||
|
||||
**ONLY CREATE A PLAN. DO NOT WRITE ANY CODE.** Your task is to create
|
||||
a thorough, comprehensive strategy for understanding and resolving the issue.
|
||||
" | str trim
|
||||
}
|
19
.github/scripts/ghostty-tip
vendored
19
.github/scripts/ghostty-tip
vendored
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env nu
|
||||
|
||||
# Check if a given commit SHA has a corresponding tip release.
|
||||
#
|
||||
# This does not validate that the commit SHA is valid for the
|
||||
# Ghostty repository, only that a tip release exists for it.
|
||||
def main [
|
||||
commit: string, # The full length commit SHA
|
||||
] {
|
||||
let url = $"https://tip.files.ghostty.org/($commit)/ghostty-macos-universal.zip"
|
||||
|
||||
try {
|
||||
http head $url
|
||||
exit 0
|
||||
} catch {
|
||||
print -e $"The SHA ($commit) does not have a corresponding tip release."
|
||||
exit 1
|
||||
}
|
||||
}
|
4
.github/workflows/nix.yml
vendored
4
.github/workflows/nix.yml
vendored
@@ -36,13 +36,13 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
|
18
.github/workflows/release-pr.yml
vendored
18
.github/workflows/release-pr.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
sentry-cli dif upload --project ghostty --wait dsym.zip
|
||||
|
||||
build-macos:
|
||||
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -57,9 +57,9 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
determinate: true
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -95,7 +95,6 @@ jobs:
|
||||
run: |
|
||||
cd macos
|
||||
sudo xcode-select -s /Applications/Xcode_26.0.app
|
||||
xcodebuild -version
|
||||
xcodebuild -target Ghostty -configuration Release
|
||||
|
||||
# We inject the "build number" as simply the number of commits since HEAD.
|
||||
@@ -202,7 +201,7 @@ jobs:
|
||||
destination-dir: ./
|
||||
|
||||
build-macos-debug:
|
||||
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -212,9 +211,9 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
determinate: true
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
@@ -223,7 +222,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.5.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -250,7 +249,6 @@ jobs:
|
||||
run: |
|
||||
cd macos
|
||||
sudo xcode-select -s /Applications/Xcode_26.0.app
|
||||
xcodebuild -version
|
||||
xcodebuild -target Ghostty -configuration Release
|
||||
|
||||
# We inject the "build number" as simply the number of commits since HEAD.
|
||||
|
19
.github/workflows/release-tag.yml
vendored
19
.github/workflows/release-tag.yml
vendored
@@ -83,13 +83,13 @@ jobs:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
|
||||
build-macos:
|
||||
needs: [setup]
|
||||
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
|
||||
@@ -130,9 +130,9 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
determinate: true
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
@@ -141,12 +141,9 @@ jobs:
|
||||
- name: XCode Select
|
||||
run: sudo xcode-select -s /Applications/Xcode_16.4.app
|
||||
|
||||
- name: Xcode Version
|
||||
run: xcodebuild -version
|
||||
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -294,7 +291,7 @@ jobs:
|
||||
|
||||
appcast:
|
||||
needs: [setup, build-macos]
|
||||
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
||||
env:
|
||||
GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
|
||||
GHOSTTY_BUILD: ${{ needs.setup.outputs.build }}
|
||||
@@ -311,7 +308,7 @@ jobs:
|
||||
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
|
159
.github/workflows/release-tip.yml
vendored
159
.github/workflows/release-tip.yml
vendored
@@ -15,57 +15,9 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.repository_owner == 'ghostty-org' &&
|
||||
github.ref_name == 'main'
|
||||
)
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
outputs:
|
||||
should_skip: ${{ steps.check.outputs.should_skip }}
|
||||
build: ${{ steps.extract_build_info.outputs.build }}
|
||||
commit: ${{ steps.extract_build_info.outputs.commit }}
|
||||
commit_long: ${{ steps.extract_build_info.outputs.commit_long }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
- name: Extract build info
|
||||
id: extract_build_info
|
||||
run: |
|
||||
GHOSTTY_BUILD=$(git rev-list --count HEAD)
|
||||
GHOSTTY_COMMIT=$(git rev-parse --short HEAD)
|
||||
GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)
|
||||
echo "build=$GHOSTTY_BUILD" >> $GITHUB_OUTPUT
|
||||
echo "commit=$GHOSTTY_COMMIT" >> $GITHUB_OUTPUT
|
||||
echo "commit_long=$GHOSTTY_COMMIT_LONG" >> $GITHUB_OUTPUT
|
||||
- name: Check if tip already exists
|
||||
id: check
|
||||
run: |
|
||||
GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)
|
||||
if nix develop -c nu .github/scripts/ghostty-tip $GHOSTTY_COMMIT_LONG; then
|
||||
echo "Tip release already exists for commit $GHOSTTY_COMMIT_LONG"
|
||||
echo "should_skip=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "No tip release found for commit $GHOSTTY_COMMIT_LONG"
|
||||
echo "should_skip=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
tag:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: [setup, build-macos]
|
||||
if: needs.setup.outputs.should_skip != 'true'
|
||||
needs: [build-macos]
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Tip Tag
|
||||
@@ -77,10 +29,7 @@ jobs:
|
||||
|
||||
sentry-dsym-debug-slow:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: [setup, build-macos-debug-slow]
|
||||
if: needs.setup.outputs.should_skip != 'true'
|
||||
env:
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
needs: [build-macos-debug-slow]
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
@@ -90,6 +39,7 @@ jobs:
|
||||
|
||||
- name: Download dSYM
|
||||
run: |
|
||||
GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)
|
||||
curl -L https://tip.files.ghostty.dev/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-slow-dsym.zip > dsym.zip
|
||||
|
||||
- name: Upload dSYM to Sentry
|
||||
@@ -100,10 +50,7 @@ jobs:
|
||||
|
||||
sentry-dsym-debug-fast:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: [setup, build-macos-debug-fast]
|
||||
if: needs.setup.outputs.should_skip != 'true'
|
||||
env:
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
needs: [build-macos-debug-fast]
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
@@ -113,6 +60,7 @@ jobs:
|
||||
|
||||
- name: Download dSYM
|
||||
run: |
|
||||
GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)
|
||||
curl -L https://tip.files.ghostty.dev/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-fast-dsym.zip > dsym.zip
|
||||
|
||||
- name: Upload dSYM to Sentry
|
||||
@@ -123,10 +71,7 @@ jobs:
|
||||
|
||||
sentry-dsym:
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: [setup, build-macos]
|
||||
if: needs.setup.outputs.should_skip != 'true'
|
||||
env:
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
needs: [build-macos]
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
@@ -136,6 +81,7 @@ jobs:
|
||||
|
||||
- name: Download dSYM
|
||||
run: |
|
||||
GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)
|
||||
curl -L https://tip.files.ghostty.dev/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-dsym.zip > dsym.zip
|
||||
|
||||
- name: Upload dSYM to Sentry
|
||||
@@ -145,17 +91,15 @@ jobs:
|
||||
sentry-cli dif upload --project ghostty --wait dsym.zip
|
||||
|
||||
source-tarball:
|
||||
needs: [setup]
|
||||
if: |
|
||||
needs.setup.outputs.should_skip != 'true' &&
|
||||
(
|
||||
${{
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.repository_owner == 'ghostty-org' &&
|
||||
github.ref_name == 'main'
|
||||
)
|
||||
)
|
||||
}}
|
||||
runs-on: namespace-profile-ghostty-md
|
||||
env:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
@@ -163,12 +107,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -200,24 +144,18 @@ jobs:
|
||||
token: ${{ secrets.GH_RELEASE_TOKEN }}
|
||||
|
||||
build-macos:
|
||||
needs: [setup]
|
||||
if: |
|
||||
needs.setup.outputs.should_skip != 'true' &&
|
||||
(
|
||||
${{
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.repository_owner == 'ghostty-org' &&
|
||||
github.ref_name == 'main'
|
||||
)
|
||||
)
|
||||
}}
|
||||
|
||||
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
GHOSTTY_BUILD: ${{ needs.setup.outputs.build }}
|
||||
GHOSTTY_COMMIT: ${{ needs.setup.outputs.commit }}
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -225,10 +163,10 @@ jobs:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
|
||||
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
determinate: true
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
@@ -243,7 +181,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.6.4
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -251,6 +189,13 @@ jobs:
|
||||
unzip sparkle.zip
|
||||
echo "$(pwd)/bin" >> $GITHUB_PATH
|
||||
|
||||
# Load Build Number
|
||||
- name: Build Number
|
||||
run: |
|
||||
echo "GHOSTTY_BUILD=$(git rev-list --count head)" >> $GITHUB_ENV
|
||||
echo "GHOSTTY_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
echo "GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)" >> $GITHUB_ENV
|
||||
|
||||
# GhosttyKit is the framework that is built from Zig for our native
|
||||
# Mac app to access. Build this in release mode.
|
||||
- name: Build GhosttyKit
|
||||
@@ -419,24 +364,18 @@ jobs:
|
||||
destination-dir: ./
|
||||
|
||||
build-macos-debug-slow:
|
||||
needs: [setup]
|
||||
if: |
|
||||
needs.setup.outputs.should_skip != 'true' &&
|
||||
(
|
||||
${{
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.repository_owner == 'ghostty-org' &&
|
||||
github.ref_name == 'main'
|
||||
)
|
||||
)
|
||||
}}
|
||||
|
||||
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
GHOSTTY_BUILD: ${{ needs.setup.outputs.build }}
|
||||
GHOSTTY_COMMIT: ${{ needs.setup.outputs.commit }}
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -444,10 +383,10 @@ jobs:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
|
||||
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
determinate: true
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
@@ -462,7 +401,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.5.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -470,6 +409,13 @@ jobs:
|
||||
unzip sparkle.zip
|
||||
echo "$(pwd)/bin" >> $GITHUB_PATH
|
||||
|
||||
# Load Build Number
|
||||
- name: Build Number
|
||||
run: |
|
||||
echo "GHOSTTY_BUILD=$(git rev-list --count head)" >> $GITHUB_ENV
|
||||
echo "GHOSTTY_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
echo "GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)" >> $GITHUB_ENV
|
||||
|
||||
# GhosttyKit is the framework that is built from Zig for our native
|
||||
# Mac app to access. Build this in release mode.
|
||||
- name: Build GhosttyKit
|
||||
@@ -598,24 +544,18 @@ jobs:
|
||||
destination-dir: ./
|
||||
|
||||
build-macos-debug-fast:
|
||||
needs: [setup]
|
||||
if: |
|
||||
needs.setup.outputs.should_skip != 'true' &&
|
||||
(
|
||||
${{
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.repository_owner == 'ghostty-org' &&
|
||||
github.ref_name == 'main'
|
||||
)
|
||||
)
|
||||
}}
|
||||
|
||||
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
GHOSTTY_BUILD: ${{ needs.setup.outputs.build }}
|
||||
GHOSTTY_COMMIT: ${{ needs.setup.outputs.commit }}
|
||||
GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -623,10 +563,10 @@ jobs:
|
||||
# Important so that build number generation works
|
||||
fetch-depth: 0
|
||||
|
||||
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
determinate: true
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
@@ -641,7 +581,7 @@ jobs:
|
||||
# Setup Sparkle
|
||||
- name: Setup Sparkle
|
||||
env:
|
||||
SPARKLE_VERSION: 2.7.1
|
||||
SPARKLE_VERSION: 2.5.1
|
||||
run: |
|
||||
mkdir -p .action/sparkle
|
||||
cd .action/sparkle
|
||||
@@ -649,6 +589,13 @@ jobs:
|
||||
unzip sparkle.zip
|
||||
echo "$(pwd)/bin" >> $GITHUB_PATH
|
||||
|
||||
# Load Build Number
|
||||
- name: Build Number
|
||||
run: |
|
||||
echo "GHOSTTY_BUILD=$(git rev-list --count head)" >> $GITHUB_ENV
|
||||
echo "GHOSTTY_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
echo "GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)" >> $GITHUB_ENV
|
||||
|
||||
# GhosttyKit is the framework that is built from Zig for our native
|
||||
# Mac app to access. Build this in release mode.
|
||||
- name: Build GhosttyKit
|
||||
|
317
.github/workflows/test.yml
vendored
317
.github/workflows/test.yml
vendored
@@ -13,16 +13,19 @@ jobs:
|
||||
- build-bench
|
||||
- build-dist
|
||||
- build-flatpak
|
||||
- build-freebsd
|
||||
- build-linux
|
||||
- build-linux-libghostty
|
||||
- build-nix
|
||||
- build-snap
|
||||
- build-macos
|
||||
- build-macos-tahoe
|
||||
- build-macos-matrix
|
||||
- build-windows
|
||||
- flatpak-check-zig-cache
|
||||
- flatpak
|
||||
- test
|
||||
- test-gtk
|
||||
- test-gtk-ng
|
||||
- test-sentry-linux
|
||||
- test-macos
|
||||
- pinact
|
||||
@@ -34,9 +37,7 @@ jobs:
|
||||
- blueprint-compiler
|
||||
- test-pkg-linux
|
||||
- test-debian-13
|
||||
- valgrind
|
||||
- zig-fmt
|
||||
- flatpak
|
||||
steps:
|
||||
- id: status
|
||||
name: Determine status
|
||||
@@ -69,14 +70,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -100,14 +101,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -136,14 +137,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -165,14 +166,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -198,14 +199,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -242,14 +243,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -271,16 +272,16 @@ jobs:
|
||||
ghostty-source.tar.gz
|
||||
|
||||
build-macos:
|
||||
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
determinate: true
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
@@ -313,7 +314,7 @@ jobs:
|
||||
cd macos
|
||||
xcodebuild -target Ghostty-iOS "CODE_SIGNING_ALLOWED=NO"
|
||||
|
||||
build-macos-matrix:
|
||||
build-macos-tahoe:
|
||||
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||
needs: test
|
||||
steps:
|
||||
@@ -332,8 +333,45 @@ jobs:
|
||||
- name: Xcode Select
|
||||
run: sudo xcode-select -s /Applications/Xcode_26.0.app
|
||||
|
||||
- name: Xcode Version
|
||||
run: xcodebuild -version
|
||||
- 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 --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false
|
||||
|
||||
# The native app is built with native Xcode tooling. This also does
|
||||
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
|
||||
# Nix breaks xcodebuild so this has to be run outside.
|
||||
- name: Build Ghostty.app
|
||||
run: cd macos && xcodebuild -target Ghostty
|
||||
|
||||
# Build the iOS target without code signing just to verify it works.
|
||||
- name: Build Ghostty iOS
|
||||
run: |
|
||||
cd macos
|
||||
xcodebuild -target Ghostty-iOS "CODE_SIGNING_ALLOWED=NO"
|
||||
|
||||
build-macos-matrix:
|
||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
- name: Xcode Select
|
||||
run: sudo xcode-select -s /Applications/Xcode_26.0.app
|
||||
|
||||
- name: get the Zig deps
|
||||
id: deps
|
||||
@@ -362,7 +400,6 @@ jobs:
|
||||
os:
|
||||
[namespace-profile-ghostty-snap, namespace-profile-ghostty-snap-arm64]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 45
|
||||
needs: [test, build-dist]
|
||||
env:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
@@ -377,7 +414,7 @@ jobs:
|
||||
mkdir dist
|
||||
tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
@@ -397,7 +434,6 @@ jobs:
|
||||
runs-on: windows-2022
|
||||
# this will not stop other jobs from running
|
||||
continue-on-error: true
|
||||
timeout-minutes: 45
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -473,14 +509,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -494,6 +530,9 @@ jobs:
|
||||
- name: Test GTK Build
|
||||
run: nix develop -c zig build -Dapp-runtime=gtk -Demit-docs -Demit-webdata
|
||||
|
||||
- name: Test GTK-NG Build
|
||||
run: nix develop -c zig build -Dapp-runtime=gtk-ng -Demit-docs -Demit-webdata
|
||||
|
||||
# This relies on the cache being populated by the commands above.
|
||||
- name: Test System Build
|
||||
run: nix develop -c zig build --system ${ZIG_GLOBAL_CACHE_DIR}/p
|
||||
@@ -515,14 +554,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -547,6 +586,55 @@ jobs:
|
||||
-Dgtk-x11=${{ matrix.x11 }} \
|
||||
-Dgtk-wayland=${{ matrix.wayland }}
|
||||
|
||||
test-gtk-ng:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
x11: ["true", "false"]
|
||||
wayland: ["true", "false"]
|
||||
name: GTK x11=${{ matrix.x11 }} wayland=${{ matrix.wayland }}
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: test
|
||||
env:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
nix develop -c \
|
||||
zig build \
|
||||
-Dapp-runtime=gtk-ng \
|
||||
-Dgtk-x11=${{ matrix.x11 }} \
|
||||
-Dgtk-wayland=${{ matrix.wayland }} \
|
||||
test
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
nix develop -c \
|
||||
zig build \
|
||||
-Dapp-runtime=gtk-ng \
|
||||
-Dgtk-x11=${{ matrix.x11 }} \
|
||||
-Dgtk-wayland=${{ matrix.wayland }}
|
||||
|
||||
test-sentry-linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -563,14 +651,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -583,16 +671,16 @@ jobs:
|
||||
nix develop -c zig build -Dsentry=${{ matrix.sentry }}
|
||||
|
||||
test-macos:
|
||||
runs-on: namespace-profile-ghostty-macos-tahoe
|
||||
runs-on: namespace-profile-ghostty-macos-sequoia
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
determinate: true
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
@@ -601,9 +689,6 @@ jobs:
|
||||
- name: Xcode Select
|
||||
run: sudo xcode-select -s /Applications/Xcode_26.0.app
|
||||
|
||||
- name: Xcode Version
|
||||
run: xcodebuild -version
|
||||
|
||||
- name: get the Zig deps
|
||||
id: deps
|
||||
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
|
||||
@@ -621,12 +706,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -649,12 +734,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -676,12 +761,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -703,12 +788,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -730,12 +815,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -757,12 +842,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -791,12 +876,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -818,12 +903,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -853,14 +938,14 @@ jobs:
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
- uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -901,6 +986,33 @@ jobs:
|
||||
build-args: |
|
||||
DISTRO_VERSION=13
|
||||
|
||||
flatpak-check-zig-cache:
|
||||
if: github.repository == 'ghostty-org/ghostty'
|
||||
runs-on: namespace-profile-ghostty-xsm
|
||||
env:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
useDaemon: false # sometimes fails on short jobs
|
||||
- name: Check Flatpak Zig Dependencies
|
||||
run: nix develop -c ./flatpak/build-support/check-zig-cache.sh
|
||||
|
||||
flatpak:
|
||||
if: github.repository == 'ghostty-org/ghostty'
|
||||
name: "Flatpak"
|
||||
@@ -916,7 +1028,7 @@ jobs:
|
||||
- arch: aarch64
|
||||
runner: namespace-profile-ghostty-md-arm64
|
||||
runs-on: ${{ matrix.variant.runner }}
|
||||
needs: test
|
||||
needs: [flatpak-check-zig-cache, test]
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: flatpak/flatpak-github-actions/flatpak-builder@10a3c29f0162516f0f68006be14c92f34bd4fa6c # v6.5
|
||||
@@ -926,94 +1038,3 @@ jobs:
|
||||
cache-key: flatpak-builder-${{ github.sha }}
|
||||
arch: ${{ matrix.variant.arch }}
|
||||
verbose: true
|
||||
|
||||
valgrind:
|
||||
if: github.repository == 'ghostty-org/ghostty'
|
||||
runs-on: namespace-profile-ghostty-lg
|
||||
timeout-minutes: 30
|
||||
needs: test
|
||||
env:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
- name: valgrind deps
|
||||
run: |
|
||||
sudo apt update -y
|
||||
sudo apt install -y valgrind libc6-dbg
|
||||
|
||||
- name: valgrind
|
||||
run: |
|
||||
nix develop -c zig build test-valgrind
|
||||
|
||||
build-freebsd:
|
||||
name: Build on FreeBSD
|
||||
needs: test
|
||||
runs-on: namespace-profile-mitchellh-sm-systemd
|
||||
strategy:
|
||||
matrix:
|
||||
release:
|
||||
- "14.3"
|
||||
# - "15.0" # disable until fixed: https://github.com/vmactions/freebsd-vm/issues/108
|
||||
steps:
|
||||
- name: Checkout Ghostty
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Start SSH
|
||||
run: |
|
||||
sudo systemctl start ssh
|
||||
|
||||
- name: Set up FreeBSD VM
|
||||
uses: vmactions/freebsd-vm@05856381fab64eeee9b038a0818f6cec649ca17a # v1.2.3
|
||||
with:
|
||||
release: ${{ matrix.release }}
|
||||
copyback: false
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
devel/blueprint-compiler \
|
||||
devel/gettext \
|
||||
devel/git \
|
||||
devel/pkgconf \
|
||||
graphics/wayland \
|
||||
lang/zig \
|
||||
security/ca_root_nss \
|
||||
textproc/hs-pandoc \
|
||||
x11-fonts/jetbrains-mono \
|
||||
x11-toolkits/libadwaita \
|
||||
x11-toolkits/gtk40 \
|
||||
x11-toolkits/gtk4-layer-shell
|
||||
|
||||
run: |
|
||||
zig env
|
||||
|
||||
- name: Run tests
|
||||
shell: freebsd {0}
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
zig build test
|
||||
|
||||
- name: Build GTK app runtime
|
||||
shell: freebsd {0}
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
zig build
|
||||
./zig-out/bin/ghostty +version
|
||||
|
6
.github/workflows/update-colorschemes.yml
vendored
6
.github/workflows/update-colorschemes.yml
vendored
@@ -22,14 +22,14 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@a289cf5d2fcd6874376aa92f0ef7f99dc923592a # v1.2.17
|
||||
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@56a7bb7b56d9a92d4fd1bc05758de7eea4a370a8 # v31.6.0
|
||||
uses: cachix/install-nix-action@fc6e360bedc9ee72d75e701397f0bb30dce77568 # v31.5.2
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16
|
||||
@@ -50,6 +50,8 @@ jobs:
|
||||
if ! git diff --exit-code build.zig.zon; then
|
||||
nix develop -c ./nix/build-support/check-zig-cache.sh --update
|
||||
nix develop -c ./nix/build-support/check-zig-cache.sh
|
||||
nix develop -c ./flatpak/build-support/check-zig-cache.sh --update
|
||||
nix develop -c ./flatpak/build-support/check-zig-cache.sh
|
||||
fi
|
||||
|
||||
# Verify the build still works. We choose an arbitrary build type
|
||||
|
23
AGENTS.md
23
AGENTS.md
@@ -1,23 +0,0 @@
|
||||
# Agent Development Guide
|
||||
|
||||
A file for [guiding coding agents](https://agents.md/).
|
||||
|
||||
## Commands
|
||||
|
||||
- **Build:** `zig build`
|
||||
- **Test (Zig):** `zig build test`
|
||||
- **Test filter (Zig)**: `zig build test -Dtest-filter=<test name>`
|
||||
- **Formatting (Zig)**: `zig fmt .`
|
||||
- **Formatting (other)**: `prettier -w .`
|
||||
|
||||
## Directory Structure
|
||||
|
||||
- Shared Zig core: `src/`
|
||||
- C API: `include/ghostty.h`
|
||||
- macOS app: `macos/`
|
||||
- GTK (Linux and FreeBSD) app: `src/apprt/gtk`
|
||||
|
||||
## macOS App
|
||||
|
||||
- Do not use `xcodebuild`
|
||||
- Use `zig build` to build the macOS app and any shared Zig code
|
@@ -119,6 +119,7 @@
|
||||
|
||||
# GTK
|
||||
/src/apprt/gtk/ @ghostty-org/gtk
|
||||
/src/apprt/gtk-ng/ @ghostty-org/gtk
|
||||
/src/os/cgroup.zig @ghostty-org/gtk
|
||||
/src/os/flatpak.zig @ghostty-org/gtk
|
||||
/dist/linux/ @ghostty-org/gtk
|
||||
@@ -168,7 +169,6 @@
|
||||
/po/es_BO.UTF-8.po @ghostty-org/es_BO
|
||||
/po/es_AR.UTF-8.po @ghostty-org/es_AR
|
||||
/po/fr_FR.UTF-8.po @ghostty-org/fr_FR
|
||||
/po/hu_HU.UTF-8.po @ghostty-org/hu_HU
|
||||
/po/id_ID.UTF-8.po @ghostty-org/id_ID
|
||||
/po/ja_JP.UTF-8.po @ghostty-org/ja_JP
|
||||
/po/mk_MK.UTF-8.po @ghostty-org/mk_MK
|
||||
|
282
CONTRIBUTING.md
282
CONTRIBUTING.md
@@ -1,9 +1,9 @@
|
||||
# Contributing to Ghostty
|
||||
# Ghostty Development Process
|
||||
|
||||
This document describes the process of contributing to Ghostty. It is intended
|
||||
for anyone considering opening an **issue**, **discussion** or **pull request**.
|
||||
For people who are interested in developing Ghostty and technical details behind
|
||||
it, please check out our ["Developing Ghostty"](HACKING.md) document as well.
|
||||
This document describes the development process for Ghostty. It is intended for
|
||||
anyone considering opening an **issue** or **pull request**. If in doubt,
|
||||
please open a [discussion](https://github.com/ghostty-org/ghostty/discussions);
|
||||
we can always convert that to an issue later.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
@@ -13,57 +13,15 @@ it, please check out our ["Developing Ghostty"](HACKING.md) document as well.
|
||||
> time to fixing bugs, maintaining features, and reviewing code, I do kindly
|
||||
> ask you spend a few minutes reading this document. Thank you. ❤️
|
||||
|
||||
## AI Assistance Notice
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> If you are using **any kind of AI assistance** to contribute to Ghostty,
|
||||
> it must be disclosed in the pull request.
|
||||
|
||||
If you are using any kind of AI assistance while contributing to Ghostty,
|
||||
**this must be disclosed in the pull request**, along with the extent to
|
||||
which AI assistance was used (e.g. docs only vs. code generation).
|
||||
If PR responses are being generated by an AI, disclose that as well.
|
||||
As a small exception, trivial tab-completion doesn't need to be disclosed,
|
||||
so long as it is limited to single keywords or short phrases.
|
||||
|
||||
An example disclosure:
|
||||
|
||||
> This PR was written primarily by Claude Code.
|
||||
|
||||
Or a more detailed disclosure:
|
||||
|
||||
> I consulted ChatGPT to understand the codebase but the solution
|
||||
> was fully authored manually by myself.
|
||||
|
||||
Failure to disclose this is first and foremost rude to the human operators
|
||||
on the other end of the pull request, but it also makes it difficult to
|
||||
determine how much scrutiny to apply to the contribution.
|
||||
|
||||
In a perfect world, AI assistance would produce equal or higher quality
|
||||
work than any human. That isn't the world we live in today, and in most cases
|
||||
it's generating slop. I say this despite being a fan of and using them
|
||||
successfully myself (with heavy supervision)!
|
||||
|
||||
When using AI assistance, we expect contributors to understand the code
|
||||
that is produced and be able to answer critical questions about it. It
|
||||
isn't a maintainers job to review a PR so broken that it requires
|
||||
significant rework to be acceptable.
|
||||
|
||||
Please be respectful to maintainers and disclose AI assistance.
|
||||
|
||||
## Quick Guide
|
||||
|
||||
### I'd like to contribute
|
||||
**I'd like to contribute!**
|
||||
|
||||
[All issues are actionable](#issues-are-actionable). Pick one and start
|
||||
working on it. Thank you. If you need help or guidance, comment on the issue.
|
||||
Issues that are extra friendly to new contributors are tagged with
|
||||
["contributor friendly"].
|
||||
All issues are actionable. Pick one and start working on it. Thank you.
|
||||
If you need help or guidance, comment on the issue. Issues that are extra
|
||||
friendly to new contributors are tagged with "contributor friendly".
|
||||
|
||||
["contributor friendly"]: https://github.com/ghostty-org/ghostty/issues?q=is%3Aissue%20is%3Aopen%20label%3A%22contributor%20friendly%22
|
||||
|
||||
### I'd like to translate Ghostty to my language
|
||||
**I'd like to translate Ghostty to my language!**
|
||||
|
||||
We have written a [Translator's Guide](po/README_TRANSLATORS.md) for
|
||||
everyone interested in contributing translations to Ghostty.
|
||||
@@ -72,39 +30,25 @@ and you can submit pull requests directly, although please make sure that
|
||||
our [Style Guide](po/README_TRANSLATORS.md#style-guide) is followed before
|
||||
submission.
|
||||
|
||||
### I have a bug! / Something isn't working
|
||||
**I have a bug!**
|
||||
|
||||
1. Search the issue tracker and discussions for similar issues. Tip: also
|
||||
search for [closed issues] and [discussions] — your issue might have already
|
||||
been fixed!
|
||||
2. If your issue hasn't been reported already, open an ["Issue Triage" discussion]
|
||||
and make sure to fill in the template **completely**. They are vital for
|
||||
maintainers to figure out important details about your setup. Because of
|
||||
this, please make sure that you _only_ use the "Issue Triage" category for
|
||||
reporting bugs — thank you!
|
||||
1. Search the issue tracker and discussions for similar issues.
|
||||
2. If you don't have steps to reproduce, open a discussion.
|
||||
3. If you have steps to reproduce, open an issue.
|
||||
|
||||
[closed issues]: https://github.com/ghostty-org/ghostty/issues?q=is%3Aissue%20state%3Aclosed
|
||||
[discussions]: https://github.com/ghostty-org/ghostty/discussions?discussions_q=is%3Aclosed
|
||||
["Issue Triage" discussion]: https://github.com/ghostty-org/ghostty/discussions/new?category=issue-triage
|
||||
**I have an idea for a feature!**
|
||||
|
||||
### I have an idea for a feature
|
||||
1. Open a discussion.
|
||||
|
||||
Open a discussion in the ["Feature Requests, Ideas" category](https://github.com/ghostty-org/ghostty/discussions/new?category=feature-requests-ideas).
|
||||
**I've implemented a feature!**
|
||||
|
||||
### I've implemented a feature
|
||||
|
||||
1. If there is an issue for the feature, open a pull request straight away.
|
||||
1. If there is an issue for the feature, open a pull request.
|
||||
2. If there is no issue, open a discussion and link to your branch.
|
||||
3. If you want to live dangerously, open a pull request and
|
||||
[hope for the best](#pull-requests-implement-an-issue).
|
||||
3. If you want to live dangerously, open a pull request and hope for the best.
|
||||
|
||||
### I have a question
|
||||
**I have a question!**
|
||||
|
||||
Open an [Q&A discussion], or join our [Discord Server] and ask away in the
|
||||
`#help` channel.
|
||||
|
||||
[Q&A discussion]: https://github.com/ghostty-org/ghostty/discussions/new?category=q-a
|
||||
[Discord Server]: https://discord.gg/ghostty
|
||||
1. Open a discussion or use Discord.
|
||||
|
||||
## General Patterns
|
||||
|
||||
@@ -142,3 +86,187 @@ pull request will be accepted with a high degree of certainty.
|
||||
> **Pull requests are NOT a place to discuss feature design.** Please do
|
||||
> not open a WIP pull request to discuss a feature. Instead, use a discussion
|
||||
> and link to your branch.
|
||||
|
||||
# Developer Guide
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> **The remainder of this file is dedicated to developers actively
|
||||
> working on Ghostty.** If you're a user reporting an issue, you can
|
||||
> ignore the rest of this document.
|
||||
|
||||
## Including and Updating Translations
|
||||
|
||||
See the [Contributor's Guide](po/README_CONTRIBUTORS.md) for more details.
|
||||
|
||||
## Input Stack Testing
|
||||
|
||||
The input stack is the part of the codebase that starts with a
|
||||
key event and ends with text encoding being sent to the pty (it
|
||||
does not include _rendering_ the text, which is part of the
|
||||
font or rendering stack).
|
||||
|
||||
If you modify any part of the input stack, you must manually verify
|
||||
all the following input cases work properly. We unfortunately do
|
||||
not automate this in any way, but if we can do that one day that'd
|
||||
save a LOT of grief and time.
|
||||
|
||||
Note: this list may not be exhaustive, I'm still working on it.
|
||||
|
||||
### Linux IME
|
||||
|
||||
IME (Input Method Editors) are a common source of bugs in the input stack,
|
||||
especially on Linux since there are multiple different IME systems
|
||||
interacting with different windowing systems and application frameworks
|
||||
all written by different organizations.
|
||||
|
||||
The following matrix should be tested to ensure that all IME input works
|
||||
properly:
|
||||
|
||||
1. Wayland, X11
|
||||
2. ibus, fcitx, none
|
||||
3. Dead key input (e.g. Spanish), CJK (e.g. Japanese), Emoji, Unicode Hex
|
||||
4. ibus versions: 1.5.29, 1.5.30, 1.5.31 (each exhibit slightly different behaviors)
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This is a **work in progress**. I'm still working on this list and it
|
||||
> is not complete. As I find more test cases, I will add them here.
|
||||
|
||||
#### Dead Key Input
|
||||
|
||||
Set your keyboard layout to "Spanish" (or another layout that uses dead keys).
|
||||
|
||||
1. Launch Ghostty
|
||||
2. Press `'`
|
||||
3. Press `a`
|
||||
4. Verify that `á` is displayed
|
||||
|
||||
Note that the dead key may or may not show a preedit state visually.
|
||||
For ibus and fcitx it does but for the "none" case it does not. Importantly,
|
||||
the text should be correct when it is sent to the pty.
|
||||
|
||||
We should also test canceling dead key input:
|
||||
|
||||
1. Launch Ghostty
|
||||
2. Press `'`
|
||||
3. Press escape
|
||||
4. Press `a`
|
||||
5. Verify that `a` is displayed (no diacritic)
|
||||
|
||||
#### CJK Input
|
||||
|
||||
Configure fcitx or ibus with a keyboard layout like Japanese or Mozc. The
|
||||
exact layout doesn't matter.
|
||||
|
||||
1. Launch Ghostty
|
||||
2. Press `Ctrl+Shift` to switch to "Hiragana"
|
||||
3. On a US physical layout, type: `konn`, you should see `こん` in preedit.
|
||||
4. Press `Enter`
|
||||
5. Verify that `こん` is displayed in the terminal.
|
||||
|
||||
We should also test switching input methods while preedit is active, which
|
||||
should commit the text:
|
||||
|
||||
1. Launch Ghostty
|
||||
2. Press `Ctrl+Shift` to switch to "Hiragana"
|
||||
3. On a US physical layout, type: `konn`, you should see `こん` in preedit.
|
||||
4. Press `Ctrl+Shift` to switch to another layout (any)
|
||||
5. Verify that `こん` is displayed in the terminal as committed text.
|
||||
|
||||
## Nix Virtual Machines
|
||||
|
||||
Several Nix virtual machine definitions are provided by the project for testing
|
||||
and developing Ghostty against multiple different Linux desktop environments.
|
||||
|
||||
Running these requires a working Nix installation, either Nix on your
|
||||
favorite Linux distribution, NixOS, or macOS with nix-darwin installed. Further
|
||||
requirements for macOS are detailed below.
|
||||
|
||||
VMs should only be run on your local desktop and then powered off when not in
|
||||
use, which will discard any changes to the VM.
|
||||
|
||||
The VM definitions provide minimal software "out of the box" but additional
|
||||
software can be installed by using standard Nix mechanisms like `nix run nixpkgs#<package>`.
|
||||
|
||||
### Linux
|
||||
|
||||
1. Check out the Ghostty source and change to the directory.
|
||||
2. Run `nix run .#<vmtype>`. `<vmtype>` can be any of the VMs defined in the
|
||||
`nix/vm` directory (without the `.nix` suffix) excluding any file prefixed
|
||||
with `common` or `create`.
|
||||
3. The VM will build and then launch. Depending on the speed of your system, this
|
||||
can take a while, but eventually you should get a new VM window.
|
||||
4. The Ghostty source directory should be mounted to `/tmp/shared` in the VM. Depending
|
||||
on what UID and GID of the user that you launched the VM as, `/tmp/shared` _may_ be
|
||||
writable by the VM user, so be careful!
|
||||
|
||||
### macOS
|
||||
|
||||
1. To run the VMs on macOS you will need to enable the Linux builder in your `nix-darwin`
|
||||
config. This _should_ be as simple as adding `nix.linux-builder.enable=true` to your
|
||||
configuration and then rebuilding. See [this](https://nixcademy.com/posts/macos-linux-builder/)
|
||||
blog post for more information about the Linux builder and how to tune the performance.
|
||||
2. Once the Linux builder has been enabled, you should be able to follow the Linux instructions
|
||||
above to launch a VM.
|
||||
|
||||
### Custom VMs
|
||||
|
||||
To easily create a custom VM without modifying the Ghostty source, create a new
|
||||
directory, then create a file called `flake.nix` with the following text in the
|
||||
new directory.
|
||||
|
||||
```
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixpkgs-unstable";
|
||||
ghostty.url = "github:ghostty-org/ghostty";
|
||||
};
|
||||
outputs = {
|
||||
nixpkgs,
|
||||
ghostty,
|
||||
...
|
||||
}: {
|
||||
nixosConfigurations.custom-vm = ghostty.create-gnome-vm {
|
||||
nixpkgs = nixpkgs;
|
||||
system = "x86_64-linux";
|
||||
overlay = ghostty.overlays.releasefast;
|
||||
# module = ./configuration.nix # also works
|
||||
module = {pkgs, ...}: {
|
||||
environment.systemPackages = [
|
||||
pkgs.btop
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The custom VM can then be run with a command like this:
|
||||
|
||||
```
|
||||
nix run .#nixosConfigurations.custom-vm.config.system.build.vm
|
||||
```
|
||||
|
||||
A file named `ghostty.qcow2` will be created that is used to persist any changes
|
||||
made in the VM. To "reset" the VM to default delete the file and it will be
|
||||
recreated the next time you run the VM.
|
||||
|
||||
### Contributing new VM definitions
|
||||
|
||||
#### VM Acceptance Criteria
|
||||
|
||||
We welcome the contribution of new VM definitions, as long as they meet the following criteria:
|
||||
|
||||
1. They should be different enough from existing VM definitions that they represent a distinct
|
||||
user (and developer) experience.
|
||||
2. There's a significant Ghostty user population that uses a similar environment.
|
||||
3. The VMs can be built using only packages from the current stable NixOS release.
|
||||
|
||||
#### VM Definition Criteria
|
||||
|
||||
1. VMs should be as minimal as possible so that they build and launch quickly.
|
||||
Additional software can be added at runtime with a command like `nix run nixpkgs#<package name>`.
|
||||
2. VMs should not expose any services to the network, or run any remote access
|
||||
software like SSH daemons, VNC or RDP.
|
||||
3. VMs should auto-login using the "ghostty" user.
|
||||
|
355
HACKING.md
355
HACKING.md
@@ -1,355 +0,0 @@
|
||||
# Developing Ghostty
|
||||
|
||||
This document describes the technical details behind Ghostty's development.
|
||||
If you'd like to open any pull requests or would like to implement new features
|
||||
into Ghostty, please make sure to read our ["Contributing to Ghostty"](CONTRIBUTING.md)
|
||||
document first.
|
||||
|
||||
To start development on Ghostty, you need to build Ghostty from a Git checkout,
|
||||
which is very similar in process to [building Ghostty from a source tarball](http://ghostty.org/docs/install/build). One key difference is that obviously
|
||||
you need to clone the Git repository instead of unpacking the source tarball:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/ghostty-org/ghostty
|
||||
cd ghostty
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Ghostty may require [extra dependencies](#extra-dependencies)
|
||||
> when building from a Git checkout compared to a source tarball.
|
||||
> Tip versions may also require a different version of Zig or other toolchains
|
||||
> (e.g. the Xcode SDK on macOS) compared to stable versions — make sure to
|
||||
> follow the steps closely!
|
||||
|
||||
When you're developing Ghostty, it's very likely that you will want to build a
|
||||
_debug_ build to diagnose issues more easily. This is already the default for
|
||||
Zig builds, so simply run `zig build` **without any `-Doptimize` flags**.
|
||||
|
||||
There are many more build steps than just `zig build`, some of which are listed
|
||||
here:
|
||||
|
||||
| Command | Description |
|
||||
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| `zig build run` | Runs Ghostty |
|
||||
| `zig build run-valgrind` | Runs Ghostty under Valgrind to [check for memory leaks](#checking-for-memory-leaks) |
|
||||
| `zig build test` | Runs unit tests (accepts `-Dtest-filter=<filter>` to only run tests whose name matches the filter) |
|
||||
| `zig build update-translations` | Updates Ghostty's translation strings (see the [Contributor's Guide on Localizing Ghostty](po/README_CONTRIBUTORS.md)) |
|
||||
| `zig build dist` | Builds a source tarball |
|
||||
| `zig build distcheck` | Builds and validates a source tarball |
|
||||
|
||||
## Extra Dependencies
|
||||
|
||||
Building Ghostty from a Git checkout on Linux requires some additional
|
||||
dependencies:
|
||||
|
||||
- `blueprint-compiler` (version 0.16.0 or newer)
|
||||
|
||||
macOS users don't require any additional dependencies.
|
||||
|
||||
## Xcode Version and SDKs
|
||||
|
||||
Building the Ghostty macOS app requires that Xcode, the macOS SDK,
|
||||
and the iOS SDK are all installed.
|
||||
|
||||
A common issue is that the incorrect version of Xcode is either
|
||||
installed or selected. Use the `xcode-select` command to
|
||||
ensure that the correct version of Xcode is selected:
|
||||
|
||||
```shell-session
|
||||
sudo xcode-select --switch /Applications/Xcode-beta.app
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> Main branch development of Ghostty is preparing for the next major
|
||||
> macOS release, Tahoe (macOS 26). Therefore, the main branch requires
|
||||
> **Xcode 26 and the macOS 26 SDK**.
|
||||
>
|
||||
> You do not need to be running on macOS 26 to build Ghostty, you can
|
||||
> still use Xcode 26 beta on macOS 15 stable.
|
||||
|
||||
## AI and Agents
|
||||
|
||||
If you're using AI assistance with Ghostty, Ghostty provides an
|
||||
[AGENTS.md file](https://github.com/ghostty-org/ghostty/blob/main/AGENTS.md)
|
||||
read by most of the popular AI agents to help produce higher quality
|
||||
results.
|
||||
|
||||
We also provide commands in `.agents/commands` that have some vetted
|
||||
prompts for common tasks that have been shown to produce good results.
|
||||
We provide these to help reduce the amount of time a contributor has to
|
||||
spend prompting the AI to get good results, and hopefully to lower the slop
|
||||
produced.
|
||||
|
||||
- `/gh-issue <number/url>` - Produces a prompt for diagnosing a GitHub
|
||||
issue, explaining the problem, and suggesting a plan for resolving it.
|
||||
Requires `gh` to be installed with read-only access to Ghostty.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> All AI assistance usage [must be disclosed](https://github.com/ghostty-org/ghostty/blob/main/CONTRIBUTING.md#ai-assistance-notice)
|
||||
> and we expect contributors to understand the code that is produced and
|
||||
> be able to answer questions about it. If you don't understand the
|
||||
> code produced, feel free to disclose that, but if it has problems, we
|
||||
> may ask you to fix it and close the issue. It isn't a maintainers job to
|
||||
> review a PR so broken that it requires significant rework to be acceptable.
|
||||
|
||||
## Linting
|
||||
|
||||
### Prettier
|
||||
|
||||
Ghostty's docs and resources (not including Zig code) are linted using
|
||||
[Prettier](https://prettier.io) with out-of-the-box settings. A Prettier CI
|
||||
check will fail builds with improper formatting. Therefore, if you are
|
||||
modifying anything Prettier will lint, you may want to install it locally and
|
||||
run this from the repo root before you commit:
|
||||
|
||||
```
|
||||
prettier --write .
|
||||
```
|
||||
|
||||
Make sure your Prettier version matches the version of Prettier in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
|
||||
|
||||
Nix users can use the following command to format with Prettier:
|
||||
|
||||
```
|
||||
nix develop -c prettier --write .
|
||||
```
|
||||
|
||||
### Alejandra
|
||||
|
||||
Nix modules are formatted with [Alejandra](https://github.com/kamadorueda/alejandra/). An Alejandra CI check
|
||||
will fail builds with improper formatting.
|
||||
|
||||
Nix users can use the following command to format with Alejandra:
|
||||
|
||||
```
|
||||
nix develop -c alejandra .
|
||||
```
|
||||
|
||||
Non-Nix users should install Alejandra and use the following command to format with Alejandra:
|
||||
|
||||
```
|
||||
alejandra .
|
||||
```
|
||||
|
||||
Make sure your Alejandra version matches the version of Alejandra in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
|
||||
|
||||
### Updating the Zig Cache Fixed-Output Derivation Hash
|
||||
|
||||
The Nix package depends on a [fixed-output
|
||||
derivation](https://nix.dev/manual/nix/stable/language/advanced-attributes.html#adv-attr-outputHash)
|
||||
that manages the Zig package cache. This allows the package to be built in the
|
||||
Nix sandbox.
|
||||
|
||||
Occasionally (usually when `build.zig.zon` is updated), the hash that
|
||||
identifies the cache will need to be updated. There are jobs that monitor the
|
||||
hash in CI, and builds will fail if it drifts.
|
||||
|
||||
To update it, you can run the following in the repository root:
|
||||
|
||||
```
|
||||
./nix/build-support/check-zig-cache-hash.sh --update
|
||||
```
|
||||
|
||||
This will write out the `nix/zigCacheHash.nix` file with the updated hash
|
||||
that can then be committed and pushed to fix the builds.
|
||||
|
||||
## Including and Updating Translations
|
||||
|
||||
See the [Contributor's Guide](po/README_CONTRIBUTORS.md) for more details.
|
||||
|
||||
## Checking for Memory Leaks
|
||||
|
||||
While Zig does an amazing job of finding and preventing memory leaks,
|
||||
Ghostty uses many third-party libraries that are written in C. Improper usage
|
||||
of those libraries or bugs in those libraries can cause memory leaks that
|
||||
Zig cannot detect by itself.
|
||||
|
||||
### On Linux
|
||||
|
||||
On Linux the recommended tool to check for memory leaks is Valgrind. The
|
||||
recommended way to run Valgrind is via `zig build`:
|
||||
|
||||
```sh
|
||||
zig build run-valgrind
|
||||
```
|
||||
|
||||
This builds a Ghostty executable with Valgrind support and runs Valgrind
|
||||
with the proper flags to ensure we're suppressing known false positives.
|
||||
|
||||
You can combine the same build args with `run-valgrind` that you can with
|
||||
`run`, such as specifying additional configurations after a trailing `--`.
|
||||
|
||||
## Input Stack Testing
|
||||
|
||||
The input stack is the part of the codebase that starts with a
|
||||
key event and ends with text encoding being sent to the pty (it
|
||||
does not include _rendering_ the text, which is part of the
|
||||
font or rendering stack).
|
||||
|
||||
If you modify any part of the input stack, you must manually verify
|
||||
all the following input cases work properly. We unfortunately do
|
||||
not automate this in any way, but if we can do that one day that'd
|
||||
save a LOT of grief and time.
|
||||
|
||||
Note: this list may not be exhaustive, I'm still working on it.
|
||||
|
||||
### Linux IME
|
||||
|
||||
IME (Input Method Editors) are a common source of bugs in the input stack,
|
||||
especially on Linux since there are multiple different IME systems
|
||||
interacting with different windowing systems and application frameworks
|
||||
all written by different organizations.
|
||||
|
||||
The following matrix should be tested to ensure that all IME input works
|
||||
properly:
|
||||
|
||||
1. Wayland, X11
|
||||
2. ibus, fcitx, none
|
||||
3. Dead key input (e.g. Spanish), CJK (e.g. Japanese), Emoji, Unicode Hex
|
||||
4. ibus versions: 1.5.29, 1.5.30, 1.5.31 (each exhibit slightly different behaviors)
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This is a **work in progress**. I'm still working on this list and it
|
||||
> is not complete. As I find more test cases, I will add them here.
|
||||
|
||||
#### Dead Key Input
|
||||
|
||||
Set your keyboard layout to "Spanish" (or another layout that uses dead keys).
|
||||
|
||||
1. Launch Ghostty
|
||||
2. Press `'`
|
||||
3. Press `a`
|
||||
4. Verify that `á` is displayed
|
||||
|
||||
Note that the dead key may or may not show a preedit state visually.
|
||||
For ibus and fcitx it does but for the "none" case it does not. Importantly,
|
||||
the text should be correct when it is sent to the pty.
|
||||
|
||||
We should also test canceling dead key input:
|
||||
|
||||
1. Launch Ghostty
|
||||
2. Press `'`
|
||||
3. Press escape
|
||||
4. Press `a`
|
||||
5. Verify that `a` is displayed (no diacritic)
|
||||
|
||||
#### CJK Input
|
||||
|
||||
Configure fcitx or ibus with a keyboard layout like Japanese or Mozc. The
|
||||
exact layout doesn't matter.
|
||||
|
||||
1. Launch Ghostty
|
||||
2. Press `Ctrl+Shift` to switch to "Hiragana"
|
||||
3. On a US physical layout, type: `konn`, you should see `こん` in preedit.
|
||||
4. Press `Enter`
|
||||
5. Verify that `こん` is displayed in the terminal.
|
||||
|
||||
We should also test switching input methods while preedit is active, which
|
||||
should commit the text:
|
||||
|
||||
1. Launch Ghostty
|
||||
2. Press `Ctrl+Shift` to switch to "Hiragana"
|
||||
3. On a US physical layout, type: `konn`, you should see `こん` in preedit.
|
||||
4. Press `Ctrl+Shift` to switch to another layout (any)
|
||||
5. Verify that `こん` is displayed in the terminal as committed text.
|
||||
|
||||
## Nix Virtual Machines
|
||||
|
||||
Several Nix virtual machine definitions are provided by the project for testing
|
||||
and developing Ghostty against multiple different Linux desktop environments.
|
||||
|
||||
Running these requires a working Nix installation, either Nix on your
|
||||
favorite Linux distribution, NixOS, or macOS with nix-darwin installed. Further
|
||||
requirements for macOS are detailed below.
|
||||
|
||||
VMs should only be run on your local desktop and then powered off when not in
|
||||
use, which will discard any changes to the VM.
|
||||
|
||||
The VM definitions provide minimal software "out of the box" but additional
|
||||
software can be installed by using standard Nix mechanisms like `nix run nixpkgs#<package>`.
|
||||
|
||||
### Linux
|
||||
|
||||
1. Check out the Ghostty source and change to the directory.
|
||||
2. Run `nix run .#<vmtype>`. `<vmtype>` can be any of the VMs defined in the
|
||||
`nix/vm` directory (without the `.nix` suffix) excluding any file prefixed
|
||||
with `common` or `create`.
|
||||
3. The VM will build and then launch. Depending on the speed of your system, this
|
||||
can take a while, but eventually you should get a new VM window.
|
||||
4. The Ghostty source directory should be mounted to `/tmp/shared` in the VM. Depending
|
||||
on what UID and GID of the user that you launched the VM as, `/tmp/shared` _may_ be
|
||||
writable by the VM user, so be careful!
|
||||
|
||||
### macOS
|
||||
|
||||
1. To run the VMs on macOS you will need to enable the Linux builder in your `nix-darwin`
|
||||
config. This _should_ be as simple as adding `nix.linux-builder.enable=true` to your
|
||||
configuration and then rebuilding. See [this](https://nixcademy.com/posts/macos-linux-builder/)
|
||||
blog post for more information about the Linux builder and how to tune the performance.
|
||||
2. Once the Linux builder has been enabled, you should be able to follow the Linux instructions
|
||||
above to launch a VM.
|
||||
|
||||
### Custom VMs
|
||||
|
||||
To easily create a custom VM without modifying the Ghostty source, create a new
|
||||
directory, then create a file called `flake.nix` with the following text in the
|
||||
new directory.
|
||||
|
||||
```
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "nixpkgs/nixpkgs-unstable";
|
||||
ghostty.url = "github:ghostty-org/ghostty";
|
||||
};
|
||||
outputs = {
|
||||
nixpkgs,
|
||||
ghostty,
|
||||
...
|
||||
}: {
|
||||
nixosConfigurations.custom-vm = ghostty.create-gnome-vm {
|
||||
nixpkgs = nixpkgs;
|
||||
system = "x86_64-linux";
|
||||
overlay = ghostty.overlays.releasefast;
|
||||
# module = ./configuration.nix # also works
|
||||
module = {pkgs, ...}: {
|
||||
environment.systemPackages = [
|
||||
pkgs.btop
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The custom VM can then be run with a command like this:
|
||||
|
||||
```
|
||||
nix run .#nixosConfigurations.custom-vm.config.system.build.vm
|
||||
```
|
||||
|
||||
A file named `ghostty.qcow2` will be created that is used to persist any changes
|
||||
made in the VM. To "reset" the VM to default delete the file and it will be
|
||||
recreated the next time you run the VM.
|
||||
|
||||
### Contributing new VM definitions
|
||||
|
||||
#### VM Acceptance Criteria
|
||||
|
||||
We welcome the contribution of new VM definitions, as long as they meet the following criteria:
|
||||
|
||||
1. They should be different enough from existing VM definitions that they represent a distinct
|
||||
user (and developer) experience.
|
||||
2. There's a significant Ghostty user population that uses a similar environment.
|
||||
3. The VMs can be built using only packages from the current stable NixOS release.
|
||||
|
||||
#### VM Definition Criteria
|
||||
|
||||
1. VMs should be as minimal as possible so that they build and launch quickly.
|
||||
Additional software can be added at runtime with a command like `nix run nixpkgs#<package name>`.
|
||||
2. VMs should not expose any services to the network, or run any remote access
|
||||
software like SSH daemons, VNC or RDP.
|
||||
3. VMs should auto-login using the "ghostty" user.
|
128
README.md
128
README.md
@@ -13,9 +13,7 @@
|
||||
·
|
||||
<a href="https://ghostty.org/docs">Documentation</a>
|
||||
·
|
||||
<a href="CONTRIBUTING.md">Contributing</a>
|
||||
·
|
||||
<a href="HACKING.md">Developing</a>
|
||||
<a href="#developing-ghostty">Developing</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
@@ -51,14 +49,6 @@ See the [download page](https://ghostty.org/download) on the Ghostty website.
|
||||
|
||||
See the [documentation](https://ghostty.org/docs) on the Ghostty website.
|
||||
|
||||
## Contributing and Developing
|
||||
|
||||
If you have any ideas, issues, etc. regarding Ghostty, or would like to
|
||||
contribute to Ghostty through pull requests, please check out our
|
||||
["Contributing to Ghostty"](CONTRIBUTING.md) document. Those who would like
|
||||
to get involved with Ghostty's development as well should also read the
|
||||
["Developing Ghostty"](HACKING.md) document for more technical details.
|
||||
|
||||
## Roadmap and Status
|
||||
|
||||
The high-level ambitious plan for the project, in order:
|
||||
@@ -194,3 +184,119 @@ SENTRY_DSN=https://e914ee84fd895c4fe324afa3e53dac76@o4507352570920960.ingest.us.
|
||||
> stack memory of each thread at the time of the crash. This information
|
||||
> is used to rebuild the stack trace but can also contain sensitive data
|
||||
> depending when the crash occurred.
|
||||
|
||||
## Developing Ghostty
|
||||
|
||||
See the documentation on the Ghostty website for
|
||||
[building Ghostty from a source tarball](http://ghostty.org/docs/install/build).
|
||||
Building Ghostty from a Git checkout is very similar, except you want to
|
||||
omit the `-Doptimize` flag to build a debug build, and you may require
|
||||
additional dependencies since the source tarball includes some processed
|
||||
files that are not in the Git repository.
|
||||
|
||||
Other useful commands:
|
||||
|
||||
- `zig build test` for running unit tests.
|
||||
- `zig build test -Dtest-filter=<filter>` for running a specific subset of those unit tests
|
||||
- `zig build run -Dconformance=<name>` runs a conformance test case from
|
||||
the `conformance` directory. The `name` is the name of the file. This runs
|
||||
in the current running terminal emulator so if you want to check the
|
||||
behavior of this project, you must run this command in Ghostty.
|
||||
|
||||
### Extra Dependencies
|
||||
|
||||
Building Ghostty from a Git checkout on Linux requires some additional
|
||||
dependencies:
|
||||
|
||||
- `blueprint-compiler`
|
||||
|
||||
macOS users don't require any additional dependencies.
|
||||
|
||||
> [!NOTE]
|
||||
> This only applies to building from a _Git checkout_. This section does
|
||||
> not apply if you're building from a released _source tarball_. For
|
||||
> source tarballs, see the
|
||||
> [website](http://ghostty.org/docs/install/build).
|
||||
|
||||
### Xcode Version and SDKs
|
||||
|
||||
Building the Ghostty macOS app requires that Xcode, the macOS SDK,
|
||||
and the iOS SDK are all installed.
|
||||
|
||||
A common issue is that the incorrect version of Xcode is either
|
||||
installed or selected. Use the `xcode-select` command to
|
||||
ensure that the correct version of Xcode is selected:
|
||||
|
||||
```shell-session
|
||||
sudo xcode-select --switch /Applications/Xcode-beta.app
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> Main branch development of Ghostty is preparing for the next major
|
||||
> macOS release, Tahoe (macOS 26). Therefore, the main branch requires
|
||||
> **Xcode 26 and the macOS 26 SDK**.
|
||||
>
|
||||
> You do not need to be running on macOS 26 to build Ghostty, you can
|
||||
> still use Xcode 26 beta on macOS 15 stable.
|
||||
|
||||
### Linting
|
||||
|
||||
#### Prettier
|
||||
|
||||
Ghostty's docs and resources (not including Zig code) are linted using
|
||||
[Prettier](https://prettier.io) with out-of-the-box settings. A Prettier CI
|
||||
check will fail builds with improper formatting. Therefore, if you are
|
||||
modifying anything Prettier will lint, you may want to install it locally and
|
||||
run this from the repo root before you commit:
|
||||
|
||||
```
|
||||
prettier --write .
|
||||
```
|
||||
|
||||
Make sure your Prettier version matches the version of Prettier in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
|
||||
|
||||
Nix users can use the following command to format with Prettier:
|
||||
|
||||
```
|
||||
nix develop -c prettier --write .
|
||||
```
|
||||
|
||||
#### Alejandra
|
||||
|
||||
Nix modules are formatted with [Alejandra](https://github.com/kamadorueda/alejandra/). An Alejandra CI check
|
||||
will fail builds with improper formatting.
|
||||
|
||||
Nix users can use the following command to format with Alejandra:
|
||||
|
||||
```
|
||||
nix develop -c alejandra .
|
||||
```
|
||||
|
||||
Non-Nix users should install Alejandra and use the following command to format with Alejandra:
|
||||
|
||||
```
|
||||
alejandra .
|
||||
```
|
||||
|
||||
Make sure your Alejandra version matches the version of Alejandra in [devShell.nix](https://github.com/ghostty-org/ghostty/blob/main/nix/devShell.nix).
|
||||
|
||||
#### Updating the Zig Cache Fixed-Output Derivation Hash
|
||||
|
||||
The Nix package depends on a [fixed-output
|
||||
derivation](https://nix.dev/manual/nix/stable/language/advanced-attributes.html#adv-attr-outputHash)
|
||||
that manages the Zig package cache. This allows the package to be built in the
|
||||
Nix sandbox.
|
||||
|
||||
Occasionally (usually when `build.zig.zon` is updated), the hash that
|
||||
identifies the cache will need to be updated. There are jobs that monitor the
|
||||
hash in CI, and builds will fail if it drifts.
|
||||
|
||||
To update it, you can run the following in the repository root:
|
||||
|
||||
```
|
||||
./nix/build-support/check-zig-cache-hash.sh --update
|
||||
```
|
||||
|
||||
This will write out the `nix/zigCacheHash.nix` file with the updated hash
|
||||
that can then be committed and pushed to fix the builds.
|
||||
|
58
build.zig
58
build.zig
@@ -19,15 +19,7 @@ pub fn build(b: *std.Build) !void {
|
||||
// All our steps which we'll hook up later. The steps are shown
|
||||
// up here just so that they are more self-documenting.
|
||||
const run_step = b.step("run", "Run the app");
|
||||
const run_valgrind_step = b.step(
|
||||
"run-valgrind",
|
||||
"Run the app under valgrind",
|
||||
);
|
||||
const test_step = b.step("test", "Run tests");
|
||||
const test_valgrind_step = b.step(
|
||||
"test-valgrind",
|
||||
"Run tests under valgrind",
|
||||
);
|
||||
const test_step = b.step("test", "Run all tests");
|
||||
const translations_step = b.step(
|
||||
"update-translations",
|
||||
"Update translation files",
|
||||
@@ -85,11 +77,9 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
// Runtime "none" is libghostty, anything else is an executable.
|
||||
if (config.app_runtime != .none) {
|
||||
if (config.emit_exe) {
|
||||
exe.install();
|
||||
resources.install();
|
||||
if (i18n) |v| v.install();
|
||||
}
|
||||
exe.install();
|
||||
resources.install();
|
||||
if (i18n) |v| v.install();
|
||||
} else {
|
||||
// Libghostty
|
||||
//
|
||||
@@ -191,31 +181,6 @@ pub fn build(b: *std.Build) !void {
|
||||
}
|
||||
}
|
||||
|
||||
// Valgrind
|
||||
if (config.app_runtime != .none) {
|
||||
// We need to rebuild Ghostty with a baseline CPU target.
|
||||
const valgrind_exe = exe: {
|
||||
var valgrind_config = config;
|
||||
valgrind_config.target = valgrind_config.baselineTarget();
|
||||
break :exe try buildpkg.GhosttyExe.init(
|
||||
b,
|
||||
&valgrind_config,
|
||||
&deps,
|
||||
);
|
||||
};
|
||||
|
||||
const run_cmd = b.addSystemCommand(&.{
|
||||
"valgrind",
|
||||
"--leak-check=full",
|
||||
"--num-callers=50",
|
||||
b.fmt("--suppressions={s}", .{b.pathFromRoot("valgrind.supp")}),
|
||||
"--gen-suppressions=all",
|
||||
});
|
||||
run_cmd.addArtifactArg(valgrind_exe.exe);
|
||||
if (b.args) |args| run_cmd.addArgs(args);
|
||||
run_valgrind_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
|
||||
// Tests
|
||||
{
|
||||
const test_exe = b.addTest(.{
|
||||
@@ -223,7 +188,7 @@ pub fn build(b: *std.Build) !void {
|
||||
.filters = if (test_filter) |v| &.{v} else &.{},
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = config.baselineTarget(),
|
||||
.target = config.target,
|
||||
.optimize = .Debug,
|
||||
.strip = false,
|
||||
.omit_frame_pointer = false,
|
||||
@@ -233,21 +198,8 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
if (config.emit_test_exe) b.installArtifact(test_exe);
|
||||
_ = try deps.add(test_exe);
|
||||
|
||||
// Normal test running
|
||||
const test_run = b.addRunArtifact(test_exe);
|
||||
test_step.dependOn(&test_run.step);
|
||||
|
||||
// Valgrind test running
|
||||
const valgrind_run = b.addSystemCommand(&.{
|
||||
"valgrind",
|
||||
"--leak-check=full",
|
||||
"--num-callers=50",
|
||||
b.fmt("--suppressions={s}", .{b.pathFromRoot("valgrind.supp")}),
|
||||
"--gen-suppressions=all",
|
||||
});
|
||||
valgrind_run.addArtifactArg(test_exe);
|
||||
test_valgrind_step.dependOn(&valgrind_run.step);
|
||||
}
|
||||
|
||||
// update-translations does what it sounds like and updates the "pot"
|
||||
|
@@ -20,8 +20,8 @@
|
||||
},
|
||||
.z2d = .{
|
||||
// vancluever/z2d
|
||||
.url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz",
|
||||
.hash = "z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l",
|
||||
.url = "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz",
|
||||
.hash = "z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg",
|
||||
.lazy = true,
|
||||
},
|
||||
.zig_objc = .{
|
||||
@@ -55,8 +55,8 @@
|
||||
.gobject = .{
|
||||
// https://github.com/jcollie/ghostty-gobject based on zig_gobject
|
||||
// Temporary until we generate them at build time automatically.
|
||||
.url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.15.1-2025-09-04-48-1/ghostty-gobject-0.15.1-2025-09-04-48-1.tar.zst",
|
||||
.hash = "gobject-0.3.0-Skun7ET3nQAc0LzvO0NAvTiGGnmkF36cnmbeCAF6MB7Z",
|
||||
.url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst",
|
||||
.hash = "gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM",
|
||||
.lazy = true,
|
||||
},
|
||||
|
||||
@@ -112,8 +112,8 @@
|
||||
// Other
|
||||
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
||||
.iterm2_themes = .{
|
||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6cdbc8501d48601302e32d6b53e9e7934bf354b4.tar.gz",
|
||||
.hash = "N-V-__8AAAtjXwSdhZq_xYbCXo0SZMqoNoQuHFkC07sijQME",
|
||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz",
|
||||
.hash = "N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
18
build.zig.zon.json
generated
18
build.zig.zon.json
generated
@@ -24,10 +24,10 @@
|
||||
"url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
|
||||
"hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U="
|
||||
},
|
||||
"gobject-0.3.0-Skun7ET3nQAc0LzvO0NAvTiGGnmkF36cnmbeCAF6MB7Z": {
|
||||
"gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM": {
|
||||
"name": "gobject",
|
||||
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.15.1-2025-09-04-48-1/ghostty-gobject-0.15.1-2025-09-04-48-1.tar.zst",
|
||||
"hash": "sha256-h6aKUerGlX2ATVEeoN03eWaqDqvUmKdedgpxrSoHvrY="
|
||||
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst",
|
||||
"hash": "sha256-B0ziLzKud+kdKu5T1BTE9GMh8EPM/KhhhoNJlys5QPI="
|
||||
},
|
||||
"N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr": {
|
||||
"name": "gtk4_layer_shell",
|
||||
@@ -49,10 +49,10 @@
|
||||
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
|
||||
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
|
||||
},
|
||||
"N-V-__8AAAtjXwSdhZq_xYbCXo0SZMqoNoQuHFkC07sijQME": {
|
||||
"N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu": {
|
||||
"name": "iterm2_themes",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6cdbc8501d48601302e32d6b53e9e7934bf354b4.tar.gz",
|
||||
"hash": "sha256-NlUXcBOmaA8W+7RXuXcn9TIhm964dXO2Op4QCQxhDyc="
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz",
|
||||
"hash": "sha256-gl42NOZ59ok+umHCHbdBQhWCgFVpj5PAZDVGhJRpbiA="
|
||||
},
|
||||
"N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": {
|
||||
"name": "jetbrains_mono",
|
||||
@@ -129,10 +129,10 @@
|
||||
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
|
||||
"hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM="
|
||||
},
|
||||
"z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l": {
|
||||
"z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg": {
|
||||
"name": "z2d",
|
||||
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz",
|
||||
"hash": "sha256-tWrLlEOU4/0ZOlzgGOXB08fW7sqfyAFf3rlv0wl9b/c="
|
||||
"url": "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz",
|
||||
"hash": "sha256-wfadegeixcbgxRzRtf6M2H34CTuvDM22XVIhuufFBG4="
|
||||
},
|
||||
"zf-0.10.3-OIRy8aiIAACLrBllz0zjxaH0aOe5oNm3KtEMyCntST-9": {
|
||||
"name": "zf",
|
||||
|
19
build.zig.zon.nix
generated
19
build.zig.zon.nix
generated
@@ -49,7 +49,6 @@
|
||||
inherit name rev hash;
|
||||
url = url_without_query;
|
||||
deepClone = false;
|
||||
fetchSubmodules = false;
|
||||
};
|
||||
|
||||
fetchZigArtifact = {
|
||||
@@ -123,11 +122,11 @@ in
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "gobject-0.3.0-Skun7ET3nQAc0LzvO0NAvTiGGnmkF36cnmbeCAF6MB7Z";
|
||||
name = "gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM";
|
||||
path = fetchZigArtifact {
|
||||
name = "gobject";
|
||||
url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.15.1-2025-09-04-48-1/ghostty-gobject-0.15.1-2025-09-04-48-1.tar.zst";
|
||||
hash = "sha256-h6aKUerGlX2ATVEeoN03eWaqDqvUmKdedgpxrSoHvrY=";
|
||||
url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst";
|
||||
hash = "sha256-B0ziLzKud+kdKu5T1BTE9GMh8EPM/KhhhoNJlys5QPI=";
|
||||
};
|
||||
}
|
||||
{
|
||||
@@ -163,11 +162,11 @@ in
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "N-V-__8AAAtjXwSdhZq_xYbCXo0SZMqoNoQuHFkC07sijQME";
|
||||
name = "N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu";
|
||||
path = fetchZigArtifact {
|
||||
name = "iterm2_themes";
|
||||
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6cdbc8501d48601302e32d6b53e9e7934bf354b4.tar.gz";
|
||||
hash = "sha256-NlUXcBOmaA8W+7RXuXcn9TIhm964dXO2Op4QCQxhDyc=";
|
||||
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz";
|
||||
hash = "sha256-gl42NOZ59ok+umHCHbdBQhWCgFVpj5PAZDVGhJRpbiA=";
|
||||
};
|
||||
}
|
||||
{
|
||||
@@ -291,11 +290,11 @@ in
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l";
|
||||
name = "z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg";
|
||||
path = fetchZigArtifact {
|
||||
name = "z2d";
|
||||
url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz";
|
||||
hash = "sha256-tWrLlEOU4/0ZOlzgGOXB08fW7sqfyAFf3rlv0wl9b/c=";
|
||||
url = "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz";
|
||||
hash = "sha256-wfadegeixcbgxRzRtf6M2H34CTuvDM22XVIhuufFBG4=";
|
||||
};
|
||||
}
|
||||
{
|
||||
|
6
build.zig.zon.txt
generated
6
build.zig.zon.txt
generated
@@ -27,9 +27,9 @@ https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d6
|
||||
https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz
|
||||
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
|
||||
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
|
||||
https://github.com/jcollie/ghostty-gobject/releases/download/0.15.1-2025-09-04-48-1/ghostty-gobject-0.15.1-2025-09-04-48-1.tar.zst
|
||||
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6cdbc8501d48601302e32d6b53e9e7934bf354b4.tar.gz
|
||||
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst
|
||||
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz
|
||||
https://github.com/mitchellh/libxev/archive/7f803181b158a10fec8619f793e3b4df515566cb.tar.gz
|
||||
https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz
|
||||
https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz
|
||||
https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz
|
||||
https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz
|
||||
|
4
dist/linux/app.desktop.in
vendored
4
dist/linux/app.desktop.in
vendored
@@ -4,7 +4,7 @@ Name=@NAME@
|
||||
Type=Application
|
||||
Comment=A terminal emulator
|
||||
TryExec=@GHOSTTY@
|
||||
Exec=@GHOSTTY@ --gtk-single-instance=true
|
||||
Exec=@GHOSTTY@ --launched-from=desktop
|
||||
Icon=com.mitchellh.ghostty
|
||||
Categories=System;TerminalEmulator;
|
||||
Keywords=terminal;tty;pty;
|
||||
@@ -23,4 +23,4 @@ X-KDE-Shortcuts=Ctrl+Alt+T
|
||||
|
||||
[Desktop Action new-window]
|
||||
Name=New Window
|
||||
Exec=@GHOSTTY@ --gtk-single-instance=true
|
||||
Exec=@GHOSTTY@ --launched-from=desktop
|
||||
|
2
dist/linux/dbus.service.flatpak.in
vendored
2
dist/linux/dbus.service.flatpak.in
vendored
@@ -1,3 +1,3 @@
|
||||
[D-BUS Service]
|
||||
Name=@APPID@
|
||||
Exec=@GHOSTTY@ --gtk-single-instance=true --initial-window=false
|
||||
Exec=@GHOSTTY@ --launched-from=dbus
|
||||
|
2
dist/linux/dbus.service.in
vendored
2
dist/linux/dbus.service.in
vendored
@@ -1,4 +1,4 @@
|
||||
[D-BUS Service]
|
||||
Name=@APPID@
|
||||
SystemdService=app-@APPID@.service
|
||||
Exec=@GHOSTTY@ --gtk-single-instance=true --initial-window=false
|
||||
Exec=@GHOSTTY@ --launched-from=dbus
|
||||
|
2
dist/linux/systemd.service.in
vendored
2
dist/linux/systemd.service.in
vendored
@@ -8,7 +8,7 @@ Requires=dbus.socket
|
||||
Type=notify-reload
|
||||
ReloadSignal=SIGUSR2
|
||||
BusName=@APPID@
|
||||
ExecStart=@GHOSTTY@ --gtk-single-instance=true --initial-window=false
|
||||
ExecStart=@GHOSTTY@ --launched-from=systemd
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
|
28
flake.lock
generated
28
flake.lock
generated
@@ -47,19 +47,6 @@
|
||||
"url": "https://channels.nixos.org/nixos-25.05/nixexprs.tar.xz"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1755972213,
|
||||
"narHash": "sha256-VYK7aDAv8H1enXn1ECRHmGbeY6RqLnNwUJkOwloIsko=",
|
||||
"rev": "73e96df7cff5783f45e21342a75a1540c4eddce4",
|
||||
"type": "tarball",
|
||||
"url": "https://releases.nixos.org/nixos/unstable-small/nixos-25.11pre850642.73e96df7cff5/nixexprs.tar.xz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://channels.nixos.org/nixos-unstable-small/nixexprs.tar.xz"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
@@ -112,20 +99,25 @@
|
||||
},
|
||||
"zon2nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1757167408,
|
||||
"narHash": "sha256-4XyJ6fmKd9wgJ7vHUQuULYy5ps2gUgkkDk/PrJb2OPY=",
|
||||
"lastModified": 1742104771,
|
||||
"narHash": "sha256-LhidlyEA9MP8jGe1rEnyjGFCzLLgCdDpYeWggibayr0=",
|
||||
"owner": "jcollie",
|
||||
"repo": "zon2nix",
|
||||
"rev": "dc78177e2ad28d5a407c9e783ee781bd559d7dd5",
|
||||
"rev": "56c159be489cc6c0e73c3930bd908ddc6fe89613",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "jcollie",
|
||||
"repo": "zon2nix",
|
||||
"rev": "dc78177e2ad28d5a407c9e783ee781bd559d7dd5",
|
||||
"rev": "56c159be489cc6c0e73c3930bd908ddc6fe89613",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
|
@@ -24,12 +24,9 @@
|
||||
};
|
||||
|
||||
zon2nix = {
|
||||
url = "github:jcollie/zon2nix?rev=dc78177e2ad28d5a407c9e783ee781bd559d7dd5";
|
||||
url = "github:jcollie/zon2nix?rev=56c159be489cc6c0e73c3930bd908ddc6fe89613";
|
||||
inputs = {
|
||||
# Don't override nixpkgs until Zig 0.15 is available in the Nix branch
|
||||
# we are using for "normal" builds.
|
||||
#
|
||||
# nixpkgs.follows = "nixpkgs";
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
|
108
flatpak/build-support/check-zig-cache.sh
Executable file
108
flatpak/build-support/check-zig-cache.sh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# This script checks if the flatpak/zig-packages.json 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:
|
||||
#
|
||||
# - flatpak/zig-packages.json
|
||||
#
|
||||
# All of these are auto-generated and should not be edited manually.
|
||||
|
||||
# Nothing in this script should fail.
|
||||
set -eu
|
||||
set -o pipefail
|
||||
|
||||
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 " ./flatpak/build-support/check-zig-cache.sh --update"
|
||||
echo " git add flatpak/zig-packages.json"
|
||||
echo " git commit -m \"flatpak: update zig-packages.json\""
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Turn Nix's base64 hashes into regular hexadecimal form
|
||||
decode_hash() {
|
||||
input=$1
|
||||
input=${input#sha256-}
|
||||
echo "$input" | base64 -d | od -vAn -t x1 | tr -d ' \n'
|
||||
}
|
||||
|
||||
ROOT="$(realpath "$(dirname "$0")/../../")"
|
||||
ZIG_PACKAGES_JSON="$ROOT/flatpak/zig-packages.json"
|
||||
BUILD_ZIG_ZON_JSON="$ROOT/build.zig.zon.json"
|
||||
|
||||
if [ ! -f "${BUILD_ZIG_ZON_JSON}" ]; then
|
||||
echo -e "\nERROR: build.zig.zon2json-lock missing."
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "${ZIG_PACKAGES_JSON}" ]; then
|
||||
OLD_HASH=$(sha512sum "${ZIG_PACKAGES_JSON}" | awk '{print $1}')
|
||||
fi
|
||||
|
||||
while read -r url sha256 dest; do
|
||||
src_type=archive
|
||||
sha256=$(decode_hash "$sha256")
|
||||
git_commit=
|
||||
if [[ "$url" =~ ^git\+* ]]; then
|
||||
src_type=git
|
||||
sha256=
|
||||
url=${url#git+}
|
||||
git_commit=${url##*#}
|
||||
url=${url%%/\?ref*}
|
||||
url=${url%%#*}
|
||||
fi
|
||||
|
||||
jq \
|
||||
-nec \
|
||||
--arg type "$src_type" \
|
||||
--arg url "$url" \
|
||||
--arg git_commit "$git_commit" \
|
||||
--arg dest "$dest" \
|
||||
--arg sha256 "$sha256" \
|
||||
'{
|
||||
type: $type,
|
||||
url: $url,
|
||||
commit: $git_commit,
|
||||
dest: $dest,
|
||||
sha256: $sha256,
|
||||
} | with_entries(select(.value != ""))'
|
||||
done < <(jq -rc 'to_entries[] | [.value.url, .value.hash, "vendor/p/\(.key)"] | @tsv' "$BUILD_ZIG_ZON_JSON") |
|
||||
jq -s '.' >"$WORK_DIR/zig-packages.json"
|
||||
|
||||
NEW_HASH=$(sha512sum "$WORK_DIR/zig-packages.json" | awk '{print $1}')
|
||||
|
||||
if [ "${OLD_HASH}" == "${NEW_HASH}" ]; then
|
||||
echo -e "\nOK: flatpak/zig-packages.json unchanged."
|
||||
exit 0
|
||||
elif [ "${1:-}" != "--update" ]; then
|
||||
echo -e "\nERROR: flatpak/zig-packages.json needs to be updated."
|
||||
echo ""
|
||||
echo " * Old hash: ${OLD_HASH}"
|
||||
echo " * New hash: ${NEW_HASH}"
|
||||
help
|
||||
exit 1
|
||||
else
|
||||
mv "$WORK_DIR/zig-packages.json" "$ZIG_PACKAGES_JSON"
|
||||
echo -e "\nOK: flatpak/zig-packages.json updated."
|
||||
exit 0
|
||||
fi
|
@@ -2,6 +2,8 @@ app-id: com.mitchellh.ghostty-debug
|
||||
runtime: org.gnome.Platform
|
||||
runtime-version: "48"
|
||||
sdk: org.gnome.Sdk
|
||||
sdk-extensions:
|
||||
- org.freedesktop.Sdk.Extension.ziglang
|
||||
default-branch: tip
|
||||
command: ghostty
|
||||
rename-icon: com.mitchellh.ghostty
|
||||
@@ -35,7 +37,7 @@ modules:
|
||||
- name: ghostty
|
||||
buildsystem: simple
|
||||
build-options:
|
||||
append-path: /app/zig
|
||||
append-path: /usr/lib/sdk/ziglang
|
||||
build-commands:
|
||||
- zig build
|
||||
-Doptimize=Debug
|
||||
|
@@ -2,6 +2,8 @@ app-id: com.mitchellh.ghostty
|
||||
runtime: org.gnome.Platform
|
||||
runtime-version: "48"
|
||||
sdk: org.gnome.Sdk
|
||||
sdk-extensions:
|
||||
- org.freedesktop.Sdk.Extension.ziglang
|
||||
default-branch: tip
|
||||
command: ghostty
|
||||
finish-args:
|
||||
@@ -34,7 +36,7 @@ modules:
|
||||
- name: ghostty
|
||||
buildsystem: simple
|
||||
build-options:
|
||||
append-path: /app/zig
|
||||
append-path: /usr/lib/sdk/ziglang
|
||||
build-commands:
|
||||
- zig build
|
||||
-Doptimize=ReleaseFast
|
||||
|
@@ -3,24 +3,6 @@ buildsystem: simple
|
||||
build-commands:
|
||||
- true
|
||||
modules:
|
||||
- name: zig
|
||||
buildsystem: simple
|
||||
cleanup:
|
||||
- "*"
|
||||
build-commands:
|
||||
- mkdir -p /app/zig
|
||||
- cp -r ./* /app/zig
|
||||
- chmod a+x /app/zig/zig
|
||||
sources:
|
||||
- type: archive
|
||||
sha256: 24aeeec8af16c381934a6cd7d95c807a8cb2cf7df9fa40d359aa884195c4716c
|
||||
url: https://ziglang.org/download/0.14.1/zig-x86_64-linux-0.14.1.tar.xz
|
||||
only-arches: [x86_64]
|
||||
- type: archive
|
||||
sha256: f7a654acc967864f7a050ddacfaa778c7504a0eca8d2b678839c21eea47c992b
|
||||
url: https://ziglang.org/download/0.14.1/zig-aarch64-linux-0.14.1.tar.xz
|
||||
only-arches: [aarch64]
|
||||
|
||||
- name: bzip2-redirect
|
||||
buildsystem: simple
|
||||
build-commands:
|
||||
|
@@ -31,9 +31,9 @@
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.15.1-2025-09-04-48-1/ghostty-gobject-0.15.1-2025-09-04-48-1.tar.zst",
|
||||
"dest": "vendor/p/gobject-0.3.0-Skun7ET3nQAc0LzvO0NAvTiGGnmkF36cnmbeCAF6MB7Z",
|
||||
"sha256": "87a68a51eac6957d804d511ea0dd377966aa0eabd498a75e760a71ad2a07beb6"
|
||||
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst",
|
||||
"dest": "vendor/p/gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM",
|
||||
"sha256": "074ce22f32ae77e91d2aee53d414c4f46321f043ccfca861868349972b3940f2"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
@@ -61,9 +61,9 @@
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6cdbc8501d48601302e32d6b53e9e7934bf354b4.tar.gz",
|
||||
"dest": "vendor/p/N-V-__8AAAtjXwSdhZq_xYbCXo0SZMqoNoQuHFkC07sijQME",
|
||||
"sha256": "3655177013a6680f16fbb457b97727f532219bdeb87573b63a9e10090c610f27"
|
||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz",
|
||||
"dest": "vendor/p/N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu",
|
||||
"sha256": "825e3634e679f6893eba61c21db7414215828055698f93c06435468494696e20"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
@@ -157,9 +157,9 @@
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.7.2.tar.gz",
|
||||
"dest": "vendor/p/z2d-0.7.2-j5P_Hm1oDQDQsWpGfSCMARhowBnuTHlQ_sBfij6TuG7l",
|
||||
"sha256": "b56acb944394e3fd193a5ce018e5c1d3c7d6eeca9fc8015fdeb96fd3097d6ff7"
|
||||
"url": "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz",
|
||||
"dest": "vendor/p/z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg",
|
||||
"sha256": "c1f69d7a07a2c5c6e0c51cd1b5fe8cd87df8093baf0ccdb65d5221bae7c5046e"
|
||||
},
|
||||
{
|
||||
"type": "archive",
|
||||
|
@@ -419,7 +419,6 @@ typedef struct {
|
||||
ghostty_env_var_s* env_vars;
|
||||
size_t env_var_count;
|
||||
const char* initial_input;
|
||||
bool wait_after_command;
|
||||
} ghostty_surface_config_s;
|
||||
|
||||
typedef struct {
|
||||
@@ -451,28 +450,6 @@ typedef struct {
|
||||
ghostty_config_color_s colors[256];
|
||||
} ghostty_config_palette_s;
|
||||
|
||||
// config.QuickTerminalSize
|
||||
typedef enum {
|
||||
GHOSTTY_QUICK_TERMINAL_SIZE_NONE,
|
||||
GHOSTTY_QUICK_TERMINAL_SIZE_PERCENTAGE,
|
||||
GHOSTTY_QUICK_TERMINAL_SIZE_PIXELS,
|
||||
} ghostty_quick_terminal_size_tag_e;
|
||||
|
||||
typedef union {
|
||||
float percentage;
|
||||
uint32_t pixels;
|
||||
} ghostty_quick_terminal_size_value_u;
|
||||
|
||||
typedef struct {
|
||||
ghostty_quick_terminal_size_tag_e tag;
|
||||
ghostty_quick_terminal_size_value_u value;
|
||||
} ghostty_quick_terminal_size_s;
|
||||
|
||||
typedef struct {
|
||||
ghostty_quick_terminal_size_s primary;
|
||||
ghostty_quick_terminal_size_s secondary;
|
||||
} ghostty_config_quick_terminal_size_s;
|
||||
|
||||
// apprt.Target.Key
|
||||
typedef enum {
|
||||
GHOSTTY_TARGET_APP,
|
||||
@@ -703,12 +680,6 @@ typedef struct {
|
||||
uintptr_t len;
|
||||
} ghostty_action_open_url_s;
|
||||
|
||||
// apprt.action.CloseTabMode
|
||||
typedef enum {
|
||||
GHOSTTY_ACTION_CLOSE_TAB_MODE_THIS,
|
||||
GHOSTTY_ACTION_CLOSE_TAB_MODE_OTHER,
|
||||
} ghostty_action_close_tab_mode_e;
|
||||
|
||||
// apprt.surface.Message.ChildExited
|
||||
typedef struct {
|
||||
uint32_t exit_code;
|
||||
@@ -722,15 +693,15 @@ typedef enum {
|
||||
GHOSTTY_PROGRESS_STATE_ERROR,
|
||||
GHOSTTY_PROGRESS_STATE_INDETERMINATE,
|
||||
GHOSTTY_PROGRESS_STATE_PAUSE,
|
||||
} ghostty_action_progress_report_state_e;
|
||||
} ghostty_terminal_osc_command_progressreport_state_e;
|
||||
|
||||
// terminal.osc.Command.ProgressReport.C
|
||||
typedef struct {
|
||||
ghostty_action_progress_report_state_e state;
|
||||
ghostty_terminal_osc_command_progressreport_state_e state;
|
||||
// -1 if no progress was reported, otherwise 0-100 indicating percent
|
||||
// completeness.
|
||||
int8_t progress;
|
||||
} ghostty_action_progress_report_s;
|
||||
} ghostty_terminal_osc_command_progressreport_s;
|
||||
|
||||
// apprt.Action.Key
|
||||
typedef enum {
|
||||
@@ -815,9 +786,8 @@ typedef union {
|
||||
ghostty_action_reload_config_s reload_config;
|
||||
ghostty_action_config_change_s config_change;
|
||||
ghostty_action_open_url_s open_url;
|
||||
ghostty_action_close_tab_mode_e close_tab_mode;
|
||||
ghostty_surface_message_childexited_s child_exited;
|
||||
ghostty_action_progress_report_s progress_report;
|
||||
ghostty_terminal_osc_command_progressreport_s progress_report;
|
||||
} ghostty_action_u;
|
||||
|
||||
typedef struct {
|
||||
@@ -964,7 +934,7 @@ void ghostty_surface_mouse_scroll(ghostty_surface_t,
|
||||
double,
|
||||
ghostty_input_scroll_mods_t);
|
||||
void ghostty_surface_mouse_pressure(ghostty_surface_t, uint32_t, double);
|
||||
void ghostty_surface_ime_point(ghostty_surface_t, double*, double*, double*, double*);
|
||||
void ghostty_surface_ime_point(ghostty_surface_t, double*, double*);
|
||||
void ghostty_surface_request_close(ghostty_surface_t);
|
||||
void ghostty_surface_split(ghostty_surface_t, ghostty_action_split_direction_e);
|
||||
void ghostty_surface_split_focus(ghostty_surface_t,
|
||||
|
@@ -104,7 +104,6 @@
|
||||
A5AEB1652D5BE7D000513529 /* LastWindowPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AEB1642D5BE7BF00513529 /* LastWindowPosition.swift */; };
|
||||
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
||||
A5B4EA852DFE691B0022C3A2 /* NSMenuItem+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B4EA842DFE69140022C3A2 /* NSMenuItem+Extension.swift */; };
|
||||
A5BB78B92DF9D8CE009AC3FA /* QuickTerminalSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */; };
|
||||
A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */; };
|
||||
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378D2D31D6C100931030 /* Weak.swift */; };
|
||||
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */; };
|
||||
@@ -127,7 +126,6 @@
|
||||
A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CF66D62D29DDB100139794 /* Ghostty.Event.swift */; };
|
||||
A5D0AF3B2B36A1DE00D21823 /* TerminalRestorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D0AF3A2B36A1DE00D21823 /* TerminalRestorable.swift */; };
|
||||
A5D0AF3D2B37804400D21823 /* CodableBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D0AF3C2B37804400D21823 /* CodableBridge.swift */; };
|
||||
A5D689BE2E654D98002E2346 /* Ghostty.Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */; };
|
||||
A5E112932AF73E6E00C6E0C2 /* ClipboardConfirmation.xib in Resources */ = {isa = PBXBuildFile; fileRef = A5E112922AF73E6E00C6E0C2 /* ClipboardConfirmation.xib */; };
|
||||
A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */; };
|
||||
A5E112972AF7401B00C6E0C2 /* ClipboardConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112962AF7401B00C6E0C2 /* ClipboardConfirmationView.swift */; };
|
||||
@@ -254,7 +252,6 @@
|
||||
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>"; };
|
||||
A5B4EA842DFE69140022C3A2 /* NSMenuItem+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSMenuItem+Extension.swift"; sourceTree = "<group>"; };
|
||||
A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalSize.swift; sourceTree = "<group>"; };
|
||||
A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardLayout.swift; sourceTree = "<group>"; };
|
||||
A5CA378D2D31D6C100931030 /* Weak.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weak.swift; sourceTree = "<group>"; };
|
||||
A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableWindowView.swift; sourceTree = "<group>"; };
|
||||
@@ -640,7 +637,6 @@
|
||||
CFBB5FE92D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift */,
|
||||
A5CBD0632CA122E70017A1AE /* QuickTerminalPosition.swift */,
|
||||
A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */,
|
||||
A5BB78B82DF9D8CE009AC3FA /* QuickTerminalSize.swift */,
|
||||
A5CBD05F2CA0C9080017A1AE /* QuickTerminalWindow.swift */,
|
||||
);
|
||||
path = QuickTerminal;
|
||||
@@ -943,10 +939,6 @@
|
||||
A53A297B2DB2E49700B6E02C /* CommandPalette.swift in Sources */,
|
||||
A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */,
|
||||
A5BB78B92DF9D8CE009AC3FA /* QuickTerminalSize.swift in Sources */,
|
||||
A51B78472AF4B58B00F3EDB9 /* TitlebarTabsVenturaTerminalWindow.swift in Sources */,
|
||||
A5BB78B92DF9D8CE009AC3FA /* QuickTerminalSize.swift in Sources */,
|
||||
A57D79272C9C879B001D522E /* SecureInput.swift in Sources */,
|
||||
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
|
||||
A5593FE12DF8D74000B47B10 /* HiddenTitlebarTerminalWindow.swift in Sources */,
|
||||
@@ -988,7 +980,6 @@
|
||||
A5333E232B5A219A008AEFF7 /* SurfaceView.swift in Sources */,
|
||||
A5333E202B5A2111008AEFF7 /* SurfaceView_UIKit.swift in Sources */,
|
||||
A5333E1D2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */,
|
||||
A5D689BE2E654D98002E2346 /* Ghostty.Action.swift in Sources */,
|
||||
A53D0C9C2B543F7B00305CE6 /* Package.swift in Sources */,
|
||||
A53D0C9B2B543F3B00305CE6 /* Ghostty.App.swift in Sources */,
|
||||
A5333E242B5A22D9008AEFF7 /* Ghostty.Shell.swift in Sources */,
|
||||
|
@@ -6,8 +6,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sparkle-project/Sparkle",
|
||||
"state" : {
|
||||
"revision" : "df074165274afaa39539c05d57b0832620775b11",
|
||||
"version" : "2.7.1"
|
||||
"revision" : "0ef1ee0220239b3776f433314515fd849025673f",
|
||||
"version" : "2.6.4"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@@ -119,9 +119,6 @@ class AppDelegate: NSObject,
|
||||
@Published private(set) var appIcon: NSImage? = nil {
|
||||
didSet {
|
||||
NSApplication.shared.applicationIconImage = appIcon
|
||||
let appPath = Bundle.main.bundlePath
|
||||
NSWorkspace.shared.setIcon(appIcon, forFile: appPath, options: [])
|
||||
NSWorkspace.shared.noteFileSystemChanged(appPath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,21 +255,13 @@ class AppDelegate: NSObject,
|
||||
|
||||
// Setup signal handlers
|
||||
setupSignals()
|
||||
|
||||
switch Ghostty.launchSource {
|
||||
case .app:
|
||||
// Don't have to do anything.
|
||||
break
|
||||
|
||||
case .zig_run, .cli:
|
||||
// Part of launch services (clicking an app, using `open`, etc.) activates
|
||||
// the application and brings it to the front. When using the CLI we don't
|
||||
// get this behavior, so we have to do it manually.
|
||||
|
||||
|
||||
// If we launched via zig run then we need to force foreground.
|
||||
if Ghostty.launchSource == .zig_run {
|
||||
// This never gets called until we click the dock icon. This forces it
|
||||
// activate immediately.
|
||||
applicationDidBecomeActive(.init(name: NSApplication.didBecomeActiveNotification))
|
||||
|
||||
|
||||
// We run in the background, this forces us to the front.
|
||||
DispatchQueue.main.async {
|
||||
NSApp.setActivationPolicy(.regular)
|
||||
@@ -402,69 +391,34 @@ class AppDelegate: NSObject,
|
||||
// Ghostty will validate as well but we can avoid creating an entirely new
|
||||
// surface by doing our own validation here. We can also show a useful error
|
||||
// this way.
|
||||
|
||||
|
||||
var isDirectory = ObjCBool(true)
|
||||
guard FileManager.default.fileExists(atPath: filename, isDirectory: &isDirectory) else { return false }
|
||||
|
||||
// Set to true if confirmation is required before starting up the
|
||||
// new terminal.
|
||||
var requiresConfirm: Bool = false
|
||||
|
||||
|
||||
// Initialize the surface config which will be used to create the tab or window for the opened file.
|
||||
var config = Ghostty.SurfaceConfiguration()
|
||||
|
||||
|
||||
if (isDirectory.boolValue) {
|
||||
// When opening a directory, check the configuration to decide
|
||||
// whether to open in a new tab or new window.
|
||||
// When opening a directory, create a new tab in the main
|
||||
// window with that as the working directory.
|
||||
// If no windows exist, a new one will be created.
|
||||
config.workingDirectory = filename
|
||||
_ = TerminalController.newTab(ghostty, withBaseConfig: config)
|
||||
} else {
|
||||
// Unconditionally require confirmation in the file execution case.
|
||||
// In the future I have ideas about making this more fine-grained if
|
||||
// we can not inherit of unsandboxed state. For now, we need to confirm
|
||||
// because there is a sandbox escape possible if a sandboxed application
|
||||
// somehow is tricked into `open`-ing a non-sandboxed application.
|
||||
requiresConfirm = true
|
||||
|
||||
// When opening a file, we want to execute the file. To do this, we
|
||||
// don't override the command directly, because it won't load the
|
||||
// profile/rc files for the shell, which is super important on macOS
|
||||
// due to things like Homebrew. Instead, we set the command to
|
||||
// `<filename>; exit` which is what Terminal and iTerm2 do.
|
||||
config.initialInput = "\(filename); exit\n"
|
||||
|
||||
// For commands executed directly, we want to ensure we wait after exit
|
||||
// because in most cases scripts don't block on exit and we don't want
|
||||
// the window to just flash closed once complete.
|
||||
config.waitAfterCommand = true
|
||||
|
||||
|
||||
// Set the parent directory to our working directory so that relative
|
||||
// paths in scripts work.
|
||||
config.workingDirectory = (filename as NSString).deletingLastPathComponent
|
||||
|
||||
_ = TerminalController.newWindow(ghostty, withBaseConfig: config)
|
||||
}
|
||||
|
||||
if requiresConfirm {
|
||||
// Confirmation required. We use an app-wide NSAlert for now. In the future we
|
||||
// may want to show this as a sheet on the focused window (especially if we're
|
||||
// opening a tab). I'm not sure.
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Allow Ghostty to execute \"\(filename)\"?"
|
||||
alert.addButton(withTitle: "Allow")
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
alert.alertStyle = .warning
|
||||
switch (alert.runModal()) {
|
||||
case .alertFirstButtonReturn:
|
||||
break
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch ghostty.config.macosDockDropBehavior {
|
||||
case .new_tab: _ = TerminalController.newTab(ghostty, withBaseConfig: config)
|
||||
case .new_window: _ = TerminalController.newWindow(ghostty, withBaseConfig: config)
|
||||
}
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -880,13 +834,6 @@ class AppDelegate: NSObject,
|
||||
case .xray:
|
||||
self.appIcon = NSImage(named: "XrayImage")!
|
||||
|
||||
case .custom:
|
||||
if let userIcon = NSImage(contentsOfFile: config.macosCustomIcon) {
|
||||
self.appIcon = userIcon
|
||||
} else {
|
||||
self.appIcon = nil // Revert back to official icon if invalid location
|
||||
}
|
||||
|
||||
case .customStyle:
|
||||
guard let ghostColor = config.macosIconGhostColor else { break }
|
||||
guard let screenColors = config.macosIconScreenColor else { break }
|
||||
@@ -945,7 +892,7 @@ class AppDelegate: NSObject,
|
||||
func findSurface(forUUID uuid: UUID) -> Ghostty.SurfaceView? {
|
||||
for c in TerminalController.all {
|
||||
for view in c.surfaceTree {
|
||||
if view.id == uuid {
|
||||
if view.uuid == uuid {
|
||||
return view
|
||||
}
|
||||
}
|
||||
@@ -999,10 +946,18 @@ class AppDelegate: NSObject,
|
||||
|
||||
@IBAction func newWindow(_ sender: Any?) {
|
||||
_ = TerminalController.newWindow(ghostty)
|
||||
|
||||
// We also activate our app so that it becomes front. This may be
|
||||
// necessary for the dock menu.
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
@IBAction func newTab(_ sender: Any?) {
|
||||
_ = TerminalController.newTab(ghostty)
|
||||
|
||||
// We also activate our app so that it becomes front. This may be
|
||||
// necessary for the dock menu.
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
@IBAction func closeAllWindows(_ sender: Any?) {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24123.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24093.7" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24123.1"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24093.7"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
|
@@ -34,7 +34,7 @@ struct TerminalEntity: AppEntity {
|
||||
/// Returns the view associated with this entity. This may no longer exist.
|
||||
@MainActor
|
||||
var surfaceView: Ghostty.SurfaceView? {
|
||||
Self.defaultQuery.all.first { $0.id == self.id }
|
||||
Self.defaultQuery.all.first { $0.uuid == self.id }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@@ -46,7 +46,7 @@ struct TerminalEntity: AppEntity {
|
||||
|
||||
@MainActor
|
||||
init(_ view: Ghostty.SurfaceView) {
|
||||
self.id = view.id
|
||||
self.id = view.uuid
|
||||
self.title = view.title
|
||||
self.workingDirectory = view.pwd
|
||||
if let nsImage = ImageRenderer(content: view.screenshot()).nsImage {
|
||||
@@ -80,7 +80,7 @@ struct TerminalQuery: EntityStringQuery, EnumerableEntityQuery {
|
||||
@MainActor
|
||||
func entities(for identifiers: [TerminalEntity.ID]) async throws -> [TerminalEntity] {
|
||||
return all.filter {
|
||||
identifiers.contains($0.id)
|
||||
identifiers.contains($0.uuid)
|
||||
}.map {
|
||||
TerminalEntity($0)
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
private var previousActiveSpace: CGSSpace? = nil
|
||||
|
||||
/// The window frame saved when the quick terminal's surface tree becomes empty.
|
||||
///
|
||||
///
|
||||
/// This preserves the user's window size and position when all terminal surfaces
|
||||
/// are closed (e.g., via the `exit` command). When a new surface is created,
|
||||
/// the window will be restored to this frame, preventing SwiftUI from resetting
|
||||
@@ -34,9 +34,6 @@ class QuickTerminalController: BaseTerminalController {
|
||||
|
||||
/// The configuration derived from the Ghostty config so we don't need to rely on references.
|
||||
private var derivedConfig: DerivedConfig
|
||||
|
||||
/// Tracks if we're currently handling a manual resize to prevent recursion
|
||||
private var isHandlingResize: Bool = false
|
||||
|
||||
init(_ ghostty: Ghostty.App,
|
||||
position: QuickTerminalPosition = .top,
|
||||
@@ -79,11 +76,6 @@ class QuickTerminalController: BaseTerminalController {
|
||||
selector: #selector(onNewTab),
|
||||
name: Ghostty.Notification.ghosttyNewTab,
|
||||
object: nil)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(windowDidResize(_:)),
|
||||
name: NSWindow.didResizeNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@@ -117,29 +109,14 @@ class QuickTerminalController: BaseTerminalController {
|
||||
syncAppearance()
|
||||
|
||||
// Setup our initial size based on our configured position
|
||||
position.setLoaded(window, size: derivedConfig.quickTerminalSize)
|
||||
position.setLoaded(window)
|
||||
|
||||
// Upon first adding this Window to its host view, older SwiftUI
|
||||
// seems to have a "hiccup" and corrupts the frameRect,
|
||||
// sometimes setting the size to zero, sometimes corrupting it.
|
||||
// We pass the actual window's frame as "initial" frame directly
|
||||
// to the window, so it can use that instead of the frameworks
|
||||
// "interpretation"
|
||||
if let qtWindow = window as? QuickTerminalWindow {
|
||||
qtWindow.initialFrame = window.frame
|
||||
}
|
||||
|
||||
// Setup our content
|
||||
window.contentView = NSHostingView(rootView: TerminalView(
|
||||
ghostty: self.ghostty,
|
||||
viewModel: self,
|
||||
delegate: self
|
||||
))
|
||||
|
||||
// Clear out our frame at this point, the fixup from above is complete.
|
||||
if let qtWindow = window as? QuickTerminalWindow {
|
||||
qtWindow.initialFrame = nil
|
||||
}
|
||||
|
||||
// Animate the window in
|
||||
animateIn()
|
||||
@@ -217,28 +194,11 @@ class QuickTerminalController: BaseTerminalController {
|
||||
}
|
||||
}
|
||||
|
||||
override func windowDidResize(_ notification: Notification) {
|
||||
guard let window = notification.object as? NSWindow,
|
||||
window == self.window,
|
||||
visible,
|
||||
!isHandlingResize else { return }
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
|
||||
// Prevent recursive loops
|
||||
isHandlingResize = true
|
||||
defer { isHandlingResize = false }
|
||||
|
||||
switch position {
|
||||
case .top, .bottom, .center:
|
||||
// For centered positions (top, bottom, center), we need to recenter the window
|
||||
// when it's manually resized to maintain proper positioning
|
||||
let newOrigin = position.centeredOrigin(for: window, on: screen)
|
||||
window.setFrameOrigin(newOrigin)
|
||||
case .left, .right:
|
||||
// For side positions, we may need to adjust vertical centering
|
||||
let newOrigin = position.verticallyCenteredOrigin(for: window, on: screen)
|
||||
window.setFrameOrigin(newOrigin)
|
||||
}
|
||||
func windowWillResize(_ sender: NSWindow, to frameSize: NSSize) -> NSSize {
|
||||
// We use the actual screen the window is on for this, since it should
|
||||
// be on the proper screen.
|
||||
guard let screen = window?.screen ?? NSScreen.main else { return frameSize }
|
||||
return position.restrictFrameSize(frameSize, on: screen)
|
||||
}
|
||||
|
||||
// MARK: Base Controller Overrides
|
||||
@@ -358,17 +318,15 @@ class QuickTerminalController: BaseTerminalController {
|
||||
|
||||
private func animateWindowIn(window: NSWindow, from position: QuickTerminalPosition) {
|
||||
guard let screen = derivedConfig.quickTerminalScreen.screen else { return }
|
||||
|
||||
// Grab our last closed frame to use, and clear our state since we're animating in.
|
||||
let lastClosedFrame = self.lastClosedFrame
|
||||
self.lastClosedFrame = nil
|
||||
|
||||
// Move our window off screen to the initial animation position.
|
||||
position.setInitial(
|
||||
in: window,
|
||||
on: screen,
|
||||
terminalSize: derivedConfig.quickTerminalSize,
|
||||
closedFrame: lastClosedFrame)
|
||||
// Restore our previous frame if we have one
|
||||
if let lastClosedFrame {
|
||||
window.setFrame(lastClosedFrame, display: false)
|
||||
self.lastClosedFrame = nil
|
||||
}
|
||||
|
||||
// Move our window off screen to the top
|
||||
position.setInitial(in: window, on: screen)
|
||||
|
||||
// We need to set our window level to a high value. In testing, only
|
||||
// popUpMenu and above do what we want. This gets it above the menu bar
|
||||
@@ -399,11 +357,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
NSAnimationContext.runAnimationGroup({ context in
|
||||
context.duration = derivedConfig.quickTerminalAnimationDuration
|
||||
context.timingFunction = .init(name: .easeIn)
|
||||
position.setFinal(
|
||||
in: window.animator(),
|
||||
on: screen,
|
||||
terminalSize: derivedConfig.quickTerminalSize,
|
||||
closedFrame: lastClosedFrame)
|
||||
position.setFinal(in: window.animator(), on: screen)
|
||||
}, completionHandler: {
|
||||
// There is a very minor delay here so waiting at least an event loop tick
|
||||
// keeps us safe from the view not being on the window.
|
||||
@@ -481,19 +435,11 @@ class QuickTerminalController: BaseTerminalController {
|
||||
}
|
||||
|
||||
private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) {
|
||||
// If we are in fullscreen, then we exit fullscreen. We do this immediately so
|
||||
// we have th correct window.frame for the save state below.
|
||||
if let fullscreenStyle, fullscreenStyle.isFullscreen {
|
||||
fullscreenStyle.exit()
|
||||
}
|
||||
|
||||
// Save the current window frame before animating out. This preserves
|
||||
// the user's preferred window size and position for when the quick
|
||||
// terminal is reactivated with a new surface. Without this, SwiftUI
|
||||
// would reset the window to its minimum content size.
|
||||
if window.frame.width > 0 && window.frame.height > 0 {
|
||||
lastClosedFrame = window.frame
|
||||
}
|
||||
lastClosedFrame = window.frame
|
||||
|
||||
// If we hid the dock then we unhide it.
|
||||
hiddenDock = nil
|
||||
@@ -509,6 +455,11 @@ class QuickTerminalController: BaseTerminalController {
|
||||
// We always animate out to whatever screen the window is actually on.
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
|
||||
// If we are in fullscreen, then we exit fullscreen.
|
||||
if let fullscreenStyle, fullscreenStyle.isFullscreen {
|
||||
fullscreenStyle.exit()
|
||||
}
|
||||
|
||||
// If we have a previously active application, restore focus to it. We
|
||||
// do this BEFORE the animation below because when the animation completes
|
||||
// macOS will bring forward another window.
|
||||
@@ -530,11 +481,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
NSAnimationContext.runAnimationGroup({ context in
|
||||
context.duration = derivedConfig.quickTerminalAnimationDuration
|
||||
context.timingFunction = .init(name: .easeIn)
|
||||
position.setInitial(
|
||||
in: window.animator(),
|
||||
on: screen,
|
||||
terminalSize: derivedConfig.quickTerminalSize,
|
||||
closedFrame: window.frame)
|
||||
position.setInitial(in: window.animator(), on: screen)
|
||||
}, completionHandler: {
|
||||
// This causes the window to be removed from the screen list and macOS
|
||||
// handles what should be focused next.
|
||||
@@ -665,7 +612,6 @@ class QuickTerminalController: BaseTerminalController {
|
||||
let quickTerminalAnimationDuration: Double
|
||||
let quickTerminalAutoHide: Bool
|
||||
let quickTerminalSpaceBehavior: QuickTerminalSpaceBehavior
|
||||
let quickTerminalSize: QuickTerminalSize
|
||||
let backgroundOpacity: Double
|
||||
|
||||
init() {
|
||||
@@ -673,7 +619,6 @@ class QuickTerminalController: BaseTerminalController {
|
||||
self.quickTerminalAnimationDuration = 0.2
|
||||
self.quickTerminalAutoHide = true
|
||||
self.quickTerminalSpaceBehavior = .move
|
||||
self.quickTerminalSize = QuickTerminalSize()
|
||||
self.backgroundOpacity = 1.0
|
||||
}
|
||||
|
||||
@@ -682,7 +627,6 @@ class QuickTerminalController: BaseTerminalController {
|
||||
self.quickTerminalAnimationDuration = config.quickTerminalAnimationDuration
|
||||
self.quickTerminalAutoHide = config.quickTerminalAutoHide
|
||||
self.quickTerminalSpaceBehavior = config.quickTerminalSpaceBehavior
|
||||
self.quickTerminalSize = config.quickTerminalSize
|
||||
self.backgroundOpacity = config.backgroundOpacity
|
||||
}
|
||||
}
|
||||
|
@@ -7,86 +7,95 @@ enum QuickTerminalPosition : String {
|
||||
case right
|
||||
case center
|
||||
|
||||
/// Set the loaded state for a window. This should only be called when the window is first loaded,
|
||||
/// usually in `windowDidLoad` or in a similar callback. This is the initial state.
|
||||
func setLoaded(_ window: NSWindow, size: QuickTerminalSize) {
|
||||
/// Set the loaded state for a window.
|
||||
func setLoaded(_ window: NSWindow) {
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: size.calculate(position: self, screenDimensions: screen.visibleFrame.size)
|
||||
), display: false)
|
||||
switch (self) {
|
||||
case .top, .bottom:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width,
|
||||
height: screen.frame.height / 4)
|
||||
), display: false)
|
||||
|
||||
case .left, .right:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width / 4,
|
||||
height: screen.frame.height)
|
||||
), display: false)
|
||||
|
||||
case .center:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width / 2,
|
||||
height: screen.frame.height / 3)
|
||||
), display: false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the initial state for a window NOT yet into position (either before animating in or
|
||||
/// after animating out).
|
||||
func setInitial(
|
||||
in window: NSWindow,
|
||||
on screen: NSScreen,
|
||||
terminalSize: QuickTerminalSize,
|
||||
closedFrame: NSRect? = nil
|
||||
) {
|
||||
// Invisible
|
||||
/// Set the initial state for a window for animating out of this position.
|
||||
func setInitial(in window: NSWindow, on screen: NSScreen) {
|
||||
// We always start invisible
|
||||
window.alphaValue = 0
|
||||
|
||||
// Position depends
|
||||
window.setFrame(.init(
|
||||
origin: initialOrigin(for: window, on: screen),
|
||||
size: closedFrame?.size ?? configuredFrameSize(
|
||||
on: screen,
|
||||
terminalSize: terminalSize)
|
||||
size: restrictFrameSize(window.frame.size, on: screen)
|
||||
), display: false)
|
||||
}
|
||||
|
||||
/// Set the final state for a window in this position.
|
||||
func setFinal(
|
||||
in window: NSWindow,
|
||||
on screen: NSScreen,
|
||||
terminalSize: QuickTerminalSize,
|
||||
closedFrame: NSRect? = nil
|
||||
) {
|
||||
func setFinal(in window: NSWindow, on screen: NSScreen) {
|
||||
// We always end visible
|
||||
window.alphaValue = 1
|
||||
|
||||
// Position depends
|
||||
window.setFrame(.init(
|
||||
origin: finalOrigin(for: window, on: screen),
|
||||
size: closedFrame?.size ?? configuredFrameSize(
|
||||
on: screen,
|
||||
terminalSize: terminalSize)
|
||||
size: restrictFrameSize(window.frame.size, on: screen)
|
||||
), display: true)
|
||||
}
|
||||
|
||||
/// Get the configured frame size for initial positioning and animations.
|
||||
func configuredFrameSize(on screen: NSScreen, terminalSize: QuickTerminalSize) -> NSSize {
|
||||
let dimensions = terminalSize.calculate(position: self, screenDimensions: screen.visibleFrame.size)
|
||||
return NSSize(width: dimensions.width, height: dimensions.height)
|
||||
/// Restrict the frame size during resizing.
|
||||
func restrictFrameSize(_ size: NSSize, on screen: NSScreen) -> NSSize {
|
||||
var finalSize = size
|
||||
switch (self) {
|
||||
case .top, .bottom:
|
||||
finalSize.width = screen.frame.width
|
||||
|
||||
case .left, .right:
|
||||
finalSize.height = screen.visibleFrame.height
|
||||
|
||||
case .center:
|
||||
finalSize.width = screen.frame.width / 2
|
||||
finalSize.height = screen.frame.height / 3
|
||||
}
|
||||
|
||||
return finalSize
|
||||
}
|
||||
|
||||
/// The initial point origin for this position.
|
||||
func initialOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch (self) {
|
||||
case .top:
|
||||
return .init(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: screen.visibleFrame.maxY)
|
||||
return .init(x: screen.frame.minX, y: screen.frame.maxY)
|
||||
|
||||
case .bottom:
|
||||
return .init(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: -window.frame.height)
|
||||
return .init(x: screen.frame.minX, y: -window.frame.height)
|
||||
|
||||
case .left:
|
||||
return .init(
|
||||
x: screen.visibleFrame.minX-window.frame.width,
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2))
|
||||
return .init(x: screen.frame.minX-window.frame.width, y: 0)
|
||||
|
||||
case .right:
|
||||
return .init(
|
||||
x: screen.visibleFrame.maxX,
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2))
|
||||
return .init(x: screen.frame.maxX, y: 0)
|
||||
|
||||
case .center:
|
||||
return .init(x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2), y: screen.visibleFrame.height - window.frame.width)
|
||||
return .init(x: screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2, y: screen.visibleFrame.height - window.frame.width)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,27 +103,19 @@ enum QuickTerminalPosition : String {
|
||||
func finalOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch (self) {
|
||||
case .top:
|
||||
return .init(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: screen.visibleFrame.maxY - window.frame.height)
|
||||
return .init(x: screen.frame.minX, y: screen.visibleFrame.maxY - window.frame.height)
|
||||
|
||||
case .bottom:
|
||||
return .init(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: screen.visibleFrame.minY)
|
||||
return .init(x: screen.frame.minX, y: screen.frame.minY)
|
||||
|
||||
case .left:
|
||||
return .init(
|
||||
x: screen.visibleFrame.minX,
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2))
|
||||
return .init(x: screen.frame.minX, y: window.frame.origin.y)
|
||||
|
||||
case .right:
|
||||
return .init(
|
||||
x: screen.visibleFrame.maxX - window.frame.width,
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2))
|
||||
return .init(x: screen.visibleFrame.maxX - window.frame.width, y: window.frame.origin.y)
|
||||
|
||||
case .center:
|
||||
return .init(x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2), y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2))
|
||||
return .init(x: screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2, y: screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,52 +136,4 @@ enum QuickTerminalPosition : String {
|
||||
case .right: self == .top || self == .bottom
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the centered origin for a window, keeping it properly positioned after manual resizing
|
||||
func centeredOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch self {
|
||||
case .top:
|
||||
return CGPoint(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: window.frame.origin.y // Keep the same Y position
|
||||
)
|
||||
|
||||
case .bottom:
|
||||
return CGPoint(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: window.frame.origin.y // Keep the same Y position
|
||||
)
|
||||
|
||||
case .center:
|
||||
return CGPoint(
|
||||
x: round(screen.visibleFrame.origin.x + (screen.visibleFrame.width - window.frame.width) / 2),
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
|
||||
)
|
||||
|
||||
case .left, .right:
|
||||
// For left/right positions, only adjust horizontal centering if needed
|
||||
return window.frame.origin
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the vertically centered origin for side-positioned windows
|
||||
func verticallyCenteredOrigin(for window: NSWindow, on screen: NSScreen) -> CGPoint {
|
||||
switch self {
|
||||
case .left:
|
||||
return CGPoint(
|
||||
x: window.frame.origin.x, // Keep the same X position
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
|
||||
)
|
||||
|
||||
case .right:
|
||||
return CGPoint(
|
||||
x: window.frame.origin.x, // Keep the same X position
|
||||
y: round(screen.visibleFrame.origin.y + (screen.visibleFrame.height - window.frame.height) / 2)
|
||||
)
|
||||
|
||||
case .top, .bottom, .center:
|
||||
// These positions don't need vertical recentering during resize
|
||||
return window.frame.origin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,84 +0,0 @@
|
||||
import GhosttyKit
|
||||
|
||||
/// Represents the Ghostty `quick-terminal-size` configuration. See the documentation for
|
||||
/// that for more details on exactly how it works. Some of those docs will be reproduced in various comments
|
||||
/// in this file but that is the best source of truth for it.
|
||||
///
|
||||
/// The size determines the size of the quick terminal along the primary and secondary axis. The primary and
|
||||
/// secondary axis is defined by the `quick-terminal-position`.
|
||||
struct QuickTerminalSize {
|
||||
let primary: Size?
|
||||
let secondary: Size?
|
||||
|
||||
init(primary: Size? = nil, secondary: Size? = nil) {
|
||||
self.primary = primary
|
||||
self.secondary = secondary
|
||||
}
|
||||
|
||||
init(from cStruct: ghostty_config_quick_terminal_size_s) {
|
||||
self.primary = Size(from: cStruct.primary)
|
||||
self.secondary = Size(from: cStruct.secondary)
|
||||
}
|
||||
|
||||
enum Size {
|
||||
case percentage(Float)
|
||||
case pixels(UInt32)
|
||||
|
||||
init?(from cStruct: ghostty_quick_terminal_size_s) {
|
||||
switch cStruct.tag {
|
||||
case GHOSTTY_QUICK_TERMINAL_SIZE_NONE:
|
||||
return nil
|
||||
case GHOSTTY_QUICK_TERMINAL_SIZE_PERCENTAGE:
|
||||
self = .percentage(cStruct.value.percentage)
|
||||
case GHOSTTY_QUICK_TERMINAL_SIZE_PIXELS:
|
||||
self = .pixels(cStruct.value.pixels)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func toPixels(parentDimension: CGFloat) -> CGFloat {
|
||||
switch self {
|
||||
case .percentage(let value):
|
||||
return parentDimension * CGFloat(value) / 100.0
|
||||
case .pixels(let value):
|
||||
return CGFloat(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// This is an almost direct port of th Zig function QuickTerminalSize.calculate
|
||||
func calculate(position: QuickTerminalPosition, screenDimensions: CGSize) -> CGSize {
|
||||
let dims = CGSize(width: screenDimensions.width, height: screenDimensions.height)
|
||||
|
||||
switch position {
|
||||
case .left, .right:
|
||||
return CGSize(
|
||||
width: primary?.toPixels(parentDimension: dims.width) ?? 400,
|
||||
height: secondary?.toPixels(parentDimension: dims.height) ?? dims.height
|
||||
)
|
||||
|
||||
case .top, .bottom:
|
||||
return CGSize(
|
||||
width: secondary?.toPixels(parentDimension: dims.width) ?? dims.width,
|
||||
height: primary?.toPixels(parentDimension: dims.height) ?? 400
|
||||
)
|
||||
|
||||
case .center:
|
||||
if dims.width >= dims.height {
|
||||
// Landscape
|
||||
return CGSize(
|
||||
width: primary?.toPixels(parentDimension: dims.width) ?? 800,
|
||||
height: secondary?.toPixels(parentDimension: dims.height) ?? 400
|
||||
)
|
||||
} else {
|
||||
// Portrait
|
||||
return CGSize(
|
||||
width: secondary?.toPixels(parentDimension: dims.width) ?? 400,
|
||||
height: primary?.toPixels(parentDimension: dims.height) ?? 800
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -29,19 +29,4 @@ class QuickTerminalWindow: NSPanel {
|
||||
// We don't want to activate the owning app when quick terminal is triggered.
|
||||
self.styleMask.insert(.nonactivatingPanel)
|
||||
}
|
||||
|
||||
/// This is set to the frame prior to setting `contentView`. This is purely a hack to workaround
|
||||
/// bugs in older macOS versions (Ventura): https://github.com/ghostty-org/ghostty/pull/8026
|
||||
var initialFrame: NSRect? = nil
|
||||
|
||||
override func setFrame(_ frameRect: NSRect, display flag: Bool) {
|
||||
// Upon first adding this Window to its host view, older SwiftUI
|
||||
// seems to have a "hiccup" and corrupts the frameRect,
|
||||
// sometimes setting the size to zero, sometimes corrupting it.
|
||||
// If we find we have cached the "initial" frame, use that instead
|
||||
// the propagated one through the framework
|
||||
//
|
||||
// https://github.com/ghostty-org/ghostty/pull/8026
|
||||
super.setFrame(initialFrame ?? frameRect, display: flag)
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import AppKit
|
||||
|
||||
/// SplitTree represents a tree of views that can be divided.
|
||||
struct SplitTree<ViewType: NSView & Codable & Identifiable> {
|
||||
struct SplitTree<ViewType: NSView & Codable>: Codable {
|
||||
/// The root of the tree. This can be nil to indicate the tree is empty.
|
||||
let root: Node?
|
||||
|
||||
@@ -29,12 +29,12 @@ struct SplitTree<ViewType: NSView & Codable & Identifiable> {
|
||||
}
|
||||
|
||||
/// The path to a specific node in the tree.
|
||||
struct Path: Codable {
|
||||
struct Path {
|
||||
let path: [Component]
|
||||
|
||||
var isEmpty: Bool { path.isEmpty }
|
||||
|
||||
enum Component: Codable {
|
||||
enum Component {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
@@ -53,7 +53,7 @@ struct SplitTree<ViewType: NSView & Codable & Identifiable> {
|
||||
let node: Node
|
||||
let bounds: CGRect
|
||||
}
|
||||
|
||||
|
||||
/// Direction for spatial navigation within the split tree.
|
||||
enum Direction {
|
||||
case left
|
||||
@@ -127,51 +127,44 @@ extension SplitTree {
|
||||
root: try root.insert(view: view, at: at, direction: direction),
|
||||
zoomed: nil)
|
||||
}
|
||||
/// Find a node containing a view with the specified ID.
|
||||
/// - Parameter id: The ID of the view to find
|
||||
/// - Returns: The node containing the view if found, nil otherwise
|
||||
func find(id: ViewType.ID) -> Node? {
|
||||
guard let root else { return nil }
|
||||
return root.find(id: id)
|
||||
}
|
||||
|
||||
/// Remove a node from the tree. If the node being removed is part of a split,
|
||||
/// the sibling node takes the place of the parent split.
|
||||
func remove(_ target: Node) -> Self {
|
||||
guard let root else { return self }
|
||||
|
||||
|
||||
// If we're removing the root itself, return an empty tree
|
||||
if root == target {
|
||||
return .init(root: nil, zoomed: nil)
|
||||
}
|
||||
|
||||
|
||||
// Otherwise, try to remove from the tree
|
||||
let newRoot = root.remove(target)
|
||||
|
||||
|
||||
// Update zoomed if it was the removed node
|
||||
let newZoomed = (zoomed == target) ? nil : zoomed
|
||||
|
||||
|
||||
return .init(root: newRoot, zoomed: newZoomed)
|
||||
}
|
||||
|
||||
/// Replace a node in the tree with a new node.
|
||||
func replace(node: Node, with newNode: Node) throws -> Self {
|
||||
guard let root else { throw SplitError.viewNotFound }
|
||||
|
||||
|
||||
// Get the path to the node we want to replace
|
||||
guard let path = root.path(to: node) else {
|
||||
throw SplitError.viewNotFound
|
||||
}
|
||||
|
||||
|
||||
// Replace the node
|
||||
let newRoot = try root.replaceNode(at: path, with: newNode)
|
||||
|
||||
|
||||
// Update zoomed if it was the replaced node
|
||||
let newZoomed = (zoomed == node) ? newNode : zoomed
|
||||
|
||||
|
||||
return .init(root: newRoot, zoomed: newZoomed)
|
||||
}
|
||||
|
||||
|
||||
/// Find the next view to focus based on the current focused node and direction
|
||||
func focusTarget(for direction: FocusDirection, from currentNode: Node) -> ViewType? {
|
||||
guard let root else { return nil }
|
||||
@@ -237,13 +230,13 @@ extension SplitTree {
|
||||
let newRoot = root.equalize()
|
||||
return .init(root: newRoot, zoomed: zoomed)
|
||||
}
|
||||
|
||||
|
||||
/// Resize a node in the tree by the given pixel amount in the specified direction.
|
||||
///
|
||||
///
|
||||
/// This method adjusts the split ratios of the tree to accommodate the requested resize
|
||||
/// operation. For up/down resizing, it finds the nearest parent vertical split and adjusts
|
||||
/// its ratio. For left/right resizing, it finds the nearest parent horizontal split.
|
||||
/// The bounds parameter is used to construct the spatial tree representation which is
|
||||
/// The bounds parameter is used to construct the spatial tree representation which is
|
||||
/// needed to calculate the current pixel dimensions.
|
||||
///
|
||||
/// This will always reset the zoomed state.
|
||||
@@ -257,22 +250,22 @@ extension SplitTree {
|
||||
/// - Throws: SplitError.viewNotFound if the node is not found in the tree or no suitable parent split exists
|
||||
func resize(node: Node, by pixels: UInt16, in direction: Spatial.Direction, with bounds: CGRect) throws -> Self {
|
||||
guard let root else { throw SplitError.viewNotFound }
|
||||
|
||||
|
||||
// Find the path to the target node
|
||||
guard let path = root.path(to: node) else {
|
||||
throw SplitError.viewNotFound
|
||||
}
|
||||
|
||||
|
||||
// Determine which type of split we need to find based on resize direction
|
||||
let targetSplitDirection: Direction = switch direction {
|
||||
case .up, .down: .vertical
|
||||
case .left, .right: .horizontal
|
||||
}
|
||||
|
||||
|
||||
// Find the nearest parent split of the correct type by walking up the path
|
||||
var splitPath: Path?
|
||||
var splitNode: Node?
|
||||
|
||||
|
||||
for i in stride(from: path.path.count - 1, through: 0, by: -1) {
|
||||
let parentPath = Path(path: Array(path.path.prefix(i)))
|
||||
if let parent = root.node(at: parentPath), case .split(let split) = parent {
|
||||
@@ -283,29 +276,29 @@ extension SplitTree {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard let splitPath = splitPath,
|
||||
|
||||
guard let splitPath = splitPath,
|
||||
let splitNode = splitNode,
|
||||
case .split(let split) = splitNode else {
|
||||
throw SplitError.viewNotFound
|
||||
}
|
||||
|
||||
|
||||
// Get current spatial representation to calculate pixel dimensions
|
||||
let spatial = root.spatial(within: bounds.size)
|
||||
guard let splitSlot = spatial.slots.first(where: { $0.node == splitNode }) else {
|
||||
throw SplitError.viewNotFound
|
||||
}
|
||||
|
||||
|
||||
// Calculate the new ratio based on pixel change
|
||||
let pixelOffset = Double(pixels)
|
||||
let newRatio: Double
|
||||
|
||||
|
||||
switch (split.direction, direction) {
|
||||
case (.horizontal, .left):
|
||||
// Moving left boundary: decrease left side
|
||||
newRatio = Swift.max(0.1, Swift.min(0.9, split.ratio - (pixelOffset / splitSlot.bounds.width)))
|
||||
case (.horizontal, .right):
|
||||
// Moving right boundary: increase left side
|
||||
// Moving right boundary: increase left side
|
||||
newRatio = Swift.max(0.1, Swift.min(0.9, split.ratio + (pixelOffset / splitSlot.bounds.width)))
|
||||
case (.vertical, .up):
|
||||
// Moving top boundary: decrease top side
|
||||
@@ -317,7 +310,7 @@ extension SplitTree {
|
||||
// Direction doesn't match split type - shouldn't happen due to earlier logic
|
||||
throw SplitError.viewNotFound
|
||||
}
|
||||
|
||||
|
||||
// Create new split with adjusted ratio
|
||||
let newSplit = Node.Split(
|
||||
direction: split.direction,
|
||||
@@ -325,12 +318,12 @@ extension SplitTree {
|
||||
left: split.left,
|
||||
right: split.right
|
||||
)
|
||||
|
||||
|
||||
// Replace the split node with the new one
|
||||
let newRoot = try root.replaceNode(at: splitPath, with: .split(newSplit))
|
||||
return .init(root: newRoot, zoomed: nil)
|
||||
}
|
||||
|
||||
|
||||
/// Returns the total bounds of the split hierarchy using NSView bounds.
|
||||
/// Ignores x/y coordinates and assumes views are laid out in a perfect grid.
|
||||
/// Also ignores any possible padding between views.
|
||||
@@ -341,60 +334,6 @@ extension SplitTree {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SplitTree Codable
|
||||
|
||||
fileprivate enum CodingKeys: String, CodingKey {
|
||||
case version
|
||||
case root
|
||||
case zoomed
|
||||
|
||||
static let currentVersion: Int = 1
|
||||
}
|
||||
|
||||
extension SplitTree: Codable {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
// Check version
|
||||
let version = try container.decode(Int.self, forKey: .version)
|
||||
guard version == CodingKeys.currentVersion else {
|
||||
throw DecodingError.dataCorrupted(
|
||||
DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "Unsupported SplitTree version: \(version)"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Decode root
|
||||
self.root = try container.decodeIfPresent(Node.self, forKey: .root)
|
||||
|
||||
// Zoomed is encoded as its path. Get the path and then find it.
|
||||
if let zoomedPath = try container.decodeIfPresent(Path.self, forKey: .zoomed),
|
||||
let root = self.root {
|
||||
self.zoomed = root.node(at: zoomedPath)
|
||||
} else {
|
||||
self.zoomed = nil
|
||||
}
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
// Encode version
|
||||
try container.encode(CodingKeys.currentVersion, forKey: .version)
|
||||
|
||||
// Encode root
|
||||
try container.encodeIfPresent(root, forKey: .root)
|
||||
|
||||
// Zoomed is encoded as its path since its a reference type. This lets us
|
||||
// map it on decode back to the correct node in root.
|
||||
if let zoomed, let path = root?.path(to: zoomed) {
|
||||
try container.encode(path, forKey: .zoomed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SplitTree.Node
|
||||
|
||||
extension SplitTree.Node {
|
||||
@@ -403,23 +342,6 @@ extension SplitTree.Node {
|
||||
typealias SplitError = SplitTree.SplitError
|
||||
typealias Path = SplitTree.Path
|
||||
|
||||
/// Find a node containing a view with the specified ID.
|
||||
/// - Parameter id: The ID of the view to find
|
||||
/// - Returns: The node containing the view if found, nil otherwise
|
||||
func find(id: ViewType.ID) -> Node? {
|
||||
switch self {
|
||||
case .leaf(let view):
|
||||
return view.id == id ? self : nil
|
||||
|
||||
case .split(let split):
|
||||
if let found = split.left.find(id: id) {
|
||||
return found
|
||||
}
|
||||
|
||||
return split.right.find(id: id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the node in the tree that contains the given view.
|
||||
func node(view: ViewType) -> Node? {
|
||||
switch (self) {
|
||||
@@ -474,20 +396,20 @@ extension SplitTree.Node {
|
||||
|
||||
return search(self) ? Path(path: components) : nil
|
||||
}
|
||||
|
||||
|
||||
/// Returns the node at the given path from this node as root.
|
||||
func node(at path: Path) -> Node? {
|
||||
if path.isEmpty {
|
||||
return self
|
||||
}
|
||||
|
||||
|
||||
guard case .split(let split) = self else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let component = path.path[0]
|
||||
let remainingPath = Path(path: Array(path.path.dropFirst()))
|
||||
|
||||
|
||||
switch component {
|
||||
case .left:
|
||||
return split.left.node(at: remainingPath)
|
||||
@@ -599,12 +521,12 @@ extension SplitTree.Node {
|
||||
if self == target {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
switch self {
|
||||
case .leaf:
|
||||
// A leaf that isn't the target stays as is
|
||||
return self
|
||||
|
||||
|
||||
case .split(let split):
|
||||
// Neither child is directly the target, so we need to recursively
|
||||
// try to remove from both children
|
||||
@@ -621,7 +543,7 @@ extension SplitTree.Node {
|
||||
} else if newRight == nil {
|
||||
return newLeft
|
||||
}
|
||||
|
||||
|
||||
// Both children still exist after removal
|
||||
return .split(.init(
|
||||
direction: split.direction,
|
||||
@@ -640,7 +562,7 @@ extension SplitTree.Node {
|
||||
case .leaf:
|
||||
// Leaf nodes don't have a ratio to resize
|
||||
return self
|
||||
|
||||
|
||||
case .split(let split):
|
||||
// Create a new split with the updated ratio
|
||||
return .split(.init(
|
||||
@@ -651,7 +573,7 @@ extension SplitTree.Node {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Get the leftmost leaf in this subtree
|
||||
func leftmostLeaf() -> ViewType {
|
||||
switch self {
|
||||
@@ -661,7 +583,7 @@ extension SplitTree.Node {
|
||||
return split.left.leftmostLeaf()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Get the rightmost leaf in this subtree
|
||||
func rightmostLeaf() -> ViewType {
|
||||
switch self {
|
||||
@@ -671,7 +593,7 @@ extension SplitTree.Node {
|
||||
return split.right.rightmostLeaf()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Equalize this node and all its children, returning a new node with splits
|
||||
/// adjusted so that each split's ratio is based on the relative weight
|
||||
/// (number of leaves) of its children.
|
||||
@@ -679,14 +601,14 @@ extension SplitTree.Node {
|
||||
let (equalizedNode, _) = equalizeWithWeight()
|
||||
return equalizedNode
|
||||
}
|
||||
|
||||
|
||||
/// Internal helper that equalizes and returns both the node and its weight.
|
||||
private func equalizeWithWeight() -> (node: Node, weight: Int) {
|
||||
switch self {
|
||||
case .leaf:
|
||||
// A leaf has weight 1 and doesn't change
|
||||
return (self, 1)
|
||||
|
||||
|
||||
case .split(let split):
|
||||
// Calculate weights based on split direction
|
||||
let leftWeight = split.left.weightForDirection(split.direction)
|
||||
@@ -707,7 +629,7 @@ extension SplitTree.Node {
|
||||
left: leftNode,
|
||||
right: rightNode
|
||||
)
|
||||
|
||||
|
||||
return (.split(newSplit), totalWeight)
|
||||
}
|
||||
}
|
||||
@@ -734,12 +656,12 @@ extension SplitTree.Node {
|
||||
switch self {
|
||||
case .leaf(let view):
|
||||
return [(view, bounds)]
|
||||
|
||||
|
||||
case .split(let split):
|
||||
// Calculate bounds for left and right based on split direction and ratio
|
||||
let leftBounds: CGRect
|
||||
let rightBounds: CGRect
|
||||
|
||||
|
||||
switch split.direction {
|
||||
case .horizontal:
|
||||
// Split horizontally: left | right
|
||||
@@ -756,7 +678,7 @@ extension SplitTree.Node {
|
||||
width: bounds.width * (1 - split.ratio),
|
||||
height: bounds.height
|
||||
)
|
||||
|
||||
|
||||
case .vertical:
|
||||
// Split vertically: top / bottom
|
||||
// Note: In our normalized coordinate system, Y increases upward
|
||||
@@ -774,13 +696,13 @@ extension SplitTree.Node {
|
||||
height: bounds.height * split.ratio
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Recursively calculate bounds for children
|
||||
return split.left.calculateViewBounds(in: leftBounds) +
|
||||
split.right.calculateViewBounds(in: rightBounds)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Returns the total bounds of this subtree using NSView bounds.
|
||||
/// Ignores x/y coordinates and assumes views are laid out in a perfect grid.
|
||||
/// - Returns: The total width and height needed to contain all views in this subtree
|
||||
@@ -788,11 +710,11 @@ extension SplitTree.Node {
|
||||
switch self {
|
||||
case .leaf(let view):
|
||||
return view.bounds.size
|
||||
|
||||
|
||||
case .split(let split):
|
||||
let leftBounds = split.left.viewBounds()
|
||||
let rightBounds = split.right.viewBounds()
|
||||
|
||||
|
||||
switch split.direction {
|
||||
case .horizontal:
|
||||
// Horizontal split: width is sum, height is max
|
||||
@@ -800,7 +722,7 @@ extension SplitTree.Node {
|
||||
width: leftBounds.width + rightBounds.width,
|
||||
height: Swift.max(leftBounds.height, rightBounds.height)
|
||||
)
|
||||
|
||||
|
||||
case .vertical:
|
||||
// Vertical split: height is sum, width is max
|
||||
return CGSize(
|
||||
@@ -838,7 +760,7 @@ extension SplitTree.Node {
|
||||
/// // +--------+----+
|
||||
/// // | C | D |
|
||||
/// // +--------+----+
|
||||
/// //
|
||||
/// //
|
||||
/// // The spatial representation would have:
|
||||
/// // - Total dimensions: (width: 2, height: 2)
|
||||
/// // - Node bounds based on actual split ratios
|
||||
@@ -883,7 +805,7 @@ extension SplitTree.Node {
|
||||
/// Example:
|
||||
/// ```
|
||||
/// // Single leaf: (1, 1)
|
||||
/// // Horizontal split with 2 leaves: (2, 1)
|
||||
/// // Horizontal split with 2 leaves: (2, 1)
|
||||
/// // Vertical split with 2 leaves: (1, 2)
|
||||
/// // Complex layout with both: (2, 2) or larger
|
||||
/// ```
|
||||
@@ -924,7 +846,7 @@ extension SplitTree.Node {
|
||||
///
|
||||
/// The calculation process:
|
||||
/// 1. **Leaf nodes**: Create a single slot with the provided bounds
|
||||
/// 2. **Split nodes**:
|
||||
/// 2. **Split nodes**:
|
||||
/// - Divide the bounds according to the split ratio and direction
|
||||
/// - Create a slot for the split node itself
|
||||
/// - Recursively calculate slots for both children
|
||||
@@ -1004,7 +926,7 @@ extension SplitTree.Spatial {
|
||||
///
|
||||
/// This method finds all slots positioned in the given direction from the reference node:
|
||||
/// - **Left**: Slots with bounds to the left of the reference node
|
||||
/// - **Right**: Slots with bounds to the right of the reference node
|
||||
/// - **Right**: Slots with bounds to the right of the reference node
|
||||
/// - **Up**: Slots with bounds above the reference node (Y=0 is top)
|
||||
/// - **Down**: Slots with bounds below the reference node
|
||||
///
|
||||
@@ -1033,41 +955,41 @@ extension SplitTree.Spatial {
|
||||
let dy = rect2.minY - rect1.minY
|
||||
return sqrt(dx * dx + dy * dy)
|
||||
}
|
||||
|
||||
|
||||
let result = switch direction {
|
||||
case .left:
|
||||
// Slots to the left: their right edge is at or left of reference's left edge
|
||||
slots.filter {
|
||||
$0.node != referenceNode && $0.bounds.maxX <= refSlot.bounds.minX
|
||||
$0.node != referenceNode && $0.bounds.maxX <= refSlot.bounds.minX
|
||||
}.sorted {
|
||||
distance(from: refSlot.bounds, to: $0.bounds) < distance(from: refSlot.bounds, to: $1.bounds)
|
||||
}
|
||||
|
||||
|
||||
case .right:
|
||||
// Slots to the right: their left edge is at or right of reference's right edge
|
||||
slots.filter {
|
||||
$0.node != referenceNode && $0.bounds.minX >= refSlot.bounds.maxX
|
||||
$0.node != referenceNode && $0.bounds.minX >= refSlot.bounds.maxX
|
||||
}.sorted {
|
||||
distance(from: refSlot.bounds, to: $0.bounds) < distance(from: refSlot.bounds, to: $1.bounds)
|
||||
}
|
||||
|
||||
|
||||
case .up:
|
||||
// Slots above: their bottom edge is at or above reference's top edge
|
||||
slots.filter {
|
||||
$0.node != referenceNode && $0.bounds.maxY <= refSlot.bounds.minY
|
||||
$0.node != referenceNode && $0.bounds.maxY <= refSlot.bounds.minY
|
||||
}.sorted {
|
||||
distance(from: refSlot.bounds, to: $0.bounds) < distance(from: refSlot.bounds, to: $1.bounds)
|
||||
}
|
||||
|
||||
|
||||
case .down:
|
||||
// Slots below: their top edge is at or below reference's bottom edge
|
||||
slots.filter {
|
||||
$0.node != referenceNode && $0.bounds.minY >= refSlot.bounds.maxY
|
||||
$0.node != referenceNode && $0.bounds.minY >= refSlot.bounds.maxY
|
||||
}.sorted {
|
||||
distance(from: refSlot.bounds, to: $0.bounds) < distance(from: refSlot.bounds, to: $1.bounds)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1086,14 +1008,14 @@ extension SplitTree.Spatial {
|
||||
func doesBorder(side: Direction, from node: SplitTree.Node) -> Bool {
|
||||
// Find the slot for this node
|
||||
guard let slot = slots.first(where: { $0.node == node }) else { return false }
|
||||
|
||||
|
||||
// Calculate the overall bounds of all slots
|
||||
let overallBounds = slots.reduce(CGRect.null) { result, slot in
|
||||
result.union(slot.bounds)
|
||||
}
|
||||
|
||||
|
||||
return switch side {
|
||||
case .up:
|
||||
case .up:
|
||||
slot.bounds.minY == overallBounds.minY
|
||||
case .down:
|
||||
slot.bounds.maxY == overallBounds.maxY
|
||||
@@ -1130,10 +1052,10 @@ extension SplitTree.Node {
|
||||
case view
|
||||
case split
|
||||
}
|
||||
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
|
||||
if container.contains(.view) {
|
||||
let view = try container.decode(ViewType.self, forKey: .view)
|
||||
self = .leaf(view: view)
|
||||
@@ -1149,14 +1071,14 @@ extension SplitTree.Node {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
|
||||
switch self {
|
||||
case .leaf(let view):
|
||||
try container.encode(view, forKey: .view)
|
||||
|
||||
|
||||
case .split(let split):
|
||||
try container.encode(split, forKey: .split)
|
||||
}
|
||||
@@ -1171,7 +1093,7 @@ extension SplitTree.Node {
|
||||
switch self {
|
||||
case .leaf(let view):
|
||||
return [view]
|
||||
|
||||
|
||||
case .split(let split):
|
||||
return split.left.leaves() + split.right.leaves()
|
||||
}
|
||||
@@ -1223,7 +1145,7 @@ extension SplitTree.Node {
|
||||
var structuralIdentity: StructuralIdentity {
|
||||
StructuralIdentity(self)
|
||||
}
|
||||
|
||||
|
||||
/// A hashable representation of a node that captures its structural identity.
|
||||
///
|
||||
/// This type provides a way to track changes to a node's structure in SwiftUI
|
||||
@@ -1237,20 +1159,20 @@ extension SplitTree.Node {
|
||||
/// for unchanged portions of the tree.
|
||||
struct StructuralIdentity: Hashable {
|
||||
private let node: SplitTree.Node
|
||||
|
||||
|
||||
init(_ node: SplitTree.Node) {
|
||||
self.node = node
|
||||
}
|
||||
|
||||
|
||||
static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
lhs.node.isStructurallyEqual(to: rhs.node)
|
||||
}
|
||||
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
node.hashStructure(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Checks if this node is structurally equal to another node.
|
||||
/// Two nodes are structurally equal if they have the same tree structure
|
||||
/// and the same views (by identity) in the same positions.
|
||||
@@ -1259,26 +1181,26 @@ extension SplitTree.Node {
|
||||
case let (.leaf(view1), .leaf(view2)):
|
||||
// Views must be the same instance
|
||||
return view1 === view2
|
||||
|
||||
|
||||
case let (.split(split1), .split(split2)):
|
||||
// Splits must have same direction and structurally equal children
|
||||
// Note: We intentionally don't compare ratios as they may change slightly
|
||||
return split1.direction == split2.direction &&
|
||||
split1.left.isStructurallyEqual(to: split2.left) &&
|
||||
split1.right.isStructurallyEqual(to: split2.right)
|
||||
|
||||
|
||||
default:
|
||||
// Different node types
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Hash keys for structural identity
|
||||
private enum HashKey: UInt8 {
|
||||
case leaf = 0
|
||||
case split = 1
|
||||
}
|
||||
|
||||
|
||||
/// Hashes the structural identity of this node.
|
||||
/// Includes the tree structure and view identities in the hash.
|
||||
fileprivate func hashStructure(into hasher: inout Hasher) {
|
||||
@@ -1286,7 +1208,7 @@ extension SplitTree.Node {
|
||||
case .leaf(let view):
|
||||
hasher.combine(HashKey.leaf)
|
||||
hasher.combine(ObjectIdentifier(view))
|
||||
|
||||
|
||||
case .split(let split):
|
||||
hasher.combine(HashKey.split)
|
||||
hasher.combine(split.direction)
|
||||
@@ -1325,17 +1247,17 @@ extension SplitTree {
|
||||
struct StructuralIdentity: Hashable {
|
||||
private let root: Node?
|
||||
private let zoomed: Node?
|
||||
|
||||
|
||||
init(_ tree: SplitTree) {
|
||||
self.root = tree.root
|
||||
self.zoomed = tree.zoomed
|
||||
}
|
||||
|
||||
|
||||
static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
areNodesStructurallyEqual(lhs.root, rhs.root) &&
|
||||
areNodesStructurallyEqual(lhs.zoomed, rhs.zoomed)
|
||||
}
|
||||
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(0) // Tree marker
|
||||
if let root = root {
|
||||
@@ -1346,7 +1268,7 @@ extension SplitTree {
|
||||
zoomed.hashStructure(into: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Helper to compare optional nodes for structural equality
|
||||
private static func areNodesStructurallyEqual(_ lhs: Node?, _ rhs: Node?) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
|
@@ -290,12 +290,8 @@ class BaseTerminalController: NSWindowController,
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
alert.alertStyle = .warning
|
||||
alert.beginSheetModal(for: window) { response in
|
||||
let alertWindow = alert.window
|
||||
self.alert = nil
|
||||
if response == .alertFirstButtonReturn {
|
||||
// This is important so that we avoid losing focus when Stage
|
||||
// Manager is used (#8336)
|
||||
alertWindow.orderOut(nil)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
@@ -95,11 +95,6 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
selector: #selector(onCloseTab),
|
||||
name: .ghosttyCloseTab,
|
||||
object: nil)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(onCloseOtherTabs),
|
||||
name: .ghosttyCloseOtherTabs,
|
||||
object: nil)
|
||||
center.addObserver(
|
||||
self,
|
||||
selector: #selector(onResetWindowSize),
|
||||
@@ -201,12 +196,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
|
||||
if let parent {
|
||||
if parent.styleMask.contains(.fullScreen) {
|
||||
// If our previous window was fullscreen then we want our new window to
|
||||
// be fullscreen. This behavior actually doesn't match the native tabbing
|
||||
// behavior of macOS apps where new windows create tabs when in native
|
||||
// fullscreen but this is how we've always done it. This matches iTerm2
|
||||
// behavior.
|
||||
c.toggleFullscreen(mode: .native)
|
||||
parent.toggleFullScreen(nil)
|
||||
} else if ghostty.config.windowFullscreen {
|
||||
switch (ghostty.config.windowFullscreenMode) {
|
||||
case .native:
|
||||
@@ -236,10 +226,6 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
}
|
||||
|
||||
c.showWindow(self)
|
||||
|
||||
// All new_window actions force our app to be active, so that the new
|
||||
// window is focused and visible.
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
// Setup our undo
|
||||
@@ -346,10 +332,6 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
|
||||
controller.showWindow(self)
|
||||
window.makeKeyAndOrderFront(self)
|
||||
|
||||
// We also activate our app so that it becomes front. This may be
|
||||
// necessary for the dock menu.
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
// It takes an event loop cycle until the macOS tabGroup state becomes
|
||||
@@ -439,7 +421,8 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
continue
|
||||
}
|
||||
|
||||
if let equiv = ghostty.config.keyboardShortcut(for: "goto_tab:\(tab)") {
|
||||
let action = "goto_tab:\(tab)"
|
||||
if let equiv = ghostty.config.keyboardShortcut(for: action) {
|
||||
window.keyEquivalent = "\(equiv)"
|
||||
} else {
|
||||
window.keyEquivalent = ""
|
||||
@@ -563,7 +546,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
closeWindow(nil)
|
||||
}
|
||||
|
||||
private func closeTabImmediately(registerRedo: Bool = true) {
|
||||
private func closeTabImmediately() {
|
||||
guard let window = window else { return }
|
||||
guard let tabGroup = window.tabGroup,
|
||||
tabGroup.windows.count > 1 else {
|
||||
@@ -580,69 +563,19 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
expiresAfter: undoExpiration
|
||||
) { ghostty in
|
||||
let newController = TerminalController(ghostty, with: undoState)
|
||||
|
||||
if registerRedo {
|
||||
undoManager.registerUndo(
|
||||
withTarget: newController,
|
||||
expiresAfter: newController.undoExpiration
|
||||
) { target in
|
||||
target.closeTabImmediately()
|
||||
}
|
||||
|
||||
// Register redo action
|
||||
undoManager.registerUndo(
|
||||
withTarget: newController,
|
||||
expiresAfter: newController.undoExpiration
|
||||
) { target in
|
||||
target.closeTabImmediately()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.close()
|
||||
}
|
||||
|
||||
private func closeOtherTabsImmediately() {
|
||||
guard let window = window else { return }
|
||||
guard let tabGroup = window.tabGroup else { return }
|
||||
guard tabGroup.windows.count > 1 else { return }
|
||||
|
||||
// Start an undo grouping
|
||||
if let undoManager {
|
||||
undoManager.beginUndoGrouping()
|
||||
}
|
||||
defer {
|
||||
undoManager?.endUndoGrouping()
|
||||
}
|
||||
|
||||
// Iterate through all tabs except the current one.
|
||||
for window in tabGroup.windows where window != self.window {
|
||||
// We ignore any non-terminal tabs. They don't currently exist and we can't
|
||||
// properly undo them anyways so I'd rather ignore them and get a bug report
|
||||
// later if and when we introduce non-terminal tabs.
|
||||
if let controller = window.windowController as? TerminalController {
|
||||
// We must not register a redo, because it messes with our own redo
|
||||
// that we register later.
|
||||
controller.closeTabImmediately(registerRedo: false)
|
||||
}
|
||||
}
|
||||
|
||||
if let undoManager {
|
||||
undoManager.setActionName("Close Other Tabs")
|
||||
|
||||
// We need to register an undo that refocuses this window. Otherwise, the
|
||||
// undo operation above for each tab will steal focus.
|
||||
undoManager.registerUndo(
|
||||
withTarget: self,
|
||||
expiresAfter: undoExpiration
|
||||
) { target in
|
||||
DispatchQueue.main.async {
|
||||
target.window?.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
|
||||
// Register redo action
|
||||
undoManager.registerUndo(
|
||||
withTarget: target,
|
||||
expiresAfter: target.undoExpiration
|
||||
) { target in
|
||||
target.closeOtherTabsImmediately()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes the current window (including any other tabs) immediately and without
|
||||
/// confirmation. This will setup proper undo state so the action can be undone.
|
||||
@@ -806,9 +739,6 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
alert.alertStyle = .warning
|
||||
alert.beginSheetModal(for: alertWindow, completionHandler: { response in
|
||||
if (response == .alertFirstButtonReturn) {
|
||||
// This is important so that we avoid losing focus when Stage
|
||||
// Manager is used (#8336)
|
||||
alert.window.orderOut(nil)
|
||||
closeAllWindowsImmediately()
|
||||
}
|
||||
})
|
||||
@@ -860,7 +790,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
|
||||
// Restore focus to the previously focused surface
|
||||
if let focusedUUID = undoState.focusedSurface,
|
||||
let focusTarget = surfaceTree.first(where: { $0.id == focusedUUID }) {
|
||||
let focusTarget = surfaceTree.first(where: { $0.uuid == focusedUUID }) {
|
||||
DispatchQueue.main.async {
|
||||
Ghostty.moveFocus(to: focusTarget, from: nil)
|
||||
}
|
||||
@@ -875,7 +805,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
return .init(
|
||||
frame: window.frame,
|
||||
surfaceTree: surfaceTree,
|
||||
focusedSurface: focusedSurface?.id,
|
||||
focusedSurface: focusedSurface?.uuid,
|
||||
tabIndex: window.tabGroup?.windows.firstIndex(of: window),
|
||||
tabGroup: window.tabGroup)
|
||||
}
|
||||
@@ -1077,38 +1007,6 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func closeOtherTabs(_ sender: Any?) {
|
||||
guard let window = window else { return }
|
||||
guard let tabGroup = window.tabGroup else { return }
|
||||
|
||||
// If we only have one window then we have no other tabs to close
|
||||
guard tabGroup.windows.count > 1 else { return }
|
||||
|
||||
// Check if we have to confirm close.
|
||||
guard tabGroup.windows.contains(where: { window in
|
||||
// Ignore ourself
|
||||
if window == self.window { return false }
|
||||
|
||||
// Ignore non-terminals
|
||||
guard let controller = window.windowController as? TerminalController else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if any surfaces require confirmation
|
||||
return controller.surfaceTree.contains(where: { $0.needsConfirmQuit })
|
||||
}) else {
|
||||
self.closeOtherTabsImmediately()
|
||||
return
|
||||
}
|
||||
|
||||
confirmClose(
|
||||
messageText: "Close Other Tabs?",
|
||||
informativeText: "At least one other tab still has a running process. If you close the tab the process will be killed."
|
||||
) {
|
||||
self.closeOtherTabsImmediately()
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func returnToDefaultSize(_ sender: Any?) {
|
||||
guard let defaultSize else { return }
|
||||
window?.setFrame(defaultSize, display: true)
|
||||
@@ -1292,12 +1190,6 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
||||
closeTab(self)
|
||||
}
|
||||
|
||||
@objc private func onCloseOtherTabs(notification: SwiftUI.Notification) {
|
||||
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
||||
guard surfaceTree.contains(target) else { return }
|
||||
closeOtherTabs(self)
|
||||
}
|
||||
|
||||
@objc private func onCloseWindow(notification: SwiftUI.Notification) {
|
||||
guard let target = notification.object as? Ghostty.SurfaceView else { return }
|
||||
guard surfaceTree.contains(target) else { return }
|
||||
|
@@ -4,13 +4,13 @@ import Cocoa
|
||||
class TerminalRestorableState: Codable {
|
||||
static let selfKey = "state"
|
||||
static let versionKey = "version"
|
||||
static let version: Int = 5
|
||||
static let version: Int = 4
|
||||
|
||||
let focusedSurface: String?
|
||||
let surfaceTree: SplitTree<Ghostty.SurfaceView>
|
||||
|
||||
init(from controller: TerminalController) {
|
||||
self.focusedSurface = controller.focusedSurface?.id.uuidString
|
||||
self.focusedSurface = controller.focusedSurface?.uuid.uuidString
|
||||
self.surfaceTree = controller.surfaceTree
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ class TerminalWindowRestoration: NSObject, NSWindowRestoration {
|
||||
if let focusedStr = state.focusedSurface {
|
||||
var foundView: Ghostty.SurfaceView?
|
||||
for view in c.surfaceTree {
|
||||
if view.id.uuidString == focusedStr {
|
||||
if view.uuid.uuidString == focusedStr {
|
||||
foundView = view
|
||||
break
|
||||
}
|
||||
|
@@ -31,16 +31,9 @@ class HiddenTitlebarTerminalWindow: TerminalWindow {
|
||||
.closable,
|
||||
.miniaturizable,
|
||||
]
|
||||
|
||||
|
||||
/// Apply the hidden titlebar style.
|
||||
private func reapplyHiddenStyle() {
|
||||
// If our window is fullscreen then we don't reapply the hidden style because
|
||||
// it can result in messing up non-native fullscreen. See:
|
||||
// https://github.com/ghostty-org/ghostty/issues/8415
|
||||
if terminalController?.fullscreenStyle?.isFullscreen ?? false {
|
||||
return
|
||||
}
|
||||
|
||||
// Apply our style mask while preserving the .fullScreen option
|
||||
if styleMask.contains(.fullScreen) {
|
||||
styleMask = Self.hiddenStyleMask.union([.fullScreen])
|
||||
|
@@ -70,39 +70,4 @@ extension Ghostty.Action {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProgressReport {
|
||||
enum State {
|
||||
case remove
|
||||
case set
|
||||
case error
|
||||
case indeterminate
|
||||
case pause
|
||||
|
||||
init(_ c: ghostty_action_progress_report_state_e) {
|
||||
switch c {
|
||||
case GHOSTTY_PROGRESS_STATE_REMOVE:
|
||||
self = .remove
|
||||
case GHOSTTY_PROGRESS_STATE_SET:
|
||||
self = .set
|
||||
case GHOSTTY_PROGRESS_STATE_ERROR:
|
||||
self = .error
|
||||
case GHOSTTY_PROGRESS_STATE_INDETERMINATE:
|
||||
self = .indeterminate
|
||||
case GHOSTTY_PROGRESS_STATE_PAUSE:
|
||||
self = .pause
|
||||
default:
|
||||
self = .remove
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let state: State
|
||||
let progress: UInt8?
|
||||
|
||||
init(c: ghostty_action_progress_report_s) {
|
||||
self.state = State(c.state)
|
||||
self.progress = c.progress >= 0 ? UInt8(c.progress) : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -455,7 +455,7 @@ extension Ghostty {
|
||||
newSplit(app, target: target, direction: action.action.new_split)
|
||||
|
||||
case GHOSTTY_ACTION_CLOSE_TAB:
|
||||
closeTab(app, target: target, mode: action.action.close_tab_mode)
|
||||
closeTab(app, target: target)
|
||||
|
||||
case GHOSTTY_ACTION_CLOSE_WINDOW:
|
||||
closeWindow(app, target: target)
|
||||
@@ -543,9 +543,6 @@ extension Ghostty {
|
||||
|
||||
case GHOSTTY_ACTION_KEY_SEQUENCE:
|
||||
keySequence(app, target: target, v: action.action.key_sequence)
|
||||
|
||||
case GHOSTTY_ACTION_PROGRESS_REPORT:
|
||||
progressReport(app, target: target, v: action.action.progress_report)
|
||||
|
||||
case GHOSTTY_ACTION_CONFIG_CHANGE:
|
||||
configChange(app, target: target, v: action.action.config_change)
|
||||
@@ -781,34 +778,20 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
private static func closeTab(_ app: ghostty_app_t, target: ghostty_target_s, mode: ghostty_action_close_tab_mode_e) {
|
||||
private static func closeTab(_ app: ghostty_app_t, target: ghostty_target_s) {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("close tabs does nothing with an app target")
|
||||
Ghostty.logger.warning("close tab does nothing with an app target")
|
||||
return
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
|
||||
switch (mode) {
|
||||
case GHOSTTY_ACTION_CLOSE_TAB_MODE_THIS:
|
||||
NotificationCenter.default.post(
|
||||
name: .ghosttyCloseTab,
|
||||
object: surfaceView
|
||||
)
|
||||
return
|
||||
|
||||
case GHOSTTY_ACTION_CLOSE_TAB_MODE_OTHER:
|
||||
NotificationCenter.default.post(
|
||||
name: .ghosttyCloseOtherTabs,
|
||||
object: surfaceView
|
||||
)
|
||||
return
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
NotificationCenter.default.post(
|
||||
name: .ghosttyCloseTab,
|
||||
object: surfaceView
|
||||
)
|
||||
|
||||
|
||||
default:
|
||||
@@ -1526,33 +1509,6 @@ extension Ghostty {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private static func progressReport(
|
||||
_ app: ghostty_app_t,
|
||||
target: ghostty_target_s,
|
||||
v: ghostty_action_progress_report_s) {
|
||||
switch (target.tag) {
|
||||
case GHOSTTY_TARGET_APP:
|
||||
Ghostty.logger.warning("progress report does nothing with an app target")
|
||||
return
|
||||
|
||||
case GHOSTTY_TARGET_SURFACE:
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
|
||||
let progressReport = Ghostty.Action.ProgressReport(c: v)
|
||||
DispatchQueue.main.async {
|
||||
if progressReport.state == .remove {
|
||||
surfaceView.progressReport = nil
|
||||
} else {
|
||||
surfaceView.progressReport = progressReport
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
private static func configReload(
|
||||
_ app: ghostty_app_t,
|
||||
|
@@ -164,7 +164,7 @@ extension Ghostty {
|
||||
let key = "window-position-x"
|
||||
return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil
|
||||
}
|
||||
|
||||
|
||||
var windowPositionY: Int16? {
|
||||
guard let config = self.config else { return nil }
|
||||
var v: Int16 = 0
|
||||
@@ -282,17 +282,6 @@ extension Ghostty {
|
||||
return MacOSTitlebarProxyIcon(rawValue: str) ?? defaultValue
|
||||
}
|
||||
|
||||
var macosDockDropBehavior: MacDockDropBehavior {
|
||||
let defaultValue = MacDockDropBehavior.new_tab
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-dock-drop-behavior"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
let str = String(cString: ptr)
|
||||
return MacDockDropBehavior(rawValue: str) ?? defaultValue
|
||||
}
|
||||
|
||||
var macosWindowShadow: Bool {
|
||||
guard let config = self.config else { return false }
|
||||
var v = false;
|
||||
@@ -312,24 +301,6 @@ extension Ghostty {
|
||||
return MacOSIcon(rawValue: str) ?? defaultValue
|
||||
}
|
||||
|
||||
var macosCustomIcon: String {
|
||||
#if os(macOS)
|
||||
let homeDirURL = FileManager.default.homeDirectoryForCurrentUser
|
||||
let ghosttyConfigIconPath = homeDirURL.appendingPathComponent(
|
||||
".config/ghostty/Ghostty.icns",
|
||||
conformingTo: .fileURL).path()
|
||||
let defaultValue = ghosttyConfigIconPath
|
||||
guard let config = self.config else { return defaultValue }
|
||||
var v: UnsafePointer<Int8>? = nil
|
||||
let key = "macos-custom-icon"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return defaultValue }
|
||||
guard let ptr = v else { return defaultValue }
|
||||
return String(cString: ptr)
|
||||
#else
|
||||
return ""
|
||||
#endif
|
||||
}
|
||||
|
||||
var macosIconFrame: MacOSIconFrame {
|
||||
let defaultValue = MacOSIconFrame.aluminum
|
||||
guard let config = self.config else { return defaultValue }
|
||||
@@ -504,14 +475,6 @@ extension Ghostty {
|
||||
let str = String(cString: ptr)
|
||||
return QuickTerminalSpaceBehavior(fromGhosttyConfig: str) ?? .move
|
||||
}
|
||||
|
||||
var quickTerminalSize: QuickTerminalSize {
|
||||
guard let config = self.config else { return QuickTerminalSize() }
|
||||
var v = ghostty_config_quick_terminal_size_s()
|
||||
let key = "quick-terminal-size"
|
||||
guard ghostty_config_get(config, &v, key, UInt(key.count)) else { return QuickTerminalSize() }
|
||||
return QuickTerminalSize(from: v)
|
||||
}
|
||||
#endif
|
||||
|
||||
var resizeOverlay: ResizeOverlay {
|
||||
@@ -626,11 +589,6 @@ extension Ghostty.Config {
|
||||
static let attention = BellFeatures(rawValue: 1 << 2)
|
||||
static let title = BellFeatures(rawValue: 1 << 3)
|
||||
}
|
||||
|
||||
enum MacDockDropBehavior: String {
|
||||
case new_tab = "new-tab"
|
||||
case new_window = "new-window"
|
||||
}
|
||||
|
||||
enum MacHidden : String {
|
||||
case never
|
||||
|
@@ -280,7 +280,6 @@ extension Ghostty {
|
||||
case paper
|
||||
case retro
|
||||
case xray
|
||||
case custom
|
||||
case customStyle = "custom-style"
|
||||
}
|
||||
|
||||
@@ -329,9 +328,6 @@ extension Notification.Name {
|
||||
/// Close tab
|
||||
static let ghosttyCloseTab = Notification.Name("com.mitchellh.ghostty.closeTab")
|
||||
|
||||
/// Close other tabs
|
||||
static let ghosttyCloseOtherTabs = Notification.Name("com.mitchellh.ghostty.closeOtherTabs")
|
||||
|
||||
/// Close window
|
||||
static let ghosttyCloseWindow = Notification.Name("com.mitchellh.ghostty.closeWindow")
|
||||
|
||||
|
@@ -113,11 +113,6 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
.ghosttySurfaceView(surfaceView)
|
||||
|
||||
// Progress report overlay
|
||||
if let progressReport = surfaceView.progressReport {
|
||||
ProgressReportOverlay(report: progressReport)
|
||||
}
|
||||
|
||||
#if canImport(AppKit)
|
||||
// If we are in the middle of a key sequence, then we show a visual element. We only
|
||||
@@ -272,49 +267,6 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
// Progress report overlay that shows a progress bar at the top of the terminal
|
||||
struct ProgressReportOverlay: View {
|
||||
let report: Action.ProgressReport
|
||||
|
||||
@ViewBuilder
|
||||
private var progressBar: some View {
|
||||
if let progress = report.progress {
|
||||
// Determinate progress bar
|
||||
ProgressView(value: Double(progress), total: 100)
|
||||
.progressViewStyle(.linear)
|
||||
.tint(report.state == .error ? .red : report.state == .pause ? .orange : nil)
|
||||
.animation(.easeInOut(duration: 0.2), value: progress)
|
||||
} else {
|
||||
// Indeterminate states
|
||||
switch report.state {
|
||||
case .indeterminate:
|
||||
ProgressView()
|
||||
.progressViewStyle(.linear)
|
||||
case .error:
|
||||
ProgressView()
|
||||
.progressViewStyle(.linear)
|
||||
.tint(.red)
|
||||
case .pause:
|
||||
Rectangle().fill(Color.orange)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
progressBar
|
||||
.scaleEffect(x: 1, y: 0.5, anchor: .center)
|
||||
.frame(height: 2)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
// This is the resize overlay that shows on top of a surface to show the current
|
||||
// size during a resize operation.
|
||||
struct SurfaceResizeOverlay: View {
|
||||
@@ -472,9 +424,6 @@ extension Ghostty {
|
||||
|
||||
/// Extra input to send as stdin
|
||||
var initialInput: String? = nil
|
||||
|
||||
/// Wait after the command
|
||||
var waitAfterCommand: Bool = false
|
||||
|
||||
init() {}
|
||||
|
||||
@@ -526,9 +475,6 @@ extension Ghostty {
|
||||
|
||||
// Zero is our default value that means to inherit the font size.
|
||||
config.font_size = fontSize ?? 0
|
||||
|
||||
// Set wait after command
|
||||
config.wait_after_command = waitAfterCommand
|
||||
|
||||
// Use withCString to ensure strings remain valid for the duration of the closure
|
||||
return try workingDirectory.withCString { cWorkingDir in
|
||||
|
@@ -6,11 +6,9 @@ import GhosttyKit
|
||||
|
||||
extension Ghostty {
|
||||
/// The NSView implementation for a terminal surface.
|
||||
class SurfaceView: OSView, ObservableObject, Codable, Identifiable {
|
||||
typealias ID = UUID
|
||||
|
||||
class SurfaceView: OSView, ObservableObject, Codable {
|
||||
/// Unique ID per surface
|
||||
let id: UUID
|
||||
let uuid: UUID
|
||||
|
||||
// The current title of the surface as defined by the pty. This can be
|
||||
// changed with escape codes. This is public because the callbacks go
|
||||
@@ -43,23 +41,6 @@ extension Ghostty {
|
||||
|
||||
// The hovered URL string
|
||||
@Published var hoverUrl: String? = nil
|
||||
|
||||
// The progress report (if any)
|
||||
@Published var progressReport: Action.ProgressReport? = nil {
|
||||
didSet {
|
||||
// Cancel any existing timer
|
||||
progressReportTimer?.invalidate()
|
||||
progressReportTimer = nil
|
||||
|
||||
// If we have a new progress report, start a timer to remove it after 15 seconds
|
||||
if progressReport != nil {
|
||||
progressReportTimer = Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { [weak self] _ in
|
||||
self?.progressReport = nil
|
||||
self?.progressReportTimer = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The currently active key sequence. The sequence is not active if this is empty.
|
||||
@Published var keySequence: [KeyboardShortcut] = []
|
||||
@@ -161,9 +142,6 @@ extension Ghostty {
|
||||
|
||||
// A timer to fallback to ghost emoji if no title is set within the grace period
|
||||
private var titleFallbackTimer: Timer?
|
||||
|
||||
// Timer to remove progress report after 15 seconds
|
||||
private var progressReportTimer: 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
|
||||
@@ -182,7 +160,7 @@ extension Ghostty {
|
||||
|
||||
init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) {
|
||||
self.markedText = NSMutableAttributedString()
|
||||
self.id = uuid ?? .init()
|
||||
self.uuid = uuid ?? .init()
|
||||
|
||||
// Our initial config always is our application wide config.
|
||||
if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
|
||||
@@ -370,9 +348,6 @@ extension Ghostty {
|
||||
// Remove any notifications associated with this surface
|
||||
let identifiers = Array(self.notificationIdentifiers)
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers)
|
||||
|
||||
// Cancel progress report timer
|
||||
progressReportTimer?.invalidate()
|
||||
}
|
||||
|
||||
func focusDidChange(_ focused: Bool) {
|
||||
@@ -1266,7 +1241,7 @@ extension Ghostty {
|
||||
|
||||
var key_ev = event.ghosttyKeyEvent(action, translationMods: translationEvent?.modifierFlags)
|
||||
key_ev.composing = composing
|
||||
|
||||
|
||||
// For text, we only encode UTF8 if we don't have a single control
|
||||
// character. Control characters are encoded by Ghostty itself.
|
||||
// Without this, `ctrl+enter` does the wrong thing.
|
||||
@@ -1352,7 +1327,7 @@ extension Ghostty {
|
||||
var item: NSMenuItem
|
||||
|
||||
// If we have a selection, add copy
|
||||
if let text = self.accessibilitySelectedText(), text.count > 0 {
|
||||
if self.selectedRange().length > 0 {
|
||||
menu.addItem(withTitle: "Copy", action: #selector(copy(_:)), keyEquivalent: "")
|
||||
}
|
||||
menu.addItem(withTitle: "Paste", action: #selector(paste(_:)), keyEquivalent: "")
|
||||
@@ -1470,7 +1445,7 @@ extension Ghostty {
|
||||
content.body = body
|
||||
content.sound = UNNotificationSound.default
|
||||
content.categoryIdentifier = Ghostty.userNotificationCategory
|
||||
content.userInfo = ["surface": self.id.uuidString]
|
||||
content.userInfo = ["surface": self.uuid.uuidString]
|
||||
|
||||
let uuid = UUID().uuidString
|
||||
let request = UNNotificationRequest(
|
||||
@@ -1578,7 +1553,7 @@ extension Ghostty {
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(pwd, forKey: .pwd)
|
||||
try container.encode(id.uuidString, forKey: .uuid)
|
||||
try container.encode(uuid.uuidString, forKey: .uuid)
|
||||
try container.encode(title, forKey: .title)
|
||||
try container.encode(titleFromTerminal != nil, forKey: .isUserSetTitle)
|
||||
}
|
||||
@@ -1685,10 +1660,8 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
||||
}
|
||||
|
||||
// Ghostty will tell us where it thinks an IME keyboard should render.
|
||||
var x: Double = 0
|
||||
var y: Double = 0
|
||||
var width: Double = cellSize.width
|
||||
var height: Double = cellSize.height
|
||||
var x: Double = 0;
|
||||
var y: Double = 0;
|
||||
|
||||
// QuickLook never gives us a matching range to our selection so if we detect
|
||||
// this then we return the top-left selection point rather than the cursor point.
|
||||
@@ -1706,19 +1679,15 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
||||
// Free our text
|
||||
ghostty_surface_free_text(surface, &text)
|
||||
} else {
|
||||
ghostty_surface_ime_point(surface, &x, &y, &width, &height)
|
||||
ghostty_surface_ime_point(surface, &x, &y)
|
||||
}
|
||||
} else {
|
||||
ghostty_surface_ime_point(surface, &x, &y, &width, &height)
|
||||
ghostty_surface_ime_point(surface, &x, &y)
|
||||
}
|
||||
|
||||
// Ghostty coordinates are in top-left (0, 0) so we have to convert to
|
||||
// bottom-left since that is what UIKit expects
|
||||
let viewRect = NSMakeRect(
|
||||
x,
|
||||
frame.size.height - y,
|
||||
max(width, cellSize.width),
|
||||
max(height, cellSize.height))
|
||||
let viewRect = NSMakeRect(x, frame.size.height - y, 0, 0)
|
||||
|
||||
// Convert the point to the window coordinates
|
||||
let winRect = self.convert(viewRect, to: nil)
|
||||
|
@@ -30,9 +30,6 @@ extension Ghostty {
|
||||
|
||||
// The hovered URL
|
||||
@Published var hoverUrl: String? = nil
|
||||
|
||||
// The progress report (if any)
|
||||
@Published var progressReport: Action.ProgressReport? = nil
|
||||
|
||||
// The time this surface last became focused. This is a ContinuousClock.Instant
|
||||
// on supported platforms.
|
||||
|
@@ -407,14 +407,8 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
||||
self.styleMask = window.styleMask
|
||||
self.toolbar = window.toolbar
|
||||
self.toolbarStyle = window.toolbarStyle
|
||||
self.titlebarAccessoryViewControllers = window.titlebarAccessoryViewControllers
|
||||
self.dock = window.screen?.hasDock ?? false
|
||||
|
||||
self.titlebarAccessoryViewControllers = if (window.hasTitleBar) {
|
||||
// Accessing titlebarAccessoryViewControllers without a titlebar triggers a crash.
|
||||
window.titlebarAccessoryViewControllers
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
|
||||
if let cgWindowId = window.cgWindowId {
|
||||
// We hide the menu only if this window is not on any fullscreen
|
||||
|
@@ -9,7 +9,6 @@
|
||||
# - build.zig.zon.nix
|
||||
# - build.zig.zon.txt
|
||||
# - build.zig.zon.json
|
||||
# - flatpak/zig-packages.json
|
||||
#
|
||||
# All of these are auto-generated and should not be edited manually.
|
||||
|
||||
@@ -35,8 +34,8 @@ help() {
|
||||
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 build.zig.zon.txt build.zig.zon.json flatpak/zig-packages.json"
|
||||
echo " git commit -m \"nix: update build.zig.zon.nix build.zig.zon.txt build.zig.zon.json flatpak/zig-packages.json\""
|
||||
echo " git add build.zig.zon.nix build.zig.zon.txt build.zig.zon.json"
|
||||
echo " git commit -m \"nix: update build.zig.zon.nix build.zig.zon.txt build.zig.zon.json\""
|
||||
echo ""
|
||||
}
|
||||
|
||||
@@ -45,7 +44,6 @@ BUILD_ZIG_ZON="$ROOT/build.zig.zon"
|
||||
BUILD_ZIG_ZON_NIX="$ROOT/build.zig.zon.nix"
|
||||
BUILD_ZIG_ZON_TXT="$ROOT/build.zig.zon.txt"
|
||||
BUILD_ZIG_ZON_JSON="$ROOT/build.zig.zon.json"
|
||||
ZIG_PACKAGES_JSON="$ROOT/flatpak/zig-packages.json"
|
||||
|
||||
if [ -f "${BUILD_ZIG_ZON_NIX}" ]; then
|
||||
OLD_HASH_NIX=$(sha512sum "${BUILD_ZIG_ZON_NIX}" | awk '{print $1}')
|
||||
@@ -71,40 +69,27 @@ elif [ "$1" != "--update" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "${ZIG_PACKAGES_JSON}" ]; then
|
||||
OLD_HASH_FLATPAK=$(sha512sum "${ZIG_PACKAGES_JSON}" | awk '{print $1}')
|
||||
elif [ "$1" != "--update" ]; then
|
||||
echo -e "\nERROR: flatpak/zig-packages.json missing."
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
zon2nix "$BUILD_ZIG_ZON" --nix "$WORK_DIR/build.zig.zon.nix" --txt "$WORK_DIR/build.zig.zon.txt" --json "$WORK_DIR/build.zig.zon.json" --flatpak "$WORK_DIR/zig-packages.json"
|
||||
zon2nix "$BUILD_ZIG_ZON" --nix "$WORK_DIR/build.zig.zon.nix" --txt "$WORK_DIR/build.zig.zon.txt" --json "$WORK_DIR/build.zig.zon.json"
|
||||
alejandra --quiet "$WORK_DIR/build.zig.zon.nix"
|
||||
prettier --log-level warn --write "$WORK_DIR/build.zig.zon.json"
|
||||
prettier --log-level warn --write "$WORK_DIR/zig-packages.json"
|
||||
prettier --write "$WORK_DIR/build.zig.zon.json"
|
||||
|
||||
NEW_HASH_NIX=$(sha512sum "$WORK_DIR/build.zig.zon.nix" | awk '{print $1}')
|
||||
NEW_HASH_TXT=$(sha512sum "$WORK_DIR/build.zig.zon.txt" | awk '{print $1}')
|
||||
NEW_HASH_JSON=$(sha512sum "$WORK_DIR/build.zig.zon.json" | awk '{print $1}')
|
||||
NEW_HASH_FLATPAK=$(sha512sum "$WORK_DIR/zig-packages.json" | awk '{print $1}')
|
||||
|
||||
if [ "${OLD_HASH_NIX}" == "${NEW_HASH_NIX}" ] && [ "${OLD_HASH_TXT}" == "${NEW_HASH_TXT}" ] && [ "${OLD_HASH_JSON}" == "${NEW_HASH_JSON}" ] && [ "${OLD_HASH_FLATPAK}" == "${NEW_HASH_FLATPAK}" ]; then
|
||||
if [ "${OLD_HASH_NIX}" == "${NEW_HASH_NIX}" ] && [ "${OLD_HASH_TXT}" == "${NEW_HASH_TXT}" ] && [ "${OLD_HASH_JSON}" == "${NEW_HASH_JSON}" ]; then
|
||||
echo -e "\nOK: build.zig.zon.nix unchanged."
|
||||
echo -e "OK: build.zig.zon.txt unchanged."
|
||||
echo -e "OK: build.zig.zon.json unchanged."
|
||||
echo -e "OK: flatpak/zig-packages.json unchanged."
|
||||
exit 0
|
||||
elif [ "$1" != "--update" ]; then
|
||||
echo -e "\nERROR: build.zig.zon.nix, build.zig.zon.txt, or build.zig.zon.json needs to be updated.\n"
|
||||
echo " * Old build.zig.zon.nix hash: ${OLD_HASH_NIX}"
|
||||
echo " * New build.zig.zon.nix hash: ${NEW_HASH_NIX}"
|
||||
echo " * Old build.zig.zon.txt hash: ${OLD_HASH_TXT}"
|
||||
echo " * New build.zig.zon.txt hash: ${NEW_HASH_TXT}"
|
||||
echo " * Old build.zig.zon.json hash: ${OLD_HASH_JSON}"
|
||||
echo " * New build.zig.zon.json hash: ${NEW_HASH_JSON}"
|
||||
echo " * Old flatpak/zig-packages.json hash: ${OLD_HASH_FLATPAK}"
|
||||
echo " * New flatpak/zig-packages.json hash: ${NEW_HASH_FLATPAK}"
|
||||
echo " * Old build.zig.zon.nix hash: ${OLD_HASH_NIX}"
|
||||
echo " * New build.zig.zon.nix hash: ${NEW_HASH_NIX}"
|
||||
echo " * Old build.zig.zon.txt hash: ${OLD_HASH_TXT}"
|
||||
echo " * New build.zig.zon.txt hash: ${NEW_HASH_TXT}"
|
||||
echo " * Old build.zig.zon.json hash: ${OLD_HASH_JSON}"
|
||||
echo " * New build.zig.zon.json hash: ${NEW_HASH_JSON}"
|
||||
help
|
||||
exit 1
|
||||
else
|
||||
@@ -114,8 +99,6 @@ else
|
||||
echo -e "OK: build.zig.zon.txt updated."
|
||||
mv "$WORK_DIR/build.zig.zon.json" "$BUILD_ZIG_ZON_JSON"
|
||||
echo -e "OK: build.zig.zon.json updated."
|
||||
mv "$WORK_DIR/zig-packages.json" "$ZIG_PACKAGES_JSON"
|
||||
echo -e "OK: flatpak/zig-packages.json updated."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
@@ -3,7 +3,6 @@
|
||||
lib,
|
||||
stdenv,
|
||||
bashInteractive,
|
||||
nushell,
|
||||
appstream,
|
||||
flatpak-builder,
|
||||
gdb,
|
||||
@@ -61,7 +60,6 @@
|
||||
pandoc,
|
||||
pinact,
|
||||
hyperfine,
|
||||
poop,
|
||||
typos,
|
||||
shellcheck,
|
||||
uv,
|
||||
@@ -126,9 +124,6 @@ in
|
||||
# CI
|
||||
uv
|
||||
|
||||
# Scripting
|
||||
nushell
|
||||
|
||||
# We need these GTK-related deps on all platform so we can build
|
||||
# dist tarballs.
|
||||
blueprint-compiler
|
||||
@@ -188,9 +183,6 @@ in
|
||||
# developer shell
|
||||
glycin-loaders
|
||||
librsvg
|
||||
|
||||
# for benchmarking
|
||||
poop
|
||||
];
|
||||
|
||||
# This should be set onto the rpath of the ghostty binary if you want
|
||||
|
@@ -13,7 +13,11 @@ pub fn build(b: *std.Build) !void {
|
||||
|
||||
const unit_tests = b.addTest(.{
|
||||
.name = "test",
|
||||
.root_module = module,
|
||||
.root_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
}),
|
||||
});
|
||||
unit_tests.linkLibC();
|
||||
|
||||
@@ -30,6 +34,12 @@ pub fn build(b: *std.Build) !void {
|
||||
.file = wuffs_dep.path("release/c/wuffs-v0.4.c"),
|
||||
.flags = flags.items,
|
||||
});
|
||||
|
||||
unit_tests.addIncludePath(wuffs_dep.path("release/c"));
|
||||
unit_tests.addCSourceFile(.{
|
||||
.file = wuffs_dep.path("release/c/wuffs-v0.4.c"),
|
||||
.flags = flags.items,
|
||||
});
|
||||
}
|
||||
|
||||
if (b.lazyDependency("pixels", .{})) |pixels_dep| {
|
||||
|
@@ -2,15 +2,14 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Damyan Bogoev <damyan.bogoev@gmail.com>, 2025.
|
||||
# reo101 <pavel.atanasov2001@gmail.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-22 14:52+0300\n"
|
||||
"Last-Translator: reo101 <pavel.atanasov2001@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-05-19 11:34+0300\n"
|
||||
"Last-Translator: Damyan Bogoev <damyan.bogoev@gmail.com>\n"
|
||||
"Language-Team: Bulgarian <dict@ludost.net>\n"
|
||||
"Language: bg\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -209,12 +208,12 @@ msgstr "Позволи"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Запомни избора за това разделяне"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "За да покажеш това съобщение отново, презареди конфигурацията"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -279,15 +278,15 @@ msgstr "Копирано в клипборда"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Клипбордът е изчистен"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Командата завърши успешно"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Командата завърши неуспешно"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -2,15 +2,14 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Francesc Arpi <francesc.arpi@gmail.com>, 2025.
|
||||
# Kristofer Soler <31729650+KristoferSoler@users.noreply.github.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-24 19:22+0200\n"
|
||||
"Last-Translator: Kristofer Soler <31729650+KristoferSoler@users.noreply.github.com>\n"
|
||||
"PO-Revision-Date: 2025-03-20 08:07+0100\n"
|
||||
"Last-Translator: Francesc Arpi <francesc.arpi@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: ca\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -88,7 +87,7 @@ msgstr "Divideix a la dreta"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Executa una ordre…"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -161,7 +160,7 @@ msgstr "Obre la configuració"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Paleta de comandes"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -209,12 +208,12 @@ msgstr "Permet"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Recorda l’opció per a aquest panell dividit"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Recarrega la configuració per tornar a mostrar aquest missatge"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -279,15 +278,15 @@ msgstr "Copiat al porta-retalls"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Porta-retalls netejat"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Comanda completada amb èxit"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Comanda fallida"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -299,7 +298,7 @@ msgstr "Mostra les pestanyes obertes"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr "Nova divisió"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -9,7 +9,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-25 19:38+0100\n"
|
||||
"PO-Revision-Date: 2025-03-06 14:57+0100\n"
|
||||
"Last-Translator: Robin <r@rpfaeffle.com>\n"
|
||||
"Language-Team: German <translation-team-de@lists.sourceforge.net>\n"
|
||||
"Language: de\n"
|
||||
@@ -39,7 +39,7 @@ msgstr "OK"
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:5
|
||||
msgid "Configuration Errors"
|
||||
msgstr "Konfigurationsfehler"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6
|
||||
@@ -47,14 +47,11 @@ msgid ""
|
||||
"One or more configuration errors were found. Please review the errors below, "
|
||||
"and either reload your configuration or ignore these errors."
|
||||
msgstr ""
|
||||
"Ein oder mehrere Konfigurationsfehler wurden gefunden. Bitte überprüfe "
|
||||
"die untenstehenden Fehler und lade entweder deine Konfiguration erneut oder "
|
||||
"ignoriere die Fehler."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:9
|
||||
msgid "Ignore"
|
||||
msgstr "Ignorieren"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97
|
||||
@@ -89,7 +86,7 @@ msgstr "Fenster nach rechts teilen"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Einen Befehl ausführen…"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -162,7 +159,7 @@ msgstr "Konfiguration öffnen"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Befehlspalette"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -210,13 +207,12 @@ msgstr "Erlauben"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Auswahl für dieses geteilte Fenster beibehalten"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr ""
|
||||
"Lade die Konfiguration erneut, um diese Eingabeaufforderung erneut anzuzeigen"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -281,15 +277,15 @@ msgstr "In die Zwischenablage kopiert"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Zwischenablage geleert"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Befehl erfolgreich"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Befehl fehlgeschlagen"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -301,7 +297,7 @@ msgstr "Offene Tabs einblenden"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr "Neues geteiltes Fenster"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-22 09:35-0300\n"
|
||||
"PO-Revision-Date: 2025-05-19 20:17-0300\n"
|
||||
"Last-Translator: Alan Moyano <alanmoyano203@gmail.com>\n"
|
||||
"Language-Team: Argentinian <es@tp.org.es>\n"
|
||||
"Language: es_AR\n"
|
||||
@@ -208,12 +208,12 @@ msgstr "Permitir"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Recordar elección para esta división"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Recargar la configuración para volver a mostrar este mensaje"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -278,15 +278,15 @@ msgstr "Copiado al portapapeles"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Portapapeles limpiado"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Comando ejecutado correctamente"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "El comando ha fallado"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-23 17:46+0200\n"
|
||||
"PO-Revision-Date: 2025-03-28 17:46+0200\n"
|
||||
"Last-Translator: Miguel Peredo <miguelp@quientienemail.com>\n"
|
||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||
"Language: es_BO\n"
|
||||
@@ -87,7 +87,7 @@ msgstr "Dividir a la derecha"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Ejecutar comando..."
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -160,7 +160,7 @@ msgstr "Abrir configuración"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Paleta de comandos"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -208,12 +208,12 @@ msgstr "Permitir"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Recordar su elección para esta división de ventana"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Recargar configuración para mostrar este aviso nuevamente"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -278,15 +278,15 @@ msgstr "Copiado al portapapeles"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "El portapapeles está limpio"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Comando ejecutado con éxito"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Comando fallido"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -298,7 +298,7 @@ msgstr "Ver pestañas abiertas"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr "Nueva ventana dividida"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-23 21:01+0200\n"
|
||||
"PO-Revision-Date: 2025-03-22 09:31+0100\n"
|
||||
"Last-Translator: Kirwiisp <swiip__@hotmail.com>\n"
|
||||
"Language-Team: French <traduc@traduc.org>\n"
|
||||
"Language: fr\n"
|
||||
@@ -88,7 +88,7 @@ msgstr "Panneau à droite"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Exécuter une commande…"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -161,7 +161,7 @@ msgstr "Ouvrir la configuration"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Palette de commandes"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -209,12 +209,12 @@ msgstr "Autoriser"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Se rappeler du choix pour ce panneau"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Recharger la configuration pour afficher à nouveau ce message"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -279,15 +279,15 @@ msgstr "Copié dans le presse-papiers"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Presse-papiers vidé"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Commande réussie"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "La commande a échoué"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -299,7 +299,7 @@ msgstr "Voir les onglets ouverts"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr "Nouveau panneau"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-26 15:46+0100\n"
|
||||
"PO-Revision-Date: 2025-06-29 21:15+0100\n"
|
||||
"Last-Translator: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>\n"
|
||||
"Language-Team: Irish <gaeilge-gnulinux@lists.sourceforge.net>\n"
|
||||
"Language: ga\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;\n"
|
||||
"X-Generator: Poedit 3.4.2\n"
|
||||
"X-Generator: Poedit 3.4.4\n"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
|
||||
msgid "Change Terminal Title"
|
||||
@@ -209,12 +209,12 @@ msgstr "Ceadaigh"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Sábháil an rogha don scoilt seo"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Athlódáil an chumraíocht chun an teachtaireacht seo a thaispeáint arís"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -280,15 +280,15 @@ msgstr "Cóipeáilte chuig an ghearrthaisce"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Gearrthaisce glanta"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "D'éirigh leis an ordú"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Theip ar an ordú"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -2,15 +2,15 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Sl (Shahaf Levi), Sl's Repository Ltd <ghostty@slsrepo.com>, 2025.
|
||||
# CraziestOwl <craziestowl@proton.me>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-23 08:00+0300\n"
|
||||
"Last-Translator: CraziestOwl <craziestowl@proton.me>\n"
|
||||
"PO-Revision-Date: 2025-03-13 00:00+0000\n"
|
||||
"Last-Translator: Sl (Shahaf Levi), Sl's Repository Ltd <ghostty@slsrepo."
|
||||
"com>\n"
|
||||
"Language-Team: Hebrew <he_IL@lists.sourceforge.net>\n"
|
||||
"Language: he\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -276,15 +276,15 @@ msgstr "הועתק ללוח ההעתקה"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "לוח ההעתקה רוקן"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "הפקודה הצליחה"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "הפקודה נכשלה"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -1,320 +0,0 @@
|
||||
# Hungarian translations for com.mitchellh.ghostty package.
|
||||
# Copyright (C) 2025 Mitchell Hashimoto
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Balázs Szücs <bszucs1209@gmail.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-23 17:14+0200\n"
|
||||
"Last-Translator: Balázs Szücs <bszucs1209@gmail.com>\n"
|
||||
"Language-Team: Hungarian <translation-team-hu@lists.sourceforge.net>\n"
|
||||
"Language: hu\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
|
||||
msgid "Change Terminal Title"
|
||||
msgstr "Terminál címének módosítása"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6
|
||||
msgid "Leave blank to restore the default title."
|
||||
msgstr "Hagyja üresen az alapértelmezett cím visszaállításához."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/ui/1.2/ccw-paste.blp:10
|
||||
#: src/apprt/gtk/CloseDialog.zig:44
|
||||
msgid "Cancel"
|
||||
msgstr "Mégse"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10
|
||||
msgid "OK"
|
||||
msgstr "Rendben"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:5
|
||||
msgid "Configuration Errors"
|
||||
msgstr "Konfigurációs hibák"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6
|
||||
msgid ""
|
||||
"One or more configuration errors were found. Please review the errors below, "
|
||||
"and either reload your configuration or ignore these errors."
|
||||
msgstr ""
|
||||
"Egy vagy több konfigurációs hiba található. Kérjük, ellenőrizze az alábbi "
|
||||
"hibákat, és frissítse a konfigurációt, vagy hagyja figyelmen kívül ezeket a "
|
||||
"hibákat."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:9
|
||||
msgid "Ignore"
|
||||
msgstr "Figyelmen kívül hagyás"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10
|
||||
msgid "Reload Configuration"
|
||||
msgstr "Konfiguráció frissítése"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50
|
||||
msgid "Split Up"
|
||||
msgstr "Felosztás felfelé"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55
|
||||
msgid "Split Down"
|
||||
msgstr "Felosztás lefelé"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60
|
||||
msgid "Split Left"
|
||||
msgstr "Felosztás balra"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65
|
||||
msgid "Split Right"
|
||||
msgstr "Felosztás jobbra"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Parancs végrehajtása…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
msgid "Copy"
|
||||
msgstr "Másolás"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 src/apprt/gtk/ui/1.2/ccw-paste.blp:11
|
||||
msgid "Paste"
|
||||
msgstr "Beillesztés"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73
|
||||
msgid "Clear"
|
||||
msgstr "Törlés"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78
|
||||
msgid "Reset"
|
||||
msgstr "Visszaállítás"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42
|
||||
msgid "Split"
|
||||
msgstr "Felosztás"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45
|
||||
msgid "Change Title…"
|
||||
msgstr "Cím módosítása…"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59
|
||||
msgid "Tab"
|
||||
msgstr "Fül"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30
|
||||
#: src/apprt/gtk/Window.zig:265
|
||||
msgid "New Tab"
|
||||
msgstr "Új fül"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35
|
||||
msgid "Close Tab"
|
||||
msgstr "Fül bezárása"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73
|
||||
msgid "Window"
|
||||
msgstr "Ablak"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18
|
||||
msgid "New Window"
|
||||
msgstr "Új ablak"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23
|
||||
msgid "Close Window"
|
||||
msgstr "Ablak bezárása"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89
|
||||
msgid "Config"
|
||||
msgstr "Konfiguráció"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
|
||||
msgid "Open Configuration"
|
||||
msgstr "Konfiguráció megnyitása"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Parancspaletta"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
msgstr "Terminálvizsgáló"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107
|
||||
#: src/apprt/gtk/Window.zig:1038
|
||||
msgid "About Ghostty"
|
||||
msgstr "A Ghostty névjegye"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112
|
||||
msgid "Quit"
|
||||
msgstr "Kilépés"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:6
|
||||
msgid "Authorize Clipboard Access"
|
||||
msgstr "Vágólap-hozzáférés engedélyezése"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:7
|
||||
msgid ""
|
||||
"An application is attempting to read from the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr ""
|
||||
"Egy alkalmazás megpróbál olvasni a vágólapról. A vágólap jelenlegi tartalma "
|
||||
"lent látható."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:10
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:10
|
||||
msgid "Deny"
|
||||
msgstr "Elutasítás"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:11
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:11
|
||||
msgid "Allow"
|
||||
msgstr "Engedélyezés"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Választás megjegyzése erre a felosztásra"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Konfiguráció frissítése a kérdés újbóli megjelenítéséhez"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
msgid ""
|
||||
"An application is attempting to write to the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr ""
|
||||
"Egy alkalmazás megpróbál írni a vágólapra. A vágólap jelenlegi tartalma lent "
|
||||
"látható."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6
|
||||
msgid "Warning: Potentially Unsafe Paste"
|
||||
msgstr "Figyelem: potenciálisan veszélyes beillesztés"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 src/apprt/gtk/ui/1.2/ccw-paste.blp:7
|
||||
msgid ""
|
||||
"Pasting this text into the terminal may be dangerous as it looks like some "
|
||||
"commands may be executed."
|
||||
msgstr ""
|
||||
"Ennek a szövegnek a terminálba való beillesztése veszélyes lehet, mivel "
|
||||
"néhány parancs végrehajtásra kerülhet."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2531
|
||||
msgid "Close"
|
||||
msgstr "Bezárás"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:87
|
||||
msgid "Quit Ghostty?"
|
||||
msgstr "Kilép a Ghostty-ból?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:88
|
||||
msgid "Close Window?"
|
||||
msgstr "Ablak bezárása?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:89
|
||||
msgid "Close Tab?"
|
||||
msgstr "Fül bezárása?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:90
|
||||
msgid "Close Split?"
|
||||
msgstr "Felosztás bezárása?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:96
|
||||
msgid "All terminal sessions will be terminated."
|
||||
msgstr "Minden terminál munkamenet lezárul."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:97
|
||||
msgid "All terminal sessions in this window will be terminated."
|
||||
msgstr "Ebben az ablakban minden terminál munkamenet lezárul."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:98
|
||||
msgid "All terminal sessions in this tab will be terminated."
|
||||
msgstr "Ezen a fülön minden terminál munkamenet lezárul."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:99
|
||||
msgid "The currently running process in this split will be terminated."
|
||||
msgstr "Ebben a felosztásban a jelenleg futó folyamat lezárul."
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1266
|
||||
msgid "Copied to clipboard"
|
||||
msgstr "Vágólapra másolva"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Vágólap törölve"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Parancs sikeres"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Parancs sikertelen"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
msgstr "Főmenü"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:239
|
||||
msgid "View Open Tabs"
|
||||
msgstr "Megnyitott fülek megtekintése"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr "Új felosztás"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
"⚠️ You're running a debug build of Ghostty! Performance will be degraded."
|
||||
msgstr ""
|
||||
"⚠️ A Ghostty hibakereső verzióját futtatja! A teljesítmény csökkenni fog."
|
||||
|
||||
#: src/apprt/gtk/Window.zig:775
|
||||
msgid "Reloaded the configuration"
|
||||
msgstr "Konfiguráció frissítve"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:1019
|
||||
msgid "Ghostty Developers"
|
||||
msgstr "Ghostty fejlesztők"
|
||||
|
||||
#: src/apprt/gtk/inspector.zig:144
|
||||
msgid "Ghostty: Terminal Inspector"
|
||||
msgstr "Ghostty: Terminálvizsgáló"
|
@@ -2,15 +2,14 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Andrej Daskalov <andrej.daskalov@gmail.com>, 2025.
|
||||
# Marija Gjorgjieva Gjondeva <mgjorgjieva2013@gmail.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-25 22:17+0200\n"
|
||||
"Last-Translator: Marija Gjorgjieva Gjondeva <mgjorgjieva2013@gmail.com>\n"
|
||||
"PO-Revision-Date: 2025-03-23 14:17+0100\n"
|
||||
"Last-Translator: Andrej Daskalov <andrej.daskalov@gmail.com>\n"
|
||||
"Language-Team: Macedonian\n"
|
||||
"Language: mk\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -88,7 +87,7 @@ msgstr "Подели надесно"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Изврши команда…"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -161,7 +160,7 @@ msgstr "Отвори конфигурација"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Командна палета"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -209,12 +208,12 @@ msgstr "Дозволи"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Запомни го изборот за оваа поделба"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Одново вчитај конфигурација за да се повторно прикаже пораката"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -279,15 +278,15 @@ msgstr "Копирано во привремена меморија"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Исчистена привремена меморија"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Командата успеа"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Командата не успеа"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -299,7 +298,7 @@ msgstr "Прегледај отворени јазичиња"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr "Нова поделба"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Norwegian Bokmal translations for com.mitchellh.ghostty package.
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Hanna Rose <me@hanna.lol>, 2025.
|
||||
# Hanna Rose <hanna@hanna.lol>, 2025.
|
||||
# Uzair Aftab <uzaaft@outlook.com>, 2025.
|
||||
# Christoffer Tønnessen <christoffer@cto.gg>, 2025.
|
||||
# cryptocode <cryptocode@zolo.io>, 2025.
|
||||
@@ -11,8 +11,8 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-23 12:52+0000\n"
|
||||
"Last-Translator: Hanna Rose <me@hanna.lol>\n"
|
||||
"PO-Revision-Date: 2025-04-14 16:25+0200\n"
|
||||
"Last-Translator: cryptocode <cryptocode@zolo.io>\n"
|
||||
"Language-Team: Norwegian Bokmal <l10n-no@lister.huftis.org>\n"
|
||||
"Language: nb\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -90,7 +90,7 @@ msgstr "Del til høyre"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Kjør en kommando..."
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -163,7 +163,7 @@ msgstr "Åpne konfigurasjon"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Kommandopalett"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -211,12 +211,12 @@ msgstr "Tillat"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Husk valget for dette delte vinduet?"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Last inn konfigurasjonen på nytt for å vise denne meldingen igjen"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -281,15 +281,15 @@ msgstr "Kopiert til utklippstavlen"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Utklippstavle tømt"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Kommando lyktes"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Kommando mislyktes"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -3,15 +3,14 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Gustavo Peres <gsodevel@gmail.com>, 2025.
|
||||
# Guilherme Tiscoski <github@guilhermetiscoski.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-25 11:46-0500\n"
|
||||
"Last-Translator: Guilherme Tiscoski <github@guihermetiscoski.com>\n"
|
||||
"PO-Revision-Date: 2025-06-20 10:19-0300\n"
|
||||
"Last-Translator: Mário Victor Ribeiro Silva <mariovictorrs@gmail.com>\n"
|
||||
"Language-Team: Brazilian Portuguese <ldpbr-translation@lists.sourceforge."
|
||||
"net>\n"
|
||||
"Language: pt_BR\n"
|
||||
@@ -90,7 +89,7 @@ msgstr "Dividir à direita"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Executar um comando…"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -163,7 +162,7 @@ msgstr "Abrir configuração"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Paleta de comandos"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -211,12 +210,12 @@ msgstr "Permitir"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Lembrar escolha para esta divisão"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Recarregue a configuração para mostrar este aviso novamente"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -281,15 +280,15 @@ msgstr "Copiado para a área de transferência"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Área de transferência limpa"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Comando executado com sucesso"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Comando falhou"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -3,16 +3,15 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# blackzeshi <sergey_zhuzhgov@mail.ru>, 2025.
|
||||
# Ivan Bastrakov <bastaynav@proton.me>, 2025.
|
||||
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-03-24 00:01+0500\n"
|
||||
"Last-Translator: blackzeshi <sergey_zhuzhgov@mail.ru>\n"
|
||||
"Language-Team: Russian <gnu@d07.ru>\n"
|
||||
"PO-Revision-Date: 2025-09-03 01:50+0300\n"
|
||||
"Last-Translator: Ivan Bastrakov <bastaynav@proton.me>\n"
|
||||
"Language: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -90,7 +89,7 @@ msgstr "Сплит вправо"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Выполнить команду…"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -163,7 +162,7 @@ msgstr "Открыть конфигурационный файл"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Палитра команд"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -211,12 +210,12 @@ msgstr "Разрешить"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Запомнить выбор для этого сплита"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Перезагрузите конфигурацию, чтобы снова увидеть это сообщение"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -224,8 +223,7 @@ msgid ""
|
||||
"An application is attempting to write to the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr ""
|
||||
"Приложение пытается записать данные в буфер обмена. Текущее содержимое "
|
||||
"буфера обмена показано ниже."
|
||||
"Приложение пытается записать данные в буфер обмена. Эти данные показаны ниже."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6
|
||||
msgid "Warning: Potentially Unsafe Paste"
|
||||
@@ -281,15 +279,15 @@ msgstr "Скопировано в буфер обмена"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Буфер обмена очищен"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Команда выполнена успешно"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Команда завершилась с ошибкой"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -301,7 +299,7 @@ msgstr "Просмотреть открытые вкладки"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr "Новый сплит"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
|
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-23 17:30+0300\n"
|
||||
"PO-Revision-Date: 2025-03-24 22:01+0300\n"
|
||||
"Last-Translator: Emir SARI <emir_sari@icloud.com>\n"
|
||||
"Language-Team: Turkish\n"
|
||||
"Language: tr\n"
|
||||
@@ -88,7 +88,7 @@ msgstr "Sağa Doğru Böl"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Bir komut çalıştır…"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -161,7 +161,7 @@ msgstr "Yapılandırmayı Aç"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Komut Paleti"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -209,12 +209,12 @@ msgstr "İzin Ver"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Bu bölme için tercihi anımsa"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Bu istemi tekrar göstermek için yapılandırmayı yeniden yükle"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -279,15 +279,15 @@ msgstr "Panoya kopyalandı"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Pano temizlendi"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Komut başarılı oldu"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Komut başarısız oldu"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
|
@@ -2,15 +2,14 @@
|
||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||
# Danylo Zalizchuk <danilmail0110@gmail.com>, 2025.
|
||||
# Volodymyr Chernetskyi <19735328+chernetskyi@users.noreply.github.com>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||
"PO-Revision-Date: 2025-08-25 19:59+0100\n"
|
||||
"Last-Translator: Volodymyr Chernetskyi <19735328+chernetskyi@users.noreply.github.com>\n"
|
||||
"PO-Revision-Date: 2025-03-16 20:16+0200\n"
|
||||
"Last-Translator: Danylo Zalizchuk <danilmail0110@gmail.com>\n"
|
||||
"Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\n"
|
||||
"Language: uk\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -21,17 +20,17 @@ msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5
|
||||
msgid "Change Terminal Title"
|
||||
msgstr "Змінити заголовок терміналу"
|
||||
msgstr "Змінити назву терміналу"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6
|
||||
msgid "Leave blank to restore the default title."
|
||||
msgstr "Залиште порожнім, щоб відновити заголовок за замовчуванням."
|
||||
msgstr "Залиште порожнім, щоб відновити назву за замовчуванням."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/ui/1.2/ccw-paste.blp:10
|
||||
#: src/apprt/gtk/CloseDialog.zig:44
|
||||
msgid "Cancel"
|
||||
msgstr "Скасувати"
|
||||
msgstr "Відмінити"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10
|
||||
msgid "OK"
|
||||
@@ -40,7 +39,7 @@ msgstr "ОК"
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:5
|
||||
msgid "Configuration Errors"
|
||||
msgstr "Помилки налаштування"
|
||||
msgstr "Помилки конфігурації"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6
|
||||
@@ -48,8 +47,9 @@ msgid ""
|
||||
"One or more configuration errors were found. Please review the errors below, "
|
||||
"and either reload your configuration or ignore these errors."
|
||||
msgstr ""
|
||||
"Виявлено одну або декілька помилок налаштування. Будь ласка, перегляньте "
|
||||
"помилки нижче і або перезавантажте налаштування, або проігноруйте ці помилки."
|
||||
"Виявлено одну або декілька помилок у конфігурації. Будь ласка, перегляньте "
|
||||
"наведені нижче помилки і або перезавантажте конфігурацію, або проігноруйте "
|
||||
"ці помилки."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:9
|
||||
@@ -61,35 +61,35 @@ msgstr "Ігнорувати"
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100
|
||||
#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10
|
||||
msgid "Reload Configuration"
|
||||
msgstr "Перезавантажити налаштування"
|
||||
msgstr "Перезавантажити конфігурацію"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50
|
||||
msgid "Split Up"
|
||||
msgstr "Нова панель зверху"
|
||||
msgstr "Розділити панель вгору"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55
|
||||
msgid "Split Down"
|
||||
msgstr "Нова панель знизу"
|
||||
msgstr "Розділити панель вниз"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60
|
||||
msgid "Split Left"
|
||||
msgstr "Нова панель ліворуч"
|
||||
msgstr "Розділити панель ліворуч"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65
|
||||
msgid "Split Right"
|
||||
msgstr "Нова панель праворуч"
|
||||
msgstr "Розділити панель праворуч"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||
msgid "Execute a command…"
|
||||
msgstr "Виконати команду…"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||
@@ -115,7 +115,7 @@ msgstr "Скинути"
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42
|
||||
msgid "Split"
|
||||
msgstr "Панель"
|
||||
msgstr "Розділена панель"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45
|
||||
@@ -153,16 +153,16 @@ msgstr "Закрити вікно"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89
|
||||
msgid "Config"
|
||||
msgstr "Налаштування"
|
||||
msgstr "Конфігурація"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95
|
||||
msgid "Open Configuration"
|
||||
msgstr "Відкрити налаштування"
|
||||
msgstr "Відкрити конфігурацію"
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||
msgid "Command Palette"
|
||||
msgstr "Палітра команд"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||
msgid "Terminal Inspector"
|
||||
@@ -182,7 +182,7 @@ msgstr "Завершити"
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:6
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:6
|
||||
msgid "Authorize Clipboard Access"
|
||||
msgstr "Надати доступ до буфера обміну"
|
||||
msgstr "Дозволити доступ до буфера обміну"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:7
|
||||
@@ -190,15 +190,15 @@ msgid ""
|
||||
"An application is attempting to read from the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr ""
|
||||
"Програма намагається прочитати дані з буфера обміну. Нижче наведено вміст "
|
||||
"буфера обміну."
|
||||
"Програма намагається прочитати дані з буфера обміну. Нижче показано поточний "
|
||||
"вміст буфера обміну."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:10
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:10
|
||||
msgid "Deny"
|
||||
msgstr "Заборонити"
|
||||
msgstr "Відхилити"
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11
|
||||
@@ -210,12 +210,12 @@ msgstr "Дозволити"
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||
msgid "Remember choice for this split"
|
||||
msgstr "Запамʼятати для цієї панелі"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||
msgid "Reload configuration to show this prompt again"
|
||||
msgstr "Перезавантажте налаштування, щоб показати це повідомлення знову"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||
@@ -223,8 +223,8 @@ msgid ""
|
||||
"An application is attempting to write to the clipboard. The current "
|
||||
"clipboard contents are shown below."
|
||||
msgstr ""
|
||||
"Програма намагається записати дані до буфера обміну. Нижче наведено вміст "
|
||||
"буфера обміну."
|
||||
"Програма намагається записати дані до буфера обміну. Нижче показано поточний "
|
||||
"вміст буфера обміну."
|
||||
|
||||
#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6
|
||||
msgid "Warning: Potentially Unsafe Paste"
|
||||
@@ -235,8 +235,8 @@ msgid ""
|
||||
"Pasting this text into the terminal may be dangerous as it looks like some "
|
||||
"commands may be executed."
|
||||
msgstr ""
|
||||
"Вставка цього тексту в термінал може бути небезпечною, бо схоже, що деякі "
|
||||
"команди можуть бути виконані."
|
||||
"Вставка цього тексту в термінал може бути небезпечною, оскільки виглядає "
|
||||
"так, ніби деякі команди можуть бути виконані."
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:47 src/apprt/gtk/Surface.zig:2531
|
||||
msgid "Close"
|
||||
@@ -256,7 +256,7 @@ msgstr "Закрити вкладку?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:90
|
||||
msgid "Close Split?"
|
||||
msgstr "Закрити панель?"
|
||||
msgstr "Закрити розділену панель?"
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:96
|
||||
msgid "All terminal sessions will be terminated."
|
||||
@@ -272,23 +272,24 @@ msgstr "Всі сесії терміналу в цій вкладці будут
|
||||
|
||||
#: src/apprt/gtk/CloseDialog.zig:99
|
||||
msgid "The currently running process in this split will be terminated."
|
||||
msgstr "Процес, що виконується в цій панелі, буде завершено."
|
||||
msgstr ""
|
||||
"Поточний процес, що виконується в цій розділеній панелі, буде завершено."
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1266
|
||||
msgid "Copied to clipboard"
|
||||
msgstr "Скопійовано до буферa обміну"
|
||||
msgstr "Скопійовано в буфер обміну"
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:1268
|
||||
msgid "Cleared clipboard"
|
||||
msgstr "Буфер обміну очищено"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2525
|
||||
msgid "Command succeeded"
|
||||
msgstr "Команда завершилась успішно"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Surface.zig:2527
|
||||
msgid "Command failed"
|
||||
msgstr "Команда завершилась з помилкою"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:216
|
||||
msgid "Main Menu"
|
||||
@@ -300,7 +301,7 @@ msgstr "Переглянути відкриті вкладки"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:266
|
||||
msgid "New Split"
|
||||
msgstr "Нова панель"
|
||||
msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:329
|
||||
msgid ""
|
||||
@@ -310,7 +311,7 @@ msgstr ""
|
||||
|
||||
#: src/apprt/gtk/Window.zig:775
|
||||
msgid "Reloaded the configuration"
|
||||
msgstr "Налаштування перезавантажено"
|
||||
msgstr "Конфігурацію перезавантажено"
|
||||
|
||||
#: src/apprt/gtk/Window.zig:1019
|
||||
msgid "Ghostty Developers"
|
||||
|
366
src/Surface.zig
366
src/Surface.zig
@@ -247,7 +247,6 @@ const DerivedConfig = struct {
|
||||
clipboard_paste_protection: bool,
|
||||
clipboard_paste_bracketed_safe: bool,
|
||||
copy_on_select: configpkg.CopyOnSelect,
|
||||
right_click_action: configpkg.RightClickAction,
|
||||
confirm_close_surface: configpkg.ConfirmCloseSurface,
|
||||
cursor_click_to_move: bool,
|
||||
desktop_notifications: bool,
|
||||
@@ -258,7 +257,6 @@ const DerivedConfig = struct {
|
||||
mouse_shift_capture: configpkg.MouseShiftCapture,
|
||||
macos_non_native_fullscreen: configpkg.NonNativeFullscreen,
|
||||
macos_option_as_alt: ?configpkg.OptionAsAlt,
|
||||
selection_clear_on_copy: bool,
|
||||
selection_clear_on_typing: bool,
|
||||
vt_kam_allowed: bool,
|
||||
wait_after_command: bool,
|
||||
@@ -273,7 +271,6 @@ const DerivedConfig = struct {
|
||||
title_report: bool,
|
||||
links: []Link,
|
||||
link_previews: configpkg.LinkPreviews,
|
||||
scroll_to_bottom: configpkg.Config.ScrollToBottom,
|
||||
|
||||
const Link = struct {
|
||||
regex: oni.Regex,
|
||||
@@ -317,7 +314,6 @@ const DerivedConfig = struct {
|
||||
.clipboard_paste_protection = config.@"clipboard-paste-protection",
|
||||
.clipboard_paste_bracketed_safe = config.@"clipboard-paste-bracketed-safe",
|
||||
.copy_on_select = config.@"copy-on-select",
|
||||
.right_click_action = config.@"right-click-action",
|
||||
.confirm_close_surface = config.@"confirm-close-surface",
|
||||
.cursor_click_to_move = config.@"cursor-click-to-move",
|
||||
.desktop_notifications = config.@"desktop-notifications",
|
||||
@@ -328,7 +324,6 @@ const DerivedConfig = struct {
|
||||
.mouse_shift_capture = config.@"mouse-shift-capture",
|
||||
.macos_non_native_fullscreen = config.@"macos-non-native-fullscreen",
|
||||
.macos_option_as_alt = config.@"macos-option-as-alt",
|
||||
.selection_clear_on_copy = config.@"selection-clear-on-copy",
|
||||
.selection_clear_on_typing = config.@"selection-clear-on-typing",
|
||||
.vt_kam_allowed = config.@"vt-kam-allowed",
|
||||
.wait_after_command = config.@"wait-after-command",
|
||||
@@ -343,7 +338,6 @@ const DerivedConfig = struct {
|
||||
.title_report = config.@"title-report",
|
||||
.links = links,
|
||||
.link_previews = config.@"link-previews",
|
||||
.scroll_to_bottom = config.@"scroll-to-bottom",
|
||||
|
||||
// Assignments happen sequentially so we have to do this last
|
||||
// so that the memory is captured from allocs above.
|
||||
@@ -1535,6 +1529,11 @@ pub const Text = struct {
|
||||
|
||||
/// The viewport information about this text, if it is visible in
|
||||
/// the viewport.
|
||||
///
|
||||
/// NOTE(mitchellh): This will only be non-null currently if the entirety
|
||||
/// of the selection is contained within the viewport. We don't have a
|
||||
/// use case currently for partial bounds but we should support this
|
||||
/// eventually.
|
||||
viewport: ?Viewport = null,
|
||||
|
||||
pub const Viewport = struct {
|
||||
@@ -1545,13 +1544,6 @@ pub const Text = struct {
|
||||
/// The linear offset of the start of the selection and the length.
|
||||
/// This is "linear" in the sense that it is the offset in the
|
||||
/// flattened viewport as a single array of text.
|
||||
///
|
||||
/// Note: these values are currently wrong if there is a partially
|
||||
/// visible selection in the viewport (i.e. the top-left or
|
||||
/// bottom-right of the selection is outside the viewport). But the
|
||||
/// apprt usecase we have right now doesn't require these to be
|
||||
/// correct so... let's fix this later. The wrong values will always
|
||||
/// be within the text bounds so we aren't risking an overflow.
|
||||
offset_start: u32,
|
||||
offset_len: u32,
|
||||
};
|
||||
@@ -1593,57 +1585,17 @@ pub fn dumpTextLocked(
|
||||
|
||||
// Calculate our viewport info if we can.
|
||||
const vp: ?Text.Viewport = viewport: {
|
||||
// If our bottom right pin is before the viewport, then we can't
|
||||
// possibly have this text be within the viewport.
|
||||
const vp_tl_pin = self.io.terminal.screen.pages.getTopLeft(.viewport);
|
||||
const br_pin = sel.bottomRight(&self.io.terminal.screen);
|
||||
if (br_pin.before(vp_tl_pin)) break :viewport null;
|
||||
|
||||
// If our top-left pin is after the viewport, then we can't possibly
|
||||
// have this text be within the viewport.
|
||||
const vp_br_pin = self.io.terminal.screen.pages.getBottomRight(.viewport) orelse {
|
||||
// I don't think this is possible but I don't want to crash on
|
||||
// that assertion so let's just break out...
|
||||
log.warn("viewport bottom-right pin not found, bug?", .{});
|
||||
break :viewport null;
|
||||
};
|
||||
const tl_pin = sel.topLeft(&self.io.terminal.screen);
|
||||
if (vp_br_pin.before(tl_pin)) break :viewport null;
|
||||
|
||||
// We established that our top-left somewhere before the viewport
|
||||
// bottom-right and that our bottom-right is somewhere after
|
||||
// the top-left. This means that at least some portion of our
|
||||
// selection is within the viewport.
|
||||
|
||||
// Our top-left point. If it doesn't exist in the viewport it must
|
||||
// be before and we can return (0,0).
|
||||
const tl_pt: terminal.Point = self.io.terminal.screen.pages.pointFromPin(
|
||||
// If our tl or br is not in the viewport then we don't
|
||||
// have a viewport. One day we should extend this to support
|
||||
// partial selections that are in the viewport.
|
||||
const tl_pt = self.io.terminal.screen.pages.pointFromPin(
|
||||
.viewport,
|
||||
tl_pin,
|
||||
) orelse tl: {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
assert(tl_pin.before(vp_tl_pin));
|
||||
}
|
||||
|
||||
break :tl .{ .viewport = .{} };
|
||||
};
|
||||
|
||||
// Our bottom-right point. If it doesn't exist in the viewport
|
||||
// it must be the bottom-right of the viewport.
|
||||
sel.topLeft(&self.io.terminal.screen),
|
||||
) orelse break :viewport null;
|
||||
const br_pt = self.io.terminal.screen.pages.pointFromPin(
|
||||
.viewport,
|
||||
br_pin,
|
||||
) orelse br: {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
assert(vp_br_pin.before(br_pin));
|
||||
}
|
||||
|
||||
break :br self.io.terminal.screen.pages.pointFromPin(
|
||||
.viewport,
|
||||
vp_br_pin,
|
||||
).?;
|
||||
};
|
||||
|
||||
sel.bottomRight(&self.io.terminal.screen),
|
||||
) orelse break :viewport null;
|
||||
const tl_coord = tl_pt.coord();
|
||||
const br_coord = br_pt.coord();
|
||||
|
||||
@@ -1714,6 +1666,73 @@ pub fn selectionString(self: *Surface, alloc: Allocator) !?[:0]const u8 {
|
||||
});
|
||||
}
|
||||
|
||||
/// Return the apprt selection metadata used by apprt's for implementing
|
||||
/// things like contextual information on right click and so on.
|
||||
///
|
||||
/// This only returns non-null if the selection is fully contained within
|
||||
/// the viewport. The use case for this function at the time of authoring
|
||||
/// it is for apprt's to implement right-click contextual menus and
|
||||
/// those only make sense for selections fully contained within the
|
||||
/// viewport. We don't handle the case where you right click a word-wrapped
|
||||
/// word at the end of the viewport yet.
|
||||
pub fn selectionInfo(self: *const Surface) ?apprt.Selection {
|
||||
self.renderer_state.mutex.lock();
|
||||
defer self.renderer_state.mutex.unlock();
|
||||
const sel = self.io.terminal.screen.selection orelse return null;
|
||||
|
||||
// Get the TL/BR pins for the selection and convert to viewport.
|
||||
const tl = sel.topLeft(&self.io.terminal.screen);
|
||||
const br = sel.bottomRight(&self.io.terminal.screen);
|
||||
const tl_pt = self.io.terminal.screen.pages.pointFromPin(.viewport, tl) orelse return null;
|
||||
const br_pt = self.io.terminal.screen.pages.pointFromPin(.viewport, br) orelse return null;
|
||||
const tl_coord = tl_pt.coord();
|
||||
const br_coord = br_pt.coord();
|
||||
|
||||
// Utilize viewport sizing to convert to offsets
|
||||
const start = tl_coord.y * self.io.terminal.screen.pages.cols + tl_coord.x;
|
||||
const end = br_coord.y * self.io.terminal.screen.pages.cols + br_coord.x;
|
||||
|
||||
// Our sizes are all scaled so we need to send the unscaled values back.
|
||||
const content_scale = self.rt_surface.getContentScale() catch .{ .x = 1, .y = 1 };
|
||||
|
||||
const x: f64 = x: {
|
||||
// Simple x * cell width gives the left
|
||||
var x: f64 = @floatFromInt(tl_coord.x * self.size.cell.width);
|
||||
|
||||
// Add padding
|
||||
x += @floatFromInt(self.size.padding.left);
|
||||
|
||||
// Scale
|
||||
x /= content_scale.x;
|
||||
|
||||
break :x x;
|
||||
};
|
||||
|
||||
const y: f64 = y: {
|
||||
// Simple y * cell height gives the top
|
||||
var y: f64 = @floatFromInt(tl_coord.y * self.size.cell.height);
|
||||
|
||||
// We want the text baseline
|
||||
y += @floatFromInt(self.size.cell.height);
|
||||
y -= @floatFromInt(self.font_metrics.cell_baseline);
|
||||
|
||||
// Add padding
|
||||
y += @floatFromInt(self.size.padding.top);
|
||||
|
||||
// Scale
|
||||
y /= content_scale.y;
|
||||
|
||||
break :y y;
|
||||
};
|
||||
|
||||
return .{
|
||||
.tl_x_px = x,
|
||||
.tl_y_px = y,
|
||||
.offset_start = start,
|
||||
.offset_len = end - start,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the pwd of the terminal, if any. This is always copied because
|
||||
/// the pwd can change at any point from termio. If we are calling from the IO
|
||||
/// thread you should just check the terminal directly.
|
||||
@@ -1732,7 +1751,6 @@ pub fn pwd(
|
||||
pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
||||
self.renderer_state.mutex.lock();
|
||||
const cursor = self.renderer_state.terminal.screen.cursor;
|
||||
const preedit_width: usize = if (self.renderer_state.preedit) |preedit| preedit.width() else 0;
|
||||
self.renderer_state.mutex.unlock();
|
||||
|
||||
// TODO: need to handle when scrolling and the cursor is not
|
||||
@@ -1767,38 +1785,7 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos {
|
||||
break :y y;
|
||||
};
|
||||
|
||||
// Our height for now is always just the cell height because our preedit
|
||||
// rendering only renders in a single line.
|
||||
const height: f64 = height: {
|
||||
var height: f64 = @floatFromInt(self.size.cell.height);
|
||||
height /= content_scale.y;
|
||||
break :height height;
|
||||
};
|
||||
const width: f64 = width: {
|
||||
var width: f64 = @floatFromInt(preedit_width * self.size.cell.width);
|
||||
|
||||
// Our max width is the remaining screen width after the cursor.
|
||||
// We don't have to deal with wrapping because the preedit doesn't
|
||||
// wrap right now.
|
||||
const screen_width: f64 = @floatFromInt(self.size.terminal().width);
|
||||
const x_offset: f64 = @floatFromInt((cursor.x + 1) * self.size.cell.width);
|
||||
const max = screen_width - x_offset;
|
||||
width = @min(width, max);
|
||||
|
||||
// Note: we don't apply content scale here because it looks like
|
||||
// for some reason in macOS its already scaled. I'm not sure why
|
||||
// that is so I'm going to just leave this comment here so its known
|
||||
// that I left this out on purpose pending more investigation.
|
||||
|
||||
break :width width;
|
||||
};
|
||||
|
||||
return .{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
return .{ .x = x, .y = y };
|
||||
}
|
||||
|
||||
fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard) !void {
|
||||
@@ -1846,32 +1833,6 @@ fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard)
|
||||
};
|
||||
}
|
||||
|
||||
fn copySelectionToClipboards(
|
||||
self: *Surface,
|
||||
sel: terminal.Selection,
|
||||
clipboards: []const apprt.Clipboard,
|
||||
) void {
|
||||
const buf = self.io.terminal.screen.selectionString(self.alloc, .{
|
||||
.sel = sel,
|
||||
.trim = self.config.clipboard_trim_trailing_spaces,
|
||||
}) catch |err| {
|
||||
log.err("error reading selection string err={}", .{err});
|
||||
return;
|
||||
};
|
||||
defer self.alloc.free(buf);
|
||||
|
||||
for (clipboards) |clipboard| self.rt_surface.setClipboardString(
|
||||
buf,
|
||||
clipboard,
|
||||
false,
|
||||
) catch |err| {
|
||||
log.err(
|
||||
"error setting clipboard string clipboard={} err={}",
|
||||
.{ clipboard, err },
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/// Set the selection contents.
|
||||
///
|
||||
/// This must be called with the renderer mutex held.
|
||||
@@ -1889,12 +1850,33 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void {
|
||||
const sel = sel_ orelse return;
|
||||
if (prev_) |prev| if (sel.eql(prev)) return;
|
||||
|
||||
const buf = self.io.terminal.screen.selectionString(self.alloc, .{
|
||||
.sel = sel,
|
||||
.trim = self.config.clipboard_trim_trailing_spaces,
|
||||
}) catch |err| {
|
||||
log.err("error reading selection string err={}", .{err});
|
||||
return;
|
||||
};
|
||||
defer self.alloc.free(buf);
|
||||
|
||||
// Set the clipboard. This is not super DRY but it is clear what
|
||||
// we're doing for each setting without being clever.
|
||||
switch (self.config.copy_on_select) {
|
||||
.false => unreachable, // handled above with an early exit
|
||||
|
||||
// Both standard and selection clipboards are set.
|
||||
.clipboard => {
|
||||
self.copySelectionToClipboards(sel, &.{ .standard, .selection });
|
||||
const clipboards: []const apprt.Clipboard = &.{ .standard, .selection };
|
||||
for (clipboards) |clipboard| self.rt_surface.setClipboardString(
|
||||
buf,
|
||||
clipboard,
|
||||
false,
|
||||
) catch |err| {
|
||||
log.err(
|
||||
"error setting clipboard string clipboard={} err={}",
|
||||
.{ clipboard, err },
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
// The selection clipboard is set if supported, otherwise the standard.
|
||||
@@ -1903,7 +1885,17 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void {
|
||||
.selection
|
||||
else
|
||||
.standard;
|
||||
self.copySelectionToClipboards(sel, &.{clipboard});
|
||||
|
||||
self.rt_surface.setClipboardString(
|
||||
buf,
|
||||
clipboard,
|
||||
false,
|
||||
) catch |err| {
|
||||
log.err(
|
||||
"error setting clipboard string clipboard={} err={}",
|
||||
.{ clipboard, err },
|
||||
);
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2316,8 +2308,7 @@ pub fn keyCallback(
|
||||
try self.setSelection(null);
|
||||
}
|
||||
|
||||
if (self.config.scroll_to_bottom.keystroke) try self.io.terminal.scrollViewport(.bottom);
|
||||
|
||||
try self.io.terminal.scrollViewport(.{ .bottom = {} });
|
||||
try self.queueRender();
|
||||
}
|
||||
|
||||
@@ -2803,21 +2794,8 @@ pub fn scrollCallback(
|
||||
// that a wheel tick of 1 results in single scroll event.
|
||||
const yoff_adjusted: f64 = if (scroll_mods.precision)
|
||||
yoff
|
||||
else yoff_adjusted: {
|
||||
// Round out the yoff to an absolute minimum of 1. macos tries to
|
||||
// simulate precision scrolling with non precision events by
|
||||
// ramping up the magnitude of the offsets as it detects faster
|
||||
// scrolling. Single click (very slow) scrolls are reported with a
|
||||
// magnitude of 0.1 which would normally require a few clicks
|
||||
// before we register an actual scroll event (depending on cell
|
||||
// height and the mouse_scroll_multiplier setting).
|
||||
const yoff_max: f64 = if (yoff > 0)
|
||||
@max(yoff, 1)
|
||||
else
|
||||
@min(yoff, -1);
|
||||
|
||||
break :yoff_adjusted yoff_max * cell_size * self.config.mouse_scroll_multiplier;
|
||||
};
|
||||
else
|
||||
yoff * cell_size * self.config.mouse_scroll_multiplier;
|
||||
|
||||
// Add our previously saved pending amount to the offset to get the
|
||||
// new offset value. The signs of the pending and yoff should match
|
||||
@@ -3604,63 +3582,18 @@ pub fn mouseButtonCallback(
|
||||
break :pin pin;
|
||||
};
|
||||
|
||||
switch (self.config.right_click_action) {
|
||||
.ignore => {},
|
||||
.@"context-menu" => {
|
||||
// If we already have a selection and the selection contains
|
||||
// where we clicked then we don't want to modify the selection.
|
||||
if (self.io.terminal.screen.selection) |prev_sel| {
|
||||
if (prev_sel.contains(screen, pin)) break :sel;
|
||||
// If we already have a selection and the selection contains
|
||||
// where we clicked then we don't want to modify the selection.
|
||||
if (self.io.terminal.screen.selection) |prev_sel| {
|
||||
if (prev_sel.contains(screen, pin)) break :sel;
|
||||
|
||||
// The selection doesn't contain our pin, so we create a new
|
||||
// word selection where we clicked.
|
||||
}
|
||||
|
||||
const sel = screen.selectWord(pin) orelse break :sel;
|
||||
try self.setSelection(sel);
|
||||
try self.queueRender();
|
||||
|
||||
// Don't consume so that we show the context menu in apprt.
|
||||
return false;
|
||||
},
|
||||
.copy => {
|
||||
if (self.io.terminal.screen.selection) |sel| {
|
||||
self.copySelectionToClipboards(sel, &.{.standard});
|
||||
}
|
||||
|
||||
try self.setSelection(null);
|
||||
try self.queueRender();
|
||||
},
|
||||
.@"copy-or-paste" => if (self.io.terminal.screen.selection) |sel| {
|
||||
self.copySelectionToClipboards(sel, &.{.standard});
|
||||
try self.setSelection(null);
|
||||
try self.queueRender();
|
||||
} else {
|
||||
// Pasting can trigger a lock grab in complete clipboard
|
||||
// request so we need to unlock.
|
||||
self.renderer_state.mutex.unlock();
|
||||
defer self.renderer_state.mutex.lock();
|
||||
try self.startClipboardRequest(.standard, .paste);
|
||||
|
||||
// We don't need to clear selection because we didn't have
|
||||
// one to begin with.
|
||||
},
|
||||
.paste => {
|
||||
// Before we yield the lock, clear our selection if we have
|
||||
// one.
|
||||
try self.setSelection(null);
|
||||
try self.queueRender();
|
||||
|
||||
// Pasting can trigger a lock grab in complete clipboard
|
||||
// request so we need to unlock.
|
||||
self.renderer_state.mutex.unlock();
|
||||
defer self.renderer_state.mutex.lock();
|
||||
try self.startClipboardRequest(.standard, .paste);
|
||||
},
|
||||
// The selection doesn't contain our pin, so we create a new
|
||||
// word selection where we clicked.
|
||||
}
|
||||
|
||||
// Consume the event such that the context menu is not displayed.
|
||||
return true;
|
||||
const sel = screen.selectWord(pin) orelse break :sel;
|
||||
try self.setSelection(sel);
|
||||
try self.queueRender();
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -4546,17 +4479,6 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
return true;
|
||||
};
|
||||
|
||||
// Clear the selection if configured to do so.
|
||||
if (self.config.selection_clear_on_copy) {
|
||||
if (self.setSelection(null)) {
|
||||
self.queueRender() catch |err| {
|
||||
log.warn("failed to queue render after clear selection err={}", .{err});
|
||||
};
|
||||
} else |err| {
|
||||
log.warn("failed to clear selection after copy err={}", .{err});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4569,32 +4491,19 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
|
||||
const pos = try self.rt_surface.getCursorPos();
|
||||
if (try self.linkAtPos(pos)) |link_info| {
|
||||
const url_text = switch (link_info[0]) {
|
||||
.open => url_text: {
|
||||
// For regex links, get the text from selection
|
||||
break :url_text (self.io.terminal.screen.selectionString(self.alloc, .{
|
||||
.sel = link_info[1],
|
||||
.trim = self.config.clipboard_trim_trailing_spaces,
|
||||
})) catch |err| {
|
||||
log.err("error reading url string err={}", .{err});
|
||||
return false;
|
||||
};
|
||||
},
|
||||
|
||||
._open_osc8 => url_text: {
|
||||
// For OSC8 links, get the URI directly from hyperlink data
|
||||
const uri = self.osc8URI(link_info[1].start()) orelse {
|
||||
log.warn("failed to get URI for OSC8 hyperlink", .{});
|
||||
return false;
|
||||
};
|
||||
break :url_text try self.alloc.dupeZ(u8, uri);
|
||||
},
|
||||
// Get the URL text from selection
|
||||
const url_text = (self.io.terminal.screen.selectionString(self.alloc, .{
|
||||
.sel = link_info[1],
|
||||
.trim = self.config.clipboard_trim_trailing_spaces,
|
||||
})) catch |err| {
|
||||
log.err("error reading url string err={}", .{err});
|
||||
return false;
|
||||
};
|
||||
defer self.alloc.free(url_text);
|
||||
|
||||
self.rt_surface.setClipboardString(url_text, .standard, false) catch |err| {
|
||||
log.err("error copying url to clipboard err={}", .{err});
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
return true;
|
||||
@@ -4762,13 +4671,10 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
{},
|
||||
),
|
||||
|
||||
.close_tab => |v| return try self.rt_app.performAction(
|
||||
.close_tab => return try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.close_tab,
|
||||
switch (v) {
|
||||
.this => .this,
|
||||
.other => .other,
|
||||
},
|
||||
{},
|
||||
),
|
||||
|
||||
inline .previous_tab,
|
||||
|
@@ -17,6 +17,7 @@ const structs = @import("apprt/structs.zig");
|
||||
pub const action = @import("apprt/action.zig");
|
||||
pub const ipc = @import("apprt/ipc.zig");
|
||||
pub const gtk = @import("apprt/gtk.zig");
|
||||
pub const gtk_ng = @import("apprt/gtk-ng.zig");
|
||||
pub const none = @import("apprt/none.zig");
|
||||
pub const browser = @import("apprt/browser.zig");
|
||||
pub const embedded = @import("apprt/embedded.zig");
|
||||
@@ -43,6 +44,7 @@ pub const runtime = switch (build_config.artifact) {
|
||||
.exe => switch (build_config.app_runtime) {
|
||||
.none => none,
|
||||
.gtk => gtk,
|
||||
.@"gtk-ng" => gtk_ng,
|
||||
},
|
||||
.lib => embedded,
|
||||
.wasm_module => browser,
|
||||
@@ -60,18 +62,21 @@ pub const Runtime = enum {
|
||||
|
||||
/// GTK4. Rich windowed application. This uses a full GObject-based
|
||||
/// approach to building the application.
|
||||
@"gtk-ng",
|
||||
|
||||
/// GTK-backed. Rich windowed application. GTK is dynamically linked.
|
||||
/// WARNING: Deprecated. This will be removed very soon. All bug fixes
|
||||
/// and features should go into the gtk-ng backend.
|
||||
gtk,
|
||||
|
||||
pub fn default(target: std.Target) Runtime {
|
||||
return switch (target.os.tag) {
|
||||
// The Linux and FreeBSD default is GTK because it is a full
|
||||
// featured application.
|
||||
.linux, .freebsd => .gtk,
|
||||
// Otherwise, we do NONE so we don't create an exe and we create
|
||||
// libghostty. On macOS, Xcode is used to build the app that links
|
||||
// to libghostty.
|
||||
else => .none,
|
||||
};
|
||||
// The Linux default is GTK because it is a full featured application.
|
||||
if (target.os.tag == .linux) return .@"gtk-ng";
|
||||
|
||||
// Otherwise, we do NONE so we don't create an exe and we
|
||||
// create libghostty. On macOS, Xcode is used to build the app
|
||||
// that links to libghostty.
|
||||
return .none;
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -83,9 +83,8 @@ pub const Action = union(Key) {
|
||||
/// the tab should be opened in a new window.
|
||||
new_tab,
|
||||
|
||||
/// Closes the tab belonging to the currently focused split, or all other
|
||||
/// tabs, depending on the mode.
|
||||
close_tab: CloseTabMode,
|
||||
/// Closes the tab belonging to the currently focused split.
|
||||
close_tab,
|
||||
|
||||
/// Create a new split. The value determines the location of the split
|
||||
/// relative to the target.
|
||||
@@ -542,7 +541,7 @@ pub const InitialSize = extern struct {
|
||||
|
||||
/// Make this a valid gobject if we're in a GTK environment.
|
||||
pub const getGObjectType = switch (build_config.app_runtime) {
|
||||
.gtk => @import("gobject").ext.defineBoxed(
|
||||
.gtk, .@"gtk-ng" => @import("gobject").ext.defineBoxed(
|
||||
InitialSize,
|
||||
.{ .name = "GhosttyApprtInitialSize" },
|
||||
),
|
||||
@@ -702,11 +701,3 @@ pub const OpenUrl = struct {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// sync with ghostty_action_close_tab_mode_e in ghostty.h
|
||||
pub const CloseTabMode = enum(c_int) {
|
||||
/// Close the current tab.
|
||||
this,
|
||||
/// Close all other tabs.
|
||||
other,
|
||||
};
|
||||
|
@@ -447,9 +447,6 @@ pub const Surface = struct {
|
||||
|
||||
/// Input to send to the command after it is started.
|
||||
initial_input: ?[*:0]const u8 = null,
|
||||
|
||||
/// Wait after the command exits
|
||||
wait_after_command: bool = false,
|
||||
};
|
||||
|
||||
pub fn init(self: *Surface, app: *App, opts: Options) !void {
|
||||
@@ -543,11 +540,6 @@ pub const Surface = struct {
|
||||
);
|
||||
}
|
||||
|
||||
// Wait after command
|
||||
if (opts.wait_after_command) {
|
||||
config.@"wait-after-command" = true;
|
||||
}
|
||||
|
||||
// Initialize our surface right away. We're given a view that is
|
||||
// ready to use.
|
||||
try self.core_surface.init(
|
||||
@@ -909,7 +901,10 @@ pub const Surface = struct {
|
||||
// our translation settings for Ghostty. If we aren't from
|
||||
// the desktop then we didn't set our LANGUAGE var so we
|
||||
// don't need to remove it.
|
||||
if (internal_os.launchedFromDesktop()) env.remove("LANGUAGE");
|
||||
switch (self.app.config.@"launched-from".?) {
|
||||
.desktop => env.remove("LANGUAGE"),
|
||||
.dbus, .systemd, .cli => {},
|
||||
}
|
||||
}
|
||||
|
||||
return env;
|
||||
@@ -1819,18 +1814,10 @@ pub const CAPI = struct {
|
||||
surface.mousePressureCallback(stage, pressure);
|
||||
}
|
||||
|
||||
export fn ghostty_surface_ime_point(
|
||||
surface: *Surface,
|
||||
x: *f64,
|
||||
y: *f64,
|
||||
width: *f64,
|
||||
height: *f64,
|
||||
) void {
|
||||
export fn ghostty_surface_ime_point(surface: *Surface, x: *f64, y: *f64) void {
|
||||
const pos = surface.core_surface.imePoint();
|
||||
x.* = pos.x;
|
||||
y.* = pos.y;
|
||||
width.* = pos.width;
|
||||
height.* = pos.height;
|
||||
}
|
||||
|
||||
/// Request that the surface become closed. This will go through the
|
||||
|
15
src/apprt/gtk-ng.zig
Normal file
15
src/apprt/gtk-ng.zig
Normal file
@@ -0,0 +1,15 @@
|
||||
const internal_os = @import("../os/main.zig");
|
||||
|
||||
// The required comptime API for any apprt.
|
||||
pub const App = @import("gtk-ng/App.zig");
|
||||
pub const Surface = @import("gtk-ng/Surface.zig");
|
||||
pub const resourcesDir = internal_os.resourcesDir;
|
||||
|
||||
// The exported API, custom for the apprt.
|
||||
pub const class = @import("gtk-ng/class.zig");
|
||||
pub const WeakRef = @import("gtk-ng/weak_ref.zig").WeakRef;
|
||||
|
||||
test {
|
||||
@import("std").testing.refAllDecls(@This());
|
||||
_ = @import("gtk-ng/ext.zig");
|
||||
}
|
104
src/apprt/gtk-ng/App.zig
Normal file
104
src/apprt/gtk-ng/App.zig
Normal file
@@ -0,0 +1,104 @@
|
||||
/// This is the main entrypoint to the apprt for Ghostty. Ghostty will
|
||||
/// initialize this in main to start the application..
|
||||
const App = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const adw = @import("adw");
|
||||
const gio = @import("gio");
|
||||
const apprt = @import("../../apprt.zig");
|
||||
const configpkg = @import("../../config.zig");
|
||||
const internal_os = @import("../../os/main.zig");
|
||||
const Config = configpkg.Config;
|
||||
const CoreApp = @import("../../App.zig");
|
||||
|
||||
const Application = @import("class/application.zig").Application;
|
||||
const Surface = @import("Surface.zig");
|
||||
const gtk_version = @import("gtk_version.zig");
|
||||
const adw_version = @import("adw_version.zig");
|
||||
const ipcNewWindow = @import("ipc/new_window.zig").newWindow;
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
/// This is detected by the Renderer, in which case it sends a `redraw_surface`
|
||||
/// message so that we can call `drawFrame` ourselves from the app thread,
|
||||
/// because GTK's `GLArea` does not support drawing from a different thread.
|
||||
pub const must_draw_from_app_thread = true;
|
||||
|
||||
/// GTK application ID
|
||||
pub const application_id = switch (builtin.mode) {
|
||||
.Debug, .ReleaseSafe => "com.mitchellh.ghostty-debug",
|
||||
.ReleaseFast, .ReleaseSmall => "com.mitchellh.ghostty",
|
||||
};
|
||||
|
||||
/// GTK object path
|
||||
pub const object_path = switch (builtin.mode) {
|
||||
.Debug, .ReleaseSafe => "/com/mitchellh/ghostty_debug",
|
||||
.ReleaseFast, .ReleaseSmall => "/com/mitchellh/ghostty",
|
||||
};
|
||||
|
||||
/// The GObject Application instance
|
||||
app: *Application,
|
||||
|
||||
pub fn init(
|
||||
self: *App,
|
||||
core_app: *CoreApp,
|
||||
|
||||
// Required by the apprt interface but we don't use it.
|
||||
opts: struct {},
|
||||
) !void {
|
||||
_ = opts;
|
||||
|
||||
const app: *Application = try .new(self, core_app);
|
||||
errdefer app.unref();
|
||||
self.* = .{ .app = app };
|
||||
return;
|
||||
}
|
||||
|
||||
pub fn run(self: *App) !void {
|
||||
try self.app.run();
|
||||
}
|
||||
|
||||
pub fn terminate(self: *App) void {
|
||||
// We force deinitialize the app. We don't unref because other things
|
||||
// tend to have a reference at this point, so this just forces the
|
||||
// disposal now.
|
||||
self.app.deinit();
|
||||
}
|
||||
|
||||
/// Called by CoreApp to wake up the event loop.
|
||||
pub fn wakeup(self: *App) void {
|
||||
self.app.wakeup();
|
||||
}
|
||||
|
||||
pub fn performAction(
|
||||
self: *App,
|
||||
target: apprt.Target,
|
||||
comptime action: apprt.Action.Key,
|
||||
value: apprt.Action.Value(action),
|
||||
) !bool {
|
||||
return try self.app.performAction(target, action, value);
|
||||
}
|
||||
|
||||
/// Send the given IPC to a running Ghostty. Returns `true` if the action was
|
||||
/// able to be performed, `false` otherwise.
|
||||
///
|
||||
/// Note that this is a static function. Since this is called from a CLI app (or
|
||||
/// some other process that is not Ghostty) there is no full-featured apprt App
|
||||
/// to use.
|
||||
pub fn performIpc(
|
||||
alloc: Allocator,
|
||||
target: apprt.ipc.Target,
|
||||
comptime action: apprt.ipc.Action.Key,
|
||||
value: apprt.ipc.Action.Value(action),
|
||||
) !bool {
|
||||
switch (action) {
|
||||
.new_window => return try ipcNewWindow(alloc, target, value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Redraw the inspector for the given surface.
|
||||
pub fn redrawInspector(_: *App, surface: *Surface) void {
|
||||
surface.redrawInspector();
|
||||
}
|
103
src/apprt/gtk-ng/Surface.zig
Normal file
103
src/apprt/gtk-ng/Surface.zig
Normal file
@@ -0,0 +1,103 @@
|
||||
const Self = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const apprt = @import("../../apprt.zig");
|
||||
const CoreSurface = @import("../../Surface.zig");
|
||||
const ApprtApp = @import("App.zig");
|
||||
const Application = @import("class/application.zig").Application;
|
||||
const Surface = @import("class/surface.zig").Surface;
|
||||
|
||||
/// The GObject Surface
|
||||
surface: *Surface,
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
/// Returns the GObject surface for this apprt surface. This is a function
|
||||
/// so we can add some extra logic if we ever have to here.
|
||||
pub fn gobj(self: *Self) *Surface {
|
||||
return self.surface;
|
||||
}
|
||||
|
||||
pub fn core(self: *Self) *CoreSurface {
|
||||
// This asserts the non-optional because libghostty should only
|
||||
// be calling this for initialized surfaces.
|
||||
return self.surface.core().?;
|
||||
}
|
||||
|
||||
pub fn rtApp(self: *Self) *ApprtApp {
|
||||
_ = self;
|
||||
return Application.default().rt();
|
||||
}
|
||||
|
||||
pub fn close(self: *Self, process_active: bool) void {
|
||||
_ = process_active;
|
||||
self.surface.close();
|
||||
}
|
||||
|
||||
pub fn cgroup(self: *Self) ?[]const u8 {
|
||||
return self.surface.cgroupPath();
|
||||
}
|
||||
|
||||
pub fn getTitle(self: *Self) ?[:0]const u8 {
|
||||
return self.surface.getTitle();
|
||||
}
|
||||
|
||||
pub fn getContentScale(self: *const Self) !apprt.ContentScale {
|
||||
return self.surface.getContentScale();
|
||||
}
|
||||
|
||||
pub fn getSize(self: *const Self) !apprt.SurfaceSize {
|
||||
return self.surface.getSize();
|
||||
}
|
||||
|
||||
pub fn getCursorPos(self: *const Self) !apprt.CursorPos {
|
||||
return self.surface.getCursorPos();
|
||||
}
|
||||
|
||||
pub fn supportsClipboard(
|
||||
self: *const Self,
|
||||
clipboard_type: apprt.Clipboard,
|
||||
) bool {
|
||||
_ = self;
|
||||
return switch (clipboard_type) {
|
||||
.standard,
|
||||
.selection,
|
||||
.primary,
|
||||
=> true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn clipboardRequest(
|
||||
self: *Self,
|
||||
clipboard_type: apprt.Clipboard,
|
||||
state: apprt.ClipboardRequest,
|
||||
) !void {
|
||||
try self.surface.clipboardRequest(
|
||||
clipboard_type,
|
||||
state,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn setClipboardString(
|
||||
self: *Self,
|
||||
val: [:0]const u8,
|
||||
clipboard_type: apprt.Clipboard,
|
||||
confirm: bool,
|
||||
) !void {
|
||||
self.surface.setClipboardString(
|
||||
val,
|
||||
clipboard_type,
|
||||
confirm,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap {
|
||||
return try self.surface.defaultTermioEnv();
|
||||
}
|
||||
|
||||
/// Redraw the inspector for our surface.
|
||||
pub fn redrawInspector(self: *Self) void {
|
||||
self.surface.redrawInspector();
|
||||
}
|
122
src/apprt/gtk-ng/adw_version.zig
Normal file
122
src/apprt/gtk-ng/adw_version.zig
Normal file
@@ -0,0 +1,122 @@
|
||||
const std = @import("std");
|
||||
|
||||
// Until the gobject bindings are built at the same time we are building
|
||||
// Ghostty, we need to import `adwaita.h` directly to ensure that the version
|
||||
// macros match the version of `libadwaita` that we are building/linking
|
||||
// against.
|
||||
const c = @cImport({
|
||||
@cInclude("adwaita.h");
|
||||
});
|
||||
|
||||
const adw = @import("adw");
|
||||
|
||||
const log = std.log.scoped(.gtk);
|
||||
|
||||
pub const comptime_version: std.SemanticVersion = .{
|
||||
.major = c.ADW_MAJOR_VERSION,
|
||||
.minor = c.ADW_MINOR_VERSION,
|
||||
.patch = c.ADW_MICRO_VERSION,
|
||||
};
|
||||
|
||||
pub fn getRuntimeVersion() std.SemanticVersion {
|
||||
return .{
|
||||
.major = adw.getMajorVersion(),
|
||||
.minor = adw.getMinorVersion(),
|
||||
.patch = adw.getMicroVersion(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn logVersion() void {
|
||||
log.info("libadwaita version build={} runtime={}", .{
|
||||
comptime_version,
|
||||
getRuntimeVersion(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Verifies that the running libadwaita version is at least the given
|
||||
/// version. This will return false if Ghostty is configured to not build with
|
||||
/// libadwaita.
|
||||
///
|
||||
/// This can be run in both a comptime and runtime context. If it is run in a
|
||||
/// comptime context, it will only check the version in the headers. If it is
|
||||
/// run in a runtime context, it will check the actual version of the library we
|
||||
/// are linked against. So generally you probably want to do both checks!
|
||||
///
|
||||
/// This is inlined so that the comptime checks will disable the runtime checks
|
||||
/// if the comptime checks fail.
|
||||
pub inline fn atLeast(
|
||||
comptime major: u16,
|
||||
comptime minor: u16,
|
||||
comptime micro: u16,
|
||||
) bool {
|
||||
// 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 very slightly faster.
|
||||
if (comptime comptime_version.order(.{
|
||||
.major = major,
|
||||
.minor = minor,
|
||||
.patch = micro,
|
||||
}) == .lt) return false;
|
||||
|
||||
// If we're in comptime then we can't check the runtime version.
|
||||
if (@inComptime()) return true;
|
||||
|
||||
return runtimeAtLeast(major, minor, micro);
|
||||
}
|
||||
|
||||
/// Verifies that the libadwaita version at runtime is at least the given version.
|
||||
///
|
||||
/// This function should be used in cases where the only the runtime behavior
|
||||
/// is affected by the version check. For checks which would affect code
|
||||
/// generation, use `atLeast`.
|
||||
pub inline fn runtimeAtLeast(
|
||||
comptime major: u16,
|
||||
comptime minor: u16,
|
||||
comptime micro: u16,
|
||||
) bool {
|
||||
// We use the functions instead of the constants such as c.GTK_MINOR_VERSION
|
||||
// because the function gets the actual runtime version.
|
||||
const runtime_version = getRuntimeVersion();
|
||||
return runtime_version.order(.{
|
||||
.major = major,
|
||||
.minor = minor,
|
||||
.patch = micro,
|
||||
}) != .lt;
|
||||
}
|
||||
|
||||
test "versionAtLeast" {
|
||||
const testing = std.testing;
|
||||
|
||||
const funs = &.{ atLeast, runtimeAtLeast };
|
||||
inline for (funs) |fun| {
|
||||
try testing.expect(fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION));
|
||||
try testing.expect(!fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION + 1));
|
||||
try testing.expect(!fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION + 1, c.ADW_MICRO_VERSION));
|
||||
try testing.expect(!fun(c.ADW_MAJOR_VERSION + 1, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION));
|
||||
try testing.expect(fun(c.ADW_MAJOR_VERSION - 1, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION));
|
||||
try testing.expect(fun(c.ADW_MAJOR_VERSION - 1, c.ADW_MINOR_VERSION + 1, c.ADW_MICRO_VERSION));
|
||||
try testing.expect(fun(c.ADW_MAJOR_VERSION - 1, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION + 1));
|
||||
try testing.expect(fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION - 1, c.ADW_MICRO_VERSION + 1));
|
||||
}
|
||||
}
|
||||
|
||||
// Whether AdwDialog, AdwAlertDialog, etc. are supported (1.5+)
|
||||
pub inline fn supportsDialogs() bool {
|
||||
return atLeast(1, 5, 0);
|
||||
}
|
||||
|
||||
pub inline fn supportsTabOverview() bool {
|
||||
return atLeast(1, 4, 0);
|
||||
}
|
||||
|
||||
pub inline fn supportsSwitchRow() bool {
|
||||
return atLeast(1, 4, 0);
|
||||
}
|
||||
|
||||
pub inline fn supportsToolbarView() bool {
|
||||
return atLeast(1, 4, 0);
|
||||
}
|
||||
|
||||
pub inline fn supportsBanner() bool {
|
||||
return atLeast(1, 3, 0);
|
||||
}
|
@@ -14,10 +14,10 @@ pub const app_id = "com.mitchellh.ghostty";
|
||||
/// The path to the Blueprint files. The folder structure is expected to be
|
||||
/// `{version}/{name}.blp` where `version` is the major and minor
|
||||
/// minimum adwaita version.
|
||||
pub const ui_path = "src/apprt/gtk/ui";
|
||||
pub const ui_path = "src/apprt/gtk-ng/ui";
|
||||
|
||||
/// The path to the CSS files.
|
||||
pub const css_path = "src/apprt/gtk/css";
|
||||
pub const css_path = "src/apprt/gtk-ng/css";
|
||||
|
||||
/// The possible icon sizes we'll embed into the gresource file.
|
||||
/// If any size doesn't exist then it will be an error. We could
|
213
src/apprt/gtk-ng/cgroup.zig
Normal file
213
src/apprt/gtk-ng/cgroup.zig
Normal file
@@ -0,0 +1,213 @@
|
||||
/// Contains all the logic for putting the Ghostty process and
|
||||
/// each individual surface into its own cgroup.
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const gio = @import("gio");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
|
||||
const App = @import("App.zig");
|
||||
const internal_os = @import("../../os/main.zig");
|
||||
|
||||
const log = std.log.scoped(.gtk_systemd_cgroup);
|
||||
|
||||
pub const Options = struct {
|
||||
memory_high: ?u64 = null,
|
||||
pids_max: ?u64 = null,
|
||||
};
|
||||
|
||||
/// Initialize the cgroup for the app. This will create our
|
||||
/// transient scope, initialize the cgroups we use for the app,
|
||||
/// configure them, and return the cgroup path for the app.
|
||||
///
|
||||
/// Returns the path of the current cgroup for the app, which is
|
||||
/// allocated with the given allocator.
|
||||
pub fn init(
|
||||
alloc: Allocator,
|
||||
dbus: *gio.DBusConnection,
|
||||
opts: Options,
|
||||
) ![]const u8 {
|
||||
const pid = std.os.linux.getpid();
|
||||
|
||||
// Get our initial cgroup. We need this so we can compare
|
||||
// and detect when we've switched to our transient group.
|
||||
const original = try internal_os.cgroup.current(
|
||||
alloc,
|
||||
pid,
|
||||
) orelse "";
|
||||
defer alloc.free(original);
|
||||
|
||||
// Create our transient scope. If this succeeds then the unit
|
||||
// was created, but we may not have moved into it yet, so we need
|
||||
// to do a dumb busy loop to wait for the move to complete.
|
||||
try createScope(dbus, pid);
|
||||
const transient = transient: while (true) {
|
||||
const current = try internal_os.cgroup.current(
|
||||
alloc,
|
||||
pid,
|
||||
) orelse "";
|
||||
if (!std.mem.eql(u8, original, current)) break :transient current;
|
||||
alloc.free(current);
|
||||
std.time.sleep(25 * std.time.ns_per_ms);
|
||||
};
|
||||
errdefer alloc.free(transient);
|
||||
log.info("transient scope created cgroup={s}", .{transient});
|
||||
|
||||
// Create the app cgroup and put ourselves in it. This is
|
||||
// required because controllers can't be configured while a
|
||||
// process is in a cgroup.
|
||||
try internal_os.cgroup.create(transient, "app", pid);
|
||||
|
||||
// Create a cgroup that will contain all our surfaces. We will
|
||||
// enable the controllers and configure resource limits for surfaces
|
||||
// only on this cgroup so that it doesn't affect our main app.
|
||||
try internal_os.cgroup.create(transient, "surfaces", null);
|
||||
const surfaces = try std.fmt.allocPrint(alloc, "{s}/surfaces", .{transient});
|
||||
defer alloc.free(surfaces);
|
||||
|
||||
// Enable all of our cgroup controllers. If these fail then
|
||||
// we just log. We can't reasonably undo what we've done above
|
||||
// so we log the warning and still return the transient group.
|
||||
// I don't know a scenario where this fails yet.
|
||||
try enableControllers(alloc, transient);
|
||||
try enableControllers(alloc, surfaces);
|
||||
|
||||
// Configure the "high" memory limit. This limit is used instead
|
||||
// of "max" because it's a soft limit that can be exceeded and
|
||||
// can be monitored by things like systemd-oomd to kill if needed,
|
||||
// versus an instant hard kill.
|
||||
if (opts.memory_high) |limit| {
|
||||
try internal_os.cgroup.configureLimit(surfaces, .{
|
||||
.memory_high = limit,
|
||||
});
|
||||
}
|
||||
|
||||
// Configure the "max" pids limit. This is a hard limit and cannot be
|
||||
// exceeded.
|
||||
if (opts.pids_max) |limit| {
|
||||
try internal_os.cgroup.configureLimit(surfaces, .{
|
||||
.pids_max = limit,
|
||||
});
|
||||
}
|
||||
|
||||
return transient;
|
||||
}
|
||||
|
||||
/// Enable all the cgroup controllers for the given cgroup.
|
||||
fn enableControllers(alloc: Allocator, cgroup: []const u8) !void {
|
||||
const raw = try internal_os.cgroup.controllers(alloc, cgroup);
|
||||
defer alloc.free(raw);
|
||||
|
||||
// Build our string builder for enabling all controllers
|
||||
var builder = std.ArrayList(u8).init(alloc);
|
||||
defer builder.deinit();
|
||||
|
||||
// Controllers are space-separated
|
||||
var it = std.mem.splitScalar(u8, raw, ' ');
|
||||
while (it.next()) |controller| {
|
||||
try builder.append('+');
|
||||
try builder.appendSlice(controller);
|
||||
if (it.rest().len > 0) try builder.append(' ');
|
||||
}
|
||||
|
||||
// Enable them all
|
||||
try internal_os.cgroup.configureControllers(
|
||||
cgroup,
|
||||
builder.items,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a transient systemd scope unit for the current process and
|
||||
/// move our process into it.
|
||||
fn createScope(
|
||||
dbus: *gio.DBusConnection,
|
||||
pid_: std.os.linux.pid_t,
|
||||
) !void {
|
||||
const pid: u32 = @intCast(pid_);
|
||||
|
||||
// The unit name needs to be unique. We use the pid for this.
|
||||
var name_buf: [256]u8 = undefined;
|
||||
const name = std.fmt.bufPrintZ(
|
||||
&name_buf,
|
||||
"app-ghostty-transient-{}.scope",
|
||||
.{pid},
|
||||
) catch unreachable;
|
||||
|
||||
const builder_type = glib.VariantType.new("(ssa(sv)a(sa(sv)))");
|
||||
defer glib.free(builder_type);
|
||||
|
||||
// Initialize our builder to build up our parameters
|
||||
var builder: glib.VariantBuilder = undefined;
|
||||
builder.init(builder_type);
|
||||
|
||||
builder.add("s", name.ptr);
|
||||
builder.add("s", "fail");
|
||||
|
||||
{
|
||||
// Properties
|
||||
const properties_type = glib.VariantType.new("a(sv)");
|
||||
defer glib.free(properties_type);
|
||||
|
||||
builder.open(properties_type);
|
||||
defer builder.close();
|
||||
|
||||
// https://www.freedesktop.org/software/systemd/man/latest/systemd-oomd.service.html
|
||||
const pressure_value = glib.Variant.newString("kill");
|
||||
|
||||
builder.add("(sv)", "ManagedOOMMemoryPressure", pressure_value);
|
||||
|
||||
// Delegate
|
||||
const delegate_value = glib.Variant.newBoolean(1);
|
||||
builder.add("(sv)", "Delegate", delegate_value);
|
||||
|
||||
// Pid to move into the unit
|
||||
const pids_value_type = glib.VariantType.new("u");
|
||||
defer glib.free(pids_value_type);
|
||||
|
||||
const pids_value = glib.Variant.newFixedArray(pids_value_type, &pid, 1, @sizeOf(u32));
|
||||
|
||||
builder.add("(sv)", "PIDs", pids_value);
|
||||
}
|
||||
|
||||
{
|
||||
// Aux
|
||||
const aux_type = glib.VariantType.new("a(sa(sv))");
|
||||
defer glib.free(aux_type);
|
||||
|
||||
builder.open(aux_type);
|
||||
defer builder.close();
|
||||
}
|
||||
|
||||
var err: ?*glib.Error = null;
|
||||
defer if (err) |e| e.free();
|
||||
|
||||
const reply_type = glib.VariantType.new("(o)");
|
||||
defer glib.free(reply_type);
|
||||
|
||||
const value = builder.end();
|
||||
|
||||
const reply = dbus.callSync(
|
||||
"org.freedesktop.systemd1",
|
||||
"/org/freedesktop/systemd1",
|
||||
"org.freedesktop.systemd1.Manager",
|
||||
"StartTransientUnit",
|
||||
value,
|
||||
reply_type,
|
||||
.{},
|
||||
-1,
|
||||
null,
|
||||
&err,
|
||||
) orelse {
|
||||
if (err) |e| log.err(
|
||||
"creating transient cgroup scope failed code={} err={s}",
|
||||
.{
|
||||
e.f_code,
|
||||
if (e.f_message) |msg| msg else "(no message)",
|
||||
},
|
||||
);
|
||||
return error.DbusCallFailed;
|
||||
};
|
||||
defer reply.unref();
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
//! This files contains all the GObject classes for the GTK apprt
|
||||
//! along with helpers to work with them.
|
||||
|
||||
const std = @import("std");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
@@ -54,111 +53,6 @@ pub fn Common(
|
||||
}
|
||||
}).private else {};
|
||||
|
||||
/// Get the class for the object.
|
||||
///
|
||||
/// This _seems_ ugly and unsafe but this is how GObject
|
||||
/// works under the hood. From the [GObject Type System
|
||||
/// Concepts](https://docs.gtk.org/gobject/concepts.html) documentation:
|
||||
///
|
||||
/// Every object must define two structures: its class structure
|
||||
/// and its instance structure. All class structures must contain
|
||||
/// as first member a GTypeClass structure. All instance structures
|
||||
/// must contain as first member a GTypeInstance structure.
|
||||
/// …
|
||||
/// These constraints allow the type system to make sure that
|
||||
/// every object instance (identified by a pointer to the object’s
|
||||
/// instance structure) contains in its first bytes a pointer to the
|
||||
/// object’s class structure.
|
||||
/// …
|
||||
/// The C standard mandates that the first field of a C structure is
|
||||
/// stored starting in the first byte of the buffer used to hold the
|
||||
/// structure’s fields in memory. This means that the first field of
|
||||
/// an instance of an object B is A’s first field which in turn is
|
||||
/// GTypeInstance‘s first field which in turn is g_class, a pointer
|
||||
/// to B’s class structure.
|
||||
///
|
||||
/// This means that to access the class structure for an object you cast it
|
||||
/// to `*gobject.TypeInstance` and then access the `f_g_class` field.
|
||||
///
|
||||
/// https://gitlab.gnome.org/GNOME/glib/-/blob/2c08654b62d52a31c4e4d13d7d85e12b989e72be/gobject/gtype.h#L555-571
|
||||
/// https://gitlab.gnome.org/GNOME/glib/-/blob/2c08654b62d52a31c4e4d13d7d85e12b989e72be/gobject/gtype.h#L2673
|
||||
///
|
||||
pub fn getClass(self: *Self) ?*Self.Class {
|
||||
const type_instance: *gobject.TypeInstance = @ptrCast(self);
|
||||
return @ptrCast(type_instance.f_g_class orelse return null);
|
||||
}
|
||||
|
||||
/// Define a virtual method. The `Self.Class` type must have a field
|
||||
/// named `name` which is a function pointer in the following form:
|
||||
///
|
||||
/// ?*const fn (*Self) callconv(.c) void
|
||||
///
|
||||
/// The virtual method may take additional parameters and specify
|
||||
/// a non-void return type. The parameters and return type must be
|
||||
/// valid for the C calling convention.
|
||||
pub fn defineVirtualMethod(
|
||||
comptime name: [:0]const u8,
|
||||
) type {
|
||||
return struct {
|
||||
pub fn call(
|
||||
class: anytype,
|
||||
object: *ClassInstance(@TypeOf(class)),
|
||||
params: anytype,
|
||||
) (fn_info.return_type orelse void) {
|
||||
const func = @field(
|
||||
gobject.ext.as(Self.Class, class),
|
||||
name,
|
||||
).?;
|
||||
@call(.auto, func, .{
|
||||
gobject.ext.as(Self, object),
|
||||
} ++ params);
|
||||
}
|
||||
|
||||
pub fn implement(
|
||||
class: anytype,
|
||||
implementation: *const ImplementFunc(@TypeOf(class)),
|
||||
) void {
|
||||
@field(gobject.ext.as(
|
||||
Self.Class,
|
||||
class,
|
||||
), name) = @ptrCast(implementation);
|
||||
}
|
||||
|
||||
/// The type info of the virtual method.
|
||||
const fn_info = fn_info: {
|
||||
// This is broken down like this so its slightly more
|
||||
// readable. We expect a field named "name" on the Class
|
||||
// with the rough type of `?*const fn` and we need the
|
||||
// function info.
|
||||
const Field = @FieldType(Self.Class, name);
|
||||
const opt = @typeInfo(Field).optional;
|
||||
const ptr = @typeInfo(opt.child).pointer;
|
||||
break :fn_info @typeInfo(ptr.child).@"fn";
|
||||
};
|
||||
|
||||
/// The instance type for a class.
|
||||
fn ClassInstance(comptime T: type) type {
|
||||
return @typeInfo(T).pointer.child.Instance;
|
||||
}
|
||||
|
||||
/// The function type for implementations. This is the same type
|
||||
/// as the virtual method but the self parameter points to the
|
||||
/// target instead of the original class.
|
||||
fn ImplementFunc(comptime T: type) type {
|
||||
var params: [fn_info.params.len]std.builtin.Type.Fn.Param = undefined;
|
||||
@memcpy(¶ms, fn_info.params);
|
||||
params[0].type = *ClassInstance(T);
|
||||
return @Type(.{ .@"fn" = .{
|
||||
.calling_convention = fn_info.calling_convention,
|
||||
.is_generic = fn_info.is_generic,
|
||||
.is_var_args = fn_info.is_var_args,
|
||||
.return_type = fn_info.return_type,
|
||||
.params = ¶ms,
|
||||
} });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// A helper that creates a property that reads and writes a
|
||||
/// private field with only shallow copies. This is good for primitives
|
||||
/// such as bools, numbers, etc.
|
@@ -116,11 +116,6 @@ pub const Application = extern struct {
|
||||
/// and initialization was successful.
|
||||
transient_cgroup_base: ?[]const u8 = null,
|
||||
|
||||
/// This is set to true so long as we request a window exactly
|
||||
/// once. This prevents quitting the app before we've shown one
|
||||
/// window.
|
||||
requested_window: bool = false,
|
||||
|
||||
/// This is set to false internally when the event loop
|
||||
/// should exit and the application should quit. This must
|
||||
/// only be set by the main loop thread.
|
||||
@@ -223,8 +218,10 @@ pub const Application = extern struct {
|
||||
const single_instance = switch (config.@"gtk-single-instance") {
|
||||
.true => true,
|
||||
.false => false,
|
||||
// This should have been resolved to true/false during config loading.
|
||||
.detect => unreachable,
|
||||
.desktop => switch (config.@"launched-from".?) {
|
||||
.desktop, .systemd, .dbus => true,
|
||||
.cli => false,
|
||||
},
|
||||
};
|
||||
|
||||
// Setup the flags for our application.
|
||||
@@ -416,7 +413,9 @@ pub const Application = extern struct {
|
||||
// This just calls the `activate` signal but its part of the normal startup
|
||||
// routine so we just call it, but only if the config allows it (this allows
|
||||
// for launching Ghostty in the "background" without immediately opening
|
||||
// a window).
|
||||
// a window). An initial window will not be immediately created if we were
|
||||
// launched by D-Bus activation or systemd. D-Bus activation will send it's
|
||||
// own `activate` or `new-window` signal later.
|
||||
//
|
||||
// https://gitlab.gnome.org/GNOME/glib/-/blob/bd2ccc2f69ecfd78ca3f34ab59e42e2b462bad65/gio/gapplication.c#L2302
|
||||
const priv = self.private();
|
||||
@@ -424,11 +423,15 @@ pub const Application = extern struct {
|
||||
// We need to scope any config access because once we run our
|
||||
// event loop, this can change out from underneath us.
|
||||
const config = priv.config.get();
|
||||
if (config.@"initial-window") self.as(gio.Application).activate();
|
||||
if (config.@"initial-window") switch (config.@"launched-from".?) {
|
||||
.desktop, .cli => self.as(gio.Application).activate(),
|
||||
.dbus, .systemd => {},
|
||||
};
|
||||
}
|
||||
|
||||
// If we are NOT the primary instance, then we never want to run.
|
||||
// This means that another instance of the GTK app is running.
|
||||
// This means that another instance of the GTK app is running and
|
||||
// our "activate" call above will open a window.
|
||||
if (self.as(gio.Application).getIsRemote() != 0) {
|
||||
log.debug(
|
||||
"application is remote, exiting run loop after activation",
|
||||
@@ -458,24 +461,11 @@ pub const Application = extern struct {
|
||||
// If the quit timer has expired, quit.
|
||||
if (priv.quit_timer == .expired) break :q true;
|
||||
|
||||
// If we have no windows attached to our app, also quit.
|
||||
if (priv.requested_window and @as(
|
||||
?*glib.List,
|
||||
self.as(gtk.Application).getWindows(),
|
||||
) == null) break :q true;
|
||||
|
||||
// No quit conditions met
|
||||
// There's no quit timer running, or it hasn't expired, don't quit.
|
||||
break :q false;
|
||||
};
|
||||
|
||||
if (must_quit) {
|
||||
// All must quit scenarios do not need confirmation.
|
||||
// Furthermore, must quit scenarios may result in a situation
|
||||
// where its unsafe to even access the app/surface memory
|
||||
// since its in the process of being freed. We must simply
|
||||
// begin our exit immediately.
|
||||
self.quitNow();
|
||||
}
|
||||
if (must_quit) self.quit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,15 +488,7 @@ pub const Application = extern struct {
|
||||
const parent: ?*gtk.Widget = parent: {
|
||||
const list = gtk.Window.listToplevels();
|
||||
defer list.free();
|
||||
const focused = @as(?*glib.List, list.findCustom(
|
||||
null,
|
||||
findActiveWindow,
|
||||
)) orelse {
|
||||
// If we have an active surface then we should have
|
||||
// a window available but in the rare case we don't we
|
||||
// should exit so we don't crash.
|
||||
break :parent null;
|
||||
};
|
||||
const focused = list.findCustom(null, findActiveWindow);
|
||||
break :parent @ptrCast(@alignCast(focused.f_data));
|
||||
};
|
||||
|
||||
@@ -560,7 +542,7 @@ pub const Application = extern struct {
|
||||
value: apprt.Action.Value(action),
|
||||
) !bool {
|
||||
switch (action) {
|
||||
.close_tab => return Action.closeTab(target, value),
|
||||
.close_tab => return Action.closeTab(target),
|
||||
.close_window => return Action.closeWindow(target),
|
||||
|
||||
.config_change => try Action.configChange(
|
||||
@@ -731,24 +713,27 @@ pub const Application = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn loadRuntimeCss(self: *Self) Allocator.Error!void {
|
||||
fn loadRuntimeCss(
|
||||
self: *Self,
|
||||
) Allocator.Error!void {
|
||||
const alloc = self.allocator();
|
||||
|
||||
const config = self.private().config.get();
|
||||
|
||||
var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(alloc, 2048);
|
||||
var buf: std.ArrayListUnmanaged(u8) = .empty;
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
const writer = buf.writer(alloc);
|
||||
|
||||
const config = self.private().config.get();
|
||||
const window_theme = config.@"window-theme";
|
||||
const unfocused_fill: CoreConfig.Color = config.@"unfocused-split-fill" orelse config.background;
|
||||
const headerbar_background = config.@"window-titlebar-background" orelse config.background;
|
||||
const headerbar_foreground = config.@"window-titlebar-foreground" orelse config.foreground;
|
||||
|
||||
try writer.print(
|
||||
\\widget.unfocused-split {{
|
||||
\\ opacity: {d:.2};
|
||||
\\ background-color: rgb({d},{d},{d});
|
||||
\\}}
|
||||
\\
|
||||
, .{
|
||||
1.0 - config.@"unfocused-split-opacity",
|
||||
unfocused_fill.r,
|
||||
@@ -762,7 +747,6 @@ pub const Application = extern struct {
|
||||
\\ color: rgb({[r]d},{[g]d},{[b]d});
|
||||
\\ background: rgb({[r]d},{[g]d},{[b]d});
|
||||
\\}}
|
||||
\\
|
||||
, .{
|
||||
.r = color.r,
|
||||
.g = color.g,
|
||||
@@ -775,129 +759,9 @@ pub const Application = extern struct {
|
||||
\\.window headerbar {{
|
||||
\\ font-family: "{[font_family]s}";
|
||||
\\}}
|
||||
\\
|
||||
, .{ .font_family = font_family });
|
||||
}
|
||||
|
||||
try loadRuntimeCss414(config, &writer);
|
||||
try loadRuntimeCss416(config, &writer);
|
||||
|
||||
// ensure that we have a sentinel
|
||||
try writer.writeByte(0);
|
||||
|
||||
const data = buf.items[0 .. buf.items.len - 1 :0];
|
||||
|
||||
log.debug("runtime CSS is {d} bytes", .{data.len + 1});
|
||||
|
||||
// Clears any previously loaded CSS from this provider
|
||||
loadCssProviderFromData(
|
||||
self.private().css_provider,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
/// Load runtime CSS for older than GTK 4.16
|
||||
fn loadRuntimeCss414(
|
||||
config: *const CoreConfig,
|
||||
writer: *const std.ArrayListUnmanaged(u8).Writer,
|
||||
) Allocator.Error!void {
|
||||
if (gtk_version.runtimeAtLeast(4, 16, 0)) return;
|
||||
|
||||
const window_theme = config.@"window-theme";
|
||||
const headerbar_background = config.@"window-titlebar-background" orelse config.background;
|
||||
const headerbar_foreground = config.@"window-titlebar-foreground" orelse config.foreground;
|
||||
|
||||
switch (window_theme) {
|
||||
.ghostty => try writer.print(
|
||||
\\windowhandle {{
|
||||
\\ background-color: rgb({d},{d},{d});
|
||||
\\ color: rgb({d},{d},{d});
|
||||
\\}}
|
||||
\\windowhandle:backdrop {{
|
||||
\\ background-color: oklab(from rgb({d},{d},{d}) calc(l * 0.9) a b / alpha);
|
||||
\\}}
|
||||
\\
|
||||
, .{
|
||||
headerbar_background.r,
|
||||
headerbar_background.g,
|
||||
headerbar_background.b,
|
||||
headerbar_foreground.r,
|
||||
headerbar_foreground.g,
|
||||
headerbar_foreground.b,
|
||||
headerbar_background.r,
|
||||
headerbar_background.g,
|
||||
headerbar_background.b,
|
||||
}),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Load runtime for GTK 4.16 and newer
|
||||
fn loadRuntimeCss416(
|
||||
config: *const CoreConfig,
|
||||
writer: *const std.ArrayListUnmanaged(u8).Writer,
|
||||
) Allocator.Error!void {
|
||||
if (gtk_version.runtimeUntil(4, 16, 0)) return;
|
||||
|
||||
const window_theme = config.@"window-theme";
|
||||
const headerbar_background = config.@"window-titlebar-background" orelse config.background;
|
||||
const headerbar_foreground = config.@"window-titlebar-foreground" orelse config.foreground;
|
||||
|
||||
try writer.writeAll(
|
||||
\\/*
|
||||
\\ * Child Exited Overlay
|
||||
\\ */
|
||||
\\
|
||||
\\.child-exited.normal revealer widget {
|
||||
\\ background-color: color-mix(
|
||||
\\ in srgb,
|
||||
\\ var(--success-bg-color),
|
||||
\\ transparent 50%
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
\\.child-exited.abnormal revealer widget {
|
||||
\\ background-color: color-mix(
|
||||
\\ in srgb,
|
||||
\\ var(--error-bg-color),
|
||||
\\ transparent 50%
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
\\/*
|
||||
\\ * Surface
|
||||
\\ */
|
||||
\\
|
||||
\\.surface progressbar.error trough progress {
|
||||
\\ background-color: color-mix(
|
||||
\\ in srgb,
|
||||
\\ var(--error-bg-color),
|
||||
\\ transparent 50%
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
\\.surface .bell-overlay {
|
||||
\\ border-color: color-mix(
|
||||
\\ in srgb,
|
||||
\\ var(--accent-color),
|
||||
\\ transparent 50%
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
\\/*
|
||||
\\ * Splits
|
||||
\\ */
|
||||
\\
|
||||
\\.window .split paned > separator {
|
||||
\\ background-color: color-mix(
|
||||
\\ in srgb,
|
||||
\\ var(--window-bg-color),
|
||||
\\ transparent 0%
|
||||
\\ );
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
|
||||
switch (window_theme) {
|
||||
.ghostty => try writer.print(
|
||||
\\:root {{
|
||||
@@ -930,6 +794,15 @@ pub const Application = extern struct {
|
||||
}),
|
||||
else => {},
|
||||
}
|
||||
|
||||
const data = try alloc.dupeZ(u8, buf.items);
|
||||
defer alloc.free(data);
|
||||
|
||||
// Clears any previously loaded CSS from this provider
|
||||
loadCssProviderFromData(
|
||||
self.private().css_provider,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
fn loadCustomCss(self: *Self) !void {
|
||||
@@ -999,8 +872,7 @@ pub const Application = extern struct {
|
||||
self.syncActionAccelerator("win.close", .{ .close_window = {} });
|
||||
self.syncActionAccelerator("win.new-window", .{ .new_window = {} });
|
||||
self.syncActionAccelerator("win.new-tab", .{ .new_tab = {} });
|
||||
self.syncActionAccelerator("win.close-tab::this", .{ .close_tab = .this });
|
||||
self.syncActionAccelerator("tab.close::this", .{ .close_tab = .this });
|
||||
self.syncActionAccelerator("win.close-tab", .{ .close_tab = {} });
|
||||
self.syncActionAccelerator("win.split-right", .{ .new_split = .right });
|
||||
self.syncActionAccelerator("win.split-down", .{ .new_split = .down });
|
||||
self.syncActionAccelerator("win.split-left", .{ .new_split = .left });
|
||||
@@ -1704,16 +1576,12 @@ pub const Application = extern struct {
|
||||
|
||||
/// All apprt action handlers
|
||||
const Action = struct {
|
||||
pub fn closeTab(target: apprt.Target, value: apprt.Action.Value(.close_tab)) bool {
|
||||
pub fn closeTab(target: apprt.Target) bool {
|
||||
switch (target) {
|
||||
.app => return false,
|
||||
.surface => |core| {
|
||||
const surface = core.rt_surface.surface;
|
||||
return surface.as(gtk.Widget).activateAction(
|
||||
"tab.close",
|
||||
glib.ext.VariantType.stringFor([:0]const u8),
|
||||
@as([*:0]const u8, @tagName(value)),
|
||||
) != 0;
|
||||
return surface.as(gtk.Widget).activateAction("tab.close", null) != 0;
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1985,13 +1853,6 @@ const Action = struct {
|
||||
self: *Application,
|
||||
parent: ?*CoreSurface,
|
||||
) !void {
|
||||
// Note that we've requested a window at least once. This is used
|
||||
// to trigger quit on no windows. Note I'm not sure if this is REALLY
|
||||
// necessary, but I don't want to risk a bug where on a slow machine
|
||||
// or something we quit immediately after starting up because there
|
||||
// was a delay in the event loop before we created a Window.
|
||||
self.private().requested_window = true;
|
||||
|
||||
const win = Window.new(self);
|
||||
initAndShowWindow(self, win, parent);
|
||||
}
|
||||
@@ -2355,7 +2216,7 @@ const Action = struct {
|
||||
Window,
|
||||
surface.as(gtk.Widget),
|
||||
) orelse {
|
||||
log.warn("surface is not in a window, ignoring toggle_window_decorations", .{});
|
||||
log.warn("surface is not in a window, ignoring new_tab", .{});
|
||||
return false;
|
||||
};
|
||||
|
@@ -10,7 +10,7 @@ const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
const Dialog = @import("dialog.zig").Dialog;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_close_confirmation_dialog);
|
||||
const log = std.log.scoped(.gtk_ghostty_config_errors_dialog);
|
||||
|
||||
pub const CloseConfirmationDialog = extern struct {
|
||||
const Self = @This();
|
@@ -35,18 +35,36 @@ pub const ImguiWidget = extern struct {
|
||||
|
||||
pub const properties = struct {};
|
||||
|
||||
pub const signals = struct {};
|
||||
pub const signals = struct {
|
||||
/// Emitted when the child widget should render. During the callback,
|
||||
/// the Imgui context is valid.
|
||||
pub const render = struct {
|
||||
pub const name = "render";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{},
|
||||
void,
|
||||
);
|
||||
};
|
||||
|
||||
pub const virtual_methods = struct {
|
||||
/// This virtual method will be called to allow the Dear ImGui
|
||||
/// application to do one-time setup of the context. The correct context
|
||||
/// will be current when the virtual method is called.
|
||||
pub const setup = C.defineVirtualMethod("setup");
|
||||
|
||||
/// This virtual method will be called at each frame to allow the Dear
|
||||
/// ImGui application to draw the application. The correct context will
|
||||
/// be current when the virtual method is called.
|
||||
pub const render = C.defineVirtualMethod("render");
|
||||
/// Emitted when first realized to allow the embedded ImGui application
|
||||
/// to initialize itself. When this is called, the ImGui context
|
||||
/// is properly set.
|
||||
///
|
||||
/// This might be called multiple times, but each time it is
|
||||
/// called a new Imgui context will be created.
|
||||
pub const setup = struct {
|
||||
pub const name = "setup";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{},
|
||||
void,
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const Private = struct {
|
||||
@@ -95,25 +113,6 @@ pub const ImguiWidget = extern struct {
|
||||
priv.gl_area.queueRender();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Public wrappers for virtual methods
|
||||
|
||||
/// This virtual method will be called to allow the Dear ImGui application
|
||||
/// to do one-time setup of the context. The correct context will be current
|
||||
/// when the virtual method is called.
|
||||
pub fn setup(self: *Self) callconv(.c) void {
|
||||
const class = self.getClass() orelse return;
|
||||
virtual_methods.setup.call(class, self, .{});
|
||||
}
|
||||
|
||||
/// This virtual method will be called at each frame to allow the Dear ImGui
|
||||
/// application to draw the application. The correct context will be current
|
||||
/// when the virtual method is called.
|
||||
pub fn render(self: *Self) callconv(.c) void {
|
||||
const class = self.getClass() orelse return;
|
||||
virtual_methods.render.call(class, self, .{});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Private Methods
|
||||
|
||||
@@ -233,8 +232,13 @@ pub const ImguiWidget = extern struct {
|
||||
// initialize the ImgUI OpenGL backend for our context.
|
||||
_ = cimgui.ImGui_ImplOpenGL3_Init(null);
|
||||
|
||||
// Call the virtual method to setup the UI.
|
||||
self.setup();
|
||||
// Setup our app
|
||||
signals.setup.impl.emit(
|
||||
self,
|
||||
null,
|
||||
.{},
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
/// Handle a request to unrealize the GLArea
|
||||
@@ -275,8 +279,13 @@ pub const ImguiWidget = extern struct {
|
||||
self.newFrame();
|
||||
cimgui.c.igNewFrame();
|
||||
|
||||
// Call the virtual method to draw the UI.
|
||||
self.render();
|
||||
// Use the callback to draw the UI.
|
||||
signals.render.impl.emit(
|
||||
self,
|
||||
null,
|
||||
.{},
|
||||
null,
|
||||
);
|
||||
|
||||
// Render
|
||||
cimgui.c.igRender();
|
||||
@@ -413,34 +422,15 @@ pub const ImguiWidget = extern struct {
|
||||
cimgui.c.ImGuiIO_AddInputCharactersUTF8(io, bytes);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Default virtual method handlers
|
||||
|
||||
/// Default setup function. Does nothing but log a warning.
|
||||
fn defaultSetup(_: *Self) callconv(.c) void {
|
||||
log.warn("default Dear ImGui setup called, this is a bug.", .{});
|
||||
}
|
||||
|
||||
/// Default render function. Does nothing but log a warning.
|
||||
fn defaultRender(_: *Self) callconv(.c) void {
|
||||
log.warn("default Dear ImGui render called, this is a bug.", .{});
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
pub const refSink = C.refSink;
|
||||
pub const unref = C.unref;
|
||||
pub const getClass = C.getClass;
|
||||
const private = C.private;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent_class: Parent.Class,
|
||||
|
||||
/// Function pointers for virtual methods.
|
||||
setup: ?*const fn (*Self) callconv(.c) void,
|
||||
render: ?*const fn (*Self) callconv(.c) void,
|
||||
|
||||
var parent: *Parent.Class = undefined;
|
||||
pub const Instance = Self;
|
||||
|
||||
@@ -454,10 +444,6 @@ pub const ImguiWidget = extern struct {
|
||||
}),
|
||||
);
|
||||
|
||||
// Initialize our virtual methods with default functions.
|
||||
class.setup = defaultSetup;
|
||||
class.render = defaultRender;
|
||||
|
||||
// Bindings
|
||||
class.bindTemplateChildPrivate("gl_area", .{});
|
||||
class.bindTemplateChildPrivate("im_context", .{});
|
||||
@@ -478,6 +464,8 @@ pub const ImguiWidget = extern struct {
|
||||
class.bindTemplateCallback("im_commit", &imCommit);
|
||||
|
||||
// Signals
|
||||
signals.render.impl.register(.{});
|
||||
signals.setup.impl.register(.{});
|
||||
|
||||
// Virtual methods
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
@@ -17,7 +17,7 @@ const log = std.log.scoped(.gtk_ghostty_inspector_widget);
|
||||
pub const InspectorWidget = extern struct {
|
||||
const Self = @This();
|
||||
parent_instance: Parent,
|
||||
pub const Parent = ImguiWidget;
|
||||
pub const Parent = adw.Bin;
|
||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||
.name = "GhosttyInspectorWidget",
|
||||
.instanceInit = &init,
|
||||
@@ -50,6 +50,9 @@ pub const InspectorWidget = extern struct {
|
||||
/// We attach a weak notify to the object.
|
||||
surface: ?*Surface = null,
|
||||
|
||||
/// The embedded Dear ImGui widget.
|
||||
imgui_widget: *ImguiWidget,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
@@ -75,30 +78,13 @@ pub const InspectorWidget = extern struct {
|
||||
);
|
||||
}
|
||||
|
||||
/// Called to do initial setup of the UI.
|
||||
fn imguiSetup(
|
||||
_: *Self,
|
||||
) callconv(.c) void {
|
||||
Inspector.setup();
|
||||
}
|
||||
|
||||
/// Called for every frame to draw the UI.
|
||||
fn imguiRender(
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
const surface = priv.surface orelse return;
|
||||
const core_surface = surface.core() orelse return;
|
||||
const inspector = core_surface.inspector orelse return;
|
||||
inspector.render();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Public methods
|
||||
|
||||
/// Queue a render of the Dear ImGui widget.
|
||||
pub fn queueRender(self: *Self) void {
|
||||
self.as(ImguiWidget).queueRender();
|
||||
const priv = self.private();
|
||||
priv.imgui_widget.queueRender();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
@@ -203,6 +189,24 @@ pub const InspectorWidget = extern struct {
|
||||
// for completeness sake we should clean this up.
|
||||
}
|
||||
|
||||
fn imguiRender(
|
||||
_: *ImguiWidget,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
const surface = priv.surface orelse return;
|
||||
const core_surface = surface.core() orelse return;
|
||||
const inspector = core_surface.inspector orelse return;
|
||||
inspector.render();
|
||||
}
|
||||
|
||||
fn imguiSetup(
|
||||
_: *ImguiWidget,
|
||||
_: *Self,
|
||||
) callconv(.c) void {
|
||||
Inspector.setup();
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
@@ -226,6 +230,13 @@ pub const InspectorWidget = extern struct {
|
||||
}),
|
||||
);
|
||||
|
||||
// Bindings
|
||||
class.bindTemplateChildPrivate("imgui_widget", .{});
|
||||
|
||||
// Template callbacks
|
||||
class.bindTemplateCallback("imgui_render", &imguiRender);
|
||||
class.bindTemplateCallback("imgui_setup", &imguiSetup);
|
||||
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.surface.impl,
|
||||
@@ -234,8 +245,6 @@ pub const InspectorWidget = extern struct {
|
||||
// Signals
|
||||
|
||||
// Virtual methods
|
||||
ImguiWidget.virtual_methods.setup.implement(class, imguiSetup);
|
||||
ImguiWidget.virtual_methods.render.implement(class, imguiRender);
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||
}
|
||||
|
@@ -105,24 +105,6 @@ pub const Surface = extern struct {
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"error" = struct {
|
||||
pub const name = "error";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
bool,
|
||||
.{
|
||||
.default = false,
|
||||
.accessor = gobject.ext.privateFieldAccessor(
|
||||
Self,
|
||||
Private,
|
||||
&Private.offset,
|
||||
"error",
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"font-size-request" = struct {
|
||||
pub const name = "font-size-request";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
@@ -490,15 +472,6 @@ pub const Surface = extern struct {
|
||||
// false by a parent widget.
|
||||
bell_ringing: bool = false,
|
||||
|
||||
/// True if this surface is in an error state. This is currently
|
||||
/// a simple boolean with no additional information on WHAT the
|
||||
/// error state is, because we don't yet need it or use it. For now,
|
||||
/// if this is true, then it means the terminal is non-functional.
|
||||
@"error": bool = false,
|
||||
|
||||
/// The source that handles setting our child property.
|
||||
idle_rechild: ?c_uint = null,
|
||||
|
||||
/// A weak reference to an inspector window.
|
||||
inspector: ?*InspectorWindow = null,
|
||||
|
||||
@@ -507,8 +480,6 @@ pub const Surface = extern struct {
|
||||
context_menu: *gtk.PopoverMenu,
|
||||
drop_target: *gtk.DropTarget,
|
||||
progress_bar_overlay: *gtk.ProgressBar,
|
||||
error_page: *adw.StatusPage,
|
||||
terminal_page: *gtk.Overlay,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
@@ -583,21 +554,13 @@ pub const Surface = extern struct {
|
||||
config_: ?*Config,
|
||||
bell_ringing_: c_int,
|
||||
) callconv(.c) c_int {
|
||||
const bell_ringing = bell_ringing_ != 0;
|
||||
|
||||
// If the bell isn't ringing exit early because when the surface is
|
||||
// first created there's a race between this code being run and the
|
||||
// config being set on the surface. That way we don't overwhelm people
|
||||
// with the warning that we issue if the config isn't set and overwhelm
|
||||
// ourselves with large numbers of bug reports.
|
||||
if (!bell_ringing) return @intFromBool(false);
|
||||
|
||||
const config = if (config_) |v| v.get() else {
|
||||
log.warn("config unavailable for computing whether border should be shown, likely bug", .{});
|
||||
log.warn("config unavailable for computing whether border should be shown , likely bug", .{});
|
||||
return @intFromBool(false);
|
||||
};
|
||||
|
||||
return @intFromBool(config.@"bell-features".border);
|
||||
const bell_ringing = bell_ringing_ != 0;
|
||||
return @intFromBool(config.@"bell-features".border and bell_ringing);
|
||||
}
|
||||
|
||||
pub fn toggleFullscreen(self: *Self) void {
|
||||
@@ -867,7 +830,7 @@ pub const Surface = extern struct {
|
||||
// such as single quote on a US international keyboard layout.
|
||||
if (priv.im_composing) return true;
|
||||
|
||||
// If we were composing and now we're not, it means that we committed
|
||||
// If we were composing and now we're not it means that we committed
|
||||
// the text. We also don't want to encode a key event for this.
|
||||
// Example: enable Japanese input method, press "konn" and then
|
||||
// press enter. The final enter should not be encoded and "konn"
|
||||
@@ -907,24 +870,9 @@ pub const Surface = extern struct {
|
||||
|
||||
// We want to get the physical unmapped key to process physical keybinds.
|
||||
// (These are keybinds explicitly marked as requesting physical mapping).
|
||||
const physical_key = keycode: {
|
||||
const w3c_key: input.Key = w3c: for (input.keycodes.entries) |entry| {
|
||||
if (entry.native == keycode) break :w3c entry.key;
|
||||
} else .unidentified;
|
||||
|
||||
// If the key should be remappable, then consult the pre-remapped
|
||||
// XKB keyval/keysym to get the (possibly) remapped key.
|
||||
//
|
||||
// See the docs for `shouldBeRemappable` for why we even have to
|
||||
// do this in the first place.
|
||||
if (w3c_key.shouldBeRemappable()) {
|
||||
if (gtk_key.keyFromKeyval(keyval)) |remapped|
|
||||
break :keycode remapped;
|
||||
}
|
||||
|
||||
// Return the original physical key
|
||||
break :keycode w3c_key;
|
||||
};
|
||||
const physical_key = keycode: for (input.keycodes.entries) |entry| {
|
||||
if (entry.native == keycode) break :keycode entry.key;
|
||||
} else .unidentified;
|
||||
|
||||
// Get our modifier for the event
|
||||
const mods: input.Mods = gtk_key.eventMods(
|
||||
@@ -1364,19 +1312,6 @@ pub const Surface = extern struct {
|
||||
priv.progress_bar_timer = null;
|
||||
}
|
||||
|
||||
if (priv.idle_rechild) |v| {
|
||||
if (glib.Source.remove(v) == 0) {
|
||||
log.warn("unable to remove idle source", .{});
|
||||
}
|
||||
priv.idle_rechild = null;
|
||||
}
|
||||
|
||||
// This works around a GTK double-free bug where if you bind
|
||||
// to a top-level template child, it frees twice if the widget is
|
||||
// also the root child of the template. By unsetting the child here,
|
||||
// we avoid the double-free.
|
||||
self.as(adw.Bin).setChild(null);
|
||||
|
||||
gtk.Widget.disposeTemplate(
|
||||
self.as(gtk.Widget),
|
||||
getGObjectType(),
|
||||
@@ -1582,12 +1517,6 @@ pub const Surface = extern struct {
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"bell-ringing".impl.param_spec);
|
||||
}
|
||||
|
||||
pub fn setError(self: *Self, v: bool) void {
|
||||
const priv = self.private();
|
||||
priv.@"error" = v;
|
||||
self.as(gobject.Object).notifyByPspec(properties.@"error".impl.param_spec);
|
||||
}
|
||||
|
||||
fn propConfig(
|
||||
self: *Self,
|
||||
_: *gobject.ParamSpec,
|
||||
@@ -1640,46 +1569,6 @@ pub const Surface = extern struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn propError(
|
||||
self: *Self,
|
||||
_: *gobject.ParamSpec,
|
||||
_: ?*anyopaque,
|
||||
) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
if (priv.@"error") {
|
||||
// Ensure we have an opaque background. The window will NOT set
|
||||
// this if we have transparency set and we need an opaque
|
||||
// background for the error message to be readable.
|
||||
self.as(gtk.Widget).addCssClass("background");
|
||||
} else {
|
||||
// Regardless of transparency setting, we remove the background
|
||||
// CSS class from this widget. Parent widgets will set it
|
||||
// appropriately (see window.zig for example).
|
||||
self.as(gtk.Widget).removeCssClass("background");
|
||||
}
|
||||
|
||||
// We need to set our child property on an idle tick, because the
|
||||
// error property can be triggered by signals that are in the middle
|
||||
// of widget mapping and changing our child during that time
|
||||
// results in a hard gtk crash.
|
||||
if (priv.idle_rechild == null) priv.idle_rechild = glib.idleAdd(
|
||||
onIdleRechild,
|
||||
self,
|
||||
);
|
||||
}
|
||||
|
||||
fn onIdleRechild(ud: ?*anyopaque) callconv(.c) c_int {
|
||||
const self: *Self = @ptrCast(@alignCast(ud orelse return 0));
|
||||
const priv = self.private();
|
||||
priv.idle_rechild = null;
|
||||
if (priv.@"error") {
|
||||
self.as(adw.Bin).setChild(priv.error_page.as(gtk.Widget));
|
||||
} else {
|
||||
self.as(adw.Bin).setChild(priv.terminal_page.as(gtk.Widget));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn propMouseHoverUrl(
|
||||
self: *Self,
|
||||
_: *gobject.ParamSpec,
|
||||
@@ -2030,11 +1919,8 @@ pub const Surface = extern struct {
|
||||
// Bell stops ringing if any mouse button is pressed.
|
||||
self.setBellRinging(false);
|
||||
|
||||
// Get our surface. If we don't have one, ignore this.
|
||||
const priv = self.private();
|
||||
const core_surface = priv.core_surface orelse return;
|
||||
|
||||
// If we don't have focus, grab it.
|
||||
const priv = self.private();
|
||||
const gl_area_widget = priv.gl_area.as(gtk.Widget);
|
||||
if (gl_area_widget.hasFocus() == 0) {
|
||||
_ = gl_area_widget.grabFocus();
|
||||
@@ -2042,10 +1928,10 @@ pub const Surface = extern struct {
|
||||
|
||||
// Report the event
|
||||
const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton());
|
||||
const consumed = consumed: {
|
||||
const consumed = if (priv.core_surface) |surface| consumed: {
|
||||
const gtk_mods = event.getModifierState();
|
||||
const mods = gtk_key.translateMods(gtk_mods);
|
||||
break :consumed core_surface.mouseButtonCallback(
|
||||
break :consumed surface.mouseButtonCallback(
|
||||
.press,
|
||||
button,
|
||||
mods,
|
||||
@@ -2053,7 +1939,7 @@ pub const Surface = extern struct {
|
||||
log.warn("error in key callback err={}", .{err});
|
||||
break :err false;
|
||||
};
|
||||
};
|
||||
} else false;
|
||||
|
||||
// If a right click isn't consumed, mouseButtonCallback selects the hovered
|
||||
// word and returns false. We can use this to handle the context menu
|
||||
@@ -2394,23 +2280,21 @@ pub const Surface = extern struct {
|
||||
) callconv(.c) void {
|
||||
log.debug("realize", .{});
|
||||
|
||||
// Make the GL area current so we can detect any OpenGL errors. If
|
||||
// we have errors here we can't render and we switch to the error
|
||||
// state.
|
||||
const priv = self.private();
|
||||
priv.gl_area.makeCurrent();
|
||||
if (priv.gl_area.getError()) |err| {
|
||||
log.warn("failed to make GL context current: {s}", .{err.f_message orelse "(no message)"});
|
||||
log.warn("this error is almost always due to a library, driver, or GTK issue", .{});
|
||||
log.warn("this is a common cause of this issue: https://ghostty.org/docs/help/gtk-opengl-context", .{});
|
||||
self.setError(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we already have an initialized surface then we notify it.
|
||||
// If we don't, we'll initialize it on the first resize so we have
|
||||
// our proper initial dimensions.
|
||||
const priv = self.private();
|
||||
if (priv.core_surface) |v| realize: {
|
||||
// We need to make the context current so we can call GL functions.
|
||||
// This is required for all surface operations.
|
||||
priv.gl_area.makeCurrent();
|
||||
if (priv.gl_area.getError()) |err| {
|
||||
log.warn("failed to make GL context current: {s}", .{err.f_message orelse "(no message)"});
|
||||
log.warn("this error is usually due to a driver or gtk bug", .{});
|
||||
log.warn("this is a common cause of this issue: https://gitlab.gnome.org/GNOME/gtk/-/issues/4950", .{});
|
||||
break :realize;
|
||||
}
|
||||
|
||||
v.renderer.displayRealized() catch |err| {
|
||||
log.warn("core displayRealized failed err={}", .{err});
|
||||
break :realize;
|
||||
@@ -2724,10 +2608,8 @@ pub const Surface = extern struct {
|
||||
class.bindTemplateChildPrivate("url_right", .{});
|
||||
class.bindTemplateChildPrivate("child_exited_overlay", .{});
|
||||
class.bindTemplateChildPrivate("context_menu", .{});
|
||||
class.bindTemplateChildPrivate("error_page", .{});
|
||||
class.bindTemplateChildPrivate("progress_bar_overlay", .{});
|
||||
class.bindTemplateChildPrivate("resize_overlay", .{});
|
||||
class.bindTemplateChildPrivate("terminal_page", .{});
|
||||
class.bindTemplateChildPrivate("drop_target", .{});
|
||||
class.bindTemplateChildPrivate("im_context", .{});
|
||||
|
||||
@@ -2757,7 +2639,6 @@ pub const Surface = extern struct {
|
||||
class.bindTemplateCallback("child_exited_close", &childExitedClose);
|
||||
class.bindTemplateCallback("context_menu_closed", &contextMenuClosed);
|
||||
class.bindTemplateCallback("notify_config", &propConfig);
|
||||
class.bindTemplateCallback("notify_error", &propError);
|
||||
class.bindTemplateCallback("notify_mouse_hover_url", &propMouseHoverUrl);
|
||||
class.bindTemplateCallback("notify_mouse_hidden", &propMouseHidden);
|
||||
class.bindTemplateCallback("notify_mouse_shape", &propMouseShape);
|
||||
@@ -2770,7 +2651,6 @@ pub const Surface = extern struct {
|
||||
properties.config.impl,
|
||||
properties.@"child-exited".impl,
|
||||
properties.@"default-size".impl,
|
||||
properties.@"error".impl,
|
||||
properties.@"font-size-request".impl,
|
||||
properties.focused.impl,
|
||||
properties.@"min-size".impl,
|
@@ -18,6 +18,7 @@ const gresource = @import("../build/gresource.zig");
|
||||
const Common = @import("../class.zig").Common;
|
||||
const Config = @import("config.zig").Config;
|
||||
const Application = @import("application.zig").Application;
|
||||
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
||||
const SplitTree = @import("split_tree.zig").SplitTree;
|
||||
const Surface = @import("surface.zig").Surface;
|
||||
|
||||
@@ -198,11 +199,8 @@ pub const Tab = extern struct {
|
||||
}
|
||||
|
||||
fn initActionMap(self: *Self) void {
|
||||
const s_param_type = glib.ext.VariantType.newFor([:0]const u8);
|
||||
defer s_param_type.free();
|
||||
|
||||
const actions = [_]ext.actions.Action(Self){
|
||||
.init("close", actionClose, s_param_type),
|
||||
.init("close", actionClose, null),
|
||||
.init("ring-bell", actionRingBell, null),
|
||||
};
|
||||
|
||||
@@ -316,44 +314,18 @@ pub const Tab = extern struct {
|
||||
|
||||
fn actionClose(
|
||||
_: *gio.SimpleAction,
|
||||
param_: ?*glib.Variant,
|
||||
_: ?*glib.Variant,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
const param = param_ orelse {
|
||||
log.warn("tab.close-tab called without a parameter", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
var str: ?[*:0]const u8 = null;
|
||||
param.get("&s", &str);
|
||||
|
||||
const tab_view = ext.getAncestor(
|
||||
adw.TabView,
|
||||
self.as(gtk.Widget),
|
||||
) orelse return;
|
||||
|
||||
const page = tab_view.getPage(self.as(gtk.Widget));
|
||||
|
||||
const mode = std.meta.stringToEnum(
|
||||
apprt.action.CloseTabMode,
|
||||
std.mem.span(
|
||||
str orelse {
|
||||
log.warn("invalid mode provided to tab.close-tab", .{});
|
||||
return;
|
||||
},
|
||||
),
|
||||
) orelse {
|
||||
// Need to be defensive here since actions can be triggered externally.
|
||||
log.warn("invalid mode provided to tab.close-tab: {s}", .{str.?});
|
||||
return;
|
||||
};
|
||||
|
||||
// Delegate to our parent to handle this, since this will emit
|
||||
// a close-page signal that the parent can intercept.
|
||||
switch (mode) {
|
||||
.this => tab_view.closePage(page),
|
||||
.other => tab_view.closeOtherPages(page),
|
||||
}
|
||||
tab_view.closePage(page);
|
||||
}
|
||||
|
||||
fn actionRingBell(
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user