Merge remote-tracking branch 'upstream/main' into jacob/uucode
commit
563cfb94ba
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ jobs:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -279,7 +279,7 @@ jobs:
|
||||||
curl -sL https://sentry.io/get-cli/ | bash
|
curl -sL https://sentry.io/get-cli/ | bash
|
||||||
|
|
||||||
- name: Download macOS Artifacts
|
- name: Download macOS Artifacts
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: macos
|
name: macos
|
||||||
|
|
||||||
|
|
@ -302,7 +302,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Download macOS Artifacts
|
- name: Download macOS Artifacts
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: macos
|
name: macos
|
||||||
|
|
||||||
|
|
@ -350,17 +350,17 @@ jobs:
|
||||||
GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
|
GHOSTTY_VERSION: ${{ needs.setup.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Download macOS Artifacts
|
- name: Download macOS Artifacts
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: macos
|
name: macos
|
||||||
|
|
||||||
- name: Download Sparkle Artifacts
|
- name: Download Sparkle Artifacts
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: sparkle
|
name: sparkle
|
||||||
|
|
||||||
- name: Download Source Tarball Artifacts
|
- name: Download Source Tarball Artifacts
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: source-tarball
|
name: source-tarball
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -101,7 +101,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -137,7 +137,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -166,7 +166,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -199,7 +199,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -243,7 +243,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -406,7 +406,7 @@ jobs:
|
||||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||||
steps:
|
steps:
|
||||||
- name: Download Source Tarball Artifacts
|
- name: Download Source Tarball Artifacts
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: source-tarball
|
name: source-tarball
|
||||||
- name: Extract tarball
|
- name: Extract tarball
|
||||||
|
|
@ -414,7 +414,7 @@ jobs:
|
||||||
mkdir dist
|
mkdir dist
|
||||||
tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz
|
tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -509,7 +509,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -554,7 +554,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -603,7 +603,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -651,7 +651,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -706,7 +706,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -734,7 +734,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -761,7 +761,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -788,7 +788,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -815,7 +815,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -842,7 +842,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -876,7 +876,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -903,7 +903,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -938,7 +938,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
@ -969,7 +969,7 @@ jobs:
|
||||||
uses: namespacelabs/nscloud-setup-buildx-action@01628ae51ea5d6b0c90109c7dccbf511953aff29 # v0.0.18
|
uses: namespacelabs/nscloud-setup-buildx-action@01628ae51ea5d6b0c90109c7dccbf511953aff29 # v0.0.18
|
||||||
|
|
||||||
- name: Download Source Tarball Artifacts
|
- name: Download Source Tarball Artifacts
|
||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: source-tarball
|
name: source-tarball
|
||||||
|
|
||||||
|
|
@ -996,7 +996,7 @@ jobs:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Cache
|
- name: Setup Cache
|
||||||
uses: namespacelabs/nscloud-cache-action@9ff6d4004df1c3fd97cecafe010c874d77c48599 # v1.2.13
|
uses: namespacelabs/nscloud-cache-action@305bfa7ea980a858d511af4899414a84847c7991 # v1.2.16
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
/nix
|
/nix
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,5 @@ glad.zip
|
||||||
/Box_test.ppm
|
/Box_test.ppm
|
||||||
/Box_test_diff.ppm
|
/Box_test_diff.ppm
|
||||||
/ghostty.qcow2
|
/ghostty.qcow2
|
||||||
|
|
||||||
|
vgcore.*
|
||||||
|
|
|
||||||
16
build.zig
16
build.zig
|
|
@ -27,7 +27,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
|
|
||||||
// Ghostty resources like terminfo, shell integration, themes, etc.
|
// Ghostty resources like terminfo, shell integration, themes, etc.
|
||||||
const resources = try buildpkg.GhosttyResources.init(b, &config);
|
const resources = try buildpkg.GhosttyResources.init(b, &config);
|
||||||
const i18n = try buildpkg.GhosttyI18n.init(b, &config);
|
const i18n = if (config.i18n) try buildpkg.GhosttyI18n.init(b, &config) else null;
|
||||||
|
|
||||||
// Ghostty dependencies used by many artifacts.
|
// Ghostty dependencies used by many artifacts.
|
||||||
const deps = try buildpkg.SharedDeps.init(b, &config);
|
const deps = try buildpkg.SharedDeps.init(b, &config);
|
||||||
|
|
@ -79,7 +79,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
if (config.app_runtime != .none) {
|
if (config.app_runtime != .none) {
|
||||||
exe.install();
|
exe.install();
|
||||||
resources.install();
|
resources.install();
|
||||||
i18n.install();
|
if (i18n) |v| v.install();
|
||||||
} else {
|
} else {
|
||||||
// Libghostty
|
// Libghostty
|
||||||
//
|
//
|
||||||
|
|
@ -112,7 +112,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
// The xcframework build always installs resources because our
|
// The xcframework build always installs resources because our
|
||||||
// macOS xcode project contains references to them.
|
// macOS xcode project contains references to them.
|
||||||
resources.install();
|
resources.install();
|
||||||
i18n.install();
|
if (i18n) |v| v.install();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ghostty macOS app
|
// Ghostty macOS app
|
||||||
|
|
@ -122,7 +122,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
.{
|
.{
|
||||||
.xcframework = &xcframework,
|
.xcframework = &xcframework,
|
||||||
.docs = &docs,
|
.docs = &docs,
|
||||||
.i18n = &i18n,
|
.i18n = if (i18n) |v| &v else null,
|
||||||
.resources = &resources,
|
.resources = &resources,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -166,7 +166,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
.{
|
.{
|
||||||
.xcframework = &xcframework_native,
|
.xcframework = &xcframework_native,
|
||||||
.docs = &docs,
|
.docs = &docs,
|
||||||
.i18n = &i18n,
|
.i18n = if (i18n) |v| &v else null,
|
||||||
.resources = &resources,
|
.resources = &resources,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -204,5 +204,9 @@ pub fn build(b: *std.Build) !void {
|
||||||
|
|
||||||
// update-translations does what it sounds like and updates the "pot"
|
// update-translations does what it sounds like and updates the "pot"
|
||||||
// files. These should be committed to the repo.
|
// files. These should be committed to the repo.
|
||||||
translations_step.dependOn(i18n.update_step);
|
if (i18n) |v| {
|
||||||
|
translations_step.dependOn(v.update_step);
|
||||||
|
} else {
|
||||||
|
try translations_step.addError("cannot update translations when i18n is disabled", .{});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@
|
||||||
.gobject = .{
|
.gobject = .{
|
||||||
// https://github.com/jcollie/ghostty-gobject based on zig_gobject
|
// https://github.com/jcollie/ghostty-gobject based on zig_gobject
|
||||||
// Temporary until we generate them at build time automatically.
|
// Temporary until we generate them at build time automatically.
|
||||||
.url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-07-23-28-1/ghostty-gobject-0.14.1-2025-07-23-28-1.tar.zst",
|
.url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst",
|
||||||
.hash = "gobject-0.3.0-Skun7EXXnAB96BrWabxhzOw7HY-NzVexaPOIYw5t-dIE",
|
.hash = "gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM",
|
||||||
.lazy = true,
|
.lazy = true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -120,8 +120,8 @@
|
||||||
// Other
|
// Other
|
||||||
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
.apple_sdk = .{ .path = "./pkg/apple-sdk" },
|
||||||
.iterm2_themes = .{
|
.iterm2_themes = .{
|
||||||
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b2742b8baf86f556d6be4d9c6515bfd9d9c7a140.tar.gz",
|
.url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz",
|
||||||
.hash = "N-V-__8AAN83XASXgcKp4RDTj_WcQ19E5X24C3FjQoffeMFv",
|
.hash = "N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu",
|
||||||
.lazy = true,
|
.lazy = true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,10 @@
|
||||||
"url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
|
"url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
|
||||||
"hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U="
|
"hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U="
|
||||||
},
|
},
|
||||||
"gobject-0.3.0-Skun7EXXnAB96BrWabxhzOw7HY-NzVexaPOIYw5t-dIE": {
|
"gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM": {
|
||||||
"name": "gobject",
|
"name": "gobject",
|
||||||
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-07-23-28-1/ghostty-gobject-0.14.1-2025-07-23-28-1.tar.zst",
|
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst",
|
||||||
"hash": "sha256-ybeHo+NwcVZuyU037XB+/OofDoIoLsPnyNCG2jyiXC0="
|
"hash": "sha256-B0ziLzKud+kdKu5T1BTE9GMh8EPM/KhhhoNJlys5QPI="
|
||||||
},
|
},
|
||||||
"N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr": {
|
"N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr": {
|
||||||
"name": "gtk4_layer_shell",
|
"name": "gtk4_layer_shell",
|
||||||
|
|
@ -49,10 +49,10 @@
|
||||||
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
|
"url": "https://deps.files.ghostty.org/imgui-1220bc6b9daceaf7c8c60f3c3998058045ba0c5c5f48ae255ff97776d9cd8bfc6402.tar.gz",
|
||||||
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
|
"hash": "sha256-oF/QHgTPEat4Hig4fGIdLkIPHmBEyOJ6JeYD6pnveGA="
|
||||||
},
|
},
|
||||||
"N-V-__8AAN83XASXgcKp4RDTj_WcQ19E5X24C3FjQoffeMFv": {
|
"N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu": {
|
||||||
"name": "iterm2_themes",
|
"name": "iterm2_themes",
|
||||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b2742b8baf86f556d6be4d9c6515bfd9d9c7a140.tar.gz",
|
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz",
|
||||||
"hash": "sha256-w/biUQZ+AJv0atXypwQxJlKkHRUaFS0AlE/VlBJXlVU="
|
"hash": "sha256-gl42NOZ59ok+umHCHbdBQhWCgFVpj5PAZDVGhJRpbiA="
|
||||||
},
|
},
|
||||||
"N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": {
|
"N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": {
|
||||||
"name": "jetbrains_mono",
|
"name": "jetbrains_mono",
|
||||||
|
|
|
||||||
|
|
@ -122,11 +122,11 @@ in
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
name = "gobject-0.3.0-Skun7EXXnAB96BrWabxhzOw7HY-NzVexaPOIYw5t-dIE";
|
name = "gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM";
|
||||||
path = fetchZigArtifact {
|
path = fetchZigArtifact {
|
||||||
name = "gobject";
|
name = "gobject";
|
||||||
url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-07-23-28-1/ghostty-gobject-0.14.1-2025-07-23-28-1.tar.zst";
|
url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst";
|
||||||
hash = "sha256-ybeHo+NwcVZuyU037XB+/OofDoIoLsPnyNCG2jyiXC0=";
|
hash = "sha256-B0ziLzKud+kdKu5T1BTE9GMh8EPM/KhhhoNJlys5QPI=";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
@ -162,11 +162,11 @@ in
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
name = "N-V-__8AAN83XASXgcKp4RDTj_WcQ19E5X24C3FjQoffeMFv";
|
name = "N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu";
|
||||||
path = fetchZigArtifact {
|
path = fetchZigArtifact {
|
||||||
name = "iterm2_themes";
|
name = "iterm2_themes";
|
||||||
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b2742b8baf86f556d6be4d9c6515bfd9d9c7a140.tar.gz";
|
url = "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz";
|
||||||
hash = "sha256-w/biUQZ+AJv0atXypwQxJlKkHRUaFS0AlE/VlBJXlVU=";
|
hash = "sha256-gl42NOZ59ok+umHCHbdBQhWCgFVpj5PAZDVGhJRpbiA=";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d6
|
||||||
https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz
|
https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz
|
||||||
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
|
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
|
||||||
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
|
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
|
||||||
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-07-23-28-1/ghostty-gobject-0.14.1-2025-07-23-28-1.tar.zst
|
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst
|
||||||
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b2742b8baf86f556d6be4d9c6515bfd9d9c7a140.tar.gz
|
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz
|
||||||
https://github.com/mitchellh/libxev/archive/7f803181b158a10fec8619f793e3b4df515566cb.tar.gz
|
https://github.com/mitchellh/libxev/archive/7f803181b158a10fec8619f793e3b4df515566cb.tar.gz
|
||||||
https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz
|
https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz
|
||||||
https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz
|
https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-07-23-28-1/ghostty-gobject-0.14.1-2025-07-23-28-1.tar.zst",
|
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst",
|
||||||
"dest": "vendor/p/gobject-0.3.0-Skun7EXXnAB96BrWabxhzOw7HY-NzVexaPOIYw5t-dIE",
|
"dest": "vendor/p/gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM",
|
||||||
"sha256": "c9b787a3e37071566ec94d37ed707efcea1f0e82282ec3e7c8d086da3ca25c2d"
|
"sha256": "074ce22f32ae77e91d2aee53d414c4f46321f043ccfca861868349972b3940f2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
|
|
@ -61,9 +61,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/b2742b8baf86f556d6be4d9c6515bfd9d9c7a140.tar.gz",
|
"url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz",
|
||||||
"dest": "vendor/p/N-V-__8AAN83XASXgcKp4RDTj_WcQ19E5X24C3FjQoffeMFv",
|
"dest": "vendor/p/N-V-__8AABemXQQj_VhMpwuOSOiSzywW_yGD6aEL9YGI9uBu",
|
||||||
"sha256": "c3f6e251067e009bf46ad5f2a704312652a41d151a152d00944fd59412579555"
|
"sha256": "825e3634e679f6893eba61c21db7414215828055698f93c06435468494696e20"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ struct TerminalEntity: AppEntity {
|
||||||
@Property(title: "Kind")
|
@Property(title: "Kind")
|
||||||
var kind: Kind
|
var kind: Kind
|
||||||
|
|
||||||
var screenshot: Image?
|
var screenshot: NSImage?
|
||||||
|
|
||||||
static var typeDisplayRepresentation: TypeDisplayRepresentation {
|
static var typeDisplayRepresentation: TypeDisplayRepresentation {
|
||||||
TypeDisplayRepresentation(name: "Terminal")
|
TypeDisplayRepresentation(name: "Terminal")
|
||||||
|
|
@ -24,8 +24,7 @@ struct TerminalEntity: AppEntity {
|
||||||
var displayRepresentation: DisplayRepresentation {
|
var displayRepresentation: DisplayRepresentation {
|
||||||
var rep = DisplayRepresentation(title: "\(title)")
|
var rep = DisplayRepresentation(title: "\(title)")
|
||||||
if let screenshot,
|
if let screenshot,
|
||||||
let nsImage = ImageRenderer(content: screenshot).nsImage,
|
let data = screenshot.tiffRepresentation {
|
||||||
let data = nsImage.tiffRepresentation {
|
|
||||||
rep.image = .init(data: data)
|
rep.image = .init(data: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,11 +44,14 @@ struct TerminalEntity: AppEntity {
|
||||||
|
|
||||||
static var defaultQuery = TerminalQuery()
|
static var defaultQuery = TerminalQuery()
|
||||||
|
|
||||||
|
@MainActor
|
||||||
init(_ view: Ghostty.SurfaceView) {
|
init(_ view: Ghostty.SurfaceView) {
|
||||||
self.id = view.uuid
|
self.id = view.uuid
|
||||||
self.title = view.title
|
self.title = view.title
|
||||||
self.workingDirectory = view.pwd
|
self.workingDirectory = view.pwd
|
||||||
self.screenshot = view.screenshot()
|
if let nsImage = ImageRenderer(content: view.screenshot()).nsImage {
|
||||||
|
self.screenshot = nsImage
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the kind based on the window controller type
|
// Determine the kind based on the window controller type
|
||||||
if view.window?.windowController is QuickTerminalController {
|
if view.window?.windowController is QuickTerminalController {
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@
|
||||||
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
# Copyright (C) 2025 Mitchell Hashimoto, Ghostty contributors
|
||||||
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
# This file is distributed under the same license as the com.mitchellh.ghostty package.
|
||||||
# Bartosz Sokorski <b.sokorski@gmail.com>, 2025.
|
# Bartosz Sokorski <b.sokorski@gmail.com>, 2025.
|
||||||
|
# trag1c <dev@jakubr.me>, 2025.
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: com.mitchellh.ghostty\n"
|
"Project-Id-Version: com.mitchellh.ghostty\n"
|
||||||
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
"Report-Msgid-Bugs-To: m@mitchellh.com\n"
|
||||||
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
"POT-Creation-Date: 2025-07-22 17:18+0000\n"
|
||||||
"PO-Revision-Date: 2025-03-17 12:15+0100\n"
|
"PO-Revision-Date: 2025-08-05 16:27+0200\n"
|
||||||
"Last-Translator: Bartosz Sokorski <b.sokorski@gmail.com>\n"
|
"Last-Translator: trag1c <dev@jakubr.me>\n"
|
||||||
"Language-Team: Polish <translation-team-pl@lists.sourceforge.net>\n"
|
"Language-Team: Polish <translation-team-pl@lists.sourceforge.net>\n"
|
||||||
"Language: pl\n"
|
"Language: pl\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
|
|
@ -89,7 +90,7 @@ msgstr "Podziel w prawo"
|
||||||
|
|
||||||
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
#: src/apprt/gtk/ui/1.5/command-palette.blp:16
|
||||||
msgid "Execute a command…"
|
msgid "Execute a command…"
|
||||||
msgstr ""
|
msgstr "Wykonaj komendę…"
|
||||||
|
|
||||||
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6
|
||||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6
|
||||||
|
|
@ -162,7 +163,7 @@ msgstr "Otwórz konfigurację"
|
||||||
|
|
||||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85
|
||||||
msgid "Command Palette"
|
msgid "Command Palette"
|
||||||
msgstr ""
|
msgstr "Paleta komend"
|
||||||
|
|
||||||
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90
|
||||||
msgid "Terminal Inspector"
|
msgid "Terminal Inspector"
|
||||||
|
|
@ -210,12 +211,12 @@ msgstr "Zezwól"
|
||||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81
|
||||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77
|
||||||
msgid "Remember choice for this split"
|
msgid "Remember choice for this split"
|
||||||
msgstr ""
|
msgstr "Zapamiętaj wybór dla tego podziału"
|
||||||
|
|
||||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82
|
||||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78
|
||||||
msgid "Reload configuration to show this prompt again"
|
msgid "Reload configuration to show this prompt again"
|
||||||
msgstr ""
|
msgstr "Przeładuj konfigurację, by ponownie wyświetlić ten komunikat"
|
||||||
|
|
||||||
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7
|
||||||
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7
|
||||||
|
|
@ -280,15 +281,15 @@ msgstr "Skopiowano do schowka"
|
||||||
|
|
||||||
#: src/apprt/gtk/Surface.zig:1268
|
#: src/apprt/gtk/Surface.zig:1268
|
||||||
msgid "Cleared clipboard"
|
msgid "Cleared clipboard"
|
||||||
msgstr ""
|
msgstr "Wyczyszczono schowek"
|
||||||
|
|
||||||
#: src/apprt/gtk/Surface.zig:2525
|
#: src/apprt/gtk/Surface.zig:2525
|
||||||
msgid "Command succeeded"
|
msgid "Command succeeded"
|
||||||
msgstr ""
|
msgstr "Komenda wykonana pomyślnie"
|
||||||
|
|
||||||
#: src/apprt/gtk/Surface.zig:2527
|
#: src/apprt/gtk/Surface.zig:2527
|
||||||
msgid "Command failed"
|
msgid "Command failed"
|
||||||
msgstr ""
|
msgstr "Komenda nie powiodła się"
|
||||||
|
|
||||||
#: src/apprt/gtk/Window.zig:216
|
#: src/apprt/gtk/Window.zig:216
|
||||||
msgid "Main Menu"
|
msgid "Main Menu"
|
||||||
|
|
@ -300,7 +301,7 @@ msgstr "Zobacz otwarte karty"
|
||||||
|
|
||||||
#: src/apprt/gtk/Window.zig:266
|
#: src/apprt/gtk/Window.zig:266
|
||||||
msgid "New Split"
|
msgid "New Split"
|
||||||
msgstr ""
|
msgstr "Nowy podział"
|
||||||
|
|
||||||
#: src/apprt/gtk/Window.zig:329
|
#: src/apprt/gtk/Window.zig:329
|
||||||
msgid ""
|
msgid ""
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,13 @@ pub const blueprints: []const Blueprint = &.{
|
||||||
.{ .major = 1, .minor = 2, .name = "debug-warning" },
|
.{ .major = 1, .minor = 2, .name = "debug-warning" },
|
||||||
.{ .major = 1, .minor = 3, .name = "debug-warning" },
|
.{ .major = 1, .minor = 3, .name = "debug-warning" },
|
||||||
.{ .major = 1, .minor = 2, .name = "resize-overlay" },
|
.{ .major = 1, .minor = 2, .name = "resize-overlay" },
|
||||||
|
.{ .major = 1, .minor = 5, .name = "split-tree" },
|
||||||
|
.{ .major = 1, .minor = 5, .name = "split-tree-split" },
|
||||||
.{ .major = 1, .minor = 2, .name = "surface" },
|
.{ .major = 1, .minor = 2, .name = "surface" },
|
||||||
.{ .major = 1, .minor = 3, .name = "surface-child-exited" },
|
.{ .major = 1, .minor = 3, .name = "surface-child-exited" },
|
||||||
.{ .major = 1, .minor = 5, .name = "tab" },
|
.{ .major = 1, .minor = 5, .name = "tab" },
|
||||||
.{ .major = 1, .minor = 5, .name = "window" },
|
.{ .major = 1, .minor = 5, .name = "window" },
|
||||||
|
.{ .major = 1, .minor = 5, .name = "command-palette" },
|
||||||
};
|
};
|
||||||
|
|
||||||
/// CSS files in css_path
|
/// CSS files in css_path
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ const glib = @import("glib");
|
||||||
const gobject = @import("gobject");
|
const gobject = @import("gobject");
|
||||||
const gtk = @import("gtk");
|
const gtk = @import("gtk");
|
||||||
|
|
||||||
|
const ext = @import("ext.zig");
|
||||||
pub const Application = @import("class/application.zig").Application;
|
pub const Application = @import("class/application.zig").Application;
|
||||||
pub const Window = @import("class/window.zig").Window;
|
pub const Window = @import("class/window.zig").Window;
|
||||||
pub const Config = @import("class/config.zig").Config;
|
pub const Config = @import("class/config.zig").Config;
|
||||||
|
|
@ -29,6 +30,12 @@ pub fn Common(
|
||||||
return @ptrCast(@alignCast(gobject.Object.ref(self.as(gobject.Object))));
|
return @ptrCast(@alignCast(gobject.Object.ref(self.as(gobject.Object))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If the reference count is 1 and the object is floating, clear the
|
||||||
|
/// floating attribute. Otherwise, increase the reference count by 1.
|
||||||
|
pub fn refSink(self: *Self) *Self {
|
||||||
|
return @ptrCast(@alignCast(gobject.Object.refSink(self.as(gobject.Object))));
|
||||||
|
}
|
||||||
|
|
||||||
/// Decrease the reference count of the object.
|
/// Decrease the reference count of the object.
|
||||||
pub fn unref(self: *Self) void {
|
pub fn unref(self: *Self) void {
|
||||||
gobject.Object.unref(self.as(gobject.Object));
|
gobject.Object.unref(self.as(gobject.Object));
|
||||||
|
|
@ -73,7 +80,10 @@ pub fn Common(
|
||||||
fn set(self: *Self, value: *const gobject.Value) void {
|
fn set(self: *Self, value: *const gobject.Value) void {
|
||||||
const priv = private(self);
|
const priv = private(self);
|
||||||
if (@field(priv, name)) |v| {
|
if (@field(priv, name)) |v| {
|
||||||
glib.ext.destroy(v);
|
ext.boxedFree(
|
||||||
|
@typeInfo(@TypeOf(v)).pointer.child,
|
||||||
|
v,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const T = @TypeOf(@field(priv, name));
|
const T = @TypeOf(@field(priv, name));
|
||||||
|
|
@ -212,6 +222,11 @@ pub fn Common(
|
||||||
if (func_ti != .@"fn") {
|
if (func_ti != .@"fn") {
|
||||||
@compileError("bound function must be a function pointer");
|
@compileError("bound function must be a function pointer");
|
||||||
}
|
}
|
||||||
|
if (func_ti.@"fn".return_type == bool) {
|
||||||
|
// glib booleans are ints and returning a Zig bool type
|
||||||
|
// I think uses a byte and causes ABI issues.
|
||||||
|
@compileError("bound function must return c_int instead of bool");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk.Widget.Class.bindTemplateCallbackFull(
|
gtk.Widget.Class.bindTemplateCallbackFull(
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,17 @@ const apprt = @import("../../../apprt.zig");
|
||||||
const cgroup = @import("../cgroup.zig");
|
const cgroup = @import("../cgroup.zig");
|
||||||
const CoreApp = @import("../../../App.zig");
|
const CoreApp = @import("../../../App.zig");
|
||||||
const configpkg = @import("../../../config.zig");
|
const configpkg = @import("../../../config.zig");
|
||||||
|
const input = @import("../../../input.zig");
|
||||||
const internal_os = @import("../../../os/main.zig");
|
const internal_os = @import("../../../os/main.zig");
|
||||||
const systemd = @import("../../../os/systemd.zig");
|
const systemd = @import("../../../os/systemd.zig");
|
||||||
const terminal = @import("../../../terminal/main.zig");
|
const terminal = @import("../../../terminal/main.zig");
|
||||||
const xev = @import("../../../global.zig").xev;
|
const xev = @import("../../../global.zig").xev;
|
||||||
|
const Binding = @import("../../../input.zig").Binding;
|
||||||
const CoreConfig = configpkg.Config;
|
const CoreConfig = configpkg.Config;
|
||||||
const CoreSurface = @import("../../../Surface.zig");
|
const CoreSurface = @import("../../../Surface.zig");
|
||||||
|
|
||||||
const ext = @import("../ext.zig");
|
const ext = @import("../ext.zig");
|
||||||
|
const key = @import("../key.zig");
|
||||||
const adw_version = @import("../adw_version.zig");
|
const adw_version = @import("../adw_version.zig");
|
||||||
const gtk_version = @import("../gtk_version.zig");
|
const gtk_version = @import("../gtk_version.zig");
|
||||||
const winprotopkg = @import("../winproto.zig");
|
const winprotopkg = @import("../winproto.zig");
|
||||||
|
|
@ -31,9 +34,11 @@ const Common = @import("../class.zig").Common;
|
||||||
const WeakRef = @import("../weak_ref.zig").WeakRef;
|
const WeakRef = @import("../weak_ref.zig").WeakRef;
|
||||||
const Config = @import("config.zig").Config;
|
const Config = @import("config.zig").Config;
|
||||||
const Surface = @import("surface.zig").Surface;
|
const Surface = @import("surface.zig").Surface;
|
||||||
|
const SplitTree = @import("split_tree.zig").SplitTree;
|
||||||
const Window = @import("window.zig").Window;
|
const Window = @import("window.zig").Window;
|
||||||
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
||||||
const ConfigErrorsDialog = @import("config_errors_dialog.zig").ConfigErrorsDialog;
|
const ConfigErrorsDialog = @import("config_errors_dialog.zig").ConfigErrorsDialog;
|
||||||
|
const GlobalShortcuts = @import("global_shortcuts.zig").GlobalShortcuts;
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk_ghostty_application);
|
const log = std.log.scoped(.gtk_ghostty_application);
|
||||||
|
|
||||||
|
|
@ -75,8 +80,6 @@ pub const Application = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*Config,
|
?*Config,
|
||||||
.{
|
.{
|
||||||
.nick = "Config",
|
|
||||||
.blurb = "The current active configuration for the application.",
|
|
||||||
.accessor = gobject.ext.typedAccessor(
|
.accessor = gobject.ext.typedAccessor(
|
||||||
Self,
|
Self,
|
||||||
?*Config,
|
?*Config,
|
||||||
|
|
@ -105,6 +108,9 @@ pub const Application = extern struct {
|
||||||
/// State and logic for the underlying windowing protocol.
|
/// State and logic for the underlying windowing protocol.
|
||||||
winproto: winprotopkg.App,
|
winproto: winprotopkg.App,
|
||||||
|
|
||||||
|
/// The global shortcut logic.
|
||||||
|
global_shortcuts: *GlobalShortcuts,
|
||||||
|
|
||||||
/// The base path of the transient cgroup used to put all surfaces
|
/// The base path of the transient cgroup used to put all surfaces
|
||||||
/// into their own cgroup. This is only set if cgroups are enabled
|
/// into their own cgroup. This is only set if cgroups are enabled
|
||||||
/// and initialization was successful.
|
/// and initialization was successful.
|
||||||
|
|
@ -127,7 +133,7 @@ pub const Application = extern struct {
|
||||||
/// If non-null, we're currently showing a config errors dialog.
|
/// If non-null, we're currently showing a config errors dialog.
|
||||||
/// This is a WeakRef because the dialog can close on its own
|
/// This is a WeakRef because the dialog can close on its own
|
||||||
/// outside of our own lifecycle and that's okay.
|
/// outside of our own lifecycle and that's okay.
|
||||||
config_errors_dialog: WeakRef(ConfigErrorsDialog) = .{},
|
config_errors_dialog: WeakRef(ConfigErrorsDialog) = .empty,
|
||||||
|
|
||||||
/// glib source for our signal handler.
|
/// glib source for our signal handler.
|
||||||
signal_source: ?c_uint = null,
|
signal_source: ?c_uint = null,
|
||||||
|
|
@ -305,6 +311,7 @@ pub const Application = extern struct {
|
||||||
.winproto = wp,
|
.winproto = wp,
|
||||||
.css_provider = css_provider,
|
.css_provider = css_provider,
|
||||||
.custom_css_providers = .empty,
|
.custom_css_providers = .empty,
|
||||||
|
.global_shortcuts = gobject.ext.newInstance(GlobalShortcuts, .{}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
|
|
@ -332,6 +339,7 @@ pub const Application = extern struct {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
priv.config.unref();
|
priv.config.unref();
|
||||||
priv.winproto.deinit(alloc);
|
priv.winproto.deinit(alloc);
|
||||||
|
priv.global_shortcuts.unref();
|
||||||
if (priv.transient_cgroup_base) |base| alloc.free(base);
|
if (priv.transient_cgroup_base) |base| alloc.free(base);
|
||||||
if (gdk.Display.getDefault()) |display| {
|
if (gdk.Display.getDefault()) |display| {
|
||||||
gtk.StyleContext.removeProviderForDisplay(
|
gtk.StyleContext.removeProviderForDisplay(
|
||||||
|
|
@ -545,6 +553,10 @@ pub const Application = extern struct {
|
||||||
|
|
||||||
.desktop_notification => Action.desktopNotification(self, target, value),
|
.desktop_notification => Action.desktopNotification(self, target, value),
|
||||||
|
|
||||||
|
.equalize_splits => return Action.equalizeSplits(target),
|
||||||
|
|
||||||
|
.goto_split => return Action.gotoSplit(target, value),
|
||||||
|
|
||||||
.goto_tab => return Action.gotoTab(target, value),
|
.goto_tab => return Action.gotoTab(target, value),
|
||||||
|
|
||||||
.initial_size => return Action.initialSize(target, value),
|
.initial_size => return Action.initialSize(target, value),
|
||||||
|
|
@ -555,6 +567,8 @@ pub const Application = extern struct {
|
||||||
|
|
||||||
.move_tab => return Action.moveTab(target, value),
|
.move_tab => return Action.moveTab(target, value),
|
||||||
|
|
||||||
|
.new_split => return Action.newSplit(target, value),
|
||||||
|
|
||||||
.new_tab => return Action.newTab(target),
|
.new_tab => return Action.newTab(target),
|
||||||
|
|
||||||
.new_window => try Action.newWindow(
|
.new_window => try Action.newWindow(
|
||||||
|
|
@ -598,16 +612,13 @@ pub const Application = extern struct {
|
||||||
.toggle_quick_terminal => return Action.toggleQuickTerminal(self),
|
.toggle_quick_terminal => return Action.toggleQuickTerminal(self),
|
||||||
.toggle_tab_overview => return Action.toggleTabOverview(target),
|
.toggle_tab_overview => return Action.toggleTabOverview(target),
|
||||||
.toggle_window_decorations => return Action.toggleWindowDecorations(target),
|
.toggle_window_decorations => return Action.toggleWindowDecorations(target),
|
||||||
|
.toggle_command_palette => return Action.toggleCommandPalette(target),
|
||||||
|
|
||||||
// Unimplemented but todo on gtk-ng branch
|
// Unimplemented but todo on gtk-ng branch
|
||||||
.prompt_title,
|
.prompt_title,
|
||||||
.toggle_command_palette,
|
|
||||||
.inspector,
|
.inspector,
|
||||||
// TODO: splits
|
// TODO: splits
|
||||||
.new_split,
|
|
||||||
.resize_split,
|
.resize_split,
|
||||||
.equalize_splits,
|
|
||||||
.goto_split,
|
|
||||||
.toggle_split_zoom,
|
.toggle_split_zoom,
|
||||||
=> {
|
=> {
|
||||||
log.warn("unimplemented action={}", .{action});
|
log.warn("unimplemented action={}", .{action});
|
||||||
|
|
@ -735,7 +746,7 @@ pub const Application = extern struct {
|
||||||
|
|
||||||
if (config.@"split-divider-color") |color| {
|
if (config.@"split-divider-color") |color| {
|
||||||
try writer.print(
|
try writer.print(
|
||||||
\\.terminal-window .notebook separator {{
|
\\.window .split paned > separator {{
|
||||||
\\ color: rgb({[r]d},{[g]d},{[b]d});
|
\\ color: rgb({[r]d},{[g]d},{[b]d});
|
||||||
\\ background: rgb({[r]d},{[g]d},{[b]d});
|
\\ background: rgb({[r]d},{[g]d},{[b]d});
|
||||||
\\}}
|
\\}}
|
||||||
|
|
@ -854,6 +865,65 @@ pub const Application = extern struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn syncActionAccelerators(self: *Self) void {
|
||||||
|
self.syncActionAccelerator("app.quit", .{ .quit = {} });
|
||||||
|
self.syncActionAccelerator("app.open-config", .{ .open_config = {} });
|
||||||
|
self.syncActionAccelerator("app.reload-config", .{ .reload_config = {} });
|
||||||
|
self.syncActionAccelerator("win.toggle-inspector", .{ .inspector = .toggle });
|
||||||
|
self.syncActionAccelerator("app.show-gtk-inspector", .show_gtk_inspector);
|
||||||
|
self.syncActionAccelerator("win.toggle-command-palette", .toggle_command_palette);
|
||||||
|
self.syncActionAccelerator("win.close", .{ .close_window = {} });
|
||||||
|
self.syncActionAccelerator("win.new-window", .{ .new_window = {} });
|
||||||
|
self.syncActionAccelerator("win.new-tab", .{ .new_tab = {} });
|
||||||
|
self.syncActionAccelerator("win.close-tab", .{ .close_tab = {} });
|
||||||
|
self.syncActionAccelerator("win.split-right", .{ .new_split = .right });
|
||||||
|
self.syncActionAccelerator("win.split-down", .{ .new_split = .down });
|
||||||
|
self.syncActionAccelerator("win.split-left", .{ .new_split = .left });
|
||||||
|
self.syncActionAccelerator("win.split-up", .{ .new_split = .up });
|
||||||
|
self.syncActionAccelerator("win.copy", .{ .copy_to_clipboard = {} });
|
||||||
|
self.syncActionAccelerator("win.paste", .{ .paste_from_clipboard = {} });
|
||||||
|
self.syncActionAccelerator("win.reset", .{ .reset = {} });
|
||||||
|
self.syncActionAccelerator("win.clear", .{ .clear_screen = {} });
|
||||||
|
self.syncActionAccelerator("win.prompt-title", .{ .prompt_surface_title = {} });
|
||||||
|
self.syncActionAccelerator("split-tree.new-left", .{ .new_split = .left });
|
||||||
|
self.syncActionAccelerator("split-tree.new-right", .{ .new_split = .right });
|
||||||
|
self.syncActionAccelerator("split-tree.new-up", .{ .new_split = .up });
|
||||||
|
self.syncActionAccelerator("split-tree.new-down", .{ .new_split = .down });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syncActionAccelerator(
|
||||||
|
self: *Self,
|
||||||
|
gtk_action: [:0]const u8,
|
||||||
|
action: input.Binding.Action,
|
||||||
|
) void {
|
||||||
|
const gtk_app = self.as(gtk.Application);
|
||||||
|
|
||||||
|
// Reset it initially
|
||||||
|
const zero = [_:null]?[*:0]const u8{};
|
||||||
|
gtk_app.setAccelsForAction(gtk_action, &zero);
|
||||||
|
|
||||||
|
const config = self.private().config.get();
|
||||||
|
const trigger = config.keybind.set.getTrigger(action) orelse return;
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
const accel = if (key.accelFromTrigger(
|
||||||
|
&buf,
|
||||||
|
trigger,
|
||||||
|
)) |accel_|
|
||||||
|
accel_ orelse return
|
||||||
|
else |err| switch (err) {
|
||||||
|
// This should really never, never happen. Its not critical enough
|
||||||
|
// to actually crash, but this is a bug somewhere. An accelerator
|
||||||
|
// for a trigger can't possibly be more than 1024 bytes.
|
||||||
|
error.NoSpaceLeft => {
|
||||||
|
log.warn("accelerator somehow longer than 1024 bytes: {}", .{trigger});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const accels = [_:null]?[*:0]const u8{accel};
|
||||||
|
|
||||||
|
gtk_app.setAccelsForAction(gtk_action, &accels);
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Properties
|
// Properties
|
||||||
|
|
||||||
|
|
@ -884,6 +954,9 @@ pub const Application = extern struct {
|
||||||
_: *gobject.ParamSpec,
|
_: *gobject.ParamSpec,
|
||||||
self: *Self,
|
self: *Self,
|
||||||
) callconv(.c) void {
|
) callconv(.c) void {
|
||||||
|
// Sync our accelerators for menu items.
|
||||||
|
self.syncActionAccelerators();
|
||||||
|
|
||||||
// Load our runtime and custom CSS. If this fails then our window is
|
// Load our runtime and custom CSS. If this fails then our window is
|
||||||
// just stuck with the old CSS but we don't want to fail the entire
|
// just stuck with the old CSS but we don't want to fail the entire
|
||||||
// config change operation.
|
// config change operation.
|
||||||
|
|
@ -912,7 +985,7 @@ pub const Application = extern struct {
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Virtual Methods
|
// Virtual Methods
|
||||||
|
|
||||||
fn startup(self: *Self) callconv(.C) void {
|
fn startup(self: *Self) callconv(.c) void {
|
||||||
log.debug("startup", .{});
|
log.debug("startup", .{});
|
||||||
|
|
||||||
gio.Application.virtual_methods.startup.call(
|
gio.Application.virtual_methods.startup.call(
|
||||||
|
|
@ -935,6 +1008,9 @@ pub const Application = extern struct {
|
||||||
// Setup our action map
|
// Setup our action map
|
||||||
self.startupActionMap();
|
self.startupActionMap();
|
||||||
|
|
||||||
|
// Setup our global shortcuts
|
||||||
|
self.startupGlobalShortcuts();
|
||||||
|
|
||||||
// Setup our cgroup for the application.
|
// Setup our cgroup for the application.
|
||||||
self.startupCgroup() catch |err| {
|
self.startupCgroup() catch |err| {
|
||||||
log.warn("cgroup initialization failed err={}", .{err});
|
log.warn("cgroup initialization failed err={}", .{err});
|
||||||
|
|
@ -1073,6 +1149,34 @@ pub const Application = extern struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Setup our global shortcuts.
|
||||||
|
fn startupGlobalShortcuts(self: *Self) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
// On startup, our dbus connection should be available.
|
||||||
|
priv.global_shortcuts.setDbusConnection(
|
||||||
|
self.as(gio.Application).getDbusConnection(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setup a binding so that the shortcut config always matches the app.
|
||||||
|
_ = gobject.Object.bindProperty(
|
||||||
|
self.as(gobject.Object),
|
||||||
|
"config",
|
||||||
|
priv.global_shortcuts.as(gobject.Object),
|
||||||
|
"config",
|
||||||
|
.{ .sync_create = true },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setup the signal handler for global shortcut triggers
|
||||||
|
_ = GlobalShortcuts.signals.trigger.connect(
|
||||||
|
priv.global_shortcuts,
|
||||||
|
*Application,
|
||||||
|
globalShortcutTrigger,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const CgroupError = error{
|
const CgroupError = error{
|
||||||
DbusConnectionFailed,
|
DbusConnectionFailed,
|
||||||
CgroupInitFailed,
|
CgroupInitFailed,
|
||||||
|
|
@ -1139,7 +1243,7 @@ pub const Application = extern struct {
|
||||||
priv.transient_cgroup_base = path;
|
priv.transient_cgroup_base = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(self: *Self) callconv(.C) void {
|
fn activate(self: *Self) callconv(.c) void {
|
||||||
log.debug("activate", .{});
|
log.debug("activate", .{});
|
||||||
|
|
||||||
// Queue a new window
|
// Queue a new window
|
||||||
|
|
@ -1155,12 +1259,13 @@ pub const Application = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispose(self: *Self) callconv(.C) void {
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
if (priv.config_errors_dialog.get()) |diag| {
|
if (priv.config_errors_dialog.get()) |diag| {
|
||||||
diag.close();
|
diag.close();
|
||||||
diag.unref(); // strong ref from get()
|
diag.unref(); // strong ref from get()
|
||||||
}
|
}
|
||||||
|
priv.config_errors_dialog.set(null);
|
||||||
if (priv.signal_source) |v| {
|
if (priv.signal_source) |v| {
|
||||||
if (glib.Source.remove(v) == 0) {
|
if (glib.Source.remove(v) == 0) {
|
||||||
log.warn("unable to remove signal source", .{});
|
log.warn("unable to remove signal source", .{});
|
||||||
|
|
@ -1174,7 +1279,7 @@ pub const Application = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(self: *Self) callconv(.C) void {
|
fn finalize(self: *Self) callconv(.c) void {
|
||||||
self.deinit();
|
self.deinit();
|
||||||
gobject.Object.virtual_methods.finalize.call(
|
gobject.Object.virtual_methods.finalize.call(
|
||||||
Class.parent,
|
Class.parent,
|
||||||
|
|
@ -1303,6 +1408,16 @@ pub const Application = extern struct {
|
||||||
dialog.present(null);
|
dialog.present(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn globalShortcutTrigger(
|
||||||
|
_: *GlobalShortcuts,
|
||||||
|
action: *const Binding.Action,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
self.core().performAllAction(self.rt(), action.*) catch |err| {
|
||||||
|
log.warn("failed to perform action={}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn actionReloadConfig(
|
fn actionReloadConfig(
|
||||||
_: *gio.SimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: ?*glib.Variant,
|
_: ?*glib.Variant,
|
||||||
|
|
@ -1437,7 +1552,7 @@ pub const Application = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
// Register our compiled resources exactly once.
|
// Register our compiled resources exactly once.
|
||||||
{
|
{
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
|
|
@ -1538,6 +1653,52 @@ const Action = struct {
|
||||||
gio_app.sendNotification(n.body, notification);
|
gio_app.sendNotification(n.body, notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn equalizeSplits(target: apprt.Target) bool {
|
||||||
|
switch (target) {
|
||||||
|
.app => {
|
||||||
|
log.warn("equalize splits to app is unexpected", .{});
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
.surface => |core| {
|
||||||
|
const surface = core.rt_surface.surface;
|
||||||
|
return surface.as(gtk.Widget).activateAction("split-tree.equalize", null) != 0;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gotoSplit(
|
||||||
|
target: apprt.Target,
|
||||||
|
to: apprt.action.GotoSplit,
|
||||||
|
) bool {
|
||||||
|
switch (target) {
|
||||||
|
.app => return false,
|
||||||
|
.surface => |core| {
|
||||||
|
// Design note: we can't use widget actions here because
|
||||||
|
// we need to know whether there is a goto target for returning
|
||||||
|
// the proper perform result (boolean).
|
||||||
|
|
||||||
|
const surface = core.rt_surface.surface;
|
||||||
|
const tree = ext.getAncestor(
|
||||||
|
SplitTree,
|
||||||
|
surface.as(gtk.Widget),
|
||||||
|
) orelse {
|
||||||
|
log.warn("surface is not in a split tree, ignoring goto_split", .{});
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return tree.goto(switch (to) {
|
||||||
|
.previous => .previous_wrapped,
|
||||||
|
.next => .next_wrapped,
|
||||||
|
.up => .{ .spatial = .up },
|
||||||
|
.down => .{ .spatial = .down },
|
||||||
|
.left => .{ .spatial = .left },
|
||||||
|
.right => .{ .spatial = .right },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn gotoTab(
|
pub fn gotoTab(
|
||||||
target: apprt.Target,
|
target: apprt.Target,
|
||||||
tab: apprt.action.GotoTab,
|
tab: apprt.action.GotoTab,
|
||||||
|
|
@ -1587,16 +1748,9 @@ const Action = struct {
|
||||||
) void {
|
) void {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
.app => log.warn("mouse over link to app is unexpected", .{}),
|
.app => log.warn("mouse over link to app is unexpected", .{}),
|
||||||
.surface => |surface| {
|
.surface => |surface| surface.rt_surface.gobj().setMouseHoverUrl(
|
||||||
var v = gobject.ext.Value.new([:0]const u8);
|
if (value.url.len > 0) value.url else null,
|
||||||
if (value.url.len > 0) gobject.ext.Value.set(&v, value.url);
|
),
|
||||||
defer v.unset();
|
|
||||||
gobject.Object.setProperty(
|
|
||||||
surface.rt_surface.gobj().as(gobject.Object),
|
|
||||||
"mouse-hover-url",
|
|
||||||
&v,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1606,15 +1760,7 @@ const Action = struct {
|
||||||
) void {
|
) void {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
.app => log.warn("mouse shape to app is unexpected", .{}),
|
.app => log.warn("mouse shape to app is unexpected", .{}),
|
||||||
.surface => |surface| {
|
.surface => |surface| surface.rt_surface.gobj().setMouseShape(shape),
|
||||||
var value = gobject.ext.Value.newFrom(shape);
|
|
||||||
defer value.unset();
|
|
||||||
gobject.Object.setProperty(
|
|
||||||
surface.rt_surface.gobj().as(gobject.Object),
|
|
||||||
"mouse-shape",
|
|
||||||
&value,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1624,18 +1770,10 @@ const Action = struct {
|
||||||
) void {
|
) void {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
.app => log.warn("mouse visibility to app is unexpected", .{}),
|
.app => log.warn("mouse visibility to app is unexpected", .{}),
|
||||||
.surface => |surface| {
|
.surface => |surface| surface.rt_surface.gobj().setMouseHidden(switch (visibility) {
|
||||||
var value = gobject.ext.Value.newFrom(switch (visibility) {
|
.visible => false,
|
||||||
.visible => false,
|
.hidden => true,
|
||||||
.hidden => true,
|
}),
|
||||||
});
|
|
||||||
defer value.unset();
|
|
||||||
gobject.Object.setProperty(
|
|
||||||
surface.rt_surface.gobj().as(gobject.Object),
|
|
||||||
"mouse-hidden",
|
|
||||||
&value,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1663,6 +1801,28 @@ const Action = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn newSplit(
|
||||||
|
target: apprt.Target,
|
||||||
|
direction: apprt.action.SplitDirection,
|
||||||
|
) bool {
|
||||||
|
switch (target) {
|
||||||
|
.app => {
|
||||||
|
log.warn("new split to app is unexpected", .{});
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
.surface => |core| {
|
||||||
|
const surface = core.rt_surface.surface;
|
||||||
|
return surface.as(gtk.Widget).activateAction(switch (direction) {
|
||||||
|
.right => "split-tree.new-right",
|
||||||
|
.left => "split-tree.new-left",
|
||||||
|
.down => "split-tree.new-down",
|
||||||
|
.up => "split-tree.new-up",
|
||||||
|
}, null) != 0;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn newTab(target: apprt.Target) bool {
|
pub fn newTab(target: apprt.Target) bool {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
.app => {
|
.app => {
|
||||||
|
|
@ -1756,15 +1916,7 @@ const Action = struct {
|
||||||
) void {
|
) void {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
.app => log.warn("pwd to app is unexpected", .{}),
|
.app => log.warn("pwd to app is unexpected", .{}),
|
||||||
.surface => |surface| {
|
.surface => |surface| surface.rt_surface.gobj().setPwd(value.pwd),
|
||||||
var v = gobject.ext.Value.newFrom(value.pwd);
|
|
||||||
defer v.unset();
|
|
||||||
gobject.Object.setProperty(
|
|
||||||
surface.rt_surface.gobj().as(gobject.Object),
|
|
||||||
"pwd",
|
|
||||||
&v,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1864,15 +2016,7 @@ const Action = struct {
|
||||||
) void {
|
) void {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
.app => log.warn("set_title to app is unexpected", .{}),
|
.app => log.warn("set_title to app is unexpected", .{}),
|
||||||
.surface => |surface| {
|
.surface => |surface| surface.rt_surface.gobj().setTitle(value.title),
|
||||||
var v = gobject.ext.Value.newFrom(value.title);
|
|
||||||
defer v.unset();
|
|
||||||
gobject.Object.setProperty(
|
|
||||||
surface.rt_surface.gobj().as(gobject.Object),
|
|
||||||
"title",
|
|
||||||
&v,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2003,6 +2147,15 @@ const Action = struct {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggleCommandPalette(target: apprt.Target) bool {
|
||||||
|
switch (target) {
|
||||||
|
.app => return false,
|
||||||
|
.surface => |surface| {
|
||||||
|
return surface.rt_surface.gobj().toggleCommandPalette();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This sets various GTK-related environment variables as necessary
|
/// This sets various GTK-related environment variables as necessary
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,6 @@ pub const ClipboardConfirmationDialog = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Can Remember",
|
|
||||||
.blurb = "Allow remembering the choice.",
|
|
||||||
.default = false,
|
.default = false,
|
||||||
.accessor = gobject.ext.privateFieldAccessor(
|
.accessor = gobject.ext.privateFieldAccessor(
|
||||||
Self,
|
Self,
|
||||||
|
|
@ -57,8 +55,6 @@ pub const ClipboardConfirmationDialog = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*apprt.ClipboardRequest,
|
?*apprt.ClipboardRequest,
|
||||||
.{
|
.{
|
||||||
.nick = "Request",
|
|
||||||
.blurb = "The clipboard request.",
|
|
||||||
.accessor = C.privateBoxedFieldAccessor("request"),
|
.accessor = C.privateBoxedFieldAccessor("request"),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -71,8 +67,6 @@ pub const ClipboardConfirmationDialog = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*gtk.TextBuffer,
|
?*gtk.TextBuffer,
|
||||||
.{
|
.{
|
||||||
.nick = "Clipboard Contents",
|
|
||||||
.blurb = "The clipboard contents being read/written.",
|
|
||||||
.accessor = C.privateObjFieldAccessor("clipboard_contents"),
|
.accessor = C.privateObjFieldAccessor("clipboard_contents"),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -85,8 +79,6 @@ pub const ClipboardConfirmationDialog = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Blur",
|
|
||||||
.blurb = "Blur the contents, allowing the user to reveal.",
|
|
||||||
.default = false,
|
.default = false,
|
||||||
.accessor = gobject.ext.privateFieldAccessor(
|
.accessor = gobject.ext.privateFieldAccessor(
|
||||||
Self,
|
Self,
|
||||||
|
|
@ -150,7 +142,7 @@ pub const ClipboardConfirmationDialog = extern struct {
|
||||||
return gobject.ext.newInstance(Self, .{});
|
return gobject.ext.newInstance(Self, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
|
|
||||||
// Trigger initial values
|
// Trigger initial values
|
||||||
|
|
@ -239,7 +231,7 @@ pub const ClipboardConfirmationDialog = extern struct {
|
||||||
fn response(
|
fn response(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
response_id: [*:0]const u8,
|
response_id: [*:0]const u8,
|
||||||
) callconv(.C) void {
|
) callconv(.c) void {
|
||||||
const remember: bool = if (comptime can_remember) remember: {
|
const remember: bool = if (comptime can_remember) remember: {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
break :remember priv.remember_choice.getActive() != 0;
|
break :remember priv.remember_choice.getActive() != 0;
|
||||||
|
|
@ -262,7 +254,7 @@ pub const ClipboardConfirmationDialog = extern struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispose(self: *Self) callconv(.C) void {
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
if (priv.clipboard_contents) |v| {
|
if (priv.clipboard_contents) |v| {
|
||||||
v.unref();
|
v.unref();
|
||||||
|
|
@ -280,7 +272,7 @@ pub const ClipboardConfirmationDialog = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(self: *Self) callconv(.C) void {
|
fn finalize(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
if (priv.request) |v| {
|
if (priv.request) |v| {
|
||||||
glib.ext.destroy(v);
|
glib.ext.destroy(v);
|
||||||
|
|
@ -304,7 +296,7 @@ pub const ClipboardConfirmationDialog = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
gtk.Widget.Class.setTemplateFromResource(
|
gtk.Widget.Class.setTemplateFromResource(
|
||||||
class.as(gtk.Widget.Class),
|
class.as(gtk.Widget.Class),
|
||||||
if (comptime adw_version.atLeast(1, 4, 0))
|
if (comptime adw_version.atLeast(1, 4, 0))
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,6 @@ pub const CloseConfirmationDialog = extern struct {
|
||||||
Self,
|
Self,
|
||||||
Target,
|
Target,
|
||||||
.{
|
.{
|
||||||
.nick = "Target",
|
|
||||||
.blurb = "The target for this close confirmation.",
|
|
||||||
.default = .app,
|
.default = .app,
|
||||||
.accessor = gobject.ext.privateFieldAccessor(
|
.accessor = gobject.ext.privateFieldAccessor(
|
||||||
Self,
|
Self,
|
||||||
|
|
@ -81,7 +79,7 @@ pub const CloseConfirmationDialog = extern struct {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,7 +100,7 @@ pub const CloseConfirmationDialog = extern struct {
|
||||||
fn response(
|
fn response(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
response_id: [*:0]const u8,
|
response_id: [*:0]const u8,
|
||||||
) callconv(.C) void {
|
) callconv(.c) void {
|
||||||
if (std.mem.orderZ(u8, response_id, "close") == .eq) {
|
if (std.mem.orderZ(u8, response_id, "close") == .eq) {
|
||||||
signals.@"close-request".impl.emit(
|
signals.@"close-request".impl.emit(
|
||||||
self,
|
self,
|
||||||
|
|
@ -120,7 +118,7 @@ pub const CloseConfirmationDialog = extern struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispose(self: *Self) callconv(.C) void {
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
gtk.Widget.disposeTemplate(
|
gtk.Widget.disposeTemplate(
|
||||||
self.as(gtk.Widget),
|
self.as(gtk.Widget),
|
||||||
getGObjectType(),
|
getGObjectType(),
|
||||||
|
|
@ -135,6 +133,7 @@ pub const CloseConfirmationDialog = extern struct {
|
||||||
const C = Common(Self, Private);
|
const C = Common(Self, Private);
|
||||||
pub const as = C.as;
|
pub const as = C.as;
|
||||||
pub const ref = C.ref;
|
pub const ref = C.ref;
|
||||||
|
pub const refSink = C.refSink;
|
||||||
pub const unref = C.unref;
|
pub const unref = C.unref;
|
||||||
const private = C.private;
|
const private = C.private;
|
||||||
|
|
||||||
|
|
@ -143,7 +142,7 @@ pub const CloseConfirmationDialog = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
gobject.ext.ensureType(Dialog);
|
gobject.ext.ensureType(Dialog);
|
||||||
gtk.Widget.Class.setTemplateFromResource(
|
gtk.Widget.Class.setTemplateFromResource(
|
||||||
class.as(gtk.Widget.Class),
|
class.as(gtk.Widget.Class),
|
||||||
|
|
@ -181,12 +180,14 @@ pub const Target = enum(c_int) {
|
||||||
app,
|
app,
|
||||||
tab,
|
tab,
|
||||||
window,
|
window,
|
||||||
|
surface,
|
||||||
|
|
||||||
pub fn title(self: Target) [*:0]const u8 {
|
pub fn title(self: Target) [*:0]const u8 {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.app => i18n._("Quit Ghostty?"),
|
.app => i18n._("Quit Ghostty?"),
|
||||||
.tab => i18n._("Close Tab?"),
|
.tab => i18n._("Close Tab?"),
|
||||||
.window => i18n._("Close Window?"),
|
.window => i18n._("Close Window?"),
|
||||||
|
.surface => i18n._("Close Split?"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,6 +196,7 @@ pub const Target = enum(c_int) {
|
||||||
.app => i18n._("All terminal sessions will be terminated."),
|
.app => i18n._("All terminal sessions will be terminated."),
|
||||||
.tab => i18n._("All terminal sessions in this tab will be terminated."),
|
.tab => i18n._("All terminal sessions in this tab will be terminated."),
|
||||||
.window => i18n._("All terminal sessions in this window will be terminated."),
|
.window => i18n._("All terminal sessions in this window will be terminated."),
|
||||||
|
.surface => i18n._("The currently running process in this split will be terminated."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,568 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
|
const adw = @import("adw");
|
||||||
|
const gio = @import("gio");
|
||||||
|
const gobject = @import("gobject");
|
||||||
|
const gtk = @import("gtk");
|
||||||
|
|
||||||
|
const input = @import("../../../input.zig");
|
||||||
|
const gresource = @import("../build/gresource.zig");
|
||||||
|
const key = @import("../key.zig");
|
||||||
|
const Common = @import("../class.zig").Common;
|
||||||
|
const Application = @import("application.zig").Application;
|
||||||
|
const Window = @import("window.zig").Window;
|
||||||
|
const Config = @import("config.zig").Config;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.gtk_ghostty_command_palette);
|
||||||
|
|
||||||
|
pub const CommandPalette = extern struct {
|
||||||
|
const Self = @This();
|
||||||
|
parent_instance: Parent,
|
||||||
|
pub const Parent = adw.Bin;
|
||||||
|
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||||
|
.name = "GhosttyCommandPalette",
|
||||||
|
.instanceInit = &init,
|
||||||
|
.classInit = &Class.init,
|
||||||
|
.parent_class = &Class.parent,
|
||||||
|
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const properties = struct {
|
||||||
|
pub const config = struct {
|
||||||
|
pub const name = "config";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?*Config,
|
||||||
|
.{
|
||||||
|
.accessor = C.privateObjFieldAccessor("config"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const signals = struct {
|
||||||
|
/// Emitted when a command from the command palette is activated. The
|
||||||
|
/// action contains pointers to allocated data so if a receiver of this
|
||||||
|
/// signal needs to keep the action around it will need to clone the
|
||||||
|
/// action or there may be use-after-free errors.
|
||||||
|
pub const trigger = struct {
|
||||||
|
pub const name = "trigger";
|
||||||
|
pub const connect = impl.connect;
|
||||||
|
const impl = gobject.ext.defineSignal(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
&.{*const input.Binding.Action},
|
||||||
|
void,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Private = struct {
|
||||||
|
/// The configuration that this command palette is using.
|
||||||
|
config: ?*Config = null,
|
||||||
|
|
||||||
|
/// The dialog object containing the palette UI.
|
||||||
|
dialog: *adw.Dialog,
|
||||||
|
|
||||||
|
/// The search input text field.
|
||||||
|
search: *gtk.SearchEntry,
|
||||||
|
|
||||||
|
/// The view containing each result row.
|
||||||
|
view: *gtk.ListView,
|
||||||
|
|
||||||
|
/// The model that provides filtered data for the view to display.
|
||||||
|
model: *gtk.SingleSelection,
|
||||||
|
|
||||||
|
/// The list that serves as the data source of the model.
|
||||||
|
/// This is where all command data is ultimately stored.
|
||||||
|
source: *gio.ListStore,
|
||||||
|
|
||||||
|
pub var offset: c_int = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create a new instance of the command palette. The caller will own a
|
||||||
|
/// reference to the object.
|
||||||
|
pub fn new() *Self {
|
||||||
|
const self = gobject.ext.newInstance(Self, .{});
|
||||||
|
|
||||||
|
// Sink ourselves so that we aren't floating anymore. We'll unref
|
||||||
|
// ourselves when the palette is closed or an action is activated.
|
||||||
|
_ = self.refSink();
|
||||||
|
|
||||||
|
// Bump the ref so that the caller has a reference.
|
||||||
|
return self.ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Virtual Methods
|
||||||
|
|
||||||
|
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||||
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
|
|
||||||
|
// Listen for any changes to our config.
|
||||||
|
_ = gobject.Object.signals.notify.connect(
|
||||||
|
self,
|
||||||
|
?*anyopaque,
|
||||||
|
propConfig,
|
||||||
|
null,
|
||||||
|
.{
|
||||||
|
.detail = "config",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
priv.source.removeAll();
|
||||||
|
|
||||||
|
if (priv.config) |config| {
|
||||||
|
config.unref();
|
||||||
|
priv.config = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk.Widget.disposeTemplate(
|
||||||
|
self.as(gtk.Widget),
|
||||||
|
getGObjectType(),
|
||||||
|
);
|
||||||
|
|
||||||
|
gobject.Object.virtual_methods.dispose.call(
|
||||||
|
Class.parent,
|
||||||
|
self.as(Parent),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Signal Handlers
|
||||||
|
|
||||||
|
fn propConfig(self: *CommandPalette, _: *gobject.ParamSpec, _: ?*anyopaque) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
const config = priv.config orelse {
|
||||||
|
log.warn("command palette does not have a config!", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cfg = config.get();
|
||||||
|
|
||||||
|
// Clear existing binds
|
||||||
|
priv.source.removeAll();
|
||||||
|
|
||||||
|
for (cfg.@"command-palette-entry".value.items) |command| {
|
||||||
|
// Filter out actions that are not implemented or don't make sense
|
||||||
|
// for GTK.
|
||||||
|
switch (command.action) {
|
||||||
|
.close_all_windows,
|
||||||
|
.toggle_secure_input,
|
||||||
|
.check_for_updates,
|
||||||
|
.redo,
|
||||||
|
.undo,
|
||||||
|
.reset_window_size,
|
||||||
|
.toggle_window_float_on_top,
|
||||||
|
=> continue,
|
||||||
|
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
const cmd = Command.new(config, command);
|
||||||
|
const cmd_ref = cmd.as(gobject.Object);
|
||||||
|
priv.source.append(cmd_ref);
|
||||||
|
cmd_ref.unref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(self: *CommandPalette) void {
|
||||||
|
const priv = self.private();
|
||||||
|
_ = priv.dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dialogClosed(_: *adw.Dialog, self: *CommandPalette) callconv(.c) void {
|
||||||
|
self.unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn searchStopped(_: *gtk.SearchEntry, self: *CommandPalette) callconv(.c) void {
|
||||||
|
// ESC was pressed - close the palette
|
||||||
|
self.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn searchActivated(_: *gtk.SearchEntry, self: *CommandPalette) callconv(.c) void {
|
||||||
|
// If Enter is pressed, activate the selected entry
|
||||||
|
const priv = self.private();
|
||||||
|
self.activated(priv.model.getSelected());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rowActivated(_: *gtk.ListView, pos: c_uint, self: *CommandPalette) callconv(.c) void {
|
||||||
|
self.activated(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Show or hide the command palette dialog. If the dialog is shown it will
|
||||||
|
/// be modal over the given window.
|
||||||
|
pub fn toggle(self: *CommandPalette, window: *Window) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
// If the dialog has been shown, close it.
|
||||||
|
if (priv.dialog.as(gtk.Widget).getRealized() != 0) {
|
||||||
|
self.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the dialog
|
||||||
|
priv.dialog.present(window.as(gtk.Widget));
|
||||||
|
|
||||||
|
// Focus on the search bar when opening the dialog
|
||||||
|
_ = priv.search.as(gtk.Widget).grabFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to send a signal containing the action that should be
|
||||||
|
/// performed.
|
||||||
|
fn activated(self: *CommandPalette, pos: c_uint) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
// Use priv.model and not priv.source here to use the list of *visible* results
|
||||||
|
const object_ = priv.model.as(gio.ListModel).getObject(pos);
|
||||||
|
defer if (object_) |object| object.unref();
|
||||||
|
|
||||||
|
// Close before running the action in order to avoid being replaced by
|
||||||
|
// another dialog (such as the change title dialog). If that occurs then
|
||||||
|
// the command palette dialog won't be counted as having closed properly
|
||||||
|
// and cannot receive focus when reopened.
|
||||||
|
self.close();
|
||||||
|
|
||||||
|
const cmd = gobject.ext.cast(Command, object_ orelse return) orelse return;
|
||||||
|
const action = cmd.getAction() orelse return;
|
||||||
|
|
||||||
|
// Signal that an an action has been selected. Signals are synchronous
|
||||||
|
// so we shouldn't need to worry about cloning the action.
|
||||||
|
signals.trigger.impl.emit(
|
||||||
|
self,
|
||||||
|
null,
|
||||||
|
.{&action},
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const C = Common(Self, Private);
|
||||||
|
pub const as = C.as;
|
||||||
|
pub const ref = C.ref;
|
||||||
|
pub const refSink = C.refSink;
|
||||||
|
pub const unref = C.unref;
|
||||||
|
const private = C.private;
|
||||||
|
|
||||||
|
pub const Class = extern struct {
|
||||||
|
parent_class: Parent.Class,
|
||||||
|
var parent: *Parent.Class = undefined;
|
||||||
|
pub const Instance = Self;
|
||||||
|
|
||||||
|
fn init(class: *Class) callconv(.c) void {
|
||||||
|
gobject.ext.ensureType(Command);
|
||||||
|
gtk.Widget.Class.setTemplateFromResource(
|
||||||
|
class.as(gtk.Widget.Class),
|
||||||
|
comptime gresource.blueprint(.{
|
||||||
|
.major = 1,
|
||||||
|
.minor = 5,
|
||||||
|
.name = "command-palette",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bindings
|
||||||
|
class.bindTemplateChildPrivate("dialog", .{});
|
||||||
|
class.bindTemplateChildPrivate("search", .{});
|
||||||
|
class.bindTemplateChildPrivate("view", .{});
|
||||||
|
class.bindTemplateChildPrivate("model", .{});
|
||||||
|
class.bindTemplateChildPrivate("source", .{});
|
||||||
|
|
||||||
|
// Template Callbacks
|
||||||
|
class.bindTemplateCallback("closed", &dialogClosed);
|
||||||
|
class.bindTemplateCallback("notify_config", &propConfig);
|
||||||
|
class.bindTemplateCallback("search_stopped", &searchStopped);
|
||||||
|
class.bindTemplateCallback("search_activated", &searchActivated);
|
||||||
|
class.bindTemplateCallback("row_activated", &rowActivated);
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
gobject.ext.registerProperties(class, &.{
|
||||||
|
properties.config.impl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Signals
|
||||||
|
signals.trigger.impl.register(.{});
|
||||||
|
|
||||||
|
// Virtual methods
|
||||||
|
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const as = C.Class.as;
|
||||||
|
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||||
|
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Object that wraps around a command.
|
||||||
|
///
|
||||||
|
/// As GTK list models only accept objects that are within the GObject hierarchy,
|
||||||
|
/// we have to construct a wrapper to be easily consumed by the list model.
|
||||||
|
const Command = extern struct {
|
||||||
|
pub const Self = @This();
|
||||||
|
pub const Parent = gobject.Object;
|
||||||
|
parent: Parent,
|
||||||
|
|
||||||
|
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||||
|
.name = "GhosttyCommand",
|
||||||
|
.instanceInit = &init,
|
||||||
|
.classInit = Class.init,
|
||||||
|
.parent_class = &Class.parent,
|
||||||
|
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||||
|
});
|
||||||
|
|
||||||
|
const properties = struct {
|
||||||
|
pub const config = struct {
|
||||||
|
pub const name = "config";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?*Config,
|
||||||
|
.{
|
||||||
|
.accessor = C.privateObjFieldAccessor("config"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const action_key = struct {
|
||||||
|
pub const name = "action-key";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.default = null,
|
||||||
|
.accessor = gobject.ext.typedAccessor(
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.getter = propGetActionKey,
|
||||||
|
.getter_transfer = .none,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const action = struct {
|
||||||
|
pub const name = "action";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.default = null,
|
||||||
|
.accessor = gobject.ext.typedAccessor(
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.getter = propGetAction,
|
||||||
|
.getter_transfer = .none,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const title = struct {
|
||||||
|
pub const name = "title";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.default = null,
|
||||||
|
.accessor = gobject.ext.typedAccessor(
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.getter = propGetTitle,
|
||||||
|
.getter_transfer = .none,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const description = struct {
|
||||||
|
pub const name = "description";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.default = null,
|
||||||
|
.accessor = gobject.ext.typedAccessor(
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.getter = propGetDescription,
|
||||||
|
.getter_transfer = .none,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Private = struct {
|
||||||
|
/// The configuration we should use to get keybindings.
|
||||||
|
config: ?*Config = null,
|
||||||
|
|
||||||
|
/// Arena used to manage our allocations.
|
||||||
|
arena: ArenaAllocator,
|
||||||
|
|
||||||
|
/// The command.
|
||||||
|
command: ?input.Command = null,
|
||||||
|
|
||||||
|
/// Cache the formatted action.
|
||||||
|
action: ?[:0]const u8 = null,
|
||||||
|
|
||||||
|
/// Cache the formatted action_key.
|
||||||
|
action_key: ?[:0]const u8 = null,
|
||||||
|
|
||||||
|
pub var offset: c_int = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn new(config: *Config, command: input.Command) *Self {
|
||||||
|
const self = gobject.ext.newInstance(Self, .{
|
||||||
|
.config = config,
|
||||||
|
});
|
||||||
|
|
||||||
|
const priv = self.private();
|
||||||
|
priv.command = command.clone(priv.arena.allocator()) catch null;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||||
|
// NOTE: we do not watch for changes to the config here as the command
|
||||||
|
// palette will destroy and recreate this object if/when the config
|
||||||
|
// changes.
|
||||||
|
|
||||||
|
const priv = self.private();
|
||||||
|
priv.arena = .init(Application.default().allocator());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
if (priv.config) |config| {
|
||||||
|
config.unref();
|
||||||
|
priv.config = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
gobject.Object.virtual_methods.dispose.call(
|
||||||
|
Class.parent,
|
||||||
|
self.as(Parent),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize(self: *Self) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
priv.arena.deinit();
|
||||||
|
|
||||||
|
gobject.Object.virtual_methods.finalize.call(
|
||||||
|
Class.parent,
|
||||||
|
self.as(Parent),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
|
||||||
|
fn propGetActionKey(self: *Self) ?[:0]const u8 {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
if (priv.action_key) |action_key| return action_key;
|
||||||
|
|
||||||
|
const command = priv.command orelse return null;
|
||||||
|
|
||||||
|
priv.action_key = std.fmt.allocPrintZ(
|
||||||
|
priv.arena.allocator(),
|
||||||
|
"{}",
|
||||||
|
.{command.action},
|
||||||
|
) catch null;
|
||||||
|
|
||||||
|
return priv.action_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propGetAction(self: *Self) ?[:0]const u8 {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
if (priv.action) |action| return action;
|
||||||
|
|
||||||
|
const command = priv.command orelse return null;
|
||||||
|
|
||||||
|
const cfg = if (priv.config) |config| config.get() else return null;
|
||||||
|
const keybinds = cfg.keybind.set;
|
||||||
|
|
||||||
|
const alloc = priv.arena.allocator();
|
||||||
|
|
||||||
|
priv.action = action: {
|
||||||
|
var buf: [64]u8 = undefined;
|
||||||
|
const trigger = keybinds.getTrigger(command.action) orelse break :action null;
|
||||||
|
const accel = (key.accelFromTrigger(&buf, trigger) catch break :action null) orelse break :action null;
|
||||||
|
break :action alloc.dupeZ(u8, accel) catch return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return priv.action;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propGetTitle(self: *Self) ?[:0]const u8 {
|
||||||
|
const priv = self.private();
|
||||||
|
const command = priv.command orelse return null;
|
||||||
|
return command.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propGetDescription(self: *Self) ?[:0]const u8 {
|
||||||
|
const priv = self.private();
|
||||||
|
const command = priv.command orelse return null;
|
||||||
|
return command.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Return a copy of the action. Callers must ensure that they do not use
|
||||||
|
/// the action beyond the lifetime of this object because it has internally
|
||||||
|
/// allocated data that will be freed when this object is.
|
||||||
|
pub fn getAction(self: *Self) ?input.Binding.Action {
|
||||||
|
const priv = self.private();
|
||||||
|
const command = priv.command orelse return null;
|
||||||
|
return command.action;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
|
||||||
|
const C = Common(Self, Private);
|
||||||
|
pub const as = C.as;
|
||||||
|
pub const ref = C.ref;
|
||||||
|
pub const unref = C.unref;
|
||||||
|
const private = C.private;
|
||||||
|
|
||||||
|
pub const Class = extern struct {
|
||||||
|
parent_class: Parent.Class,
|
||||||
|
var parent: *Parent.Class = undefined;
|
||||||
|
pub const Instance = Self;
|
||||||
|
|
||||||
|
fn init(class: *Class) callconv(.c) void {
|
||||||
|
gobject.ext.registerProperties(class, &.{
|
||||||
|
properties.config.impl,
|
||||||
|
properties.action_key.impl,
|
||||||
|
properties.action.impl,
|
||||||
|
properties.title.impl,
|
||||||
|
properties.description.impl,
|
||||||
|
});
|
||||||
|
|
||||||
|
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||||
|
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -39,8 +39,6 @@ pub const Config = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*gtk.TextBuffer,
|
?*gtk.TextBuffer,
|
||||||
.{
|
.{
|
||||||
.nick = "Diagnostics Buffer",
|
|
||||||
.blurb = "A TextBuffer that contains the diagnostics.",
|
|
||||||
.accessor = gobject.ext.typedAccessor(
|
.accessor = gobject.ext.typedAccessor(
|
||||||
Self,
|
Self,
|
||||||
?*gtk.TextBuffer,
|
?*gtk.TextBuffer,
|
||||||
|
|
@ -57,8 +55,6 @@ pub const Config = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "has-diagnostics",
|
|
||||||
.blurb = "Whether the configuration has diagnostics.",
|
|
||||||
.default = false,
|
.default = false,
|
||||||
.accessor = gobject.ext.typedAccessor(
|
.accessor = gobject.ext.typedAccessor(
|
||||||
Self,
|
Self,
|
||||||
|
|
@ -139,7 +135,7 @@ pub const Config = extern struct {
|
||||||
return text_buf;
|
return text_buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(self: *Self) callconv(.C) void {
|
fn finalize(self: *Self) callconv(.c) void {
|
||||||
self.private().config.deinit();
|
self.private().config.deinit();
|
||||||
|
|
||||||
gobject.Object.virtual_methods.finalize.call(
|
gobject.Object.virtual_methods.finalize.call(
|
||||||
|
|
@ -159,7 +155,7 @@ pub const Config = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
|
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
|
||||||
gobject.ext.registerProperties(class, &.{
|
gobject.ext.registerProperties(class, &.{
|
||||||
properties.@"diagnostics-buffer",
|
properties.@"diagnostics-buffer",
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,6 @@ pub const ConfigErrorsDialog = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*Config,
|
?*Config,
|
||||||
.{
|
.{
|
||||||
.nick = "config",
|
|
||||||
.blurb = "The configuration that this dialog is showing errors for.",
|
|
||||||
.accessor = gobject.ext.typedAccessor(
|
.accessor = gobject.ext.typedAccessor(
|
||||||
Self,
|
Self,
|
||||||
?*Config,
|
?*Config,
|
||||||
|
|
@ -67,7 +65,7 @@ pub const ConfigErrorsDialog = extern struct {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +80,7 @@ pub const ConfigErrorsDialog = extern struct {
|
||||||
fn response(
|
fn response(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
response_id: [*:0]const u8,
|
response_id: [*:0]const u8,
|
||||||
) callconv(.C) void {
|
) callconv(.c) void {
|
||||||
if (std.mem.orderZ(u8, response_id, "reload") != .eq) return;
|
if (std.mem.orderZ(u8, response_id, "reload") != .eq) return;
|
||||||
signals.@"reload-config".impl.emit(
|
signals.@"reload-config".impl.emit(
|
||||||
self,
|
self,
|
||||||
|
|
@ -92,7 +90,7 @@ pub const ConfigErrorsDialog = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispose(self: *Self) callconv(.C) void {
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
if (priv.config) |v| {
|
if (priv.config) |v| {
|
||||||
v.unref();
|
v.unref();
|
||||||
|
|
@ -134,7 +132,7 @@ pub const ConfigErrorsDialog = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
gobject.ext.ensureType(Dialog);
|
gobject.ext.ensureType(Dialog);
|
||||||
gtk.Widget.Class.setTemplateFromResource(
|
gtk.Widget.Class.setTemplateFromResource(
|
||||||
class.as(gtk.Widget.Class),
|
class.as(gtk.Widget.Class),
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ pub const Dialog = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
_ = class;
|
_ = class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,632 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const adw = @import("adw");
|
||||||
|
const gio = @import("gio");
|
||||||
|
const glib = @import("glib");
|
||||||
|
const gobject = @import("gobject");
|
||||||
|
const gtk = @import("gtk");
|
||||||
|
|
||||||
|
const Binding = @import("../../../input.zig").Binding;
|
||||||
|
const gresource = @import("../build/gresource.zig");
|
||||||
|
const key = @import("../key.zig");
|
||||||
|
const Common = @import("../class.zig").Common;
|
||||||
|
const Application = @import("application.zig").Application;
|
||||||
|
const Config = @import("config.zig").Config;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.gtk_ghostty_global_shortcuts);
|
||||||
|
|
||||||
|
pub const GlobalShortcuts = extern struct {
|
||||||
|
const Self = @This();
|
||||||
|
parent_instance: Parent,
|
||||||
|
pub const Parent = gobject.Object;
|
||||||
|
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||||
|
.name = "GhosttyGlobalShortcuts",
|
||||||
|
.instanceInit = &init,
|
||||||
|
.classInit = &Class.init,
|
||||||
|
.parent_class = &Class.parent,
|
||||||
|
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const properties = struct {
|
||||||
|
pub const config = struct {
|
||||||
|
pub const name = "config";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?*Config,
|
||||||
|
.{
|
||||||
|
.accessor = C.privateObjFieldAccessor("config"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const @"dbus-connection" = struct {
|
||||||
|
pub const name = "dbus-connection";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?*gio.DBusConnection,
|
||||||
|
.{
|
||||||
|
.accessor = C.privateObjFieldAccessor("dbus_connection"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Private = struct {
|
||||||
|
/// The configuration that this is using.
|
||||||
|
config: ?*Config = null,
|
||||||
|
|
||||||
|
/// The dbus connection.
|
||||||
|
dbus_connection: ?*gio.DBusConnection = null,
|
||||||
|
|
||||||
|
/// An arena allocator that is present for each refresh.
|
||||||
|
arena: ?std.heap.ArenaAllocator = null,
|
||||||
|
|
||||||
|
/// A mapping from a unique ID to an action.
|
||||||
|
/// Currently the unique ID is simply the serialized representation of the
|
||||||
|
/// trigger that was used for the action as triggers are unique in the keymap,
|
||||||
|
/// but this may change in the future.
|
||||||
|
map: std.StringArrayHashMapUnmanaged(Binding.Action) = .{},
|
||||||
|
|
||||||
|
/// The handle of the current global shortcuts portal session,
|
||||||
|
/// as a D-Bus object path.
|
||||||
|
handle: ?[:0]const u8 = null,
|
||||||
|
|
||||||
|
/// The D-Bus signal subscription for the response signal on requests.
|
||||||
|
/// The ID is guaranteed to be non-zero, so we can use 0 to indicate null.
|
||||||
|
response_subscription: c_uint = 0,
|
||||||
|
|
||||||
|
/// The D-Bus signal subscription for the keybind activate signal.
|
||||||
|
/// The ID is guaranteed to be non-zero, so we can use 0 to indicate null.
|
||||||
|
activate_subscription: c_uint = 0,
|
||||||
|
|
||||||
|
pub var offset: c_int = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const signals = struct {
|
||||||
|
/// Emitted whenever a global shortcut is triggered.
|
||||||
|
pub const trigger = struct {
|
||||||
|
pub const name = "trigger";
|
||||||
|
pub const connect = impl.connect;
|
||||||
|
const impl = gobject.ext.defineSignal(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
&.{*const Binding.Action},
|
||||||
|
void,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||||
|
_ = gobject.Object.signals.notify.connect(
|
||||||
|
self,
|
||||||
|
*Self,
|
||||||
|
propConfig,
|
||||||
|
self,
|
||||||
|
.{ .detail = "config" },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(self: *Self) void {
|
||||||
|
const priv = self.private();
|
||||||
|
const dbus = priv.dbus_connection orelse return;
|
||||||
|
|
||||||
|
if (priv.response_subscription != 0) {
|
||||||
|
dbus.signalUnsubscribe(priv.response_subscription);
|
||||||
|
priv.response_subscription = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priv.activate_subscription != 0) {
|
||||||
|
dbus.signalUnsubscribe(priv.activate_subscription);
|
||||||
|
priv.activate_subscription = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priv.handle) |handle| {
|
||||||
|
// Close existing session
|
||||||
|
dbus.call(
|
||||||
|
"org.freedesktop.portal.Desktop",
|
||||||
|
handle,
|
||||||
|
"org.freedesktop.portal.Session",
|
||||||
|
"Close",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
.{},
|
||||||
|
-1,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
priv.handle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priv.arena) |*arena| {
|
||||||
|
arena.deinit();
|
||||||
|
priv.arena = null;
|
||||||
|
priv.map = .{}; // Uses arena memory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh(self: *Self) Allocator.Error!void {
|
||||||
|
// Always close our previous state first.
|
||||||
|
self.close();
|
||||||
|
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
// We need configuration to proceed.
|
||||||
|
const config = if (priv.config) |v| v.get() else return;
|
||||||
|
|
||||||
|
// Setup our new arena that we'll use for memory allocations.
|
||||||
|
assert(priv.arena == null);
|
||||||
|
var arena: std.heap.ArenaAllocator = .init(Application.default().allocator());
|
||||||
|
errdefer arena.deinit();
|
||||||
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
|
// Our map starts out empty again. We don't need to worry about
|
||||||
|
// memory because its part of the arena we clear.
|
||||||
|
priv.map = .{};
|
||||||
|
errdefer priv.map = .{};
|
||||||
|
|
||||||
|
// Update map
|
||||||
|
var trigger_buf: [1024]u8 = undefined;
|
||||||
|
var it = config.keybind.set.bindings.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
const leaf = switch (entry.value_ptr.*) {
|
||||||
|
// Global shortcuts can't have leaders
|
||||||
|
.leader => continue,
|
||||||
|
.leaf => |leaf| leaf,
|
||||||
|
};
|
||||||
|
if (!leaf.flags.global) continue;
|
||||||
|
|
||||||
|
const trigger = if (key.xdgShortcutFromTrigger(
|
||||||
|
&trigger_buf,
|
||||||
|
entry.key_ptr.*,
|
||||||
|
)) |shortcut_|
|
||||||
|
shortcut_ orelse continue
|
||||||
|
else |err| switch (err) {
|
||||||
|
// If there isn't space to translate the trigger, then our
|
||||||
|
// buffer might be too small (but 1024 is insane!). In any case
|
||||||
|
// we don't want to stop registering globals.
|
||||||
|
error.NoSpaceLeft => {
|
||||||
|
log.warn(
|
||||||
|
"buffer too small to translate trigger, ignoring={}",
|
||||||
|
.{entry.key_ptr.*},
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try priv.map.put(
|
||||||
|
alloc,
|
||||||
|
try alloc.dupeZ(u8, trigger),
|
||||||
|
leaf.action,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store our arena
|
||||||
|
priv.arena = arena;
|
||||||
|
|
||||||
|
// Create our session if we have global shortcuts.
|
||||||
|
if (priv.map.count() > 0) try self.request(.create_session);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Method = enum {
|
||||||
|
create_session,
|
||||||
|
bind_shortcuts,
|
||||||
|
|
||||||
|
fn name(self: Method) [:0]const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.create_session => "CreateSession",
|
||||||
|
.bind_shortcuts => "BindShortcuts",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct the payload expected by the XDG portal call.
|
||||||
|
fn makePayload(
|
||||||
|
self: Method,
|
||||||
|
shortcuts: *GlobalShortcuts,
|
||||||
|
request_token: [:0]const u8,
|
||||||
|
) ?*glib.Variant {
|
||||||
|
switch (self) {
|
||||||
|
// See https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-createsession
|
||||||
|
.create_session => {
|
||||||
|
var session_token: Token = undefined;
|
||||||
|
return glib.Variant.newParsed(
|
||||||
|
"({'handle_token': <%s>, 'session_handle_token': <%s>},)",
|
||||||
|
request_token.ptr,
|
||||||
|
generateToken(&session_token).ptr,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
// See https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-bindshortcuts
|
||||||
|
.bind_shortcuts => {
|
||||||
|
const priv = shortcuts.private();
|
||||||
|
const handle = priv.handle orelse return null;
|
||||||
|
|
||||||
|
const bind_type = glib.VariantType.new("a(sa{sv})");
|
||||||
|
defer glib.free(bind_type);
|
||||||
|
|
||||||
|
var binds: glib.VariantBuilder = undefined;
|
||||||
|
glib.VariantBuilder.init(&binds, bind_type);
|
||||||
|
|
||||||
|
var action_buf: [256]u8 = undefined;
|
||||||
|
|
||||||
|
var it = priv.map.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
const trigger = entry.key_ptr.*.ptr;
|
||||||
|
const action = std.fmt.bufPrintZ(
|
||||||
|
&action_buf,
|
||||||
|
"{}",
|
||||||
|
.{entry.value_ptr.*},
|
||||||
|
) catch continue;
|
||||||
|
|
||||||
|
binds.addParsed(
|
||||||
|
"(%s, {'description': <%s>, 'preferred_trigger': <%s>})",
|
||||||
|
trigger,
|
||||||
|
action.ptr,
|
||||||
|
trigger,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return glib.Variant.newParsed(
|
||||||
|
"(%o, %*, '', {'handle_token': <%s>})",
|
||||||
|
handle.ptr,
|
||||||
|
binds.end(),
|
||||||
|
request_token.ptr,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn onResponse(self: Method, shortcuts: *GlobalShortcuts, vardict: *glib.Variant) void {
|
||||||
|
switch (self) {
|
||||||
|
.create_session => {
|
||||||
|
var handle: ?[*:0]u8 = null;
|
||||||
|
if (vardict.lookup("session_handle", "&s", &handle) == 0) {
|
||||||
|
log.warn(
|
||||||
|
"session handle not found in response={s}",
|
||||||
|
.{vardict.print(@intFromBool(true))},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const priv = shortcuts.private();
|
||||||
|
const dbus = priv.dbus_connection.?;
|
||||||
|
const alloc = priv.arena.?.allocator();
|
||||||
|
priv.handle = alloc.dupeZ(u8, std.mem.span(handle.?)) catch {
|
||||||
|
log.warn("out of memory: failed to clone session handle", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
log.debug("session_handle={?s}", .{handle});
|
||||||
|
|
||||||
|
// Subscribe to keybind activations
|
||||||
|
assert(priv.activate_subscription == 0);
|
||||||
|
priv.activate_subscription = dbus.signalSubscribe(
|
||||||
|
null,
|
||||||
|
"org.freedesktop.portal.GlobalShortcuts",
|
||||||
|
"Activated",
|
||||||
|
"/org/freedesktop/portal/desktop",
|
||||||
|
handle,
|
||||||
|
.{ .match_arg0_path = true },
|
||||||
|
shortcutActivated,
|
||||||
|
shortcuts,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
shortcuts.request(.bind_shortcuts) catch |err| {
|
||||||
|
log.warn("failed to bind shortcuts={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
.bind_shortcuts => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Submit a request to the global shortcuts portal.
|
||||||
|
fn request(
|
||||||
|
self: *Self,
|
||||||
|
comptime method: Method,
|
||||||
|
) Allocator.Error!void {
|
||||||
|
// NOTE(pluiedev):
|
||||||
|
// XDG Portals are really, really poorly-designed pieces of hot garbage.
|
||||||
|
// How the protocol is _initially_ designed to work is as follows:
|
||||||
|
//
|
||||||
|
// 1. The client calls a method which returns the path of a Request object;
|
||||||
|
// 2. The client waits for the Response signal under said object path;
|
||||||
|
// 3. When the signal arrives, the actual return value and status code
|
||||||
|
// become available for the client for further processing.
|
||||||
|
//
|
||||||
|
// THIS DOES NOT WORK. Once the first two steps are complete, the client
|
||||||
|
// needs to immediately start listening for the third step, but an overeager
|
||||||
|
// server implementation could easily send the Response signal before the
|
||||||
|
// client is even ready, causing communications to break down over a simple
|
||||||
|
// race condition/two generals' problem that even _TCP_ had figured out
|
||||||
|
// decades ago. Worse yet, you get exactly _one_ chance to listen for the
|
||||||
|
// signal, or else your communication attempt so far has all been in vain.
|
||||||
|
//
|
||||||
|
// And they know this. Instead of fixing their freaking protocol, they just
|
||||||
|
// ask clients to manually construct the expected object path and subscribe
|
||||||
|
// to the request signal beforehand, making the whole response value of
|
||||||
|
// the original call COMPLETELY MEANINGLESS.
|
||||||
|
//
|
||||||
|
// Furthermore, this is _entirely undocumented_ aside from one tiny
|
||||||
|
// paragraph under the documentation for the Request interface, and
|
||||||
|
// anyone would be forgiven for missing it without reading the libportal
|
||||||
|
// source code.
|
||||||
|
//
|
||||||
|
// When in Rome, do as the Romans do, I guess...?
|
||||||
|
|
||||||
|
const callbacks = struct {
|
||||||
|
fn gotResponseHandle(
|
||||||
|
source: ?*gobject.Object,
|
||||||
|
res: *gio.AsyncResult,
|
||||||
|
_: ?*anyopaque,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const dbus_ = gobject.ext.cast(gio.DBusConnection, source.?).?;
|
||||||
|
|
||||||
|
var err: ?*glib.Error = null;
|
||||||
|
defer if (err) |err_| err_.free();
|
||||||
|
|
||||||
|
const params_ = dbus_.callFinish(res, &err) orelse {
|
||||||
|
if (err) |err_| log.warn("request failed={s} ({})", .{
|
||||||
|
err_.f_message orelse "(unknown)",
|
||||||
|
err_.f_code,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
defer params_.unref();
|
||||||
|
|
||||||
|
// TODO: XDG recommends updating the signal subscription if the actual
|
||||||
|
// returned request path is not the same as the expected request
|
||||||
|
// path, to retain compatibility with older versions of XDG portals.
|
||||||
|
// Although it suffers from the race condition outlined above,
|
||||||
|
// we should still implement this at some point.
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html#org-freedesktop-portal-request-response
|
||||||
|
fn responded(
|
||||||
|
dbus: *gio.DBusConnection,
|
||||||
|
_: ?[*:0]const u8,
|
||||||
|
_: [*:0]const u8,
|
||||||
|
_: [*:0]const u8,
|
||||||
|
_: [*:0]const u8,
|
||||||
|
params_: *glib.Variant,
|
||||||
|
ud: ?*anyopaque,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const self_cb: *GlobalShortcuts = @ptrCast(@alignCast(ud));
|
||||||
|
const priv = self_cb.private();
|
||||||
|
|
||||||
|
// Unsubscribe from the response signal
|
||||||
|
if (priv.response_subscription != 0) {
|
||||||
|
dbus.signalUnsubscribe(priv.response_subscription);
|
||||||
|
priv.response_subscription = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var response: u32 = 0;
|
||||||
|
var vardict: ?*glib.Variant = null;
|
||||||
|
defer if (vardict) |v| v.unref();
|
||||||
|
params_.get("(u@a{sv})", &response, &vardict);
|
||||||
|
|
||||||
|
switch (response) {
|
||||||
|
0 => {
|
||||||
|
log.debug("request successful", .{});
|
||||||
|
method.onResponse(self_cb, vardict.?);
|
||||||
|
},
|
||||||
|
1 => log.debug("request was cancelled by user", .{}),
|
||||||
|
2 => log.warn("request ended unexpectedly", .{}),
|
||||||
|
else => log.warn("unrecognized response code={}", .{response}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var request_token_buf: Token = undefined;
|
||||||
|
const request_token = generateToken(&request_token_buf);
|
||||||
|
|
||||||
|
const payload = method.makePayload(self, request_token) orelse return;
|
||||||
|
const request_path = try self.getRequestPath(request_token);
|
||||||
|
|
||||||
|
const priv = self.private();
|
||||||
|
const dbus = priv.dbus_connection.?;
|
||||||
|
|
||||||
|
assert(priv.response_subscription == 0);
|
||||||
|
priv.response_subscription = dbus.signalSubscribe(
|
||||||
|
null,
|
||||||
|
"org.freedesktop.portal.Request",
|
||||||
|
"Response",
|
||||||
|
request_path,
|
||||||
|
null,
|
||||||
|
.{},
|
||||||
|
callbacks.responded,
|
||||||
|
self,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
dbus.call(
|
||||||
|
"org.freedesktop.portal.Desktop",
|
||||||
|
"/org/freedesktop/portal/desktop",
|
||||||
|
"org.freedesktop.portal.GlobalShortcuts",
|
||||||
|
method.name(),
|
||||||
|
payload,
|
||||||
|
null,
|
||||||
|
.{},
|
||||||
|
-1,
|
||||||
|
null,
|
||||||
|
callbacks.gotResponseHandle,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the XDG portal request path for the current Ghostty instance.
|
||||||
|
///
|
||||||
|
/// If this sounds like nonsense, see `request` for an explanation as to
|
||||||
|
/// why we need to do this.
|
||||||
|
///
|
||||||
|
/// Precondition: dbus connection exists, arena setup
|
||||||
|
fn getRequestPath(self: *Self, token: [:0]const u8) Allocator.Error![:0]const u8 {
|
||||||
|
const priv = self.private();
|
||||||
|
const dbus = priv.dbus_connection.?;
|
||||||
|
const alloc = priv.arena.?.allocator();
|
||||||
|
|
||||||
|
// See https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html
|
||||||
|
// for the syntax XDG portals expect.
|
||||||
|
|
||||||
|
// `getUniqueName` should never return null here as we're using an ordinary
|
||||||
|
// message bus connection. If it doesn't, something is very wrong
|
||||||
|
const unique_name = std.mem.span(dbus.getUniqueName().?);
|
||||||
|
|
||||||
|
const object_path = try std.mem.joinZ(
|
||||||
|
alloc,
|
||||||
|
"/",
|
||||||
|
&.{
|
||||||
|
"/org/freedesktop/portal/desktop/request",
|
||||||
|
unique_name[1..], // Remove leading `:`
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sanitize the unique name by replacing every `.` with `_`.
|
||||||
|
// In effect, this will turn a unique name like `:1.192` into `1_192`.
|
||||||
|
// Valid D-Bus object path components never contain `.`s anyway, so we're
|
||||||
|
// free to replace all instances of `.` here and avoid extra allocation.
|
||||||
|
std.mem.replaceScalar(u8, object_path, '.', '_');
|
||||||
|
|
||||||
|
return object_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Property Handlers
|
||||||
|
|
||||||
|
pub fn setDbusConnection(
|
||||||
|
self: *Self,
|
||||||
|
dbus_connection: ?*gio.DBusConnection,
|
||||||
|
) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
// If we have a prior dbus connection we need to close our prior
|
||||||
|
// registrations first.
|
||||||
|
if (priv.dbus_connection) |v| {
|
||||||
|
self.close();
|
||||||
|
v.unref();
|
||||||
|
priv.dbus_connection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
priv.dbus_connection = null;
|
||||||
|
if (dbus_connection) |v| {
|
||||||
|
v.ref(); // Weird this doesn't return self
|
||||||
|
priv.dbus_connection = v;
|
||||||
|
self.refresh() catch |err| {
|
||||||
|
log.warn("error refreshing global shortcuts: {}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.@"dbus-connection".impl.param_spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propConfig(
|
||||||
|
_: *Self,
|
||||||
|
_: *gobject.ParamSpec,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
self.refresh() catch |err| {
|
||||||
|
log.warn("error refreshing global shortcuts: {}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Signal Handlers
|
||||||
|
|
||||||
|
fn shortcutActivated(
|
||||||
|
_: *gio.DBusConnection,
|
||||||
|
_: ?[*:0]const u8,
|
||||||
|
_: [*:0]const u8,
|
||||||
|
_: [*:0]const u8,
|
||||||
|
_: [*:0]const u8,
|
||||||
|
params: *glib.Variant,
|
||||||
|
ud: ?*anyopaque,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const self: *Self = @ptrCast(@alignCast(ud));
|
||||||
|
|
||||||
|
// 2nd value in the tuple is the activated shortcut ID
|
||||||
|
// See https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.GlobalShortcuts.html#org-freedesktop-portal-globalshortcuts-activated
|
||||||
|
var shortcut_id: [*:0]const u8 = undefined;
|
||||||
|
params.getChild(1, "&s", &shortcut_id);
|
||||||
|
log.debug("activated={s}", .{shortcut_id});
|
||||||
|
|
||||||
|
const action = self.private().map.get(std.mem.span(shortcut_id)) orelse return;
|
||||||
|
signals.trigger.impl.emit(
|
||||||
|
self,
|
||||||
|
null,
|
||||||
|
.{&action},
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------
|
||||||
|
// Virtual methods
|
||||||
|
|
||||||
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
|
// Since we drop references here we may lose access to things like
|
||||||
|
// dbus connections, so we need to close all our connections right
|
||||||
|
// away instead of in finalize.
|
||||||
|
self.close();
|
||||||
|
|
||||||
|
const priv = self.private();
|
||||||
|
if (priv.config) |v| {
|
||||||
|
v.unref();
|
||||||
|
priv.config = null;
|
||||||
|
}
|
||||||
|
if (priv.dbus_connection) |v| {
|
||||||
|
v.unref();
|
||||||
|
priv.dbus_connection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
gobject.Object.virtual_methods.dispose.call(
|
||||||
|
Class.parent,
|
||||||
|
self.as(Parent),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const C = Common(Self, Private);
|
||||||
|
pub const as = C.as;
|
||||||
|
pub const ref = C.ref;
|
||||||
|
pub const unref = C.unref;
|
||||||
|
const private = C.private;
|
||||||
|
|
||||||
|
pub const Class = extern struct {
|
||||||
|
parent_class: Parent.Class,
|
||||||
|
var parent: *Parent.Class = undefined;
|
||||||
|
pub const Instance = Self;
|
||||||
|
|
||||||
|
fn init(class: *Class) callconv(.c) void {
|
||||||
|
// Properties
|
||||||
|
gobject.ext.registerProperties(class, &.{
|
||||||
|
properties.config.impl,
|
||||||
|
properties.@"dbus-connection".impl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Signals
|
||||||
|
signals.trigger.impl.register(.{});
|
||||||
|
|
||||||
|
// Virtual methods
|
||||||
|
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const as = C.Class.as;
|
||||||
|
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Token = [16]u8;
|
||||||
|
|
||||||
|
/// Generate a random token suitable for use in requests.
|
||||||
|
fn generateToken(buf: *Token) [:0]const u8 {
|
||||||
|
// u28 takes up 7 bytes in hex, 8 bytes for "ghostty_" and 1 byte for NUL
|
||||||
|
// 7 + 8 + 1 = 16
|
||||||
|
return std.fmt.bufPrintZ(
|
||||||
|
buf,
|
||||||
|
"ghostty_{x:0<7}",
|
||||||
|
.{std.crypto.random.int(u28)},
|
||||||
|
) catch unreachable;
|
||||||
|
}
|
||||||
|
|
@ -42,8 +42,6 @@ pub const ResizeOverlay = extern struct {
|
||||||
Self,
|
Self,
|
||||||
c_uint,
|
c_uint,
|
||||||
.{
|
.{
|
||||||
.nick = "Duration",
|
|
||||||
.blurb = "The duration this overlay appears in milliseconds.",
|
|
||||||
.default = 750,
|
.default = 750,
|
||||||
.minimum = 250,
|
.minimum = 250,
|
||||||
.maximum = std.math.maxInt(c_uint),
|
.maximum = std.math.maxInt(c_uint),
|
||||||
|
|
@ -64,8 +62,6 @@ pub const ResizeOverlay = extern struct {
|
||||||
Self,
|
Self,
|
||||||
c_uint,
|
c_uint,
|
||||||
.{
|
.{
|
||||||
.nick = "First Delay",
|
|
||||||
.blurb = "The delay in milliseconds before any overlay is shown for the first time.",
|
|
||||||
.default = 250,
|
.default = 250,
|
||||||
.minimum = 250,
|
.minimum = 250,
|
||||||
.maximum = std.math.maxInt(c_uint),
|
.maximum = std.math.maxInt(c_uint),
|
||||||
|
|
@ -79,6 +75,19 @@ pub const ResizeOverlay = extern struct {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const label = struct {
|
||||||
|
pub const name = "label";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?[:0]const u8,
|
||||||
|
.{
|
||||||
|
.default = null,
|
||||||
|
.accessor = C.privateStringFieldAccessor("label_text"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
pub const @"overlay-halign" = struct {
|
pub const @"overlay-halign" = struct {
|
||||||
pub const name = "overlay-halign";
|
pub const name = "overlay-halign";
|
||||||
const impl = gobject.ext.defineProperty(
|
const impl = gobject.ext.defineProperty(
|
||||||
|
|
@ -86,8 +95,6 @@ pub const ResizeOverlay = extern struct {
|
||||||
Self,
|
Self,
|
||||||
gtk.Align,
|
gtk.Align,
|
||||||
.{
|
.{
|
||||||
.nick = "halign",
|
|
||||||
.blurb = "The alignment of the label.",
|
|
||||||
.default = .center,
|
.default = .center,
|
||||||
.accessor = gobject.ext.privateFieldAccessor(
|
.accessor = gobject.ext.privateFieldAccessor(
|
||||||
Self,
|
Self,
|
||||||
|
|
@ -106,8 +113,6 @@ pub const ResizeOverlay = extern struct {
|
||||||
Self,
|
Self,
|
||||||
gtk.Align,
|
gtk.Align,
|
||||||
.{
|
.{
|
||||||
.nick = "valign",
|
|
||||||
.blurb = "The alignment of the label.",
|
|
||||||
.default = .center,
|
.default = .center,
|
||||||
.accessor = gobject.ext.privateFieldAccessor(
|
.accessor = gobject.ext.privateFieldAccessor(
|
||||||
Self,
|
Self,
|
||||||
|
|
@ -124,6 +129,9 @@ pub const ResizeOverlay = extern struct {
|
||||||
/// The label with the text
|
/// The label with the text
|
||||||
label: *gtk.Label,
|
label: *gtk.Label,
|
||||||
|
|
||||||
|
/// The text to set on the label when scheduled.
|
||||||
|
label_text: ?[:0]const u8,
|
||||||
|
|
||||||
/// The time that the overlay appears.
|
/// The time that the overlay appears.
|
||||||
duration: c_uint,
|
duration: c_uint,
|
||||||
|
|
||||||
|
|
@ -147,7 +155,7 @@ pub const ResizeOverlay = extern struct {
|
||||||
pub var offset: c_int = 0;
|
pub var offset: c_int = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
|
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
|
|
@ -162,9 +170,12 @@ pub const ResizeOverlay = extern struct {
|
||||||
|
|
||||||
/// Set the label for the overlay. This will not show the
|
/// Set the label for the overlay. This will not show the
|
||||||
/// overlay if it is currently hidden; you must call schedule.
|
/// overlay if it is currently hidden; you must call schedule.
|
||||||
pub fn setLabel(self: *Self, label: [:0]const u8) void {
|
pub fn setLabel(self: *Self, label: ?[:0]const u8) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
priv.label.setText(label.ptr);
|
if (priv.label_text) |v| glib.free(@constCast(@ptrCast(v)));
|
||||||
|
priv.label_text = null;
|
||||||
|
if (label) |v| priv.label_text = glib.ext.dupeZ(u8, v);
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.label.impl.param_spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedule the overlay to be shown. To avoid flickering during
|
/// Schedule the overlay to be shown. To avoid flickering during
|
||||||
|
|
@ -192,15 +203,26 @@ pub const ResizeOverlay = extern struct {
|
||||||
// No matter what our idler is complete with this callback
|
// No matter what our idler is complete with this callback
|
||||||
priv.idler = null;
|
priv.idler = null;
|
||||||
|
|
||||||
// Show ourselves
|
// Cancel our previous show timer no matter what.
|
||||||
self.as(gtk.Widget).setVisible(1);
|
|
||||||
|
|
||||||
if (priv.timer) |timer| {
|
if (priv.timer) |timer| {
|
||||||
if (glib.Source.remove(timer) == 0) {
|
if (glib.Source.remove(timer) == 0) {
|
||||||
log.warn("unable to remove size overlay timer", .{});
|
log.warn("unable to remove size overlay timer", .{});
|
||||||
}
|
}
|
||||||
|
priv.timer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have a label to show, show ourselves. If we don't have
|
||||||
|
// label text, then hide our label.
|
||||||
|
const text = priv.label_text orelse {
|
||||||
|
self.as(gtk.Widget).setVisible(0);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set our label and show it.
|
||||||
|
priv.label.setLabel(text);
|
||||||
|
self.as(gtk.Widget).setVisible(1);
|
||||||
|
|
||||||
|
// Setup the new timer to hide ourselves after the delay.
|
||||||
priv.timer = glib.timeoutAdd(
|
priv.timer = glib.timeoutAdd(
|
||||||
priv.duration,
|
priv.duration,
|
||||||
onTimer,
|
onTimer,
|
||||||
|
|
@ -228,7 +250,7 @@ pub const ResizeOverlay = extern struct {
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
|
|
||||||
fn dispose(self: *Self) callconv(.C) void {
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
if (priv.idler) |v| {
|
if (priv.idler) |v| {
|
||||||
if (glib.Source.remove(v) == 0) {
|
if (glib.Source.remove(v) == 0) {
|
||||||
|
|
@ -260,6 +282,19 @@ pub const ResizeOverlay = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finalize(self: *Self) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
if (priv.label_text) |v| {
|
||||||
|
glib.free(@constCast(@ptrCast(v)));
|
||||||
|
priv.label_text = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
gobject.Object.virtual_methods.finalize.call(
|
||||||
|
Class.parent,
|
||||||
|
self.as(Parent),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const C = Common(Self, Private);
|
const C = Common(Self, Private);
|
||||||
pub const as = C.as;
|
pub const as = C.as;
|
||||||
pub const ref = C.ref;
|
pub const ref = C.ref;
|
||||||
|
|
@ -271,7 +306,7 @@ pub const ResizeOverlay = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
gtk.Widget.Class.setTemplateFromResource(
|
gtk.Widget.Class.setTemplateFromResource(
|
||||||
class.as(gtk.Widget.Class),
|
class.as(gtk.Widget.Class),
|
||||||
comptime gresource.blueprint(.{
|
comptime gresource.blueprint(.{
|
||||||
|
|
@ -287,6 +322,7 @@ pub const ResizeOverlay = extern struct {
|
||||||
// Properties
|
// Properties
|
||||||
gobject.ext.registerProperties(class, &.{
|
gobject.ext.registerProperties(class, &.{
|
||||||
properties.duration.impl,
|
properties.duration.impl,
|
||||||
|
properties.label.impl,
|
||||||
properties.@"first-delay".impl,
|
properties.@"first-delay".impl,
|
||||||
properties.@"overlay-halign".impl,
|
properties.@"overlay-halign".impl,
|
||||||
properties.@"overlay-valign".impl,
|
properties.@"overlay-valign".impl,
|
||||||
|
|
@ -294,6 +330,7 @@ pub const ResizeOverlay = extern struct {
|
||||||
|
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||||
|
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const as = C.Class.as;
|
pub const as = C.Class.as;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -9,6 +9,7 @@ const gobject = @import("gobject");
|
||||||
const gtk = @import("gtk");
|
const gtk = @import("gtk");
|
||||||
|
|
||||||
const apprt = @import("../../../apprt.zig");
|
const apprt = @import("../../../apprt.zig");
|
||||||
|
const datastruct = @import("../../../datastruct/main.zig");
|
||||||
const font = @import("../../../font/main.zig");
|
const font = @import("../../../font/main.zig");
|
||||||
const input = @import("../../../input.zig");
|
const input = @import("../../../input.zig");
|
||||||
const internal_os = @import("../../../os/main.zig");
|
const internal_os = @import("../../../os/main.zig");
|
||||||
|
|
@ -42,6 +43,9 @@ pub const Surface = extern struct {
|
||||||
.private = .{ .Type = Private, .offset = &Private.offset },
|
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// A SplitTree implementation that stores surfaces.
|
||||||
|
pub const Tree = datastruct.SplitTree(Self);
|
||||||
|
|
||||||
pub const properties = struct {
|
pub const properties = struct {
|
||||||
pub const config = struct {
|
pub const config = struct {
|
||||||
pub const name = "config";
|
pub const name = "config";
|
||||||
|
|
@ -50,8 +54,6 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*Config,
|
?*Config,
|
||||||
.{
|
.{
|
||||||
.nick = "Config",
|
|
||||||
.blurb = "The configuration that this surface is using.",
|
|
||||||
.accessor = C.privateObjFieldAccessor("config"),
|
.accessor = C.privateObjFieldAccessor("config"),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -64,8 +66,6 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Child Exited",
|
|
||||||
.blurb = "True when the child process has exited.",
|
|
||||||
.default = false,
|
.default = false,
|
||||||
.accessor = gobject.ext.privateFieldAccessor(
|
.accessor = gobject.ext.privateFieldAccessor(
|
||||||
Self,
|
Self,
|
||||||
|
|
@ -84,8 +84,6 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*Size,
|
?*Size,
|
||||||
.{
|
.{
|
||||||
.nick = "Default Size",
|
|
||||||
.blurb = "The default size of the window for this surface.",
|
|
||||||
.accessor = C.privateBoxedFieldAccessor("default_size"),
|
.accessor = C.privateBoxedFieldAccessor("default_size"),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -98,8 +96,6 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*font.face.DesiredSize,
|
?*font.face.DesiredSize,
|
||||||
.{
|
.{
|
||||||
.nick = "Desired Font Size",
|
|
||||||
.blurb = "The desired font size, only affects initialization.",
|
|
||||||
.accessor = C.privateBoxedFieldAccessor("font_size_request"),
|
.accessor = C.privateBoxedFieldAccessor("font_size_request"),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -112,8 +108,6 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Focused",
|
|
||||||
.blurb = "The focused state of the surface.",
|
|
||||||
.default = false,
|
.default = false,
|
||||||
.accessor = gobject.ext.privateFieldAccessor(
|
.accessor = gobject.ext.privateFieldAccessor(
|
||||||
Self,
|
Self,
|
||||||
|
|
@ -132,8 +126,6 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*Size,
|
?*Size,
|
||||||
.{
|
.{
|
||||||
.nick = "Minimum Size",
|
|
||||||
.blurb = "The minimum size of the surface.",
|
|
||||||
.accessor = C.privateBoxedFieldAccessor("min_size"),
|
.accessor = C.privateBoxedFieldAccessor("min_size"),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -146,14 +138,14 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Mouse Hidden",
|
|
||||||
.blurb = "Whether the mouse cursor should be hidden.",
|
|
||||||
.default = false,
|
.default = false,
|
||||||
.accessor = gobject.ext.privateFieldAccessor(
|
.accessor = gobject.ext.typedAccessor(
|
||||||
Self,
|
Self,
|
||||||
Private,
|
bool,
|
||||||
&Private.offset,
|
.{
|
||||||
"mouse_hidden",
|
.getter = getMouseHidden,
|
||||||
|
.setter = setMouseHidden,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -166,14 +158,14 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
terminal.MouseShape,
|
terminal.MouseShape,
|
||||||
.{
|
.{
|
||||||
.nick = "Mouse Shape",
|
|
||||||
.blurb = "The current mouse shape to show for the surface.",
|
|
||||||
.default = .text,
|
.default = .text,
|
||||||
.accessor = gobject.ext.privateFieldAccessor(
|
.accessor = gobject.ext.typedAccessor(
|
||||||
Self,
|
Self,
|
||||||
Private,
|
terminal.MouseShape,
|
||||||
&Private.offset,
|
.{
|
||||||
"mouse_shape",
|
.getter = getMouseShape,
|
||||||
|
.setter = setMouseShape,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -188,8 +180,6 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?[:0]const u8,
|
?[:0]const u8,
|
||||||
.{
|
.{
|
||||||
.nick = "Mouse Hover URL",
|
|
||||||
.blurb = "The URL the mouse is currently hovering over (if any).",
|
|
||||||
.default = null,
|
.default = null,
|
||||||
.accessor = C.privateStringFieldAccessor("mouse_hover_url"),
|
.accessor = C.privateStringFieldAccessor("mouse_hover_url"),
|
||||||
},
|
},
|
||||||
|
|
@ -205,8 +195,6 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?[:0]const u8,
|
?[:0]const u8,
|
||||||
.{
|
.{
|
||||||
.nick = "Working Directory",
|
|
||||||
.blurb = "The current working directory as reported by core.",
|
|
||||||
.default = null,
|
.default = null,
|
||||||
.accessor = C.privateStringFieldAccessor("pwd"),
|
.accessor = C.privateStringFieldAccessor("pwd"),
|
||||||
},
|
},
|
||||||
|
|
@ -222,8 +210,6 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?[:0]const u8,
|
?[:0]const u8,
|
||||||
.{
|
.{
|
||||||
.nick = "Title",
|
|
||||||
.blurb = "The title of the surface.",
|
|
||||||
.default = null,
|
.default = null,
|
||||||
.accessor = C.privateStringFieldAccessor("title"),
|
.accessor = C.privateStringFieldAccessor("title"),
|
||||||
},
|
},
|
||||||
|
|
@ -237,8 +223,6 @@ pub const Surface = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Zoom",
|
|
||||||
.blurb = "Whether the surface should be zoomed.",
|
|
||||||
.default = false,
|
.default = false,
|
||||||
.accessor = gobject.ext.privateFieldAccessor(
|
.accessor = gobject.ext.privateFieldAccessor(
|
||||||
Self,
|
Self,
|
||||||
|
|
@ -327,6 +311,18 @@ pub const Surface = extern struct {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Emitted just prior to the context menu appearing.
|
||||||
|
pub const menu = struct {
|
||||||
|
pub const name = "menu";
|
||||||
|
pub const connect = impl.connect;
|
||||||
|
const impl = gobject.ext.defineSignal(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
&.{},
|
||||||
|
void,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/// Emitted when the focus wants to be brought to the top and
|
/// Emitted when the focus wants to be brought to the top and
|
||||||
/// focused.
|
/// focused.
|
||||||
pub const @"present-request" = struct {
|
pub const @"present-request" = struct {
|
||||||
|
|
@ -462,6 +458,7 @@ pub const Surface = extern struct {
|
||||||
|
|
||||||
// Template binds
|
// Template binds
|
||||||
child_exited_overlay: *ChildExited,
|
child_exited_overlay: *ChildExited,
|
||||||
|
context_menu: *gtk.PopoverMenu,
|
||||||
drop_target: *gtk.DropTarget,
|
drop_target: *gtk.DropTarget,
|
||||||
progress_bar_overlay: *gtk.ProgressBar,
|
progress_bar_overlay: *gtk.ProgressBar,
|
||||||
|
|
||||||
|
|
@ -553,6 +550,11 @@ pub const Surface = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggleCommandPalette(self: *Self) bool {
|
||||||
|
// TODO: pass the surface with the action
|
||||||
|
return self.as(gtk.Widget).activateAction("win.toggle-command-palette", null) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the current progress report state.
|
/// Set the current progress report state.
|
||||||
pub fn setProgressReport(
|
pub fn setProgressReport(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
|
|
@ -1145,7 +1147,7 @@ pub const Surface = extern struct {
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Virtual Methods
|
// Virtual Methods
|
||||||
|
|
||||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
|
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
|
|
@ -1194,7 +1196,7 @@ pub const Surface = extern struct {
|
||||||
self.propConfig(undefined, null);
|
self.propConfig(undefined, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispose(self: *Self) callconv(.C) void {
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
if (priv.config) |v| {
|
if (priv.config) |v| {
|
||||||
v.unref();
|
v.unref();
|
||||||
|
|
@ -1218,7 +1220,7 @@ pub const Surface = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(self: *Self) callconv(.C) void {
|
fn finalize(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
if (priv.core_surface) |v| {
|
if (priv.core_surface) |v| {
|
||||||
// Remove ourselves from the list of known surfaces in the app.
|
// Remove ourselves from the list of known surfaces in the app.
|
||||||
|
|
@ -1273,11 +1275,34 @@ pub const Surface = extern struct {
|
||||||
return self.private().title;
|
return self.private().title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the title for this surface, copies the value.
|
||||||
|
pub fn setTitle(self: *Self, title: ?[:0]const u8) void {
|
||||||
|
const priv = self.private();
|
||||||
|
if (priv.title) |v| glib.free(@constCast(@ptrCast(v)));
|
||||||
|
priv.title = null;
|
||||||
|
if (title) |v| priv.title = glib.ext.dupeZ(u8, v);
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.title.impl.param_spec);
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the pwd property without a copy.
|
/// Returns the pwd property without a copy.
|
||||||
pub fn getPwd(self: *Self) ?[:0]const u8 {
|
pub fn getPwd(self: *Self) ?[:0]const u8 {
|
||||||
return self.private().pwd;
|
return self.private().pwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the pwd for this surface, copies the value.
|
||||||
|
pub fn setPwd(self: *Self, pwd: ?[:0]const u8) void {
|
||||||
|
const priv = self.private();
|
||||||
|
if (priv.pwd) |v| glib.free(@constCast(@ptrCast(v)));
|
||||||
|
priv.pwd = null;
|
||||||
|
if (pwd) |v| priv.pwd = glib.ext.dupeZ(u8, v);
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.pwd.impl.param_spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the focus state of this surface.
|
||||||
|
pub fn getFocused(self: *Self) bool {
|
||||||
|
return self.private().focused;
|
||||||
|
}
|
||||||
|
|
||||||
/// Change the configuration for this surface.
|
/// Change the configuration for this surface.
|
||||||
pub fn setConfig(self: *Self, config: *Config) void {
|
pub fn setConfig(self: *Self, config: *Config) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
|
|
@ -1330,6 +1355,34 @@ pub const Surface = extern struct {
|
||||||
self.as(gobject.Object).notifyByPspec(properties.@"min-size".impl.param_spec);
|
self.as(gobject.Object).notifyByPspec(properties.@"min-size".impl.param_spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getMouseShape(self: *Self) terminal.MouseShape {
|
||||||
|
return self.private().mouse_shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setMouseShape(self: *Self, shape: terminal.MouseShape) void {
|
||||||
|
const priv = self.private();
|
||||||
|
priv.mouse_shape = shape;
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.@"mouse-shape".impl.param_spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getMouseHidden(self: *Self) bool {
|
||||||
|
return self.private().mouse_hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setMouseHidden(self: *Self, hidden: bool) void {
|
||||||
|
const priv = self.private();
|
||||||
|
priv.mouse_hidden = hidden;
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.@"mouse-hidden".impl.param_spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setMouseHoverUrl(self: *Self, url: ?[:0]const u8) void {
|
||||||
|
const priv = self.private();
|
||||||
|
if (priv.mouse_hover_url) |v| glib.free(@constCast(@ptrCast(v)));
|
||||||
|
priv.mouse_hover_url = null;
|
||||||
|
if (url) |v| priv.mouse_hover_url = glib.ext.dupeZ(u8, v);
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.@"mouse-hover-url".impl.param_spec);
|
||||||
|
}
|
||||||
|
|
||||||
fn propConfig(
|
fn propConfig(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
_: *gobject.ParamSpec,
|
_: *gobject.ParamSpec,
|
||||||
|
|
@ -1473,6 +1526,16 @@ pub const Surface = extern struct {
|
||||||
self.close(.{ .surface = false });
|
self.close(.{ .surface = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contextMenuClosed(
|
||||||
|
_: *gtk.PopoverMenu,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
// When the context menu closes, it moves focus back to the tab
|
||||||
|
// bar if there are tabs. That's not correct. We need to grab it
|
||||||
|
// on the surface.
|
||||||
|
self.grabFocus();
|
||||||
|
}
|
||||||
|
|
||||||
fn dtDrop(
|
fn dtDrop(
|
||||||
_: *gtk.DropTarget,
|
_: *gtk.DropTarget,
|
||||||
value: *gobject.Value,
|
value: *gobject.Value,
|
||||||
|
|
@ -1604,6 +1667,7 @@ pub const Surface = extern struct {
|
||||||
priv.focused = true;
|
priv.focused = true;
|
||||||
priv.im_context.as(gtk.IMContext).focusIn();
|
priv.im_context.as(gtk.IMContext).focusIn();
|
||||||
_ = glib.idleAddOnce(idleFocus, self.ref());
|
_ = glib.idleAddOnce(idleFocus, self.ref());
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ecFocusLeave(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
|
fn ecFocusLeave(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
|
||||||
|
|
@ -1611,6 +1675,7 @@ pub const Surface = extern struct {
|
||||||
priv.focused = false;
|
priv.focused = false;
|
||||||
priv.im_context.as(gtk.IMContext).focusOut();
|
priv.im_context.as(gtk.IMContext).focusOut();
|
||||||
_ = glib.idleAddOnce(idleFocus, self.ref());
|
_ = glib.idleAddOnce(idleFocus, self.ref());
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The focus callback must be triggered on an idle loop source because
|
/// The focus callback must be triggered on an idle loop source because
|
||||||
|
|
@ -1647,9 +1712,9 @@ pub const Surface = extern struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report the event
|
// Report the event
|
||||||
|
const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton());
|
||||||
const consumed = if (priv.core_surface) |surface| consumed: {
|
const consumed = if (priv.core_surface) |surface| consumed: {
|
||||||
const gtk_mods = event.getModifierState();
|
const gtk_mods = event.getModifierState();
|
||||||
const button = translateMouseButton(gesture.as(gtk.GestureSingle).getCurrentButton());
|
|
||||||
const mods = gtk_key.translateMods(gtk_mods);
|
const mods = gtk_key.translateMods(gtk_mods);
|
||||||
break :consumed surface.mouseButtonCallback(
|
break :consumed surface.mouseButtonCallback(
|
||||||
.press,
|
.press,
|
||||||
|
|
@ -1661,10 +1726,28 @@ pub const Surface = extern struct {
|
||||||
};
|
};
|
||||||
} else false;
|
} else false;
|
||||||
|
|
||||||
// TODO: context menu
|
// If a right click isn't consumed, mouseButtonCallback selects the hovered
|
||||||
_ = consumed;
|
// word and returns false. We can use this to handle the context menu
|
||||||
_ = x;
|
// opening under normal scenarios.
|
||||||
_ = y;
|
if (!consumed and button == .right) {
|
||||||
|
signals.menu.impl.emit(
|
||||||
|
self,
|
||||||
|
null,
|
||||||
|
.{},
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const rect: gdk.Rectangle = .{
|
||||||
|
.f_x = @intFromFloat(x),
|
||||||
|
.f_y = @intFromFloat(y),
|
||||||
|
.f_width = 1,
|
||||||
|
.f_height = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const popover = priv.context_menu.as(gtk.Popover);
|
||||||
|
popover.setPointingTo(&rect);
|
||||||
|
popover.popup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gcMouseUp(
|
fn gcMouseUp(
|
||||||
|
|
@ -2234,6 +2317,7 @@ pub const Surface = extern struct {
|
||||||
const C = Common(Self, Private);
|
const C = Common(Self, Private);
|
||||||
pub const as = C.as;
|
pub const as = C.as;
|
||||||
pub const ref = C.ref;
|
pub const ref = C.ref;
|
||||||
|
pub const refSink = C.refSink;
|
||||||
pub const unref = C.unref;
|
pub const unref = C.unref;
|
||||||
const private = C.private;
|
const private = C.private;
|
||||||
|
|
||||||
|
|
@ -2242,7 +2326,7 @@ pub const Surface = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
gobject.ext.ensureType(ResizeOverlay);
|
gobject.ext.ensureType(ResizeOverlay);
|
||||||
gobject.ext.ensureType(ChildExited);
|
gobject.ext.ensureType(ChildExited);
|
||||||
gtk.Widget.Class.setTemplateFromResource(
|
gtk.Widget.Class.setTemplateFromResource(
|
||||||
|
|
@ -2259,6 +2343,7 @@ pub const Surface = extern struct {
|
||||||
class.bindTemplateChildPrivate("url_left", .{});
|
class.bindTemplateChildPrivate("url_left", .{});
|
||||||
class.bindTemplateChildPrivate("url_right", .{});
|
class.bindTemplateChildPrivate("url_right", .{});
|
||||||
class.bindTemplateChildPrivate("child_exited_overlay", .{});
|
class.bindTemplateChildPrivate("child_exited_overlay", .{});
|
||||||
|
class.bindTemplateChildPrivate("context_menu", .{});
|
||||||
class.bindTemplateChildPrivate("progress_bar_overlay", .{});
|
class.bindTemplateChildPrivate("progress_bar_overlay", .{});
|
||||||
class.bindTemplateChildPrivate("resize_overlay", .{});
|
class.bindTemplateChildPrivate("resize_overlay", .{});
|
||||||
class.bindTemplateChildPrivate("drop_target", .{});
|
class.bindTemplateChildPrivate("drop_target", .{});
|
||||||
|
|
@ -2288,6 +2373,7 @@ pub const Surface = extern struct {
|
||||||
class.bindTemplateCallback("url_mouse_enter", &ecUrlMouseEnter);
|
class.bindTemplateCallback("url_mouse_enter", &ecUrlMouseEnter);
|
||||||
class.bindTemplateCallback("url_mouse_leave", &ecUrlMouseLeave);
|
class.bindTemplateCallback("url_mouse_leave", &ecUrlMouseLeave);
|
||||||
class.bindTemplateCallback("child_exited_close", &childExitedClose);
|
class.bindTemplateCallback("child_exited_close", &childExitedClose);
|
||||||
|
class.bindTemplateCallback("context_menu_closed", &contextMenuClosed);
|
||||||
class.bindTemplateCallback("notify_config", &propConfig);
|
class.bindTemplateCallback("notify_config", &propConfig);
|
||||||
class.bindTemplateCallback("notify_mouse_hover_url", &propMouseHoverUrl);
|
class.bindTemplateCallback("notify_mouse_hover_url", &propMouseHoverUrl);
|
||||||
class.bindTemplateCallback("notify_mouse_hidden", &propMouseHidden);
|
class.bindTemplateCallback("notify_mouse_hidden", &propMouseHidden);
|
||||||
|
|
@ -2315,6 +2401,7 @@ pub const Surface = extern struct {
|
||||||
signals.@"clipboard-read".impl.register(.{});
|
signals.@"clipboard-read".impl.register(.{});
|
||||||
signals.@"clipboard-write".impl.register(.{});
|
signals.@"clipboard-write".impl.register(.{});
|
||||||
signals.init.impl.register(.{});
|
signals.init.impl.register(.{});
|
||||||
|
signals.menu.impl.register(.{});
|
||||||
signals.@"present-request".impl.register(.{});
|
signals.@"present-request".impl.register(.{});
|
||||||
signals.@"toggle-fullscreen".impl.register(.{});
|
signals.@"toggle-fullscreen".impl.register(.{});
|
||||||
signals.@"toggle-maximize".impl.register(.{});
|
signals.@"toggle-maximize".impl.register(.{});
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,6 @@ const SurfaceChildExitedBanner = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*apprt.surface.Message.ChildExited,
|
?*apprt.surface.Message.ChildExited,
|
||||||
.{
|
.{
|
||||||
.nick = "Data",
|
|
||||||
.blurb = "The child exit data.",
|
|
||||||
.accessor = C.privateBoxedFieldAccessor("data"),
|
.accessor = C.privateBoxedFieldAccessor("data"),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -72,7 +70,7 @@ const SurfaceChildExitedBanner = extern struct {
|
||||||
pub var offset: c_int = 0;
|
pub var offset: c_int = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,7 +132,7 @@ const SurfaceChildExitedBanner = extern struct {
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
|
|
||||||
fn dispose(self: *Self) callconv(.C) void {
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
gtk.Widget.disposeTemplate(
|
gtk.Widget.disposeTemplate(
|
||||||
self.as(gtk.Widget),
|
self.as(gtk.Widget),
|
||||||
getGObjectType(),
|
getGObjectType(),
|
||||||
|
|
@ -146,7 +144,7 @@ const SurfaceChildExitedBanner = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(self: *Self) callconv(.C) void {
|
fn finalize(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
if (priv.data) |v| {
|
if (priv.data) |v| {
|
||||||
glib.ext.destroy(v);
|
glib.ext.destroy(v);
|
||||||
|
|
@ -170,7 +168,7 @@ const SurfaceChildExitedBanner = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
gtk.Widget.Class.setTemplateFromResource(
|
gtk.Widget.Class.setTemplateFromResource(
|
||||||
class.as(gtk.Widget.Class),
|
class.as(gtk.Widget.Class),
|
||||||
comptime gresource.blueprint(.{
|
comptime gresource.blueprint(.{
|
||||||
|
|
@ -255,7 +253,7 @@ const SurfaceChildExitedNoop = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
_ = class;
|
_ = class;
|
||||||
signals.@"close-request".impl.register(.{});
|
signals.@"close-request".impl.register(.{});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ const Common = @import("../class.zig").Common;
|
||||||
const Config = @import("config.zig").Config;
|
const Config = @import("config.zig").Config;
|
||||||
const Application = @import("application.zig").Application;
|
const Application = @import("application.zig").Application;
|
||||||
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
||||||
|
const SplitTree = @import("split_tree.zig").SplitTree;
|
||||||
const Surface = @import("surface.zig").Surface;
|
const Surface = @import("surface.zig").Surface;
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk_ghostty_window);
|
const log = std.log.scoped(.gtk_ghostty_window);
|
||||||
|
|
@ -35,7 +36,7 @@ pub const Tab = extern struct {
|
||||||
});
|
});
|
||||||
|
|
||||||
pub const properties = struct {
|
pub const properties = struct {
|
||||||
/// The active surface is the focus that should be receiving all
|
/// The active surface is the surface that should be receiving all
|
||||||
/// surface-targeted actions. This is usually the focused surface,
|
/// surface-targeted actions. This is usually the focused surface,
|
||||||
/// but may also not be focused if the user has selected a non-surface
|
/// but may also not be focused if the user has selected a non-surface
|
||||||
/// widget.
|
/// widget.
|
||||||
|
|
@ -46,8 +47,6 @@ pub const Tab = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*Surface,
|
?*Surface,
|
||||||
.{
|
.{
|
||||||
.nick = "Active Surface",
|
|
||||||
.blurb = "The currently active surface.",
|
|
||||||
.accessor = gobject.ext.typedAccessor(
|
.accessor = gobject.ext.typedAccessor(
|
||||||
Self,
|
Self,
|
||||||
?*Surface,
|
?*Surface,
|
||||||
|
|
@ -66,13 +65,29 @@ pub const Tab = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*Config,
|
?*Config,
|
||||||
.{
|
.{
|
||||||
.nick = "Config",
|
|
||||||
.blurb = "The configuration that this surface is using.",
|
|
||||||
.accessor = C.privateObjFieldAccessor("config"),
|
.accessor = C.privateObjFieldAccessor("config"),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const @"surface-tree" = struct {
|
||||||
|
pub const name = "surface-tree";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
?*Surface.Tree,
|
||||||
|
.{
|
||||||
|
.accessor = gobject.ext.typedAccessor(
|
||||||
|
Self,
|
||||||
|
?*Surface.Tree,
|
||||||
|
.{
|
||||||
|
.getter = getSurfaceTree,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
pub const title = struct {
|
pub const title = struct {
|
||||||
pub const name = "title";
|
pub const name = "title";
|
||||||
pub const get = impl.get;
|
pub const get = impl.get;
|
||||||
|
|
@ -82,8 +97,6 @@ pub const Tab = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?[:0]const u8,
|
?[:0]const u8,
|
||||||
.{
|
.{
|
||||||
.nick = "Title",
|
|
||||||
.blurb = "The title of the active surface.",
|
|
||||||
.default = null,
|
.default = null,
|
||||||
.accessor = C.privateStringFieldAccessor("title"),
|
.accessor = C.privateStringFieldAccessor("title"),
|
||||||
},
|
},
|
||||||
|
|
@ -117,7 +130,7 @@ pub const Tab = extern struct {
|
||||||
surface_bindings: *gobject.BindingGroup,
|
surface_bindings: *gobject.BindingGroup,
|
||||||
|
|
||||||
// Template bindings
|
// Template bindings
|
||||||
surface: *Surface,
|
split_tree: *SplitTree,
|
||||||
|
|
||||||
pub var offset: c_int = 0;
|
pub var offset: c_int = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -125,15 +138,13 @@ pub const Tab = extern struct {
|
||||||
/// Set the parent of this tab page. This only affects the first surface
|
/// Set the parent of this tab page. This only affects the first surface
|
||||||
/// ever created for a tab. If a surface was already created this does
|
/// ever created for a tab. If a surface was already created this does
|
||||||
/// nothing.
|
/// nothing.
|
||||||
pub fn setParent(
|
pub fn setParent(self: *Self, parent: *CoreSurface) void {
|
||||||
self: *Self,
|
if (self.getActiveSurface()) |surface| {
|
||||||
parent: *CoreSurface,
|
surface.setParent(parent);
|
||||||
) void {
|
}
|
||||||
const priv = self.private();
|
|
||||||
priv.surface.setParent(parent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
|
|
||||||
// If our configuration is null then we get the configuration
|
// If our configuration is null then we get the configuration
|
||||||
|
|
@ -153,13 +164,15 @@ pub const Tab = extern struct {
|
||||||
.{},
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Eventually this should be set dynamically based on the
|
// Create our initial surface in the split tree.
|
||||||
// current active surface.
|
priv.split_tree.newSplit(.right, null) catch |err| switch (err) {
|
||||||
priv.surface_bindings.setSource(priv.surface.as(gobject.Object));
|
error.OutOfMemory => {
|
||||||
|
// TODO: We should make our "no surfaces" state more aesthetically
|
||||||
// We need to do this so that the title initializes properly,
|
// pleasing and show something like an "Oops, something went wrong"
|
||||||
// I think because its a dynamic getter.
|
// message. For now, this is incredibly unlikely.
|
||||||
self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec);
|
@panic("oom");
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
|
|
@ -167,15 +180,26 @@ pub const Tab = extern struct {
|
||||||
|
|
||||||
/// Get the currently active surface. See the "active-surface" property.
|
/// Get the currently active surface. See the "active-surface" property.
|
||||||
/// This does not ref the value.
|
/// This does not ref the value.
|
||||||
pub fn getActiveSurface(self: *Self) *Surface {
|
pub fn getActiveSurface(self: *Self) ?*Surface {
|
||||||
|
return self.getSplitTree().getActiveSurface();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the surface tree of this tab.
|
||||||
|
pub fn getSurfaceTree(self: *Self) ?*Surface.Tree {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
return priv.surface;
|
return priv.split_tree.getTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the split tree widget that is in this tab.
|
||||||
|
pub fn getSplitTree(self: *Self) *SplitTree {
|
||||||
|
const priv = self.private();
|
||||||
|
return priv.split_tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this tab needs confirmation before quitting based
|
/// Returns true if this tab needs confirmation before quitting based
|
||||||
/// on the various Ghostty configurations.
|
/// on the various Ghostty configurations.
|
||||||
pub fn getNeedsConfirmQuit(self: *Self) bool {
|
pub fn getNeedsConfirmQuit(self: *Self) bool {
|
||||||
const surface = self.getActiveSurface();
|
const surface = self.getActiveSurface() orelse return false;
|
||||||
const core_surface = surface.core() orelse return false;
|
const core_surface = surface.core() orelse return false;
|
||||||
return core_surface.needsConfirmQuit();
|
return core_surface.needsConfirmQuit();
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +207,7 @@ pub const Tab = extern struct {
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
|
|
||||||
fn dispose(self: *Self) callconv(.C) void {
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
if (priv.config) |v| {
|
if (priv.config) |v| {
|
||||||
v.unref();
|
v.unref();
|
||||||
|
|
@ -202,7 +226,7 @@ pub const Tab = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(self: *Self) callconv(.C) void {
|
fn finalize(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
if (priv.title) |v| {
|
if (priv.title) |v| {
|
||||||
glib.free(@constCast(@ptrCast(v)));
|
glib.free(@constCast(@ptrCast(v)));
|
||||||
|
|
@ -218,27 +242,40 @@ pub const Tab = extern struct {
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Signal handlers
|
// Signal handlers
|
||||||
|
|
||||||
fn surfaceCloseRequest(
|
fn propSplitTree(
|
||||||
_: *Surface,
|
_: *SplitTree,
|
||||||
scope: *const Surface.CloseScope,
|
_: *gobject.ParamSpec,
|
||||||
self: *Self,
|
self: *Self,
|
||||||
) callconv(.c) void {
|
) callconv(.c) void {
|
||||||
switch (scope.*) {
|
self.as(gobject.Object).notifyByPspec(properties.@"surface-tree".impl.param_spec);
|
||||||
// Handled upstream... we don't control our window close.
|
|
||||||
.window => return,
|
|
||||||
|
|
||||||
// Presently both the same, results in the tab closing.
|
// If our tree is empty we close the tab.
|
||||||
.surface, .tab => {
|
const tree: *const Surface.Tree = self.getSurfaceTree() orelse &.empty;
|
||||||
signals.@"close-request".impl.emit(
|
if (tree.isEmpty()) {
|
||||||
self,
|
signals.@"close-request".impl.emit(
|
||||||
null,
|
self,
|
||||||
.{},
|
null,
|
||||||
null,
|
.{},
|
||||||
);
|
null,
|
||||||
},
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn propActiveSurface(
|
||||||
|
_: *SplitTree,
|
||||||
|
_: *gobject.ParamSpec,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
const priv = self.private();
|
||||||
|
priv.surface_bindings.setSource(null);
|
||||||
|
if (self.getActiveSurface()) |surface| {
|
||||||
|
priv.surface_bindings.setSource(surface.as(gobject.Object));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec);
|
||||||
|
}
|
||||||
|
|
||||||
const C = Common(Self, Private);
|
const C = Common(Self, Private);
|
||||||
pub const as = C.as;
|
pub const as = C.as;
|
||||||
pub const ref = C.ref;
|
pub const ref = C.ref;
|
||||||
|
|
@ -250,7 +287,8 @@ pub const Tab = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
|
gobject.ext.ensureType(SplitTree);
|
||||||
gobject.ext.ensureType(Surface);
|
gobject.ext.ensureType(Surface);
|
||||||
gtk.Widget.Class.setTemplateFromResource(
|
gtk.Widget.Class.setTemplateFromResource(
|
||||||
class.as(gtk.Widget.Class),
|
class.as(gtk.Widget.Class),
|
||||||
|
|
@ -265,14 +303,16 @@ pub const Tab = extern struct {
|
||||||
gobject.ext.registerProperties(class, &.{
|
gobject.ext.registerProperties(class, &.{
|
||||||
properties.@"active-surface".impl,
|
properties.@"active-surface".impl,
|
||||||
properties.config.impl,
|
properties.config.impl,
|
||||||
|
properties.@"surface-tree".impl,
|
||||||
properties.title.impl,
|
properties.title.impl,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bindings
|
// Bindings
|
||||||
class.bindTemplateChildPrivate("surface", .{});
|
class.bindTemplateChildPrivate("split_tree", .{});
|
||||||
|
|
||||||
// Template Callbacks
|
// Template Callbacks
|
||||||
class.bindTemplateCallback("surface_close_request", &surfaceCloseRequest);
|
class.bindTemplateCallback("notify_active_surface", &propActiveSurface);
|
||||||
|
class.bindTemplateCallback("notify_tree", &propSplitTree);
|
||||||
|
|
||||||
// Signals
|
// Signals
|
||||||
signals.@"close-request".impl.register(.{});
|
signals.@"close-request".impl.register(.{});
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ const gtk = @import("gtk");
|
||||||
const i18n = @import("../../../os/main.zig").i18n;
|
const i18n = @import("../../../os/main.zig").i18n;
|
||||||
const apprt = @import("../../../apprt.zig");
|
const apprt = @import("../../../apprt.zig");
|
||||||
const configpkg = @import("../../../config.zig");
|
const configpkg = @import("../../../config.zig");
|
||||||
|
const TitlebarStyle = configpkg.Config.GtkTitlebarStyle;
|
||||||
const input = @import("../../../input.zig");
|
const input = @import("../../../input.zig");
|
||||||
const CoreSurface = @import("../../../Surface.zig");
|
const CoreSurface = @import("../../../Surface.zig");
|
||||||
const ext = @import("../ext.zig");
|
const ext = @import("../ext.zig");
|
||||||
|
|
@ -22,9 +23,12 @@ const Common = @import("../class.zig").Common;
|
||||||
const Config = @import("config.zig").Config;
|
const Config = @import("config.zig").Config;
|
||||||
const Application = @import("application.zig").Application;
|
const Application = @import("application.zig").Application;
|
||||||
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog;
|
||||||
|
const SplitTree = @import("split_tree.zig").SplitTree;
|
||||||
const Surface = @import("surface.zig").Surface;
|
const Surface = @import("surface.zig").Surface;
|
||||||
const Tab = @import("tab.zig").Tab;
|
const Tab = @import("tab.zig").Tab;
|
||||||
const DebugWarning = @import("debug_warning.zig").DebugWarning;
|
const DebugWarning = @import("debug_warning.zig").DebugWarning;
|
||||||
|
const CommandPalette = @import("command_palette.zig").CommandPalette;
|
||||||
|
const WeakRef = @import("../weak_ref.zig").WeakRef;
|
||||||
|
|
||||||
const log = std.log.scoped(.gtk_ghostty_window);
|
const log = std.log.scoped(.gtk_ghostty_window);
|
||||||
|
|
||||||
|
|
@ -52,8 +56,6 @@ pub const Window = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*Surface,
|
?*Surface,
|
||||||
.{
|
.{
|
||||||
.nick = "Active Surface",
|
|
||||||
.blurb = "The currently active surface.",
|
|
||||||
.accessor = gobject.ext.typedAccessor(
|
.accessor = gobject.ext.typedAccessor(
|
||||||
Self,
|
Self,
|
||||||
?*Surface,
|
?*Surface,
|
||||||
|
|
@ -72,8 +74,6 @@ pub const Window = extern struct {
|
||||||
Self,
|
Self,
|
||||||
?*Config,
|
?*Config,
|
||||||
.{
|
.{
|
||||||
.nick = "Config",
|
|
||||||
.blurb = "The configuration that this surface is using.",
|
|
||||||
.accessor = C.privateObjFieldAccessor("config"),
|
.accessor = C.privateObjFieldAccessor("config"),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -86,8 +86,6 @@ pub const Window = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Debug",
|
|
||||||
.blurb = "True if runtime safety checks are enabled.",
|
|
||||||
.default = build_config.is_debug,
|
.default = build_config.is_debug,
|
||||||
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
||||||
.getter = struct {
|
.getter = struct {
|
||||||
|
|
@ -100,6 +98,25 @@ pub const Window = extern struct {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const @"titlebar-style" = struct {
|
||||||
|
pub const name = "titlebar-style";
|
||||||
|
const impl = gobject.ext.defineProperty(
|
||||||
|
name,
|
||||||
|
Self,
|
||||||
|
TitlebarStyle,
|
||||||
|
.{
|
||||||
|
.default = .native,
|
||||||
|
.accessor = gobject.ext.typedAccessor(
|
||||||
|
Self,
|
||||||
|
TitlebarStyle,
|
||||||
|
.{
|
||||||
|
.getter = Self.getTitlebarStyle,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
pub const @"headerbar-visible" = struct {
|
pub const @"headerbar-visible" = struct {
|
||||||
pub const name = "headerbar-visible";
|
pub const name = "headerbar-visible";
|
||||||
const impl = gobject.ext.defineProperty(
|
const impl = gobject.ext.defineProperty(
|
||||||
|
|
@ -107,8 +124,6 @@ pub const Window = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Headerbar Visible",
|
|
||||||
.blurb = "True if the headerbar is visible.",
|
|
||||||
.default = true,
|
.default = true,
|
||||||
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
||||||
.getter = Self.getHeaderbarVisible,
|
.getter = Self.getHeaderbarVisible,
|
||||||
|
|
@ -117,23 +132,6 @@ pub const Window = extern struct {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const @"background-opaque" = struct {
|
|
||||||
pub const name = "background-opaque";
|
|
||||||
const impl = gobject.ext.defineProperty(
|
|
||||||
name,
|
|
||||||
Self,
|
|
||||||
bool,
|
|
||||||
.{
|
|
||||||
.nick = "Background Opaque",
|
|
||||||
.blurb = "True if the background should be opaque.",
|
|
||||||
.default = true,
|
|
||||||
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
|
||||||
.getter = Self.getBackgroundOpaque,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const @"quick-terminal" = struct {
|
pub const @"quick-terminal" = struct {
|
||||||
pub const name = "quick-terminal";
|
pub const name = "quick-terminal";
|
||||||
const impl = gobject.ext.defineProperty(
|
const impl = gobject.ext.defineProperty(
|
||||||
|
|
@ -141,8 +139,6 @@ pub const Window = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Quick Terminal",
|
|
||||||
.blurb = "Whether this window behaves like a quick terminal.",
|
|
||||||
.default = true,
|
.default = true,
|
||||||
.accessor = gobject.ext.privateFieldAccessor(
|
.accessor = gobject.ext.privateFieldAccessor(
|
||||||
Self,
|
Self,
|
||||||
|
|
@ -161,8 +157,6 @@ pub const Window = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Autohide Tab Bar",
|
|
||||||
.blurb = "If true, tab bar should autohide.",
|
|
||||||
.default = true,
|
.default = true,
|
||||||
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
||||||
.getter = Self.getTabsAutohide,
|
.getter = Self.getTabsAutohide,
|
||||||
|
|
@ -178,8 +172,6 @@ pub const Window = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Wide Tabs",
|
|
||||||
.blurb = "If true, tabs will be in the wide expanded style.",
|
|
||||||
.default = true,
|
.default = true,
|
||||||
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
||||||
.getter = Self.getTabsWide,
|
.getter = Self.getTabsWide,
|
||||||
|
|
@ -195,8 +187,6 @@ pub const Window = extern struct {
|
||||||
Self,
|
Self,
|
||||||
bool,
|
bool,
|
||||||
.{
|
.{
|
||||||
.nick = "Tab Bar Visibility",
|
|
||||||
.blurb = "If true, tab bar should be visible.",
|
|
||||||
.default = true,
|
.default = true,
|
||||||
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
.accessor = gobject.ext.typedAccessor(Self, bool, .{
|
||||||
.getter = Self.getTabsVisible,
|
.getter = Self.getTabsVisible,
|
||||||
|
|
@ -212,8 +202,6 @@ pub const Window = extern struct {
|
||||||
Self,
|
Self,
|
||||||
adw.ToolbarStyle,
|
adw.ToolbarStyle,
|
||||||
.{
|
.{
|
||||||
.nick = "Toolbar Style",
|
|
||||||
.blurb = "The style for the toolbar top/bottom bars.",
|
|
||||||
.default = .raised,
|
.default = .raised,
|
||||||
.accessor = gobject.ext.typedAccessor(
|
.accessor = gobject.ext.typedAccessor(
|
||||||
Self,
|
Self,
|
||||||
|
|
@ -261,6 +249,9 @@ pub const Window = extern struct {
|
||||||
/// See tabOverviewOpen for why we have this.
|
/// See tabOverviewOpen for why we have this.
|
||||||
tab_overview_focus_timer: ?c_uint = null,
|
tab_overview_focus_timer: ?c_uint = null,
|
||||||
|
|
||||||
|
/// A weak reference to a command palette.
|
||||||
|
command_palette: WeakRef(CommandPalette) = .empty,
|
||||||
|
|
||||||
// Template bindings
|
// Template bindings
|
||||||
tab_overview: *adw.TabOverview,
|
tab_overview: *adw.TabOverview,
|
||||||
tab_bar: *adw.TabBar,
|
tab_bar: *adw.TabBar,
|
||||||
|
|
@ -277,7 +268,7 @@ pub const Window = extern struct {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
fn init(self: *Self, _: *Class) callconv(.c) void {
|
||||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||||
|
|
||||||
// If our configuration is null then we get the configuration
|
// If our configuration is null then we get the configuration
|
||||||
|
|
@ -345,10 +336,16 @@ pub const Window = extern struct {
|
||||||
.{ "close-tab", actionCloseTab, null },
|
.{ "close-tab", actionCloseTab, null },
|
||||||
.{ "new-tab", actionNewTab, null },
|
.{ "new-tab", actionNewTab, null },
|
||||||
.{ "new-window", actionNewWindow, null },
|
.{ "new-window", actionNewWindow, null },
|
||||||
|
.{ "split-right", actionSplitRight, null },
|
||||||
|
.{ "split-left", actionSplitLeft, null },
|
||||||
|
.{ "split-up", actionSplitUp, null },
|
||||||
|
.{ "split-down", actionSplitDown, null },
|
||||||
.{ "copy", actionCopy, null },
|
.{ "copy", actionCopy, null },
|
||||||
.{ "paste", actionPaste, null },
|
.{ "paste", actionPaste, null },
|
||||||
.{ "reset", actionReset, null },
|
.{ "reset", actionReset, null },
|
||||||
.{ "clear", actionClear, null },
|
.{ "clear", actionClear, null },
|
||||||
|
// TODO: accept the surface that toggled the command palette
|
||||||
|
.{ "toggle-command-palette", actionToggleCommandPalette, null },
|
||||||
};
|
};
|
||||||
|
|
||||||
const action_map = self.as(gio.ActionMap);
|
const action_map = self.as(gio.ActionMap);
|
||||||
|
|
@ -420,6 +417,25 @@ pub const Window = extern struct {
|
||||||
.{ .sync_create = true },
|
.{ .sync_create = true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Bind signals
|
||||||
|
const split_tree = tab.getSplitTree();
|
||||||
|
_ = SplitTree.signals.changed.connect(
|
||||||
|
split_tree,
|
||||||
|
*Self,
|
||||||
|
tabSplitTreeChanged,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Run an initial notification for the surface tree so we can setup
|
||||||
|
// initial state.
|
||||||
|
tabSplitTreeChanged(
|
||||||
|
split_tree,
|
||||||
|
null,
|
||||||
|
split_tree.getTree(),
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -553,12 +569,12 @@ pub const Window = extern struct {
|
||||||
|
|
||||||
// Trigger all our dynamic properties that depend on the config.
|
// Trigger all our dynamic properties that depend on the config.
|
||||||
inline for (&.{
|
inline for (&.{
|
||||||
"background-opaque",
|
|
||||||
"headerbar-visible",
|
"headerbar-visible",
|
||||||
"tabs-autohide",
|
"tabs-autohide",
|
||||||
"tabs-visible",
|
"tabs-visible",
|
||||||
"tabs-wide",
|
"tabs-wide",
|
||||||
"toolbar-style",
|
"toolbar-style",
|
||||||
|
"titlebar-style",
|
||||||
}) |key| {
|
}) |key| {
|
||||||
self.as(gobject.Object).notifyByPspec(
|
self.as(gobject.Object).notifyByPspec(
|
||||||
@field(properties, key).impl.param_spec,
|
@field(properties, key).impl.param_spec,
|
||||||
|
|
@ -568,6 +584,12 @@ pub const Window = extern struct {
|
||||||
// Remainder uses the config
|
// Remainder uses the config
|
||||||
const config = if (priv.config) |v| v.get() else return;
|
const config = if (priv.config) |v| v.get() else return;
|
||||||
|
|
||||||
|
// Only add a solid background if we're opaque.
|
||||||
|
self.toggleCssClass(
|
||||||
|
"background",
|
||||||
|
config.@"background-opacity" >= 1,
|
||||||
|
);
|
||||||
|
|
||||||
// Apply class to color headerbar if window-theme is set to `ghostty` and
|
// Apply class to color headerbar if window-theme is set to `ghostty` and
|
||||||
// GTK version is before 4.16. The conditional is because above 4.16
|
// GTK version is before 4.16. The conditional is because above 4.16
|
||||||
// we use GTK CSS color variables.
|
// we use GTK CSS color variables.
|
||||||
|
|
@ -590,6 +612,27 @@ pub const Window = extern struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sync the state of any actions on this window.
|
||||||
|
fn syncActions(self: *Self) void {
|
||||||
|
const has_selection = selection: {
|
||||||
|
const surface = self.getActiveSurface() orelse
|
||||||
|
break :selection false;
|
||||||
|
const core_surface = surface.core() orelse
|
||||||
|
break :selection false;
|
||||||
|
break :selection core_surface.hasSelection();
|
||||||
|
};
|
||||||
|
|
||||||
|
const action_map: *gio.ActionMap = gobject.ext.cast(
|
||||||
|
gio.ActionMap,
|
||||||
|
self,
|
||||||
|
) orelse return;
|
||||||
|
const action: *gio.SimpleAction = gobject.ext.cast(
|
||||||
|
gio.SimpleAction,
|
||||||
|
action_map.lookupAction("copy") orelse return,
|
||||||
|
) orelse return;
|
||||||
|
action.setEnabled(@intFromBool(has_selection));
|
||||||
|
}
|
||||||
|
|
||||||
fn toggleCssClass(self: *Self, class: [:0]const u8, value: bool) void {
|
fn toggleCssClass(self: *Self, class: [:0]const u8, value: bool) void {
|
||||||
const widget = self.as(gtk.Widget);
|
const widget = self.as(gtk.Widget);
|
||||||
if (value)
|
if (value)
|
||||||
|
|
@ -623,6 +666,95 @@ pub const Window = extern struct {
|
||||||
self.private().toast_overlay.addToast(toast);
|
self.private().toast_overlay.addToast(toast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn connectSurfaceHandlers(
|
||||||
|
self: *Self,
|
||||||
|
tree: *const Surface.Tree,
|
||||||
|
) void {
|
||||||
|
const priv = self.private();
|
||||||
|
var it = tree.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
const surface = entry.view;
|
||||||
|
_ = Surface.signals.@"close-request".connect(
|
||||||
|
surface,
|
||||||
|
*Self,
|
||||||
|
surfaceCloseRequest,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
_ = Surface.signals.@"present-request".connect(
|
||||||
|
surface,
|
||||||
|
*Self,
|
||||||
|
surfacePresentRequest,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
_ = Surface.signals.@"clipboard-write".connect(
|
||||||
|
surface,
|
||||||
|
*Self,
|
||||||
|
surfaceClipboardWrite,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
_ = Surface.signals.menu.connect(
|
||||||
|
surface,
|
||||||
|
*Self,
|
||||||
|
surfaceMenu,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
_ = Surface.signals.@"toggle-fullscreen".connect(
|
||||||
|
surface,
|
||||||
|
*Self,
|
||||||
|
surfaceToggleFullscreen,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
_ = Surface.signals.@"toggle-maximize".connect(
|
||||||
|
surface,
|
||||||
|
*Self,
|
||||||
|
surfaceToggleMaximize,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we've never had a surface initialize yet, then we register
|
||||||
|
// this signal. Its theoretically possible to launch multiple surfaces
|
||||||
|
// before init so we could register this on multiple and that is not
|
||||||
|
// a problem because we'll check the flag again in each handler.
|
||||||
|
if (!priv.surface_init) {
|
||||||
|
_ = Surface.signals.init.connect(
|
||||||
|
surface,
|
||||||
|
*Self,
|
||||||
|
surfaceInit,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disconnect all the surface handlers for the given tree. This should
|
||||||
|
/// be called whenever a tree is no longer present in the window, e.g.
|
||||||
|
/// when a tab is detached or the tree changes.
|
||||||
|
fn disconnectSurfaceHandlers(
|
||||||
|
self: *Self,
|
||||||
|
tree: *const Surface.Tree,
|
||||||
|
) void {
|
||||||
|
var it = tree.iterator();
|
||||||
|
while (it.next()) |entry| {
|
||||||
|
const surface = entry.view;
|
||||||
|
_ = gobject.signalHandlersDisconnectMatched(
|
||||||
|
surface.as(gobject.Object),
|
||||||
|
.{ .data = true },
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Properties
|
// Properties
|
||||||
|
|
||||||
|
|
@ -702,6 +834,14 @@ pub const Window = extern struct {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn isFullscreen(self: *Window) bool {
|
||||||
|
return self.as(gtk.Window).isFullscreen() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isMaximized(self: *Window) bool {
|
||||||
|
return self.as(gtk.Window).isMaximized() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
fn getHeaderbarVisible(self: *Self) bool {
|
fn getHeaderbarVisible(self: *Self) bool {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
|
|
||||||
|
|
@ -713,52 +853,70 @@ pub const Window = extern struct {
|
||||||
if (priv.quick_terminal) return false;
|
if (priv.quick_terminal) return false;
|
||||||
|
|
||||||
// If we're fullscreen we never show the header bar.
|
// If we're fullscreen we never show the header bar.
|
||||||
if (self.as(gtk.Window).isFullscreen() != 0) return false;
|
if (self.isFullscreen()) return false;
|
||||||
|
|
||||||
// The remainder needs a config
|
// The remainder needs a config
|
||||||
const config_obj = self.private().config orelse return true;
|
const config_obj = self.private().config orelse return true;
|
||||||
const config = config_obj.get();
|
const config = config_obj.get();
|
||||||
|
|
||||||
// *Conditionally* disable the header bar when maximized,
|
// *Conditionally* disable the header bar when maximized, and
|
||||||
// and gtk-titlebar-hide-when-maximized is set
|
// gtk-titlebar-hide-when-maximized is set
|
||||||
if (self.as(gtk.Window).isMaximized() != 0 and
|
if (self.isMaximized() and config.@"gtk-titlebar-hide-when-maximized") {
|
||||||
config.@"gtk-titlebar-hide-when-maximized")
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return config.@"gtk-titlebar";
|
return switch (config.@"gtk-titlebar-style") {
|
||||||
}
|
// If the titlebar style is tabs never show the titlebar.
|
||||||
|
.tabs => false,
|
||||||
|
|
||||||
fn getBackgroundOpaque(self: *Self) bool {
|
// If the titlebar style is native show the titlebar if configured
|
||||||
const priv = self.private();
|
// to do so.
|
||||||
const config = (priv.config orelse return true).get();
|
.native => config.@"gtk-titlebar",
|
||||||
return config.@"background-opacity" >= 1.0;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getTabsAutohide(self: *Self) bool {
|
fn getTabsAutohide(self: *Self) bool {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
const config = if (priv.config) |v| v.get() else return true;
|
const config = if (priv.config) |v| v.get() else return true;
|
||||||
return switch (config.@"window-show-tab-bar") {
|
|
||||||
// Auto we always autohide... obviously.
|
|
||||||
.auto => true,
|
|
||||||
|
|
||||||
// Always we never autohide because we always show the tab bar.
|
return switch (config.@"gtk-titlebar-style") {
|
||||||
.always => false,
|
// If the titlebar style is tabs we cannot autohide.
|
||||||
|
.tabs => false,
|
||||||
|
|
||||||
// Never we autohide because it doesn't actually matter,
|
.native => switch (config.@"window-show-tab-bar") {
|
||||||
// since getTabsVisible will return false.
|
// Auto we always autohide... obviously.
|
||||||
.never => true,
|
.auto => true,
|
||||||
|
|
||||||
|
// Always we never autohide because we always show the tab bar.
|
||||||
|
.always => false,
|
||||||
|
|
||||||
|
// Never we autohide because it doesn't actually matter,
|
||||||
|
// since getTabsVisible will return false.
|
||||||
|
.never => true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getTabsVisible(self: *Self) bool {
|
fn getTabsVisible(self: *Self) bool {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
const config = if (priv.config) |v| v.get() else return true;
|
const config = if (priv.config) |v| v.get() else return true;
|
||||||
return switch (config.@"window-show-tab-bar") {
|
|
||||||
.always, .auto => true,
|
switch (config.@"gtk-titlebar-style") {
|
||||||
.never => false,
|
.tabs => {
|
||||||
};
|
// *Conditionally* disable the tab bar when maximized, the titlebar
|
||||||
|
// style is tabs, and gtk-titlebar-hide-when-maximized is set.
|
||||||
|
if (self.isMaximized() and config.@"gtk-titlebar-hide-when-maximized") return false;
|
||||||
|
|
||||||
|
// If the titlebar style is tabs the tab bar must always be visible.
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.native => {
|
||||||
|
return switch (config.@"window-show-tab-bar") {
|
||||||
|
.always, .auto => true,
|
||||||
|
.never => false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getTabsWide(self: *Self) bool {
|
fn getTabsWide(self: *Self) bool {
|
||||||
|
|
@ -777,6 +935,12 @@ pub const Window = extern struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getTitlebarStyle(self: *Self) TitlebarStyle {
|
||||||
|
const priv = self.private();
|
||||||
|
const config = if (priv.config) |v| v.get() else return .native;
|
||||||
|
return config.@"gtk-titlebar-style";
|
||||||
|
}
|
||||||
|
|
||||||
fn propConfig(
|
fn propConfig(
|
||||||
_: *adw.ApplicationWindow,
|
_: *adw.ApplicationWindow,
|
||||||
_: *gobject.ParamSpec,
|
_: *gobject.ParamSpec,
|
||||||
|
|
@ -845,23 +1009,7 @@ pub const Window = extern struct {
|
||||||
const active = button.getActive() != 0;
|
const active = button.getActive() != 0;
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
|
|
||||||
const has_selection = selection: {
|
self.syncActions();
|
||||||
const surface = self.getActiveSurface() orelse
|
|
||||||
break :selection false;
|
|
||||||
const core_surface = surface.core() orelse
|
|
||||||
break :selection false;
|
|
||||||
break :selection core_surface.hasSelection();
|
|
||||||
};
|
|
||||||
|
|
||||||
const action_map: *gio.ActionMap = gobject.ext.cast(
|
|
||||||
gio.ActionMap,
|
|
||||||
self,
|
|
||||||
) orelse return;
|
|
||||||
const action: *gio.SimpleAction = gobject.ext.cast(
|
|
||||||
gio.SimpleAction,
|
|
||||||
action_map.lookupAction("copy") orelse return,
|
|
||||||
) orelse return;
|
|
||||||
action.setEnabled(@intFromBool(has_selection));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn propQuickTerminal(
|
fn propQuickTerminal(
|
||||||
|
|
@ -884,16 +1032,6 @@ pub const Window = extern struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add or remove "background" CSS class depending on if the background
|
|
||||||
/// should be opaque.
|
|
||||||
fn propBackgroundOpaque(
|
|
||||||
_: *adw.ApplicationWindow,
|
|
||||||
_: *gobject.ParamSpec,
|
|
||||||
self: *Self,
|
|
||||||
) callconv(.c) void {
|
|
||||||
self.toggleCssClass("background", self.getBackgroundOpaque());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn propScaleFactor(
|
fn propScaleFactor(
|
||||||
_: *adw.ApplicationWindow,
|
_: *adw.ApplicationWindow,
|
||||||
_: *gobject.ParamSpec,
|
_: *gobject.ParamSpec,
|
||||||
|
|
@ -912,15 +1050,29 @@ pub const Window = extern struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn closureTitlebarStyleIsTab(
|
||||||
|
_: *Self,
|
||||||
|
value: TitlebarStyle,
|
||||||
|
) callconv(.c) c_int {
|
||||||
|
return @intFromBool(switch (value) {
|
||||||
|
.native => false,
|
||||||
|
.tabs => true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
|
|
||||||
fn dispose(self: *Self) callconv(.C) void {
|
fn dispose(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
|
|
||||||
|
priv.command_palette.set(null);
|
||||||
|
|
||||||
if (priv.config) |v| {
|
if (priv.config) |v| {
|
||||||
v.unref();
|
v.unref();
|
||||||
priv.config = null;
|
priv.config = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
priv.tab_bindings.setSource(null);
|
priv.tab_bindings.setSource(null);
|
||||||
|
|
||||||
gtk.Widget.disposeTemplate(
|
gtk.Widget.disposeTemplate(
|
||||||
|
|
@ -934,7 +1086,7 @@ pub const Window = extern struct {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(self: *Self) callconv(.C) void {
|
fn finalize(self: *Self) callconv(.c) void {
|
||||||
const priv = self.private();
|
const priv = self.private();
|
||||||
priv.tab_bindings.unref();
|
priv.tab_bindings.unref();
|
||||||
priv.winproto.deinit(Application.default().allocator());
|
priv.winproto.deinit(Application.default().allocator());
|
||||||
|
|
@ -1152,8 +1304,6 @@ pub const Window = extern struct {
|
||||||
_: c_int,
|
_: c_int,
|
||||||
self: *Self,
|
self: *Self,
|
||||||
) callconv(.c) void {
|
) callconv(.c) void {
|
||||||
const priv = self.private();
|
|
||||||
|
|
||||||
// Get the attached page which must be a Tab object.
|
// Get the attached page which must be a Tab object.
|
||||||
const child = page.getChild();
|
const child = page.getChild();
|
||||||
const tab = gobject.ext.cast(Tab, child) orelse return;
|
const tab = gobject.ext.cast(Tab, child) orelse return;
|
||||||
|
|
@ -1186,57 +1336,8 @@ pub const Window = extern struct {
|
||||||
// behavior is consistent with macOS and the previous GTK apprt,
|
// behavior is consistent with macOS and the previous GTK apprt,
|
||||||
// but that behavior was all implicit and not documented, so here
|
// but that behavior was all implicit and not documented, so here
|
||||||
// I am.
|
// I am.
|
||||||
//
|
if (tab.getSurfaceTree()) |tree| {
|
||||||
// TODO: When we have a split tree we'll want to attach to that.
|
self.connectSurfaceHandlers(tree);
|
||||||
const surface = tab.getActiveSurface();
|
|
||||||
_ = Surface.signals.@"close-request".connect(
|
|
||||||
surface,
|
|
||||||
*Self,
|
|
||||||
surfaceCloseRequest,
|
|
||||||
self,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
_ = Surface.signals.@"present-request".connect(
|
|
||||||
surface,
|
|
||||||
*Self,
|
|
||||||
surfacePresentRequest,
|
|
||||||
self,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
_ = Surface.signals.@"clipboard-write".connect(
|
|
||||||
surface,
|
|
||||||
*Self,
|
|
||||||
surfaceClipboardWrite,
|
|
||||||
self,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
_ = Surface.signals.@"toggle-fullscreen".connect(
|
|
||||||
surface,
|
|
||||||
*Self,
|
|
||||||
surfaceToggleFullscreen,
|
|
||||||
self,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
_ = Surface.signals.@"toggle-maximize".connect(
|
|
||||||
surface,
|
|
||||||
*Self,
|
|
||||||
surfaceToggleMaximize,
|
|
||||||
self,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
|
|
||||||
// If we've never had a surface initialize yet, then we register
|
|
||||||
// this signal. Its theoretically possible to launch multiple surfaces
|
|
||||||
// before init so we could register this on multiple and that is not
|
|
||||||
// a problem because we'll check the flag again in each handler.
|
|
||||||
if (!priv.surface_init) {
|
|
||||||
_ = Surface.signals.init.connect(
|
|
||||||
surface,
|
|
||||||
*Self,
|
|
||||||
surfaceInit,
|
|
||||||
self,
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1259,17 +1360,10 @@ pub const Window = extern struct {
|
||||||
self,
|
self,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove all the signals that have this window as the userdata.
|
// Remove the tree handlers
|
||||||
const surface = tab.getActiveSurface();
|
if (tab.getSurfaceTree()) |tree| {
|
||||||
_ = gobject.signalHandlersDisconnectMatched(
|
self.disconnectSurfaceHandlers(tree);
|
||||||
surface.as(gobject.Object),
|
}
|
||||||
.{ .data = true },
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
self,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tabViewCreateWindow(
|
fn tabViewCreateWindow(
|
||||||
|
|
@ -1355,6 +1449,13 @@ pub const Window = extern struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn surfaceMenu(
|
||||||
|
_: *Surface,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
self.syncActions();
|
||||||
|
}
|
||||||
|
|
||||||
fn surfacePresentRequest(
|
fn surfacePresentRequest(
|
||||||
surface: *Surface,
|
surface: *Surface,
|
||||||
self: *Self,
|
self: *Self,
|
||||||
|
|
@ -1452,6 +1553,21 @@ pub const Window = extern struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tabSplitTreeChanged(
|
||||||
|
_: *SplitTree,
|
||||||
|
old_tree: ?*const Surface.Tree,
|
||||||
|
new_tree: ?*const Surface.Tree,
|
||||||
|
self: *Self,
|
||||||
|
) callconv(.c) void {
|
||||||
|
if (old_tree) |tree| {
|
||||||
|
self.disconnectSurfaceHandlers(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_tree) |tree| {
|
||||||
|
self.connectSurfaceHandlers(tree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn actionAbout(
|
fn actionAbout(
|
||||||
_: *gio.SimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: ?*glib.Variant,
|
_: ?*glib.Variant,
|
||||||
|
|
@ -1528,6 +1644,38 @@ pub const Window = extern struct {
|
||||||
self.performBindingAction(.new_tab);
|
self.performBindingAction(.new_tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn actionSplitRight(
|
||||||
|
_: *gio.SimpleAction,
|
||||||
|
_: ?*glib.Variant,
|
||||||
|
self: *Window,
|
||||||
|
) callconv(.c) void {
|
||||||
|
self.performBindingAction(.{ .new_split = .right });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actionSplitLeft(
|
||||||
|
_: *gio.SimpleAction,
|
||||||
|
_: ?*glib.Variant,
|
||||||
|
self: *Window,
|
||||||
|
) callconv(.c) void {
|
||||||
|
self.performBindingAction(.{ .new_split = .left });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actionSplitUp(
|
||||||
|
_: *gio.SimpleAction,
|
||||||
|
_: ?*glib.Variant,
|
||||||
|
self: *Window,
|
||||||
|
) callconv(.c) void {
|
||||||
|
self.performBindingAction(.{ .new_split = .up });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actionSplitDown(
|
||||||
|
_: *gio.SimpleAction,
|
||||||
|
_: ?*glib.Variant,
|
||||||
|
self: *Window,
|
||||||
|
) callconv(.c) void {
|
||||||
|
self.performBindingAction(.{ .new_split = .down });
|
||||||
|
}
|
||||||
|
|
||||||
fn actionCopy(
|
fn actionCopy(
|
||||||
_: *gio.SimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: ?*glib.Variant,
|
_: ?*glib.Variant,
|
||||||
|
|
@ -1560,6 +1708,68 @@ pub const Window = extern struct {
|
||||||
self.performBindingAction(.clear_screen);
|
self.performBindingAction(.clear_screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Toggle the command palette.
|
||||||
|
///
|
||||||
|
/// TODO: accept the surface that toggled the command palette as a parameter
|
||||||
|
fn toggleCommandPalette(self: *Window) void {
|
||||||
|
const priv = self.private();
|
||||||
|
|
||||||
|
// Get a reference to a command palette. First check the weak reference
|
||||||
|
// that we save to see if we already have one stored. If we don't then
|
||||||
|
// create a new one.
|
||||||
|
const command_palette = priv.command_palette.get() orelse command_palette: {
|
||||||
|
// Create a fresh command palette.
|
||||||
|
const command_palette = CommandPalette.new();
|
||||||
|
|
||||||
|
// Synchronize our config to the command palette's config.
|
||||||
|
_ = gobject.Object.bindProperty(
|
||||||
|
self.as(gobject.Object),
|
||||||
|
"config",
|
||||||
|
command_palette.as(gobject.Object),
|
||||||
|
"config",
|
||||||
|
.{ .sync_create = true },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Listen to the activate signal to know if the user selected an option in
|
||||||
|
// the command palette.
|
||||||
|
_ = CommandPalette.signals.trigger.connect(
|
||||||
|
command_palette,
|
||||||
|
*Window,
|
||||||
|
signalCommandPaletteTrigger,
|
||||||
|
self,
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save a weak reference to the command palette. We use a weak reference to avoid
|
||||||
|
// reference counting cycles that might cause problems later.
|
||||||
|
priv.command_palette.set(command_palette);
|
||||||
|
|
||||||
|
break :command_palette command_palette;
|
||||||
|
};
|
||||||
|
defer command_palette.unref();
|
||||||
|
|
||||||
|
// Tell the command palette to toggle itself. If the dialog gets
|
||||||
|
// presented (instead of hidden) it will be modal over our window.
|
||||||
|
command_palette.toggle(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// React to a signal from a command palette asking an action to be performed.
|
||||||
|
fn signalCommandPaletteTrigger(_: *CommandPalette, action: *const input.Binding.Action, self: *Self) callconv(.c) void {
|
||||||
|
// If the activation actually has an action, perform it.
|
||||||
|
self.performBindingAction(action.*);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// React to a GTK action requesting that the command palette be toggled.
|
||||||
|
fn actionToggleCommandPalette(
|
||||||
|
_: *gio.SimpleAction,
|
||||||
|
_: ?*glib.Variant,
|
||||||
|
self: *Window,
|
||||||
|
) callconv(.c) void {
|
||||||
|
// TODO: accept the surface that toggled the command palette as a
|
||||||
|
// parameter
|
||||||
|
self.toggleCommandPalette();
|
||||||
|
}
|
||||||
|
|
||||||
const C = Common(Self, Private);
|
const C = Common(Self, Private);
|
||||||
pub const as = C.as;
|
pub const as = C.as;
|
||||||
pub const ref = C.ref;
|
pub const ref = C.ref;
|
||||||
|
|
@ -1571,7 +1781,7 @@ pub const Window = extern struct {
|
||||||
var parent: *Parent.Class = undefined;
|
var parent: *Parent.Class = undefined;
|
||||||
pub const Instance = Self;
|
pub const Instance = Self;
|
||||||
|
|
||||||
fn init(class: *Class) callconv(.C) void {
|
fn init(class: *Class) callconv(.c) void {
|
||||||
gobject.ext.ensureType(DebugWarning);
|
gobject.ext.ensureType(DebugWarning);
|
||||||
gtk.Widget.Class.setTemplateFromResource(
|
gtk.Widget.Class.setTemplateFromResource(
|
||||||
class.as(gtk.Widget.Class),
|
class.as(gtk.Widget.Class),
|
||||||
|
|
@ -1585,7 +1795,6 @@ pub const Window = extern struct {
|
||||||
// Properties
|
// Properties
|
||||||
gobject.ext.registerProperties(class, &.{
|
gobject.ext.registerProperties(class, &.{
|
||||||
properties.@"active-surface".impl,
|
properties.@"active-surface".impl,
|
||||||
properties.@"background-opaque".impl,
|
|
||||||
properties.config.impl,
|
properties.config.impl,
|
||||||
properties.debug.impl,
|
properties.debug.impl,
|
||||||
properties.@"headerbar-visible".impl,
|
properties.@"headerbar-visible".impl,
|
||||||
|
|
@ -1594,6 +1803,7 @@ pub const Window = extern struct {
|
||||||
properties.@"tabs-visible".impl,
|
properties.@"tabs-visible".impl,
|
||||||
properties.@"tabs-wide".impl,
|
properties.@"tabs-wide".impl,
|
||||||
properties.@"toolbar-style".impl,
|
properties.@"toolbar-style".impl,
|
||||||
|
properties.@"titlebar-style".impl,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bindings
|
// Bindings
|
||||||
|
|
@ -1615,13 +1825,13 @@ pub const Window = extern struct {
|
||||||
class.bindTemplateCallback("tab_create_window", &tabViewCreateWindow);
|
class.bindTemplateCallback("tab_create_window", &tabViewCreateWindow);
|
||||||
class.bindTemplateCallback("notify_n_pages", &tabViewNPages);
|
class.bindTemplateCallback("notify_n_pages", &tabViewNPages);
|
||||||
class.bindTemplateCallback("notify_selected_page", &tabViewSelectedPage);
|
class.bindTemplateCallback("notify_selected_page", &tabViewSelectedPage);
|
||||||
class.bindTemplateCallback("notify_background_opaque", &propBackgroundOpaque);
|
|
||||||
class.bindTemplateCallback("notify_config", &propConfig);
|
class.bindTemplateCallback("notify_config", &propConfig);
|
||||||
class.bindTemplateCallback("notify_fullscreened", &propFullscreened);
|
class.bindTemplateCallback("notify_fullscreened", &propFullscreened);
|
||||||
class.bindTemplateCallback("notify_maximized", &propMaximized);
|
class.bindTemplateCallback("notify_maximized", &propMaximized);
|
||||||
class.bindTemplateCallback("notify_menu_active", &propMenuActive);
|
class.bindTemplateCallback("notify_menu_active", &propMenuActive);
|
||||||
class.bindTemplateCallback("notify_quick_terminal", &propQuickTerminal);
|
class.bindTemplateCallback("notify_quick_terminal", &propQuickTerminal);
|
||||||
class.bindTemplateCallback("notify_scale_factor", &propScaleFactor);
|
class.bindTemplateCallback("notify_scale_factor", &propScaleFactor);
|
||||||
|
class.bindTemplateCallback("titlebar_style_is_tabs", &closureTitlebarStyleIsTab);
|
||||||
|
|
||||||
// Virtual methods
|
// Virtual methods
|
||||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
.transparent {
|
.transparent {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.window .split paned > separator {
|
||||||
|
background-color: rgba(36, 36, 36, 1);
|
||||||
|
background-clip: content-box;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,3 +101,39 @@ label.resize-overlay {
|
||||||
/* after GTK 4.16 is a requirement, switch to the following: */
|
/* after GTK 4.16 is a requirement, switch to the following: */
|
||||||
/* background-color: color-mix(in srgb, var(--error-bg-color), transparent); */
|
/* background-color: color-mix(in srgb, var(--error-bg-color), transparent); */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Command Palette
|
||||||
|
*/
|
||||||
|
.command-palette-search > image:first-child {
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-search > image:last-child {
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Splits
|
||||||
|
*/
|
||||||
|
|
||||||
|
.window .split paned > separator {
|
||||||
|
background-color: rgba(250, 250, 250, 1);
|
||||||
|
background-clip: content-box;
|
||||||
|
|
||||||
|
/* This works around the oversized drag area for the right side of GtkPaned.
|
||||||
|
*
|
||||||
|
* Upstream Gtk issue:
|
||||||
|
* https://gitlab.gnome.org/GNOME/gtk/-/issues/4484#note_2362002
|
||||||
|
*
|
||||||
|
* Ghostty issue:
|
||||||
|
* https://github.com/ghostty-org/ghostty/issues/3020
|
||||||
|
*
|
||||||
|
* Without this, it's not possible to select the first character on the
|
||||||
|
* right-hand side of a split.
|
||||||
|
*/
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ const input = @import("../../input.zig");
|
||||||
const winproto = @import("winproto.zig");
|
const winproto = @import("winproto.zig");
|
||||||
|
|
||||||
/// Returns a GTK accelerator string from a trigger.
|
/// Returns a GTK accelerator string from a trigger.
|
||||||
pub fn accelFromTrigger(buf: []u8, trigger: input.Binding.Trigger) !?[:0]const u8 {
|
pub fn accelFromTrigger(
|
||||||
|
buf: []u8,
|
||||||
|
trigger: input.Binding.Trigger,
|
||||||
|
) error{NoSpaceLeft}!?[:0]const u8 {
|
||||||
var buf_stream = std.io.fixedBufferStream(buf);
|
var buf_stream = std.io.fixedBufferStream(buf);
|
||||||
const writer = buf_stream.writer();
|
const writer = buf_stream.writer();
|
||||||
|
|
||||||
|
|
@ -30,7 +33,10 @@ pub fn accelFromTrigger(buf: []u8, trigger: input.Binding.Trigger) !?[:0]const u
|
||||||
|
|
||||||
/// Returns a XDG-compliant shortcuts string from a trigger.
|
/// Returns a XDG-compliant shortcuts string from a trigger.
|
||||||
/// Spec: https://specifications.freedesktop.org/shortcuts-spec/latest/
|
/// Spec: https://specifications.freedesktop.org/shortcuts-spec/latest/
|
||||||
pub fn xdgShortcutFromTrigger(buf: []u8, trigger: input.Binding.Trigger) !?[:0]const u8 {
|
pub fn xdgShortcutFromTrigger(
|
||||||
|
buf: []u8,
|
||||||
|
trigger: input.Binding.Trigger,
|
||||||
|
) error{NoSpaceLeft}!?[:0]const u8 {
|
||||||
var buf_stream = std.io.fixedBufferStream(buf);
|
var buf_stream = std.io.fixedBufferStream(buf);
|
||||||
const writer = buf_stream.writer();
|
const writer = buf_stream.writer();
|
||||||
|
|
||||||
|
|
@ -54,7 +60,7 @@ pub fn xdgShortcutFromTrigger(buf: []u8, trigger: input.Binding.Trigger) !?[:0]c
|
||||||
return slice[0 .. slice.len - 1 :0];
|
return slice[0 .. slice.len - 1 :0];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeTriggerKey(writer: anytype, trigger: input.Binding.Trigger) !bool {
|
fn writeTriggerKey(writer: anytype, trigger: input.Binding.Trigger) error{NoSpaceLeft}!bool {
|
||||||
switch (trigger.key) {
|
switch (trigger.key) {
|
||||||
.physical => |k| {
|
.physical => |k| {
|
||||||
const keyval = keyvalFromKey(k) orelse return false;
|
const keyval = keyvalFromKey(k) orelse return false;
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,32 @@ template $GhosttySurface: Adw.Bin {
|
||||||
focusable: false;
|
focusable: false;
|
||||||
focus-on-click: false;
|
focus-on-click: false;
|
||||||
|
|
||||||
GLArea gl_area {
|
child: Box {
|
||||||
realize => $gl_realize();
|
|
||||||
unrealize => $gl_unrealize();
|
|
||||||
render => $gl_render();
|
|
||||||
resize => $gl_resize();
|
|
||||||
hexpand: true;
|
hexpand: true;
|
||||||
vexpand: true;
|
vexpand: true;
|
||||||
focusable: true;
|
|
||||||
focus-on-click: true;
|
GLArea gl_area {
|
||||||
has-stencil-buffer: false;
|
realize => $gl_realize();
|
||||||
has-depth-buffer: false;
|
unrealize => $gl_unrealize();
|
||||||
use-es: false;
|
render => $gl_render();
|
||||||
}
|
resize => $gl_resize();
|
||||||
|
hexpand: true;
|
||||||
|
vexpand: true;
|
||||||
|
focusable: true;
|
||||||
|
focus-on-click: true;
|
||||||
|
has-stencil-buffer: false;
|
||||||
|
has-depth-buffer: false;
|
||||||
|
allowed-apis: gl;
|
||||||
|
}
|
||||||
|
|
||||||
|
PopoverMenu context_menu {
|
||||||
|
closed => $context_menu_closed();
|
||||||
|
menu-model: context_menu_model;
|
||||||
|
flags: nested;
|
||||||
|
halign: start;
|
||||||
|
has-arrow: false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
[overlay]
|
[overlay]
|
||||||
ProgressBar progress_bar_overlay {
|
ProgressBar progress_bar_overlay {
|
||||||
|
|
@ -122,3 +135,104 @@ IMMulticontext im_context {
|
||||||
preedit-end => $im_preedit_end();
|
preedit-end => $im_preedit_end();
|
||||||
commit => $im_commit();
|
commit => $im_commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menu context_menu_model {
|
||||||
|
section {
|
||||||
|
item {
|
||||||
|
label: _("Copy");
|
||||||
|
action: "win.copy";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("Paste");
|
||||||
|
action: "win.paste";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
item {
|
||||||
|
label: _("Clear");
|
||||||
|
action: "win.clear";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("Reset");
|
||||||
|
action: "win.reset";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
submenu {
|
||||||
|
label: _("Split");
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("Change Title…");
|
||||||
|
action: "win.prompt-title";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("Split Up");
|
||||||
|
action: "split-tree.new-up";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("Split Down");
|
||||||
|
action: "split-tree.new-down";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("Split Left");
|
||||||
|
action: "split-tree.new-left";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("Split Right");
|
||||||
|
action: "split-tree.new-right";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submenu {
|
||||||
|
label: _("Tab");
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("New Tab");
|
||||||
|
action: "win.new-tab";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("Close Tab");
|
||||||
|
action: "win.close-tab";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submenu {
|
||||||
|
label: _("Window");
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("New Window");
|
||||||
|
action: "win.new-window";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("Close Window");
|
||||||
|
action: "win.close";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
submenu {
|
||||||
|
label: _("Config");
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("Open Configuration");
|
||||||
|
action: "app.open-config";
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
label: _("Reload Configuration");
|
||||||
|
action: "app.reload-config";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Gio 2.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
Adw.Dialog dialog {
|
||||||
|
content-width: 700;
|
||||||
|
closed => $closed();
|
||||||
|
|
||||||
|
Adw.ToolbarView {
|
||||||
|
top-bar-style: flat;
|
||||||
|
|
||||||
|
[top]
|
||||||
|
Adw.HeaderBar {
|
||||||
|
[title]
|
||||||
|
Gtk.SearchEntry search {
|
||||||
|
hexpand: true;
|
||||||
|
placeholder-text: _("Execute a command…");
|
||||||
|
stop-search => $search_stopped();
|
||||||
|
activate => $search_activated();
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"command-palette-search",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ScrolledWindow {
|
||||||
|
min-content-height: 300;
|
||||||
|
|
||||||
|
Gtk.ListView view {
|
||||||
|
show-separators: true;
|
||||||
|
single-click-activate: true;
|
||||||
|
activate => $row_activated();
|
||||||
|
|
||||||
|
model: Gtk.SingleSelection model {
|
||||||
|
model: Gtk.FilterListModel {
|
||||||
|
incremental: true;
|
||||||
|
|
||||||
|
filter: Gtk.AnyFilter {
|
||||||
|
Gtk.StringFilter {
|
||||||
|
expression: expr item as <$GhosttyCommand>.title;
|
||||||
|
search: bind search.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.StringFilter {
|
||||||
|
expression: expr item as <$GhosttyCommand>.action-key;
|
||||||
|
search: bind search.text;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
model: Gio.ListStore source {
|
||||||
|
item-type: typeof<$GhosttyCommand>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"rich-list",
|
||||||
|
]
|
||||||
|
|
||||||
|
factory: Gtk.BuilderListItemFactory {
|
||||||
|
template Gtk.ListItem {
|
||||||
|
child: Gtk.Box {
|
||||||
|
orientation: horizontal;
|
||||||
|
spacing: 10;
|
||||||
|
tooltip-text: bind template.item as <$GhosttyCommand>.description;
|
||||||
|
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: vertical;
|
||||||
|
hexpand: true;
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
ellipsize: end;
|
||||||
|
halign: start;
|
||||||
|
wrap: false;
|
||||||
|
single-line-mode: true;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"title",
|
||||||
|
]
|
||||||
|
|
||||||
|
label: bind template.item as <$GhosttyCommand>.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
ellipsize: end;
|
||||||
|
halign: start;
|
||||||
|
wrap: false;
|
||||||
|
single-line-mode: true;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"subtitle",
|
||||||
|
"monospace",
|
||||||
|
]
|
||||||
|
|
||||||
|
label: bind template.item as <$GhosttyCommand>.action-key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ShortcutLabel {
|
||||||
|
accelerator: bind template.item as <$GhosttyCommand>.action;
|
||||||
|
valign: center;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $GhosttySplitTreeSplit: Adw.Bin {
|
||||||
|
styles [
|
||||||
|
"split",
|
||||||
|
]
|
||||||
|
|
||||||
|
// The double-nesting is required due to a GTK bug where you can't
|
||||||
|
// bind the first child of a builder layout. If you do, you get a double
|
||||||
|
// dispose. Easiest way to see that is simply remove this and see the
|
||||||
|
// GTK critical errors (and sometimes crashes).
|
||||||
|
Adw.Bin {
|
||||||
|
Paned paned {
|
||||||
|
notify::max-position => $notify_max_position();
|
||||||
|
notify::min-position => $notify_min_position();
|
||||||
|
notify::position => $notify_position();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $GhosttySplitTree: Box {
|
||||||
|
notify::tree => $notify_tree();
|
||||||
|
orientation: vertical;
|
||||||
|
|
||||||
|
Adw.Bin tree_bin {
|
||||||
|
visible: bind template.has-surfaces;
|
||||||
|
hexpand: true;
|
||||||
|
vexpand: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This could be a lot more visually pleasing but in practice this doesn't
|
||||||
|
// ever happen at the time of writing this comment. A surface-less split
|
||||||
|
// tree always closes its parent.
|
||||||
|
Label {
|
||||||
|
visible: bind template.has-surfaces inverted;
|
||||||
|
// Purposely not localized currently because this shouldn't really
|
||||||
|
// ever appear. When we have a situation it does appear, we may want
|
||||||
|
// to change the styling and text so I don't want to burden localizers
|
||||||
|
// to handle this yet.
|
||||||
|
label: "No surfaces.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,11 +5,12 @@ template $GhosttyTab: Box {
|
||||||
"tab",
|
"tab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
orientation: vertical;
|
||||||
hexpand: true;
|
hexpand: true;
|
||||||
vexpand: true;
|
vexpand: true;
|
||||||
// A tab currently just contains a surface directly. When we introduce
|
|
||||||
// splits we probably want to replace this with the split widget type.
|
$GhosttySplitTree split_tree {
|
||||||
$GhosttySurface surface {
|
notify::active-surface => $notify_active_surface();
|
||||||
close-request => $surface_close_request();
|
notify::tree => $notify_tree();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ template $GhosttyWindow: Adw.ApplicationWindow {
|
||||||
|
|
||||||
close-request => $close_request();
|
close-request => $close_request();
|
||||||
realize => $realize();
|
realize => $realize();
|
||||||
notify::background-opaque => $notify_background_opaque();
|
|
||||||
notify::config => $notify_config();
|
notify::config => $notify_config();
|
||||||
notify::fullscreened => $notify_fullscreened();
|
notify::fullscreened => $notify_fullscreened();
|
||||||
notify::maximized => $notify_maximized();
|
notify::maximized => $notify_maximized();
|
||||||
|
|
@ -50,6 +49,8 @@ template $GhosttyWindow: Adw.ApplicationWindow {
|
||||||
tooltip-text: _("New Tab");
|
tooltip-text: _("New Tab");
|
||||||
dropdown-tooltip: _("New Split");
|
dropdown-tooltip: _("New Split");
|
||||||
menu-model: split_menu;
|
menu-model: split_menu;
|
||||||
|
can-focus: false;
|
||||||
|
focus-on-click: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[end]
|
[end]
|
||||||
|
|
@ -78,6 +79,64 @@ template $GhosttyWindow: Adw.ApplicationWindow {
|
||||||
expand-tabs: bind template.tabs-wide;
|
expand-tabs: bind template.tabs-wide;
|
||||||
view: tab_view;
|
view: tab_view;
|
||||||
visible: bind template.tabs-visible;
|
visible: bind template.tabs-visible;
|
||||||
|
|
||||||
|
[start]
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: horizontal;
|
||||||
|
visible: bind $titlebar_style_is_tabs(template.titlebar-style) as <bool>;
|
||||||
|
|
||||||
|
Gtk.WindowControls {
|
||||||
|
side: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.SplitButton {
|
||||||
|
styles [
|
||||||
|
"flat",
|
||||||
|
]
|
||||||
|
|
||||||
|
clicked => $new_tab();
|
||||||
|
icon-name: "tab-new-symbolic";
|
||||||
|
tooltip-text: _("New Tab");
|
||||||
|
dropdown-tooltip: _("New Split");
|
||||||
|
menu-model: split_menu;
|
||||||
|
can-focus: false;
|
||||||
|
focus-on-click: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[end]
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: horizontal;
|
||||||
|
visible: bind $titlebar_style_is_tabs(template.titlebar-style) as <bool>;
|
||||||
|
|
||||||
|
Gtk.ToggleButton {
|
||||||
|
styles [
|
||||||
|
"flat",
|
||||||
|
]
|
||||||
|
|
||||||
|
icon-name: "view-grid-symbolic";
|
||||||
|
tooltip-text: _("View Open Tabs");
|
||||||
|
active: bind tab_overview.open bidirectional;
|
||||||
|
can-focus: false;
|
||||||
|
focus-on-click: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.MenuButton {
|
||||||
|
styles [
|
||||||
|
"flat",
|
||||||
|
]
|
||||||
|
|
||||||
|
notify::active => $notify_menu_active();
|
||||||
|
icon-name: "open-menu-symbolic";
|
||||||
|
menu-model: main_menu;
|
||||||
|
tooltip-text: _("Main Menu");
|
||||||
|
can-focus: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.WindowControls {
|
||||||
|
side: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ pub fn WeakRef(comptime T: type) type {
|
||||||
|
|
||||||
ref: gobject.WeakRef = std.mem.zeroes(gobject.WeakRef),
|
ref: gobject.WeakRef = std.mem.zeroes(gobject.WeakRef),
|
||||||
|
|
||||||
|
pub const empty: Self = .{};
|
||||||
|
|
||||||
/// Set the weak reference to the given object. This will not
|
/// Set the weak reference to the given object. This will not
|
||||||
/// increase the reference count of the object.
|
/// increase the reference count of the object.
|
||||||
pub fn set(self: *Self, v_: ?*T) void {
|
pub fn set(self: *Self, v_: ?*T) void {
|
||||||
|
|
@ -23,14 +25,9 @@ pub fn WeakRef(comptime T: type) type {
|
||||||
/// Get a strong reference to the object, or null if the object
|
/// Get a strong reference to the object, or null if the object
|
||||||
/// has been finalized. This increases the reference count by one.
|
/// has been finalized. This increases the reference count by one.
|
||||||
pub fn get(self: *Self) ?*T {
|
pub fn get(self: *Self) ?*T {
|
||||||
// The GIR of g_weak_ref_get has a bug where the optional
|
|
||||||
// is not encoded. Or, it may be a bug in zig-gobject.
|
|
||||||
const obj_: ?*gobject.Object = @ptrCast(self.ref.get());
|
|
||||||
const obj = obj_ orelse return null;
|
|
||||||
|
|
||||||
// We can't use `as` because `as` guarantees conversion and
|
// We can't use `as` because `as` guarantees conversion and
|
||||||
// that can't be statically guaranteed.
|
// that can't be statically guaranteed.
|
||||||
return gobject.ext.cast(T, obj);
|
return gobject.ext.cast(T, self.ref.get() orelse return null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +35,7 @@ pub fn WeakRef(comptime T: type) type {
|
||||||
test WeakRef {
|
test WeakRef {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
var ref: WeakRef(gtk.TextBuffer) = .{};
|
var ref: WeakRef(gtk.TextBuffer) = .empty;
|
||||||
const obj: *gtk.TextBuffer = .new(null);
|
const obj: *gtk.TextBuffer = .new(null);
|
||||||
ref.set(obj);
|
ref.set(obj);
|
||||||
ref.get().?.unref(); // The "?" asserts non-null
|
ref.get().?.unref(); // The "?" asserts non-null
|
||||||
|
|
|
||||||
|
|
@ -184,14 +184,14 @@ fn handleResponse(self: *ClipboardConfirmation, response: [*:0]const u8) void {
|
||||||
|
|
||||||
self.destroy();
|
self.destroy();
|
||||||
}
|
}
|
||||||
fn gtkChoose(dialog_: ?*gobject.Object, result: *gio.AsyncResult, ud: ?*anyopaque) callconv(.C) void {
|
fn gtkChoose(dialog_: ?*gobject.Object, result: *gio.AsyncResult, ud: ?*anyopaque) callconv(.c) void {
|
||||||
const dialog = gobject.ext.cast(DialogType, dialog_.?).?;
|
const dialog = gobject.ext.cast(DialogType, dialog_.?).?;
|
||||||
const self: *ClipboardConfirmation = @ptrCast(@alignCast(ud.?));
|
const self: *ClipboardConfirmation = @ptrCast(@alignCast(ud.?));
|
||||||
const response = dialog.chooseFinish(result);
|
const response = dialog.chooseFinish(result);
|
||||||
self.handleResponse(response);
|
self.handleResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkResponse(_: *DialogType, response: [*:0]u8, self: *ClipboardConfirmation) callconv(.C) void {
|
fn gtkResponse(_: *DialogType, response: [*:0]u8, self: *ClipboardConfirmation) callconv(.c) void {
|
||||||
self.handleResponse(response);
|
self.handleResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -335,6 +335,7 @@ fn request(
|
||||||
|
|
||||||
var response: u32 = 0;
|
var response: u32 = 0;
|
||||||
var vardict: ?*glib.Variant = null;
|
var vardict: ?*glib.Variant = null;
|
||||||
|
defer if (vardict) |v| v.unref();
|
||||||
params_.get("(u@a{sv})", &response, &vardict);
|
params_.get("(u@a{sv})", &response, &vardict);
|
||||||
|
|
||||||
switch (response) {
|
switch (response) {
|
||||||
|
|
|
||||||
|
|
@ -1121,7 +1121,7 @@ fn gtkActionToggleCommandPalette(
|
||||||
_: *gio.SimpleAction,
|
_: *gio.SimpleAction,
|
||||||
_: ?*glib.Variant,
|
_: ?*glib.Variant,
|
||||||
self: *Window,
|
self: *Window,
|
||||||
) callconv(.C) void {
|
) callconv(.c) void {
|
||||||
self.performBindingAction(.toggle_command_palette);
|
self.performBindingAction(.toggle_command_palette);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,10 +82,6 @@ pub fn Menu(
|
||||||
return self.menu_widget.as(gtk.Widget).getVisible() != 0;
|
return self.menu_widget.as(gtk.Widget).getVisible() != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setVisible(self: *const Self, visible: bool) void {
|
|
||||||
self.menu_widget.as(gtk.Widget).setVisible(@intFromBool(visible));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Refresh the menu. Right now that means enabling/disabling the "Copy"
|
/// Refresh the menu. Right now that means enabling/disabling the "Copy"
|
||||||
/// menu item based on whether there is an active selection or not, but
|
/// menu item based on whether there is an active selection or not, but
|
||||||
/// that may change in the future.
|
/// that may change in the future.
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ font_backend: font.Backend = .freetype,
|
||||||
x11: bool = false,
|
x11: bool = false,
|
||||||
wayland: bool = false,
|
wayland: bool = false,
|
||||||
sentry: bool = true,
|
sentry: bool = true,
|
||||||
|
i18n: bool = true,
|
||||||
wasm_shared: bool = true,
|
wasm_shared: bool = true,
|
||||||
|
|
||||||
/// Ghostty exe properties
|
/// Ghostty exe properties
|
||||||
|
|
@ -175,6 +176,16 @@ pub fn init(b: *std.Build) !Config {
|
||||||
"Enables linking against X11 libraries when using the GTK rendering backend.",
|
"Enables linking against X11 libraries when using the GTK rendering backend.",
|
||||||
) orelse gtk_targets.x11;
|
) orelse gtk_targets.x11;
|
||||||
|
|
||||||
|
config.i18n = b.option(
|
||||||
|
bool,
|
||||||
|
"i18n",
|
||||||
|
"Enables gettext-based internationalization. Enabled by default only for macOS, and other Unix-like systems like Linux and FreeBSD when using glibc.",
|
||||||
|
) orelse switch (target.result.os.tag) {
|
||||||
|
.macos, .ios => true,
|
||||||
|
.linux, .freebsd => target.result.isGnuLibC(),
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
// Ghostty Exe Properties
|
// Ghostty Exe Properties
|
||||||
|
|
||||||
|
|
@ -420,6 +431,7 @@ pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void {
|
||||||
step.addOption(bool, "x11", self.x11);
|
step.addOption(bool, "x11", self.x11);
|
||||||
step.addOption(bool, "wayland", self.wayland);
|
step.addOption(bool, "wayland", self.wayland);
|
||||||
step.addOption(bool, "sentry", self.sentry);
|
step.addOption(bool, "sentry", self.sentry);
|
||||||
|
step.addOption(bool, "i18n", self.i18n);
|
||||||
step.addOption(apprt.Runtime, "app_runtime", self.app_runtime);
|
step.addOption(apprt.Runtime, "app_runtime", self.app_runtime);
|
||||||
step.addOption(font.Backend, "font_backend", self.font_backend);
|
step.addOption(font.Backend, "font_backend", self.font_backend);
|
||||||
step.addOption(rendererpkg.Impl, "renderer", self.renderer);
|
step.addOption(rendererpkg.Impl, "renderer", self.renderer);
|
||||||
|
|
@ -467,6 +479,7 @@ pub fn fromOptions() Config {
|
||||||
.exe_entrypoint = std.meta.stringToEnum(ExeEntrypoint, @tagName(options.exe_entrypoint)).?,
|
.exe_entrypoint = std.meta.stringToEnum(ExeEntrypoint, @tagName(options.exe_entrypoint)).?,
|
||||||
.wasm_target = std.meta.stringToEnum(WasmTarget, @tagName(options.wasm_target)).?,
|
.wasm_target = std.meta.stringToEnum(WasmTarget, @tagName(options.wasm_target)).?,
|
||||||
.wasm_shared = options.wasm_shared,
|
.wasm_shared = options.wasm_shared,
|
||||||
|
.i18n = options.i18n,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ xctest: *std.Build.Step.Run,
|
||||||
pub const Deps = struct {
|
pub const Deps = struct {
|
||||||
xcframework: *const XCFramework,
|
xcframework: *const XCFramework,
|
||||||
docs: *const Docs,
|
docs: *const Docs,
|
||||||
i18n: *const I18n,
|
i18n: ?*const I18n,
|
||||||
resources: *const Resources,
|
resources: *const Resources,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ pub fn init(
|
||||||
// We also need all these resources because the xcode project
|
// We also need all these resources because the xcode project
|
||||||
// references them via symlinks.
|
// references them via symlinks.
|
||||||
deps.resources.addStepDependencies(&step.step);
|
deps.resources.addStepDependencies(&step.step);
|
||||||
deps.i18n.addStepDependencies(&step.step);
|
if (deps.i18n) |v| v.addStepDependencies(&step.step);
|
||||||
deps.docs.installDummy(&step.step);
|
deps.docs.installDummy(&step.step);
|
||||||
|
|
||||||
// Expect success
|
// Expect success
|
||||||
|
|
@ -113,7 +113,7 @@ pub fn init(
|
||||||
// We also need all these resources because the xcode project
|
// We also need all these resources because the xcode project
|
||||||
// references them via symlinks.
|
// references them via symlinks.
|
||||||
deps.resources.addStepDependencies(&step.step);
|
deps.resources.addStepDependencies(&step.step);
|
||||||
deps.i18n.addStepDependencies(&step.step);
|
if (deps.i18n) |v| v.addStepDependencies(&step.step);
|
||||||
deps.docs.installDummy(&step.step);
|
deps.docs.installDummy(&step.step);
|
||||||
|
|
||||||
// Expect success
|
// Expect success
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ pub const flatpak = options.flatpak;
|
||||||
pub const app_runtime: apprt.Runtime = config.app_runtime;
|
pub const app_runtime: apprt.Runtime = config.app_runtime;
|
||||||
pub const font_backend: font.Backend = config.font_backend;
|
pub const font_backend: font.Backend = config.font_backend;
|
||||||
pub const renderer: rendererpkg.Impl = config.renderer;
|
pub const renderer: rendererpkg.Impl = config.renderer;
|
||||||
|
pub const i18n: bool = config.i18n;
|
||||||
|
|
||||||
/// The bundle ID for the app. This is used in many places and is currently
|
/// The bundle ID for the app. This is used in many places and is currently
|
||||||
/// hardcoded here. We could make this configurable in the future if there
|
/// hardcoded here. We could make this configurable in the future if there
|
||||||
|
|
|
||||||
|
|
@ -2892,6 +2892,21 @@ else
|
||||||
/// more subtle border.
|
/// more subtle border.
|
||||||
@"gtk-toolbar-style": GtkToolbarStyle = .raised,
|
@"gtk-toolbar-style": GtkToolbarStyle = .raised,
|
||||||
|
|
||||||
|
/// The style of the GTK titlbar. Available values are `native` and `tabs`.
|
||||||
|
///
|
||||||
|
/// The `native` titlebar style is a traditional titlebar with a title, a few
|
||||||
|
/// buttons and window controls. A separate tab bar will show up below the
|
||||||
|
/// titlebar if you have multiple tabs open in the window.
|
||||||
|
///
|
||||||
|
/// The `tabs` titlebar merges the tab bar and the traditional titlebar.
|
||||||
|
/// This frees up vertical space on your screen if you use multiple tabs. One
|
||||||
|
/// limitation of the `tabs` titlebar is that you cannot drag the titlebar
|
||||||
|
/// by the titles any longer (as they are tab titles now). Other areas of the
|
||||||
|
/// `tabs` title bar can be used to drag the window around.
|
||||||
|
///
|
||||||
|
/// The default style is `native`.
|
||||||
|
@"gtk-titlebar-style": GtkTitlebarStyle = .native,
|
||||||
|
|
||||||
/// If `true` (default), then the Ghostty GTK tabs will be "wide." Wide tabs
|
/// If `true` (default), then the Ghostty GTK tabs will be "wide." Wide tabs
|
||||||
/// are the new typical Gnome style where tabs fill their available space.
|
/// are the new typical Gnome style where tabs fill their available space.
|
||||||
/// If you set this to `false` then tabs will only take up space they need,
|
/// If you set this to `false` then tabs will only take up space they need,
|
||||||
|
|
@ -6947,6 +6962,21 @@ pub const GtkToolbarStyle = enum {
|
||||||
@"raised-border",
|
@"raised-border",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// See gtk-titlebar-style
|
||||||
|
pub const GtkTitlebarStyle = enum(c_int) {
|
||||||
|
native,
|
||||||
|
tabs,
|
||||||
|
|
||||||
|
pub const getGObjectType = switch (build_config.app_runtime) {
|
||||||
|
.gtk, .@"gtk-ng" => @import("gobject").ext.defineEnum(
|
||||||
|
GtkTitlebarStyle,
|
||||||
|
.{ .name = "GhosttyGtkTitlebarStyle" },
|
||||||
|
),
|
||||||
|
|
||||||
|
.none => void,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/// See app-notifications
|
/// See app-notifications
|
||||||
pub const AppNotifications = packed struct {
|
pub const AppNotifications = packed struct {
|
||||||
@"clipboard-copy": bool = true,
|
@"clipboard-copy": bool = true,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ const cache_table = @import("cache_table.zig");
|
||||||
const circ_buf = @import("circ_buf.zig");
|
const circ_buf = @import("circ_buf.zig");
|
||||||
const intrusive_linked_list = @import("intrusive_linked_list.zig");
|
const intrusive_linked_list = @import("intrusive_linked_list.zig");
|
||||||
const segmented_pool = @import("segmented_pool.zig");
|
const segmented_pool = @import("segmented_pool.zig");
|
||||||
|
const split_tree = @import("split_tree.zig");
|
||||||
|
|
||||||
pub const lru = @import("lru.zig");
|
pub const lru = @import("lru.zig");
|
||||||
pub const BlockingQueue = blocking_queue.BlockingQueue;
|
pub const BlockingQueue = blocking_queue.BlockingQueue;
|
||||||
|
|
@ -13,6 +14,7 @@ pub const CacheTable = cache_table.CacheTable;
|
||||||
pub const CircBuf = circ_buf.CircBuf;
|
pub const CircBuf = circ_buf.CircBuf;
|
||||||
pub const IntrusiveDoublyLinkedList = intrusive_linked_list.DoublyLinkedList;
|
pub const IntrusiveDoublyLinkedList = intrusive_linked_list.DoublyLinkedList;
|
||||||
pub const SegmentedPool = segmented_pool.SegmentedPool;
|
pub const SegmentedPool = segmented_pool.SegmentedPool;
|
||||||
|
pub const SplitTree = split_tree.SplitTree;
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@import("std").testing.refAllDecls(@This());
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -38,6 +38,10 @@ cursor_height: u32,
|
||||||
/// The constraint height for nerd fonts icons.
|
/// The constraint height for nerd fonts icons.
|
||||||
icon_height: u32,
|
icon_height: u32,
|
||||||
|
|
||||||
|
/// Original cell width in pixels. This is used to keep
|
||||||
|
/// glyphs centered if the cell width is adjusted wider.
|
||||||
|
original_cell_width: ?u32 = null,
|
||||||
|
|
||||||
/// Minimum acceptable values for some fields to prevent modifiers
|
/// Minimum acceptable values for some fields to prevent modifiers
|
||||||
/// from being able to, for example, cause 0-thickness underlines.
|
/// from being able to, for example, cause 0-thickness underlines.
|
||||||
const Minimums = struct {
|
const Minimums = struct {
|
||||||
|
|
@ -263,6 +267,11 @@ pub fn apply(self: *Metrics, mods: ModifierSet) void {
|
||||||
const new = @max(entry.value_ptr.apply(original), 1);
|
const new = @max(entry.value_ptr.apply(original), 1);
|
||||||
if (new == original) continue;
|
if (new == original) continue;
|
||||||
|
|
||||||
|
// Preserve the original cell width if not set.
|
||||||
|
if (self.original_cell_width == null) {
|
||||||
|
self.original_cell_width = self.cell_width;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the new value
|
// Set the new value
|
||||||
@field(self, @tagName(tag)) = new;
|
@field(self, @tagName(tag)) = new;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,16 @@ pub const RenderOptions = struct {
|
||||||
y: f64,
|
y: f64,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Returns true if the constraint does anything. If it doesn't,
|
||||||
|
/// because it neither sizes nor positions the glyph, then this
|
||||||
|
/// returns false.
|
||||||
|
pub inline fn doesAnything(self: Constraint) bool {
|
||||||
|
return self.size_horizontal != .none or
|
||||||
|
self.align_horizontal != .none or
|
||||||
|
self.size_vertical != .none or
|
||||||
|
self.align_vertical != .none;
|
||||||
|
}
|
||||||
|
|
||||||
/// Apply this constraint to the provided glyph
|
/// Apply this constraint to the provided glyph
|
||||||
/// size, given the available width and height.
|
/// size, given the available width and height.
|
||||||
pub fn constrain(
|
pub fn constrain(
|
||||||
|
|
|
||||||
|
|
@ -346,89 +346,76 @@ pub const Face = struct {
|
||||||
|
|
||||||
const metrics = opts.grid_metrics;
|
const metrics = opts.grid_metrics;
|
||||||
const cell_width: f64 = @floatFromInt(metrics.cell_width);
|
const cell_width: f64 = @floatFromInt(metrics.cell_width);
|
||||||
// const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
||||||
|
|
||||||
|
// Next we apply any constraints to get the final size of the glyph.
|
||||||
|
var constraint = opts.constraint;
|
||||||
|
|
||||||
// We eliminate any negative vertical padding since these overlap
|
// We eliminate any negative vertical padding since these overlap
|
||||||
// values aren't needed under CoreText with how precisely we apply
|
// values aren't needed with how precisely we apply constraints,
|
||||||
// constraints, and they can lead to extra height that looks bad
|
// and they can lead to extra height that looks bad for things like
|
||||||
// for things like powerline glyphs.
|
// powerline glyphs.
|
||||||
var constraint = opts.constraint;
|
|
||||||
constraint.pad_top = @max(0.0, constraint.pad_top);
|
constraint.pad_top = @max(0.0, constraint.pad_top);
|
||||||
constraint.pad_bottom = @max(0.0, constraint.pad_bottom);
|
constraint.pad_bottom = @max(0.0, constraint.pad_bottom);
|
||||||
|
|
||||||
|
// We need to add the baseline position before passing to the constrain
|
||||||
|
// function since it operates on cell-relative positions, not baseline.
|
||||||
|
const cell_baseline: f64 = @floatFromInt(metrics.cell_baseline);
|
||||||
|
|
||||||
const glyph_size = constraint.constrain(
|
const glyph_size = constraint.constrain(
|
||||||
.{
|
.{
|
||||||
.width = rect.size.width,
|
.width = rect.size.width,
|
||||||
.height = rect.size.height,
|
.height = rect.size.height,
|
||||||
.x = rect.origin.x,
|
.x = rect.origin.x,
|
||||||
.y = rect.origin.y + @as(f64, @floatFromInt(metrics.cell_baseline)),
|
.y = rect.origin.y + cell_baseline,
|
||||||
},
|
},
|
||||||
metrics,
|
metrics,
|
||||||
opts.constraint_width,
|
opts.constraint_width,
|
||||||
);
|
);
|
||||||
|
|
||||||
// These calculations are an attempt to mostly imitate the effect of
|
var x = glyph_size.x;
|
||||||
// `shouldSubpixelQuantizeFonts`[^1], which helps maximize legibility
|
var y = glyph_size.y;
|
||||||
// at small pixel sizes (low DPI). We do this math ourselves instead
|
var width = glyph_size.width;
|
||||||
// of letting CoreText do it because it's not entirely clear how the
|
var height = glyph_size.height;
|
||||||
// math in CoreText works and we've run in to edge cases where glyphs
|
|
||||||
// have their bottom or left row cut off due to bad rounding.
|
|
||||||
//
|
|
||||||
// This math seems to have a mostly comparable result to whatever it
|
|
||||||
// is that CoreText does, and is even (in my opinion) better in some
|
|
||||||
// cases.
|
|
||||||
//
|
|
||||||
// I'm not entirely certain but I suspect that when you enable the
|
|
||||||
// CoreText option it also does some sort of rudimentary hinting,
|
|
||||||
// but it doesn't seem to make that big of a difference in terms
|
|
||||||
// of legibility in the end.
|
|
||||||
//
|
|
||||||
// [^1]: https://developer.apple.com/documentation/coregraphics/cgcontext/setshouldsubpixelquantizefonts(_:)?language=objc
|
|
||||||
|
|
||||||
// We only want to apply quantization if we don't have any
|
// If this is a bitmap glyph, it will always render as full pixels,
|
||||||
// constraints and this isn't a bitmap glyph, since CoreText
|
// not fractional pixels, so we need to quantize its position and
|
||||||
// doesn't seem to apply its quantization to bitmap glyphs.
|
// size accordingly to align to full pixels so we get good results.
|
||||||
//
|
if (sbix) {
|
||||||
// TODO: Maybe gate this so it only applies at small font sizes,
|
width = cell_width - @round(cell_width - width - x) - @round(x);
|
||||||
// or else offer a user config option that can disable it.
|
height = cell_height - @round(cell_height - height - y) - @round(y);
|
||||||
const should_quantize = !sbix and std.meta.eql(opts.constraint, .none);
|
x = @round(x);
|
||||||
|
y = @round(y);
|
||||||
|
}
|
||||||
|
|
||||||
// We offset our glyph by its bearings when we draw it, using `@floor`
|
// If the cell width was adjusted wider, we re-center all glyphs
|
||||||
// here rounds it *up* since we negate it right outside. Moving it by
|
// in the new width, so that they aren't weirdly off to the left.
|
||||||
// whole pixels ensures that we don't disturb the pixel alignment of
|
if (metrics.original_cell_width) |original| recenter: {
|
||||||
// the glyph, fractional pixels will still be drawn on all sides as
|
// We don't do this if the constraint has a horizontal alignment,
|
||||||
// necessary.
|
// since in that case the position was already calculated with the
|
||||||
const draw_x = -@floor(rect.origin.x);
|
// new cell width in mind.
|
||||||
const draw_y = -@floor(rect.origin.y);
|
if (opts.constraint.align_horizontal != .none) break :recenter;
|
||||||
|
|
||||||
// We use `x` and `y` for our full pixel bearings post-raster.
|
// If the original width was wider then we don't do anything.
|
||||||
// We need to subtract the fractional pixel of difference from
|
if (original >= metrics.cell_width) break :recenter;
|
||||||
// the edge of the draw area to the edge of the actual glyph.
|
|
||||||
const frac_x = rect.origin.x + draw_x;
|
|
||||||
const frac_y = rect.origin.y + draw_y;
|
|
||||||
const x = glyph_size.x - frac_x;
|
|
||||||
const y = glyph_size.y - frac_y;
|
|
||||||
|
|
||||||
// We never modify the width.
|
// We add half the difference to re-center.
|
||||||
//
|
x += (cell_width - @as(f64, @floatFromInt(original))) / 2;
|
||||||
// When using the CoreText option the widths do seem to be
|
}
|
||||||
// modified extremely subtly, but even at very small font
|
|
||||||
// sizes it's hardly a noticeable difference.
|
|
||||||
const width = glyph_size.width;
|
|
||||||
|
|
||||||
// If the top of the glyph (taking in to account the y position)
|
// Our whole-pixel bearings for the final glyph.
|
||||||
// is within half a pixel of an exact pixel edge, we round up the
|
// The fractional portion will be included in the rasterized position.
|
||||||
// height, otherwise leave it alone.
|
const px_x: i32 = @intFromFloat(@floor(x));
|
||||||
//
|
const px_y: i32 = @intFromFloat(@floor(y));
|
||||||
// This seems to match what CoreText does.
|
|
||||||
const frac_top = (glyph_size.height + frac_y) - @floor(glyph_size.height + frac_y);
|
// We offset our glyph by its bearings when we draw it, so that it's
|
||||||
const height =
|
// rendered fully inside our canvas area, but we make sure to keep the
|
||||||
if (should_quantize)
|
// fractional pixel offset so that we rasterize with the appropriate
|
||||||
if (frac_top >= 0.5)
|
// sub-pixel position.
|
||||||
glyph_size.height + 1 - frac_top
|
const frac_x = x - @floor(x);
|
||||||
else
|
const frac_y = y - @floor(y);
|
||||||
glyph_size.height
|
const draw_x = -rect.origin.x + frac_x;
|
||||||
else
|
const draw_y = -rect.origin.y + frac_y;
|
||||||
glyph_size.height;
|
|
||||||
|
|
||||||
// Add the fractional pixel to the width and height and take
|
// Add the fractional pixel to the width and height and take
|
||||||
// the ceiling to get a canvas size that will definitely fit
|
// the ceiling to get a canvas size that will definitely fit
|
||||||
|
|
@ -511,7 +498,9 @@ pub const Face = struct {
|
||||||
context.setAllowsFontSubpixelPositioning(ctx, true);
|
context.setAllowsFontSubpixelPositioning(ctx, true);
|
||||||
context.setShouldSubpixelPositionFonts(ctx, true);
|
context.setShouldSubpixelPositionFonts(ctx, true);
|
||||||
|
|
||||||
// See comments about quantization earlier in the function.
|
// We don't want subpixel quantization, since we very carefully
|
||||||
|
// manage the position of our glyphs ourselves, and dont want to
|
||||||
|
// mess that up.
|
||||||
context.setAllowsFontSubpixelQuantization(ctx, false);
|
context.setAllowsFontSubpixelQuantization(ctx, false);
|
||||||
context.setShouldSubpixelQuantizeFonts(ctx, false);
|
context.setShouldSubpixelQuantizeFonts(ctx, false);
|
||||||
|
|
||||||
|
|
@ -553,46 +542,11 @@ pub const Face = struct {
|
||||||
|
|
||||||
// This should be the distance from the bottom of
|
// This should be the distance from the bottom of
|
||||||
// the cell to the top of the glyph's bounding box.
|
// the cell to the top of the glyph's bounding box.
|
||||||
const offset_y: i32 = @as(i32, @intFromFloat(@round(y))) + @as(i32, @intCast(px_height));
|
const offset_y: i32 = px_y + @as(i32, @intCast(px_height));
|
||||||
|
|
||||||
// This should be the distance from the left of
|
// This should be the distance from the left of
|
||||||
// the cell to the left of the glyph's bounding box.
|
// the cell to the left of the glyph's bounding box.
|
||||||
const offset_x: i32 = offset_x: {
|
const offset_x: i32 = px_x;
|
||||||
// If the glyph's advance is narrower than the cell width then we
|
|
||||||
// center the advance of the glyph within the cell width. At first
|
|
||||||
// I implemented this to proportionally scale the center position
|
|
||||||
// of the glyph but that messes up glyphs that are meant to align
|
|
||||||
// vertically with others, so this is a compromise.
|
|
||||||
//
|
|
||||||
// This makes it so that when the `adjust-cell-width` config is
|
|
||||||
// used, or when a fallback font with a different advance width
|
|
||||||
// is used, we don't get weirdly aligned glyphs.
|
|
||||||
//
|
|
||||||
// We don't do this if the constraint has a horizontal alignment,
|
|
||||||
// since in that case the position was already calculated with the
|
|
||||||
// new cell width in mind.
|
|
||||||
if (opts.constraint.align_horizontal == .none) {
|
|
||||||
const advance = self.font.getAdvancesForGlyphs(.horizontal, &glyphs, null);
|
|
||||||
const new_advance =
|
|
||||||
cell_width * @as(f64, @floatFromInt(opts.cell_width orelse 1));
|
|
||||||
// If the original advance is greater than the cell width then
|
|
||||||
// it's possible that this is a ligature or other glyph that is
|
|
||||||
// intended to overflow the cell to one side or the other, and
|
|
||||||
// adjusting the bearings could mess that up, so we just leave
|
|
||||||
// it alone if that's the case.
|
|
||||||
//
|
|
||||||
// We also don't want to do anything if the advance is zero or
|
|
||||||
// less, since this is used for stuff like combining characters.
|
|
||||||
if (advance > new_advance or advance <= 0.0) {
|
|
||||||
break :offset_x @intFromFloat(@round(x));
|
|
||||||
}
|
|
||||||
break :offset_x @intFromFloat(
|
|
||||||
@round(x + (new_advance - advance) / 2),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
break :offset_x @intFromFloat(@round(x));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.width = px_width,
|
.width = px_width,
|
||||||
|
|
|
||||||
|
|
@ -156,23 +156,58 @@ pub const Face = struct {
|
||||||
/// but sometimes allocation isn't required and a static string is
|
/// but sometimes allocation isn't required and a static string is
|
||||||
/// returned.
|
/// returned.
|
||||||
pub fn name(self: *const Face, buf: []u8) Allocator.Error![]const u8 {
|
pub fn name(self: *const Face, buf: []u8) Allocator.Error![]const u8 {
|
||||||
// We don't use this today but its possible the table below
|
|
||||||
// returns UTF-16 in which case we'd want to use this for conversion.
|
|
||||||
_ = buf;
|
|
||||||
|
|
||||||
const count = self.face.getSfntNameCount();
|
const count = self.face.getSfntNameCount();
|
||||||
|
|
||||||
// We look for the font family entry.
|
// We look for the font family entry.
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
const entry = self.face.getSfntName(i) catch continue;
|
const entry = self.face.getSfntName(i) catch continue;
|
||||||
if (entry.name_id == freetype.c.TT_NAME_ID_FONT_FAMILY) {
|
if (entry.name_id == freetype.c.TT_NAME_ID_FONT_FAMILY) {
|
||||||
return entry.string[0..entry.string_len];
|
const string = entry.string[0..entry.string_len];
|
||||||
|
// There are other encodings that are something other than UTF-8
|
||||||
|
// but this is one we've seen "in the wild" so far.
|
||||||
|
if (entry.platform_id == freetype.c.TT_PLATFORM_MICROSOFT and entry.encoding_id == freetype.c.TT_MS_ID_UNICODE_CS) skip: {
|
||||||
|
if (string.len % 2 != 0) break :skip;
|
||||||
|
if (string.len > 1024) break :skip;
|
||||||
|
var tmp: [512]u16 = undefined;
|
||||||
|
const max = string.len / 2;
|
||||||
|
for (@as([]const u16, @alignCast(@ptrCast(string))), 0..) |c, j| tmp[j] = @byteSwap(c);
|
||||||
|
const len = std.unicode.utf16LeToUtf8(buf, tmp[0..max]) catch return string;
|
||||||
|
return buf[0..len];
|
||||||
|
}
|
||||||
|
return string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "face name" {
|
||||||
|
const embedded = @import("../embedded.zig");
|
||||||
|
|
||||||
|
var lib: Library = try .init(testing.allocator);
|
||||||
|
defer lib.deinit();
|
||||||
|
|
||||||
|
{
|
||||||
|
var face: Face = try .init(lib, embedded.variable, .{ .size = .{ .points = 14 } });
|
||||||
|
defer face.deinit();
|
||||||
|
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
const actual = try face.name(&buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("JetBrains Mono", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var face: Face = try .init(lib, embedded.inconsolata, .{ .size = .{ .points = 14 } });
|
||||||
|
defer face.deinit();
|
||||||
|
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
const actual = try face.name(&buf);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("Inconsolata", actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a new face that is the same as this but also has synthetic
|
/// Return a new face that is the same as this but also has synthetic
|
||||||
/// bold applied.
|
/// bold applied.
|
||||||
pub fn syntheticBold(self: *const Face, opts: font.face.Options) !Face {
|
pub fn syntheticBold(self: *const Face, opts: font.face.Options) !Face {
|
||||||
|
|
@ -328,19 +363,11 @@ pub const Face = struct {
|
||||||
self.ft_mutex.lock();
|
self.ft_mutex.lock();
|
||||||
defer self.ft_mutex.unlock();
|
defer self.ft_mutex.unlock();
|
||||||
|
|
||||||
// We enable hinting by default, and disable it if either of the
|
// Hinting should only be enabled if the configured load flags specify
|
||||||
// constraint alignments are not center or none, since this means
|
// it and the provided constraint doesn't actually do anything, since
|
||||||
// that the glyph needs to be aligned flush to the cell edge, and
|
// if it does, then it'll mess up the hinting anyway when it moves or
|
||||||
// hinting can mess that up.
|
// resizes the glyph.
|
||||||
const do_hinting = self.load_flags.hinting and
|
const do_hinting = self.load_flags.hinting and !opts.constraint.doesAnything();
|
||||||
switch (opts.constraint.align_horizontal) {
|
|
||||||
.start, .end => false,
|
|
||||||
.center, .none => true,
|
|
||||||
} and
|
|
||||||
switch (opts.constraint.align_vertical) {
|
|
||||||
.start, .end => false,
|
|
||||||
.center, .none => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load the glyph.
|
// Load the glyph.
|
||||||
try self.face.loadGlyph(glyph_index, .{
|
try self.face.loadGlyph(glyph_index, .{
|
||||||
|
|
@ -356,6 +383,11 @@ pub const Face = struct {
|
||||||
.force_autohint = self.load_flags.@"force-autohint",
|
.force_autohint = self.load_flags.@"force-autohint",
|
||||||
.no_autohint = !self.load_flags.autohint,
|
.no_autohint = !self.load_flags.autohint,
|
||||||
|
|
||||||
|
// If we're gonna be rendering this glyph in monochrome,
|
||||||
|
// then we should use the monochrome hinter as well, or
|
||||||
|
// else it won't look very good at all.
|
||||||
|
.target_mono = self.load_flags.monochrome,
|
||||||
|
|
||||||
// NO_SVG set to true because we don't currently support rendering
|
// NO_SVG set to true because we don't currently support rendering
|
||||||
// SVG glyphs under FreeType, since that requires bundling another
|
// SVG glyphs under FreeType, since that requires bundling another
|
||||||
// dependency to handle rendering the SVG.
|
// dependency to handle rendering the SVG.
|
||||||
|
|
@ -363,14 +395,45 @@ pub const Face = struct {
|
||||||
});
|
});
|
||||||
const glyph = self.face.handle.*.glyph;
|
const glyph = self.face.handle.*.glyph;
|
||||||
|
|
||||||
const glyph_width: f64 = f26dot6ToF64(glyph.*.metrics.width);
|
// We get a rect that represents the position
|
||||||
const glyph_height: f64 = f26dot6ToF64(glyph.*.metrics.height);
|
// and size of the glyph before any changes.
|
||||||
|
const rect: struct {
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
width: f64,
|
||||||
|
height: f64,
|
||||||
|
} = metrics: {
|
||||||
|
// If we're dealing with an outline glyph then we get the
|
||||||
|
// outline's bounding box instead of using the built-in
|
||||||
|
// metrics, since that's more precise and allows better
|
||||||
|
// cell-fitting.
|
||||||
|
if (glyph.*.format == freetype.c.FT_GLYPH_FORMAT_OUTLINE) {
|
||||||
|
// Get the glyph's bounding box before we transform it at all.
|
||||||
|
// We use this rather than the metrics, since it's more precise.
|
||||||
|
var bbox: freetype.c.FT_BBox = undefined;
|
||||||
|
_ = freetype.c.FT_Outline_Get_BBox(&glyph.*.outline, &bbox);
|
||||||
|
|
||||||
|
break :metrics .{
|
||||||
|
.x = f26dot6ToF64(bbox.xMin),
|
||||||
|
.y = f26dot6ToF64(bbox.yMin),
|
||||||
|
.width = f26dot6ToF64(bbox.xMax - bbox.xMin),
|
||||||
|
.height = f26dot6ToF64(bbox.yMax - bbox.yMin),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
break :metrics .{
|
||||||
|
.x = f26dot6ToF64(glyph.*.metrics.horiBearingX),
|
||||||
|
.y = f26dot6ToF64(glyph.*.metrics.horiBearingY - glyph.*.metrics.height),
|
||||||
|
.width = f26dot6ToF64(glyph.*.metrics.width),
|
||||||
|
.height = f26dot6ToF64(glyph.*.metrics.height),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// If our glyph is smaller than a quarter pixel in either axis
|
// If our glyph is smaller than a quarter pixel in either axis
|
||||||
// then it has no outlines or they're too small to render.
|
// then it has no outlines or they're too small to render.
|
||||||
//
|
//
|
||||||
// In this case we just return 0-sized glyph struct.
|
// In this case we just return 0-sized glyph struct.
|
||||||
if (glyph_width < 0.25 or glyph_height < 0.25)
|
if (rect.width < 0.25 or rect.height < 0.25)
|
||||||
return font.Glyph{
|
return font.Glyph{
|
||||||
.width = 0,
|
.width = 0,
|
||||||
.height = 0,
|
.height = 0,
|
||||||
|
|
@ -391,31 +454,70 @@ pub const Face = struct {
|
||||||
_ = freetype.c.FT_Outline_Embolden(&glyph.*.outline, @intFromFloat(amount));
|
_ = freetype.c.FT_Outline_Embolden(&glyph.*.outline, @intFromFloat(amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next we need to apply any constraints.
|
|
||||||
const metrics = opts.grid_metrics;
|
const metrics = opts.grid_metrics;
|
||||||
|
|
||||||
const cell_width: f64 = @floatFromInt(metrics.cell_width);
|
const cell_width: f64 = @floatFromInt(metrics.cell_width);
|
||||||
// const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
const cell_height: f64 = @floatFromInt(metrics.cell_height);
|
||||||
|
|
||||||
const glyph_x: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingX);
|
// Next we apply any constraints to get the final size of the glyph.
|
||||||
const glyph_y: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingY) - glyph_height;
|
var constraint = opts.constraint;
|
||||||
|
|
||||||
const glyph_size = opts.constraint.constrain(
|
// We eliminate any negative vertical padding since these overlap
|
||||||
|
// values aren't needed with how precisely we apply constraints,
|
||||||
|
// and they can lead to extra height that looks bad for things like
|
||||||
|
// powerline glyphs.
|
||||||
|
constraint.pad_top = @max(0.0, constraint.pad_top);
|
||||||
|
constraint.pad_bottom = @max(0.0, constraint.pad_bottom);
|
||||||
|
|
||||||
|
// We need to add the baseline position before passing to the constrain
|
||||||
|
// function since it operates on cell-relative positions, not baseline.
|
||||||
|
const cell_baseline: f64 = @floatFromInt(metrics.cell_baseline);
|
||||||
|
|
||||||
|
const glyph_size = constraint.constrain(
|
||||||
.{
|
.{
|
||||||
.width = glyph_width,
|
.width = rect.width,
|
||||||
.height = glyph_height,
|
.height = rect.height,
|
||||||
.x = glyph_x,
|
.x = rect.x,
|
||||||
.y = glyph_y + @as(f64, @floatFromInt(metrics.cell_baseline)),
|
.y = rect.y + cell_baseline,
|
||||||
},
|
},
|
||||||
metrics,
|
metrics,
|
||||||
opts.constraint_width,
|
opts.constraint_width,
|
||||||
);
|
);
|
||||||
|
|
||||||
const width = glyph_size.width;
|
var width = glyph_size.width;
|
||||||
const height = glyph_size.height;
|
var height = glyph_size.height;
|
||||||
// This may need to be adjusted later on.
|
|
||||||
var x = glyph_size.x;
|
var x = glyph_size.x;
|
||||||
const y = glyph_size.y;
|
var y = glyph_size.y;
|
||||||
|
|
||||||
|
// If this is a bitmap glyph, it will always render as full pixels,
|
||||||
|
// not fractional pixels, so we need to quantize its position and
|
||||||
|
// size accordingly to align to full pixels so we get good results.
|
||||||
|
if (glyph.*.format == freetype.c.FT_GLYPH_FORMAT_BITMAP) {
|
||||||
|
width = cell_width - @round(cell_width - width - x) - @round(x);
|
||||||
|
height = cell_height - @round(cell_height - height - y) - @round(y);
|
||||||
|
x = @round(x);
|
||||||
|
y = @round(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the cell width was adjusted wider, we re-center all glyphs
|
||||||
|
// in the new width, so that they aren't weirdly off to the left.
|
||||||
|
if (metrics.original_cell_width) |original| recenter: {
|
||||||
|
// We don't do this if the constraint has a horizontal alignment,
|
||||||
|
// since in that case the position was already calculated with the
|
||||||
|
// new cell width in mind.
|
||||||
|
if (opts.constraint.align_horizontal != .none) break :recenter;
|
||||||
|
|
||||||
|
// If the original width was wider then we don't do anything.
|
||||||
|
if (original >= metrics.cell_width) break :recenter;
|
||||||
|
|
||||||
|
// We add half the difference to re-center.
|
||||||
|
//
|
||||||
|
// NOTE: We round this to a whole-pixel amount because under
|
||||||
|
// FreeType, the outlines will be hinted, which isn't
|
||||||
|
// the case under CoreText. If we move the outlines by
|
||||||
|
// a non-whole-pixel amount, it completely ruins the
|
||||||
|
// hinting.
|
||||||
|
x += @round((cell_width - @as(f64, @floatFromInt(original))) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
// Now we can render the glyph.
|
// Now we can render the glyph.
|
||||||
var bitmap: freetype.c.FT_Bitmap = undefined;
|
var bitmap: freetype.c.FT_Bitmap = undefined;
|
||||||
|
|
@ -429,8 +531,8 @@ pub const Face = struct {
|
||||||
// matrix, since that has 16.16 coefficients, and also I was having
|
// matrix, since that has 16.16 coefficients, and also I was having
|
||||||
// weird issues that I can only assume where due to freetype doing
|
// weird issues that I can only assume where due to freetype doing
|
||||||
// some bad caching or something when I did this using the matrix.
|
// some bad caching or something when I did this using the matrix.
|
||||||
const scale_x = width / glyph_width;
|
const scale_x = width / rect.width;
|
||||||
const scale_y = height / glyph_height;
|
const scale_y = height / rect.height;
|
||||||
const skew: f64 =
|
const skew: f64 =
|
||||||
if (self.synthetic.italic)
|
if (self.synthetic.italic)
|
||||||
// We skew by 12 degrees to synthesize italics.
|
// We skew by 12 degrees to synthesize italics.
|
||||||
|
|
@ -438,19 +540,24 @@ pub const Face = struct {
|
||||||
else
|
else
|
||||||
0.0;
|
0.0;
|
||||||
|
|
||||||
var bbox_before: freetype.c.FT_BBox = undefined;
|
|
||||||
_ = freetype.c.FT_Outline_Get_BBox(&glyph.*.outline, &bbox_before);
|
|
||||||
|
|
||||||
const outline = &glyph.*.outline;
|
const outline = &glyph.*.outline;
|
||||||
for (outline.points[0..@intCast(outline.n_points)]) |*p| {
|
for (outline.points[0..@intCast(outline.n_points)]) |*p| {
|
||||||
// Convert to f64 for processing
|
// Convert to f64 for processing
|
||||||
var px = f26dot6ToF64(p.x);
|
var px = f26dot6ToF64(p.x);
|
||||||
var py = f26dot6ToF64(p.y);
|
var py = f26dot6ToF64(p.y);
|
||||||
|
|
||||||
|
// Subtract original bearings
|
||||||
|
px -= rect.x;
|
||||||
|
py -= rect.y;
|
||||||
|
|
||||||
// Scale
|
// Scale
|
||||||
px *= scale_x;
|
px *= scale_x;
|
||||||
py *= scale_y;
|
py *= scale_y;
|
||||||
|
|
||||||
|
// Add new bearings
|
||||||
|
px += x;
|
||||||
|
py += y - cell_baseline;
|
||||||
|
|
||||||
// Skew
|
// Skew
|
||||||
px += py * skew;
|
px += py * skew;
|
||||||
|
|
||||||
|
|
@ -459,16 +566,6 @@ pub const Face = struct {
|
||||||
p.y = @as(i32, @bitCast(F26Dot6.from(py)));
|
p.y = @as(i32, @bitCast(F26Dot6.from(py)));
|
||||||
}
|
}
|
||||||
|
|
||||||
var bbox_after: freetype.c.FT_BBox = undefined;
|
|
||||||
_ = freetype.c.FT_Outline_Get_BBox(&glyph.*.outline, &bbox_after);
|
|
||||||
|
|
||||||
// If our bounding box changed, account for the lsb difference.
|
|
||||||
//
|
|
||||||
// This can happen when we skew glyphs that have a bit sticking
|
|
||||||
// out to the left higher up, like the top of the T or the serif
|
|
||||||
// on the lower case l in many monospace fonts.
|
|
||||||
x += f26dot6ToF64(bbox_after.xMin) - f26dot6ToF64(bbox_before.xMin);
|
|
||||||
|
|
||||||
try self.face.renderGlyph(
|
try self.face.renderGlyph(
|
||||||
if (self.load_flags.monochrome)
|
if (self.load_flags.monochrome)
|
||||||
.mono
|
.mono
|
||||||
|
|
@ -566,6 +663,10 @@ pub const Face = struct {
|
||||||
) != 0) {
|
) != 0) {
|
||||||
return error.BitmapHandlingError;
|
return error.BitmapHandlingError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the bearings to account for the new positioning.
|
||||||
|
glyph.*.bitmap_top = @intFromFloat(@floor(y - cell_baseline + height));
|
||||||
|
glyph.*.bitmap_left = @intFromFloat(@floor(x));
|
||||||
},
|
},
|
||||||
|
|
||||||
else => |f| {
|
else => |f| {
|
||||||
|
|
@ -600,6 +701,20 @@ pub const Face = struct {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Our whole-pixel bearings for the final glyph.
|
||||||
|
// The fractional portion will be included in the rasterized position.
|
||||||
|
//
|
||||||
|
// For the Y position, FreeType's `bitmap_top` is the distance from the
|
||||||
|
// baseline to the top of the glyph, but we need the distance from the
|
||||||
|
// bottom of the cell to the bottom of the glyph, so first we add the
|
||||||
|
// baseline to get the distance from the bottom of the cell to the top
|
||||||
|
// of the glyph, then we subtract the height of the glyph to get the
|
||||||
|
// bottom.
|
||||||
|
const px_x: i32 = glyph.*.bitmap_left;
|
||||||
|
const px_y: i32 = glyph.*.bitmap_top +
|
||||||
|
@as(i32, @intCast(metrics.cell_baseline)) -
|
||||||
|
@as(i32, @intCast(bitmap.rows));
|
||||||
|
|
||||||
const px_width = bitmap.width;
|
const px_width = bitmap.width;
|
||||||
const px_height = bitmap.rows;
|
const px_height = bitmap.rows;
|
||||||
const len: usize = @intCast(
|
const len: usize = @intCast(
|
||||||
|
|
@ -635,48 +750,11 @@ pub const Face = struct {
|
||||||
|
|
||||||
// This should be the distance from the bottom of
|
// This should be the distance from the bottom of
|
||||||
// the cell to the top of the glyph's bounding box.
|
// the cell to the top of the glyph's bounding box.
|
||||||
const offset_y: i32 =
|
const offset_y: i32 = px_y + @as(i32, @intCast(px_height));
|
||||||
@as(i32, @intFromFloat(@floor(y))) +
|
|
||||||
@as(i32, @intCast(px_height));
|
|
||||||
|
|
||||||
// This should be the distance from the left of
|
// This should be the distance from the left of
|
||||||
// the cell to the left of the glyph's bounding box.
|
// the cell to the left of the glyph's bounding box.
|
||||||
const offset_x: i32 = offset_x: {
|
const offset_x: i32 = px_x;
|
||||||
// If the glyph's advance is narrower than the cell width then we
|
|
||||||
// center the advance of the glyph within the cell width. At first
|
|
||||||
// I implemented this to proportionally scale the center position
|
|
||||||
// of the glyph but that messes up glyphs that are meant to align
|
|
||||||
// vertically with others, so this is a compromise.
|
|
||||||
//
|
|
||||||
// This makes it so that when the `adjust-cell-width` config is
|
|
||||||
// used, or when a fallback font with a different advance width
|
|
||||||
// is used, we don't get weirdly aligned glyphs.
|
|
||||||
//
|
|
||||||
// We don't do this if the constraint has a horizontal alignment,
|
|
||||||
// since in that case the position was already calculated with the
|
|
||||||
// new cell width in mind.
|
|
||||||
if (opts.constraint.align_horizontal == .none) {
|
|
||||||
const advance = f26dot6ToFloat(glyph.*.advance.x);
|
|
||||||
const new_advance =
|
|
||||||
cell_width * @as(f64, @floatFromInt(opts.cell_width orelse 1));
|
|
||||||
// If the original advance is greater than the cell width then
|
|
||||||
// it's possible that this is a ligature or other glyph that is
|
|
||||||
// intended to overflow the cell to one side or the other, and
|
|
||||||
// adjusting the bearings could mess that up, so we just leave
|
|
||||||
// it alone if that's the case.
|
|
||||||
//
|
|
||||||
// We also don't want to do anything if the advance is zero or
|
|
||||||
// less, since this is used for stuff like combining characters.
|
|
||||||
if (advance > new_advance or advance <= 0.0) {
|
|
||||||
break :offset_x @intFromFloat(@floor(x));
|
|
||||||
}
|
|
||||||
break :offset_x @intFromFloat(
|
|
||||||
@floor(x + (new_advance - advance) / 2),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
break :offset_x @intFromFloat(@floor(x));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Glyph{
|
return Glyph{
|
||||||
.width = px_width,
|
.width = px_width,
|
||||||
|
|
|
||||||
|
|
@ -1833,7 +1833,10 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
|
||||||
grid_ptr.* = try .init(alloc, .{ .collection = c });
|
grid_ptr.* = try .init(alloc, .{ .collection = c });
|
||||||
errdefer grid_ptr.*.deinit(alloc);
|
errdefer grid_ptr.*.deinit(alloc);
|
||||||
|
|
||||||
var shaper = try Shaper.init(alloc, .{});
|
var shaper = try Shaper.init(alloc, .{
|
||||||
|
// Some of our tests rely on dlig being enabled by default
|
||||||
|
.features = &.{"dlig"},
|
||||||
|
});
|
||||||
errdefer shaper.deinit();
|
errdefer shaper.deinit();
|
||||||
|
|
||||||
return TestShaper{
|
return TestShaper{
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,6 @@ pub const FeatureList = struct {
|
||||||
/// These features are hardcoded to always be on by default. Users
|
/// These features are hardcoded to always be on by default. Users
|
||||||
/// can turn them off by setting the features to "-liga" for example.
|
/// can turn them off by setting the features to "-liga" for example.
|
||||||
pub const default_features = [_]Feature{
|
pub const default_features = [_]Feature{
|
||||||
.{ .tag = "dlig".*, .value = 1 },
|
|
||||||
.{ .tag = "liga".*, .value = 1 },
|
.{ .tag = "liga".*, .value = 1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1296,7 +1296,10 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper {
|
||||||
grid_ptr.* = try .init(alloc, .{ .collection = c });
|
grid_ptr.* = try .init(alloc, .{ .collection = c });
|
||||||
errdefer grid_ptr.*.deinit(alloc);
|
errdefer grid_ptr.*.deinit(alloc);
|
||||||
|
|
||||||
var shaper = try Shaper.init(alloc, .{});
|
var shaper = try Shaper.init(alloc, .{
|
||||||
|
// Some of our tests rely on dlig being enabled by default
|
||||||
|
.features = &.{"dlig"},
|
||||||
|
});
|
||||||
errdefer shaper.deinit();
|
errdefer shaper.deinit();
|
||||||
|
|
||||||
return TestShaper{
|
return TestShaper{
|
||||||
|
|
|
||||||
|
|
@ -247,7 +247,7 @@ pub const RunIterator = struct {
|
||||||
if (j == self.i) current_font = font_info.idx;
|
if (j == self.i) current_font = font_info.idx;
|
||||||
|
|
||||||
// If our fonts are not equal, then we're done with our run.
|
// If our fonts are not equal, then we're done with our run.
|
||||||
if (font_info.idx.int() != current_font.int()) break;
|
if (font_info.idx != current_font) break;
|
||||||
|
|
||||||
// If we're a fallback character, add that and continue; we
|
// If we're a fallback character, add that and continue; we
|
||||||
// don't want to add the entire grapheme.
|
// don't want to add the entire grapheme.
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ const Binding = @This();
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const build_config = @import("../build_config.zig");
|
||||||
const ziglyph = @import("ziglyph");
|
const ziglyph = @import("ziglyph");
|
||||||
const key = @import("key.zig");
|
const key = @import("key.zig");
|
||||||
const KeyEvent = key.KeyEvent;
|
const KeyEvent = key.KeyEvent;
|
||||||
|
|
@ -729,6 +730,16 @@ pub const Action = union(enum) {
|
||||||
|
|
||||||
pub const Key = @typeInfo(Action).@"union".tag_type.?;
|
pub const Key = @typeInfo(Action).@"union".tag_type.?;
|
||||||
|
|
||||||
|
/// Make this a valid gobject if we're in a GTK environment.
|
||||||
|
pub const getGObjectType = switch (build_config.app_runtime) {
|
||||||
|
.gtk, .@"gtk-ng" => @import("gobject").ext.defineBoxed(
|
||||||
|
Action,
|
||||||
|
.{ .name = "GhosttyBindingAction" },
|
||||||
|
),
|
||||||
|
|
||||||
|
.none => void,
|
||||||
|
};
|
||||||
|
|
||||||
pub const CrashThread = enum {
|
pub const CrashThread = enum {
|
||||||
main,
|
main,
|
||||||
io,
|
io,
|
||||||
|
|
|
||||||
|
|
@ -756,7 +756,7 @@ fn renderSizeWindow(self: *Inspector) void {
|
||||||
{
|
{
|
||||||
_ = cimgui.c.igTableSetColumnIndex(1);
|
_ = cimgui.c.igTableSetColumnIndex(1);
|
||||||
cimgui.c.igText(
|
cimgui.c.igText(
|
||||||
"%d px",
|
"%.2f px",
|
||||||
self.surface.font_size.pixels(),
|
self.surface.font_size.pixels(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,8 @@ pub const InitError = error{
|
||||||
/// want to set the domain for the entire application since this is also
|
/// want to set the domain for the entire application since this is also
|
||||||
/// used by libghostty.
|
/// used by libghostty.
|
||||||
pub fn init(resources_dir: []const u8) InitError!void {
|
pub fn init(resources_dir: []const u8) InitError!void {
|
||||||
|
if (comptime !build_config.i18n) return;
|
||||||
|
|
||||||
switch (builtin.os.tag) {
|
switch (builtin.os.tag) {
|
||||||
// i18n is unsupported on Windows
|
// i18n is unsupported on Windows
|
||||||
.windows => return,
|
.windows => return,
|
||||||
|
|
@ -102,11 +104,13 @@ pub fn init(resources_dir: []const u8) InitError!void {
|
||||||
/// This should only be called for apprts that are fully owning the
|
/// This should only be called for apprts that are fully owning the
|
||||||
/// Ghostty application. This should not be called for libghostty users.
|
/// Ghostty application. This should not be called for libghostty users.
|
||||||
pub fn initGlobalDomain() error{OutOfMemory}!void {
|
pub fn initGlobalDomain() error{OutOfMemory}!void {
|
||||||
|
if (comptime !build_config.i18n) return;
|
||||||
_ = textdomain(build_config.bundle_id) orelse return error.OutOfMemory;
|
_ = textdomain(build_config.bundle_id) orelse return error.OutOfMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translate a message for the Ghostty domain.
|
/// Translate a message for the Ghostty domain.
|
||||||
pub fn _(msgid: [*:0]const u8) [*:0]const u8 {
|
pub fn _(msgid: [*:0]const u8) [*:0]const u8 {
|
||||||
|
if (comptime !build_config.i18n) return msgid;
|
||||||
return dgettext(build_config.bundle_id, msgid);
|
return dgettext(build_config.bundle_id, msgid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,8 +136,15 @@ pub fn canonicalizeLocale(
|
||||||
buf: []u8,
|
buf: []u8,
|
||||||
locale: []const u8,
|
locale: []const u8,
|
||||||
) error{NoSpaceLeft}![:0]const u8 {
|
) error{NoSpaceLeft}![:0]const u8 {
|
||||||
|
if (comptime !build_config.i18n) return locale;
|
||||||
|
|
||||||
// Fix zh locales for macOS
|
// Fix zh locales for macOS
|
||||||
if (fixZhLocale(locale)) |fixed| return fixed;
|
if (fixZhLocale(locale)) |fixed| {
|
||||||
|
if (buf.len < fixed.len + 1) return error.NoSpaceLeft;
|
||||||
|
@memcpy(buf[0..fixed.len], fixed);
|
||||||
|
buf[fixed.len] = 0;
|
||||||
|
return buf[0..fixed.len :0];
|
||||||
|
}
|
||||||
|
|
||||||
// Buffer must be 16 or at least as long as the locale and null term
|
// Buffer must be 16 or at least as long as the locale and null term
|
||||||
if (buf.len < @max(16, locale.len + 1)) return error.NoSpaceLeft;
|
if (buf.len < @max(16, locale.len + 1)) return error.NoSpaceLeft;
|
||||||
|
|
|
||||||
|
|
@ -229,24 +229,39 @@ pub fn isCovering(cp: u21) bool {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true of the codepoint is a "symbol-like" character, which
|
||||||
|
/// for now we define as anything in a private use area and anything
|
||||||
|
/// in the "dingbats" unicode block.
|
||||||
|
///
|
||||||
|
/// In the future it may be prudent to expand this to encompass more
|
||||||
|
/// symbol-like characters, and/or exclude some PUA sections.
|
||||||
|
pub fn isSymbol(cp: u21) bool {
|
||||||
|
return uucode.get(.general_category, cp) == .other_private_use or
|
||||||
|
uucode.get(.block, cp) == .dingbats;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the appropriate `constraint_width` for
|
/// Returns the appropriate `constraint_width` for
|
||||||
/// the provided cell when rendering its glyph(s).
|
/// the provided cell when rendering its glyph(s).
|
||||||
pub fn constraintWidth(cell_pin: terminal.Pin) u2 {
|
pub fn constraintWidth(cell_pin: terminal.Pin) u2 {
|
||||||
const cell = cell_pin.rowAndCell().cell;
|
const cell = cell_pin.rowAndCell().cell;
|
||||||
const cp = cell.codepoint();
|
const cp = cell.codepoint();
|
||||||
|
|
||||||
// If not a Co (Private Use) and not a Dingbats, use grid width.
|
const grid_width = cell.gridWidth();
|
||||||
if (uucode.get("general_category", cp) != .Co and
|
|
||||||
uucode.get("block", cp) != .dingbats)
|
// If the grid width of the cell is 2, the constraint
|
||||||
{
|
// width will always be 2, so we can just return early.
|
||||||
return cell.gridWidth();
|
if (grid_width > 1) return grid_width;
|
||||||
}
|
|
||||||
|
// We allow "symbol-like" glyphs to extend to 2 cells wide if there's
|
||||||
|
// space, and if the previous glyph wasn't also a symbol. So if this
|
||||||
|
// codepoint isn't a symbol then we can return the grid width.
|
||||||
|
if (!isSymbol(cp)) return grid_width;
|
||||||
|
|
||||||
// If we are at the end of the screen it must be constrained to one cell.
|
// If we are at the end of the screen it must be constrained to one cell.
|
||||||
if (cell_pin.x == cell_pin.node.data.size.cols - 1) return 1;
|
if (cell_pin.x == cell_pin.node.data.size.cols - 1) return 1;
|
||||||
|
|
||||||
// If we have a previous cell and it was PUA then we need to
|
// If we have a previous cell and it was a symbol then we need
|
||||||
// also constrain. This is so that multiple PUA glyphs align.
|
// to also constrain. This is so that multiple PUA glyphs align.
|
||||||
// As an exception, we ignore powerline glyphs since they are
|
// As an exception, we ignore powerline glyphs since they are
|
||||||
// used for box drawing and we consider them whitespace.
|
// used for box drawing and we consider them whitespace.
|
||||||
if (cell_pin.x > 0) prev: {
|
if (cell_pin.x > 0) prev: {
|
||||||
|
|
@ -260,14 +275,13 @@ pub fn constraintWidth(cell_pin: terminal.Pin) u2 {
|
||||||
// We consider powerline glyphs whitespace.
|
// We consider powerline glyphs whitespace.
|
||||||
if (isPowerline(prev_cp)) break :prev;
|
if (isPowerline(prev_cp)) break :prev;
|
||||||
|
|
||||||
// If it's Private Use (Co) use 1 as the width.
|
if (isSymbol(prev_cp)) {
|
||||||
if (uucode.get("general_category", prev_cp) == .Co) {
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the next cell is whitespace, then
|
// If the next cell is whitespace, then we
|
||||||
// we allow it to be up to two cells wide.
|
// allow the glyph to be up to two cells wide.
|
||||||
const next_cp = next_cp: {
|
const next_cp = next_cp: {
|
||||||
var copy = cell_pin;
|
var copy = cell_pin;
|
||||||
copy.x += 1;
|
copy.x += 1;
|
||||||
|
|
@ -281,7 +295,7 @@ pub fn constraintWidth(cell_pin: terminal.Pin) u2 {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be constrained
|
// Otherwise, this has to be 1 cell wide.
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,66 @@
|
||||||
# You must gracefully exit Ghostty (do not SIGINT) by closing all windows
|
# You must gracefully exit Ghostty (do not SIGINT) by closing all windows
|
||||||
# and quitting. Otherwise, we leave a number of GTK resources around.
|
# and quitting. Otherwise, we leave a number of GTK resources around.
|
||||||
|
|
||||||
|
|
||||||
|
# Reproduction:
|
||||||
|
# 1. Launch Ghostty (no config)
|
||||||
|
# 2. Right Click on the terminal
|
||||||
|
# 3. Hover over "Split" to get a submenu
|
||||||
|
# 4. Close menu by clicking away
|
||||||
|
# 5. Exit
|
||||||
|
#
|
||||||
|
# The menu model and popover are fully defined in the blueprint so I don't
|
||||||
|
# THINK we need to do any manual unrefing. But there's a lot of leaks here
|
||||||
|
# so if someone wants to take a closer look I'd appreciate it.
|
||||||
|
{
|
||||||
|
GTK PopOver Menu Model Leak
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
...
|
||||||
|
fun:gtk_menu_section_box_insert_func
|
||||||
|
...
|
||||||
|
fun:gtk_popover_menu_set_menu_model
|
||||||
|
...
|
||||||
|
}
|
||||||
|
{
|
||||||
|
GTK/Blueprint Popover GSK Transform
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
...
|
||||||
|
fun:gtk_popover_size_allocate
|
||||||
|
fun:gtk_widget_allocate
|
||||||
|
fun:gtk_popover_native_layout
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reproduction:
|
||||||
|
#
|
||||||
|
# 1. Launch Ghostty
|
||||||
|
# 2. Split Right
|
||||||
|
# 3. Hit "X" to close
|
||||||
|
{
|
||||||
|
GTK CSS Node State
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
fun:malloc
|
||||||
|
fun:g_malloc
|
||||||
|
fun:g_memdup2
|
||||||
|
fun:gtk_css_node_declaration_set_state
|
||||||
|
fun:gtk_css_node_set_state
|
||||||
|
fun:gtk_widget_propagate_state
|
||||||
|
fun:gtk_widget_update_state_flags
|
||||||
|
fun:gtk_main_do_event
|
||||||
|
fun:surface_event
|
||||||
|
fun:_gdk_marshal_BOOLEAN__POINTERv
|
||||||
|
fun:gdk_surface_event_marshallerv
|
||||||
|
fun:_g_closure_invoke_va
|
||||||
|
fun:signal_emit_valist_unlocked
|
||||||
|
fun:g_signal_emit_valist
|
||||||
|
fun:g_signal_emit
|
||||||
|
fun:gdk_surface_handle_event
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
GTK CSS Provider Leak
|
GTK CSS Provider Leak
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
|
|
@ -484,9 +544,7 @@
|
||||||
pango font map
|
pango font map
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
match-leak-kinds: possible
|
match-leak-kinds: possible
|
||||||
fun:calloc
|
...
|
||||||
fun:g_malloc0
|
|
||||||
fun:g_rc_box_alloc_full
|
|
||||||
fun:pango_fc_font_map_load_fontset
|
fun:pango_fc_font_map_load_fontset
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
@ -840,6 +898,26 @@
|
||||||
fun:FcConfigSubstituteWithPat
|
fun:FcConfigSubstituteWithPat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
FcConfigValues
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
fun:malloc
|
||||||
|
obj:/usr/lib*/libfontconfig.so*
|
||||||
|
obj:/usr/lib*/libfontconfig.so*
|
||||||
|
fun:FcConfigValues
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
FcValueSave
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
fun:malloc
|
||||||
|
obj:/usr/lib*/libfontconfig.so*
|
||||||
|
obj:/usr/lib*/libfontconfig.so*
|
||||||
|
fun:FcValueSave
|
||||||
|
}
|
||||||
|
|
||||||
# Pixman
|
# Pixman
|
||||||
{
|
{
|
||||||
pixman_image_composite32
|
pixman_image_composite32
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue