diff --git a/.github/DISCUSSION_TEMPLATE/issue-triage.yml b/.github/DISCUSSION_TEMPLATE/issue-triage.yml index 35ed2dc33..270bb86f5 100644 --- a/.github/DISCUSSION_TEMPLATE/issue-triage.yml +++ b/.github/DISCUSSION_TEMPLATE/issue-triage.yml @@ -1,4 +1,4 @@ -labels: ["needs confirmation"] +labels: ["needs-confirmation"] body: - type: markdown attributes: @@ -14,22 +14,37 @@ body: description: | Provide a detailed description of the issue. Include relevant information, such as: - The feature or configuration option you encounter the issue with. - - The expected behavior. - - The actual behavior (and how it deviates from the expected behavior, if it is not immediately obvious). - - Relevant Ghostty logs or other stacktraces. - - Relevant screenshots, screen recordings, or other supporting media (as needed). + - Screenshots, screen recordings, or other supporting media (as needed). - If this is a regression of an existing issue that was closed or resolved, please include the previous item reference (Discussion, Issue, PR, commit) in your description. - >[!TIP] + > [!TIP] > **Not sure what information to include?** > Here are some recommendations: - > - **Input issues:** include your keyboard layout, a screenshot of the terminal inspector's logged keystrokes (Linux: ctrl+shift+i; MacOS: cmd+alt+i), input method, Linux input method engine (IBus, Fcitx 5, or none) and its version. + > - **Input issues:** include your keyboard layout, a screenshot of logged keystrokes from the terminal inspector's "Keyboard" tab (Linux: ctrl+shift+i; MacOS: cmd+alt+i), input method, Linux input method engine (IBus, Fcitx 5, or none) and its version. > - **Font issues:** include the problematic character(s), the output of `ghostty +show-face` for these character(s), and if they work in other applications. - > - **VT issues (including image rendering issues):** attach an [asciinema](https://docs.asciinema.org/getting-started/) cast file, shell script, or text file for reproduction. + > - **Terminal emulation issues (including image rendering issues):** attach an [asciinema](https://docs.asciinema.org/getting-started/) cast file, shell script, or text file for reproduction. > - **Renderer issues:** (Linux) include your OpenGL version, graphics card, driver version. - + > - **Crashes:** (macOS) include the [Sentry UUID](https://github.com/ghostty-org/ghostty?tab=readme-ov-file#crash-reports); (Linux) try to reproduce using a debug build and provide the stack trace. placeholder: | - Example: When using SSH to connect to my remote Linux machine from my local macOS device in Ghostty, I try to run `clear`, and the screen does not clear. Instead, I see the following error message printed to the terminal: `Error opening terminal: xterm-ghostty.` + When using SSH to connect to my remote Linux machine from my local macOS device in Ghostty, I try to run `clear`, and the screen does not clear. Instead, I see the following error message printed to the terminal: `Error opening terminal: xterm-ghostty.` + validations: + required: true + - type: textarea + attributes: + label: Expected Behavior + description: | + Describe how you expect Ghostty to behave in this situation. Include any relevant documentation links. + placeholder: | + The screen is cleared and the prompt is redrawn at the top of the window. + validations: + required: true + - type: textarea + attributes: + label: Actual Behavior + description: | + Describe how Ghostty actually behaves in this situation. If it is not immediately obvious how the actual behavior differs from the expected behavior described above, please be sure to mention the deviation specifically. + placeholder: | + The screen is not cleared, and an error is printed: `Error opening terminal: xterm-ghostty`. validations: required: true - type: textarea @@ -44,6 +59,12 @@ body: 4. Observe `xterm-ghostty` error message above. validations: required: true + - type: textarea + attributes: + label: Ghostty Logs + description: | + Provide any captured Ghostty logs or stacktraces during your issue reproduction in this field. On Linux, logs can be found by running `ghostty` from the command-line; on macOS, logs can be viewed with `sudo log stream --level debug --predicate 'subsystem=="com.mitchellh.ghostty"'` from another terminal emulator. + render: text - type: textarea attributes: label: Ghostty Version @@ -93,9 +114,9 @@ body: required: false - type: textarea attributes: - label: Ghostty Configuration + label: Minimal Ghostty Configuration description: | - Please provide the minimum configuration needed to reproduce this issue. If you cannot determine this, paste the output of `ghostty +show-config` here. + Please provide the **minimum** configuration needed to reproduce this issue. If you can still reproduce the issue with one of the lines removed, do not include that line. If and **only** if you are not able to determine this, paste the contents of your Ghostty configuration file here. placeholder: | font-family = CommitMono Nerd Font font-family-bold = CommitMono Nerd Font @@ -112,15 +133,15 @@ body: attributes: label: Additional Relevant Configuration description: | - If your issue involves other programs, tools, or applications in addition to Ghostty (e.g. Neovim, tmux, Zellij, etc.), please provide the minimum configuration needed for all relevant programs to reproduce the issue here. If you use custom CSS or shaders for Ghostty, also include them here, if applicable to your issue. + If your issue involves other programs, tools, or applications in addition to Ghostty (e.g. Neovim, tmux, Zellij, etc.), please provide the minimum configuration and versions needed for all relevant programs to reproduce the issue here. If you use custom CSS or shaders for Ghostty, also include them here, if applicable to your issue. placeholder: | - `tmux.conf` - --- + #### `tmux.conf` (tmux 3.5a) + ``` set -g default-terminal "tmux-256color" set-option -sa terminal-overrides ",xterm*:Tc" set -g base-index 1 setw -g pane-base-index 1 - render: text + ``` validations: required: false - type: markdown @@ -137,3 +158,5 @@ body: required: true - label: I have searched the Ghostty repository (both open and closed Discussions and Issues) and confirm this is not a duplicate of an existing issue or discussion. required: true + - label: I have checked the "Preview" tab on all text fields to ensure that everything looks right, and have wrapped all configuration and code in code blocks with a group of three backticks (` ``` `) on separate lines. + required: true diff --git a/.github/scripts/check-translations.sh b/.github/scripts/check-translations.sh new file mode 100755 index 000000000..18d5cd67b --- /dev/null +++ b/.github/scripts/check-translations.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +old_pot=$(mktemp) +cp po/com.mitchellh.ghostty.pot "$old_pot" +zig build update-translations + +# Compare previous POT to current POT +msgcmp "$old_pot" po/com.mitchellh.ghostty.pot --use-untranslated + +# Compare all other POs to current POT +for f in po/*.po; do + # Ignore untranslated entries + msgcmp --use-untranslated "$f" po/com.mitchellh.ghostty.pot; +done diff --git a/.github/scripts/request_review.py b/.github/scripts/request_review.py deleted file mode 100644 index d799e7c58..000000000 --- a/.github/scripts/request_review.py +++ /dev/null @@ -1,189 +0,0 @@ -# /// script -# requires-python = ">=3.9" -# dependencies = [ -# "githubkit", -# "loguru", -# ] -# /// - -from __future__ import annotations - -import asyncio -import os -import re -import sys -from collections.abc import Iterator -from contextlib import contextmanager -from itertools import chain - -from githubkit import GitHub -from githubkit.exception import RequestFailed -from loguru import logger - -ORG_NAME = "ghostty-org" -REPO_NAME = "ghostty" -ALLOWED_PARENT_TEAM = "localization" -LOCALIZATION_TEAM_NAME_PATTERN = re.compile(r"[a-z]{2}_[A-Z]{2}") -LEVEL_MAP = {"DEBUG": "DBG", "WARNING": "WRN", "ERROR": "ERR"} - -logger.remove() -logger.add( - sys.stderr, - format=lambda record: ( - "{time:YYYY-MM-DD HH:mm:ss.SSS} | " - f"{LEVEL_MAP[record['level'].name]} | " - "{function}:{line} - " - "{message}\n" - ), - backtrace=True, - diagnose=True, -) - - -@contextmanager -def log_fail(message: str, *, die: bool = True) -> Iterator[None]: - try: - yield - except RequestFailed as exc: - logger.error(message) - logger.error(exc) - logger.error(exc.response.raw_response.json()) - if die: - sys.exit(1) - - -gh = GitHub(os.environ["GITHUB_TOKEN"]) - -with log_fail("Invalid token"): - # Do the simplest request as a test - gh.rest.rate_limit.get() - - -async def fetch_and_parse_codeowners() -> dict[str, str]: - logger.debug("Fetching CODEOWNERS file...") - with log_fail("Failed to fetch CODEOWNERS file"): - content = ( - await gh.rest.repos.async_get_content( - ORG_NAME, - REPO_NAME, - "CODEOWNERS", - headers={"Accept": "application/vnd.github.raw+json"}, - ) - ).text - - logger.debug("Parsing CODEOWNERS file...") - codeowners: dict[str, str] = {} - for line in content.splitlines(): - if not line or line.lstrip().startswith("#"): - continue - - # This assumes that all entries only list one owner - # and that this owner is a team (ghostty-org/foobar) - path, owner = line.split() - path = path.lstrip("/") - owner = owner.removeprefix(f"@{ORG_NAME}/") - - if not is_localization_team(owner): - logger.debug(f"Skipping non-l11n codeowner {owner!r} for {path}") - continue - - codeowners[path] = owner - logger.debug(f"Found codeowner {owner!r} for {path}") - return codeowners - - -async def get_team_members(team_name: str) -> list[str]: - logger.debug(f"Fetching team {team_name!r}...") - with log_fail(f"Failed to fetch team {team_name!r}"): - team = (await gh.rest.teams.async_get_by_name(ORG_NAME, team_name)).parsed_data - - if team.parent and team.parent.slug == ALLOWED_PARENT_TEAM: - logger.debug(f"Fetching team {team_name!r} members...") - with log_fail(f"Failed to fetch team {team_name!r} members"): - resp = await gh.rest.teams.async_list_members_in_org(ORG_NAME, team_name) - members = [m.login for m in resp.parsed_data] - logger.debug(f"Team {team_name!r} members: {', '.join(members)}") - return members - - logger.warning(f"Team {team_name} does not have a {ALLOWED_PARENT_TEAM!r} parent") - return [] - - -async def get_changed_files(pr_number: int) -> list[str]: - logger.debug("Gathering changed files...") - with log_fail("Failed to gather changed files"): - diff_entries = ( - await gh.rest.pulls.async_list_files( - ORG_NAME, - REPO_NAME, - pr_number, - per_page=3000, - headers={"Accept": "application/vnd.github+json"}, - ) - ).parsed_data - return [d.filename for d in diff_entries] - - -async def request_review(pr_number: int, user: str, pr_author: str) -> None: - if user == pr_author: - logger.debug(f"Skipping review request for {user!r} (is PR author)") - logger.debug(f"Requesting review from {user!r}...") - with log_fail(f"Failed to request review from {user}", die=False): - await gh.rest.pulls.async_request_reviewers( - ORG_NAME, - REPO_NAME, - pr_number, - headers={"Accept": "application/vnd.github+json"}, - data={"reviewers": [user]}, - ) - - -def is_localization_team(team_name: str) -> bool: - return LOCALIZATION_TEAM_NAME_PATTERN.fullmatch(team_name) is not None - - -async def get_pr_author(pr_number: int) -> str: - logger.debug("Fetching PR author...") - with log_fail("Failed to fetch PR author"): - resp = await gh.rest.pulls.async_get(ORG_NAME, REPO_NAME, pr_number) - pr_author = resp.parsed_data.user.login - logger.debug(f"Found author: {pr_author!r}") - return pr_author - - -async def main() -> None: - logger.debug("Reading PR number...") - pr_number = int(os.environ["PR_NUMBER"]) - logger.debug(f"Starting review request process for PR #{pr_number}...") - - changed_files = await get_changed_files(pr_number) - logger.debug(f"Changed files: {', '.join(map(repr, changed_files))}") - - pr_author = await get_pr_author(pr_number) - codeowners = await fetch_and_parse_codeowners() - - found_owners = set[str]() - for file in changed_files: - logger.debug(f"Finding owner for {file!r}...") - for path, owner in codeowners.items(): - if file.startswith(path): - logger.debug(f"Found owner: {owner!r}") - break - else: - logger.debug("No owner found") - continue - found_owners.add(owner) - - member_lists = await asyncio.gather( - *(get_team_members(owner) for owner in found_owners) - ) - await asyncio.gather( - *( - request_review(pr_number, user, pr_author) - for user in chain.from_iterable(member_lists) - ) - ) - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml deleted file mode 100644 index 9abe0b5e2..000000000 --- a/.github/workflows/review.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Request Review - -on: - pull_request: - types: - - opened - - synchronize - -env: - PY_COLORS: 1 - -jobs: - review: - runs-on: namespace-profile-ghostty-xsm - steps: - - uses: actions/checkout@v4 - - - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.0 - with: - path: | - /nix - /zig - - - uses: cachix/install-nix-action@v30 - with: - nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v15 - with: - name: ghostty - authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - - - name: Request Localization Review - env: - GITHUB_TOKEN: ${{ secrets.GH_REVIEW_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: nix develop -c uv run .github/scripts/request_review.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a4557703a..e6e3a77a0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,8 @@ jobs: - build-macos-matrix - build-windows - build-windows-cross + - flatpak-check-zig-cache + - flatpak - test - test-gtk - test-sentry-linux @@ -351,15 +353,19 @@ jobs: os: [namespace-profile-ghostty-snap, namespace-profile-ghostty-snap-arm64] runs-on: ${{ matrix.os }} - needs: test + needs: [test, build-dist] env: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 + - name: Download Source Tarball Artifacts + uses: actions/download-artifact@v4 with: - fetch-depth: 0 - fetch-tags: true + name: source-tarball + - name: Extract tarball + run: | + mkdir dist + tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz - name: Setup Cache uses: namespacelabs/nscloud-cache-action@v1.2.0 with: @@ -368,7 +374,14 @@ jobs: /zig - run: sudo apt install -y udev - run: sudo systemctl start systemd-udevd + # Workaround until this is fixed: https://github.com/canonical/lxd-pkg-snap/pull/789 + - run: | + _LXD_SNAP_DEVCGROUP_CONFIG="/var/lib/snapd/cgroup/snap.lxd.device" + sudo mkdir -p /var/lib/snapd/cgroup + echo 'self-managed=true' | sudo tee "${_LXD_SNAP_DEVCGROUP_CONFIG}" - uses: snapcore/action-build@v1 + with: + path: dist build-windows: runs-on: windows-2022 @@ -734,7 +747,7 @@ jobs: translations: if: github.repository == 'ghostty-org/ghostty' - runs-on: namespace-profile-ghostty-sm + runs-on: namespace-profile-ghostty-xsm timeout-minutes: 60 env: ZIG_LOCAL_CACHE_DIR: /zig/local-cache @@ -757,23 +770,11 @@ jobs: skipPush: true useDaemon: false # sometimes fails on short jobs - name: check translations - run: | - old_pot=$(mktemp) - cp po/com.mitchellh.ghostty.pot "$old_pot" - nix develop -c zig build update-translations - - # Compare previous POT to current POT - msgcmp "$old_pot" po/com.mitchellh.ghostty.pot --use-untranslated - - # Compare all other POs to current POT - for f in po/*.po; do - # Ignore untranslated entries - msgcmp --use-untranslated "$f" po/com.mitchellh.ghostty.pot; - done + run: nix develop -c .github/scripts/check-translations.sh blueprint-compiler: if: github.repository == 'ghostty-org/ghostty' - runs-on: namespace-profile-ghostty-sm + runs-on: namespace-profile-ghostty-xsm timeout-minutes: 60 env: ZIG_LOCAL_CACHE_DIR: /zig/local-cache @@ -863,3 +864,56 @@ jobs: file: dist/src/build/docker/debian/Dockerfile build-args: | DISTRO_VERSION=12 + + 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@v4 + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@v1.2.0 + with: + path: | + /nix + /zig + - name: Setup Nix + uses: cachix/install-nix-action@v30 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@v15 + 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" + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-47 + options: --privileged + strategy: + fail-fast: false + matrix: + variant: + - arch: x86_64 + runner: namespace-profile-ghostty-md + - arch: aarch64 + runner: namespace-profile-ghostty-md-arm64 + runs-on: ${{ matrix.variant.runner }} + needs: [flatpak-check-zig-cache, test] + steps: + - uses: actions/checkout@v4 + - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: com.mitchellh.ghostty + manifest-path: flatpak/com.mitchellh.ghostty.yml + cache-key: flatpak-builder-${{ github.sha }} + arch: ${{ matrix.variant.arch }} + verbose: true diff --git a/.github/workflows/update-colorschemes.yml b/.github/workflows/update-colorschemes.yml index f6147bb96..fed6d2db7 100644 --- a/.github/workflows/update-colorschemes.yml +++ b/.github/workflows/update-colorschemes.yml @@ -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 @@ -69,6 +71,7 @@ jobs: build.zig.zon.nix build.zig.zon.txt build.zig.zon.json + flatpak/zig-packages.json body: | Upstream revision: https://github.com/mbadolato/iTerm2-Color-Schemes/tree/${{ steps.zig_fetch.outputs.upstream_rev }} labels: dependencies diff --git a/.gitignore b/.gitignore index f39b0c780..95eb1d5c3 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ zig-out/ example/*.wasm test/ghostty test/cases/**/*.actual.png +flatpak/builddir/ +flatpak/repo/ glad.zip /Box_test.ppm diff --git a/CODEOWNERS b/CODEOWNERS index 5e7263ce0..2a3058521 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -144,7 +144,7 @@ # Shell /src/shell-integration/ @ghostty-org/shell -/src/termio/shell-integration.zig @ghostty-org/shell +/src/termio/shell_integration.zig @ghostty-org/shell # Terminal /src/simd/ @ghostty-org/terminal diff --git a/build.zig.zon b/build.zig.zon index 086e19dd8..7c5ff8ffc 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -20,8 +20,8 @@ }, .z2d = .{ // vancluever/z2d - .url = "https://github.com/vancluever/z2d/archive/1e89605a624940c310c7a1d81b46a7c5c05919e3.tar.gz", - .hash = "z2d-0.6.0-j5P_HvLdCABu-dXpCeRM7Uk4m16vULg1980lMNCQj4_C", + .url = "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz", + .hash = "z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW", .lazy = true, }, .zig_objc = .{ @@ -103,8 +103,8 @@ // Other .apple_sdk = .{ .path = "./pkg/apple-sdk" }, .iterm2_themes = .{ - .url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/4c57d8c11d352a4aeda6928b65d78794c28883a5.tar.gz", - .hash = "N-V-__8AAEH8MwQaEsARbyV42-bSZGcu1am8xtg2h67wTFC3", + .url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/1e4957e65005908993250f8f07be3f70e805195e.tar.gz", + .hash = "N-V-__8AAHOASAQuLADcCSHLHEJiKFVLZiCD9Aq2rh5GT01A", .lazy = true, }, }, diff --git a/build.zig.zon.json b/build.zig.zon.json index d43bf3d56..513ee0dcd 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -54,10 +54,10 @@ "url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz", "hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA=" }, - "N-V-__8AAEH8MwQaEsARbyV42-bSZGcu1am8xtg2h67wTFC3": { + "N-V-__8AAHOASAQuLADcCSHLHEJiKFVLZiCD9Aq2rh5GT01A": { "name": "iterm2_themes", - "url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/4c57d8c11d352a4aeda6928b65d78794c28883a5.tar.gz", - "hash": "sha256-c+twvkEPiz1DaULYlnGXLxis19Q2h+TgBJxoARMasjU=" + "url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/1e4957e65005908993250f8f07be3f70e805195e.tar.gz", + "hash": "sha256-xpDitXpZrdU/EcgLyG4G0cEiT4r42viy+DJALmy2sQE=" }, "N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD": { "name": "libpng", @@ -124,10 +124,10 @@ "url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", "hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM=" }, - "z2d-0.6.0-j5P_HvLdCABu-dXpCeRM7Uk4m16vULg1980lMNCQj4_C": { + "z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW": { "name": "z2d", - "url": "https://github.com/vancluever/z2d/archive/1e89605a624940c310c7a1d81b46a7c5c05919e3.tar.gz", - "hash": "sha256-PEKVSUZ6teRbDyhFPWSiuBSe40pgr0kVRivIY8Cn8HQ=" + "url": "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz", + "hash": "sha256-wiJs6/LUiy+ApC5s7VPypbBukjBr4vjx3v/l9OrT70U=" }, "zf-0.10.3-OIRy8aiIAACLrBllz0zjxaH0aOe5oNm3KtEMyCntST-9": { "name": "zf", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 1dc56da50..46cf07cc9 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -170,11 +170,11 @@ in }; } { - name = "N-V-__8AAEH8MwQaEsARbyV42-bSZGcu1am8xtg2h67wTFC3"; + name = "N-V-__8AAHOASAQuLADcCSHLHEJiKFVLZiCD9Aq2rh5GT01A"; path = fetchZigArtifact { name = "iterm2_themes"; - url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/4c57d8c11d352a4aeda6928b65d78794c28883a5.tar.gz"; - hash = "sha256-c+twvkEPiz1DaULYlnGXLxis19Q2h+TgBJxoARMasjU="; + url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/1e4957e65005908993250f8f07be3f70e805195e.tar.gz"; + hash = "sha256-xpDitXpZrdU/EcgLyG4G0cEiT4r42viy+DJALmy2sQE="; }; } { @@ -282,11 +282,11 @@ in }; } { - name = "z2d-0.6.0-j5P_HvLdCABu-dXpCeRM7Uk4m16vULg1980lMNCQj4_C"; + name = "z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW"; path = fetchZigArtifact { name = "z2d"; - url = "https://github.com/vancluever/z2d/archive/1e89605a624940c310c7a1d81b46a7c5c05919e3.tar.gz"; - hash = "sha256-PEKVSUZ6teRbDyhFPWSiuBSe40pgr0kVRivIY8Cn8HQ="; + url = "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz"; + hash = "sha256-wiJs6/LUiy+ApC5s7VPypbBukjBr4vjx3v/l9OrT70U="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index b9bdc50d2..5f06418a7 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -27,8 +27,8 @@ https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5. https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst -https://github.com/mbadolato/iTerm2-Color-Schemes/archive/4c57d8c11d352a4aeda6928b65d78794c28883a5.tar.gz +https://github.com/mbadolato/iTerm2-Color-Schemes/archive/1e4957e65005908993250f8f07be3f70e805195e.tar.gz https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz -https://github.com/vancluever/z2d/archive/1e89605a624940c310c7a1d81b46a7c5c05919e3.tar.gz +https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz diff --git a/com.mitchellh.ghostty.yml b/com.mitchellh.ghostty.yml deleted file mode 100644 index aa7785b27..000000000 --- a/com.mitchellh.ghostty.yml +++ /dev/null @@ -1,59 +0,0 @@ -# Note: the flatpak build is likely broken right now and is not actively -# maintained. We may completely remove this file in the future. For now, -# we want to keep _trying_ but its something with known issues. -app-id: com.mitchellh.ghostty -runtime: org.gnome.Platform -runtime-version: "43" -sdk: org.gnome.Sdk -default-branch: tip -command: ghostty -build-options: - append-path: /app/tmp/zig - strip: false - no-debuginfo: true -# Note: we have to use cleanup-commands because flatpak-builder doesn't -# run "cleanup" on its own: https://github.com/flatpak/flatpak-builder/issues/14 -cleanup-commands: - - "rm -rf /app/tmp" -finish-args: - # 3D rendering - - --device=dri - # Windowing - - --share=ipc - - --socket=x11 - - --socket=wayland - # Files (we are a terminal so we need all of them) - - --filesystem=host - # So we can escape the sandbox - - --talk-name=org.freedesktop.Flatpak -modules: - # Note: this should be kept in sync with our flake.nix. Over time this - # should stabilize to being a release version and not a nightly. - - name: zig - buildsystem: simple - build-commands: - - mkdir -p /app/tmp/zig - - cp -r ./* /app/tmp/zig - sources: - - type: archive - url: https://ziglang.org/builds/zig-linux-x86_64-0.12.0-dev.141+ddf5859c2.tar.xz - sha256: eaf519b1ec3cb0f3c9bcbc47ead5f50610f9c106279a35b9feb09bed8afc628b - only-arches: - - x86_64 - - type: archive - url: https://ziglang.org/builds/zig-linux-aarch64-0.12.0-dev.141+ddf5859c2.tar.xz - sha256: 4f918ae185a5dc281b5d30be92cd4c36ebd77b8665729c5e2c47a8eeccd243e8 - only-arches: - - aarch64 - - - name: ghostty - buildsystem: simple - build-commands: - - MACH_SDK_PATH="$(pwd)/vendor/mach-sdk" zig build -Doptimize=ReleaseSafe -Dcpu=baseline -Dflatpak=true -Dapp-runtime=gtk --prefix /app - sources: - - type: dir - path: . - skip: - - .flatpak-builder - - zig-cache - - zig-out diff --git a/dist/linux/com.mitchellh.ghostty.metainfo.xml b/dist/linux/com.mitchellh.ghostty.metainfo.xml new file mode 100644 index 000000000..0424d3a09 --- /dev/null +++ b/dist/linux/com.mitchellh.ghostty.metainfo.xml @@ -0,0 +1,59 @@ + + + com.mitchellh.ghostty + com.mitchellh.ghostty.desktop + Ghostty + https://ghostty.org + https://ghostty.org/docs + https://github.com/ghostty-org/ghostty/discussions + https://ghostty.org/docs/help + https://github.com/ghostty-org/ghostty/blob/main/CONTRIBUTING.md + https://github.com/ghostty-org/ghostty/blob/main/po/README_TRANSLATORS.md + https://github.com/ghostty-org/ghostty + Ghostty is a fast, feature-rich, and cross-platform terminal emulator + MIT + MIT + + + Mitchell Hashimoto + + m@mitchellh.com + +

+ Ghostty is a terminal emulator that differentiates itself by being fast, + feature-rich, and native. While there are many excellent terminal + emulators available, they all force you to choose between speed, + features, or native UIs. Ghostty provides all three. +

+
+ + keyboard + pointing + + + + 360 + + com.mitchellh.ghostty + + + + + + + https://ghostty.org/docs/install/release-notes/1-0-1 + + +
diff --git a/flatpak/build-support/check-zig-cache.sh b/flatpak/build-support/check-zig-cache.sh new file mode 100755 index 000000000..bea718640 --- /dev/null +++ b/flatpak/build-support/check-zig-cache.sh @@ -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 diff --git a/flatpak/com.mitchellh.ghostty.Devel.yml b/flatpak/com.mitchellh.ghostty.Devel.yml new file mode 100644 index 000000000..244c3987f --- /dev/null +++ b/flatpak/com.mitchellh.ghostty.Devel.yml @@ -0,0 +1,63 @@ +app-id: com.mitchellh.ghostty.Devel +runtime: org.gnome.Platform +runtime-version: "48" +sdk: org.gnome.Sdk +sdk-extensions: + - org.freedesktop.Sdk.Extension.ziglang +default-branch: tip +command: ghostty +# Integrate the rename into zig build, maybe? +rename-desktop-file: com.mitchellh.ghostty.desktop +rename-appdata-file: com.mitchellh.ghostty.metainfo.xml +rename-icon: com.mitchellh.ghostty +desktop-file-name-suffix: " (Devel)" +finish-args: + # 3D rendering + - --device=dri + # Windowing + - --share=ipc + - --socket=fallback-x11 + - --socket=wayland + # Allow user to specify additional config files in home by default + - --filesystem=home:ro + # So we can escape the sandbox + - --talk-name=org.freedesktop.Flatpak +cleanup: + - /include + - /lib/girepository-1.0 + - /lib/pkgconfig + - /share/gir-1.0 + - /share/pkgconfig + - /share/vala + - "*.la" + - "*.a" + - "*.so" + +modules: + - dependencies.yml + + - name: ghostty + buildsystem: simple + build-options: + append-path: /usr/lib/sdk/ziglang + build-commands: + - zig build + -Doptimize=Debug + -Dcpu=baseline + -Dflatpak=true + -Dstrip=false + -fno-sys=oniguruma + --prefix /app + --search-prefix /app + --system $PWD/vendor/p + sources: + - type: dir + path: .. + skip: + - flatpak/.flatpak-builder + - flatpak/builddir + - flatpak/repo + - zig-cache + - zig-out + + - zig-packages.json diff --git a/flatpak/com.mitchellh.ghostty.yml b/flatpak/com.mitchellh.ghostty.yml new file mode 100644 index 000000000..17c92633f --- /dev/null +++ b/flatpak/com.mitchellh.ghostty.yml @@ -0,0 +1,58 @@ +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: + # 3D rendering + - --device=dri + # Windowing + - --share=ipc + - --socket=fallback-x11 + - --socket=wayland + # Allow user to specify additional config files in home by default + - --filesystem=home:ro + # So we can escape the sandbox + - --talk-name=org.freedesktop.Flatpak +cleanup: + - /include + - /lib/girepository-1.0 + - /lib/pkgconfig + - /share/gir-1.0 + - /share/pkgconfig + - /share/vala + - "*.la" + - "*.a" + - "*.so" + +modules: + - dependencies.yml + + - name: ghostty + buildsystem: simple + build-options: + append-path: /usr/lib/sdk/ziglang + build-commands: + - zig build + -Doptimize=ReleaseFast + -Dcpu=baseline + -Dflatpak=true + -Dstrip=false + -fno-sys=oniguruma + --prefix /app + --search-prefix /app + --system $PWD/vendor/p + sources: + - type: dir + path: .. + skip: + - flatpak/.flatpak-builder + - flatpak/builddir + - flatpak/repo + - zig-cache + - zig-out + + - zig-packages.json diff --git a/flatpak/dependencies.yml b/flatpak/dependencies.yml new file mode 100644 index 000000000..efb5851e9 --- /dev/null +++ b/flatpak/dependencies.yml @@ -0,0 +1,66 @@ +name: dependencies-meta +buildsystem: simple +build-commands: + - true +modules: + - name: bzip2-redirect + buildsystem: simple + build-commands: + - install -Dm644 libbzip2.so /app/lib/libbzip2.so + sources: + - type: inline + contents: INPUT(libbz2.so) + dest-filename: libbzip2.so + + - name: blueprint-compiler + buildsystem: meson + cleanup: + - "*" + sources: + - type: git + url: https://gitlab.gnome.org/jwestman/blueprint-compiler.git + tag: v0.16.0 + commit: 04ef0944db56ab01307a29aaa7303df6067cb3c0 + x-checker-data: + type: git + tag-pattern: ^v([\d.]+)$ + + - name: gtk4-layer-shell + buildsystem: meson + sources: + # no x-checker-data since this should be synchronized with Nix + # + # TODO: Automate this with check-zig-cache.sh + - type: archive + url: https://github.com/wmww/gtk4-layer-shell/archive/refs/tags/v1.1.0.tar.gz + sha256: 98284281260a5eef5b4f63a55f16c4bf6a788a1020a6db037ecb0f71fa336988 + + - name: pandoc + buildsystem: simple + cleanup: + - "*" + build-commands: + - install -Dm755 bin/pandoc /app/bin/pandoc + sources: + - type: archive + sha256: d04c95c138202f87d6b00ac19aa3dd874c681f60a9feb3b55c74f764d6d1a17d + url: https://github.com/jgm/pandoc/releases/download/3.6.3/pandoc-3.6.3-linux-amd64.tar.gz + only-arches: [x86_64] + x-checker-data: + type: json + url: https://api.github.com/repos/jgm/pandoc/releases/latest + url-query: + .assets[] | select(.name=="pandoc-" + $version + "-linux-amd64.tar.gz") + | .browser_download_url + version-query: .tag_name + - type: archive + sha256: 4e774cb1bdb6e56bc55b8eb79200bd9aa6a39905a04ecda7267f5149116f0881 + url: https://github.com/jgm/pandoc/releases/download/3.6.3/pandoc-3.6.3-linux-arm64.tar.gz + only-arches: [aarch64] + x-checker-data: + type: json + url: https://api.github.com/repos/jgm/pandoc/releases/latest + url-query: + .assets[] | select(.name=="pandoc-" + $version + "-linux-arm64.tar.gz") + | .browser_download_url + version-query: .tag_name diff --git a/flatpak/exceptions.json b/flatpak/exceptions.json new file mode 100644 index 000000000..176e8c320 --- /dev/null +++ b/flatpak/exceptions.json @@ -0,0 +1,3 @@ +{ + "com.mitchellh.ghostty": ["finish-args-flatpak-spawn-access"] +} diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json new file mode 100644 index 000000000..bc3b6cd0c --- /dev/null +++ b/flatpak/zig-packages.json @@ -0,0 +1,206 @@ +[ + { + "type": "archive", + "url": "https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz", + "dest": "vendor/p/N-V-__8AALw2uwF_03u4JRkZwRLc3Y9hakkYV7NKRR9-RIZJ", + "sha256": "6cca98943d1a990766cef61077c09aff5938063fe17a1efe1228e5412b6d6ad9" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz", + "dest": "vendor/p/N-V-__8AAIrfdwARSa-zMmxWwFuwpXf1T3asIN7s5jqi9c1v", + "sha256": "3ba2dd92158718acec5caaf1a716043b5aa055c27b081d914af3ccb40dce8a55" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz", + "dest": "vendor/p/N-V-__8AAKLKpwC4H27Ps_0iL3bPkQb-z6ZVSrB-x_3EEkub", + "sha256": "427201f5d5151670d05c1f5b45bef5dda1f2e7dd971ef54f0feaaa7ffd2ab90c" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/gettext-0.24.tar.gz", + "dest": "vendor/p/N-V-__8AADcZkgn4cMhTUpIz6mShCKyqqB-NBtf_S2bHaTC-", + "sha256": "c918503d593d70daf4844d175a13d816afacb667c06fba1ec9dcd5002c1518b7" + }, + { + "type": "archive", + "url": "https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz", + "dest": "vendor/p/N-V-__8AAMrJSwAUGb9-vTzkNR-5LXS81MR__ZRVfF3tWgG6", + "sha256": "3373755d402531e6c1a395f53f2fbd6318ca5e067a79a72a59109b526c0b290a" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz", + "dest": "vendor/p/N-V-__8AABzkUgISeKGgXAzgtutgJsZc0-kkeqBBscJgMkvy", + "sha256": "14a2edbb509cb3e51a9a53e3f5e435dbf5971604b4b833e63e6076e8c0a997b5" + }, + { + "type": "archive", + "url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst", + "dest": "vendor/p/gobject-0.2.0-Skun7IWDlQAOKu4BV7LapIxL9Imbq1JRmzvcIkazvAxR", + "sha256": "85672997459ddd7c9d4fe458efe548a315cf842cde95ed48a7be984a1f8a98b2" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz", + "dest": "vendor/p/N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr", + "sha256": "98284281260a5eef5b4f63a55f16c4bf6a788a1020a6db037ecb0f71fa336988" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz", + "dest": "vendor/p/N-V-__8AAG02ugUcWec-Ndp-i7JTsJ0dgF8nnJRUInkGLG7G", + "sha256": "f16351bafe214725fe2c1d5b59f0d93e49905a4b247899fb90d70cff953a2b9b" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/highway-66486a10623fa0d72fe91260f96c892e41aceb06.tar.gz", + "dest": "vendor/p/N-V-__8AAGmZhABbsPJLfbqrh6JTHsXhY6qCaLAQyx25e0XE", + "sha256": "87d4f8893ef4e08f224973608ffebf94268a81380ba79c12e8841968c80aa212" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz", + "dest": "vendor/p/N-V-__8AAH0GaQC8a52s6vfIxg88OZgFgEW6DFxfSK4lX_l3", + "sha256": "a05fd01e04cf11ab781e28387c621d2e420f1e6044c8e27a25e603ea99ef7860" + }, + { + "type": "archive", + "url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/1e4957e65005908993250f8f07be3f70e805195e.tar.gz", + "dest": "vendor/p/N-V-__8AAHOASAQuLADcCSHLHEJiKFVLZiCD9Aq2rh5GT01A", + "sha256": "c690e2b57a59add53f11c80bc86e06d1c1224f8af8daf8b2f832402e6cb6b101" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz", + "dest": "vendor/p/N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD", + "sha256": "fecc95b46cf05e8e3fc8a414750e0ba5aad00d89e9fdf175e94ff041caf1a03a" + }, + { + "type": "archive", + "url": "https://github.com/mitchellh/libxev/archive/3df9337a9e84450a58a2c4af434ec1a036f7b494.tar.gz", + "dest": "vendor/p/libxev-0.0.0-86vtc-ziEgDbLP0vihUn1MhsxNKY4GJEga6BEr7oyHpz", + "sha256": "a0a66a03d77bf631e7a7f1eca89590137dc57e7e447b91b85679507a942e638a" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz", + "dest": "vendor/p/N-V-__8AAG3RoQEyRC2Vw7Qoro5SYBf62IHn3HjqtNVY6aWK", + "sha256": "6c28059e2e3eeb42b5b4b16489e3916a6346c1095a74fee3bc65cdc5d89a6215" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz", + "dest": "vendor/p/N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c", + "sha256": "001aa1202e78448f4c0bf1a48c76e556876b36f16d92ce3207eccfd61d99f2a0" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz", + "dest": "vendor/p/N-V-__8AADYiAAB_80AWnH1AxXC0tql9thT-R-DYO1gBqTLc", + "sha256": "55e83b16d091082502bf149bf457f31f42092c5982650e3ffbae7b48871bf11a" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz", + "dest": "vendor/p/N-V-__8AAKYZBAB-CFHBKs3u4JkeiT4BMvyHu3Y5aaWF3Bbs", + "sha256": "5c58ba214acd8e6bca3426dc08b022c46a8dd997b29a1b3e28badf71c20df441" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz", + "dest": "vendor/p/N-V-__8AAPlZGwBEa-gxrcypGBZ2R8Bse4JYSfo_ul8i2jlG", + "sha256": "2ac6497cc8d61a8d31093e47addb8c9b2c45b16b0705bb334a835b6423c318df" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz", + "dest": "vendor/p/N-V-__8AANb6pwD7O1WG6L5nvD_rNMvnSc9Cpg1ijSlTYywv", + "sha256": "b52b6fcfc45e7fa69b1f06a1362c155473444e2cc09995556b156c53ba6657e3" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/utfcpp-1220d4d18426ca72fc2b7e56ce47273149815501d0d2395c2a98c726b31ba931e641.tar.gz", + "dest": "vendor/p/N-V-__8AAHffAgDU0YQmynL8K35WzkcnMUmBVQHQ0jlcKpjH", + "sha256": "ffc668a310e77607d393f3c18b32715f223da1eac4c4d6e0579a11df8e6b59cf" + }, + { + "type": "git", + "url": "https://github.com/rockorager/libvaxis", + "commit": "1f41c121e8fc153d9ce8c6eb64b2bbab68ad7d23", + "dest": "vendor/p/vaxis-0.1.0-BWNV_FUICQAFZnTCL11TUvnUr1Y0_ZdqtXHhd51d76Rn" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz", + "dest": "vendor/p/N-V-__8AAKrHGAAs2shYq8UkE6bGcR1QJtLTyOE_lcosMn6t", + "sha256": "ea4191d68e437677e51f3aacde27829810144e931d397a327dc6035e2c39c50d" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz", + "dest": "vendor/p/N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S", + "sha256": "5cedcadde81b75e60f23e5e83b5dd2b8eb4efb9f8f79bd7a347d148aeb0530f8" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", + "dest": "vendor/p/N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs", + "sha256": "9e4cd20abe96e6c4c6ede9c3057108860126e7be2e2c3e35515476c250be1c13" + }, + { + "type": "archive", + "url": "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz", + "dest": "vendor/p/z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW", + "sha256": "c2226cebf2d48b2f80a42e6ced53f2a5b06e92306be2f8f1deffe5f4ead3ef45" + }, + { + "type": "archive", + "url": "https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz", + "dest": "vendor/p/zf-0.10.3-OIRy8aiIAACLrBllz0zjxaH0aOe5oNm3KtEMyCntST-9", + "sha256": "de7ba535077fe2b678a5a7972585f002588d37244db08397feadf3d4907c0bb2" + }, + { + "type": "git", + "url": "https://codeberg.org/atman/zg", + "commit": "4a002763419a34d61dcbb1f415821b83b9bf8ddc", + "dest": "vendor/p/zg-0.13.4-AAAAAGiZ7QLz4pvECFa_wG4O4TP4FLABHHbemH2KakWM" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz", + "dest": "vendor/p/N-V-__8AAB9YCQBaZtQjJZVndk-g_GDIK-NTZcIa63bFp9yZ", + "sha256": "7f235e0956c2f5401a28963a261019953d00e3bf4cfc029830f2161196c3583d" + }, + { + "type": "archive", + "url": "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz", + "dest": "vendor/p/zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt", + "sha256": "ce7d6d47ac614a60e56b8509dedf869e2e0d8b747c75e48aded11eec31b3357c" + }, + { + "type": "archive", + "url": "https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz", + "dest": "vendor/p/wayland-0.4.0-dev-lQa1kjfIAQCmhhQu3xF0KH-94-TzeMXOqfnP0-Dg6Wyy", + "sha256": "13bec6675e403d86db3b55b39ae262f1e1bdfe24056dcd82824341c6308b5219" + }, + { + "type": "git", + "url": "https://github.com/TUSF/zigimg", + "commit": "31268548fe3276c0e95f318a6c0d2ab10565b58d", + "dest": "vendor/p/zigimg-0.1.0-lly-O6N2EABOxke8dqyzCwhtUCAafqP35zC7wsZ4Ddxj" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz", + "dest": "vendor/p/ziglyph-0.11.2-AAAAAHPtHwB4Mbzn1KvOV7Wpjo82NYEc_v0WC8oCLrkf", + "sha256": "72c7bdf3e16df105235fe3fcf32c987dac49389190f4ced89b0ee31710f3f3d9" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz", + "dest": "vendor/p/N-V-__8AAB0eQwD-0MdOEBmz7intriBReIsIDNlukNVoNu6o", + "sha256": "17e88863f3600672ab49182f217281b6fc4d3c762bde361935e436a95214d05c" + } +] diff --git a/include/ghostty.h b/include/ghostty.h index c4ef11930..18c547910 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -279,6 +279,13 @@ typedef struct { ghostty_input_mods_e mods; } ghostty_input_trigger_s; +typedef struct { + const char* action_key; + const char* action; + const char* title; + const char* description; +} ghostty_command_s; + typedef enum { GHOSTTY_BUILD_MODE_DEBUG, GHOSTTY_BUILD_MODE_RELEASE_SAFE, @@ -350,6 +357,11 @@ typedef struct { size_t len; } ghostty_config_color_list_s; +// config.Palette +typedef struct { + ghostty_config_color_s colors[256]; +} ghostty_config_palette_s; + // apprt.Target.Key typedef enum { GHOSTTY_TARGET_APP, @@ -417,6 +429,13 @@ typedef enum { GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH, } ghostty_action_fullscreen_e; +// apprt.action.FloatWindow +typedef enum { + GHOSTTY_FLOAT_WINDOW_ON, + GHOSTTY_FLOAT_WINDOW_OFF, + GHOSTTY_FLOAT_WINDOW_TOGGLE, +} ghostty_action_float_window_e; + // apprt.action.SecureInput typedef enum { GHOSTTY_SECURE_INPUT_ON, @@ -573,6 +592,7 @@ typedef enum { GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW, GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS, GHOSTTY_ACTION_TOGGLE_QUICK_TERMINAL, + GHOSTTY_ACTION_TOGGLE_COMMAND_PALETTE, GHOSTTY_ACTION_TOGGLE_VISIBILITY, GHOSTTY_ACTION_MOVE_TAB, GHOSTTY_ACTION_GOTO_TAB, @@ -597,6 +617,7 @@ typedef enum { GHOSTTY_ACTION_RENDERER_HEALTH, GHOSTTY_ACTION_OPEN_CONFIG, GHOSTTY_ACTION_QUIT_TIMER, + GHOSTTY_ACTION_FLOAT_WINDOW, GHOSTTY_ACTION_SECURE_INPUT, GHOSTTY_ACTION_KEY_SEQUENCE, GHOSTTY_ACTION_COLOR_CHANGE, @@ -625,6 +646,7 @@ typedef union { ghostty_action_mouse_over_link_s mouse_over_link; ghostty_action_renderer_health_e renderer_health; ghostty_action_quit_timer_e quit_timer; + ghostty_action_float_window_e float_window; ghostty_action_secure_input_e secure_input; ghostty_action_key_sequence_s key_sequence; ghostty_action_color_change_s color_change; @@ -724,6 +746,7 @@ void ghostty_surface_set_color_scheme(ghostty_surface_t, ghostty_color_scheme_e); ghostty_input_mods_e ghostty_surface_key_translation_mods(ghostty_surface_t, ghostty_input_mods_e); +void ghostty_surface_commands(ghostty_surface_t, ghostty_command_s**, size_t*); bool ghostty_surface_key(ghostty_surface_t, ghostty_input_key_s); bool ghostty_surface_key_is_binding(ghostty_surface_t, ghostty_input_key_s); void ghostty_surface_text(ghostty_surface_t, const char*, uintptr_t); diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index b69541504..a34c4685f 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -34,6 +34,10 @@ A5333E242B5A22D9008AEFF7 /* Ghostty.Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */; }; A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */; }; A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A535B9D9299C569B0017E2E4 /* ErrorView.swift */; }; + A53A297B2DB2E49700B6E02C /* CommandPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A297A2DB2E49400B6E02C /* CommandPalette.swift */; }; + A53A297F2DB4480F00B6E02C /* EventModifiers+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A297E2DB4480A00B6E02C /* EventModifiers+Extension.swift */; }; + A53A29812DB44A6100B6E02C /* KeyboardShortcut+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A29802DB44A5E00B6E02C /* KeyboardShortcut+Extension.swift */; }; + A53A29882DB69D2F00B6E02C /* TerminalCommandPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A29872DB69D2C00B6E02C /* TerminalCommandPalette.swift */; }; A53A6C032CCC1B7F00943E98 /* Ghostty.Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */; }; A53D0C8E2B53B0EA00305CE6 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; }; A53D0C942B53B43700305CE6 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C932B53B43700305CE6 /* iOSApp.swift */; }; @@ -138,6 +142,10 @@ A5333E212B5A2128008AEFF7 /* SurfaceView_AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView_AppKit.swift; sourceTree = ""; }; A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; A535B9D9299C569B0017E2E4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; + A53A297A2DB2E49400B6E02C /* CommandPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandPalette.swift; sourceTree = ""; }; + A53A297E2DB4480A00B6E02C /* EventModifiers+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EventModifiers+Extension.swift"; sourceTree = ""; }; + A53A29802DB44A5E00B6E02C /* KeyboardShortcut+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyboardShortcut+Extension.swift"; sourceTree = ""; }; + A53A29872DB69D2C00B6E02C /* TerminalCommandPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalCommandPalette.swift; sourceTree = ""; }; A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Action.swift; sourceTree = ""; }; A53D0C932B53B43700305CE6 /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.App.swift; sourceTree = ""; }; @@ -267,6 +275,7 @@ A5CBD05A2CA0C5910017A1AE /* QuickTerminal */, A5E112912AF73E4D00C6E0C2 /* ClipboardConfirmation */, A57D79252C9C8782001D522E /* Secure Input */, + A53A29742DB2E04900B6E02C /* Command Palette */, A534263E2A7DCC5800EBB7A2 /* Settings */, A51BFC1C2B2FB5AB00E92F16 /* About */, A54B0CE72D0CEC9800CBEFF8 /* Colorized Ghostty Icon */, @@ -288,8 +297,10 @@ A52FFF582CAA4FF1000C6A5B /* Fullscreen.swift */, A59630962AEE163600D64628 /* HostingWindow.swift */, A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */, + A53A29802DB44A5E00B6E02C /* KeyboardShortcut+Extension.swift */, A59FB5D02AE0DEA7009128F3 /* MetalView.swift */, A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */, + A53A297E2DB4480A00B6E02C /* EventModifiers+Extension.swift */, C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */, A599CDAF2CF103F20049FA26 /* NSAppearance+Extension.swift */, A5A2A3CB2D444AB80033CF96 /* NSApplication+Extension.swift */, @@ -319,6 +330,15 @@ path = Settings; sourceTree = ""; }; + A53A29742DB2E04900B6E02C /* Command Palette */ = { + isa = PBXGroup; + children = ( + A53A297A2DB2E49400B6E02C /* CommandPalette.swift */, + A53A29872DB69D2C00B6E02C /* TerminalCommandPalette.swift */, + ); + path = "Command Palette"; + sourceTree = ""; + }; A53D0C912B53B41900305CE6 /* App */ = { isa = PBXGroup; children = ( @@ -667,6 +687,7 @@ A5A2A3CA2D4445E30033CF96 /* Dock.swift in Sources */, A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */, A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */, + A53A29812DB44A6100B6E02C /* KeyboardShortcut+Extension.swift in Sources */, A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */, C1F26EE92B76CBFC00404083 /* VibrantLayer.m in Sources */, A59630972AEE163600D64628 /* HostingWindow.swift in Sources */, @@ -691,6 +712,8 @@ A5A2A3CC2D444ABB0033CF96 /* NSApplication+Extension.swift in Sources */, A59630A22AF0415000D64628 /* Ghostty.TerminalSplit.swift in Sources */, A5FEB3002ABB69450068369E /* main.swift in Sources */, + A53A297F2DB4480F00B6E02C /* EventModifiers+Extension.swift in Sources */, + A53A297B2DB2E49700B6E02C /* CommandPalette.swift in Sources */, A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */, A51B78472AF4B58B00F3EDB9 /* TerminalWindow.swift in Sources */, A57D79272C9C879B001D522E /* SecureInput.swift in Sources */, @@ -706,6 +729,7 @@ A52FFF572CA90484000C6A5B /* QuickTerminalScreen.swift in Sources */, A5CC36132C9CD72D004D6760 /* SecureInputOverlay.swift in Sources */, A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */, + A53A29882DB69D2F00B6E02C /* TerminalCommandPalette.swift in Sources */, A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */, A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */, A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */, diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 9d866d734..a3a3185d9 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -52,6 +52,8 @@ class AppDelegate: NSObject, @IBOutlet private var menuSelectSplitLeft: NSMenuItem? @IBOutlet private var menuSelectSplitRight: NSMenuItem? @IBOutlet private var menuReturnToDefaultSize: NSMenuItem? + @IBOutlet private var menuFloatOnTop: NSMenuItem? + @IBOutlet private var menuUseAsDefault: NSMenuItem? @IBOutlet private var menuIncreaseFontSize: NSMenuItem? @IBOutlet private var menuDecreaseFontSize: NSMenuItem? @@ -59,6 +61,7 @@ class AppDelegate: NSObject, @IBOutlet private var menuChangeTitle: NSMenuItem? @IBOutlet private var menuQuickTerminal: NSMenuItem? @IBOutlet private var menuTerminalInspector: NSMenuItem? + @IBOutlet private var menuCommandPalette: NSMenuItem? @IBOutlet private var menuEqualizeSplits: NSMenuItem? @IBOutlet private var menuMoveSplitDividerUp: NSMenuItem? @@ -174,6 +177,12 @@ class AppDelegate: NSObject, handler: localEventHandler) // Notifications + NotificationCenter.default.addObserver( + self, + selector: #selector(windowDidBecomeKey), + name: NSWindow.didBecomeKeyNotification, + object: nil + ) NotificationCenter.default.addObserver( self, selector: #selector(quickTerminalDidChangeVisibility), @@ -199,6 +208,7 @@ class AppDelegate: NSObject, ] let center = UNUserNotificationCenter.current() + center.setNotificationCategories([ UNNotificationCategory( identifier: Ghostty.userNotificationCategory, @@ -231,6 +241,9 @@ class AppDelegate: NSObject, // If we're back manually then clear the hidden state because macOS handles it. self.hiddenState = nil + // Clear the dock badge when the app becomes active + self.setDockBadge(nil) + // First launch stuff if (!applicationHasBecomeActive) { applicationHasBecomeActive = true @@ -398,10 +411,12 @@ class AppDelegate: NSObject, syncMenuShortcut(config, action: "increase_font_size:1", menuItem: self.menuIncreaseFontSize) syncMenuShortcut(config, action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize) syncMenuShortcut(config, action: "reset_font_size", menuItem: self.menuResetFontSize) - syncMenuShortcut(config, action: "change_title_prompt", menuItem: self.menuChangeTitle) + syncMenuShortcut(config, action: "prompt_surface_title", menuItem: self.menuChangeTitle) syncMenuShortcut(config, action: "toggle_quick_terminal", menuItem: self.menuQuickTerminal) syncMenuShortcut(config, action: "toggle_visibility", menuItem: self.menuToggleVisibility) + syncMenuShortcut(config, action: "toggle_window_float_on_top", menuItem: self.menuFloatOnTop) syncMenuShortcut(config, action: "inspector:toggle", menuItem: self.menuTerminalInspector) + syncMenuShortcut(config, action: "toggle_command_palette", menuItem: self.menuCommandPalette) syncMenuShortcut(config, action: "toggle_secure_input", menuItem: self.menuSecureInput) @@ -419,15 +434,15 @@ class AppDelegate: NSObject, /// action string used for the Ghostty configuration. private func syncMenuShortcut(_ config: Ghostty.Config, action: String, menuItem: NSMenuItem?) { guard let menu = menuItem else { return } - guard let equiv = config.keyEquivalent(for: action) else { + guard let shortcut = config.keyboardShortcut(for: action) else { // No shortcut, clear the menu item menu.keyEquivalent = "" menu.keyEquivalentModifierMask = [] return } - menu.keyEquivalent = equiv.key - menu.keyEquivalentModifierMask = equiv.modifiers + menu.keyEquivalent = shortcut.key.character.description + menu.keyEquivalentModifierMask = .init(swiftUIFlags: shortcut.modifiers) } private func focusedSurface() -> ghostty_surface_t? { @@ -491,6 +506,10 @@ class AppDelegate: NSObject, return event } + @objc private func windowDidBecomeKey(_ notification: Notification) { + syncFloatOnTopMenu(notification.object as? NSWindow) + } + @objc private func quickTerminalDidChangeVisibility(_ notification: Notification) { guard let quickController = notification.object as? QuickTerminalController else { return } self.menuQuickTerminal?.state = if (quickController.visible) { .on } else { .off } @@ -511,6 +530,53 @@ class AppDelegate: NSObject, @objc private func ghosttyBellDidRing(_ notification: Notification) { // Bounce the dock icon if we're not focused. NSApp.requestUserAttention(.informationalRequest) + + // Handle setting the dock badge based on permissions + ghosttyUpdateBadgeForBell() + } + + private func ghosttyUpdateBadgeForBell() { + let center = UNUserNotificationCenter.current() + center.getNotificationSettings { settings in + switch settings.authorizationStatus { + case .authorized: + // Already authorized, check badge setting and set if enabled + if settings.badgeSetting == .enabled { + DispatchQueue.main.async { + self.setDockBadge() + } + } + + case .notDetermined: + // Not determined yet, request authorization for badge + center.requestAuthorization(options: [.badge]) { granted, error in + if let error = error { + Self.logger.warning("Error requesting badge authorization: \(error)") + return + } + + if granted { + // Permission granted, set the badge + DispatchQueue.main.async { + self.setDockBadge() + } + } + } + + case .denied, .provisional, .ephemeral: + // In these known non-authorized states, do not attempt to set the badge. + break + + @unknown default: + // Handle future unknown states by doing nothing. + break + } + } + } + + private func setDockBadge(_ label: String? = "•") { + NSApp.dockTile.badgeLabel = label + NSApp.dockTile.display() } private func ghosttyConfigDidChange(config: Ghostty.Config) { @@ -790,12 +856,12 @@ class AppDelegate: NSObject, hiddenState?.restore() hiddenState = nil } - + @IBAction func bringAllToFront(_ sender: Any) { if !NSApp.isActive { NSApp.activate(ignoringOtherApps: true) } - + NSApplication.shared.arrangeInFront(sender) } @@ -846,3 +912,50 @@ class AppDelegate: NSObject, } } } + +// MARK: Floating Windows + +extension AppDelegate { + func syncFloatOnTopMenu(_ window: NSWindow?) { + guard let window = (window ?? NSApp.keyWindow) as? TerminalWindow else { + // If some other window became key we always turn this off + self.menuFloatOnTop?.state = .off + return + } + + self.menuFloatOnTop?.state = window.level == .floating ? .on : .off + } + + @IBAction func floatOnTop(_ menuItem: NSMenuItem) { + menuItem.state = menuItem.state == .on ? .off : .on + guard let window = NSApp.keyWindow else { return } + window.level = menuItem.state == .on ? .floating : .normal + } + + @IBAction func useAsDefault(_ sender: NSMenuItem) { + let ud = UserDefaults.standard + let key = TerminalWindow.defaultLevelKey + if (menuFloatOnTop?.state == .on) { + ud.set(NSWindow.Level.floating, forKey: key) + } else { + ud.removeObject(forKey: key) + } + } +} + +// MARK: NSMenuItemValidation + +extension AppDelegate: NSMenuItemValidation { + func validateMenuItem(_ item: NSMenuItem) -> Bool { + switch item.action { + case #selector(floatOnTop(_:)), + #selector(useAsDefault(_:)): + // Float on top items only active if the key window is a primary + // terminal window (not quick terminal). + return NSApp.keyWindow is TerminalWindow + + default: + return true + } + } +} diff --git a/macos/Sources/App/macOS/MainMenu.xib b/macos/Sources/App/macOS/MainMenu.xib index 88db6ed01..724f21355 100644 --- a/macos/Sources/App/macOS/MainMenu.xib +++ b/macos/Sources/App/macOS/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -21,9 +21,11 @@ + + @@ -55,6 +57,7 @@ + @@ -249,6 +252,12 @@ + + + + + + @@ -395,6 +404,19 @@ + + + + + + + + + + + + + diff --git a/macos/Sources/Features/Command Palette/CommandPalette.swift b/macos/Sources/Features/Command Palette/CommandPalette.swift new file mode 100644 index 000000000..3e5a3a36f --- /dev/null +++ b/macos/Sources/Features/Command Palette/CommandPalette.swift @@ -0,0 +1,276 @@ +import SwiftUI + +struct CommandOption: Identifiable, Hashable { + let id = UUID() + let title: String + let description: String? + let symbols: [String]? + let action: () -> Void + + static func == (lhs: CommandOption, rhs: CommandOption) -> Bool { + lhs.id == rhs.id + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} + +struct CommandPaletteView: View { + @Binding var isPresented: Bool + var backgroundColor: Color = Color(nsColor: .windowBackgroundColor) + var options: [CommandOption] + @State private var query = "" + @State private var selectedIndex: UInt? + @State private var hoveredOptionID: UUID? + + // The options that we should show, taking into account any filtering from + // the query. + var filteredOptions: [CommandOption] { + if query.isEmpty { + return options + } else { + return options.filter { $0.title.localizedCaseInsensitiveContains(query) } + } + } + + var selectedOption: CommandOption? { + guard let selectedIndex else { return nil } + return if selectedIndex < filteredOptions.count { + filteredOptions[Int(selectedIndex)] + } else { + filteredOptions.last + } + } + + var body: some View { + let scheme: ColorScheme = if OSColor(backgroundColor).isLightColor { + .light + } else { + .dark + } + + VStack(alignment: .leading, spacing: 0) { + CommandPaletteQuery(query: $query) { event in + switch (event) { + case .exit: + isPresented = false + + case .submit: + isPresented = false + selectedOption?.action() + + case .move(.up): + if filteredOptions.isEmpty { break } + let current = selectedIndex ?? UInt(filteredOptions.count) + selectedIndex = (current == 0) + ? UInt(filteredOptions.count - 1) + : current - 1 + + case .move(.down): + if filteredOptions.isEmpty { break } + let current = selectedIndex ?? UInt.max + selectedIndex = (current >= UInt(filteredOptions.count - 1)) + ? 0 + : current + 1 + + case .move(_): + // Unknown, ignore + break + } + } + .onChange(of: query) { newValue in + // If the user types a query then we want to make sure the first + // value is selected. If the user clears the query and we were selecting + // the first, we unset any selection. + if !newValue.isEmpty { + if selectedIndex == nil { + selectedIndex = 0 + } + } else { + if let selectedIndex, selectedIndex == 0 { + self.selectedIndex = nil + } + } + } + + Divider() + + CommandTable( + options: filteredOptions, + selectedIndex: $selectedIndex, + hoveredOptionID: $hoveredOptionID) { option in + isPresented = false + option.action() + } + } + .frame(maxWidth: 500) + .background( + ZStack { + Rectangle() + .fill(.ultraThinMaterial) + Rectangle() + .fill(backgroundColor) + .blendMode(.color) + } + .compositingGroup() + ) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(Color(nsColor: .tertiaryLabelColor).opacity(0.75)) + ) + .shadow(radius: 32, x: 0, y: 12) + .padding() + .environment(\.colorScheme, scheme) + } +} + +/// The text field for building the query for the command palette. +fileprivate struct CommandPaletteQuery: View { + @Binding var query: String + var onEvent: ((KeyboardEvent) -> Void)? = nil + @FocusState private var isTextFieldFocused: Bool + + enum KeyboardEvent { + case exit + case submit + case move(MoveCommandDirection) + } + + var body: some View { + ZStack { + Group { + Button { onEvent?(.move(.up)) } label: { Color.clear } + .buttonStyle(PlainButtonStyle()) + .keyboardShortcut(.upArrow, modifiers: []) + Button { onEvent?(.move(.down)) } label: { Color.clear } + .buttonStyle(PlainButtonStyle()) + .keyboardShortcut(.downArrow, modifiers: []) + + Button { onEvent?(.move(.up)) } label: { Color.clear } + .buttonStyle(PlainButtonStyle()) + .keyboardShortcut(.init("p"), modifiers: [.control]) + Button { onEvent?(.move(.down)) } label: { Color.clear } + .buttonStyle(PlainButtonStyle()) + .keyboardShortcut(.init("n"), modifiers: [.control]) + } + .frame(width: 0, height: 0) + .accessibilityHidden(true) + + TextField("Execute a command…", text: $query) + .padding() + .font(.system(size: 20, weight: .light)) + .frame(height: 48) + .textFieldStyle(.plain) + .focused($isTextFieldFocused) + .onAppear { + isTextFieldFocused = true + } + .onChange(of: isTextFieldFocused) { focused in + if !focused { + onEvent?(.exit) + } + } + .onExitCommand { onEvent?(.exit) } + .onMoveCommand { onEvent?(.move($0)) } + .onSubmit { onEvent?(.submit) } + } + } +} + +fileprivate struct CommandTable: View { + var options: [CommandOption] + @Binding var selectedIndex: UInt? + @Binding var hoveredOptionID: UUID? + var action: (CommandOption) -> Void + + var body: some View { + if options.isEmpty { + Text("No matches") + .foregroundStyle(.secondary) + .padding() + } else { + ScrollViewReader { proxy in + ScrollView { + VStack(alignment: .leading, spacing: 0) { + ForEach(Array(options.enumerated()), id: \.1.id) { index, option in + CommandRow( + option: option, + isSelected: { + if let selected = selectedIndex { + return selected == index || + (selected >= options.count && + index == options.count - 1) + } else { + return false + } + }(), + hoveredID: $hoveredOptionID + ) { + action(option) + } + } + } + .padding(10) + } + .frame(maxHeight: 200) + .onChange(of: selectedIndex) { _ in + guard let selectedIndex, + selectedIndex < options.count else { return } + proxy.scrollTo( + options[Int(selectedIndex)].id) + } + } + } + } +} + +/// A single row in the command palette. +fileprivate struct CommandRow: View { + let option: CommandOption + var isSelected: Bool + @Binding var hoveredID: UUID? + var action: () -> Void + + var body: some View { + Button(action: action) { + HStack { + Text(option.title) + Spacer() + if let symbols = option.symbols { + ShortcutSymbolsView(symbols: symbols) + .foregroundStyle(.secondary) + } + } + .padding(8) + .background( + isSelected + ? Color.accentColor.opacity(0.2) + : (hoveredID == option.id + ? Color.secondary.opacity(0.2) + : Color.clear) + ) + .cornerRadius(5) + } + .help(option.description ?? "") + .buttonStyle(.plain) + .onHover { hovering in + hoveredID = hovering ? option.id : nil + } + } +} + +/// A row of Text representing a shortcut. +fileprivate struct ShortcutSymbolsView: View { + let symbols: [String] + + var body: some View { + HStack(spacing: 1) { + ForEach(symbols, id: \.self) { symbol in + Text(symbol) + .frame(minWidth: 13) + } + } + } +} diff --git a/macos/Sources/Features/Command Palette/TerminalCommandPalette.swift b/macos/Sources/Features/Command Palette/TerminalCommandPalette.swift new file mode 100644 index 000000000..57a76dd43 --- /dev/null +++ b/macos/Sources/Features/Command Palette/TerminalCommandPalette.swift @@ -0,0 +1,105 @@ +import SwiftUI +import GhosttyKit + +struct TerminalCommandPaletteView: View { + /// The surface that this command palette represents. + let surfaceView: Ghostty.SurfaceView + + /// Set this to true to show the view, this will be set to false if any actions + /// result in the view disappearing. + @Binding var isPresented: Bool + + /// The configuration so we can lookup keyboard shortcuts. + @ObservedObject var ghosttyConfig: Ghostty.Config + + /// The callback when an action is submitted. + var onAction: ((String) -> Void) + + // The commands available to the command palette. + private var commandOptions: [CommandOption] { + guard let surface = surfaceView.surface else { return [] } + + var ptr: UnsafeMutablePointer? = nil + var count: Int = 0 + ghostty_surface_commands(surface, &ptr, &count) + guard let ptr else { return [] } + + let buffer = UnsafeBufferPointer(start: ptr, count: count) + return Array(buffer).filter { c in + let key = String(cString: c.action_key) + switch (key) { + case "toggle_tab_overview", + "toggle_window_decorations": + return false + default: + return true + } + }.map { c in + let action = String(cString: c.action) + return CommandOption( + title: String(cString: c.title), + description: String(cString: c.description), + symbols: ghosttyConfig.keyboardShortcut(for: action)?.keyList + ) { + onAction(action) + } + } + } + + var body: some View { + ZStack { + if isPresented { + GeometryReader { geometry in + VStack { + Spacer().frame(height: geometry.size.height * 0.05) + + ResponderChainInjector(responder: surfaceView) + .frame(width: 0, height: 0) + + CommandPaletteView( + isPresented: $isPresented, + backgroundColor: ghosttyConfig.backgroundColor, + options: commandOptions + ) + .transition( + .move(edge: .top) + .combined(with: .opacity) + .animation(.spring(response: 0.4, dampingFraction: 0.8)) + ) // Spring animation + .zIndex(1) // Ensure it's on top + + Spacer() + } + .frame(width: geometry.size.width, height: geometry.size.height, alignment: .top) + } + } + } + .onChange(of: isPresented) { newValue in + // When the command palette disappears we need to send focus back to the + // surface view we were overlaid on top of. There's probably a better way + // to handle the first responder state here but I don't know it. + if !newValue { + // Has to be on queue because onChange happens on a user-interactive + // thread and Xcode is mad about this call on that. + DispatchQueue.main.async { + surfaceView.window?.makeFirstResponder(surfaceView) + } + } + } + } +} + +/// This is done to ensure that the given view is in the responder chain. +fileprivate struct ResponderChainInjector: NSViewRepresentable { + let responder: NSResponder + + func makeNSView(context: Context) -> NSView { + let dummy = NSView() + DispatchQueue.main.async { + dummy.nextResponder = responder + } + return dummy + } + + func updateNSView(_ nsView: NSView, context: Context) {} +} diff --git a/macos/Sources/Features/Terminal/BaseTerminalController.swift b/macos/Sources/Features/Terminal/BaseTerminalController.swift index 3b4b1a2ef..62384586a 100644 --- a/macos/Sources/Features/Terminal/BaseTerminalController.swift +++ b/macos/Sources/Features/Terminal/BaseTerminalController.swift @@ -1,5 +1,6 @@ import Cocoa import SwiftUI +import Combine import GhosttyKit /// A base class for windows that can contain Ghostty windows. This base class implements @@ -45,6 +46,9 @@ class BaseTerminalController: NSWindowController, didSet { surfaceTreeDidChange(from: oldValue, to: surfaceTree) } } + /// This can be set to show/hide the command palette. + @Published var commandPaletteIsShowing: Bool = false + /// Whether the terminal surface should focus when the mouse is over it. var focusFollowsMouse: Bool { self.derivedConfig.focusFollowsMouse @@ -68,6 +72,9 @@ class BaseTerminalController: NSWindowController, /// The configuration derived from the Ghostty config so we don't need to rely on references. private var derivedConfig: DerivedConfig + /// The cancellables related to our focused surface. + private var focusedSurfaceCancellables: Set = [] + struct SavedFrame { let window: NSRect let screen: NSRect @@ -107,6 +114,16 @@ class BaseTerminalController: NSWindowController, selector: #selector(ghosttyConfigDidChangeBase(_:)), name: .ghosttyConfigDidChange, object: nil) + center.addObserver( + self, + selector: #selector(ghosttyCommandPaletteDidToggle(_:)), + name: .ghosttyCommandPaletteDidToggle, + object: nil) + center.addObserver( + self, + selector: #selector(ghosttyMaximizeDidToggle(_:)), + name: .ghosttyMaximizeDidToggle, + object: nil) // Listen for local events that we need to know of outside of // single surface handlers. @@ -144,6 +161,7 @@ class BaseTerminalController: NSWindowController, // Our focus state requires that this window is key and our currently // focused surface is the surface in this leaf. let focused: Bool = (window?.isKeyWindow ?? false) && + !commandPaletteIsShowing && focusedSurface != nil && leaf.surface == focusedSurface! leaf.surface.focusDidChange(focused) @@ -209,16 +227,29 @@ class BaseTerminalController: NSWindowController, // We only care if the configuration is a global configuration, not a // surface-specific one. guard notification.object == nil else { return } - + // Get our managed configuration object out guard let config = notification.userInfo?[ Notification.Name.GhosttyConfigChangeKey ] as? Ghostty.Config else { return } - + // Update our derived config self.derivedConfig = DerivedConfig(config) } + @objc private func ghosttyCommandPaletteDidToggle(_ notification: Notification) { + guard let surfaceView = notification.object as? Ghostty.SurfaceView else { return } + guard surfaceTree?.contains(view: surfaceView) ?? false else { return } + toggleCommandPalette(nil) + } + + @objc private func ghosttyMaximizeDidToggle(_ notification: Notification) { + guard let window else { return } + guard let surfaceView = notification.object as? Ghostty.SurfaceView else { return } + guard surfaceTree?.contains(view: surfaceView) ?? false else { return } + window.zoom(nil) + } + // MARK: Local Events private func localEventHandler(_ event: NSEvent) -> NSEvent? { @@ -259,7 +290,26 @@ class BaseTerminalController: NSWindowController, func surfaceTreeDidChange() {} func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) { + let lastFocusedSurface = focusedSurface focusedSurface = to + + // Important to cancel any prior subscriptions + focusedSurfaceCancellables = [] + + // Setup our title listener. If we have a focused surface we always use that. + // Otherwise, we try to use our last focused surface. In either case, we only + // want to care if the surface is in the tree so we don't listen to titles of + // closed surfaces. + if let titleSurface = focusedSurface ?? lastFocusedSurface, + surfaceTree?.contains(view: titleSurface) ?? false { + // If we have a surface, we want to listen for title changes. + titleSurface.$title + .sink { [weak self] in self?.titleDidChange(to: $0) } + .store(in: &focusedSurfaceCancellables) + } else { + // There is no surface to listen to titles for. + titleDidChange(to: "👻") + } } func titleDidChange(to: String) { @@ -288,6 +338,15 @@ class BaseTerminalController: NSWindowController, func zoomStateDidChange(to: Bool) {} + func performAction(_ action: String, on surfaceView: Ghostty.SurfaceView) { + guard let surface = surfaceView.surface else { return } + let len = action.utf8CString.count + if (len == 0) { return } + _ = action.withCString { cString in + ghostty_surface_binding_action(surface, cString, UInt(len - 1)) + } + } + // MARK: Fullscreen /// Toggle fullscreen for the given mode. @@ -337,14 +396,6 @@ class BaseTerminalController: NSWindowController, } } - func fullscreenDidChange() { - // For some reason focus can get lost when we change fullscreen. Regardless of - // mode above we just move it back. - if let focusedSurface { - Ghostty.moveFocus(to: focusedSurface) - } - } - // MARK: Clipboard Confirmation @objc private func onConfirmClipboardRequest(notification: SwiftUI.Notification) { @@ -619,6 +670,10 @@ class BaseTerminalController: NSWindowController, ghostty.changeFontSize(surface: surface, .reset) } + @IBAction func toggleCommandPalette(_ sender: Any?) { + commandPaletteIsShowing.toggle() + } + @objc func resetTerminal(_ sender: Any) { guard let surface = focusedSurface?.surface else { return } ghostty.resetTerminal(surface: surface) diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index f54eb6539..cf2dd3348 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -121,9 +121,7 @@ class TerminalController: BaseTerminalController { } - override func fullscreenDidChange() { - super.fullscreenDidChange() - + func fullscreenDidChange() { // When our fullscreen state changes, we resync our appearance because some // properties change when fullscreen or not. guard let focusedSurface else { return } @@ -192,7 +190,7 @@ class TerminalController: BaseTerminalController { } let action = "goto_tab:\(tab)" - if let equiv = ghostty.config.keyEquivalent(for: action) { + if let equiv = ghostty.config.keyboardShortcut(for: action) { window.keyEquivalent = "\(equiv)" } else { window.keyEquivalent = "" diff --git a/macos/Sources/Features/Terminal/TerminalView.swift b/macos/Sources/Features/Terminal/TerminalView.swift index 3d4165e91..7caceb071 100644 --- a/macos/Sources/Features/Terminal/TerminalView.swift +++ b/macos/Sources/Features/Terminal/TerminalView.swift @@ -8,9 +8,6 @@ protocol TerminalViewDelegate: AnyObject { /// Called when the currently focused surface changed. This can be nil. func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) - /// The title of the terminal should change. - func titleDidChange(to: String) - /// The URL of the pwd should change. func pwdDidChange(to: URL?) @@ -23,6 +20,9 @@ protocol TerminalViewDelegate: AnyObject { /// This is called when a split is zoomed. func zoomStateDidChange(to: Bool) + + /// Perform an action. At the time of writing this is only triggered by the command palette. + func performAction(_ action: String, on: Ghostty.SurfaceView) } /// The view model is a required implementation for TerminalView callers. This contains @@ -32,6 +32,9 @@ protocol TerminalViewModel: ObservableObject { /// The tree of terminal surfaces (splits) within the view. This is mutated by TerminalView /// and children. This should be @Published. var surfaceTree: Ghostty.SplitNode? { get set } + + /// The command palette state. + var commandPaletteIsShowing: Bool { get set } } /// The main terminal view. This terminal view supports splits. @@ -44,24 +47,19 @@ struct TerminalView: View { // An optional delegate to receive information about terminal changes. weak var delegate: (any TerminalViewDelegate)? = nil + // The most recently focused surface, equal to focusedSurface when + // it is non-nil. + @State private var lastFocusedSurface: Weak = .init() + // This seems like a crutch after switching from SwiftUI to AppKit lifecycle. @FocusState private var focused: Bool // Various state values sent back up from the currently focused terminals. @FocusedValue(\.ghosttySurfaceView) private var focusedSurface - @FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle @FocusedValue(\.ghosttySurfacePwd) private var surfacePwd @FocusedValue(\.ghosttySurfaceZoomed) private var zoomedSplit @FocusedValue(\.ghosttySurfaceCellSize) private var cellSize - // The title for our window - private var title: String { - if let surfaceTitle, !surfaceTitle.isEmpty { - return surfaceTitle - } - return "👻" - } - // The pwd of the focused surface as a URL private var pwdURL: URL? { guard let surfacePwd, surfacePwd != "" else { return nil } @@ -75,42 +73,55 @@ struct TerminalView: View { case .error: ErrorView() case .ready: - VStack(spacing: 0) { - // If we're running in debug mode we show a warning so that users - // know that performance will be degraded. - if (Ghostty.info.mode == GHOSTTY_BUILD_MODE_DEBUG || Ghostty.info.mode == GHOSTTY_BUILD_MODE_RELEASE_SAFE) { - DebugBuildWarningView() - } + ZStack { + VStack(spacing: 0) { + // If we're running in debug mode we show a warning so that users + // know that performance will be degraded. + if (Ghostty.info.mode == GHOSTTY_BUILD_MODE_DEBUG || Ghostty.info.mode == GHOSTTY_BUILD_MODE_RELEASE_SAFE) { + DebugBuildWarningView() + } - Ghostty.TerminalSplit(node: $viewModel.surfaceTree) - .environmentObject(ghostty) - .focused($focused) - .onAppear { self.focused = true } - .onChange(of: focusedSurface) { newValue in - self.delegate?.focusedSurfaceDidChange(to: newValue) - } - .onChange(of: title) { newValue in - self.delegate?.titleDidChange(to: newValue) - } - .onChange(of: pwdURL) { newValue in - self.delegate?.pwdDidChange(to: newValue) - } - .onChange(of: cellSize) { newValue in - guard let size = newValue else { return } - self.delegate?.cellSizeDidChange(to: size) - } - .onChange(of: viewModel.surfaceTree?.hashValue) { _ in - // This is funky, but its the best way I could think of to detect - // ANY CHANGE within the deeply nested surface tree -- detecting a change - // in the hash value. - self.delegate?.surfaceTreeDidChange() - } - .onChange(of: zoomedSplit) { newValue in - self.delegate?.zoomStateDidChange(to: newValue ?? false) + Ghostty.TerminalSplit(node: $viewModel.surfaceTree) + .environmentObject(ghostty) + .focused($focused) + .onAppear { self.focused = true } + .onChange(of: focusedSurface) { newValue in + // We want to keep track of our last focused surface so even if + // we lose focus we keep this set to the last non-nil value. + if newValue != nil { + lastFocusedSurface = .init(newValue) + self.delegate?.focusedSurfaceDidChange(to: newValue) + } + } + .onChange(of: pwdURL) { newValue in + self.delegate?.pwdDidChange(to: newValue) + } + .onChange(of: cellSize) { newValue in + guard let size = newValue else { return } + self.delegate?.cellSizeDidChange(to: size) + } + .onChange(of: viewModel.surfaceTree?.hashValue) { _ in + // This is funky, but its the best way I could think of to detect + // ANY CHANGE within the deeply nested surface tree -- detecting a change + // in the hash value. + self.delegate?.surfaceTreeDidChange() + } + .onChange(of: zoomedSplit) { newValue in + self.delegate?.zoomStateDidChange(to: newValue ?? false) + } + } + // Ignore safe area to extend up in to the titlebar region if we have the "hidden" titlebar style + .ignoresSafeArea(.container, edges: ghostty.config.macosTitlebarStyle == "hidden" ? .top : []) + + if let surfaceView = lastFocusedSurface.value { + TerminalCommandPaletteView( + surfaceView: surfaceView, + isPresented: $viewModel.commandPaletteIsShowing, + ghosttyConfig: ghostty.config) { action in + self.delegate?.performAction(action, on: surfaceView) } + } } - // Ignore safe area to extend up in to the titlebar region if we have the "hidden" titlebar style - .ignoresSafeArea(.container, edges: ghostty.config.macosTitlebarStyle == "hidden" ? .top : []) } } } diff --git a/macos/Sources/Features/Terminal/TerminalWindow.swift b/macos/Sources/Features/Terminal/TerminalWindow.swift index 3209449e4..62b8dc5bf 100644 --- a/macos/Sources/Features/Terminal/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/TerminalWindow.swift @@ -1,6 +1,9 @@ import Cocoa class TerminalWindow: NSWindow { + /// This is the key in UserDefaults to use for the default `level` value. + static let defaultLevelKey: String = "TerminalDefaultLevel" + @objc dynamic var keyEquivalent: String = "" /// This is used to determine if certain elements should be drawn light or dark and should @@ -63,6 +66,8 @@ class TerminalWindow: NSWindow { if titlebarTabs { generateToolbar() } + + level = UserDefaults.standard.value(forKey: Self.defaultLevelKey) as? NSWindow.Level ?? .normal } deinit { diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index dfd066870..65e91ce83 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -496,6 +496,9 @@ extension Ghostty { case GHOSTTY_ACTION_OPEN_CONFIG: ghostty_config_open() + case GHOSTTY_ACTION_FLOAT_WINDOW: + toggleFloatWindow(app, target: target, mode: action.action.float_window) + case GHOSTTY_ACTION_SECURE_INPUT: toggleSecureInput(app, target: target, mode: action.action.secure_input) @@ -520,6 +523,12 @@ extension Ghostty { case GHOSTTY_ACTION_RENDERER_HEALTH: rendererHealth(app, target: target, v: action.action.renderer_health) + case GHOSTTY_ACTION_TOGGLE_COMMAND_PALETTE: + toggleCommandPalette(app, target: target) + + case GHOSTTY_ACTION_TOGGLE_MAXIMIZE: + toggleMaximize(app, target: target) + case GHOSTTY_ACTION_TOGGLE_QUICK_TERMINAL: toggleQuickTerminal(app, target: target) @@ -742,6 +751,51 @@ extension Ghostty { } } + private static func toggleCommandPalette( + _ app: ghostty_app_t, + target: ghostty_target_s) { + switch (target.tag) { + case GHOSTTY_TARGET_APP: + Ghostty.logger.warning("toggle command palette does nothing with an app target") + return + + case GHOSTTY_TARGET_SURFACE: + guard let surface = target.target.surface else { return } + guard let surfaceView = self.surfaceView(from: surface) else { return } + NotificationCenter.default.post( + name: .ghosttyCommandPaletteDidToggle, + object: surfaceView + ) + + + default: + assertionFailure() + } + } + + private static func toggleMaximize( + _ app: ghostty_app_t, + target: ghostty_target_s + ) { + switch (target.tag) { + case GHOSTTY_TARGET_APP: + Ghostty.logger.warning("toggle maximize does nothing with an app target") + return + + case GHOSTTY_TARGET_SURFACE: + guard let surface = target.target.surface else { return } + guard let surfaceView = self.surfaceView(from: surface) else { return } + NotificationCenter.default.post( + name: .ghosttyMaximizeDidToggle, + object: surfaceView + ) + + + default: + assertionFailure() + } + } + private static func toggleVisibility( _ app: ghostty_app_t, target: ghostty_target_s @@ -1001,6 +1055,43 @@ extension Ghostty { } } + private static func toggleFloatWindow( + _ app: ghostty_app_t, + target: ghostty_target_s, + mode mode_raw: ghostty_action_float_window_e + ) { + guard let mode = SetFloatWIndow.from(mode_raw) else { return } + + switch (target.tag) { + case GHOSTTY_TARGET_APP: + Ghostty.logger.warning("toggle float window 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 } + guard let window = surfaceView.window as? TerminalWindow else { return } + + switch (mode) { + case .on: + window.level = .floating + + case .off: + window.level = .normal + + case .toggle: + window.level = window.level == .floating ? .normal : .floating + } + + if let appDelegate = NSApplication.shared.delegate as? AppDelegate { + appDelegate.syncFloatOnTopMenu(window) + } + + default: + assertionFailure() + } + } + private static func toggleSecureInput( _ app: ghostty_app_t, target: ghostty_target_s, @@ -1306,7 +1397,7 @@ extension Ghostty { name: Notification.didContinueKeySequence, object: surfaceView, userInfo: [ - Notification.KeySequenceKey: keyEquivalent(for: v.trigger) as Any + Notification.KeySequenceKey: keyboardShortcut(for: v.trigger) as Any ] ) } else { diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index d146477dc..d7be4eb5b 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -102,11 +102,11 @@ extension Ghostty { /// configuration would be "quit" action. /// /// Returns nil if there is no key equivalent for the given action. - func keyEquivalent(for action: String) -> KeyEquivalent? { + func keyboardShortcut(for action: String) -> KeyboardShortcut? { guard let cfg = self.config else { return nil } let trigger = ghostty_config_trigger(cfg, action, UInt(action.count)) - return Ghostty.keyEquivalent(for: trigger) + return Ghostty.keyboardShortcut(for: trigger) } #endif diff --git a/macos/Sources/Ghostty/Ghostty.Input.swift b/macos/Sources/Ghostty/Ghostty.Input.swift index 0a279ea1f..0be579122 100644 --- a/macos/Sources/Ghostty/Ghostty.Input.swift +++ b/macos/Sources/Ghostty/Ghostty.Input.swift @@ -1,66 +1,52 @@ import Cocoa +import SwiftUI import GhosttyKit extension Ghostty { - // MARK: Key Equivalents + // MARK: Keyboard Shortcuts - /// Returns the "keyEquivalent" string for a given input key. This doesn't always have a corresponding key. - static func keyEquivalent(key: ghostty_input_key_e) -> String? { + /// Returns the SwiftUI KeyEquivalent for a given key. Note that not all keys known by + /// Ghostty have a macOS equivalent since macOS doesn't allow all keys as equivalents. + static func keyEquivalent(key: ghostty_input_key_e) -> KeyEquivalent? { return Self.keyToEquivalent[key] } - /// A convenience struct that has the key + modifiers for some keybinding. - struct KeyEquivalent: CustomStringConvertible { - let key: String - let modifiers: NSEvent.ModifierFlags - - var description: String { - var key = self.key - - // Note: the order below matters; it matches the ordering modifiers - // shown for macOS menu shortcut labels. - if modifiers.contains(.command) { key = "⌘\(key)" } - if modifiers.contains(.shift) { key = "⇧\(key)" } - if modifiers.contains(.option) { key = "⌥\(key)" } - if modifiers.contains(.control) { key = "⌃\(key)" } - - return key - } - } - /// Return the key equivalent for the given trigger. /// - /// Returns nil if the trigger can't be processed. This should only happen for unknown trigger types - /// or keys. - static func keyEquivalent(for trigger: ghostty_input_trigger_s) -> KeyEquivalent? { - let equiv: String + /// Returns nil if the trigger doesn't have an equivalent KeyboardShortcut. This is possible + /// because Ghostty input triggers are a superset of what can be represented by a macOS + /// KeyboardShortcut. For example, macOS doesn't have any way to represent function keys + /// (F1, F2, ...) with a KeyboardShortcut. This doesn't represent a practical issue because input + /// handling for Ghostty is handled at a lower level (usually). This function should generally only + /// be used for things like NSMenu that only support keyboard shortcuts anyways. + static func keyboardShortcut(for trigger: ghostty_input_trigger_s) -> KeyboardShortcut? { + let key: KeyEquivalent switch (trigger.tag) { case GHOSTTY_TRIGGER_TRANSLATED: if let v = Ghostty.keyEquivalent(key: trigger.key.translated) { - equiv = v + key = v } else { return nil } case GHOSTTY_TRIGGER_PHYSICAL: if let v = Ghostty.keyEquivalent(key: trigger.key.physical) { - equiv = v + key = v } else { return nil } case GHOSTTY_TRIGGER_UNICODE: guard let scalar = UnicodeScalar(trigger.key.unicode) else { return nil } - equiv = String(scalar) + key = KeyEquivalent(Character(scalar)) default: return nil } - return KeyEquivalent( - key: equiv, - modifiers: Ghostty.eventModifierFlags(mods: trigger.mods) - ) + return KeyboardShortcut( + key, + modifiers: EventModifiers(nsFlags: Ghostty.eventModifierFlags(mods: trigger.mods))) } // MARK: Mods @@ -96,8 +82,10 @@ extension Ghostty { return ghostty_input_mods_e(mods) } - /// A map from the Ghostty key enum to the keyEquivalent string for shortcuts. - static let keyToEquivalent: [ghostty_input_key_e : String] = [ + /// A map from the Ghostty key enum to the keyEquivalent string for shortcuts. Note that + /// not all ghostty key enum values are represented here because not all of them can be + /// mapped to a KeyEquivalent. + static let keyToEquivalent: [ghostty_input_key_e : KeyEquivalent] = [ // 0-9 GHOSTTY_KEY_ZERO: "0", GHOSTTY_KEY_ONE: "1", @@ -152,48 +140,19 @@ extension Ghostty { GHOSTTY_KEY_SLASH: "/", // Function keys - GHOSTTY_KEY_UP: "\u{F700}", - GHOSTTY_KEY_DOWN: "\u{F701}", - GHOSTTY_KEY_LEFT: "\u{F702}", - GHOSTTY_KEY_RIGHT: "\u{F703}", - GHOSTTY_KEY_HOME: "\u{F729}", - GHOSTTY_KEY_END: "\u{F72B}", - GHOSTTY_KEY_INSERT: "\u{F727}", - GHOSTTY_KEY_DELETE: "\u{F728}", - GHOSTTY_KEY_PAGE_UP: "\u{F72C}", - GHOSTTY_KEY_PAGE_DOWN: "\u{F72D}", - GHOSTTY_KEY_ESCAPE: "\u{1B}", - GHOSTTY_KEY_ENTER: "\r", - GHOSTTY_KEY_TAB: "\t", - GHOSTTY_KEY_BACKSPACE: "\u{7F}", - GHOSTTY_KEY_PRINT_SCREEN: "\u{F72E}", - GHOSTTY_KEY_PAUSE: "\u{F72F}", - - GHOSTTY_KEY_F1: "\u{F704}", - GHOSTTY_KEY_F2: "\u{F705}", - GHOSTTY_KEY_F3: "\u{F706}", - GHOSTTY_KEY_F4: "\u{F707}", - GHOSTTY_KEY_F5: "\u{F708}", - GHOSTTY_KEY_F6: "\u{F709}", - GHOSTTY_KEY_F7: "\u{F70A}", - GHOSTTY_KEY_F8: "\u{F70B}", - GHOSTTY_KEY_F9: "\u{F70C}", - GHOSTTY_KEY_F10: "\u{F70D}", - GHOSTTY_KEY_F11: "\u{F70E}", - GHOSTTY_KEY_F12: "\u{F70F}", - GHOSTTY_KEY_F13: "\u{F710}", - GHOSTTY_KEY_F14: "\u{F711}", - GHOSTTY_KEY_F15: "\u{F712}", - GHOSTTY_KEY_F16: "\u{F713}", - GHOSTTY_KEY_F17: "\u{F714}", - GHOSTTY_KEY_F18: "\u{F715}", - GHOSTTY_KEY_F19: "\u{F716}", - GHOSTTY_KEY_F20: "\u{F717}", - GHOSTTY_KEY_F21: "\u{F718}", - GHOSTTY_KEY_F22: "\u{F719}", - GHOSTTY_KEY_F23: "\u{F71A}", - GHOSTTY_KEY_F24: "\u{F71B}", - GHOSTTY_KEY_F25: "\u{F71C}", + GHOSTTY_KEY_UP: .upArrow, + GHOSTTY_KEY_DOWN: .downArrow, + GHOSTTY_KEY_LEFT: .leftArrow, + GHOSTTY_KEY_RIGHT: .rightArrow, + GHOSTTY_KEY_HOME: .home, + GHOSTTY_KEY_END: .end, + GHOSTTY_KEY_DELETE: .delete, + GHOSTTY_KEY_PAGE_UP: .pageUp, + GHOSTTY_KEY_PAGE_DOWN: .pageDown, + GHOSTTY_KEY_ESCAPE: .escape, + GHOSTTY_KEY_ENTER: .return, + GHOSTTY_KEY_TAB: .tab, + GHOSTTY_KEY_BACKSPACE: .delete, ] static let asciiToKey: [UInt8 : ghostty_input_key_e] = [ diff --git a/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift b/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift index 127c925e1..3e942d774 100644 --- a/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift +++ b/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift @@ -45,8 +45,6 @@ extension Ghostty { /// this one. @Binding var zoomedSurface: SurfaceView? - @FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String? - var body: some View { let center = NotificationCenter.default let pubZoom = center.publisher(for: Notification.didToggleSplitZoom) @@ -77,7 +75,6 @@ extension Ghostty { .onReceive(pubZoom) { onZoom(notification: $0) } } } - .navigationTitle(surfaceTitle ?? "Ghostty") .id(node) // Needed for change detection on node } else { // On these events we want to reset the split state and call it. diff --git a/macos/Sources/Ghostty/InspectorView.swift b/macos/Sources/Ghostty/InspectorView.swift index b6147647e..a6e80bd47 100644 --- a/macos/Sources/Ghostty/InspectorView.swift +++ b/macos/Sources/Ghostty/InspectorView.swift @@ -31,7 +31,6 @@ extension Ghostty { }, right: { InspectorViewRepresentable(surfaceView: surfaceView) .focused($inspectorFocus) - .focusedValue(\.ghosttySurfaceTitle, surfaceView.title) .focusedValue(\.ghosttySurfaceView, surfaceView) }) } diff --git a/macos/Sources/Ghostty/NSEvent+Extension.swift b/macos/Sources/Ghostty/NSEvent+Extension.swift index 754bb7a3a..058e7aace 100644 --- a/macos/Sources/Ghostty/NSEvent+Extension.swift +++ b/macos/Sources/Ghostty/NSEvent+Extension.swift @@ -33,11 +33,13 @@ extension NSEvent { .subtracting([.control, .command])) // Our unshifted codepoint is the codepoint with no modifiers. We - // ignore multi-codepoint values. + // ignore multi-codepoint values. We have to use `byApplyingModifiers` + // instead of `charactersIgnoringModifiers` because the latter changes + // behavior with ctrl pressed and we don't want any of that. key_ev.unshifted_codepoint = 0 if type == .keyDown || type == .keyUp { - if let charactersIgnoringModifiers, - let codepoint = charactersIgnoringModifiers.unicodeScalars.first + if let chars = characters(byApplyingModifiers: []), + let codepoint = chars.unicodeScalars.first { key_ev.unshifted_codepoint = codepoint.value } diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index 3afca56aa..30d5573df 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -42,6 +42,28 @@ extension Ghostty { // MARK: Swift Types for C Types extension Ghostty { + enum SetFloatWIndow { + case on + case off + case toggle + + static func from(_ c: ghostty_action_float_window_e) -> Self? { + switch (c) { + case GHOSTTY_FLOAT_WINDOW_ON: + return .on + + case GHOSTTY_FLOAT_WINDOW_OFF: + return .off + + case GHOSTTY_FLOAT_WINDOW_TOGGLE: + return .toggle + + default: + return nil + } + } + } + enum SetSecureInput { case on case off @@ -256,6 +278,10 @@ extension Notification.Name { /// Ring the bell static let ghosttyBellDidRing = Notification.Name("com.mitchellh.ghostty.ghosttyBellDidRing") + static let ghosttyCommandPaletteDidToggle = Notification.Name("com.mitchellh.ghostty.commandPaletteDidToggle") + + /// Toggle maximize of current window + static let ghosttyMaximizeDidToggle = Notification.Name("com.mitchellh.ghostty.maximizeDidToggle") } // NOTE: I am moving all of these to Notification.Name extensions over time. This diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 7eebd3ef1..1e9a4cfef 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -6,14 +6,12 @@ extension Ghostty { /// Render a terminal for the active app in the environment. struct Terminal: View { @EnvironmentObject private var ghostty: Ghostty.App - @FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String? var body: some View { if let app = self.ghostty.app { SurfaceForApp(app) { surfaceView in SurfaceWrapper(surfaceView: surfaceView) } - .navigationTitle(surfaceTitle ?? "Ghostty") } } } @@ -83,7 +81,6 @@ extension Ghostty { Surface(view: surfaceView, size: geo.size) .focused($surfaceFocus) - .focusedValue(\.ghosttySurfaceTitle, title) .focusedValue(\.ghosttySurfacePwd, surfaceView.pwd) .focusedValue(\.ghosttySurfaceView, surfaceView) .focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize) @@ -329,7 +326,7 @@ extension Ghostty { Spacer() } - Text(verbatim: "\(size.columns)c ⨯ \(size.rows)r") + Text(verbatim: "\(size.columns) ⨯ \(size.rows)") .padding(.init(top: padding, leading: padding, bottom: padding, trailing: padding)) .background( RoundedRectangle(cornerRadius: 4) @@ -496,15 +493,6 @@ extension FocusedValues { typealias Value = Ghostty.SurfaceView } - var ghosttySurfaceTitle: String? { - get { self[FocusedGhosttySurfaceTitle.self] } - set { self[FocusedGhosttySurfaceTitle.self] = newValue } - } - - struct FocusedGhosttySurfaceTitle: FocusedValueKey { - typealias Value = String - } - var ghosttySurfacePwd: String? { get { self[FocusedGhosttySurfacePwd.self] } set { self[FocusedGhosttySurfacePwd.self] = newValue } diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 574c88044..921c32c8b 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -43,7 +43,7 @@ extension Ghostty { @Published var hoverUrl: String? = nil // The currently active key sequence. The sequence is not active if this is empty. - @Published var keySequence: [Ghostty.KeyEquivalent] = [] + @Published var keySequence: [KeyboardShortcut] = [] // The time this surface last became focused. This is a ContinuousClock.Instant // on supported platforms. @@ -526,7 +526,7 @@ extension Ghostty { @objc private func ghosttyDidContinueKeySequence(notification: SwiftUI.Notification) { guard let keyAny = notification.userInfo?[Ghostty.Notification.KeySequenceKey] else { return } - guard let key = keyAny as? Ghostty.KeyEquivalent else { return } + guard let key = keyAny as? KeyboardShortcut else { return } DispatchQueue.main.async { [weak self] in self?.keySequence.append(key) } @@ -975,7 +975,14 @@ extension Ghostty { event: event, translationEvent: translationEvent, text: translationEvent.ghosttyCharacters, - composing: markedText.length > 0 + + // We're composing if we have preedit (the obvious case). But we're also + // composing if we don't have preedit and we had marked text before, + // because this input probably just reset the preedit state. It shouldn't + // be encoded. Example: Japanese begin composing, the press backspace. + // This should only cancel the composing state but not actually delete + // the prior input characters (prior to the composing). + composing: markedText.length > 0 || markedTextBefore ) } } @@ -1169,7 +1176,12 @@ extension Ghostty { var key_ev = event.ghosttyKeyEvent(action, translationMods: translationEvent?.modifierFlags) key_ev.composing = composing - if let text { + + // 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. + if let text, text.count > 0, + let codepoint = text.utf8.first, codepoint >= 0x20 { return text.withCString { ptr in key_ev.text = ptr return ghostty_surface_key(surface, key_ev) diff --git a/macos/Sources/Helpers/EventModifiers+Extension.swift b/macos/Sources/Helpers/EventModifiers+Extension.swift new file mode 100644 index 000000000..8d379bd99 --- /dev/null +++ b/macos/Sources/Helpers/EventModifiers+Extension.swift @@ -0,0 +1,27 @@ +import SwiftUI + +// MARK: EventModifiers to NSEvent and Back + +extension EventModifiers { + init(nsFlags: NSEvent.ModifierFlags) { + var result: SwiftUI.EventModifiers = [] + if nsFlags.contains(.shift) { result.insert(.shift) } + if nsFlags.contains(.control) { result.insert(.control) } + if nsFlags.contains(.option) { result.insert(.option) } + if nsFlags.contains(.command) { result.insert(.command) } + if nsFlags.contains(.capsLock) { result.insert(.capsLock) } + self = result + } +} + +extension NSEvent.ModifierFlags { + init(swiftUIFlags: SwiftUI.EventModifiers) { + var result: NSEvent.ModifierFlags = [] + if swiftUIFlags.contains(.shift) { result.insert(.shift) } + if swiftUIFlags.contains(.control) { result.insert(.control) } + if swiftUIFlags.contains(.option) { result.insert(.option) } + if swiftUIFlags.contains(.command) { result.insert(.command) } + if swiftUIFlags.contains(.capsLock) { result.insert(.capsLock) } + self = result + } +} diff --git a/macos/Sources/Helpers/Fullscreen.swift b/macos/Sources/Helpers/Fullscreen.swift index b6fb08271..6094bf844 100644 --- a/macos/Sources/Helpers/Fullscreen.swift +++ b/macos/Sources/Helpers/Fullscreen.swift @@ -171,6 +171,13 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { guard let savedState = SavedState(window) else { return } self.savedState = savedState + // Get our current first responder on this window. For non-native fullscreen + // we have to restore this because for some reason the operations below + // lose it (see: https://github.com/ghostty-org/ghostty/issues/6999). + // I don't know the root cause here so if we can figure that out there may + // be a nicer way than this. + let firstResponder = window.firstResponder + // We hide the dock if the window is on a screen with the dock. // We must hide the dock FIRST then hide the menu: // If you specify autoHideMenuBar, it must be accompanied by either hideDock or autoHideDock. @@ -207,6 +214,10 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { // https://github.com/ghostty-org/ghostty/issues/1996 DispatchQueue.main.async { self.window.setFrame(self.fullscreenFrame(screen), display: true) + if let firstResponder { + self.window.makeFirstResponder(firstResponder) + } + self.delegate?.fullscreenDidChange() } } @@ -220,6 +231,9 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { let center = NotificationCenter.default center.removeObserver(self, name: NSWindow.didChangeScreenNotification, object: window) + // See enter where we do the same thing to understand why. + let firstResponder = window.firstResponder + // Unhide our elements if savedState.dock { unhideDock() @@ -258,6 +272,10 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { } } + if let firstResponder { + window.makeFirstResponder(firstResponder) + } + // Unset our saved state, we're restored! self.savedState = nil @@ -355,16 +373,23 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { self.styleMask = window.styleMask self.dock = window.screen?.hasDock ?? false - // We hide the menu only if this window is not on any fullscreen - // spaces. We do this because fullscreen spaces already hide the - // menu and if we insert/remove this presentation option we get - // issues (see #7075) - let activeSpace = CGSSpace.active() - let spaces = CGSSpace.list(for: window.cgWindowId) - if spaces.contains(activeSpace) { - self.menu = activeSpace.type != .fullscreen + if let cgWindowId = window.cgWindowId { + // We hide the menu only if this window is not on any fullscreen + // spaces. We do this because fullscreen spaces already hide the + // menu and if we insert/remove this presentation option we get + // issues (see #7075) + let activeSpace = CGSSpace.active() + let spaces = CGSSpace.list(for: cgWindowId) + if spaces.contains(activeSpace) { + self.menu = activeSpace.type != .fullscreen + } else { + self.menu = spaces.allSatisfy { $0.type != .fullscreen } + } } else { - self.menu = spaces.allSatisfy { $0.type != .fullscreen } + // Window doesn't have a window device, its not visible or something. + // In this case, we assume we can hide the menu. We may want to do + // something more sophisticated but this works for now. + self.menu = true } } } diff --git a/macos/Sources/Helpers/KeyboardShortcut+Extension.swift b/macos/Sources/Helpers/KeyboardShortcut+Extension.swift new file mode 100644 index 000000000..7891f12d7 --- /dev/null +++ b/macos/Sources/Helpers/KeyboardShortcut+Extension.swift @@ -0,0 +1,53 @@ +import SwiftUI + +extension KeyboardShortcut: @retroactive CustomStringConvertible { + public var keyList: [String] { + var result: [String] = [] + + if modifiers.contains(.control) { + result.append("⌃") + } + if modifiers.contains(.option) { + result.append("⌥") + } + if modifiers.contains(.shift) { + result.append("⇧") + } + if modifiers.contains(.command) { + result.append("⌘") + } + + let keyString: String + switch key { + case .return: keyString = "⏎" + case .escape: keyString = "⎋" + case .delete: keyString = "⌫" + case .space: keyString = "␣" + case .tab: keyString = "⇥" + case .upArrow: keyString = "▲" + case .downArrow: keyString = "▼" + case .leftArrow: keyString = "◀" + case .rightArrow: keyString = "▶" + case .pageUp: keyString = "↑" + case .pageDown: keyString = "↓" + case .home: keyString = "⤒" + case .end: keyString = "⤓" + default: + keyString = String(key.character.uppercased()) + } + + result.append(keyString) + return result + } + + public var description: String { + return self.keyList.joined() + } +} + +// This is available in macOS 14 so this only applies to early macOS versions. +extension KeyEquivalent: @retroactive Equatable { + public static func == (lhs: KeyEquivalent, rhs: KeyEquivalent) -> Bool { + lhs.character == rhs.character + } +} diff --git a/macos/Sources/Helpers/NSWindow+Extension.swift b/macos/Sources/Helpers/NSWindow+Extension.swift index c7523bdb7..06a9fa4e0 100644 --- a/macos/Sources/Helpers/NSWindow+Extension.swift +++ b/macos/Sources/Helpers/NSWindow+Extension.swift @@ -2,7 +2,11 @@ import AppKit extension NSWindow { /// Get the CGWindowID type for the window (used for low level CoreGraphics APIs). - var cgWindowId: CGWindowID { - CGWindowID(windowNumber) + var cgWindowId: CGWindowID? { + // "If the window doesn’t have a window device, the value of this + // property is equal to or less than 0." - Docs. In practice I've + // found this is true if a window is not visible. + guard windowNumber > 0 else { return nil } + return CGWindowID(windowNumber) } } diff --git a/macos/Sources/Helpers/Weak.swift b/macos/Sources/Helpers/Weak.swift index d5f784844..0fbb9bd87 100644 --- a/macos/Sources/Helpers/Weak.swift +++ b/macos/Sources/Helpers/Weak.swift @@ -3,7 +3,7 @@ class Weak { weak var value: T? - init(_ value: T) { + init(_ value: T? = nil) { self.value = value } } diff --git a/nix/devShell.nix b/nix/devShell.nix index 5b69f882b..498102ef4 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -3,6 +3,8 @@ lib, stdenv, bashInteractive, + appstream, + flatpak-builder, gdb, #, glxinfo # unused ncurses, @@ -128,6 +130,8 @@ in # build only has the qemu-system files. qemu + appstream + flatpak-builder gdb snapcraft valgrind diff --git a/pkg/cimgui/main.zig b/pkg/cimgui/main.zig index e6e54c357..b890a49ee 100644 --- a/pkg/cimgui/main.zig +++ b/pkg/cimgui/main.zig @@ -1,20 +1,20 @@ pub const c = @import("c.zig").c; // OpenGL -pub extern fn ImGui_ImplOpenGL3_Init(?[*:0]const u8) callconv(.C) bool; -pub extern fn ImGui_ImplOpenGL3_Shutdown() callconv(.C) void; -pub extern fn ImGui_ImplOpenGL3_NewFrame() callconv(.C) void; -pub extern fn ImGui_ImplOpenGL3_RenderDrawData(*c.ImDrawData) callconv(.C) void; +pub extern fn ImGui_ImplOpenGL3_Init(?[*:0]const u8) callconv(.c) bool; +pub extern fn ImGui_ImplOpenGL3_Shutdown() callconv(.c) void; +pub extern fn ImGui_ImplOpenGL3_NewFrame() callconv(.c) void; +pub extern fn ImGui_ImplOpenGL3_RenderDrawData(*c.ImDrawData) callconv(.c) void; // Metal -pub extern fn ImGui_ImplMetal_Init(*anyopaque) callconv(.C) bool; -pub extern fn ImGui_ImplMetal_Shutdown() callconv(.C) void; -pub extern fn ImGui_ImplMetal_NewFrame(*anyopaque) callconv(.C) void; -pub extern fn ImGui_ImplMetal_RenderDrawData(*c.ImDrawData, *anyopaque, *anyopaque) callconv(.C) void; +pub extern fn ImGui_ImplMetal_Init(*anyopaque) callconv(.c) bool; +pub extern fn ImGui_ImplMetal_Shutdown() callconv(.c) void; +pub extern fn ImGui_ImplMetal_NewFrame(*anyopaque) callconv(.c) void; +pub extern fn ImGui_ImplMetal_RenderDrawData(*c.ImDrawData, *anyopaque, *anyopaque) callconv(.c) void; // OSX -pub extern fn ImGui_ImplOSX_Init(*anyopaque) callconv(.C) bool; -pub extern fn ImGui_ImplOSX_Shutdown() callconv(.C) void; -pub extern fn ImGui_ImplOSX_NewFrame(*anyopaque) callconv(.C) void; +pub extern fn ImGui_ImplOSX_Init(*anyopaque) callconv(.c) bool; +pub extern fn ImGui_ImplOSX_Shutdown() callconv(.c) void; +pub extern fn ImGui_ImplOSX_NewFrame(*anyopaque) callconv(.c) void; test {} diff --git a/pkg/glfw/Joystick.zig b/pkg/glfw/Joystick.zig index dd55c731d..a8152513e 100644 --- a/pkg/glfw/Joystick.zig +++ b/pkg/glfw/Joystick.zig @@ -333,7 +333,7 @@ pub inline fn setCallback(comptime callback: ?fn (joystick: Joystick, event: Eve if (callback) |user_callback| { const CWrapper = struct { - pub fn joystickCallbackWrapper(jid: c_int, event: c_int) callconv(.C) void { + pub fn joystickCallbackWrapper(jid: c_int, event: c_int) callconv(.c) void { @call(.always_inline, user_callback, .{ Joystick{ .jid = @as(Joystick.Id, @enumFromInt(jid)) }, @as(Event, @enumFromInt(event)), diff --git a/pkg/glfw/Monitor.zig b/pkg/glfw/Monitor.zig index 868872e19..4accb23cd 100644 --- a/pkg/glfw/Monitor.zig +++ b/pkg/glfw/Monitor.zig @@ -389,7 +389,7 @@ pub inline fn setCallback(comptime callback: ?fn (monitor: Monitor, event: Event if (callback) |user_callback| { const CWrapper = struct { - pub fn monitorCallbackWrapper(monitor: ?*c.GLFWmonitor, event: c_int) callconv(.C) void { + pub fn monitorCallbackWrapper(monitor: ?*c.GLFWmonitor, event: c_int) callconv(.c) void { @call(.always_inline, user_callback, .{ Monitor{ .handle = monitor.? }, @as(Event, @enumFromInt(event)), diff --git a/pkg/glfw/Window.zig b/pkg/glfw/Window.zig index 29dcac23e..804184f0e 100644 --- a/pkg/glfw/Window.zig +++ b/pkg/glfw/Window.zig @@ -1230,7 +1230,7 @@ pub inline fn setPosCallback(self: Window, comptime callback: ?fn (window: Windo if (callback) |user_callback| { const CWrapper = struct { - pub fn posCallbackWrapper(handle: ?*c.GLFWwindow, xpos: c_int, ypos: c_int) callconv(.C) void { + pub fn posCallbackWrapper(handle: ?*c.GLFWwindow, xpos: c_int, ypos: c_int) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), @as(i32, @intCast(xpos)), @@ -1263,7 +1263,7 @@ pub inline fn setSizeCallback(self: Window, comptime callback: ?fn (window: Wind if (callback) |user_callback| { const CWrapper = struct { - pub fn sizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void { + pub fn sizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), @as(i32, @intCast(width)), @@ -1304,7 +1304,7 @@ pub inline fn setCloseCallback(self: Window, comptime callback: ?fn (window: Win if (callback) |user_callback| { const CWrapper = struct { - pub fn closeCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.C) void { + pub fn closeCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), }); @@ -1341,7 +1341,7 @@ pub inline fn setRefreshCallback(self: Window, comptime callback: ?fn (window: W if (callback) |user_callback| { const CWrapper = struct { - pub fn refreshCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.C) void { + pub fn refreshCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), }); @@ -1379,7 +1379,7 @@ pub inline fn setFocusCallback(self: Window, comptime callback: ?fn (window: Win if (callback) |user_callback| { const CWrapper = struct { - pub fn focusCallbackWrapper(handle: ?*c.GLFWwindow, focused: c_int) callconv(.C) void { + pub fn focusCallbackWrapper(handle: ?*c.GLFWwindow, focused: c_int) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), focused == c.GLFW_TRUE, @@ -1413,7 +1413,7 @@ pub inline fn setIconifyCallback(self: Window, comptime callback: ?fn (window: W if (callback) |user_callback| { const CWrapper = struct { - pub fn iconifyCallbackWrapper(handle: ?*c.GLFWwindow, iconified: c_int) callconv(.C) void { + pub fn iconifyCallbackWrapper(handle: ?*c.GLFWwindow, iconified: c_int) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), iconified == c.GLFW_TRUE, @@ -1448,7 +1448,7 @@ pub inline fn setMaximizeCallback(self: Window, comptime callback: ?fn (window: if (callback) |user_callback| { const CWrapper = struct { - pub fn maximizeCallbackWrapper(handle: ?*c.GLFWwindow, maximized: c_int) callconv(.C) void { + pub fn maximizeCallbackWrapper(handle: ?*c.GLFWwindow, maximized: c_int) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), maximized == c.GLFW_TRUE, @@ -1483,7 +1483,7 @@ pub inline fn setFramebufferSizeCallback(self: Window, comptime callback: ?fn (w if (callback) |user_callback| { const CWrapper = struct { - pub fn framebufferSizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void { + pub fn framebufferSizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), @as(u32, @intCast(width)), @@ -1519,7 +1519,7 @@ pub inline fn setContentScaleCallback(self: Window, comptime callback: ?fn (wind if (callback) |user_callback| { const CWrapper = struct { - pub fn windowScaleCallbackWrapper(handle: ?*c.GLFWwindow, xscale: f32, yscale: f32) callconv(.C) void { + pub fn windowScaleCallbackWrapper(handle: ?*c.GLFWwindow, xscale: f32, yscale: f32) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), xscale, @@ -1871,7 +1871,7 @@ pub inline fn setKeyCallback(self: Window, comptime callback: ?fn (window: Windo if (callback) |user_callback| { const CWrapper = struct { - pub fn keyCallbackWrapper(handle: ?*c.GLFWwindow, key: c_int, scancode: c_int, action: c_int, mods: c_int) callconv(.C) void { + pub fn keyCallbackWrapper(handle: ?*c.GLFWwindow, key: c_int, scancode: c_int, action: c_int, mods: c_int) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), @as(Key, @enumFromInt(key)), @@ -1917,7 +1917,7 @@ pub inline fn setCharCallback(self: Window, comptime callback: ?fn (window: Wind if (callback) |user_callback| { const CWrapper = struct { - pub fn charCallbackWrapper(handle: ?*c.GLFWwindow, codepoint: c_uint) callconv(.C) void { + pub fn charCallbackWrapper(handle: ?*c.GLFWwindow, codepoint: c_uint) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), @as(u21, @intCast(codepoint)), @@ -1958,7 +1958,7 @@ pub inline fn setMouseButtonCallback(self: Window, comptime callback: ?fn (windo if (callback) |user_callback| { const CWrapper = struct { - pub fn mouseButtonCallbackWrapper(handle: ?*c.GLFWwindow, button: c_int, action: c_int, mods: c_int) callconv(.C) void { + pub fn mouseButtonCallbackWrapper(handle: ?*c.GLFWwindow, button: c_int, action: c_int, mods: c_int) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), @as(MouseButton, @enumFromInt(button)), @@ -1996,7 +1996,7 @@ pub inline fn setCursorPosCallback(self: Window, comptime callback: ?fn (window: if (callback) |user_callback| { const CWrapper = struct { - pub fn cursorPosCallbackWrapper(handle: ?*c.GLFWwindow, xpos: f64, ypos: f64) callconv(.C) void { + pub fn cursorPosCallbackWrapper(handle: ?*c.GLFWwindow, xpos: f64, ypos: f64) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), xpos, @@ -2030,7 +2030,7 @@ pub inline fn setCursorEnterCallback(self: Window, comptime callback: ?fn (windo if (callback) |user_callback| { const CWrapper = struct { - pub fn cursorEnterCallbackWrapper(handle: ?*c.GLFWwindow, entered: c_int) callconv(.C) void { + pub fn cursorEnterCallbackWrapper(handle: ?*c.GLFWwindow, entered: c_int) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), entered == c.GLFW_TRUE, @@ -2067,7 +2067,7 @@ pub inline fn setScrollCallback(self: Window, comptime callback: ?fn (window: Wi if (callback) |user_callback| { const CWrapper = struct { - pub fn scrollCallbackWrapper(handle: ?*c.GLFWwindow, xoffset: f64, yoffset: f64) callconv(.C) void { + pub fn scrollCallbackWrapper(handle: ?*c.GLFWwindow, xoffset: f64, yoffset: f64) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), xoffset, @@ -2110,7 +2110,7 @@ pub inline fn setDropCallback(self: Window, comptime callback: ?fn (window: Wind if (callback) |user_callback| { const CWrapper = struct { - pub fn dropCallbackWrapper(handle: ?*c.GLFWwindow, path_count: c_int, paths: [*c][*c]const u8) callconv(.C) void { + pub fn dropCallbackWrapper(handle: ?*c.GLFWwindow, path_count: c_int, paths: [*c][*c]const u8) callconv(.c) void { @call(.always_inline, user_callback, .{ from(handle.?), @as([*][*:0]const u8, @ptrCast(paths))[0..@as(u32, @intCast(path_count))], diff --git a/pkg/glfw/errors.zig b/pkg/glfw/errors.zig index ce98ec5cd..b9721fd05 100644 --- a/pkg/glfw/errors.zig +++ b/pkg/glfw/errors.zig @@ -300,7 +300,7 @@ pub inline fn mustGetErrorString() [:0]const u8 { pub fn setErrorCallback(comptime callback: ?fn (error_code: ErrorCode, description: [:0]const u8) void) void { if (callback) |user_callback| { const CWrapper = struct { - pub fn errorCallbackWrapper(err_int: c_int, c_description: [*c]const u8) callconv(.C) void { + pub fn errorCallbackWrapper(err_int: c_int, c_description: [*c]const u8) callconv(.c) void { convertError(err_int) catch |error_code| { user_callback(error_code, mem.sliceTo(c_description, 0)); }; diff --git a/pkg/glfw/opengl.zig b/pkg/glfw/opengl.zig index de99582c2..04bc3a65c 100644 --- a/pkg/glfw/opengl.zig +++ b/pkg/glfw/opengl.zig @@ -161,7 +161,7 @@ pub const GLProc = *const fn () callconv(if (builtin.os.tag == .windows and buil /// @thread_safety This function may be called from any thread. /// /// see also: context_glext, glfwExtensionSupported -pub fn getProcAddress(proc_name: [*:0]const u8) callconv(.C) ?GLProc { +pub fn getProcAddress(proc_name: [*:0]const u8) callconv(.c) ?GLProc { internal_debug.assertInitialized(); if (c.glfwGetProcAddress(proc_name)) |proc_address| return @ptrCast(proc_address); return null; diff --git a/pkg/glfw/vulkan.zig b/pkg/glfw/vulkan.zig index 6c6021d02..1b84145d5 100644 --- a/pkg/glfw/vulkan.zig +++ b/pkg/glfw/vulkan.zig @@ -33,7 +33,7 @@ pub fn initVulkanLoader(loader_function: ?VKGetInstanceProcAddr) void { c.glfwInitVulkanLoader(loader_function orelse null); } -pub const VKGetInstanceProcAddr = *const fn (vk_instance: c.VkInstance, name: [*c]const u8) callconv(.C) ?VKProc; +pub const VKGetInstanceProcAddr = *const fn (vk_instance: c.VkInstance, name: [*c]const u8) callconv(.c) ?VKProc; /// Returns whether the Vulkan loader and an ICD have been found. /// @@ -127,7 +127,7 @@ pub const VKProc = *const fn () callconv(if (builtin.os.tag == .windows and buil /// @pointer_lifetime The returned function pointer is valid until the library is terminated. /// /// @thread_safety This function may be called from any thread. -pub fn getInstanceProcAddress(vk_instance: ?*anyopaque, proc_name: [*:0]const u8) callconv(.C) ?VKProc { +pub fn getInstanceProcAddress(vk_instance: ?*anyopaque, proc_name: [*:0]const u8) callconv(.c) ?VKProc { internal_debug.assertInitialized(); if (c.glfwGetInstanceProcAddress(if (vk_instance) |v| @as(c.VkInstance, @ptrCast(v)) else null, proc_name)) |proc_address| return proc_address; return null; diff --git a/pkg/harfbuzz/blob.zig b/pkg/harfbuzz/blob.zig index d25df6974..9472e4c75 100644 --- a/pkg/harfbuzz/blob.zig +++ b/pkg/harfbuzz/blob.zig @@ -77,11 +77,11 @@ pub const Blob = struct { comptime T: type, key: ?*anyopaque, ptr: ?*T, - comptime destroycb: ?*const fn (?*T) callconv(.C) void, + comptime destroycb: ?*const fn (?*T) callconv(.c) void, replace: bool, ) bool { const Callback = struct { - pub fn callback(data: ?*anyopaque) callconv(.C) void { + pub fn callback(data: ?*anyopaque) callconv(.c) void { @call(.{ .modifier = .always_inline }, destroycb, .{ @as(?*T, @ptrCast(@alignCast(data))), }); diff --git a/pkg/macos/foundation/array.zig b/pkg/macos/foundation/array.zig index 37fa2b985..d3a977539 100644 --- a/pkg/macos/foundation/array.zig +++ b/pkg/macos/foundation/array.zig @@ -84,7 +84,7 @@ pub const MutableArray = opaque { a: *const Elem, b: *const Elem, context: ?*Context, - ) callconv(.C) ComparisonResult, + ) callconv(.c) ComparisonResult, ) void { CFArraySortValues( self, @@ -155,7 +155,7 @@ test "array sorting" { void, null, struct { - fn compare(a: *const u8, b: *const u8, _: ?*void) callconv(.C) ComparisonResult { + fn compare(a: *const u8, b: *const u8, _: ?*void) callconv(.c) ComparisonResult { if (a.* > b.*) return .greater; if (a.* == b.*) return .equal; return .less; diff --git a/pkg/macos/video/display_link.zig b/pkg/macos/video/display_link.zig index ca0c80d0b..4bbf58a0c 100644 --- a/pkg/macos/video/display_link.zig +++ b/pkg/macos/video/display_link.zig @@ -66,7 +66,7 @@ pub const DisplayLink = opaque { flagsIn: c.CVOptionFlags, flagsOut: *c.CVOptionFlags, inner_userinfo: ?*anyopaque, - ) callconv(.C) c.CVReturn { + ) callconv(.c) c.CVReturn { _ = inNow; _ = inOutputTime; _ = flagsIn; diff --git a/pkg/opengl/glad.zig b/pkg/opengl/glad.zig index 79a2e4d6b..663e75e12 100644 --- a/pkg/opengl/glad.zig +++ b/pkg/opengl/glad.zig @@ -13,8 +13,8 @@ pub threadlocal var context: Context = undefined; /// The getProcAddress param is an anytype so that we can accept multiple /// forms of the function depending on what we're interfacing with. pub fn load(getProcAddress: anytype) !c_int { - const GlProc = *const fn () callconv(.C) void; - const GlfwFn = *const fn ([*:0]const u8) callconv(.C) ?GlProc; + const GlProc = *const fn () callconv(.c) void; + const GlfwFn = *const fn ([*:0]const u8) callconv(.c) ?GlProc; const res = switch (@TypeOf(getProcAddress)) { // glfw diff --git a/pkg/sentry/transport.zig b/pkg/sentry/transport.zig index 835b87cd3..747187211 100644 --- a/pkg/sentry/transport.zig +++ b/pkg/sentry/transport.zig @@ -5,8 +5,8 @@ const Envelope = @import("envelope.zig").Envelope; /// sentry_transport_t pub const Transport = opaque { - pub const SendFunc = *const fn (envelope: *Envelope, state: ?*anyopaque) callconv(.C) void; - pub const FreeFunc = *const fn (state: ?*anyopaque) callconv(.C) void; + pub const SendFunc = *const fn (envelope: *Envelope, state: ?*anyopaque) callconv(.c) void; + pub const FreeFunc = *const fn (state: ?*anyopaque) callconv(.c) void; pub fn init(f: SendFunc) *Transport { return @ptrCast(c.sentry_transport_new(@ptrCast(f)).?); diff --git a/po/ca_ES.UTF-8.po b/po/ca_ES.UTF-8.po index 5cbb7efd5..712f0d5af 100644 --- a/po/ca_ES.UTF-8.po +++ b/po/ca_ES.UTF-8.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-20 08:07+0100\n" "Last-Translator: Francesc Arpi \n" "Language-Team: \n" @@ -43,8 +43,8 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"S'han trobat un o més errors de configuració. Si us plau, revisa els errors a " -"continuació i torna a carregar la configuració o ignora aquests errors." +"S'han trobat un o més errors de configuració. Si us plau, revisa els errors " +"a continuació i torna a carregar la configuració o ignora aquests errors." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -56,6 +56,30 @@ msgstr "Ignora" msgid "Reload Configuration" msgstr "Carrega la configuració" +#: 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 "Divideix cap amunt" + +#: 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 "Divideix cap avall" + +#: 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 "Divideix a l'esquerra" + +#: 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 "Divideix a la dreta" + #: 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" @@ -87,33 +111,13 @@ msgstr "Divideix" msgid "Change Title…" msgstr "Canvia el títol…" -#: 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 "Divideix cap amunt" - -#: 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 "Divideix cap avall" - -#: 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 "Divideix a l'esquerra" - -#: 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 "Divideix a la dreta" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Pestanya" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Nova pestanya" @@ -150,7 +154,7 @@ msgid "Terminal Inspector" msgstr "Inspector de terminal" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "Sobre Ghostty" @@ -201,13 +205,32 @@ msgstr "" "Enganxar aquest text al terminal pot ser perillós, ja que sembla que es " "podrien executar algunes ordres." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Inspector de terminal" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Menú principal" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "Copiat al porta-retalls" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Mostra les pestanyes obertes" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Estàs executant una versió de depuració de Ghostty! El rendiment es veurà " +"afectat." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "S'ha tornat a carregar la configuració" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Desenvolupadors de Ghostty" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -245,25 +268,10 @@ msgstr "Totes les sessions del terminal en aquesta pestanya es tancaran." msgid "The currently running process in this split will be terminated." msgstr "El procés actualment en execució en aquesta divisió es tancarà." -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Menú principal" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Copiat al porta-retalls" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Mostra les pestanyes obertes" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "" -"⚠️ Estàs executant una versió de depuració de Ghostty! El rendiment es " -"veurà afectat." - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "S'ha tornat a carregar la configuració" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Desenvolupadors de Ghostty" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Inspector de terminal" diff --git a/po/com.mitchellh.ghostty.pot b/po/com.mitchellh.ghostty.pot index 4bf47da53..3892d14d8 100644 --- a/po/com.mitchellh.ghostty.pot +++ b/po/com.mitchellh.ghostty.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -54,6 +54,30 @@ msgstr "" msgid "Reload Configuration" 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 "" + +#: 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 "" + +#: 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 "" + +#: 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 "" + #: 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" @@ -85,33 +109,13 @@ msgstr "" msgid "Change Title…" msgstr "" -#: 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 "" - -#: 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 "" - -#: 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 "" - -#: 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 "" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "" @@ -148,7 +152,7 @@ msgid "Terminal Inspector" msgstr "" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "" @@ -193,12 +197,29 @@ msgid "" "commands may be executed." msgstr "" -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" msgstr "" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" msgstr "" #: src/apprt/gtk/CloseDialog.zig:47 @@ -237,23 +258,10 @@ msgstr "" msgid "The currently running process in this split will be terminated." msgstr "" -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" msgstr "" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "" - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" msgstr "" diff --git a/po/de_DE.UTF-8.po b/po/de_DE.UTF-8.po index 1de7a7b96..44f3bae39 100644 --- a/po/de_DE.UTF-8.po +++ b/po/de_DE.UTF-8.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-06 14:57+0100\n" "Last-Translator: Robin \n" "Language-Team: German \n" @@ -55,6 +55,30 @@ msgstr "" msgid "Reload Configuration" msgstr "Konfiguration neu laden" +#: 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 "Fenster nach oben teilen" + +#: 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 "Fenster nach unten teilen" + +#: 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 "Fenter nach links teilen" + +#: 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 "Fenster nach rechts teilen" + #: 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" @@ -86,33 +110,13 @@ msgstr "Fenster teilen" msgid "Change Title…" msgstr "Titel bearbeiten…" -#: 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 "Fenster nach oben teilen" - -#: 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 "Fenster nach unten teilen" - -#: 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 "Fenter nach links teilen" - -#: 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 "Fenster nach rechts teilen" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Tab" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Neuer Tab" @@ -149,7 +153,7 @@ msgid "Terminal Inspector" msgstr "Terminalinspektor" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "Über Ghostty" @@ -200,13 +204,32 @@ msgstr "" "Diesen Text in das Terminal einzufügen könnte möglicherweise gefährlich " "sein. Es scheint, dass Anweisungen ausgeführt werden könnten." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Hauptmenü" + +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Offene Tabs einblenden" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" msgstr "" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "In die Zwischenablage kopiert" +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Du verwendest einen Debug Build von Ghostty! Die Leistung wird reduziert " +"sein." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "Konfiguration wurde neu geladen" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Ghostty-Entwickler" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -244,25 +267,10 @@ msgstr "Alle Terminalsitzungen in diesem Tab werden beendet." msgid "The currently running process in this split will be terminated." msgstr "Der aktuell laufende Prozess in diesem geteilten Fenster wird beendet." -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Hauptmenü" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "In die Zwischenablage kopiert" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Offene Tabs einblenden" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" msgstr "" -"⚠️ Du verwendest einen Debug Build von Ghostty! Die Leistung wird reduziert " -"sein." - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "Konfiguration wurde neu geladen" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Ghostty-Entwickler" diff --git a/po/es_BO.UTF-8.po b/po/es_BO.UTF-8.po index 339ff54c4..f3a62748a 100644 --- a/po/es_BO.UTF-8.po +++ b/po/es_BO.UTF-8.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-28 17:46+0200\n" "Last-Translator: Miguel Peredo \n" "Language-Team: Spanish \n" @@ -43,8 +43,8 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Se encontraron uno o más errores de configuración. Por favor revise los errores a continuación, " -"y recargue su configuración o ignore estos errores." +"Se encontraron uno o más errores de configuración. Por favor revise los " +"errores a continuación, y recargue su configuración o ignore estos errores." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -56,6 +56,30 @@ msgstr "Ignorar" msgid "Reload Configuration" msgstr "Recargar configuración" +#: 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 "Dividir arriba" + +#: 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 "Dividir abajo" + +#: 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 "Dividir a la izquierda" + +#: 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 "Dividir a la derecha" + #: 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" @@ -87,33 +111,13 @@ msgstr "Dividir" msgid "Change Title…" msgstr "Cambiar título…" -#: 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 "Dividir arriba" - -#: 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 "Dividir abajo" - -#: 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 "Dividir a la izquierda" - -#: 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 "Dividir a la derecha" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Pestaña" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Nueva pestaña" @@ -150,7 +154,7 @@ msgid "Terminal Inspector" msgstr "Inspector de la terminal" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "Acerca de Ghostty" @@ -201,13 +205,32 @@ msgstr "" "Pegar este texto en la terminal puede ser peligroso ya que parece que " "algunos comandos podrían ejecutarse." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Inspector de la terminal" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Menú principal" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "Copiado al portapapeles" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Ver pestañas abiertas" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Está ejecutando una versión de depuración de Ghostty. El rendimiento no " +"será óptimo." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "Configuración recargada" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Desarrolladores de Ghostty" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -245,23 +268,10 @@ msgstr "Todas las sesiones de terminal en esta pestaña serán terminadas." msgid "The currently running process in this split will be terminated." msgstr "El proceso actualmente en ejecución en esta división será terminado." -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Menú principal" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Copiado al portapapeles" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Ver pestañas abiertas" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "⚠️ Está ejecutando una versión de depuración de Ghostty. El rendimiento no será óptimo." - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "Configuración recargada" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Desarrolladores de Ghostty" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Inspector de la terminal" diff --git a/po/fr_FR.UTF-8.po b/po/fr_FR.UTF-8.po index fc5bfd054..4db72a23e 100644 --- a/po/fr_FR.UTF-8.po +++ b/po/fr_FR.UTF-8.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-22 09:31+0100\n" "Last-Translator: Kirwiisp \n" "Language-Team: French \n" @@ -43,8 +43,9 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Une ou plusieurs erreurs de configuration ont été trouvées. Veuillez lire les erreurs ci-dessous," -"et recharger votre configuration ou bien ignorer ces erreurs." +"Une ou plusieurs erreurs de configuration ont été trouvées. Veuillez lire " +"les erreurs ci-dessous,et recharger votre configuration ou bien ignorer ces " +"erreurs." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -56,6 +57,30 @@ msgstr "Ignorer" msgid "Reload Configuration" msgstr "Recharger la configuration" +#: 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 "Panneau en haut" + +#: 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 "Panneau en bas" + +#: 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 "Panneau à gauche" + +#: 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 "Panneau à droite" + #: 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" @@ -87,33 +112,13 @@ msgstr "Créer panneau" msgid "Change Title…" msgstr "Changer le titre…" -#: 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 "Panneau en haut" - -#: 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 "Panneau en bas" - -#: 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 "Panneau à gauche" - -#: 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 "Panneau à droite" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Onglet" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Nouvel onglet" @@ -150,7 +155,7 @@ msgid "Terminal Inspector" msgstr "Inspecteur de terminal" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "À propos de Ghostty" @@ -168,8 +173,8 @@ msgid "" "An application is attempting to read from the clipboard. The current " "clipboard contents are shown below." msgstr "" -"Une application essaie de lire depuis le presse-papiers." -"Le contenu actuel du presse-papiers est affiché ci-dessous." +"Une application essaie de lire depuis le presse-papiers.Le contenu actuel du " +"presse-papiers est affiché ci-dessous." #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 @@ -186,8 +191,8 @@ msgid "" "An application is attempting to write to the clipboard. The current " "clipboard contents are shown below." msgstr "" -"Une application essaie d'écrire dans le presse-papiers." -"Le contenu actuel du presse-papiers est affiché ci-dessous." +"Une application essaie d'écrire dans le presse-papiers.Le contenu actuel du " +"presse-papiers est affiché ci-dessous." #: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 msgid "Warning: Potentially Unsafe Paste" @@ -198,16 +203,35 @@ msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." msgstr "" -"Coller ce texte dans le terminal pourrait être dangereux, " -"il semblerait que certaines commandes pourraient être exécutées." +"Coller ce texte dans le terminal pourrait être dangereux, il semblerait que " +"certaines commandes pourraient être exécutées." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Inspecteur" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Menu principal" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "Copié dans le presse-papiers" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Voir les onglets ouverts" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Vous utilisez une version de débogage de Ghostty ! Les performances seront " +"dégradées." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "Recharger la configuration" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Les développeurs de Ghostty" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -245,24 +269,10 @@ msgstr "Toutes les sessions de cet onglet vont être arrêtées." msgid "The currently running process in this split will be terminated." msgstr "Le processus en cours dans ce panneau va être arrêté." -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Menu principal" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Copié dans le presse-papiers" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Voir les onglets ouverts" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "" -"⚠️ Vous utilisez une version de débogage de Ghostty ! Les performances seront dégradées." - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "Recharger la configuration" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Les développeurs de Ghostty" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Inspecteur" diff --git a/po/id_ID.UTF-8.po b/po/id_ID.UTF-8.po index c8b89a89e..d5204d420 100644 --- a/po/id_ID.UTF-8.po +++ b/po/id_ID.UTF-8.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-20 15:19+0700\n" "Last-Translator: Satrio Bayu Aji \n" "Language-Team: Indonesian \n" @@ -42,8 +42,8 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Ditemukan satu atau lebih kesalahan konfigurasi. Silakan tinjau kesalahan di bawah ini, " -"dan muat ulang konfigurasi anda atau abaikan kesalahan ini." +"Ditemukan satu atau lebih kesalahan konfigurasi. Silakan tinjau kesalahan di " +"bawah ini, dan muat ulang konfigurasi anda atau abaikan kesalahan ini." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -55,6 +55,30 @@ msgstr "Abaikan" msgid "Reload Configuration" msgstr "Muat ulang konfigurasi" +#: 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 "Belah atas" + +#: 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 "Belah bawah" + +#: 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 "Belah kiri" + +#: 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 "Belah kanan" + #: 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" @@ -86,33 +110,13 @@ msgstr "Belah" msgid "Change Title…" msgstr "Ubah judul…" -#: 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 "Belah atas" - -#: 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 "Belah bawah" - -#: 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 "Belah kiri" - -#: 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 "Belah kanan" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Tab" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Tab baru" @@ -149,7 +153,7 @@ msgid "Terminal Inspector" msgstr "Inspektur terminal" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "Tentang Ghostty" @@ -167,8 +171,8 @@ msgid "" "An application is attempting to read from the clipboard. The current " "clipboard contents are shown below." msgstr "" -"Aplikasi sedang mencoba membaca dari papan klip. Isi papan klip " -"saat ini ditampilkan di bawah ini." +"Aplikasi sedang mencoba membaca dari papan klip. Isi papan klip saat ini " +"ditampilkan di bawah ini." #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 @@ -185,8 +189,8 @@ msgid "" "An application is attempting to write to the clipboard. The current " "clipboard contents are shown below." msgstr "" -"Aplikasi sedang mencoba menulis ke papan klip. Isi papan klip " -"saat ini ditampilkan di bawah ini." +"Aplikasi sedang mencoba menulis ke papan klip. Isi papan klip saat ini " +"ditampilkan di bawah ini." #: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 msgid "Warning: Potentially Unsafe Paste" @@ -200,13 +204,31 @@ msgstr "" "Menempelkan teks ini ke terminal mungkin berbahaya karena sepertinya " "beberapa perintah mungkin dijalankan." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Inspektur terminal" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Menu utama" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "Disalin ke papan klip" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Lihat tab terbuka" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Anda sedang menjalankan versi debug dari Ghostty! Performa akan menurun." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "Memuat ulang konfigurasi" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Pengembang Ghostty" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -244,23 +266,10 @@ msgstr "Semua sesi terminal di tab ini akan diakhiri." msgid "The currently running process in this split will be terminated." msgstr "Proses yang sedang berjalan dalam belahan ini akan diakhiri." -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Menu utama" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Disalin ke papan klip" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Lihat tab terbuka" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "⚠️ Anda sedang menjalankan versi debug dari Ghostty! Performa akan menurun." - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "Memuat ulang konfigurasi" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Pengembang Ghostty" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Inspektur terminal" diff --git a/po/ja_JP.UTF-8.po b/po/ja_JP.UTF-8.po index e06ec2fbc..e6e015f8a 100644 --- a/po/ja_JP.UTF-8.po +++ b/po/ja_JP.UTF-8.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-21 00:08+0900\n" "Last-Translator: Lon Sagisawa \n" "Language-Team: Japanese\n" @@ -44,8 +44,8 @@ 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 msgid "Ignore" @@ -57,6 +57,30 @@ msgstr "無視" msgid "Reload Configuration" 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 "上に分割" + +#: 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 "下に分割" + +#: 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 "左に分割" + +#: 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 "右に分割" + #: 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" @@ -88,33 +112,13 @@ msgstr "分割" msgid "Change Title…" msgstr "タイトルを変更…" -#: 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 "上に分割" - -#: 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 "下に分割" - -#: 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 "左に分割" - -#: 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 "右に分割" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "タブ" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "新しいタブ" @@ -151,7 +155,7 @@ msgid "Terminal Inspector" msgstr "端末インスペクター" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "Ghostty について" @@ -169,8 +173,8 @@ 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 @@ -187,8 +191,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 msgid "Warning: Potentially Unsafe Paste" @@ -199,16 +203,34 @@ msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." msgstr "" -"このテキストには実行可能なコマンドが含まれており、" -"ターミナルに貼り付けるのは危険な可能性があります。" +"このテキストには実行可能なコマンドが含まれており、ターミナルに貼り付けるのは" +"危険な可能性があります。" -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: 端末インスペクター" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "メインメニュー" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "クリップボードにコピーしました" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "開いているすべてのタブを表示" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Ghostty のデバッグビルドを実行しています! パフォーマンスが低下しています。" + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "設定を再読み込みしました" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Ghostty 開発者" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -246,23 +268,10 @@ msgstr "タブ内のすべてのターミナルセッションが終了します msgid "The currently running process in this split will be terminated." msgstr "分割ウィンドウ内のすべてのプロセスが終了します。" -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "メインメニュー" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "クリップボードにコピーしました" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "開いているすべてのタブを表示" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "⚠️ Ghostty のデバッグビルドを実行しています! パフォーマンスが低下しています。" - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "設定を再読み込みしました" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Ghostty 開発者" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: 端末インスペクター" diff --git a/po/mk_MK.UTF-8.po b/po/mk_MK.UTF-8.po index 5552cc6e4..39bb72b91 100644 --- a/po/mk_MK.UTF-8.po +++ b/po/mk_MK.UTF-8.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-23 14:17+0100\n" "Last-Translator: Andrej Daskalov \n" "Language-Team: Macedonian\n" @@ -41,7 +41,10 @@ msgstr "Грешки во конфигурацијата" msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." -msgstr "Пронајдени се една или повеќе грешки во конфигурацијата. Прегледајте ги грешките подолу и повторно вчитајте ја конфигурацијата или игнорирајте ги овие грешки." +msgstr "" +"Пронајдени се една или повеќе грешки во конфигурацијата. Прегледајте ги " +"грешките подолу и повторно вчитајте ја конфигурацијата или игнорирајте ги " +"овие грешки." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -53,6 +56,30 @@ msgstr "Игнорирај" msgid "Reload Configuration" 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 "Подели нагоре" + +#: 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 "Подели надолу" + +#: 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 "Подели налево" + +#: 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 "Подели надесно" + #: 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" @@ -84,33 +111,13 @@ msgstr "Подели" msgid "Change Title…" msgstr "Промени наслов…" -#: 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 "Подели нагоре" - -#: 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 "Подели надолу" - -#: 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 "Подели налево" - -#: 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 "Подели надесно" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Јазиче" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Ново јазиче" @@ -147,7 +154,7 @@ msgid "Terminal Inspector" msgstr "Инспектор на терминал" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "За Ghostty" @@ -164,7 +171,9 @@ msgstr "Авторизирај пристап до привремена мемо msgid "" "An application is attempting to read from the clipboard. The current " "clipboard contents are shown below." -msgstr "Апликација се обидува да чита од привремената меморија. Содржината е прикажана подолу." +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 @@ -180,7 +189,9 @@ msgstr "Дозволи" msgid "" "An application is attempting to write to the clipboard. The current " "clipboard contents are shown below." -msgstr "Апликација се обидува да запише во привремената меморија. Содржината е прикажана подолу." +msgstr "" +"Апликација се обидува да запише во привремената меморија. Содржината е " +"прикажана подолу." #: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 msgid "Warning: Potentially Unsafe Paste" @@ -190,15 +201,35 @@ msgstr "Предупредување: Потенцијално небезбед msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." -msgstr "Вметнувањето на овој текст во терминалот може да биде опасно, бидејќи изгледа како да ќе се извршат одредени команди." +msgstr "" +"Вметнувањето на овој текст во терминалот може да биде опасно, бидејќи " +"изгледа како да ќе се извршат одредени команди." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Инспектор на терминал" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Главно мени" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "Копирано во привремена меморија" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Прегледај отворени јазичиња" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Извршувате дебаг верзија на Ghostty! Перформансите ќе бидат намалени." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "Конфигурацијата е одново вчитана" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Развивачи на Ghostty" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -236,23 +267,10 @@ msgstr "Сите сесии во ова јазиче ќе бидат преки msgid "The currently running process in this split will be terminated." msgstr "Процесот кој моментално се извршува во оваа поделба ќе биде прекинат." -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Главно мени" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Копирано во привремена меморија" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Прегледај отворени јазичиња" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "⚠️ Извршувате дебаг верзија на Ghostty! Перформансите ќе бидат намалени." - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "Конфигурацијата е одново вчитана" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Развивачи на Ghostty" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Инспектор на терминал" diff --git a/po/nb_NO.UTF-8.po b/po/nb_NO.UTF-8.po index bd7c8876a..ad76eea3d 100644 --- a/po/nb_NO.UTF-8.po +++ b/po/nb_NO.UTF-8.po @@ -10,6 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-04-14 16:25+0200\n" "Last-Translator: cryptocode \n" "Language-Team: Norwegian Bokmal \n" @@ -45,8 +46,8 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Én eller flere konfigurasjonsfeil ble funnet. Vennligst gjennomgå feilene under, " -"og enten last konfigurasjonen din på nytt eller ignorer disse feilene." +"Én eller flere konfigurasjonsfeil ble funnet. Vennligst gjennomgå feilene " +"under, og enten last konfigurasjonen din på nytt eller ignorer disse feilene." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -58,6 +59,30 @@ msgstr "Ignorer" msgid "Reload Configuration" msgstr "Last konfigurasjon på nytt" +#: 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 "Splitt opp" + +#: 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 "Splitt ned" + +#: 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 "Splitt venstre" + +#: 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 "Splitt høyre" + #: 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" @@ -89,33 +114,13 @@ msgstr "Splitt" msgid "Change Title…" msgstr "Endre tittel…" -#: 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 "Splitt opp" - -#: 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 "Splitt ned" - -#: 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 "Splitt venstre" - -#: 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 "Splitt høyre" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Fane" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Ny fane" @@ -152,7 +157,7 @@ msgid "Terminal Inspector" msgstr "Terminalinspektør" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "Om Ghostty" @@ -203,13 +208,30 @@ msgstr "" "Det ser ut som at kommandoer vil bli kjørt hvis du limer inn dette, vurder " "om du mener det er trygt." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Terminalinspektør" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Hovedmeny" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "Kopiert til utklippstavlen" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Se åpne faner" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ Du kjører et debug-bygg av Ghostty. Debug-bygg har redusert ytelse." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "Konfigurasjonen ble lastet på nytt" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Ghostty-utviklere" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -247,23 +269,10 @@ msgstr "Alle terminaløkter i denne fanen vil bli avsluttet." msgid "The currently running process in this split will be terminated." msgstr "Den kjørende prosessen for denne splitten vil bli avsluttet." -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Hovedmeny" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Kopiert til utklippstavlen" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Se åpne faner" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "⚠️ Du kjører et debug-bygg av Ghostty. Debug-bygg har redusert ytelse." - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "Konfigurasjonen ble lastet på nytt" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Ghostty-utviklere" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Terminalinspektør" diff --git a/po/nl_NL.UTF-8.po b/po/nl_NL.UTF-8.po index 6ebea478b..466116352 100644 --- a/po/nl_NL.UTF-8.po +++ b/po/nl_NL.UTF-8.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-24 15:00+0100\n" "Last-Translator: Nico Geesink \n" "Language-Team: Dutch \n" @@ -43,8 +43,8 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Er zijn één of meer configuratiefouten gevonden. Bekijk de onderstaande fouten " -"en herlaad je configuratie of negeer deze fouten." +"Er zijn één of meer configuratiefouten gevonden. Bekijk de onderstaande " +"fouten en herlaad je configuratie of negeer deze fouten." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -56,6 +56,30 @@ msgstr "Negeer" msgid "Reload Configuration" msgstr "Herlaad configuratie" +#: 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 "Splits naar boven" + +#: 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 "Splits naar beneden" + +#: 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 "Splits naar links" + +#: 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 "Splits naar rechts" + #: 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" @@ -87,33 +111,13 @@ msgstr "Splitsen" msgid "Change Title…" msgstr "Wijzig titel…" -#: 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 "Splits naar boven" - -#: 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 "Splits naar beneden" - -#: 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 "Splits naar links" - -#: 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 "Splits naar rechts" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Tabblad" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Nieuw tabblad" @@ -150,7 +154,7 @@ msgid "Terminal Inspector" msgstr "Terminal inspecteur" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "Over Ghostty" @@ -198,16 +202,35 @@ msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." msgstr "" -"Het plakken van deze tekst in de terminal is mogelijk gevaarlijk, omdat " -"het lijkt op een commando dat uitgevoerd kan worden." +"Het plakken van deze tekst in de terminal is mogelijk gevaarlijk, omdat het " +"lijkt op een commando dat uitgevoerd kan worden." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: terminal inspecteur" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Hoofdmenu" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "Gekopieerd naar klembord" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Open tabbladen bekijken" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Je draait een debug versie van Ghostty! Prestaties zullen minder zijn dan " +"normaal." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "De configuratie is herladen" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Ghostty ontwikkelaars" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -243,26 +266,13 @@ msgstr "Alle terminalsessies binnen dit tabblad zullen worden beëindigd." #: src/apprt/gtk/CloseDialog.zig:99 msgid "The currently running process in this split will be terminated." -msgstr "Alle processen die nu draaien in deze splitsing zullen worden beëindigd." - -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Hoofdmenu" - -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Open tabbladen bekijken" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." msgstr "" -"⚠️ Je draait een debug versie van Ghostty! Prestaties zullen minder zijn dan normaal." +"Alle processen die nu draaien in deze splitsing zullen worden beëindigd." -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "De configuratie is herladen" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Gekopieerd naar klembord" -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Ghostty ontwikkelaars" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: terminal inspecteur" diff --git a/po/pl_PL.UTF-8.po b/po/pl_PL.UTF-8.po index 492326c17..22d2cd975 100644 --- a/po/pl_PL.UTF-8.po +++ b/po/pl_PL.UTF-8.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-18 11:48+0100\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-17 12:15+0100\n" "Last-Translator: Bartosz Sokorski \n" "Language-Team: Polish \n" @@ -45,8 +45,8 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Znaleziono jeden lub więcej błędów konfiguracji. Sprawdź błędy wylistowane poniżej " -"i przeładuj konfigurację lub zignoruj je." +"Znaleziono jeden lub więcej błędów konfiguracji. Sprawdź błędy wylistowane " +"poniżej i przeładuj konfigurację lub zignoruj je." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -58,6 +58,30 @@ msgstr "Zignoruj" msgid "Reload Configuration" msgstr "Przeładuj konfigurację" +#: 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 "Podziel w górę" + +#: 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 "Podziel w dół" + +#: 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 "Podziel w lewo" + +#: 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 "Podziel w prawo" + #: 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" @@ -89,33 +113,13 @@ msgstr "Podział" msgid "Change Title…" msgstr "Zmień tytuł…" -#: 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 "Podziel w górę" - -#: 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 "Podziel w dół" - -#: 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 "Podziel w lewo" - -#: 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 "Podziel w prawo" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Karta" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Nowa karta" @@ -152,7 +156,7 @@ msgid "Terminal Inspector" msgstr "Inspektor terminala" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:958 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "O Ghostty" @@ -203,31 +207,31 @@ msgstr "" "Wklejenie tego tekstu do terminala może być niebezpieczne, ponieważ może " "spowodować wykonanie komend." -#: src/apprt/gtk/Window.zig:200 +#: src/apprt/gtk/Window.zig:201 msgid "Main Menu" msgstr "Menu główne" -#: src/apprt/gtk/Window.zig:221 +#: src/apprt/gtk/Window.zig:222 msgid "View Open Tabs" msgstr "Zobacz otwarte karty" -#: src/apprt/gtk/Window.zig:295 +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 msgid "" "⚠️ You're running a debug build of Ghostty! Performance will be degraded." msgstr "⚠️ Używasz wersji Ghostty do debugowania! Wydajność będzie obniżona." -#: src/apprt/gtk/Window.zig:725 +#: src/apprt/gtk/Window.zig:744 msgid "Reloaded the configuration" msgstr "Przeładowano konfigurację" -#: src/apprt/gtk/Window.zig:939 +#: src/apprt/gtk/Window.zig:984 msgid "Ghostty Developers" msgstr "Twórcy Ghostty" -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Inspektor terminala Ghostty" - #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" msgstr "Zamknij" @@ -264,6 +268,10 @@ msgstr "Wszystkie sesje terminala w obecnej karcie zostaną zakończone." msgid "The currently running process in this split will be terminated." msgstr "Wszyskie trwające procesy w obecnym podziale zostaną zakończone." -#: src/apprt/gtk/Surface.zig:1242 +#: src/apprt/gtk/Surface.zig:1243 msgid "Copied to clipboard" msgstr "Skopiowano do schowka" + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Inspektor terminala Ghostty" diff --git a/po/pt_BR.UTF-8.po b/po/pt_BR.UTF-8.po index f9fadce66..f6d2f26a2 100644 --- a/po/pt_BR.UTF-8.po +++ b/po/pt_BR.UTF-8.po @@ -8,11 +8,11 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-28 11:04-0300\n" "Last-Translator: Gustavo Peres \n" -"Language-Team: Brazilian Portuguese \n" +"Language-Team: Brazilian Portuguese \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -45,8 +45,8 @@ msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." msgstr "" -"Um ou mais erros de configuração encontrados. Por favor revise os erros abaixo, " -"e ou recarregue sua configuração, ou ignore esses erros." +"Um ou mais erros de configuração encontrados. Por favor revise os erros " +"abaixo, e ou recarregue sua configuração, ou ignore esses erros." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -58,6 +58,30 @@ msgstr "Ignorar" msgid "Reload Configuration" msgstr "Recarregar configuração" +#: 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 "Dividir para cima" + +#: 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 "Dividir para baixo" + +#: 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 "Dividir à esquerda" + +#: 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 "Dividir à direita" + #: 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" @@ -89,33 +113,13 @@ msgstr "Dividir" msgid "Change Title…" msgstr "Mudar título…" -#: 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 "Dividir para cima" - -#: 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 "Dividir para baixo" - -#: 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 "Dividir à esquerda" - -#: 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 "Dividir à direita" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Aba" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Nova aba" @@ -152,7 +156,7 @@ msgid "Terminal Inspector" msgstr "Inspetor de terminal" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "Sobre o Ghostty" @@ -170,8 +174,8 @@ msgid "" "An application is attempting to read from the clipboard. The current " "clipboard contents are shown below." msgstr "" -"Uma aplicação está tentando ler da área de transferência. O conteúdo " -"atual da área de transferência está sendo exibido abaixo." +"Uma aplicação está tentando ler da área de transferência. O conteúdo atual " +"da área de transferência está sendo exibido abaixo." #: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 #: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 @@ -203,13 +207,31 @@ msgstr "" "Colar esse texto em um terminal pode ser perigoso, pois parece que alguns " "comandos podem ser executados." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Inspetor de terminal" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Menu Principal" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "Copiado para a área de transferência" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Visualizar abas abertas" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Você está rodando uma build de debug do Ghostty! O desempenho será afetado." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "Configuração recarregada" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Desenvolvedores Ghostty" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -247,23 +269,10 @@ msgstr "Todas as sessões de terminal nessa aba serão finalizadas." msgid "The currently running process in this split will be terminated." msgstr "O processo atual rodando nessa divisão será finalizado." -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Menu Principal" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Copiado para a área de transferência" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Visualizar abas abertas" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "⚠️ Você está rodando uma build de debug do Ghostty! O desempenho será afetado." - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "Configuração recarregada" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Desenvolvedores Ghostty" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Inspetor de terminal" diff --git a/po/ru_RU.UTF-8.po b/po/ru_RU.UTF-8.po index a3c21a246..9e9cf8077 100644 --- a/po/ru_RU.UTF-8.po +++ b/po/ru_RU.UTF-8.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:28-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-24 00:01+0500\n" "Last-Translator: blackzeshi \n" "Language-Team: Russian \n" @@ -44,9 +44,9 @@ msgstr "Ошибки конфигурации" msgid "" "One or more configuration errors were found. Please review the errors below, " "and either reload your configuration or ignore these errors." -msgstr "" -"Конфигурация содержит ошибки. Проверьте их ниже, а затем" -"либо перезагрузите конфигурацию, либо проигнорируйте ошибки." +msgstr "" +"Конфигурация содержит ошибки. Проверьте их ниже, а затем либо перезагрузите " +"конфигурацию, либо проигнорируйте ошибки." #: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 msgid "Ignore" @@ -58,6 +58,30 @@ msgstr "Игнорировать" msgid "Reload Configuration" 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 "Сплит вверх" + +#: 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 "Сплит вниз" + +#: 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 "Сплит влево" + +#: 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 "Сплит вправо" + #: 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" @@ -89,33 +113,13 @@ msgstr "Сплит" msgid "Change Title…" msgstr "Изменить заголовок…" -#: 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 "Сплит вверх" - -#: 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 "Сплит вниз" - -#: 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 "Сплит влево" - -#: 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 "Сплит вправо" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Вкладка" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Новая вкладка" @@ -152,7 +156,7 @@ msgid "Terminal Inspector" msgstr "Инспектор терминала" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "О Ghostty" @@ -169,9 +173,9 @@ msgstr "Разрешить доступ к буферу обмена" msgid "" "An application is attempting to read from the clipboard. The current " "clipboard contents are shown below." -msgstr "" -"Приложение пытается прочитать данные из буфера обмена. Эти данные " -"отображены ниже." +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 @@ -187,9 +191,8 @@ msgstr "Разрешить" msgid "" "An application is attempting to write to the clipboard. The current " "clipboard contents are shown below." -msgstr "" -"Приложение пытается записать данные в буфер обмена. Эти данные " -"показаны ниже." +msgstr "" +"Приложение пытается записать данные в буфер обмена. Эти данные показаны ниже." #: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 msgid "Warning: Potentially Unsafe Paste" @@ -199,17 +202,36 @@ msgstr "Внимание! Вставляемые данные могут нан msgid "" "Pasting this text into the terminal may be dangerous as it looks like some " "commands may be executed." -msgstr "" -"Вставка этого текста в терминал может быть опасной. Это выглядит " -"как команды, которые могут быть исполнены." +msgstr "" +"Вставка этого текста в терминал может быть опасной. Это выглядит как " +"команды, которые могут быть исполнены." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: инспектор терминала" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Главное меню" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "Скопировано в буфер обмена" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Просмотреть открытые вкладки" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Вы запустили отладочную сборку Ghostty! Это может влиять на " +"производительность." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "Конфигурация была обновлена" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Разработчики Ghostty" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -247,24 +269,10 @@ msgstr "Все сессии терминала в этой вкладке буд msgid "The currently running process in this split will be terminated." msgstr "Процесс, работающий в этой сплит-области, будет остановлен." -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Главное меню" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Скопировано в буфер обмена" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Просмотреть открытые вкладки" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "" -"⚠️ Вы запустили отладочную сборку Ghostty! Это может влиять на производительность." - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "Конфигурация была обновлена" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Разработчики Ghostty" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: инспектор терминала" diff --git a/po/tr_TR.UTF-8.po b/po/tr_TR.UTF-8.po index cee17a6a1..ac1bfdfc7 100644 --- a/po/tr_TR.UTF-8.po +++ b/po/tr_TR.UTF-8.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-24 22:01+0300\n" "Last-Translator: Emir SARI \n" "Language-Team: Turkish\n" @@ -57,6 +57,30 @@ msgstr "Yok Say" msgid "Reload Configuration" msgstr "Yapılandırmayı Yeniden Yükle" +#: 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 "Yukarı Doğru Böl" + +#: 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 "Aşağı Doğru Böl" + +#: 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 "Sola Doğru Böl" + +#: 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 "Sağa Doğru Böl" + #: 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" @@ -88,33 +112,13 @@ msgstr "Böl" msgid "Change Title…" msgstr "Başlığı Değiştir…" -#: 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 "Yukarı Doğru Böl" - -#: 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 "Aşağı Doğru Böl" - -#: 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 "Sola Doğru Böl" - -#: 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 "Sağa Doğru Böl" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Sekme" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Yeni Sekme" @@ -151,7 +155,7 @@ msgid "Terminal Inspector" msgstr "Uçbirim Denetçisi" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "Ghostty Hakkında" @@ -202,13 +206,32 @@ msgstr "" "Bu metni uçbirime yapıştırmak tehlikeli olabilir; çünkü bir komut " "yürütülebilecekmiş gibi duruyor." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Uçbirim Denetçisi" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Ana Menü" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "Panoya kopyalandı" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Açık Sekmeleri Görüntüle" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Ghostty’nin hata ayıklama amaçlı yapılmış bir sürümünü kullanıyorsunuz! " +"Başarım normale göre daha düşük olacaktır." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "Yapılandırma yeniden yüklendi" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Ghostty Geliştiricileri" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -246,25 +269,10 @@ msgstr "Bu sekmedeki tüm uçbirim oturumları sonlandırılacaktır." msgid "The currently running process in this split will be terminated." msgstr "Bu bölmedeki şu anda çalışan süreç sonlandırılacaktır." -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Ana Menü" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Panoya kopyalandı" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Açık Sekmeleri Görüntüle" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "" -"⚠️ Ghostty’nin hata ayıklama amaçlı yapılmış bir sürümünü kullanıyorsunuz! " -"Başarım normale göre daha düşük olacaktır." - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "Yapılandırma yeniden yüklendi" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Ghostty Geliştiricileri" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Uçbirim Denetçisi" diff --git a/po/uk_UA.UTF-8.po b/po/uk_UA.UTF-8.po index 662117071..5a264b537 100644 --- a/po/uk_UA.UTF-8.po +++ b/po/uk_UA.UTF-8.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-03-16 20:16+0200\n" "Last-Translator: Danylo Zalizchuk \n" "Language-Team: Ukrainian \n" @@ -44,8 +44,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 msgid "Ignore" @@ -57,6 +58,30 @@ msgstr "Ігнорувати" msgid "Reload Configuration" 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 "Розділити панель вгору" + +#: 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 "Розділити панель вниз" + +#: 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 "Розділити панель ліворуч" + +#: 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 "Розділити панель праворуч" + #: 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" @@ -88,33 +113,13 @@ msgstr "Розділена панель" msgid "Change Title…" msgstr "Змінити заголовок…" -#: 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 "Розділити панель вгору" - -#: 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 "Розділити панель вниз" - -#: 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 "Розділити панель ліворуч" - -#: 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 "Розділити панель праворуч" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "Вкладка" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "Нова вкладка" @@ -151,7 +156,7 @@ msgid "Terminal Inspector" msgstr "Інспектор терміналу" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "Про Ghostty" @@ -202,13 +207,31 @@ msgstr "" "Вставка цього тексту в термінал може бути небезпечною, оскільки виглядає " "так, ніби деякі команди можуть бути виконані." -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty: Інспектор терміналу" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "Головне меню" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "Скопійовано в буфер обміну" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "Переглянути відкриті вкладки" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "" +"⚠️ Ви використовуєте відладочну збірку Ghostty! Продуктивність буде погіршено." + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "Конфігурацію перезавантажено" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Розробники Ghostty" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -247,24 +270,10 @@ msgid "The currently running process in this split will be terminated." msgstr "" "Поточний процес, що виконується в цій розділеній панелі, буде завершено." -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "Головне меню" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Скопійовано в буфер обміну" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "Переглянути відкриті вкладки" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "" -"⚠️ Ви використовуєте відладочну збірку Ghostty! Продуктивність буде погіршено." - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "Конфігурацію перезавантажено" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Розробники Ghostty" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Інспектор терміналу" diff --git a/po/zh_CN.UTF-8.po b/po/zh_CN.UTF-8.po index cdb4c3873..80c3766aa 100644 --- a/po/zh_CN.UTF-8.po +++ b/po/zh_CN.UTF-8.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: com.mitchellh.ghostty\n" "Report-Msgid-Bugs-To: m@mitchellh.com\n" -"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"POT-Creation-Date: 2025-04-22 08:57-0700\n" "PO-Revision-Date: 2025-02-27 09:16+0100\n" "Last-Translator: Leah \n" "Language-Team: Chinese (simplified) \n" @@ -55,6 +55,30 @@ msgstr "忽略" msgid "Reload Configuration" 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 "向上分屏" + +#: 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 "向下分屏" + +#: 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 "向左分屏" + +#: 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 "向右分屏" + #: 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" @@ -86,33 +110,13 @@ msgstr "分屏" msgid "Change Title…" msgstr "更改标题……" -#: 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 "向上分屏" - -#: 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 "向下分屏" - -#: 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 "向左分屏" - -#: 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 "向右分屏" - #: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 msgid "Tab" msgstr "标签页" #: 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:246 +#: src/apprt/gtk/Window.zig:248 msgid "New Tab" msgstr "新建标签页" @@ -149,7 +153,7 @@ msgid "Terminal Inspector" msgstr "终端调试器" #: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 -#: src/apprt/gtk/Window.zig:960 +#: src/apprt/gtk/Window.zig:1003 msgid "About Ghostty" msgstr "关于 Ghostty" @@ -194,13 +198,30 @@ msgid "" "commands may be executed." msgstr "将以下内容粘贴至终端内将可能执行有害命令。" -#: src/apprt/gtk/inspector.zig:144 -msgid "Ghostty: Terminal Inspector" -msgstr "Ghostty 终端调试器" +#: src/apprt/gtk/Window.zig:201 +msgid "Main Menu" +msgstr "主菜单" -#: src/apprt/gtk/Surface.zig:1243 -msgid "Copied to clipboard" -msgstr "已复制至剪贴板" +#: src/apprt/gtk/Window.zig:222 +msgid "View Open Tabs" +msgstr "浏览标签页" + +#: src/apprt/gtk/Window.zig:249 +msgid "New Split" +msgstr "" + +#: src/apprt/gtk/Window.zig:312 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ Ghostty 正在以调试模式运行!性能将大打折扣。" + +#: src/apprt/gtk/Window.zig:744 +msgid "Reloaded the configuration" +msgstr "已重新加载配置" + +#: src/apprt/gtk/Window.zig:984 +msgid "Ghostty Developers" +msgstr "Ghostty 开发团队" #: src/apprt/gtk/CloseDialog.zig:47 msgid "Close" @@ -238,23 +259,10 @@ msgstr "标签页内所有运行中的进程将被终止。" msgid "The currently running process in this split will be terminated." msgstr "分屏内正在运行中的进程将被终止。" -#: src/apprt/gtk/Window.zig:200 -msgid "Main Menu" -msgstr "主菜单" +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "已复制至剪贴板" -#: src/apprt/gtk/Window.zig:221 -msgid "View Open Tabs" -msgstr "浏览标签页" - -#: src/apprt/gtk/Window.zig:295 -msgid "" -"⚠️ You're running a debug build of Ghostty! Performance will be degraded." -msgstr "⚠️ Ghostty 正在以调试模式运行!性能将大打折扣。" - -#: src/apprt/gtk/Window.zig:725 -msgid "Reloaded the configuration" -msgstr "已重新加载配置" - -#: src/apprt/gtk/Window.zig:941 -msgid "Ghostty Developers" -msgstr "Ghostty 开发团队" +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty 终端调试器" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 43d15f813..8f1a7180a 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -70,7 +70,6 @@ parts: plugin: nil build-attributes: [enable-patchelf] build-packages: - - blueprint-compiler - libgtk-4-dev - libadwaita-1-dev # TODO: Add when the Snap is updated to Ubuntu 24.10+ @@ -80,7 +79,7 @@ parts: - patchelf - gettext override-build: | - craftctl set version=$(git describe --abbrev=8) + craftctl set version=$(cat VERSION) $CRAFT_PART_SRC/../../zig/src/zig build -Dpatch-rpath=\$ORIGIN/../usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/core24/current/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR -Doptimize=ReleaseFast cp -rp zig-out/* $CRAFT_PART_INSTALL/ sed -i 's|Icon=com.mitchellh.ghostty|Icon=/snap/ghostty/current/share/icons/hicolor/512x512/apps/com.mitchellh.ghostty.png|g' $CRAFT_PART_INSTALL/share/applications/com.mitchellh.ghostty.desktop diff --git a/src/Surface.zig b/src/Surface.zig index b9eb9e14a..0d4c9d984 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -4119,6 +4119,14 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, .unlocked); }, + .scroll_to_selection => { + self.renderer_state.mutex.lock(); + defer self.renderer_state.mutex.unlock(); + const sel = self.io.terminal.screen.selection orelse return false; + const tl = sel.topLeft(&self.io.terminal.screen); + self.io.terminal.screen.scroll(.{ .pin = tl }); + }, + .scroll_page_up => { const rows: isize = @intCast(self.size.grid().rows); self.io.queueMessage(.{ @@ -4289,12 +4297,24 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool {}, ), + .toggle_window_float_on_top => return try self.rt_app.performAction( + .{ .surface = self }, + .float_window, + .toggle, + ), + .toggle_secure_input => return try self.rt_app.performAction( .{ .surface = self }, .secure_input, .toggle, ), + .toggle_command_palette => return try self.rt_app.performAction( + .{ .surface = self }, + .toggle_command_palette, + {}, + ), + .select_all => { const sel = self.io.terminal.screen.selectAll(); if (sel) |s| { diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 30cbfb1e1..4be296f09 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -107,6 +107,9 @@ pub const Action = union(Key) { /// Toggle the quick terminal in or out. toggle_quick_terminal, + /// Toggle the command palette. This currently only works on macOS. + toggle_command_palette, + /// Toggle the visibility of all Ghostty terminal windows. toggle_visibility, @@ -202,6 +205,10 @@ pub const Action = union(Key) { /// happen and can be ignored or cause a restart it isn't that important. quit_timer: QuitTimer, + /// Set the window floating state. A floating window is one that is + /// always on top of other windows even when not focused. + float_window: FloatWindow, + /// Set the secure input functionality on or off. "Secure input" means /// that the user is currently at some sort of prompt where they may be /// entering a password or other sensitive information. This can be used @@ -244,6 +251,8 @@ pub const Action = union(Key) { /// Closes the currently focused window. close_window, + /// Called when the bell character is seen. The apprt should do whatever + /// it needs to ring the bell. This is usually a sound or visual effect. ring_bell, /// Sync with: ghostty_action_tag_e @@ -259,6 +268,7 @@ pub const Action = union(Key) { toggle_tab_overview, toggle_window_decorations, toggle_quick_terminal, + toggle_command_palette, toggle_visibility, move_tab, goto_tab, @@ -283,6 +293,7 @@ pub const Action = union(Key) { renderer_health, open_config, quit_timer, + float_window, secure_input, key_sequence, color_change, @@ -419,6 +430,12 @@ pub const Fullscreen = enum(c_int) { macos_non_native_padded_notch, }; +pub const FloatWindow = enum(c_int) { + on, + off, + toggle, +}; + pub const SecureInput = enum(c_int) { on, off, diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index e8da8612c..c953300cd 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -43,15 +43,15 @@ pub const App = struct { /// Callback called to wakeup the event loop. This should trigger /// a full tick of the app loop. - wakeup: *const fn (AppUD) callconv(.C) void, + wakeup: *const fn (AppUD) callconv(.c) void, /// Callback called to handle an action. - action: *const fn (*App, apprt.Target.C, apprt.Action.C) callconv(.C) bool, + action: *const fn (*App, apprt.Target.C, apprt.Action.C) callconv(.c) bool, /// Read the clipboard value. The return value must be preserved /// by the host until the next call. If there is no valid clipboard /// value then this should return null. - read_clipboard: *const fn (SurfaceUD, c_int, *apprt.ClipboardRequest) callconv(.C) void, + read_clipboard: *const fn (SurfaceUD, c_int, *apprt.ClipboardRequest) callconv(.c) void, /// This may be called after a read clipboard call to request /// confirmation that the clipboard value is safe to read. The embedder @@ -61,13 +61,13 @@ pub const App = struct { [*:0]const u8, *apprt.ClipboardRequest, apprt.ClipboardRequestType, - ) callconv(.C) void, + ) callconv(.c) void, /// Write the clipboard value. - write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int, bool) callconv(.C) void, + write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int, bool) callconv(.c) void, /// Close the current surface given by this function. - close_surface: ?*const fn (SurfaceUD, bool) callconv(.C) void = null, + close_surface: ?*const fn (SurfaceUD, bool) callconv(.c) void = null, }; /// This is the key event sent for ghostty_surface_key and @@ -1487,6 +1487,23 @@ pub const CAPI = struct { return @intCast(@as(input.Mods.Backing, @bitCast(result))); } + /// Returns the current possible commands for a surface + /// in the output parameter. The memory is owned by libghostty + /// and doesn't need to be freed. + export fn ghostty_surface_commands( + surface: *Surface, + out: *[*]const input.Command.C, + len: *usize, + ) void { + // In the future we may use this information to filter + // some commands. + _ = surface; + + const commands = input.command.defaultsC; + out.* = commands.ptr; + len.* = commands.len; + } + /// Send this for raw keypresses (i.e. the keyDown event on macOS). /// This will handle the keymap translation and send the appropriate /// key and char events. diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index c5ee802c4..9d1c8a6b5 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -228,12 +228,14 @@ pub const App = struct { .toggle_tab_overview, .toggle_window_decorations, .toggle_quick_terminal, + .toggle_command_palette, .toggle_visibility, .goto_tab, .move_tab, .inspector, .render_inspector, .quit_timer, + .float_window, .secure_input, .key_sequence, .desktop_notification, diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index a14383ca3..c9a973611 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -159,6 +159,7 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { opengl: bool = false, /// disable GLES, Ghostty can't use GLES @"gl-disable-gles": bool = false, + // GTK's new renderer can cause blurry font when using fractional scaling. @"gl-no-fractional": bool = false, /// Disabling Vulkan can improve startup times by hundreds of /// milliseconds on some systems. We don't use Vulkan so we can just @@ -190,7 +191,6 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { // For the remainder of "why" see the 4.14 comment below. gdk_disable.@"gles-api" = true; gdk_disable.vulkan = true; - gdk_debug.@"gl-no-fractional" = true; break :environment; } if (gtk_version.runtimeAtLeast(4, 14, 0)) { @@ -201,8 +201,12 @@ pub fn init(core_app: *CoreApp, opts: Options) !App { // // Upstream issue: https://gitlab.gnome.org/GNOME/gtk/-/issues/6589 gdk_debug.@"gl-disable-gles" = true; - gdk_debug.@"gl-no-fractional" = true; gdk_debug.@"vulkan-disable" = true; + + if (gtk_version.runtimeUntil(4, 17, 5)) { + // Removed at GTK v4.17.5 + gdk_debug.@"gl-no-fractional" = true; + } break :environment; } // Versions prior to 4.14 are a bit of an unknown for Ghostty. It @@ -488,6 +492,8 @@ pub fn performAction( // Unimplemented .close_all_windows, + .float_window, + .toggle_command_palette, .toggle_visibility, .cell_size, .key_sequence, @@ -1290,6 +1296,13 @@ pub fn run(self: *App) !void { // Setup our actions self.initActions(); + // On startup, we want to check for configuration errors right away + // so we can show our error window. We also need to setup other initial + // state. + self.syncConfigChanges(null) catch |err| { + log.warn("error handling configuration changes err={}", .{err}); + }; + while (self.running) { _ = glib.MainContext.iteration(self.ctx, 1); @@ -1514,7 +1527,7 @@ fn adwNotifyDark( style_manager: *adw.StyleManager, _: *gobject.ParamSpec, self: *App, -) callconv(.C) void { +) callconv(.c) void { const color_scheme: apprt.ColorScheme = if (style_manager.getDark() == 0) .light else diff --git a/src/apprt/gtk/Builder.zig b/src/apprt/gtk/Builder.zig index 028629200..dbd765ba3 100644 --- a/src/apprt/gtk/Builder.zig +++ b/src/apprt/gtk/Builder.zig @@ -18,88 +18,37 @@ pub fn init( /// The minor version of the minimum Adwaita version that is required to use /// this resource. comptime minor: u16, - /// `blp` signifies that the resource is a Blueprint that has been compiled - /// to GTK Builder XML at compile time. `ui` signifies that the resource is - /// a GTK Builder XML file that is included in the Ghostty source (perhaps - /// because the Blueprint compiler on some target platforms cannot compile a - /// Blueprint that generates the necessary resources). - comptime kind: enum { blp, ui }, ) Builder { const resource_path = comptime resource_path: { const gresource = @import("gresource.zig"); - switch (kind) { - .blp => { - // Check to make sure that our file is listed as a - // `blueprint_file` in `gresource.zig`. If it isn't Ghostty - // could crash at runtime when we try and load a nonexistent - // GResource. - for (gresource.blueprint_files) |file| { - if (major != file.major or minor != file.minor or !std.mem.eql(u8, file.name, name)) continue; - // Use @embedFile to make sure that the `.blp` file exists - // at compile time. Zig _should_ discard the data so that - // it doesn't end up in the final executable. At runtime we - // will load the data from a GResource. - const blp_filename = std.fmt.comptimePrint( - "ui/{d}.{d}/{s}.blp", - .{ - file.major, - file.minor, - file.name, - }, - ); - _ = @embedFile(blp_filename); - break :resource_path std.fmt.comptimePrint( - "/com/mitchellh/ghostty/ui/{d}.{d}/{s}.ui", - .{ - file.major, - file.minor, - file.name, - }, - ); - } else @compileError("missing blueprint file '" ++ name ++ "' in gresource.zig"); - }, - .ui => { - // Check to make sure that our file is listed as a `ui_file` in - // `gresource.zig`. If it isn't Ghostty could crash at runtime - // when we try and load a nonexistent GResource. - for (gresource.ui_files) |file| { - if (major != file.major or minor != file.minor or !std.mem.eql(u8, file.name, name)) continue; - // Use @embedFile to make sure that the `.ui` file exists - // at compile time. Zig _should_ discard the data so that - // it doesn't end up in the final executable. At runtime we - // will load the data from a GResource. - const ui_filename = std.fmt.comptimePrint( - "ui/{d}.{d}/{s}.ui", - .{ - file.major, - file.minor, - file.name, - }, - ); - _ = @embedFile(ui_filename); - // Also use @embedFile to make sure that a matching `.blp` - // file exists at compile time. Zig _should_ discard the - // data so that it doesn't end up in the final executable. - const blp_filename = std.fmt.comptimePrint( - "ui/{d}.{d}/{s}.blp", - .{ - file.major, - file.minor, - file.name, - }, - ); - _ = @embedFile(blp_filename); - break :resource_path std.fmt.comptimePrint( - "/com/mitchellh/ghostty/ui/{d}.{d}/{s}.ui", - .{ - file.major, - file.minor, - file.name, - }, - ); - } else @compileError("missing ui file '" ++ name ++ "' in gresource.zig"); - }, - } + // Check to make sure that our file is listed as a + // `blueprint_file` in `gresource.zig`. If it isn't Ghostty + // could crash at runtime when we try and load a nonexistent + // GResource. + for (gresource.blueprint_files) |file| { + if (major != file.major or minor != file.minor or !std.mem.eql(u8, file.name, name)) continue; + // Use @embedFile to make sure that the `.blp` file exists + // at compile time. Zig _should_ discard the data so that + // it doesn't end up in the final executable. At runtime we + // will load the data from a GResource. + const blp_filename = std.fmt.comptimePrint( + "ui/{d}.{d}/{s}.blp", + .{ + file.major, + file.minor, + file.name, + }, + ); + _ = @embedFile(blp_filename); + break :resource_path std.fmt.comptimePrint( + "/com/mitchellh/ghostty/ui/{d}.{d}/{s}.ui", + .{ + file.major, + file.minor, + file.name, + }, + ); + } else @compileError("missing blueprint file '" ++ name ++ "' in gresource.zig"); }; return .{ diff --git a/src/apprt/gtk/ClipboardConfirmationWindow.zig b/src/apprt/gtk/ClipboardConfirmationWindow.zig index a28b7ddd4..f10fc79ac 100644 --- a/src/apprt/gtk/ClipboardConfirmationWindow.zig +++ b/src/apprt/gtk/ClipboardConfirmationWindow.zig @@ -71,14 +71,14 @@ fn init( ) !void { var builder = switch (DialogType) { adw.AlertDialog => switch (request) { - .osc_52_read => Builder.init("ccw-osc-52-read", 1, 5, .blp), - .osc_52_write => Builder.init("ccw-osc-52-write", 1, 5, .blp), - .paste => Builder.init("ccw-paste", 1, 5, .blp), + .osc_52_read => Builder.init("ccw-osc-52-read", 1, 5), + .osc_52_write => Builder.init("ccw-osc-52-write", 1, 5), + .paste => Builder.init("ccw-paste", 1, 5), }, adw.MessageDialog => switch (request) { - .osc_52_read => Builder.init("ccw-osc-52-read", 1, 2, .ui), - .osc_52_write => Builder.init("ccw-osc-52-write", 1, 2, .ui), - .paste => Builder.init("ccw-paste", 1, 2, .ui), + .osc_52_read => Builder.init("ccw-osc-52-read", 1, 2), + .osc_52_write => Builder.init("ccw-osc-52-write", 1, 2), + .paste => Builder.init("ccw-paste", 1, 2), }, else => unreachable, }; @@ -152,7 +152,7 @@ fn init( } } -fn gtkResponse(_: *DialogType, response: [*:0]u8, self: *ClipboardConfirmation) callconv(.C) void { +fn gtkResponse(_: *DialogType, response: [*:0]u8, self: *ClipboardConfirmation) callconv(.c) void { if (std.mem.orderZ(u8, response, "ok") == .eq) { self.core_surface.completeClipboardRequest( self.pending_req, @@ -165,7 +165,7 @@ fn gtkResponse(_: *DialogType, response: [*:0]u8, self: *ClipboardConfirmation) self.destroy(); } -fn gtkRevealButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv(.C) void { +fn gtkRevealButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv(.c) void { self.text_view_scroll.as(gtk.Widget).setSensitive(@intFromBool(true)); self.text_view.as(gtk.Widget).removeCssClass("blurred"); @@ -173,7 +173,7 @@ fn gtkRevealButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv self.reveal_button.as(gtk.Widget).setVisible(@intFromBool(false)); } -fn gtkHideButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv(.C) void { +fn gtkHideButtonClicked(_: *gtk.Button, self: *ClipboardConfirmation) callconv(.c) void { self.text_view_scroll.as(gtk.Widget).setSensitive(@intFromBool(false)); self.text_view.as(gtk.Widget).addCssClass("blurred"); diff --git a/src/apprt/gtk/CloseDialog.zig b/src/apprt/gtk/CloseDialog.zig index ea683c477..559737cf4 100644 --- a/src/apprt/gtk/CloseDialog.zig +++ b/src/apprt/gtk/CloseDialog.zig @@ -64,7 +64,7 @@ fn responseCallback( _: *DialogType, response: [*:0]const u8, target: *Target, -) callconv(.C) void { +) callconv(.c) void { const alloc = target.allocator(); defer alloc.destroy(target); @@ -141,7 +141,7 @@ pub const Target = union(enum) { } }; -fn findActiveWindow(data: ?*const anyopaque, _: ?*const anyopaque) callconv(.C) c_int { +fn findActiveWindow(data: ?*const anyopaque, _: ?*const anyopaque) callconv(.c) c_int { const window: *gtk.Window = @ptrCast(@alignCast(@constCast(data orelse return -1))); // Confusingly, `isActive` returns 1 when active, diff --git a/src/apprt/gtk/ConfigErrorsDialog.zig b/src/apprt/gtk/ConfigErrorsDialog.zig index c10f8e679..a1a2a61af 100644 --- a/src/apprt/gtk/ConfigErrorsDialog.zig +++ b/src/apprt/gtk/ConfigErrorsDialog.zig @@ -30,8 +30,8 @@ pub fn maybePresent(app: *App, window: ?*Window) void { if (app.config._diagnostics.empty()) return; var builder = switch (DialogType) { - adw.AlertDialog => Builder.init("config-errors-dialog", 1, 5, .blp), - adw.MessageDialog => Builder.init("config-errors-dialog", 1, 2, .ui), + adw.AlertDialog => Builder.init("config-errors-dialog", 1, 5), + adw.MessageDialog => Builder.init("config-errors-dialog", 1, 2), else => unreachable, }; defer builder.deinit(); @@ -67,7 +67,7 @@ pub fn maybePresent(app: *App, window: ?*Window) void { } } -fn onResponse(_: *DialogType, response: [*:0]const u8, app: *App) callconv(.C) void { +fn onResponse(_: *DialogType, response: [*:0]const u8, app: *App) callconv(.c) void { if (std.mem.orderZ(u8, response, "reload") == .eq) { app.reloadConfig(.app, .{}) catch |err| { log.warn("error reloading config error={}", .{err}); diff --git a/src/apprt/gtk/ImguiWidget.zig b/src/apprt/gtk/ImguiWidget.zig index f1f0c8f6b..338fd7982 100644 --- a/src/apprt/gtk/ImguiWidget.zig +++ b/src/apprt/gtk/ImguiWidget.zig @@ -221,12 +221,12 @@ fn translateMouseButton(button: c_uint) ?c_int { }; } -fn gtkDestroy(_: *gtk.GLArea, self: *ImguiWidget) callconv(.C) void { +fn gtkDestroy(_: *gtk.GLArea, self: *ImguiWidget) callconv(.c) void { log.debug("imgui widget destroy", .{}); self.deinit(); } -fn gtkRealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.C) void { +fn gtkRealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.c) void { log.debug("gl surface realized", .{}); // We need to make the context current so we can call GL functions. @@ -242,7 +242,7 @@ fn gtkRealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.C) void { _ = cimgui.ImGui_ImplOpenGL3_Init(null); } -fn gtkUnrealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.C) void { +fn gtkUnrealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.c) void { _ = area; log.debug("gl surface unrealized", .{}); @@ -250,7 +250,7 @@ fn gtkUnrealize(area: *gtk.GLArea, self: *ImguiWidget) callconv(.C) void { cimgui.ImGui_ImplOpenGL3_Shutdown(); } -fn gtkResize(area: *gtk.GLArea, width: c_int, height: c_int, self: *ImguiWidget) callconv(.C) void { +fn gtkResize(area: *gtk.GLArea, width: c_int, height: c_int, self: *ImguiWidget) callconv(.c) void { cimgui.c.igSetCurrentContext(self.ig_ctx); const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO(); const scale_factor = area.as(gtk.Widget).getScaleFactor(); @@ -273,7 +273,7 @@ fn gtkResize(area: *gtk.GLArea, width: c_int, height: c_int, self: *ImguiWidget) active_style.* = style.*; } -fn gtkRender(_: *gtk.GLArea, _: *gdk.GLContext, self: *ImguiWidget) callconv(.C) c_int { +fn gtkRender(_: *gtk.GLArea, _: *gdk.GLContext, self: *ImguiWidget) callconv(.c) c_int { cimgui.c.igSetCurrentContext(self.ig_ctx); // Setup our frame. We render twice because some ImGui behaviors @@ -307,7 +307,7 @@ fn gtkMouseMotion( x: f64, y: f64, self: *ImguiWidget, -) callconv(.C) void { +) callconv(.c) void { cimgui.c.igSetCurrentContext(self.ig_ctx); const io: *cimgui.c.ImGuiIO = cimgui.c.igGetIO(); const scale_factor: f64 = @floatFromInt(self.gl_area.as(gtk.Widget).getScaleFactor()); @@ -325,7 +325,7 @@ fn gtkMouseDown( _: f64, _: f64, self: *ImguiWidget, -) callconv(.C) void { +) callconv(.c) void { self.queueRender(); cimgui.c.igSetCurrentContext(self.ig_ctx); @@ -343,7 +343,7 @@ fn gtkMouseUp( _: f64, _: f64, self: *ImguiWidget, -) callconv(.C) void { +) callconv(.c) void { self.queueRender(); cimgui.c.igSetCurrentContext(self.ig_ctx); @@ -359,7 +359,7 @@ fn gtkMouseScroll( x: f64, y: f64, self: *ImguiWidget, -) callconv(.C) c_int { +) callconv(.c) c_int { self.queueRender(); cimgui.c.igSetCurrentContext(self.ig_ctx); @@ -373,7 +373,7 @@ fn gtkMouseScroll( return @intFromBool(true); } -fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *ImguiWidget) callconv(.C) void { +fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *ImguiWidget) callconv(.c) void { self.queueRender(); cimgui.c.igSetCurrentContext(self.ig_ctx); @@ -381,7 +381,7 @@ fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *ImguiWidget) callconv(.C) cimgui.c.ImGuiIO_AddFocusEvent(io, true); } -fn gtkFocusLeave(_: *gtk.EventControllerFocus, self: *ImguiWidget) callconv(.C) void { +fn gtkFocusLeave(_: *gtk.EventControllerFocus, self: *ImguiWidget) callconv(.c) void { self.queueRender(); cimgui.c.igSetCurrentContext(self.ig_ctx); @@ -393,7 +393,7 @@ fn gtkInputCommit( _: *gtk.IMMulticontext, bytes: [*:0]u8, self: *ImguiWidget, -) callconv(.C) void { +) callconv(.c) void { self.queueRender(); cimgui.c.igSetCurrentContext(self.ig_ctx); @@ -407,7 +407,7 @@ fn gtkKeyPressed( keycode: c_uint, gtk_mods: gdk.ModifierType, self: *ImguiWidget, -) callconv(.C) c_int { +) callconv(.c) c_int { return @intFromBool(self.keyEvent( .press, ec_key, @@ -423,7 +423,7 @@ fn gtkKeyReleased( keycode: c_uint, gtk_mods: gdk.ModifierType, self: *ImguiWidget, -) callconv(.C) void { +) callconv(.c) void { _ = self.keyEvent( .release, ec_key, diff --git a/src/apprt/gtk/ResizeOverlay.zig b/src/apprt/gtk/ResizeOverlay.zig index 47f2aea1a..767cf097d 100644 --- a/src/apprt/gtk/ResizeOverlay.zig +++ b/src/apprt/gtk/ResizeOverlay.zig @@ -104,7 +104,7 @@ pub fn maybeShow(self: *ResizeOverlay) void { /// Actually update the overlay widget. This should only be called from a GTK /// idle handler. -fn gtkUpdate(ud: ?*anyopaque) callconv(.C) c_int { +fn gtkUpdate(ud: ?*anyopaque) callconv(.c) c_int { const self: *ResizeOverlay = @ptrCast(@alignCast(ud orelse return 0)); // No matter what our idler is complete with this callback @@ -198,7 +198,7 @@ fn setPosition(label: *gtk.Label, config: *DerivedConfig) void { /// If this fires, it means that the delay period has expired and the resize /// overlay widget should be hidden. -fn gtkTimerExpired(ud: ?*anyopaque) callconv(.C) c_int { +fn gtkTimerExpired(ud: ?*anyopaque) callconv(.c) c_int { const self: *ResizeOverlay = @ptrCast(@alignCast(ud orelse return 0)); self.timer = null; if (self.label) |label| hide(label); diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index e99fe29ce..7ff96480e 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -1025,7 +1025,7 @@ pub fn setTitle(self: *Surface, slice: [:0]const u8, source: SetTitleSource) !vo self.update_title_timer = glib.timeoutAdd(75, updateTitleTimerExpired, self); } -fn updateTitleTimerExpired(ud: ?*anyopaque) callconv(.C) c_int { +fn updateTitleTimerExpired(ud: ?*anyopaque) callconv(.c) c_int { const self: *Surface = @ptrCast(@alignCast(ud.?)); self.updateTitleLabels(); @@ -1061,7 +1061,7 @@ pub fn promptTitle(self: *Surface) !void { if (!adw_version.atLeast(1, 5, 0)) return; const window = self.container.window() orelse return; - var builder = Builder.init("prompt-title-dialog", 1, 5, .blp); + var builder = Builder.init("prompt-title-dialog", 1, 5); defer builder.deinit(); const entry = builder.getObject(gtk.Entry, "title_entry").?; @@ -1265,7 +1265,7 @@ fn gtkClipboardRead( source: ?*gobject.Object, res: *gio.AsyncResult, ud: ?*anyopaque, -) callconv(.C) void { +) callconv(.c) void { const clipboard = gobject.ext.cast(gdk.Clipboard, source orelse return) orelse return; const req: *ClipboardRequest = @ptrCast(@alignCast(ud orelse return)); const self = req.self; @@ -1349,7 +1349,7 @@ pub fn showDesktopNotification( app.sendNotification(body.ptr, notification); } -fn gtkRealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.C) void { +fn gtkRealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.c) void { log.debug("gl surface realized", .{}); // We need to make the context current so we can call GL functions. @@ -1377,7 +1377,7 @@ fn gtkRealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.C) void { /// This is called when the underlying OpenGL resources must be released. /// This is usually due to the OpenGL area changing GDK surfaces. -fn gtkUnrealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.C) void { +fn gtkUnrealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.c) void { log.debug("gl surface unrealized", .{}); // See gtkRealize for why we do this here. @@ -1405,7 +1405,7 @@ fn gtkUnrealize(gl_area: *gtk.GLArea, self: *Surface) callconv(.C) void { } /// render signal -fn gtkRender(_: *gtk.GLArea, _: *gdk.GLContext, self: *Surface) callconv(.C) c_int { +fn gtkRender(_: *gtk.GLArea, _: *gdk.GLContext, self: *Surface) callconv(.c) c_int { self.render() catch |err| { log.err("surface failed to render: {}", .{err}); return 0; @@ -1415,7 +1415,7 @@ fn gtkRender(_: *gtk.GLArea, _: *gdk.GLContext, self: *Surface) callconv(.C) c_i } /// resize signal -fn gtkResize(gl_area: *gtk.GLArea, width: c_int, height: c_int, self: *Surface) callconv(.C) void { +fn gtkResize(gl_area: *gtk.GLArea, width: c_int, height: c_int, self: *Surface) callconv(.c) void { // Some debug output to help understand what GTK is telling us. { const scale_factor = scale: { @@ -1471,7 +1471,7 @@ fn gtkResize(gl_area: *gtk.GLArea, width: c_int, height: c_int, self: *Surface) } /// "destroy" signal for surface -fn gtkDestroy(_: *gtk.GLArea, self: *Surface) callconv(.C) void { +fn gtkDestroy(_: *gtk.GLArea, self: *Surface) callconv(.c) void { log.debug("gl destroy", .{}); const alloc = self.app.core_app.alloc; @@ -1505,7 +1505,7 @@ fn gtkMouseDown( x: f64, y: f64, self: *Surface, -) callconv(.C) void { +) callconv(.c) void { const event = gesture.as(gtk.EventController).getCurrentEvent() orelse return; const gtk_mods = event.getModifierState(); @@ -1538,7 +1538,7 @@ fn gtkMouseUp( _: f64, _: f64, self: *Surface, -) callconv(.C) void { +) callconv(.c) void { const event = gesture.as(gtk.EventController).getCurrentEvent() orelse return; const gtk_mods = event.getModifierState(); @@ -1557,7 +1557,7 @@ fn gtkMouseMotion( x: f64, y: f64, self: *Surface, -) callconv(.C) void { +) callconv(.c) void { const event = ec.as(gtk.EventController).getCurrentEvent() orelse return; const scaled = self.scaledCoordinates(x, y); @@ -1603,7 +1603,7 @@ fn gtkMouseMotion( fn gtkMouseLeave( ec_motion: *gtk.EventControllerMotion, self: *Surface, -) callconv(.C) void { +) callconv(.c) void { const event = ec_motion.as(gtk.EventController).getCurrentEvent() orelse return; // Get our modifiers @@ -1618,14 +1618,14 @@ fn gtkMouseLeave( fn gtkMouseScrollPrecisionBegin( _: *gtk.EventControllerScroll, self: *Surface, -) callconv(.C) void { +) callconv(.c) void { self.precision_scroll = true; } fn gtkMouseScrollPrecisionEnd( _: *gtk.EventControllerScroll, self: *Surface, -) callconv(.C) void { +) callconv(.c) void { self.precision_scroll = false; } @@ -1634,7 +1634,7 @@ fn gtkMouseScroll( x: f64, y: f64, self: *Surface, -) callconv(.C) c_int { +) callconv(.c) c_int { const scaled = self.scaledCoordinates(x, y); // GTK doesn't support any of the scroll mods. @@ -1664,7 +1664,7 @@ fn gtkKeyPressed( keycode: c_uint, gtk_mods: gdk.ModifierType, self: *Surface, -) callconv(.C) c_int { +) callconv(.c) c_int { return @intFromBool(self.keyEvent( .press, ec_key, @@ -1680,7 +1680,7 @@ fn gtkKeyReleased( keycode: c_uint, state: gdk.ModifierType, self: *Surface, -) callconv(.C) void { +) callconv(.c) void { _ = self.keyEvent( .release, ec_key, @@ -1971,7 +1971,7 @@ pub fn keyEvent( fn gtkInputPreeditStart( _: *gtk.IMMulticontext, self: *Surface, -) callconv(.C) void { +) callconv(.c) void { // log.warn("GTKIM: preedit start", .{}); // Start our composing state for the input method and reset our @@ -1983,7 +1983,7 @@ fn gtkInputPreeditStart( fn gtkInputPreeditChanged( ctx: *gtk.IMMulticontext, self: *Surface, -) callconv(.C) void { +) callconv(.c) void { // Any preedit change should mark that we're composing. Its possible this // is false using fcitx5-hangul and typing "dkssud" ("안녕"). The // second "s" results in a "commit" for "안" which sets composing to false, @@ -2009,7 +2009,7 @@ fn gtkInputPreeditChanged( fn gtkInputPreeditEnd( _: *gtk.IMMulticontext, self: *Surface, -) callconv(.C) void { +) callconv(.c) void { // log.warn("GTKIM: preedit end", .{}); // End our composing state for GTK, allowing us to commit the text. @@ -2025,7 +2025,7 @@ fn gtkInputCommit( _: *gtk.IMMulticontext, bytes: [*:0]u8, self: *Surface, -) callconv(.C) void { +) callconv(.c) void { const str = std.mem.sliceTo(bytes, 0); // log.debug("GTKIM: input commit composing={} keyevent={} str={s}", .{ @@ -2100,7 +2100,7 @@ fn gtkInputCommit( }; } -fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *Surface) callconv(.C) void { +fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *Surface) callconv(.c) void { if (!self.realized) return; // Notify our IM context @@ -2125,7 +2125,7 @@ fn gtkFocusEnter(_: *gtk.EventControllerFocus, self: *Surface) callconv(.C) void }; } -fn gtkFocusLeave(_: *gtk.EventControllerFocus, self: *Surface) callconv(.C) void { +fn gtkFocusLeave(_: *gtk.EventControllerFocus, self: *Surface) callconv(.c) void { if (!self.realized) return; // Notify our IM context @@ -2243,7 +2243,7 @@ fn gtkDrop( _: f64, _: f64, self: *Surface, -) callconv(.C) c_int { +) callconv(.c) c_int { const alloc = self.app.core_app.alloc; if (g_value_holds(value, gdk.FileList.getGObjectType())) { @@ -2395,7 +2395,7 @@ fn g_value_holds(value_: ?*gobject.Value, g_type: gobject.Type) bool { return false; } -fn gtkPromptTitleResponse(source_object: ?*gobject.Object, result: *gio.AsyncResult, ud: ?*anyopaque) callconv(.C) void { +fn gtkPromptTitleResponse(source_object: ?*gobject.Object, result: *gio.AsyncResult, ud: ?*anyopaque) callconv(.c) void { if (!adw_version.supportsDialogs()) return; const dialog = gobject.ext.cast(adw.AlertDialog, source_object.?).?; const self: *Surface = @ptrCast(@alignCast(ud)); diff --git a/src/apprt/gtk/Tab.zig b/src/apprt/gtk/Tab.zig index 57a9644d9..c32fa19fc 100644 --- a/src/apprt/gtk/Tab.zig +++ b/src/apprt/gtk/Tab.zig @@ -161,7 +161,7 @@ pub fn closeWithConfirmation(tab: *Tab) void { } } -fn gtkDestroy(_: *gtk.Box, self: *Tab) callconv(.C) void { +fn gtkDestroy(_: *gtk.Box, self: *Tab) callconv(.c) void { log.debug("tab box destroy", .{}); const alloc = self.window.app.core_app.alloc; diff --git a/src/apprt/gtk/TabView.zig b/src/apprt/gtk/TabView.zig index ddd0951d2..29a069a6d 100644 --- a/src/apprt/gtk/TabView.zig +++ b/src/apprt/gtk/TabView.zig @@ -227,7 +227,7 @@ pub fn createWindow(window: *Window) !*Window { return new_window; } -fn adwPageAttached(_: *adw.TabView, page: *adw.TabPage, _: c_int, self: *TabView) callconv(.C) void { +fn adwPageAttached(_: *adw.TabView, page: *adw.TabPage, _: c_int, self: *TabView) callconv(.c) void { const child = page.getChild().as(gobject.Object); const tab: *Tab = @ptrCast(@alignCast(child.getData(Tab.GHOSTTY_TAB) orelse return)); tab.window = self.window; @@ -239,7 +239,7 @@ fn adwClosePage( _: *adw.TabView, page: *adw.TabPage, self: *TabView, -) callconv(.C) c_int { +) callconv(.c) c_int { const child = page.getChild().as(gobject.Object); const tab: *Tab = @ptrCast(@alignCast(child.getData(Tab.GHOSTTY_TAB) orelse return 0)); self.tab_view.closePageFinish(page, @intFromBool(self.forcing_close)); @@ -251,7 +251,7 @@ fn adwClosePage( fn adwTabViewCreateWindow( _: *adw.TabView, self: *TabView, -) callconv(.C) ?*adw.TabView { +) callconv(.c) ?*adw.TabView { const window = createWindow(self.window) catch |err| { log.warn("error creating new window error={}", .{err}); return null; @@ -259,7 +259,7 @@ fn adwTabViewCreateWindow( return window.notebook.tab_view; } -fn adwSelectPage(_: *adw.TabView, _: *gobject.ParamSpec, self: *TabView) callconv(.C) void { +fn adwSelectPage(_: *adw.TabView, _: *gobject.ParamSpec, self: *TabView) callconv(.c) void { const page = self.tab_view.getSelectedPage() orelse return; // If the tab was previously marked as needing attention diff --git a/src/apprt/gtk/URLWidget.zig b/src/apprt/gtk/URLWidget.zig index d1628aa6e..e59827aaf 100644 --- a/src/apprt/gtk/URLWidget.zig +++ b/src/apprt/gtk/URLWidget.zig @@ -101,7 +101,7 @@ fn gtkLeftEnter( _: f64, _: f64, right: *gtk.Label, -) callconv(.C) void { +) callconv(.c) void { right.as(gtk.Widget).removeCssClass("hidden"); } @@ -110,6 +110,6 @@ fn gtkLeftEnter( fn gtkLeftLeave( _: *gtk.EventControllerMotion, right: *gtk.Label, -) callconv(.C) void { +) callconv(.c) void { right.as(gtk.Widget).addCssClass("hidden"); } diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 129c149e7..d82087ff0 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -25,6 +25,7 @@ const input = @import("../../input.zig"); const CoreSurface = @import("../../Surface.zig"); const App = @import("App.zig"); +const Builder = @import("Builder.zig"); const Color = configpkg.Config.Color; const Surface = @import("Surface.zig"); const Menu = @import("menu.zig").Menu; @@ -242,12 +243,19 @@ pub fn init(self: *Window, app: *App) !void { } { - const btn = gtk.Button.newFromIconName("tab-new-symbolic"); + const btn = adw.SplitButton.new(); + btn.setIconName("tab-new-symbolic"); btn.as(gtk.Widget).setTooltipText(i18n._("New Tab")); - _ = gtk.Button.signals.clicked.connect( + btn.setDropdownTooltip(i18n._("New Split")); + + var builder = Builder.init("menu-headerbar-split_menu", 1, 0); + defer builder.deinit(); + btn.setMenuModel(builder.getObject(gio.MenuModel, "menu")); + + _ = adw.SplitButton.signals.clicked.connect( btn, *Window, - gtkTabNewClick, + adwNewTabClick, self, .{}, ); @@ -784,7 +792,7 @@ fn gtkWindowNotifyIsActive( _: *adw.ApplicationWindow, _: *gobject.ParamSpec, self: *Window, -) callconv(.C) void { +) callconv(.c) void { if (!self.isQuickTerminal()) return; // Hide when we're unfocused @@ -824,6 +832,11 @@ fn gtkTabNewClick(_: *gtk.Button, self: *Window) callconv(.c) void { self.performBindingAction(.{ .new_tab = {} }); } +/// Create a new surface (tab or split). +fn adwNewTabClick(_: *adw.SplitButton, self: *Window) callconv(.c) void { + self.performBindingAction(.{ .new_tab = {} }); +} + /// Create a new tab from the AdwTabOverview. We can't copy gtkTabNewClick /// because we need to return an AdwTabPage from this function. fn gtkNewTabFromOverview(_: *adw.TabOverview, self: *Window) callconv(.c) *adw.TabPage { @@ -870,7 +883,7 @@ fn adwTabOverviewOpen( fn adwTabOverviewFocusTimer( ud: ?*anyopaque, -) callconv(.C) c_int { +) callconv(.c) c_int { if (!adw_version.supportsTabOverview()) unreachable; const self: *Window = @ptrCast(@alignCast(ud orelse return 0)); self.adw_tab_overview_focus_timer = null; @@ -957,7 +970,7 @@ fn gtkActionAbout( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { const name = "Ghostty"; const icon = "com.mitchellh.ghostty"; const website = "https://ghostty.org"; @@ -1001,7 +1014,7 @@ fn gtkActionClose( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.closeWithConfirmation(); } @@ -1009,7 +1022,7 @@ fn gtkActionNewWindow( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .new_window = {} }); } @@ -1017,7 +1030,7 @@ fn gtkActionNewTab( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .new_tab = {} }); } @@ -1025,7 +1038,7 @@ fn gtkActionCloseTab( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .close_tab = {} }); } @@ -1033,7 +1046,7 @@ fn gtkActionSplitRight( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .new_split = .right }); } @@ -1041,7 +1054,7 @@ fn gtkActionSplitDown( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .new_split = .down }); } @@ -1049,7 +1062,7 @@ fn gtkActionSplitLeft( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .new_split = .left }); } @@ -1057,15 +1070,15 @@ fn gtkActionSplitUp( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { - self.performBindingAction(.{ .new_split = .right }); +) callconv(.c) void { + self.performBindingAction(.{ .new_split = .up }); } fn gtkActionToggleInspector( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .inspector = .toggle }); } @@ -1073,7 +1086,7 @@ fn gtkActionCopy( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .copy_to_clipboard = {} }); } @@ -1081,7 +1094,7 @@ fn gtkActionPaste( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .paste_from_clipboard = {} }); } @@ -1089,7 +1102,7 @@ fn gtkActionReset( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .reset = {} }); } @@ -1097,7 +1110,7 @@ fn gtkActionClear( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .clear_screen = {} }); } @@ -1105,7 +1118,7 @@ fn gtkActionPromptTitle( _: *gio.SimpleAction, _: ?*glib.Variant, self: *Window, -) callconv(.C) void { +) callconv(.c) void { self.performBindingAction(.{ .prompt_surface_title = {} }); } @@ -1120,7 +1133,7 @@ fn gtkTitlebarMenuActivate( btn: *gtk.MenuButton, _: *gobject.ParamSpec, self: *Window, -) callconv(.C) void { +) callconv(.c) void { // debian 12 is stuck on GTK 4.8 if (!gtk_version.atLeast(4, 10, 0)) return; const active = btn.getActive() != 0; diff --git a/src/apprt/gtk/blueprint_compiler.zig b/src/apprt/gtk/blueprint_compiler.zig index 7a0442e92..9bc515655 100644 --- a/src/apprt/gtk/blueprint_compiler.zig +++ b/src/apprt/gtk/blueprint_compiler.zig @@ -4,62 +4,157 @@ pub const c = @cImport({ @cInclude("adwaita.h"); }); +const adwaita_version = std.SemanticVersion{ + .major = c.ADW_MAJOR_VERSION, + .minor = c.ADW_MINOR_VERSION, + .patch = c.ADW_MICRO_VERSION, +}; +const required_blueprint_version = std.SemanticVersion{ + .major = 0, + .minor = 16, + .patch = 0, +}; + pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const alloc = gpa.allocator(); + var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + defer _ = debug_allocator.deinit(); + const alloc = debug_allocator.allocator(); var it = try std.process.argsWithAllocator(alloc); defer it.deinit(); _ = it.next(); - const major = try std.fmt.parseUnsigned(u8, it.next() orelse return error.NoMajorVersion, 10); - const minor = try std.fmt.parseUnsigned(u8, it.next() orelse return error.NoMinorVersion, 10); + const required_adwaita_version = std.SemanticVersion{ + .major = try std.fmt.parseUnsigned(u8, it.next() orelse return error.NoMajorVersion, 10), + .minor = try std.fmt.parseUnsigned(u8, it.next() orelse return error.NoMinorVersion, 10), + .patch = 0, + }; const output = it.next() orelse return error.NoOutput; const input = it.next() orelse return error.NoInput; - if (c.ADW_MAJOR_VERSION < major or (c.ADW_MAJOR_VERSION == major and c.ADW_MINOR_VERSION < minor)) { - // If the Adwaita version is too old, generate an "empty" file. - const file = try std.fs.createFileAbsolute(output, .{ - .truncate = true, - }); - try file.writeAll( - \\ - \\ - ); - defer file.close(); - - return; + if (adwaita_version.order(required_adwaita_version) == .lt) { + std.debug.print( + \\`libadwaita` is too old. + \\ + \\Ghostty requires a version {} or newer of `libadwaita` to + \\compile this blueprint. Please install it, ensure that it is + \\available on your PATH, and then retry building Ghostty. + , .{required_adwaita_version}); + std.posix.exit(1); } - var compiler = std.process.Child.init( - &.{ - "blueprint-compiler", - "compile", - "--output", - output, - input, - }, - alloc, - ); + { + var stdout: std.ArrayListUnmanaged(u8) = .empty; + defer stdout.deinit(alloc); + var stderr: std.ArrayListUnmanaged(u8) = .empty; + defer stderr.deinit(alloc); - const term = compiler.spawnAndWait() catch |err| switch (err) { - error.FileNotFound => { - std.log.err( - \\`blueprint-compiler` not found. + var blueprint_compiler = std.process.Child.init( + &.{ + "blueprint-compiler", + "--version", + }, + alloc, + ); + blueprint_compiler.stdout_behavior = .Pipe; + blueprint_compiler.stderr_behavior = .Pipe; + try blueprint_compiler.spawn(); + try blueprint_compiler.collectOutput( + alloc, + &stdout, + &stderr, + std.math.maxInt(u16), + ); + const term = blueprint_compiler.wait() catch |err| switch (err) { + error.FileNotFound => { + std.debug.print( + \\`blueprint-compiler` not found. + \\ + \\Ghostty requires version {} or newer of + \\`blueprint-compiler` as a build-time dependency starting + \\from version 1.2. Please install it, ensure that it is + \\available on your PATH, and then retry building Ghostty. + \\ + , .{required_blueprint_version}); + std.posix.exit(1); + }, + else => return err, + }; + switch (term) { + .Exited => |rc| { + if (rc != 0) std.process.exit(1); + }, + else => std.process.exit(1), + } + + const version = try std.SemanticVersion.parse(std.mem.trim(u8, stdout.items, &std.ascii.whitespace)); + if (version.order(required_blueprint_version) == .lt) { + std.debug.print( + \\`blueprint-compiler` is the wrong version. \\ - \\Ghostty requires `blueprint-compiler` as a build-time dependency starting from version 1.2. - \\Please install it, ensure that it is available on your PATH, and then retry building Ghostty. - , .{}); + \\Ghostty requires version {} or newer of + \\`blueprint-compiler` as a build-time dependency starting + \\from version 1.2. Please install it, ensure that it is + \\available on your PATH, and then retry building Ghostty. + \\ + , .{required_blueprint_version}); std.posix.exit(1); - }, - else => return err, - }; + } + } - switch (term) { - .Exited => |rc| { - if (rc != 0) std.process.exit(1); - }, - else => std.process.exit(1), + { + var stdout: std.ArrayListUnmanaged(u8) = .empty; + defer stdout.deinit(alloc); + var stderr: std.ArrayListUnmanaged(u8) = .empty; + defer stderr.deinit(alloc); + + var blueprint_compiler = std.process.Child.init( + &.{ + "blueprint-compiler", + "compile", + "--output", + output, + input, + }, + alloc, + ); + blueprint_compiler.stdout_behavior = .Pipe; + blueprint_compiler.stderr_behavior = .Pipe; + try blueprint_compiler.spawn(); + try blueprint_compiler.collectOutput( + alloc, + &stdout, + &stderr, + std.math.maxInt(u16), + ); + const term = blueprint_compiler.wait() catch |err| switch (err) { + error.FileNotFound => { + std.debug.print( + \\`blueprint-compiler` not found. + \\ + \\Ghostty requires version {} or newer of + \\`blueprint-compiler` as a build-time dependency starting + \\from version 1.2. Please install it, ensure that it is + \\available on your PATH, and then retry building Ghostty. + \\ + , .{required_blueprint_version}); + std.posix.exit(1); + }, + else => return err, + }; + + switch (term) { + .Exited => |rc| { + if (rc != 0) { + std.debug.print("{s}", .{stderr.items}); + std.process.exit(1); + } + }, + else => { + std.debug.print("{s}", .{stderr.items}); + std.process.exit(1); + }, + } } } diff --git a/src/apprt/gtk/builder_check.zig b/src/apprt/gtk/builder_check.zig deleted file mode 100644 index 015c6310d..000000000 --- a/src/apprt/gtk/builder_check.zig +++ /dev/null @@ -1,32 +0,0 @@ -const std = @import("std"); -const build_options = @import("build_options"); - -const gtk = @import("gtk"); -const adw = @import("adw"); - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const alloc = gpa.allocator(); - - const filename = filename: { - var it = try std.process.argsWithAllocator(alloc); - defer it.deinit(); - - _ = it.next() orelse return error.NoFilename; - break :filename try alloc.dupeZ(u8, it.next() orelse return error.NoFilename); - }; - defer alloc.free(filename); - - const data = try std.fs.cwd().readFileAllocOptions(alloc, filename, std.math.maxInt(u16), null, 1, 0); - defer alloc.free(data); - - if (gtk.initCheck() == 0) { - std.debug.print("{s}: skipping builder check because we can't connect to display!\n", .{filename}); - return; - } - - adw.init(); - - const builder = gtk.Builder.newFromString(data.ptr, @intCast(data.len)); - defer builder.unref(); -} diff --git a/src/apprt/gtk/gresource.zig b/src/apprt/gtk/gresource.zig index 64067c199..a1db8ac62 100644 --- a/src/apprt/gtk/gresource.zig +++ b/src/apprt/gtk/gresource.zig @@ -53,19 +53,6 @@ const icons = [_]struct { }, }; -pub const VersionedBuilderXML = struct { - major: u16, - minor: u16, - name: []const u8, -}; - -pub const ui_files = [_]VersionedBuilderXML{ - .{ .major = 1, .minor = 2, .name = "config-errors-dialog" }, - .{ .major = 1, .minor = 2, .name = "ccw-osc-52-read" }, - .{ .major = 1, .minor = 2, .name = "ccw-osc-52-write" }, - .{ .major = 1, .minor = 2, .name = "ccw-paste" }, -}; - pub const VersionedBlueprint = struct { major: u16, minor: u16, @@ -75,21 +62,27 @@ pub const VersionedBlueprint = struct { pub const blueprint_files = [_]VersionedBlueprint{ .{ .major = 1, .minor = 5, .name = "prompt-title-dialog" }, .{ .major = 1, .minor = 5, .name = "config-errors-dialog" }, + .{ .major = 1, .minor = 0, .name = "menu-headerbar-split_menu" }, .{ .major = 1, .minor = 0, .name = "menu-surface-context_menu" }, .{ .major = 1, .minor = 0, .name = "menu-window-titlebar_menu" }, .{ .major = 1, .minor = 5, .name = "ccw-osc-52-read" }, .{ .major = 1, .minor = 5, .name = "ccw-osc-52-write" }, .{ .major = 1, .minor = 5, .name = "ccw-paste" }, + .{ .major = 1, .minor = 2, .name = "config-errors-dialog" }, + .{ .major = 1, .minor = 2, .name = "ccw-osc-52-read" }, + .{ .major = 1, .minor = 2, .name = "ccw-osc-52-write" }, + .{ .major = 1, .minor = 2, .name = "ccw-paste" }, }; pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const alloc = gpa.allocator(); + var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + defer _ = debug_allocator.deinit(); + const alloc = debug_allocator.allocator(); - var extra_ui_files = std.ArrayList([]const u8).init(alloc); + var extra_ui_files: std.ArrayListUnmanaged([]const u8) = .empty; defer { for (extra_ui_files.items) |item| alloc.free(item); - extra_ui_files.deinit(); + extra_ui_files.deinit(alloc); } var it = try std.process.argsWithAllocator(alloc); @@ -97,7 +90,7 @@ pub fn main() !void { while (it.next()) |argument| { if (std.mem.eql(u8, std.fs.path.extension(argument), ".ui")) { - try extra_ui_files.append(try alloc.dupe(u8, argument)); + try extra_ui_files.append(alloc, try alloc.dupe(u8, argument)); } } @@ -131,16 +124,11 @@ pub fn main() !void { \\ \\ ); - for (ui_files) |ui_file| { - try writer.print( - " src/apprt/gtk/ui/{0d}.{1d}/{2s}.ui\n", - .{ ui_file.major, ui_file.minor, ui_file.name }, - ); - } for (extra_ui_files.items) |ui_file| { - const stem = std.fs.path.stem(ui_file); for (blueprint_files) |file| { - if (!std.mem.eql(u8, file.name, stem)) continue; + const expected = try std.fmt.allocPrint(alloc, "/{d}.{d}/{s}.ui", .{ file.major, file.minor, file.name }); + defer alloc.free(expected); + if (!std.mem.endsWith(u8, ui_file, expected)) continue; try writer.print( " {s}\n", .{ file.major, file.minor, file.name, ui_file }, @@ -156,7 +144,7 @@ pub fn main() !void { } pub const dependencies = deps: { - const total = css_files.len + icons.len + ui_files.len + blueprint_files.len; + const total = css_files.len + icons.len + blueprint_files.len; var deps: [total][]const u8 = undefined; var index: usize = 0; for (css_files) |css_file| { @@ -167,14 +155,6 @@ pub const dependencies = deps: { deps[index] = std.fmt.comptimePrint("images/icons/icon_{s}.png", .{icon.source}); index += 1; } - for (ui_files) |ui_file| { - deps[index] = std.fmt.comptimePrint("src/apprt/gtk/ui/{d}.{d}/{s}.ui", .{ - ui_file.major, - ui_file.minor, - ui_file.name, - }); - index += 1; - } for (blueprint_files) |blueprint_file| { deps[index] = std.fmt.comptimePrint("src/apprt/gtk/ui/{d}.{d}/{s}.blp", .{ blueprint_file.major, diff --git a/src/apprt/gtk/gtk_version.zig b/src/apprt/gtk/gtk_version.zig index 59d7a5782..5d75fb4fe 100644 --- a/src/apprt/gtk/gtk_version.zig +++ b/src/apprt/gtk/gtk_version.zig @@ -87,10 +87,23 @@ pub inline fn runtimeAtLeast( }) != .lt; } +pub inline fn runtimeUntil( + comptime major: u16, + comptime minor: u16, + comptime micro: u16, +) bool { + const runtime_version = getRuntimeVersion(); + return runtime_version.order(.{ + .major = major, + .minor = minor, + .patch = micro, + }) == .lt; +} + test "atLeast" { const testing = std.testing; - const funs = &.{ atLeast, runtimeAtLeast }; + const funs = &.{ atLeast, runtimeAtLeast, runtimeUntil }; inline for (funs) |fun| { try testing.expect(fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION)); diff --git a/src/apprt/gtk/inspector.zig b/src/apprt/gtk/inspector.zig index aa4f6e435..e3e61e258 100644 --- a/src/apprt/gtk/inspector.zig +++ b/src/apprt/gtk/inspector.zig @@ -177,7 +177,7 @@ const Window = struct { } /// "destroy" signal for the window - fn gtkDestroy(_: *gtk.ApplicationWindow, self: *Window) callconv(.C) void { + fn gtkDestroy(_: *gtk.ApplicationWindow, self: *Window) callconv(.c) void { log.debug("window destroy", .{}); self.deinit(); } diff --git a/src/apprt/gtk/menu.zig b/src/apprt/gtk/menu.zig index d0a93b80d..d9d0083d0 100644 --- a/src/apprt/gtk/menu.zig +++ b/src/apprt/gtk/menu.zig @@ -41,7 +41,7 @@ pub fn Menu( else => unreachable, }; - var builder = Builder.init("menu-" ++ object_type ++ "-" ++ menu_name, 1, 0, .blp); + var builder = Builder.init("menu-" ++ object_type ++ "-" ++ menu_name, 1, 0); defer builder.deinit(); const menu_model = builder.getObject(gio.MenuModel, "menu").?; @@ -130,7 +130,7 @@ pub fn Menu( } /// Refocus tab that lost focus because of the popover menu - fn gtkRefocusTerm(_: *gtk.PopoverMenu, self: *Self) callconv(.C) void { + fn gtkRefocusTerm(_: *gtk.PopoverMenu, self: *Self) callconv(.c) void { const window: *Window = switch (T) { Window => self.parent, Surface => self.parent.container.window() orelse return, diff --git a/src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp b/src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp new file mode 100644 index 000000000..90de02845 --- /dev/null +++ b/src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp @@ -0,0 +1,25 @@ +using Gtk 4.0; + +menu menu { + section { + item { + label: _("Split Up"); + action: "win.split-up"; + } + + item { + label: _("Split Down"); + action: "win.split-down"; + } + + item { + label: _("Split Left"); + action: "win.split-left"; + } + + item { + label: _("Split Right"); + action: "win.split-right"; + } + } +} diff --git a/src/apprt/gtk/ui/1.2/ccw-osc-52-read.ui b/src/apprt/gtk/ui/1.2/ccw-osc-52-read.ui deleted file mode 100644 index 82512e3a2..000000000 --- a/src/apprt/gtk/ui/1.2/ccw-osc-52-read.ui +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - Authorize Clipboard Access - An application is attempting to read from the clipboard. The current clipboard contents are shown below. - - Deny - Allow - - cancel - cancel - - - - - - 500 - 250 - - - false - false - true - 8 - 8 - 8 - 8 - - - - - - - - false - 2 - 1 - 12 - 12 - - - view-reveal-symbolic - - - - - - - false - 2 - 1 - 12 - 12 - - - - view-conceal-symbolic - - - - - - - - \ No newline at end of file diff --git a/src/apprt/gtk/ui/1.2/ccw-osc-52-write.ui b/src/apprt/gtk/ui/1.2/ccw-osc-52-write.ui deleted file mode 100644 index 195fb1de1..000000000 --- a/src/apprt/gtk/ui/1.2/ccw-osc-52-write.ui +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - Authorize Clipboard Access - An application is attempting to write to the clipboard. The current clipboard contents are shown below. - - Deny - Allow - - cancel - cancel - - - - - - 500 - 250 - - - false - false - true - 8 - 8 - 8 - 8 - - - - - - - - false - 2 - 1 - 12 - 12 - - - view-reveal-symbolic - - - - - - - false - 2 - 1 - 12 - 12 - - - - view-conceal-symbolic - - - - - - - - \ No newline at end of file diff --git a/src/apprt/gtk/ui/1.2/ccw-paste.ui b/src/apprt/gtk/ui/1.2/ccw-paste.ui deleted file mode 100644 index 342c767e6..000000000 --- a/src/apprt/gtk/ui/1.2/ccw-paste.ui +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - Warning: Potentially Unsafe Paste - Pasting this text into the terminal may be dangerous as it looks like some commands may be executed. - - Cancel - Paste - - cancel - cancel - - - - - - 500 - 250 - - - false - false - true - 8 - 8 - 8 - 8 - - - - - - - - false - 2 - 1 - 12 - 12 - - - view-reveal-symbolic - - - - - - - false - 2 - 1 - 12 - 12 - - - - view-conceal-symbolic - - - - - - - - \ No newline at end of file diff --git a/src/apprt/gtk/ui/1.2/config-errors-dialog.ui b/src/apprt/gtk/ui/1.2/config-errors-dialog.ui deleted file mode 100644 index 1d7517f7a..000000000 --- a/src/apprt/gtk/ui/1.2/config-errors-dialog.ui +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - Configuration Errors - One or more configuration errors were found. Please review the errors below, and either reload your configuration or ignore these errors. - - Ignore - Reload Configuration - - - - 500 - 100 - - - false - false - 8 - 8 - 8 - 8 - - - - - - - - - diff --git a/src/apprt/gtk/ui/README.md b/src/apprt/gtk/ui/README.md index 08f3f367c..b9dc732b6 100644 --- a/src/apprt/gtk/ui/README.md +++ b/src/apprt/gtk/ui/README.md @@ -1,21 +1,15 @@ # GTK UI files -This directory is for storing GTK resource definitions. With one exception, the -files should be be in the Blueprint markup language. +This directory is for storing GTK blueprints. GTK blueprints are compiled into +GTK resource builder `.ui` files by `blueprint-compiler` at build time and then +converted into an embeddable resource by `glib-compile-resources`. -Resource files should be stored in directories that represent the minimum -Adwaita version needed to use that resource. Resource files should also be -formatted using `blueprint-compiler format` as well to ensure consistency. +Blueprint files should be stored in directories that represent the minimum +Adwaita version needed to use that resource. Blueprint files should also be +formatted using `blueprint-compiler format` as well to ensure consistency +(formatting will be checked in CI). -The one exception to files being in Blueprint markup language is when Adwaita -features are used that the `blueprint-compiler` on a supported platform does not -compile. For example, Debian 12 includes Adwaita 1.2 and `blueprint-compiler` -0.6.0. Adwaita 1.2 includes support for `MessageDialog` but `blueprint-compiler` -0.6.0 does not. In cases like that the Blueprint markup should be compiled on a -platform that provides a new enough `blueprint-compiler` and the resulting `.ui` -file should be committed to the Ghostty source code. Care should be taken that -the `.blp` file and the `.ui` file remain in sync. - -In all other cases only the `.blp` should be committed to the Ghostty source -code. The build process will use `blueprint-compiler` to generate the `.ui` -files necessary at runtime. +`blueprint-compiler` version 0.16.0 or newer is required to compile Blueprint +files. If your system does not have `blueprint-compiler` or does not have a +new enough version you can use the generated source tarballs, which contain +precompiled versions of the blueprints. diff --git a/src/apprt/gtk/winproto/wayland.zig b/src/apprt/gtk/winproto/wayland.zig index 6737e98e2..5f5feca6e 100644 --- a/src/apprt/gtk/winproto/wayland.zig +++ b/src/apprt/gtk/winproto/wayland.zig @@ -410,7 +410,7 @@ pub const Window = struct { _: *gdk.Surface, monitor: *gdk.Monitor, apprt_window: *ApprtWindow, - ) callconv(.C) void { + ) callconv(.c) void { const window = apprt_window.window.as(gtk.Window); const size = apprt_window.config.quick_terminal_size; const position = apprt_window.config.quick_terminal_position; diff --git a/src/build/GhosttyDist.zig b/src/build/GhosttyDist.zig index 5af8b7480..3d7ba3b8d 100644 --- a/src/build/GhosttyDist.zig +++ b/src/build/GhosttyDist.zig @@ -36,6 +36,17 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyDist { "--format=tgz", }); + // embed the Ghostty version in the tarball + { + const version = b.addWriteFiles().add("VERSION", b.fmt("{}", .{cfg.version})); + // --add-file uses the most recent --prefix to determine the path + // in the archive to copy the file (the directory only). + git_archive.addArg(b.fmt("--prefix=ghostty-{}/", .{ + cfg.version, + })); + git_archive.addPrefixedFileArg("--add-file=", version); + } + // Add all of our resources into the tarball. for (resources.items) |resource| { // Our dist path basename may not match our generated file basename, diff --git a/src/build/GhosttyResources.zig b/src/build/GhosttyResources.zig index 9d463bf7d..3d6b99a34 100644 --- a/src/build/GhosttyResources.zig +++ b/src/build/GhosttyResources.zig @@ -202,6 +202,12 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyResources { "share/applications/com.mitchellh.ghostty.desktop", ).step); + // AppStream metainfo so that application has rich metadata within app stores + try steps.append(&b.addInstallFile( + b.path("dist/linux/com.mitchellh.ghostty.metainfo.xml"), + "share/metainfo/com.mitchellh.ghostty.metainfo.xml", + ).step); + // Right click menu action for Plasma desktop try steps.append(&b.addInstallFile( b.path("dist/linux/ghostty_dolphin.desktop"), diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index 4f9373adb..4b97298f7 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -662,34 +662,6 @@ fn addGTK( } { - // For our actual build, we validate our GTK builder files if we can. - { - const gtk_builder_check = b.addExecutable(.{ - .name = "gtk_builder_check", - .root_source_file = b.path("src/apprt/gtk/builder_check.zig"), - .target = b.graph.host, - }); - gtk_builder_check.root_module.addOptions("build_options", self.options); - if (gobject_) |gobject| { - gtk_builder_check.root_module.addImport( - "gtk", - gobject.module("gtk4"), - ); - gtk_builder_check.root_module.addImport( - "adw", - gobject.module("adw1"), - ); - } - - for (gresource.dependencies) |pathname| { - const extension = std.fs.path.extension(pathname); - if (!std.mem.eql(u8, extension, ".ui")) continue; - const check = b.addRunArtifact(gtk_builder_check); - check.addFileArg(b.path(pathname)); - step.step.dependOn(&check.step); - } - } - // Get our gresource c/h files and add them to our build. const dist = gtkDistResources(b); step.addCSourceFile(.{ .file = dist.resources_c.path(b), .flags = &.{} }); diff --git a/src/cli/list_themes.zig b/src/cli/list_themes.zig index 8ebac4487..54f4c0969 100644 --- a/src/cli/list_themes.zig +++ b/src/cli/list_themes.zig @@ -24,6 +24,9 @@ pub const Options = struct { /// If true, force a plain list of themes. plain: bool = false, + /// Specifies the color scheme of the themes to include in the list. + color: enum { all, dark, light } = .all, + pub fn deinit(self: Options) void { _ = self; } @@ -93,6 +96,9 @@ const ThemeListElement = struct { /// * `--path`: Show the full path to the theme. /// /// * `--plain`: Force a plain listing of themes. +/// +/// * `--color`: Specify the color scheme of the themes included in the list. +/// This can be `dark`, `light`, or `all`. The default is `all`. pub fn run(gpa_alloc: std.mem.Allocator) !u8 { var opts: Options = .{}; defer opts.deinit(); @@ -137,11 +143,30 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 { if (std.mem.eql(u8, entry.name, ".DS_Store")) continue; count += 1; - try themes.append(.{ - .location = loc.location, - .path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name }), - .theme = try alloc.dupe(u8, entry.name), - }); + + const path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name }); + // if there is no need to filter just append the theme to the list + if (opts.color == .all) { + try themes.append(.{ + .path = path, + .location = loc.location, + .theme = try alloc.dupe(u8, entry.name), + }); + continue; + } + + // otherwise check if the theme should be included based on the provided options + var config = try Config.default(alloc); + defer config.deinit(); + try config.loadFile(config._arena.?.allocator(), path); + + if (shouldIncludeTheme(opts, config)) { + try themes.append(.{ + .path = path, + .location = loc.location, + .theme = try alloc.dupe(u8, entry.name), + }); + } }, else => {}, } @@ -1594,3 +1619,13 @@ fn preview(allocator: std.mem.Allocator, themes: []ThemeListElement) !void { defer app.deinit(); try app.run(); } + +fn shouldIncludeTheme(opts: Options, theme_config: Config) bool { + const rf = @as(f32, @floatFromInt(theme_config.background.r)) / 255.0; + const gf = @as(f32, @floatFromInt(theme_config.background.g)) / 255.0; + const bf = @as(f32, @floatFromInt(theme_config.background.b)) / 255.0; + const luminance = 0.2126 * rf + 0.7152 * gf + 0.0722 * bf; + const is_dark = luminance < 0.5; + + return (opts.color == .dark and is_dark) or (opts.color == .light and !is_dark); +} diff --git a/src/config/Config.zig b/src/config/Config.zig index d57ed161b..95dcf3420 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1866,7 +1866,7 @@ keybind: Keybinds = .{}, /// /// Valid values are: /// -/// * `system` (default) +/// * `system` /// /// Instructs the system to notify the user using built-in system functions. /// This could result in an audiovisual effect, a notification, or something @@ -2276,6 +2276,18 @@ keybind: Keybinds = .{}, /// Custom CSS files to be loaded. /// +/// GTK CSS documentation can be found at the following links: +/// +/// * - An overview of GTK CSS. +/// * - A comprehensive list +/// of supported CSS properties. +/// +/// Launch Ghostty with `env GTK_DEBUG=interactive ghostty` to tweak Ghostty's +/// CSS in real time using the GTK Inspector. Errors in your CSS files would +/// also be reported in the terminal you started Ghostty from. See +/// for more +/// information about the GTK Inspector. +/// /// This configuration can be repeated multiple times to load multiple files. /// Prepend a ? character to the file path to suppress errors if the file does /// not exist. If you want to include a file that begins with a literal ? @@ -3930,6 +3942,24 @@ pub const Palette = struct { /// The actual value that is updated as we parse. value: terminal.color.Palette = terminal.color.default, + /// ghostty_config_palette_s + pub const C = extern struct { + colors: [265]Color.C, + }; + + pub fn cval(self: Self) Palette.C { + var result: Palette.C = undefined; + for (self.value, 0..) |color, i| { + result.colors[i] = Color.C{ + .r = color.r, + .g = color.g, + .b = color.b, + }; + } + + return result; + } + pub fn parseCLI( self: *Self, input: ?[]const u8, @@ -4866,6 +4896,13 @@ pub const Keybinds = struct { .{ .jump_to_prompt = 1 }, ); + // Toggle command palette, matches VSCode + try self.set.put( + alloc, + .{ .key = .{ .translated = .p }, .mods = .{ .super = true, .shift = true } }, + .{ .toggle_command_palette = {} }, + ); + // Inspector, matching Chromium try self.set.put( alloc, diff --git a/src/crash/sentry.zig b/src/crash/sentry.zig index e9c49048c..c29184020 100644 --- a/src/crash/sentry.zig +++ b/src/crash/sentry.zig @@ -166,7 +166,7 @@ fn beforeSend( event_val: sentry.c.sentry_value_t, _: ?*anyopaque, _: ?*anyopaque, -) callconv(.C) sentry.c.sentry_value_t { +) callconv(.c) sentry.c.sentry_value_t { // The native SDK at the time of writing doesn't support thread-local // scopes. The full SDK has one global scope. So we use the beforeSend // handler to set thread-specific data such as window size, grid size, @@ -237,7 +237,7 @@ fn beforeSend( } pub const Transport = struct { - pub fn send(envelope: *sentry.Envelope, ud: ?*anyopaque) callconv(.C) void { + pub fn send(envelope: *sentry.Envelope, ud: ?*anyopaque) callconv(.c) void { _ = ud; defer envelope.deinit(); diff --git a/src/font/CodepointResolver.zig b/src/font/CodepointResolver.zig index 326ca0186..37093b59a 100644 --- a/src/font/CodepointResolver.zig +++ b/src/font/CodepointResolver.zig @@ -380,7 +380,7 @@ test getIndex { const testEmoji = font.embedded.emoji; const testEmojiText = font.embedded.emoji_text; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var c = Collection.init(); @@ -461,7 +461,7 @@ test "getIndex disabled font style" { var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); defer atlas_grayscale.deinit(alloc); - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var c = Collection.init(); @@ -513,7 +513,7 @@ test "getIndex box glyph" { const testing = std.testing; const alloc = testing.allocator; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); const c = Collection.init(); diff --git a/src/font/Collection.zig b/src/font/Collection.zig index cfc633b04..59f89d402 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -78,8 +78,8 @@ pub const AddError = Allocator.Error || error{ /// next in priority if others exist already, i.e. it'll be the _last_ to be /// searched for a glyph in that list. /// -/// The collection takes ownership of the face. The face will be deallocated -/// when the collection is deallocated. +/// If no error is encountered then the collection takes ownership of the face, +/// in which case face will be deallocated when the collection is deallocated. /// /// If a loaded face is added to the collection, it should be the same /// size as all the other faces in the collection. This function will not @@ -700,7 +700,7 @@ test "add full" { const alloc = testing.allocator; const testFont = font.embedded.regular; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var c = init(); @@ -714,15 +714,18 @@ test "add full" { ) }); } - try testing.expectError(error.CollectionFull, c.add( - alloc, - .regular, - .{ .loaded = try Face.init( - lib, - testFont, - .{ .size = .{ .points = 12 } }, - ) }, - )); + var face = try Face.init( + lib, + testFont, + .{ .size = .{ .points = 12 } }, + ); + // We have to deinit it manually since the + // collection doesn't do it if adding fails. + defer face.deinit(); + try testing.expectError( + error.CollectionFull, + c.add(alloc, .regular, .{ .loaded = face }), + ); } test "add deferred without loading options" { @@ -746,7 +749,7 @@ test getFace { const alloc = testing.allocator; const testFont = font.embedded.regular; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var c = init(); @@ -770,7 +773,7 @@ test getIndex { const alloc = testing.allocator; const testFont = font.embedded.regular; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var c = init(); @@ -801,7 +804,7 @@ test completeStyles { const alloc = testing.allocator; const testFont = font.embedded.regular; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var c = init(); @@ -828,7 +831,7 @@ test setSize { const alloc = testing.allocator; const testFont = font.embedded.regular; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var c = init(); @@ -851,7 +854,7 @@ test hasCodepoint { const alloc = testing.allocator; const testFont = font.embedded.regular; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var c = init(); @@ -875,7 +878,7 @@ test "hasCodepoint emoji default graphical" { const alloc = testing.allocator; const testEmoji = font.embedded.emoji; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var c = init(); @@ -898,7 +901,7 @@ test "metrics" { const alloc = testing.allocator; const testFont = font.embedded.inconsolata; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var c = init(); diff --git a/src/font/DeferredFace.zig b/src/font/DeferredFace.zig index 3ee104386..8794ccea9 100644 --- a/src/font/DeferredFace.zig +++ b/src/font/DeferredFace.zig @@ -407,7 +407,7 @@ test "fontconfig" { const alloc = testing.allocator; // Load freetype - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); // Get a deferred face from fontconfig @@ -425,7 +425,8 @@ test "fontconfig" { try testing.expect(n.len > 0); // Load it and verify it works - const face = try def.load(lib, .{ .size = .{ .points = 12 } }); + var face = try def.load(lib, .{ .size = .{ .points = 12 } }); + defer face.deinit(); try testing.expect(face.glyphIndex(' ') != null); } @@ -437,7 +438,7 @@ test "coretext" { const alloc = testing.allocator; // Load freetype - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); // Get a deferred face from fontconfig @@ -456,6 +457,7 @@ test "coretext" { try testing.expect(n.len > 0); // Load it and verify it works - const face = try def.load(lib, .{ .size = .{ .points = 12 } }); + var face = try def.load(lib, .{ .size = .{ .points = 12 } }); + defer face.deinit(); try testing.expect(face.glyphIndex(' ') != null); } diff --git a/src/font/SharedGrid.zig b/src/font/SharedGrid.zig index 65c7ecd87..72e97fad8 100644 --- a/src/font/SharedGrid.zig +++ b/src/font/SharedGrid.zig @@ -338,7 +338,7 @@ test getIndex { const alloc = testing.allocator; // const testEmoji = @import("test.zig").fontEmoji; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var grid = try testGrid(.normal, alloc, lib); diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index ca535eaf8..8ad30629e 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -50,7 +50,7 @@ pub const InitError = Library.InitError; /// Initialize a new SharedGridSet. pub fn init(alloc: Allocator) InitError!SharedGridSet { - var font_lib = try Library.init(); + var font_lib = try Library.init(alloc); errdefer font_lib.deinit(); return .{ diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 3749b4824..639eae43c 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -46,7 +46,11 @@ pub const Face = struct { }; /// Initialize a CoreText-based font from a TTF/TTC in memory. - pub fn init(lib: font.Library, source: [:0]const u8, opts: font.face.Options) !Face { + pub fn init( + lib: font.Library, + source: [:0]const u8, + opts: font.face.Options, + ) !Face { _ = lib; const data = try macos.foundation.Data.createWithBytesNoCopy(source); @@ -914,7 +918,7 @@ test "in-memory" { var atlas = try font.Atlas.init(alloc, 512, .grayscale); defer atlas.deinit(alloc); - var lib = try font.Library.init(); + var lib = try font.Library.init(alloc); defer lib.deinit(); var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); @@ -941,7 +945,7 @@ test "variable" { var atlas = try font.Atlas.init(alloc, 512, .grayscale); defer atlas.deinit(alloc); - var lib = try font.Library.init(); + var lib = try font.Library.init(alloc); defer lib.deinit(); var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); @@ -968,7 +972,7 @@ test "variable set variation" { var atlas = try font.Atlas.init(alloc, 512, .grayscale); defer atlas.deinit(alloc); - var lib = try font.Library.init(); + var lib = try font.Library.init(alloc); defer lib.deinit(); var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); @@ -996,7 +1000,7 @@ test "svg font table" { const alloc = testing.allocator; const testFont = font.embedded.julia_mono; - var lib = try font.Library.init(); + var lib = try font.Library.init(alloc); defer lib.deinit(); var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); @@ -1010,9 +1014,10 @@ test "svg font table" { test "glyphIndex colored vs text" { const testing = std.testing; + const alloc = testing.allocator; const testFont = font.embedded.julia_mono; - var lib = try font.Library.init(); + var lib = try font.Library.init(alloc); defer lib.deinit(); var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index c2eab4599..bf86b88de 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -29,12 +29,20 @@ pub const Face = struct { assert(font.face.FreetypeLoadFlags != void); } - /// Our freetype library - lib: freetype.Library, + /// Our Library + lib: Library, /// Our font face. face: freetype.Face, + /// This mutex MUST be held while doing anything with the + /// glyph slot on the freetype face, because this struct + /// may be shared across multiple surfaces. + /// + /// This means that anywhere where `self.face.loadGlyph` + /// is called, this mutex must be held. + ft_mutex: *std.Thread.Mutex, + /// Harfbuzz font corresponding to this face. hb_font: harfbuzz.Font, @@ -59,30 +67,52 @@ pub const Face = struct { }; /// Initialize a new font face with the given source in-memory. - pub fn initFile(lib: Library, path: [:0]const u8, index: i32, opts: font.face.Options) !Face { + pub fn initFile( + lib: Library, + path: [:0]const u8, + index: i32, + opts: font.face.Options, + ) !Face { + lib.mutex.lock(); + defer lib.mutex.unlock(); const face = try lib.lib.initFace(path, index); errdefer face.deinit(); return try initFace(lib, face, opts); } /// Initialize a new font face with the given source in-memory. - pub fn init(lib: Library, source: [:0]const u8, opts: font.face.Options) !Face { + pub fn init( + lib: Library, + source: [:0]const u8, + opts: font.face.Options, + ) !Face { + lib.mutex.lock(); + defer lib.mutex.unlock(); const face = try lib.lib.initMemoryFace(source, 0); errdefer face.deinit(); return try initFace(lib, face, opts); } - fn initFace(lib: Library, face: freetype.Face, opts: font.face.Options) !Face { + fn initFace( + lib: Library, + face: freetype.Face, + opts: font.face.Options, + ) !Face { try face.selectCharmap(.unicode); try setSize_(face, opts.size); var hb_font = try harfbuzz.freetype.createFont(face.handle); errdefer hb_font.destroy(); + const ft_mutex = try lib.alloc.create(std.Thread.Mutex); + errdefer lib.alloc.destroy(ft_mutex); + ft_mutex.* = .{}; + var result: Face = .{ - .lib = lib.lib, + .lib = lib, .face = face, .hb_font = hb_font, + .ft_mutex = ft_mutex, .load_flags = opts.freetype_load_flags, }; result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); @@ -114,7 +144,13 @@ pub const Face = struct { } pub fn deinit(self: *Face) void { - self.face.deinit(); + self.lib.alloc.destroy(self.ft_mutex); + { + self.lib.mutex.lock(); + defer self.lib.mutex.unlock(); + + self.face.deinit(); + } self.hb_font.destroy(); self.* = undefined; } @@ -147,11 +183,7 @@ pub const Face = struct { self.face.ref(); errdefer self.face.deinit(); - var f = try initFace( - .{ .lib = self.lib }, - self.face, - opts, - ); + var f = try initFace(self.lib, self.face, opts); errdefer f.deinit(); f.synthetic = self.synthetic; f.synthetic.bold = true; @@ -166,11 +198,7 @@ pub const Face = struct { self.face.ref(); errdefer self.face.deinit(); - var f = try initFace( - .{ .lib = self.lib }, - self.face, - opts, - ); + var f = try initFace(self.lib, self.face, opts); errdefer f.deinit(); f.synthetic = self.synthetic; f.synthetic.italic = true; @@ -228,7 +256,7 @@ pub const Face = struct { // first thing we have to do is get all the vars and put them into // an array. const mm = try self.face.getMMVar(); - defer self.lib.doneMMVar(mm); + defer self.lib.lib.doneMMVar(mm); // To avoid allocations, we cap the number of variation axes we can // support. This is arbitrary but Firefox caps this at 16 so I @@ -270,6 +298,9 @@ pub const Face = struct { /// Returns true if the given glyph ID is colorized. pub fn isColorGlyph(self: *const Face, glyph_id: u32) bool { + self.ft_mutex.lock(); + defer self.ft_mutex.unlock(); + // Load the glyph and see what pixel mode it renders with. // All modes other than BGRA are non-color. // If the glyph fails to load, just return false. @@ -296,6 +327,9 @@ pub const Face = struct { glyph_index: u32, opts: font.face.RenderOptions, ) !Glyph { + self.ft_mutex.lock(); + defer self.ft_mutex.unlock(); + const metrics = opts.grid_metrics; // If we have synthetic italic, then we apply a transformation matrix. @@ -741,6 +775,9 @@ pub const Face = struct { // If we fail to load any visible ASCII we just use max_advance from // the metrics provided by FreeType. const cell_width: f64 = cell_width: { + self.ft_mutex.lock(); + defer self.ft_mutex.unlock(); + var max: f64 = 0.0; var c: u8 = ' '; while (c < 127) : (c += 1) { @@ -780,6 +817,8 @@ pub const Face = struct { break :heights .{ cap: { + self.ft_mutex.lock(); + defer self.ft_mutex.unlock(); if (face.getCharIndex('H')) |glyph_index| { if (face.loadGlyph(glyph_index, .{ .render = true, @@ -791,6 +830,8 @@ pub const Face = struct { break :cap null; }, ex: { + self.ft_mutex.lock(); + defer self.ft_mutex.unlock(); if (face.getCharIndex('x')) |glyph_index| { if (face.loadGlyph(glyph_index, .{ .render = true, @@ -832,7 +873,7 @@ test { const testFont = font.embedded.inconsolata; const alloc = testing.allocator; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var atlas = try font.Atlas.init(alloc, 512, .grayscale); @@ -881,7 +922,7 @@ test "color emoji" { const alloc = testing.allocator; const testFont = font.embedded.emoji; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var atlas = try font.Atlas.init(alloc, 512, .rgba); @@ -936,7 +977,7 @@ test "mono to rgba" { const alloc = testing.allocator; const testFont = font.embedded.emoji; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var atlas = try font.Atlas.init(alloc, 512, .rgba); @@ -958,7 +999,7 @@ test "svg font table" { const alloc = testing.allocator; const testFont = font.embedded.julia_mono; - var lib = try font.Library.init(); + var lib = try font.Library.init(alloc); defer lib.deinit(); var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12, .xdpi = 72, .ydpi = 72 } }); @@ -995,7 +1036,7 @@ test "bitmap glyph" { const alloc = testing.allocator; const testFont = font.embedded.terminus_ttf; - var lib = try Library.init(); + var lib = try Library.init(alloc); defer lib.deinit(); var atlas = try font.Atlas.init(alloc, 512, .grayscale); diff --git a/src/font/library.zig b/src/font/library.zig index b00bbfce0..43aa101b7 100644 --- a/src/font/library.zig +++ b/src/font/library.zig @@ -1,5 +1,7 @@ //! A library represents the shared state that the underlying font //! library implementation(s) require per-process. +const std = @import("std"); +const Allocator = std.mem.Allocator; const builtin = @import("builtin"); const options = @import("main.zig").options; const freetype = @import("freetype"); @@ -24,13 +26,26 @@ pub const Library = switch (options.backend) { pub const FreetypeLibrary = struct { lib: freetype.Library, - pub const InitError = freetype.Error; + alloc: Allocator, - pub fn init() InitError!Library { - return Library{ .lib = try freetype.Library.init() }; + /// Mutex to be held any time the library is + /// being used to create or destroy a face. + mutex: *std.Thread.Mutex, + + pub const InitError = freetype.Error || Allocator.Error; + + pub fn init(alloc: Allocator) InitError!Library { + const lib = try freetype.Library.init(); + errdefer lib.deinit(); + + const mutex = try alloc.create(std.Thread.Mutex); + mutex.* = .{}; + + return Library{ .lib = lib, .alloc = alloc, .mutex = mutex }; } pub fn deinit(self: *Library) void { + self.alloc.destroy(self.mutex); self.lib.deinit(); } }; @@ -38,7 +53,8 @@ pub const FreetypeLibrary = struct { pub const NoopLibrary = struct { pub const InitError = error{}; - pub fn init() InitError!Library { + pub fn init(alloc: Allocator) InitError!Library { + _ = alloc; return Library{}; } diff --git a/src/font/opentype/svg.zig b/src/font/opentype/svg.zig index 01d172d17..ff8eeed49 100644 --- a/src/font/opentype/svg.zig +++ b/src/font/opentype/svg.zig @@ -99,7 +99,7 @@ test "SVG" { const alloc = testing.allocator; const testFont = font.embedded.julia_mono; - var lib = try font.Library.init(); + var lib = try font.Library.init(alloc); defer lib.deinit(); var face = try font.Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); diff --git a/src/font/shaper/coretext.zig b/src/font/shaper/coretext.zig index ec64fe6eb..f2ac5b85d 100644 --- a/src/font/shaper/coretext.zig +++ b/src/font/shaper/coretext.zig @@ -1761,7 +1761,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper { .nerd_font => font.embedded.nerd_font, }; - var lib = try Library.init(); + var lib = try Library.init(alloc); errdefer lib.deinit(); var c = Collection.init(); diff --git a/src/font/shaper/harfbuzz.zig b/src/font/shaper/harfbuzz.zig index b284dc140..eb8130f79 100644 --- a/src/font/shaper/harfbuzz.zig +++ b/src/font/shaper/harfbuzz.zig @@ -1220,7 +1220,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper { .arabic => font.embedded.arabic, }; - var lib = try Library.init(); + var lib = try Library.init(alloc); errdefer lib.deinit(); var c = Collection.init(); diff --git a/src/font/sprite/Box.zig b/src/font/sprite/Box.zig index f387ab240..68acdabe5 100644 --- a/src/font/sprite/Box.zig +++ b/src/font/sprite/Box.zig @@ -2239,7 +2239,7 @@ fn draw_branch_node( @min(float_width - cx, float_height - cy), ); - var ctx = canvas.getContext() catch return; + var ctx = canvas.getContext(); defer ctx.deinit(); ctx.setSource(.{ .opaque_pattern = .{ .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, @@ -2290,7 +2290,7 @@ fn draw_circle( }; const r: f64 = 0.5 * @min(float_width, float_height); - var ctx = canvas.getContext() catch return; + var ctx = canvas.getContext(); defer ctx.deinit(); ctx.setSource(.{ .opaque_pattern = .{ .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, @@ -2680,7 +2680,7 @@ fn draw_arc( // Fraction away from the center to place the middle control points, const s: f64 = 0.25; - var ctx = try canvas.getContext(); + var ctx = canvas.getContext(); defer ctx.deinit(); ctx.setSource(.{ .opaque_pattern = .{ .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, @@ -2974,7 +2974,7 @@ fn draw_separated_block_quadrant(self: Box, canvas: *font.sprite.Canvas, comptim } } - var ctx = try canvas.getContext(); + var ctx = canvas.getContext(); defer ctx.deinit(); ctx.setSource(.{ .opaque_pattern = .{ .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, diff --git a/src/font/sprite/canvas.zig b/src/font/sprite/canvas.zig index 072e5bd46..ed00aef12 100644 --- a/src/font/sprite/canvas.zig +++ b/src/font/sprite/canvas.zig @@ -149,8 +149,8 @@ pub const Canvas = struct { } /// Acquires a z2d drawing context, caller MUST deinit context. - pub fn getContext(self: *Canvas) Allocator.Error!z2d.Context { - return try z2d.Context.init(self.alloc, &self.sfc); + pub fn getContext(self: *Canvas) z2d.Context { + return z2d.Context.init(self.alloc, &self.sfc); } /// Draw and fill a single pixel diff --git a/src/input.zig b/src/input.zig index 83be38d3d..caaf80509 100644 --- a/src/input.zig +++ b/src/input.zig @@ -5,6 +5,7 @@ const mouse = @import("input/mouse.zig"); const key = @import("input/key.zig"); const keyboard = @import("input/keyboard.zig"); +pub const command = @import("input/command.zig"); pub const function_keys = @import("input/function_keys.zig"); pub const keycodes = @import("input/keycodes.zig"); pub const kitty = @import("input/kitty.zig"); @@ -12,6 +13,7 @@ pub const kitty = @import("input/kitty.zig"); pub const ctrlOrSuper = key.ctrlOrSuper; pub const Action = key.Action; pub const Binding = @import("input/Binding.zig"); +pub const Command = command.Command; pub const Link = @import("input/Link.zig"); pub const Key = key.Key; pub const KeyboardLayout = keyboard.Layout; diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 244cd29cd..6583e1462 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -222,12 +222,12 @@ pub fn lessThan(_: void, lhs: Binding, rhs: Binding) bool { pub const Action = union(enum) { /// Ignore this key combination, don't send it to the child process, just /// black hole it. - ignore: void, + ignore, /// This action is used to flag that the binding should be removed from /// the set. This should never exist in an active set and `set.put` has an /// assertion to verify this. - unbind: void, + unbind, /// Send a CSI sequence. The value should be the CSI sequence without the /// CSI header (`ESC [` or `\x1b[`). @@ -252,35 +252,36 @@ pub const Action = union(enum) { /// If you do this while in a TUI program such as vim, this may break /// the program. If you do this while in a shell, you may have to press /// enter after to get a new prompt. - reset: void, + reset, /// Copy and paste. - copy_to_clipboard: void, - paste_from_clipboard: void, - paste_from_selection: void, + copy_to_clipboard, + paste_from_clipboard, + paste_from_selection, /// Copy the URL under the cursor to the clipboard. If there is no /// URL under the cursor, this does nothing. - copy_url_to_clipboard: void, + copy_url_to_clipboard, /// Increase/decrease the font size by a certain amount. increase_font_size: f32, decrease_font_size: f32, /// Reset the font size to the original configured size. - reset_font_size: void, + reset_font_size, /// Clear the screen. This also clears all scrollback. - clear_screen: void, + clear_screen, /// Select all text on the screen. - select_all: void, + select_all, /// Scroll the screen varying amounts. - scroll_to_top: void, - scroll_to_bottom: void, - scroll_page_up: void, - scroll_page_down: void, + scroll_to_top, + scroll_to_bottom, + scroll_to_selection, + scroll_page_up, + scroll_page_down, scroll_page_fractional: f32, scroll_page_lines: i16, @@ -321,19 +322,19 @@ pub const Action = union(enum) { /// Open a new window. If the application isn't currently focused, /// this will bring it to the front. - new_window: void, + new_window, /// Open a new tab. - new_tab: void, + new_tab, /// Go to the previous tab. - previous_tab: void, + previous_tab, /// Go to the next tab. - next_tab: void, + next_tab, /// Go to the last tab (the one with the highest index) - last_tab: void, + last_tab, /// Go to the tab with the specific number, 1-indexed. If the tab number /// is higher than the number of tabs, this will go to the last tab. @@ -345,11 +346,11 @@ pub const Action = union(enum) { move_tab: isize, /// Toggle the tab overview. - /// This only works with libadwaita enabled currently. - toggle_tab_overview: void, + /// This only works with libadwaita version 1.4.0 or newer. + toggle_tab_overview, /// Change the title of the current focused surface via a prompt. - prompt_surface_title: void, + prompt_surface_title, /// Create a new split in the given direction. /// @@ -365,7 +366,7 @@ pub const Action = union(enum) { goto_split: SplitFocusDirection, /// zoom/unzoom the current split. - toggle_split_zoom: void, + toggle_split_zoom, /// Resize the current split in a given direction. /// @@ -378,12 +379,12 @@ pub const Action = union(enum) { resize_split: SplitResizeParameter, /// Equalize all splits in the current window - equalize_splits: void, + equalize_splits, /// Reset the window to the default size. The "default size" is the /// size that a new window would be created with. This has no effect /// if the window is fullscreen. - reset_window_size: void, + reset_window_size, /// Control the terminal inspector visibility. /// @@ -397,39 +398,46 @@ pub const Action = union(enum) { /// Open the configuration file in the default OS editor. If your default OS /// editor isn't configured then this will fail. Currently, any failures to /// open the configuration will show up only in the logs. - open_config: void, + open_config, /// Reload the configuration. The exact meaning depends on the app runtime /// in use but this usually involves re-reading the configuration file /// and applying any changes. Note that not all changes can be applied at /// runtime. - reload_config: void, + reload_config, /// Close the current "surface", whether that is a window, tab, split, etc. /// This only closes ONE surface. This will trigger close confirmation as /// configured. - close_surface: void, + close_surface, /// Close the current tab, regardless of how many splits there may be. /// This will trigger close confirmation as configured. - close_tab: void, + close_tab, /// Close the window, regardless of how many tabs or splits there may be. /// This will trigger close confirmation as configured. - close_window: void, + close_window, /// Close all windows. This will trigger close confirmation as configured. /// This only works for macOS currently. - close_all_windows: void, + close_all_windows, /// Toggle maximized window state. This only works on Linux. - toggle_maximize: void, + toggle_maximize, /// Toggle fullscreen mode of window. - toggle_fullscreen: void, + toggle_fullscreen, /// Toggle window decorations on and off. This only works on Linux. - toggle_window_decorations: void, + toggle_window_decorations, + + /// Toggle whether the terminal window is always on top of other + /// windows even when it is not focused. Terminal windows always start + /// as normal (not always on top) windows. + /// + /// This only works on macOS. + toggle_window_float_on_top, /// Toggle secure input mode on or off. This is used to prevent apps /// that monitor input from seeing what you type. This is useful for @@ -439,7 +447,15 @@ pub const Action = union(enum) { /// terminal. You must toggle it off to disable it, or quit Ghostty. /// /// This only works on macOS, since this is a system API on macOS. - toggle_secure_input: void, + toggle_secure_input, + + /// Toggle the command palette. The command palette is a UI element + /// that lets you see what actions you can perform, their associated + /// keybindings (if any), a search bar to filter the actions, and + /// the ability to then execute the action. + /// + /// This only works on macOS. + toggle_command_palette, /// Toggle the "quick" terminal. The quick terminal is a terminal that /// appears on demand from a keybinding, often sliding in from a screen @@ -480,7 +496,7 @@ pub const Action = union(enum) { /// plugin enabled, open System Settings > Apps & Windows > Window /// Management > Desktop Effects, and enable the plugin in the plugin list. /// Ghostty would then need to be restarted for this to take effect. - toggle_quick_terminal: void, + toggle_quick_terminal, /// Show/hide all windows. If all windows become shown, we also ensure /// Ghostty becomes focused. When hiding all windows, focus is yielded @@ -489,10 +505,10 @@ pub const Action = union(enum) { /// Note: When the focused surface is fullscreen, this method does nothing. /// /// This currently only works on macOS. - toggle_visibility: void, + toggle_visibility, /// Quit ghostty. - quit: void, + quit, /// Crash ghostty in the desired thread for the focused surface. /// @@ -552,6 +568,8 @@ pub const Action = union(enum) { left, up, auto, // splits along the larger direction + + pub const default: SplitDirection = .auto; }; pub const SplitFocusDirection = enum { @@ -713,7 +731,28 @@ pub const Action = union(enum) { Action.CursorKey => return Error.InvalidAction, else => { - const idx = colonIdx orelse return Error.InvalidFormat; + // Get the parameter after the colon. The parameter + // can be optional for action types that can have a + // "default" decl. + const idx = colonIdx orelse { + switch (@typeInfo(field.type)) { + .@"struct", + .@"union", + .@"enum", + => if (@hasDecl(field.type, "default")) { + return @unionInit( + Action, + field.name, + @field(field.type, "default"), + ); + }, + + else => {}, + } + + return Error.InvalidFormat; + }; + const param = input[idx + 1 ..]; return @unionInit( Action, @@ -774,6 +813,7 @@ pub const Action = union(enum) { .select_all, .scroll_to_top, .scroll_to_bottom, + .scroll_to_selection, .scroll_page_up, .scroll_page_down, .scroll_page_fractional, @@ -789,7 +829,9 @@ pub const Action = union(enum) { .toggle_maximize, .toggle_fullscreen, .toggle_window_decorations, + .toggle_window_float_on_top, .toggle_secure_input, + .toggle_command_palette, .reset_window_size, .crash, => .surface, @@ -1017,15 +1059,6 @@ pub const Action = union(enum) { } }; -// A key for the C API to execute an action. This must be kept in sync -// with include/ghostty.h. -pub const Key = enum(c_int) { - copy_to_clipboard, - paste_from_clipboard, - new_tab, - new_window, -}; - /// Trigger is the associated key state that can trigger an action. /// This is an extern struct because this is also used in the C API. /// @@ -2005,6 +2038,17 @@ test "parse: action with enum" { } } +test "parse: action with enum with default" { + const testing = std.testing; + + // parameter + { + const binding = try parseSingle("a=new_split"); + try testing.expect(binding.action == .new_split); + try testing.expectEqual(Action.SplitDirection.auto, binding.action.new_split); + } +} + test "parse: action with int" { const testing = std.testing; diff --git a/src/input/KeyEncoder.zig b/src/input/KeyEncoder.zig index 4aaae25e9..e79856a94 100644 --- a/src/input/KeyEncoder.zig +++ b/src/input/KeyEncoder.zig @@ -35,7 +35,7 @@ pub fn encode( self: *const KeyEncoder, buf: []u8, ) ![]const u8 { - // log.warn("KEYENCODER self={}", .{self.j}); + // log.warn("KEYENCODER self={}", .{self.*}); if (self.kitty_flags.int() != 0) return try self.kitty(buf); return try self.legacy(buf); } diff --git a/src/input/command.zig b/src/input/command.zig new file mode 100644 index 000000000..1f685269b --- /dev/null +++ b/src/input/command.zig @@ -0,0 +1,420 @@ +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const Action = @import("Binding.zig").Action; + +/// A command is a named binding action that can be executed from +/// something like a command palette. +/// +/// A command must be associated with a binding; all commands can be +/// mapped to traditional `keybind` configurations. This restriction +/// makes it so that there is nothing special about commands and likewise +/// it makes it trivial and consistent to define custom commands. +/// +/// For apprt implementers: a command palette doesn't have to make use +/// of all the fields here. We try to provide as much information as +/// possible to make it easier to implement a command palette in the way +/// that makes the most sense for the application. +pub const Command = struct { + action: Action, + title: [:0]const u8, + description: [:0]const u8, + + /// ghostty_command_s + pub const C = extern struct { + action_key: [*:0]const u8, + action: [*:0]const u8, + title: [*:0]const u8, + description: [*:0]const u8, + }; + + /// Convert this command to a C struct. + pub fn comptimeCval(self: Command) C { + assert(@inComptime()); + + return .{ + .action_key = @tagName(self.action), + .action = std.fmt.comptimePrint("{s}", .{self.action}), + .title = self.title, + .description = self.description, + }; + } + + /// Implements a comparison function for std.mem.sortUnstable + /// and similar functions. The sorting is defined by Ghostty + /// to be what we prefer. If a caller wants some other sorting, + /// they should do it themselves. + pub fn lessThan(_: void, lhs: Command, rhs: Command) bool { + return std.ascii.orderIgnoreCase(lhs.title, rhs.title) == .lt; + } +}; + +pub const defaults: []const Command = defaults: { + @setEvalBranchQuota(100_000); + + var count: usize = 0; + for (@typeInfo(Action.Key).@"enum".fields) |field| { + const action = @field(Action.Key, field.name); + count += actionCommands(action).len; + } + + var result: [count]Command = undefined; + var i: usize = 0; + for (@typeInfo(Action.Key).@"enum".fields) |field| { + const action = @field(Action.Key, field.name); + const commands = actionCommands(action); + for (commands) |cmd| { + result[i] = cmd; + i += 1; + } + } + + std.mem.sortUnstable(Command, &result, {}, Command.lessThan); + + assert(i == count); + const final = result; + break :defaults &final; +}; + +/// Defaults in C-compatible form. +pub const defaultsC: []const Command.C = defaults: { + var result: [defaults.len]Command.C = undefined; + for (defaults, 0..) |cmd, i| result[i] = cmd.comptimeCval(); + const final = result; + break :defaults &final; +}; + +/// Returns the set of commands associated with this action key by +/// default. Not all actions should have commands. As a general guideline, +/// an action should have a command only if it is useful and reasonable +/// to appear in a command palette. +fn actionCommands(action: Action.Key) []const Command { + // This is implemented as a function and switch rather than a + // flat comptime const because we want to ensure we get a compiler + // error when a new binding is added so that the contributor has + // to consider whether that new binding should have commands or not. + const result: []const Command = switch (action) { + // Note: the use of `comptime` prefix on the return values + // ensures that the data returned is all in the binary and + // and not pointing to the stack. + + .reset => comptime &.{.{ + .action = .reset, + .title = "Reset Terminal", + .description = "Reset the terminal to a clean state.", + }}, + + .copy_to_clipboard => comptime &.{.{ + .action = .copy_to_clipboard, + .title = "Copy to Clipboard", + .description = "Copy the selected text to the clipboard.", + }}, + + .copy_url_to_clipboard => comptime &.{.{ + .action = .copy_url_to_clipboard, + .title = "Copy URL to Clipboard", + .description = "Copy the URL under the cursor to the clipboard.", + }}, + + .paste_from_clipboard => comptime &.{.{ + .action = .paste_from_clipboard, + .title = "Paste from Clipboard", + .description = "Paste the contents of the clipboard.", + }}, + + .paste_from_selection => comptime &.{.{ + .action = .paste_from_selection, + .title = "Paste from Selection", + .description = "Paste the contents of the selection clipboard.", + }}, + + .increase_font_size => comptime &.{.{ + .action = .{ .increase_font_size = 1 }, + .title = "Increase Font Size", + .description = "Increase the font size by 1 point.", + }}, + + .decrease_font_size => comptime &.{.{ + .action = .{ .decrease_font_size = 1 }, + .title = "Decrease Font Size", + .description = "Decrease the font size by 1 point.", + }}, + + .reset_font_size => comptime &.{.{ + .action = .reset_font_size, + .title = "Reset Font Size", + .description = "Reset the font size to the default.", + }}, + + .clear_screen => comptime &.{.{ + .action = .clear_screen, + .title = "Clear Screen", + .description = "Clear the screen and scrollback.", + }}, + + .select_all => comptime &.{.{ + .action = .select_all, + .title = "Select All", + .description = "Select all text on the screen.", + }}, + + .scroll_to_top => comptime &.{.{ + .action = .scroll_to_top, + .title = "Scroll to Top", + .description = "Scroll to the top of the screen.", + }}, + + .scroll_to_bottom => comptime &.{.{ + .action = .scroll_to_bottom, + .title = "Scroll to Bottom", + .description = "Scroll to the bottom of the screen.", + }}, + + .scroll_to_selection => comptime &.{.{ + .action = .scroll_to_selection, + .title = "Scroll to Selection", + .description = "Scroll to the selected text.", + }}, + + .scroll_page_up => comptime &.{.{ + .action = .scroll_page_up, + .title = "Scroll Page Up", + .description = "Scroll the screen up by a page.", + }}, + + .scroll_page_down => comptime &.{.{ + .action = .scroll_page_down, + .title = "Scroll Page Down", + .description = "Scroll the screen down by a page.", + }}, + + .write_screen_file => comptime &.{ + .{ + .action = .{ .write_screen_file = .paste }, + .title = "Copy Screen to Temporary File and Paste Path", + .description = "Copy the screen contents to a temporary file and paste the path to the file.", + }, + .{ + .action = .{ .write_screen_file = .open }, + .title = "Copy Screen to Temporary File and Open", + .description = "Copy the screen contents to a temporary file and open it.", + }, + }, + + .write_selection_file => comptime &.{ + .{ + .action = .{ .write_selection_file = .paste }, + .title = "Copy Selection to Temporary File and Paste Path", + .description = "Copy the selection contents to a temporary file and paste the path to the file.", + }, + .{ + .action = .{ .write_selection_file = .open }, + .title = "Copy Selection to Temporary File and Open", + .description = "Copy the selection contents to a temporary file and open it.", + }, + }, + + .new_window => comptime &.{.{ + .action = .new_window, + .title = "New Window", + .description = "Open a new window.", + }}, + + .new_tab => comptime &.{.{ + .action = .new_tab, + .title = "New Tab", + .description = "Open a new tab.", + }}, + + .move_tab => comptime &.{ + .{ + .action = .{ .move_tab = -1 }, + .title = "Move Tab Left", + .description = "Move the current tab to the left.", + }, + .{ + .action = .{ .move_tab = 1 }, + .title = "Move Tab Right", + .description = "Move the current tab to the right.", + }, + }, + + .toggle_tab_overview => comptime &.{.{ + .action = .toggle_tab_overview, + .title = "Toggle Tab Overview", + .description = "Toggle the tab overview.", + }}, + + .prompt_surface_title => comptime &.{.{ + .action = .prompt_surface_title, + .title = "Change Title...", + .description = "Prompt for a new title for the current terminal.", + }}, + + .new_split => comptime &.{ + .{ + .action = .{ .new_split = .left }, + .title = "Split Left", + .description = "Split the terminal to the left.", + }, + .{ + .action = .{ .new_split = .right }, + .title = "Split Right", + .description = "Split the terminal to the right.", + }, + .{ + .action = .{ .new_split = .up }, + .title = "Split Up", + .description = "Split the terminal up.", + }, + .{ + .action = .{ .new_split = .down }, + .title = "Split Down", + .description = "Split the terminal down.", + }, + }, + + .toggle_split_zoom => comptime &.{.{ + .action = .toggle_split_zoom, + .title = "Toggle Split Zoom", + .description = "Toggle the zoom state of the current split.", + }}, + + .equalize_splits => comptime &.{.{ + .action = .equalize_splits, + .title = "Equalize Splits", + .description = "Equalize the size of all splits.", + }}, + + .reset_window_size => comptime &.{.{ + .action = .reset_window_size, + .title = "Reset Window Size", + .description = "Reset the window size to the default.", + }}, + + .inspector => comptime &.{.{ + .action = .{ .inspector = .toggle }, + .title = "Toggle Inspector", + .description = "Toggle the inspector.", + }}, + + .open_config => comptime &.{.{ + .action = .open_config, + .title = "Open Config", + .description = "Open the config file.", + }}, + + .reload_config => comptime &.{.{ + .action = .reload_config, + .title = "Reload Config", + .description = "Reload the config file.", + }}, + + .close_surface => comptime &.{.{ + .action = .close_surface, + .title = "Close Terminal", + .description = "Close the current terminal.", + }}, + + .close_tab => comptime &.{.{ + .action = .close_tab, + .title = "Close Tab", + .description = "Close the current tab.", + }}, + + .close_window => comptime &.{.{ + .action = .close_window, + .title = "Close Window", + .description = "Close the current window.", + }}, + + .close_all_windows => comptime &.{.{ + .action = .close_all_windows, + .title = "Close All Windows", + .description = "Close all windows.", + }}, + + .toggle_maximize => comptime &.{.{ + .action = .toggle_maximize, + .title = "Toggle Maximize", + .description = "Toggle the maximized state of the current window.", + }}, + + .toggle_fullscreen => comptime &.{.{ + .action = .toggle_fullscreen, + .title = "Toggle Fullscreen", + .description = "Toggle the fullscreen state of the current window.", + }}, + + .toggle_window_decorations => comptime &.{.{ + .action = .toggle_window_decorations, + .title = "Toggle Window Decorations", + .description = "Toggle the window decorations.", + }}, + + .toggle_window_float_on_top => comptime &.{.{ + .action = .toggle_window_float_on_top, + .title = "Toggle Float on Top", + .description = "Toggle the float on top state of the current window.", + }}, + + .toggle_secure_input => comptime &.{.{ + .action = .toggle_secure_input, + .title = "Toggle Secure Input", + .description = "Toggle secure input mode.", + }}, + + .quit => comptime &.{.{ + .action = .quit, + .title = "Quit", + .description = "Quit the application.", + }}, + + // No commands because they're parameterized and there + // aren't obvious values users would use. It is possible that + // these may have commands in the future if there are very + // common values that users tend to use. + .csi, + .esc, + .text, + .cursor_key, + .scroll_page_fractional, + .scroll_page_lines, + .adjust_selection, + .jump_to_prompt, + .write_scrollback_file, + .goto_tab, + .goto_split, + .resize_split, + .crash, + => comptime &.{}, + + // No commands because I'm not sure they make sense in a command + // palette context. + .toggle_command_palette, + .toggle_quick_terminal, + .toggle_visibility, + .previous_tab, + .next_tab, + .last_tab, + => comptime &.{}, + + // No commands for obvious reasons + .ignore, + .unbind, + => comptime &.{}, + }; + + // All generated commands should have the same action as the + // action passed in. + for (result) |cmd| assert(cmd.action == action); + + return result; +} + +test "command defaults" { + // This just ensures that defaults is analyzed and works. + const testing = std.testing; + try testing.expect(defaults.len > 0); + try testing.expectEqual(defaults.len, defaultsC.len); +} diff --git a/src/os/flatpak.zig b/src/os/flatpak.zig index 61a217929..7b92a8ba9 100644 --- a/src/os/flatpak.zig +++ b/src/os/flatpak.zig @@ -444,7 +444,7 @@ pub const FlatpakHostCommand = struct { _: [*c]const u8, params: ?*c.GVariant, ud: ?*anyopaque, - ) callconv(.C) void { + ) callconv(.c) void { const self = @as(*FlatpakHostCommand, @ptrCast(@alignCast(ud))); const state = state: { self.state_mutex.lock(); diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index e6f77216f..ddc94b1ec 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1524,7 +1524,7 @@ const CompletionBlock = objc.Block(struct { self: *Metal }, .{ fn bufferCompleted( block: *const CompletionBlock.Context, buffer_id: objc.c.id, -) callconv(.C) void { +) callconv(.c) void { const self = block.self; const buffer = objc.Object.fromId(buffer_id); diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index ba9e5d81f..a3a2d8f7e 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -1779,6 +1779,15 @@ pub fn rebuildCells( } } + // Free up memory, generally in case where surface has shrunk. + // If more than half of the capacity is unused, remove all unused capacity. + if (self.cells.items.len * 2 < self.cells.capacity) { + self.cells.shrinkAndFree(self.alloc, self.cells.items.len); + } + if (self.cells_bg.items.len * 2 < self.cells_bg.capacity) { + self.cells_bg.shrinkAndFree(self.alloc, self.cells_bg.items.len); + } + // Some debug mode safety checks if (std.debug.runtime_safety) { for (self.cells_bg.items) |cell| assert(cell.mode == .bg); @@ -2196,12 +2205,6 @@ pub fn setScreenSize( if (single_threaded_draw) self.draw_mutex.lock(); defer if (single_threaded_draw) self.draw_mutex.unlock(); - // Reset our buffer sizes so that we free memory when the screen shrinks. - // This could be made more clever by only doing this when the screen - // shrinks but the performance cost really isn't that much. - self.cells.clearAndFree(self.alloc); - self.cells_bg.clearAndFree(self.alloc); - // Store our screen size self.size = size; @@ -2338,6 +2341,23 @@ pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void { if (comptime is_darwin) _ = ogl.CGLLockContext(cgl_ctx); defer _ = if (comptime is_darwin) ogl.CGLUnlockContext(cgl_ctx); + // If our viewport size doesn't match the saved screen size then + // we need to update it. We rely on this over setScreenSize because + // we can pull it directly from the OpenGL context instead of relying + // on the eventual message. + { + var viewport: [4]gl.c.GLint = undefined; + gl.glad.context.GetIntegerv.?(gl.c.GL_VIEWPORT, &viewport); + const screen: renderer.ScreenSize = .{ + .width = @intCast(viewport[2]), + .height = @intCast(viewport[3]), + }; + if (!screen.equals(self.size.screen)) { + self.size.screen = screen; + self.deferred_screen_size = .{ .size = self.size }; + } + } + // Draw our terminal cells try self.drawCellProgram(gl_state); diff --git a/src/renderer/shadertoy.zig b/src/renderer/shadertoy.zig index 8c9b68447..45d86cbfe 100644 --- a/src/renderer/shadertoy.zig +++ b/src/renderer/shadertoy.zig @@ -250,7 +250,7 @@ fn spvCross( // It would be better to get this out into an output parameter to // show users but for now we can just log it. c.spvc_context_set_error_callback(ctx, @ptrCast(&(struct { - fn callback(_: ?*anyopaque, msg_ptr: [*c]const u8) callconv(.C) void { + fn callback(_: ?*anyopaque, msg_ptr: [*c]const u8) callconv(.c) void { const msg = std.mem.sliceTo(msg_ptr, 0); std.log.warn("spirv-cross error message={s}", .{msg}); } diff --git a/src/terminal/dcs.zig b/src/terminal/dcs.zig index da6f3ae23..db5f95c4f 100644 --- a/src/terminal/dcs.zig +++ b/src/terminal/dcs.zig @@ -162,7 +162,12 @@ pub const Handler = struct { break :tmux .{ .tmux = .{ .exit = {} } }; }, - .xtgettcap => |list| .{ .xtgettcap = .{ .data = list } }, + .xtgettcap => |list| xtgettcap: { + for (list.items, 0..) |b, i| { + list.items[i] = std.ascii.toUpper(b); + } + break :xtgettcap .{ .xtgettcap = .{ .data = list } }; + }, .decrqss => |buffer| .{ .decrqss = switch (buffer.len) { 0 => .none, @@ -306,6 +311,21 @@ test "XTGETTCAP command" { try testing.expect(cmd.xtgettcap.next() == null); } +test "XTGETTCAP mixed case" { + const testing = std.testing; + const alloc = testing.allocator; + + var h: Handler = .{}; + defer h.deinit(); + try testing.expect(h.hook(alloc, .{ .intermediates = "+", .final = 'q' }) == null); + for ("536d756C78") |byte| _ = h.put(byte); + var cmd = h.unhook().?; + defer cmd.deinit(); + try testing.expect(cmd == .xtgettcap); + try testing.expectEqualStrings("536D756C78", cmd.xtgettcap.next().?); + try testing.expect(cmd.xtgettcap.next() == null); +} + test "XTGETTCAP command multiple keys" { const testing = std.testing; const alloc = testing.allocator; @@ -333,7 +353,7 @@ test "XTGETTCAP command invalid data" { var cmd = h.unhook().?; defer cmd.deinit(); try testing.expect(cmd == .xtgettcap); - try testing.expectEqualStrings("who", cmd.xtgettcap.next().?); + try testing.expectEqualStrings("WHO", cmd.xtgettcap.next().?); try testing.expectEqualStrings("536D756C78", cmd.xtgettcap.next().?); try testing.expect(cmd.xtgettcap.next() == null); } diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index abe49a47b..23c626879 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -745,7 +745,7 @@ const Subprocess = struct { }); arena: std.heap.ArenaAllocator, - cwd: ?[]const u8, + cwd: ?[:0]const u8, env: ?EnvMap, args: []const [:0]const u8, grid_size: renderer.GridSize, @@ -985,8 +985,8 @@ const Subprocess = struct { // We have to copy the cwd because there is no guarantee that // pointers in full_config remain valid. - const cwd: ?[]u8 = if (cfg.working_directory) |cwd| - try alloc.dupe(u8, cwd) + const cwd: ?[:0]u8 = if (cfg.working_directory) |cwd| + try alloc.dupeZ(u8, cwd) else null; @@ -1048,6 +1048,47 @@ const Subprocess = struct { log.debug("starting command command={s}", .{self.args}); + // If we can't access the cwd, then don't set any cwd and inherit. + // This is important because our cwd can be set by the shell (OSC 7) + // and we don't want to break new windows. + const cwd: ?[:0]const u8 = if (self.cwd) |proposed| cwd: { + if ((comptime build_config.flatpak) and internal_os.isFlatpak()) { + // Flatpak sandboxing prevents access to certain reserved paths + // regardless of configured permissions. Perform a test spawn + // to get around this problem + // + // https://docs.flatpak.org/en/latest/sandbox-permissions.html#reserved-paths + log.info("flatpak detected, will use host command to verify cwd access", .{}); + const dev_null = try std.fs.cwd().openFile("/dev/null", .{ .mode = .read_write }); + defer dev_null.close(); + var cmd: internal_os.FlatpakHostCommand = .{ + .argv = &[_][]const u8{ + "/bin/sh", + "-c", + ":", + }, + .cwd = proposed, + .stdin = dev_null.handle, + .stdout = dev_null.handle, + .stderr = dev_null.handle, + }; + _ = cmd.spawn(alloc) catch |err| { + log.warn("cannot spawn command at cwd, ignoring: {}", .{err}); + break :cwd null; + }; + _ = try cmd.wait(); + + break :cwd proposed; + } + + if (std.fs.cwd().access(proposed, .{})) { + break :cwd proposed; + } else |err| { + log.warn("cannot access cwd, ignoring: {}", .{err}); + break :cwd null; + } + } else null; + // In flatpak, we use the HostCommand to execute our shell. if (internal_os.isFlatpak()) flatpak: { if (comptime !build_config.flatpak) { @@ -1058,6 +1099,7 @@ const Subprocess = struct { // Flatpak command must have a stable pointer. self.flatpak_command = .{ .argv = self.args, + .cwd = cwd, .env = if (self.env) |*env| env else null, .stdin = pty.slave, .stdout = pty.slave, @@ -1083,18 +1125,6 @@ const Subprocess = struct { }; } - // If we can't access the cwd, then don't set any cwd and inherit. - // This is important because our cwd can be set by the shell (OSC 7) - // and we don't want to break new windows. - const cwd: ?[]const u8 = if (self.cwd) |proposed| cwd: { - if (std.fs.cwd().access(proposed, .{})) { - break :cwd proposed; - } else |err| { - log.warn("cannot access cwd, ignoring: {}", .{err}); - break :cwd null; - } - } else null; - // Build our subcommand var cmd: Command = .{ .path = self.args[0], diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig index 2cf809694..fb62327d3 100644 --- a/src/termio/shell_integration.zig +++ b/src/termio/shell_integration.zig @@ -239,7 +239,7 @@ fn setupBash( resource_dir: []const u8, env: *EnvMap, ) !?config.Command { - var args = try std.ArrayList([:0]const u8).initCapacity(alloc, 2); + var args = try std.ArrayList([:0]const u8).initCapacity(alloc, 3); defer args.deinit(); // Iterator that yields each argument in the original command line. @@ -247,12 +247,17 @@ fn setupBash( var iter = try command.argIterator(alloc); defer iter.deinit(); - // Start accumulating arguments with the executable and `--posix` mode flag. + // Start accumulating arguments with the executable and initial flags. if (iter.next()) |exe| { try args.append(try alloc.dupeZ(u8, exe)); } else return null; try args.append("--posix"); + // On macOS, we request a login shell to match that platform's norms. + if (comptime builtin.target.os.tag.isDarwin()) { + try args.append("--login"); + } + // Stores the list of intercepted command line flags that will be passed // to our shell integration script: --norc --noprofile // We always include at least "1" so the script can differentiate between @@ -342,9 +347,12 @@ test "bash" { const command = try setupBash(alloc, .{ .shell = "bash" }, ".", &env); - try testing.expectEqual(2, command.?.direct.len); + try testing.expect(command.?.direct.len >= 2); try testing.expectEqualStrings("bash", command.?.direct[0]); try testing.expectEqualStrings("--posix", command.?.direct[1]); + if (comptime builtin.target.os.tag.isDarwin()) { + try testing.expectEqualStrings("--login", command.?.direct[2]); + } try testing.expectEqualStrings("./shell-integration/bash/ghostty.bash", env.get("ENV").?); try testing.expectEqualStrings("1", env.get("GHOSTTY_BASH_INJECT").?); } @@ -387,9 +395,12 @@ test "bash: inject flags" { const command = try setupBash(alloc, .{ .shell = "bash --norc" }, ".", &env); - try testing.expectEqual(2, command.?.direct.len); + try testing.expect(command.?.direct.len >= 2); try testing.expectEqualStrings("bash", command.?.direct[0]); try testing.expectEqualStrings("--posix", command.?.direct[1]); + if (comptime builtin.target.os.tag.isDarwin()) { + try testing.expectEqualStrings("--login", command.?.direct[2]); + } try testing.expectEqualStrings("1 --norc", env.get("GHOSTTY_BASH_INJECT").?); } @@ -400,9 +411,12 @@ test "bash: inject flags" { const command = try setupBash(alloc, .{ .shell = "bash --noprofile" }, ".", &env); - try testing.expectEqual(2, command.?.direct.len); + try testing.expect(command.?.direct.len >= 2); try testing.expectEqualStrings("bash", command.?.direct[0]); try testing.expectEqualStrings("--posix", command.?.direct[1]); + if (comptime builtin.target.os.tag.isDarwin()) { + try testing.expectEqualStrings("--login", command.?.direct[2]); + } try testing.expectEqualStrings("1 --noprofile", env.get("GHOSTTY_BASH_INJECT").?); } } @@ -419,18 +433,24 @@ test "bash: rcfile" { // bash --rcfile { const command = try setupBash(alloc, .{ .shell = "bash --rcfile profile.sh" }, ".", &env); - try testing.expectEqual(2, command.?.direct.len); + try testing.expect(command.?.direct.len >= 2); try testing.expectEqualStrings("bash", command.?.direct[0]); try testing.expectEqualStrings("--posix", command.?.direct[1]); + if (comptime builtin.target.os.tag.isDarwin()) { + try testing.expectEqualStrings("--login", command.?.direct[2]); + } try testing.expectEqualStrings("profile.sh", env.get("GHOSTTY_BASH_RCFILE").?); } // bash --init-file { const command = try setupBash(alloc, .{ .shell = "bash --init-file profile.sh" }, ".", &env); - try testing.expectEqual(2, command.?.direct.len); + try testing.expect(command.?.direct.len >= 2); try testing.expectEqualStrings("bash", command.?.direct[0]); try testing.expectEqualStrings("--posix", command.?.direct[1]); + if (comptime builtin.target.os.tag.isDarwin()) { + try testing.expectEqualStrings("--login", command.?.direct[2]); + } try testing.expectEqualStrings("profile.sh", env.get("GHOSTTY_BASH_RCFILE").?); } } @@ -476,25 +496,35 @@ test "bash: additional arguments" { // "-" argument separator { const command = try setupBash(alloc, .{ .shell = "bash - --arg file1 file2" }, ".", &env); - try testing.expectEqual(6, command.?.direct.len); + try testing.expect(command.?.direct.len >= 6); try testing.expectEqualStrings("bash", command.?.direct[0]); try testing.expectEqualStrings("--posix", command.?.direct[1]); - try testing.expectEqualStrings("-", command.?.direct[2]); - try testing.expectEqualStrings("--arg", command.?.direct[3]); - try testing.expectEqualStrings("file1", command.?.direct[4]); - try testing.expectEqualStrings("file2", command.?.direct[5]); + if (comptime builtin.target.os.tag.isDarwin()) { + try testing.expectEqualStrings("--login", command.?.direct[2]); + } + + const offset = if (comptime builtin.target.os.tag.isDarwin()) 3 else 2; + try testing.expectEqualStrings("-", command.?.direct[offset + 0]); + try testing.expectEqualStrings("--arg", command.?.direct[offset + 1]); + try testing.expectEqualStrings("file1", command.?.direct[offset + 2]); + try testing.expectEqualStrings("file2", command.?.direct[offset + 3]); } // "--" argument separator { const command = try setupBash(alloc, .{ .shell = "bash -- --arg file1 file2" }, ".", &env); - try testing.expectEqual(6, command.?.direct.len); + try testing.expect(command.?.direct.len >= 6); try testing.expectEqualStrings("bash", command.?.direct[0]); try testing.expectEqualStrings("--posix", command.?.direct[1]); - try testing.expectEqualStrings("--", command.?.direct[2]); - try testing.expectEqualStrings("--arg", command.?.direct[3]); - try testing.expectEqualStrings("file1", command.?.direct[4]); - try testing.expectEqualStrings("file2", command.?.direct[5]); + if (comptime builtin.target.os.tag.isDarwin()) { + try testing.expectEqualStrings("--login", command.?.direct[2]); + } + + const offset = if (comptime builtin.target.os.tag.isDarwin()) 3 else 2; + try testing.expectEqualStrings("--", command.?.direct[offset + 0]); + try testing.expectEqualStrings("--arg", command.?.direct[offset + 1]); + try testing.expectEqualStrings("file1", command.?.direct[offset + 2]); + try testing.expectEqualStrings("file2", command.?.direct[offset + 3]); } }