diff --git a/.github/pinact.yml b/.github/pinact.yml new file mode 100644 index 000000000..3a29d18ab --- /dev/null +++ b/.github/pinact.yml @@ -0,0 +1,4 @@ +version: 3 +ignore_actions: + - name: "DeterminateSystems/nix-installer-action" + ref: "main" diff --git a/.github/workflows/clean-artifacts.yml b/.github/workflows/clean-artifacts.yml index 5337c264a..69cb74ae5 100644 --- a/.github/workflows/clean-artifacts.yml +++ b/.github/workflows/clean-artifacts.yml @@ -10,7 +10,7 @@ jobs: timeout-minutes: 10 steps: - name: Remove old artifacts - uses: c-hive/gha-remove-artifacts@v1 + uses: c-hive/gha-remove-artifacts@44fc7acaf1b3d0987da0e8d4707a989d80e9554b # v1.4.0 with: age: "1 week" skip-tags: true diff --git a/.github/workflows/milestone.yml b/.github/workflows/milestone.yml index 9ac5536ff..bc5c3d76c 100644 --- a/.github/workflows/milestone.yml +++ b/.github/workflows/milestone.yml @@ -15,7 +15,7 @@ jobs: name: Milestone Update steps: - name: Set Milestone for PR - uses: hustcer/milestone-action@v2 + uses: hustcer/milestone-action@09bdc6fda0f43a4df28cda5815cc47df74cfdba7 # v2.8 if: github.event.pull_request.merged == true with: action: bind-pr # `bind-pr` is the default action @@ -24,7 +24,7 @@ jobs: # Bind milestone to closed issue that has a merged PR fix - name: Set Milestone for Issue - uses: hustcer/milestone-action@v2 + uses: hustcer/milestone-action@09bdc6fda0f43a4df28cda5815cc47df74cfdba7 # v2.8 if: github.event.issue.state == 'closed' with: action: bind-issue diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index a905531c2..bf8fd7208 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -34,18 +34,18 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - name: Setup Nix - uses: cachix/install-nix-action@v31 + uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" diff --git a/.github/workflows/publish-tag.yml b/.github/workflows/publish-tag.yml index 458982140..710d04647 100644 --- a/.github/workflows/publish-tag.yml +++ b/.github/workflows/publish-tag.yml @@ -64,7 +64,7 @@ jobs: mkdir blob mv appcast.xml blob/appcast.xml - name: Upload Appcast to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1 with: r2-account-id: ${{ secrets.CF_R2_RELEASE_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_RELEASE_AWS_KEY }} diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index a1cc2af19..cf96ffb21 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -8,7 +8,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos-debug] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install sentry-cli run: | @@ -29,7 +29,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install sentry-cli run: | @@ -51,16 +51,16 @@ jobs: timeout-minutes: 90 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -86,7 +86,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. Build this in release mode. - name: Build GhosttyKit - run: nix develop -c zig build -Doptimize=ReleaseSafe + run: nix develop -c zig build -Doptimize=ReleaseSafe -Demit-macos-app=false # The native app is built with native XCode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -189,7 +189,7 @@ jobs: cp ghostty-macos-universal.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal.zip cp ghostty-macos-universal-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-dsym.zip - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_PR_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_PR_AWS_KEY }} @@ -203,16 +203,16 @@ jobs: timeout-minutes: 90 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -238,7 +238,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. Build this in release mode. - name: Build GhosttyKit - run: nix develop -c zig build -Doptimize=Debug + run: nix develop -c zig build -Doptimize=Debug -Demit-macos-app=false # The native app is built with native XCode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -341,7 +341,7 @@ jobs: cp ghostty-macos-universal-debug.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug.zip cp ghostty-macos-universal-debug-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-dsym.zip - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_PR_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_PR_AWS_KEY }} diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 3deafd066..98ecf2fa3 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -56,7 +56,7 @@ jobs: fi - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 @@ -80,20 +80,20 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -111,7 +111,7 @@ jobs: nix develop -c minisign -S -m "ghostty-source.tar.gz" -s minisign.key < minisign.password - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: source-tarball path: |- @@ -128,12 +128,12 @@ jobs: GHOSTTY_COMMIT: ${{ needs.setup.outputs.commit }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -158,6 +158,7 @@ jobs: nix develop -c \ zig build \ -Doptimize=ReleaseFast \ + -Demit-macos-app=false \ -Dversion-string=${GHOSTTY_VERSION} # The native app is built with native XCode tooling. This also does @@ -259,7 +260,7 @@ jobs: zip -9 -r --symlinks ../../../ghostty-macos-universal-dsym.zip Ghostty.app.dSYM/ - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: macos path: |- @@ -276,7 +277,7 @@ jobs: curl -sL https://sentry.io/get-cli/ | bash - name: Download macOS Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: macos @@ -296,10 +297,10 @@ jobs: GHOSTTY_COMMIT_LONG: ${{ needs.setup.outputs.commit_long }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Download macOS Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: macos @@ -330,7 +331,7 @@ jobs: mv appcast_new.xml appcast.xml - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: sparkle path: |- @@ -347,17 +348,17 @@ jobs: GHOSTTY_VERSION: ${{ needs.setup.outputs.version }} steps: - name: Download macOS Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: macos - name: Download Sparkle Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: sparkle - name: Download Source Tarball Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: source-tarball @@ -377,7 +378,7 @@ jobs: mv Ghostty.dmg blob/${GHOSTTY_VERSION}/Ghostty.dmg mv appcast.xml blob/${GHOSTTY_VERSION}/appcast-staged.xml - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_RELEASE_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_RELEASE_AWS_KEY }} diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index 2a3277ea6..b0916e657 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -19,7 +19,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Tip Tag run: | git config user.name "github-actions[bot]" @@ -31,7 +31,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos-debug-slow] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install sentry-cli run: | @@ -52,7 +52,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos-debug-fast] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install sentry-cli run: | @@ -73,7 +73,7 @@ jobs: runs-on: namespace-profile-ghostty-sm needs: [build-macos] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install sentry-cli run: | @@ -105,17 +105,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -132,7 +132,7 @@ jobs: nix develop -c minisign -S -m ghostty-source.tar.gz -s minisign.key < minisign.password - name: Update Release - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 with: name: 'Ghostty Tip ("Nightly")' prerelease: true @@ -158,16 +158,16 @@ jobs: timeout-minutes: 90 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -196,7 +196,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. Build this in release mode. - name: Build GhosttyKit - run: nix develop -c zig build -Doptimize=ReleaseFast + run: nix develop -c zig build -Doptimize=ReleaseFast -Demit-macos-app=false # The native app is built with native XCode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -299,7 +299,7 @@ jobs: # Update Release - name: Release - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 with: name: 'Ghostty Tip ("Nightly")' prerelease: true @@ -331,7 +331,7 @@ jobs: cp Ghostty.dmg blob/${GHOSTTY_COMMIT_LONG}/Ghostty.dmg - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }} @@ -349,7 +349,7 @@ jobs: cp appcast_new.xml blob/appcast.xml - name: Upload Appcast to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }} @@ -373,16 +373,16 @@ jobs: timeout-minutes: 90 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -411,7 +411,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. Build this in release mode. - name: Build GhosttyKit - run: nix develop -c zig build -Doptimize=Debug + run: nix develop -c zig build -Doptimize=Debug -Demit-macos-app=false # The native app is built with native XCode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -507,7 +507,7 @@ jobs: # Update Release - name: Release - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 with: name: 'Ghostty Tip ("Nightly")' prerelease: true @@ -524,7 +524,7 @@ jobs: cp ghostty-macos-universal-debug-slow.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-slow.zip cp ghostty-macos-universal-debug-slow-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-slow-dsym.zip - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }} @@ -548,16 +548,16 @@ jobs: timeout-minutes: 90 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: # Important so that build number generation works fetch-depth: 0 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -586,7 +586,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. Build this in release mode. - name: Build GhosttyKit - run: nix develop -c zig build -Doptimize=ReleaseSafe + run: nix develop -c zig build -Doptimize=ReleaseSafe -Demit-macos-app=false # The native app is built with native XCode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -682,7 +682,7 @@ jobs: # Update Release - name: Release - uses: softprops/action-gh-release@v2.3.2 + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 with: name: 'Ghostty Tip ("Nightly")' prerelease: true @@ -699,7 +699,7 @@ jobs: cp ghostty-macos-universal-debug-fast.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-fast.zip cp ghostty-macos-universal-debug-fast-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-fast-dsym.zip - name: Upload to R2 - uses: ryand56/r2-upload-action@latest + uses: ryand56/r2-upload-action@b801a390acbdeb034c5e684ff5e1361c06639e7c # v1.4 with: r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }} r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4d09603f4..8af7140c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,13 +21,13 @@ jobs: - build-macos-tahoe - build-macos-matrix - build-windows - - build-windows-cross - flatpak-check-zig-cache - flatpak - test - test-gtk - test-sentry-linux - test-macos + - pinact - prettier - alejandra - typos @@ -65,26 +65,26 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Build Benchmarks - run: nix develop -c zig build -Dapp-runtime=glfw -Demit-bench + run: nix develop -c zig build -Demit-bench build-flatpak: strategy: @@ -96,20 +96,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -132,26 +132,26 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Test Build - run: nix develop -c zig build -Dapp-runtime=glfw + run: nix develop -c zig build build-linux-libghostty: runs-on: namespace-profile-ghostty-md @@ -161,20 +161,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -194,20 +194,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -238,20 +238,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -263,7 +263,7 @@ jobs: cp zig-out/dist/*.tar.gz ghostty-source.tar.gz - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: source-tarball path: |- @@ -274,13 +274,13 @@ jobs: needs: test steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -295,7 +295,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. - name: Build GhosttyKit - run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }} + run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false # The native app is built with native Xcode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -314,13 +314,13 @@ jobs: needs: test steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # TODO(tahoe): https://github.com/NixOS/nix/issues/13342 - uses: DeterminateSystems/nix-installer-action@main with: determinate: true - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -335,7 +335,7 @@ jobs: # GhosttyKit is the framework that is built from Zig for our native # Mac app to access. - name: Build GhosttyKit - run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }} + run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false # The native app is built with native Xcode tooling. This also does # codesigning. IMPORTANT: this must NOT run in a Nix environment. @@ -354,13 +354,13 @@ jobs: needs: test steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -374,33 +374,19 @@ jobs: - name: Test All run: | - # OpenGL - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape - - # Metal - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz - nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape + nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=freetype + nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext + nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_freetype + nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_harfbuzz + nix develop -c zig build test --system ${{ steps.deps.outputs.deps }} -Drenderer=metal -Dfont-backend=coretext_noshape - name: Build All run: | - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=freetype - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_freetype - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_harfbuzz - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=opengl -Dfont-backend=coretext_noshape - - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=freetype - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_freetype - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_harfbuzz - nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Dapp-runtime=glfw -Drenderer=metal -Dfont-backend=coretext_noshape + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false -Drenderer=metal -Dfont-backend=freetype + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false -Drenderer=metal -Dfont-backend=coretext + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false -Drenderer=metal -Dfont-backend=coretext_freetype + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false -Drenderer=metal -Dfont-backend=coretext_harfbuzz + nix develop -c zig build --system ${{ steps.deps.outputs.deps }} -Demit-macos-app=false -Drenderer=metal -Dfont-backend=coretext_noshape build-snap: strategy: @@ -415,7 +401,7 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Download Source Tarball Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: source-tarball - name: Extract tarball @@ -423,7 +409,7 @@ jobs: mkdir dist tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix @@ -435,7 +421,7 @@ jobs: _LXD_SNAP_DEVCGROUP_CONFIG="/var/lib/snapd/cgroup/snap.lxd.device" sudo mkdir -p /var/lib/snapd/cgroup echo 'self-managed=true' | sudo tee "${_LXD_SNAP_DEVCGROUP_CONFIG}" - - uses: snapcore/action-build@v1 + - uses: snapcore/action-build@3bdaa03e1ba6bf59a65f84a751d943d549a54e79 # v1.3.0 with: path: dist @@ -446,7 +432,7 @@ jobs: needs: test steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # This could be from a script if we wanted to but inlining here for now # in one place. @@ -507,52 +493,6 @@ jobs: shell: pwsh run: Get-Content -Path ".\build.log" - build-windows-cross: - strategy: - fail-fast: false - matrix: - os: ["namespace-profile-ghostty-md"] - - target: [ - x86-windows-gnu, - x86_64-windows-gnu, - # We don't support cross-compiling to macOS or Linux because - # we require system libraries. - #aarch64-linux, - #x86_64-linux, - #aarch64-macos, - #x86_64-macos, - ] - runs-on: ${{ matrix.os }} - needs: test - env: - ZIG_LOCAL_CACHE_DIR: /zig/local-cache - ZIG_GLOBAL_CACHE_DIR: /zig/global-cache - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 - with: - path: | - /nix - /zig - - # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 - with: - nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 - with: - name: ghostty - authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - - # Cross-compile the binary. We always use static building for this - # because its the only way to access the headers. - - name: Test Build - run: nix develop -c zig build -Dapp-runtime=glfw -Dtarget=${{ matrix.target }} - test: if: github.repository == 'ghostty-org/ghostty' runs-on: namespace-profile-ghostty-md @@ -561,20 +501,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -585,9 +525,6 @@ jobs: - name: Test GTK Build run: nix develop -c zig build -Dapp-runtime=gtk -Demit-docs - - name: Test GLFW Build - run: nix develop -c zig build -Dapp-runtime=glfw - # This relies on the cache being populated by the commands above. - name: Test System Build run: nix develop -c zig build --system ${ZIG_GLOBAL_CACHE_DIR}/p @@ -606,20 +543,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -645,20 +582,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -672,13 +609,13 @@ jobs: needs: test steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -701,17 +638,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -720,6 +657,34 @@ jobs: - name: zig fmt run: nix develop -c zig fmt --check . + pinact: + name: "GitHub Actions Pins" + if: github.repository == 'ghostty-org/ghostty' + runs-on: namespace-profile-ghostty-xsm + timeout-minutes: 60 + env: + ZIG_LOCAL_CACHE_DIR: /zig/local-cache + ZIG_GLOBAL_CACHE_DIR: /zig/global-cache + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Setup Cache + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 + with: + path: | + /nix + /zig + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 + with: + nix_path: nixpkgs=channel:nixos-unstable + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 + with: + name: ghostty + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + skipPush: true + useDaemon: false # sometimes fails on short jobs + - name: pinact check + run: nix develop -c pinact run --check + prettier: if: github.repository == 'ghostty-org/ghostty' runs-on: namespace-profile-ghostty-xsm @@ -728,17 +693,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -755,17 +720,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -782,17 +747,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -809,17 +774,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -836,17 +801,17 @@ jobs: ZIG_LOCAL_CACHE_DIR: /zig/local-cache ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - - uses: actions/checkout@v4 # Check out repo so we can lint it + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -870,20 +835,20 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig # Install Nix and use that to run our tests so our environment matches exactly. - - uses: cachix/install-nix-action@v31 + - uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -898,13 +863,13 @@ jobs: needs: [test, build-dist] steps: - name: Install and configure Namespace CLI - uses: namespacelabs/nscloud-setup@v0 + uses: namespacelabs/nscloud-setup@d1c625762f7c926a54bd39252efff0705fd11c64 # v0.0.10 - name: Configure Namespace powered Buildx - uses: namespacelabs/nscloud-setup-buildx-action@v0 + uses: namespacelabs/nscloud-setup-buildx-action@01628ae51ea5d6b0c90109c7dccbf511953aff29 # v0.0.18 - name: Download Source Tarball Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: source-tarball @@ -914,7 +879,7 @@ jobs: tar --verbose --extract --strip-components 1 --directory dist --file ghostty-source.tar.gz - name: Build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: dist file: dist/src/build/docker/debian/Dockerfile @@ -929,18 +894,18 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - name: Setup Nix - uses: cachix/install-nix-action@v31 + uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -965,8 +930,8 @@ jobs: runs-on: ${{ matrix.variant.runner }} needs: [flatpak-check-zig-cache, test] steps: - - uses: actions/checkout@v4 - - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: flatpak/flatpak-github-actions/flatpak-builder@10a3c29f0162516f0f68006be14c92f34bd4fa6c # v6.5 with: bundle: com.mitchellh.ghostty manifest-path: flatpak/com.mitchellh.ghostty.yml diff --git a/.github/workflows/update-colorschemes.yml b/.github/workflows/update-colorschemes.yml index 2533285e6..b9ded559e 100644 --- a/.github/workflows/update-colorschemes.yml +++ b/.github/workflows/update-colorschemes.yml @@ -17,22 +17,22 @@ jobs: ZIG_GLOBAL_CACHE_DIR: /zig/global-cache steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - name: Setup Cache - uses: namespacelabs/nscloud-cache-action@v1.2.8 + uses: namespacelabs/nscloud-cache-action@449c929cd5138e6607e7e78458e88cc476e76f89 # v1.2.8 with: path: | /nix /zig - name: Setup Nix - uses: cachix/install-nix-action@v31 + uses: cachix/install-nix-action@f0fe604f8a612776892427721526b4c7cfb23aba # v31.4.1 with: nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@v16 + - uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" @@ -60,7 +60,7 @@ jobs: run: nix build .#ghostty - name: Create pull request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: title: Update iTerm2 colorschemes base: main diff --git a/CODEOWNERS b/CODEOWNERS index 1af3a2e23..3bb6a4123 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -155,6 +155,7 @@ /src/input/KeyEncoder.zig @ghostty-org/terminal /src/terminal/ @ghostty-org/terminal /src/terminfo/ @ghostty-org/terminal +/src/termio/ @ghostty-org/terminal /src/unicode/ @ghostty-org/terminal /src/Surface.zig @ghostty-org/terminal /src/surface_mouse.zig @ghostty-org/terminal @@ -179,6 +180,8 @@ /po/uk_UA.UTF-8.po @ghostty-org/uk_UA /po/zh_CN.UTF-8.po @ghostty-org/zh_CN /po/ga_IE.UTF-8.po @ghostty-org/ga_IE +/po/ko_KR.UTF-8.po @ghostty-org/ko_KR +/po/he_IL.UTF-8.po @ghostty-org/he_IL # Packaging - Snap /snap/ @ghostty-org/snap diff --git a/PACKAGING.md b/PACKAGING.md index d85f55de7..1483b8591 100644 --- a/PACKAGING.md +++ b/PACKAGING.md @@ -122,11 +122,3 @@ relevant to package maintainers: often necessary for system packages to specify a specific minimum Linux version, glibc, etc. Run `zig targets` to a get a full list of available targets. - -> [!WARNING] -> -> **The GLFW runtime is not meant for distribution.** The GLFW runtime -> (`-Dapp-runtime=glfw`) is meant for development and testing only. It is -> missing many features, has known memory leak scenarios, known crashes, -> and more. Please do not package the GLFW-based Ghostty runtime for -> distribution. diff --git a/README.md b/README.md index b59964e61..a761e25ce 100644 --- a/README.md +++ b/README.md @@ -194,12 +194,6 @@ omit the `-Doptimize` flag to build a debug build, and you may require additional dependencies since the source tarball includes some processed files that are not in the Git repository. -On Linux or macOS, you can use `zig build -Dapp-runtime=glfw run` for a quick -GLFW-based app for a faster development cycle while developing core -terminal features. Note that this app is missing many features and is also -known to crash in certain scenarios, so it is only meant for development -tasks. - Other useful commands: - `zig build test` for running unit tests. diff --git a/build.zig b/build.zig index 80af88488..024e2db61 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const assert = std.debug.assert; const builtin = @import("builtin"); const buildpkg = @import("src/build/main.zig"); @@ -22,7 +23,14 @@ pub fn build(b: *std.Build) !void { // Ghostty docs const docs = try buildpkg.GhosttyDocs.init(b, &deps); - if (config.emit_docs) docs.install(); + if (config.emit_docs) { + docs.install(); + } else if (config.target.result.os.tag.isDarwin()) { + // If we aren't emitting docs we need to emit a placeholder so + // our macOS xcodeproject builds since it expects the `share/man` + // directory to exist to copy into the app bundle. + docs.installDummy(b.getInstallStep()); + } // Ghostty webdata const webdata = try buildpkg.GhosttyWebdata.init(b, &deps); @@ -42,65 +50,116 @@ pub fn build(b: *std.Build) !void { check_step.dependOn(dist.install_step); } - // If we're not building libghostty, then install the exe and resources. + // libghostty + const libghostty_shared = try buildpkg.GhosttyLib.initShared( + b, + &deps, + ); + const libghostty_static = try buildpkg.GhosttyLib.initStatic( + b, + &deps, + ); + + // Runtime "none" is libghostty, anything else is an executable. if (config.app_runtime != .none) { exe.install(); resources.install(); i18n.install(); - } + } else { + // Libghostty + // + // Note: libghostty is not stable for general purpose use. It is used + // heavily by Ghostty on macOS but it isn't built to be reusable yet. + // As such, these build steps are lacking. For example, the Darwin + // build only produces an xcframework. - // Libghostty - // - // Note: libghostty is not stable for general purpose use. It is used - // heavily by Ghostty on macOS but it isn't built to be reusable yet. - // As such, these build steps are lacking. For example, the Darwin - // build only produces an xcframework. - if (config.app_runtime == .none) { - if (config.target.result.os.tag.isDarwin()) darwin: { - if (!config.emit_xcframework) break :darwin; - - // Build the xcframework - const xcframework = try buildpkg.GhosttyXCFramework.init(b, &deps); - xcframework.install(); - - // The xcframework build always installs resources because our - // macOS xcode project contains references to them. - resources.install(); - i18n.install(); - - // If we aren't emitting docs we need to emit a placeholder so - // our macOS xcodeproject builds. - if (!config.emit_docs) { - var wf = b.addWriteFiles(); - const path = "share/man/.placeholder"; - const placeholder = wf.add(path, "emit-docs not true so no man pages"); - b.getInstallStep().dependOn(&b.addInstallFile(placeholder, path).step); - } - } else { - const libghostty_shared = try buildpkg.GhosttyLib.initShared(b, &deps); - const libghostty_static = try buildpkg.GhosttyLib.initStatic(b, &deps); + // We shouldn't have this guard but we don't currently + // build on macOS this way ironically so we need to fix that. + if (!config.target.result.os.tag.isDarwin()) { libghostty_shared.installHeader(); // Only need one header libghostty_shared.install("libghostty.so"); libghostty_static.install("libghostty.a"); } } - // Run runs the Ghostty exe - { - const run_cmd = b.addRunArtifact(exe.exe); - if (b.args) |args| run_cmd.addArgs(args); - - // Set the proper resources dir so things like shell integration - // work correctly. If we're running `zig build run` in Ghostty, - // this also ensures it overwrites the release one with our debug - // build. - run_cmd.setEnvironmentVariable( - "GHOSTTY_RESOURCES_DIR", - b.getInstallPath(.prefix, "share/ghostty"), + // macOS only artifacts. These will error if they're initialized for + // other targets. + if (config.target.result.os.tag.isDarwin()) { + // Ghostty xcframework + const xcframework = try buildpkg.GhosttyXCFramework.init( + b, + &deps, + config.xcframework_target, ); + if (config.emit_xcframework) { + xcframework.install(); - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); + // The xcframework build always installs resources because our + // macOS xcode project contains references to them. + resources.install(); + i18n.install(); + } + + // Ghostty macOS app + const macos_app = try buildpkg.GhosttyXcodebuild.init( + b, + &config, + .{ + .xcframework = &xcframework, + .docs = &docs, + .i18n = &i18n, + .resources = &resources, + }, + ); + if (config.emit_macos_app) { + macos_app.install(); + } + } + + // Run step + run: { + if (config.app_runtime != .none) { + const run_cmd = b.addRunArtifact(exe.exe); + if (b.args) |args| run_cmd.addArgs(args); + + // Set the proper resources dir so things like shell integration + // work correctly. If we're running `zig build run` in Ghostty, + // this also ensures it overwrites the release one with our debug + // build. + run_cmd.setEnvironmentVariable( + "GHOSTTY_RESOURCES_DIR", + b.getInstallPath(.prefix, "share/ghostty"), + ); + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + break :run; + } + + assert(config.app_runtime == .none); + + // On macOS we can run the macOS app. For "run" we always force + // a native-only build so that we can run as quickly as possible. + if (config.target.result.os.tag.isDarwin()) { + const xcframework_native = try buildpkg.GhosttyXCFramework.init( + b, + &deps, + .native, + ); + const macos_app_native_only = try buildpkg.GhosttyXcodebuild.init( + b, + &config, + .{ + .xcframework = &xcframework_native, + .docs = &docs, + .i18n = &i18n, + .resources = &resources, + }, + ); + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&macos_app_native_only.open.step); + } } // Tests diff --git a/build.zig.zon b/build.zig.zon index 51e2e4538..e98d878f9 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -20,14 +20,14 @@ }, .z2d = .{ // vancluever/z2d - .url = "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz", - .hash = "z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW", + .url = "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz", + .hash = "z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg", .lazy = true, }, .zig_objc = .{ // mitchellh/zig-objc - .url = "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz", - .hash = "zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt", + .url = "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz", + .hash = "zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk", .lazy = true, }, .zig_js = .{ @@ -64,7 +64,6 @@ .cimgui = .{ .path = "./pkg/cimgui", .lazy = true }, .fontconfig = .{ .path = "./pkg/fontconfig", .lazy = true }, .freetype = .{ .path = "./pkg/freetype", .lazy = true }, - .glfw = .{ .path = "./pkg/glfw", .lazy = true }, .gtk4_layer_shell = .{ .path = "./pkg/gtk4-layer-shell", .lazy = true }, .harfbuzz = .{ .path = "./pkg/harfbuzz", .lazy = true }, .highway = .{ .path = "./pkg/highway", .lazy = true }, @@ -100,6 +99,16 @@ .lazy = true, }, + // Fonts + .jetbrains_mono = .{ + .url = "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz", + .hash = "N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x", + }, + .nerd_fonts_symbols_only = .{ + .url = "https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz", + .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO26s", + }, + // Other .apple_sdk = .{ .path = "./pkg/apple-sdk" }, .iterm2_themes = .{ diff --git a/build.zig.zon.json b/build.zig.zon.json index 1d95ed93a..0b904de83 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -19,11 +19,6 @@ "url": "https://deps.files.ghostty.org/gettext-0.24.tar.gz", "hash": "sha256-yRhQPVk9cNr0hE0XWhPYFq+stmfAb7oeydzVACwVGLc=" }, - "N-V-__8AAMrJSwAUGb9-vTzkNR-5LXS81MR__ZRVfF3tWgG6": { - "name": "glfw", - "url": "https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz", - "hash": "sha256-M3N1XUAlMebBo5X1Py+9YxjKXgZ6eacqWRCbUmwLKQo=" - }, "N-V-__8AABzkUgISeKGgXAzgtutgJsZc0-kkeqBBscJgMkvy": { "name": "glslang", "url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz", @@ -59,6 +54,11 @@ "url": "https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025cb960ffa3e7373a2ed8.tar.gz", "hash": "sha256-g9o2CIc/TfWYoUS/l/HP5KZECD7qNsdQUlFruaKkVz4=" }, + "N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x": { + "name": "jetbrains_mono", + "url": "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz", + "hash": "sha256-xXppHouCrQmLWWPzlZAy5AOPORCHr3cViFulkEYQXMQ=" + }, "N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD": { "name": "libpng", "url": "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz", @@ -74,6 +74,11 @@ "url": "https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz", "hash": "sha256-bCgFni4+60K1tLFkieORamNGwQladP7jvGXNxdiaYhU=" }, + "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO26s": { + "name": "nerd_fonts_symbols_only", + "url": "https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz", + "hash": "sha256-EWTRuVbUveJI17LwmYxDzJT1ICQxoVZKeTiVsec7DQQ=" + }, "N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c": { "name": "oniguruma", "url": "https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz", @@ -124,10 +129,10 @@ "url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", "hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM=" }, - "z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW": { + "z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg": { "name": "z2d", - "url": "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz", - "hash": "sha256-wiJs6/LUiy+ApC5s7VPypbBukjBr4vjx3v/l9OrT70U=" + "url": "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz", + "hash": "sha256-wfadegeixcbgxRzRtf6M2H34CTuvDM22XVIhuufFBG4=" }, "zf-0.10.3-OIRy8aiIAACLrBllz0zjxaH0aOe5oNm3KtEMyCntST-9": { "name": "zf", @@ -144,10 +149,10 @@ "url": "https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz", "hash": "sha256-fyNeCVbC9UAaKJY6JhAZlT0A479M/AKYMPIWEZbDWD0=" }, - "zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt": { + "zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk": { "name": "zig_objc", - "url": "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz", - "hash": "sha256-zn1tR6xhSmDla4UJ3t+Gni4Ni3R8deSK3tEe7DGzNXw=" + "url": "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz", + "hash": "sha256-o3vl7qfkSi0bKXa6JWuF92qMEGP8Af/shcip5nRo5Nw=" }, "wayland-0.4.0-dev-lQa1kjfIAQCmhhQu3xF0KH-94-TzeMXOqfnP0-Dg6Wyy": { "name": "zig_wayland", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index fffc639b4..52260181e 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -113,14 +113,6 @@ in hash = "sha256-yRhQPVk9cNr0hE0XWhPYFq+stmfAb7oeydzVACwVGLc="; }; } - { - name = "N-V-__8AAMrJSwAUGb9-vTzkNR-5LXS81MR__ZRVfF3tWgG6"; - path = fetchZigArtifact { - name = "glfw"; - url = "https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz"; - hash = "sha256-M3N1XUAlMebBo5X1Py+9YxjKXgZ6eacqWRCbUmwLKQo="; - }; - } { name = "N-V-__8AABzkUgISeKGgXAzgtutgJsZc0-kkeqBBscJgMkvy"; path = fetchZigArtifact { @@ -177,6 +169,14 @@ in hash = "sha256-g9o2CIc/TfWYoUS/l/HP5KZECD7qNsdQUlFruaKkVz4="; }; } + { + name = "N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x"; + path = fetchZigArtifact { + name = "jetbrains_mono"; + url = "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz"; + hash = "sha256-xXppHouCrQmLWWPzlZAy5AOPORCHr3cViFulkEYQXMQ="; + }; + } { name = "N-V-__8AAJrvXQCqAT8Mg9o_tk6m0yf5Fz-gCNEOKLyTSerD"; path = fetchZigArtifact { @@ -201,6 +201,14 @@ in hash = "sha256-bCgFni4+60K1tLFkieORamNGwQladP7jvGXNxdiaYhU="; }; } + { + name = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO26s"; + path = fetchZigArtifact { + name = "nerd_fonts_symbols_only"; + url = "https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz"; + hash = "sha256-EWTRuVbUveJI17LwmYxDzJT1ICQxoVZKeTiVsec7DQQ="; + }; + } { name = "N-V-__8AAHjwMQDBXnLq3Q2QhaivE0kE2aD138vtX2Bq1g7c"; path = fetchZigArtifact { @@ -282,11 +290,11 @@ in }; } { - name = "z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW"; + name = "z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg"; path = fetchZigArtifact { name = "z2d"; - url = "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz"; - hash = "sha256-wiJs6/LUiy+ApC5s7VPypbBukjBr4vjx3v/l9OrT70U="; + url = "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz"; + hash = "sha256-wfadegeixcbgxRzRtf6M2H34CTuvDM22XVIhuufFBG4="; }; } { @@ -314,11 +322,11 @@ in }; } { - name = "zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt"; + name = "zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk"; path = fetchZigArtifact { name = "zig_objc"; - url = "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz"; - hash = "sha256-zn1tR6xhSmDla4UJ3t+Gni4Ni3R8deSK3tEe7DGzNXw="; + url = "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz"; + hash = "sha256-o3vl7qfkSi0bKXa6JWuF92qMEGP8Af/shcip5nRo5Nw="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index d032711e5..4427763d8 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -2,6 +2,8 @@ git+https://codeberg.org/atman/zg#4a002763419a34d61dcbb1f415821b83b9bf8ddc git+https://github.com/TUSF/zigimg#31268548fe3276c0e95f318a6c0d2ab10565b58d git+https://github.com/rockorager/libvaxis#1f41c121e8fc153d9ce8c6eb64b2bbab68ad7d23 https://codeberg.org/ifreund/zig-wayland/archive/f3c5d503e540ada8cbcb056420de240af0c094f7.tar.gz +https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz +https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d.tar.gz @@ -25,10 +27,9 @@ https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d6 https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz -https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz https://github.com/jcollie/ghostty-gobject/releases/download/0.14.0-2025-03-18-21-1/ghostty-gobject-0.14.0-2025-03-18-21-1.tar.zst https://github.com/mbadolato/iTerm2-Color-Schemes/archive/6fa671fdc1daf1fcfa025cb960ffa3e7373a2ed8.tar.gz https://github.com/mitchellh/libxev/archive/75a10d0fb374e8eb84948dcfc68d865e755e59c2.tar.gz -https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz +https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz https://github.com/natecraddock/zf/archive/7aacbe6d155d64d15937ca95ca6c014905eb531f.tar.gz -https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz +https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz diff --git a/dist/linux/app.desktop.in b/dist/linux/app.desktop.in index c39164158..32ba00cfd 100644 --- a/dist/linux/app.desktop.in +++ b/dist/linux/app.desktop.in @@ -19,6 +19,7 @@ X-TerminalArgAppId=--class= X-TerminalArgDir=--working-directory= X-TerminalArgHold=--wait-after-command DBusActivatable=true +X-KDE-Shortcuts=Ctrl+Alt+T [Desktop Action new-window] Name=New Window diff --git a/dist/linux/systemd.service.in b/dist/linux/systemd.service.in index b0ef3d59a..3ff848ddd 100644 --- a/dist/linux/systemd.service.in +++ b/dist/linux/systemd.service.in @@ -1,7 +1,11 @@ [Unit] Description=@NAME@ +After=graphical-session.target [Service] Type=dbus BusName=@APPID@ ExecStart=@GHOSTTY@ --launched-from=systemd + +[Install] +WantedBy=graphical-session.target diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index 81024bb26..0848d72d4 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -23,12 +23,6 @@ "dest": "vendor/p/N-V-__8AADcZkgn4cMhTUpIz6mShCKyqqB-NBtf_S2bHaTC-", "sha256": "c918503d593d70daf4844d175a13d816afacb667c06fba1ec9dcd5002c1518b7" }, - { - "type": "archive", - "url": "https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz", - "dest": "vendor/p/N-V-__8AAMrJSwAUGb9-vTzkNR-5LXS81MR__ZRVfF3tWgG6", - "sha256": "3373755d402531e6c1a395f53f2fbd6318ca5e067a79a72a59109b526c0b290a" - }, { "type": "archive", "url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz", @@ -71,6 +65,12 @@ "dest": "vendor/p/N-V-__8AAGHcWgTaKLjwmFkxToNT4jgz5VXUHR7hz8TQ2_AS", "sha256": "83da3608873f4df598a144bf97f1cfe4a644083eea36c75052516bb9a2a4573e" }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz", + "dest": "vendor/p/N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x", + "sha256": "c57a691e8b82ad098b5963f3959032e4038f391087af7715885ba59046105cc4" + }, { "type": "archive", "url": "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz", @@ -89,6 +89,12 @@ "dest": "vendor/p/N-V-__8AAG3RoQEyRC2Vw7Qoro5SYBf62IHn3HjqtNVY6aWK", "sha256": "6c28059e2e3eeb42b5b4b16489e3916a6346c1095a74fee3bc65cdc5d89a6215" }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz", + "dest": "vendor/p/N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO26s", + "sha256": "1164d1b956d4bde248d7b2f0998c43cc94f5202431a1564a793895b1e73b0d04" + }, { "type": "archive", "url": "https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz", @@ -151,9 +157,9 @@ }, { "type": "archive", - "url": "https://github.com/vancluever/z2d/archive/1bf4bc81819385f4b24596445c9a7cf3b3592b08.tar.gz", - "dest": "vendor/p/z2d-0.6.1-j5P_HlerCgBokMgrkl9QhJUKXuZWBGdPnH7cSXwv_ScW", - "sha256": "c2226cebf2d48b2f80a42e6ced53f2a5b06e92306be2f8f1deffe5f4ead3ef45" + "url": "https://github.com/vancluever/z2d/archive/8bbd035f4101f02b1d27947def0d7da3215df7fe.tar.gz", + "dest": "vendor/p/z2d-0.7.0-j5P_Hg_DDACq-2H2Zh7rAq6_TXWdQzv7JAUfnrdeDosg", + "sha256": "c1f69d7a07a2c5c6e0c51cd1b5fe8cd87df8093baf0ccdb65d5221bae7c5046e" }, { "type": "archive", @@ -175,9 +181,9 @@ }, { "type": "archive", - "url": "https://github.com/mitchellh/zig-objc/archive/3ab0d37c7d6b933d6ded1b3a35b6b60f05590a98.tar.gz", - "dest": "vendor/p/zig_objc-0.0.0-Ir_Sp3TyAADEVRTxXlScq3t_uKAM91MYNerZkHfbD0yt", - "sha256": "ce7d6d47ac614a60e56b8509dedf869e2e0d8b747c75e48aded11eec31b3357c" + "url": "https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz", + "dest": "vendor/p/zig_objc-0.0.0-Ir_SpwsPAQBJgi9YRm2ubJMfdoysSq5gKpsIj3izQ8Zk", + "sha256": "a37be5eea7e44a2d1b2976ba256b85f76a8c1063fc01ffec85c8a9e67468e4dc" }, { "type": "archive", diff --git a/include/ghostty.h b/include/ghostty.h index 1c6f6f538..f612c29b9 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -350,6 +350,11 @@ typedef struct { const char* message; } ghostty_diagnostic_s; +typedef struct { + const char* ptr; + uintptr_t len; +} ghostty_string_s; + typedef struct { double tl_px_x; double tl_px_y; @@ -679,6 +684,19 @@ typedef struct { bool soft; } ghostty_action_reload_config_s; +// apprt.action.OpenUrlKind +typedef enum { + GHOSTTY_ACTION_OPEN_URL_KIND_UNKNOWN, + GHOSTTY_ACTION_OPEN_URL_KIND_TEXT, +} ghostty_action_open_url_kind_e; + +// apprt.action.OpenUrl.C +typedef struct { + ghostty_action_open_url_kind_e kind; + const char* url; + uintptr_t len; +} ghostty_action_open_url_s; + // apprt.Action.Key typedef enum { GHOSTTY_ACTION_QUIT, @@ -728,7 +746,8 @@ typedef enum { GHOSTTY_ACTION_RING_BELL, GHOSTTY_ACTION_UNDO, GHOSTTY_ACTION_REDO, - GHOSTTY_ACTION_CHECK_FOR_UPDATES + GHOSTTY_ACTION_CHECK_FOR_UPDATES, + GHOSTTY_ACTION_OPEN_URL, } ghostty_action_tag_e; typedef union { @@ -756,6 +775,7 @@ typedef union { ghostty_action_color_change_s color_change; ghostty_action_reload_config_s reload_config; ghostty_action_config_change_s config_change; + ghostty_action_open_url_s open_url; } ghostty_action_u; typedef struct { @@ -795,10 +815,11 @@ typedef struct { //------------------------------------------------------------------- // Published API -int ghostty_init(void); -void ghostty_cli_main(uintptr_t, char**); +int ghostty_init(uintptr_t, char**); +void ghostty_cli_try_action(void); ghostty_info_s ghostty_info(void); const char* ghostty_translate(const char*); +void ghostty_string_free(ghostty_string_s); ghostty_config_t ghostty_config_new(); void ghostty_config_free(ghostty_config_t); @@ -813,7 +834,7 @@ ghostty_input_trigger_s ghostty_config_trigger(ghostty_config_t, uintptr_t); uint32_t ghostty_config_diagnostics_count(ghostty_config_t); ghostty_diagnostic_s ghostty_config_get_diagnostic(ghostty_config_t, uint32_t); -void ghostty_config_open(); +ghostty_string_s ghostty_config_open_path(void); ghostty_app_t ghostty_app_new(const ghostty_runtime_config_s*, ghostty_config_t); diff --git a/macos/Ghostty-Info.plist b/macos/Ghostty-Info.plist index dcce61373..ff391c0f8 100644 --- a/macos/Ghostty-Info.plist +++ b/macos/Ghostty-Info.plist @@ -48,8 +48,8 @@ LSEnvironment - GHOSTTY_MAC_APP - 1 + GHOSTTY_MAC_LAUNCH_SOURCE + app MDItemKeywords Terminal diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index ee6679e50..34e96ab92 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 857F63812A5E64F200CA4815 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 857F63802A5E64F200CA4815 /* MainMenu.xib */; }; 9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; }; A50297352DFA0F3400B4E924 /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50297342DFA0F3300B4E924 /* Double+Extension.swift */; }; + A505D21D2E1A2FA20018808F /* FileHandle+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */; }; + A505D21F2E1B6DE00018808F /* NSWorkspace+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A505D21E2E1B6DDC0018808F /* NSWorkspace+Extension.swift */; }; A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A511940E2E050590007258CC /* CloseTerminalIntent.swift */; }; A51194112E05A483007258CC /* QuickTerminalIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194102E05A480007258CC /* QuickTerminalIntent.swift */; }; A51194132E05D006007258CC /* Optional+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51194122E05D003007258CC /* Optional+Extension.swift */; }; @@ -159,6 +161,8 @@ 857F63802A5E64F200CA4815 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; 9351BE8E2D22937F003B3499 /* nvim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = nvim; path = "../zig-out/share/nvim"; sourceTree = ""; }; A50297342DFA0F3300B4E924 /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; + A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileHandle+Extension.swift"; sourceTree = ""; }; + A505D21E2E1B6DDC0018808F /* NSWorkspace+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSWorkspace+Extension.swift"; sourceTree = ""; }; A511940E2E050590007258CC /* CloseTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseTerminalIntent.swift; sourceTree = ""; }; A51194102E05A480007258CC /* QuickTerminalIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalIntent.swift; sourceTree = ""; }; A51194122E05D003007258CC /* Optional+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Optional+Extension.swift"; sourceTree = ""; }; @@ -519,6 +523,7 @@ A586366A2DF0A98900E04A10 /* Array+Extension.swift */, A50297342DFA0F3300B4E924 /* Double+Extension.swift */, A586366E2DF25D8300E04A10 /* Duration+Extension.swift */, + A505D21C2E1A2F9E0018808F /* FileHandle+Extension.swift */, A53A29802DB44A5E00B6E02C /* KeyboardShortcut+Extension.swift */, A53A297E2DB4480A00B6E02C /* EventModifiers+Extension.swift */, A51194122E05D003007258CC /* Optional+Extension.swift */, @@ -531,6 +536,7 @@ AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */, C1F26EA62B738B9900404083 /* NSView+Extension.swift */, A5874D9C2DAD785F00E83852 /* NSWindow+Extension.swift */, + A505D21E2E1B6DDC0018808F /* NSWorkspace+Extension.swift */, A5985CD62C320C4500C57AD3 /* String+Extension.swift */, A58636722DF4813000E04A10 /* UndoManager+Extension.swift */, A5CC36142C9CDA03004D6760 /* View+Extension.swift */, @@ -802,6 +808,7 @@ A5874D9D2DAD786100E83852 /* NSWindow+Extension.swift in Sources */, A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */, A58636732DF4813400E04A10 /* UndoManager+Extension.swift in Sources */, + A505D21D2E1A2FA20018808F /* FileHandle+Extension.swift in Sources */, A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */, CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */, A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */, @@ -818,6 +825,7 @@ A5CC36152C9CDA06004D6760 /* View+Extension.swift in Sources */, A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */, A5CBD0602CA0C90A0017A1AE /* QuickTerminalWindow.swift in Sources */, + A505D21F2E1B6DE00018808F /* NSWorkspace+Extension.swift in Sources */, A5CBD05E2CA0C5EC0017A1AE /* QuickTerminalController.swift in Sources */, A5CF66D72D29DDB500139794 /* Ghostty.Event.swift in Sources */, A511940F2E050595007258CC /* CloseTerminalIntent.swift in Sources */, diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift index 734fcbc20..38500b7d3 100644 --- a/macos/Sources/App/macOS/AppDelegate.swift +++ b/macos/Sources/App/macOS/AppDelegate.swift @@ -112,6 +112,9 @@ class AppDelegate: NSObject, /// The observer for the app appearance. private var appearanceObserver: NSKeyValueObservation? = nil + /// Signals + private var signals: [DispatchSourceSignal] = [] + /// The custom app icon image that is currently in use. @Published private(set) var appIcon: NSImage? = nil { didSet { @@ -249,6 +252,24 @@ class AppDelegate: NSObject, // Setup our menu setupMenuImages() + + // Setup signal handlers + setupSignals() + + // If we launched via zig run then we need to force foreground. + if Ghostty.launchSource == .zig_run { + // This never gets called until we click the dock icon. This forces it + // activate immediately. + applicationDidBecomeActive(.init(name: NSApplication.didBecomeActiveNotification)) + + // We run in the background, this forces us to the front. + DispatchQueue.main.async { + NSApp.setActivationPolicy(.regular) + NSApp.activate(ignoringOtherApps: true) + NSApp.unhide(nil) + NSApp.arrangeInFront(nil) + } + } } func applicationDidBecomeActive(_ notification: Notification) { @@ -406,6 +427,34 @@ class AppDelegate: NSObject, return dockMenu } + /// Setup signal handlers + private func setupSignals() { + // Register a signal handler for config reloading. It appears that all + // of this is required. I've commented each line because its a bit unclear. + // Warning: signal handlers don't work when run via Xcode. They have to be + // run on a real app bundle. + + // We need to ignore signals we register with makeSignalSource or they + // don't seem to handle. + signal(SIGUSR2, SIG_IGN) + + // Make the signal source and register our event handle. We keep a weak + // ref to ourself so we don't create a retain cycle. + let sigusr2 = DispatchSource.makeSignalSource(signal: SIGUSR2, queue: .main) + sigusr2.setEventHandler { [weak self] in + guard let self else { return } + Ghostty.logger.info("reloading configuration in response to SIGUSR2") + self.ghostty.reloadConfig() + } + + // The signal source starts unactivated, so we have to resume it once + // we setup the event handler. + sigusr2.resume() + + // We need to keep a strong reference to it so it isn't disabled. + signals.append(sigusr2) + } + /// Setup all the images for our menu items. private func setupMenuImages() { // Note: This COULD Be done all in the xib file, but I find it easier to @@ -883,7 +932,7 @@ class AppDelegate: NSObject, //MARK: - IB Actions @IBAction func openConfig(_ sender: Any?) { - ghostty.openConfig() + Ghostty.App.openConfig() } @IBAction func reloadConfig(_ sender: Any?) { diff --git a/macos/Sources/App/macOS/main.swift b/macos/Sources/App/macOS/main.swift index 990ef8ef1..ad32f4e70 100644 --- a/macos/Sources/App/macOS/main.swift +++ b/macos/Sources/App/macOS/main.swift @@ -2,13 +2,32 @@ import AppKit import Cocoa import GhosttyKit -// We put the GHOSTTY_MAC_APP env var into the Info.plist to detect -// whether we launch from the app or not. A user can fake this if -// they want but they're doing so at their own detriment... -let process = ProcessInfo.processInfo -if ((process.environment["GHOSTTY_MAC_APP"] ?? "") == "") { - ghostty_cli_main(UInt(CommandLine.argc), CommandLine.unsafeArgv) - exit(1) +// Initialize Ghostty global state. We do this once right away because the +// CLI APIs require it and it lets us ensure it is done immediately for the +// rest of the app. +if ghostty_init(UInt(CommandLine.argc), CommandLine.unsafeArgv) != GHOSTTY_SUCCESS { + Ghostty.logger.critical("ghostty_init failed") + + // We also write to stderr if this is executed from the CLI or zig run + switch Ghostty.launchSource { + case .cli, .zig_run: + let stderrHandle = FileHandle.standardError + stderrHandle.write( + "Ghostty failed to initialize! If you're executing Ghostty from the command line\n" + + "then this is usually because an invalid action or multiple actions were specified.\n" + + "Actions start with the `+` character.\n\n" + + "View all available actions by running `ghostty +help`.\n") + exit(1) + + case .app: + // For the app we exit immediately. We should handle this case more + // gracefully in the future. + exit(1) + } } +// This will run the CLI action and exit if one was specified. A CLI +// action is a command starting with a `+`, such as `ghostty +boo`. +ghostty_cli_try_action(); + _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) diff --git a/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift b/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift index e29fbba3f..cc3b9f63a 100644 --- a/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift +++ b/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift @@ -14,26 +14,6 @@ struct TerminalEntity: AppEntity { @Property(title: "Kind") var kind: Kind - @MainActor - @DeferredProperty(title: "Full Contents") - @available(macOS 26.0, *) - var screenContents: String? { - get async { - guard let surfaceView else { return nil } - return surfaceView.cachedScreenContents.get() - } - } - - @MainActor - @DeferredProperty(title: "Visible Contents") - @available(macOS 26.0, *) - var visibleContents: String? { - get async { - guard let surfaceView else { return nil } - return surfaceView.cachedVisibleContents.get() - } - } - var screenshot: Image? static var typeDisplayRepresentation: TypeDisplayRepresentation { diff --git a/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift index 5f4d6b177..996506f0b 100644 --- a/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift @@ -19,20 +19,27 @@ class HiddenTitlebarTerminalWindow: TerminalWindow { NotificationCenter.default.removeObserver(self) } + private static let hiddenStyleMask: NSWindow.StyleMask = [ + // We need `titled` in the mask to get the normal window frame + .titled, + + // Full size content view so we can extend + // content in to the hidden titlebar's area + .fullSizeContentView, + + .resizable, + .closable, + .miniaturizable, + ] + /// Apply the hidden titlebar style. private func reapplyHiddenStyle() { - styleMask = [ - // We need `titled` in the mask to get the normal window frame - .titled, - - // Full size content view so we can extend - // content in to the hidden titlebar's area - .fullSizeContentView, - - .resizable, - .closable, - .miniaturizable, - ] + // Apply our style mask while preserving the .fullScreen option + if styleMask.contains(.fullScreen) { + styleMask = Self.hiddenStyleMask.union([.fullScreen]) + } else { + styleMask = Self.hiddenStyleMask + } // Hide the title titleVisibility = .hidden diff --git a/macos/Sources/Ghostty/Ghostty.Action.swift b/macos/Sources/Ghostty/Ghostty.Action.swift index dfdb0bff5..a6559600d 100644 --- a/macos/Sources/Ghostty/Ghostty.Action.swift +++ b/macos/Sources/Ghostty/Ghostty.Action.swift @@ -40,4 +40,34 @@ extension Ghostty.Action { self.amount = c.amount } } + + struct OpenURL { + enum Kind { + case unknown + case text + + init(_ c: ghostty_action_open_url_kind_e) { + switch c { + case GHOSTTY_ACTION_OPEN_URL_KIND_TEXT: + self = .text + default: + self = .unknown + } + } + } + + let kind: Kind + let url: String + + init(c: ghostty_action_open_url_s) { + self.kind = Kind(c.kind) + + if let urlCString = c.url { + let data = Data(bytes: urlCString, count: Int(c.len)) + self.url = String(data: data, encoding: .utf8) ?? "" + } else { + self.url = "" + } + } + } } diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index ba0b95212..0fdea1760 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -45,12 +45,6 @@ extension Ghostty { } init() { - // Initialize ghostty global state. This happens once per process. - if ghostty_init() != GHOSTTY_SUCCESS { - logger.critical("ghostty_init failed, weird things may happen") - readiness = .error - } - // Initialize the global configuration. self.config = Config() if self.config.config == nil { @@ -120,9 +114,21 @@ extension Ghostty { ghostty_app_tick(app) } - func openConfig() { - guard let app = self.app else { return } - ghostty_app_open_config(app) + static func openConfig() { + let str = Ghostty.AllocatedString(ghostty_config_open_path()).string + guard !str.isEmpty else { return } + #if os(macOS) + let fileURL = URL(fileURLWithPath: str).absoluteString + var action = ghostty_action_open_url_s() + action.kind = GHOSTTY_ACTION_OPEN_URL_KIND_TEXT + fileURL.withCString { cStr in + action.url = cStr + action.len = UInt(fileURL.count) + _ = openURL(action) + } + #else + fatalError("Unsupported platform for opening config file") + #endif } /// Reload the configuration. @@ -494,7 +500,7 @@ extension Ghostty { pwdChanged(app, target: target, v: action.action.pwd) case GHOSTTY_ACTION_OPEN_CONFIG: - ghostty_config_open() + openConfig() case GHOSTTY_ACTION_FLOAT_WINDOW: toggleFloatWindow(app, target: target, mode: action.action.float_window) @@ -552,6 +558,9 @@ extension Ghostty { case GHOSTTY_ACTION_CHECK_FOR_UPDATES: checkForUpdates(app) + + case GHOSTTY_ACTION_OPEN_URL: + return openURL(action.action.open_url) case GHOSTTY_ACTION_UNDO: return undo(app, target: target) @@ -604,6 +613,34 @@ extension Ghostty { appDelegate.checkForUpdates(nil) } } + + private static func openURL( + _ v: ghostty_action_open_url_s + ) -> Bool { + let action = Ghostty.Action.OpenURL(c: v) + + // Convert the URL string to a URL object + guard let url = URL(string: action.url) else { + Ghostty.logger.warning("invalid URL for open URL action: \(action.url)") + return false + } + + switch action.kind { + case .text: + // Open with the default text editor + if let textEditor = NSWorkspace.shared.defaultTextEditor { + NSWorkspace.shared.open([url], withApplicationAt: textEditor, configuration: NSWorkspace.OpenConfiguration()) + return true + } + + case .unknown: + break + } + + // Open with the default application for the URL + NSWorkspace.shared.open(url) + return true + } private static func undo(_ app: ghostty_app_t, target: ghostty_target_s) -> Bool { let undoManager: UndoManager? diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index 125a09825..9b05934df 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -48,8 +48,51 @@ extension Ghostty { } } +// MARK: General Helpers + +extension Ghostty { + enum LaunchSource: String { + case cli + case app + case zig_run + } + + /// Returns the mechanism that launched the app. This is based on an env var so + /// its up to the env var being set in the correct circumstance. + static var launchSource: LaunchSource { + guard let envValue = ProcessInfo.processInfo.environment["GHOSTTY_MAC_LAUNCH_SOURCE"] else { + // We default to the CLI because the app bundle always sets the + // source. If its unset we assume we're in a CLI environment. + return .cli + } + + // If the env var is set but its unknown then we default back to the app. + return LaunchSource(rawValue: envValue) ?? .app + } +} + // MARK: Swift Types for C Types +extension Ghostty { + class AllocatedString { + private let cString: ghostty_string_s + + init(_ c: ghostty_string_s) { + self.cString = c + } + + var string: String { + guard let ptr = cString.ptr else { return "" } + let data = Data(bytes: ptr, count: Int(cString.len)) + return String(data: data, encoding: .utf8) ?? "" + } + + deinit { + ghostty_string_free(cString) + } + } +} + extension Ghostty { enum SetFloatWIndow { case on diff --git a/macos/Sources/Helpers/Extensions/FileHandle+Extension.swift b/macos/Sources/Helpers/Extensions/FileHandle+Extension.swift new file mode 100644 index 000000000..b6df4a60f --- /dev/null +++ b/macos/Sources/Helpers/Extensions/FileHandle+Extension.swift @@ -0,0 +1,9 @@ +import Foundation + +extension FileHandle: @retroactive TextOutputStream { + /// Write a string to a filehandle. + public func write(_ string: String) { + let data = Data(string.utf8) + self.write(data) + } +} diff --git a/macos/Sources/Helpers/Extensions/NSWorkspace+Extension.swift b/macos/Sources/Helpers/Extensions/NSWorkspace+Extension.swift new file mode 100644 index 000000000..bc2d028b5 --- /dev/null +++ b/macos/Sources/Helpers/Extensions/NSWorkspace+Extension.swift @@ -0,0 +1,29 @@ +import AppKit +import UniformTypeIdentifiers + +extension NSWorkspace { + /// Returns the URL of the default text editor application. + /// - Returns: The URL of the default text editor, or nil if no default text editor is found. + var defaultTextEditor: URL? { + defaultApplicationURL(forContentType: UTType.plainText.identifier) + } + + /// Returns the URL of the default application for opening files with the specified content type. + /// - Parameter contentType: The content type identifier (UTI) to find the default application for. + /// - Returns: The URL of the default application, or nil if no default application is found. + func defaultApplicationURL(forContentType contentType: String) -> URL? { + return LSCopyDefaultApplicationURLForContentType( + contentType as CFString, + .all, + nil + )?.takeRetainedValue() as? URL + } + + /// Returns the URL of the default application for opening files with the specified file extension. + /// - Parameter ext: The file extension to find the default application for. + /// - Returns: The URL of the default application, or nil if no default application is found. + func defaultApplicationURL(forExtension ext: String) -> URL? { + guard let uti = UTType(filenameExtension: ext) else { return nil} + return defaultApplicationURL(forContentType: uti.identifier) + } +} diff --git a/nix/devShell.nix b/nix/devShell.nix index f4ea62235..8a8ab441f 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -58,6 +58,7 @@ jq, minisign, pandoc, + pinact, hyperfine, typos, uv, @@ -98,6 +99,7 @@ in # Linting nodePackages.prettier alejandra + pinact typos # Testing diff --git a/pkg/freetype/freetype-zig.h b/pkg/freetype/freetype-zig.h index 29e546154..dcc65e514 100644 --- a/pkg/freetype/freetype-zig.h +++ b/pkg/freetype/freetype-zig.h @@ -5,3 +5,5 @@ #include #include #include +#include +#include diff --git a/pkg/glfw/Cursor.zig b/pkg/glfw/Cursor.zig deleted file mode 100644 index cd79e8848..000000000 --- a/pkg/glfw/Cursor.zig +++ /dev/null @@ -1,209 +0,0 @@ -//! Represents a cursor and provides facilities for setting cursor images. - -const std = @import("std"); -const testing = std.testing; - -const c = @import("c.zig").c; -const Image = @import("Image.zig"); - -const internal_debug = @import("internal_debug.zig"); - -const Cursor = @This(); - -ptr: *c.GLFWcursor, - -/// Standard system cursor shapes. -/// -/// These are the standard cursor shapes that can be requested from the platform (window system). -pub const Shape = enum(i32) { - /// The regular arrow cursor shape. - arrow = c.GLFW_ARROW_CURSOR, - - /// The text input I-beam cursor shape. - ibeam = c.GLFW_IBEAM_CURSOR, - - /// The crosshair cursor shape. - crosshair = c.GLFW_CROSSHAIR_CURSOR, - - /// The pointing hand cursor shape. - /// - /// NOTE: This supersedes the old `hand` enum. - pointing_hand = c.GLFW_POINTING_HAND_CURSOR, - - /// The horizontal resize/move arrow shape. - /// - /// The horizontal resize/move arrow shape. This is usually a horizontal double-headed arrow. - // - // NOTE: This supersedes the old `hresize` enum. - resize_ew = c.GLFW_RESIZE_EW_CURSOR, - - /// The vertical resize/move arrow shape. - /// - /// The vertical resize/move shape. This is usually a vertical double-headed arrow. - /// - /// NOTE: This supersedes the old `vresize` enum. - resize_ns = c.GLFW_RESIZE_NS_CURSOR, - - /// The top-left to bottom-right diagonal resize/move arrow shape. - /// - /// The top-left to bottom-right diagonal resize/move shape. This is usually a diagonal - /// double-headed arrow. - /// - /// macos: This shape is provided by a private system API and may fail CursorUnavailable in the - /// future. - /// - /// x11: This shape is provided by a newer standard not supported by all cursor themes. - /// - /// wayland: This shape is provided by a newer standard not supported by all cursor themes. - resize_nwse = c.GLFW_RESIZE_NWSE_CURSOR, - - /// The top-right to bottom-left diagonal resize/move arrow shape. - /// - /// The top-right to bottom-left diagonal resize/move shape. This is usually a diagonal - /// double-headed arrow. - /// - /// macos: This shape is provided by a private system API and may fail with CursorUnavailable - /// in the future. - /// - /// x11: This shape is provided by a newer standard not supported by all cursor themes. - /// - /// wayland: This shape is provided by a newer standard not supported by all cursor themes. - resize_nesw = c.GLFW_RESIZE_NESW_CURSOR, - - /// The omni-directional resize/move cursor shape. - /// - /// The omni-directional resize cursor/move shape. This is usually either a combined horizontal - /// and vertical double-headed arrow or a grabbing hand. - resize_all = c.GLFW_RESIZE_ALL_CURSOR, - - /// The operation-not-allowed shape. - /// - /// The operation-not-allowed shape. This is usually a circle with a diagonal line through it. - /// - /// x11: This shape is provided by a newer standard not supported by all cursor themes. - /// - /// wayland: This shape is provided by a newer standard not supported by all cursor themes. - not_allowed = c.GLFW_NOT_ALLOWED_CURSOR, -}; - -/// Creates a custom cursor. -/// -/// Creates a new custom cursor image that can be set for a window with glfw.Cursor.set. The cursor -/// can be destroyed with glfwCursor.destroy. Any remaining cursors are destroyed by glfw.terminate. -/// -/// The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight bits per channel with -/// the red channel first. They are arranged canonically as packed sequential rows, starting from -/// the top-left corner. -/// -/// The cursor hotspot is specified in pixels, relative to the upper-left corner of the cursor -/// image. Like all other coordinate systems in GLFW, the X-axis points to the right and the Y-axis -/// points down. -/// -/// @param[in] image The desired cursor image. -/// @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot. -/// @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot. -/// @return The handle of the created cursor. -/// -/// Possible errors include glfw.ErrorCode.PlatformError and glfw.ErrorCode.InvalidValue -/// null is returned in the event of an error. -/// -/// @pointer_lifetime The specified image data is copied before this function returns. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_object, glfw.Cursor.destroy, glfw.Cursor.createStandard -pub inline fn create(image: Image, xhot: i32, yhot: i32) ?Cursor { - internal_debug.assertInitialized(); - const img = image.toC(); - if (c.glfwCreateCursor(&img, @as(c_int, @intCast(xhot)), @as(c_int, @intCast(yhot)))) |cursor| return Cursor{ .ptr = cursor }; - return null; -} - -/// Creates a cursor with a standard shape. -/// -/// Returns a cursor with a standard shape, that can be set for a window with glfw.Window.setCursor. -/// The images for these cursors come from the system cursor theme and their exact appearance will -/// vary between platforms. -/// -/// Most of these shapes are guaranteed to exist on every supported platform but a few may not be -/// present. See the table below for details. -/// -/// | Cursor shape | Windows | macOS | X11 | Wayland | -/// |------------------|---------|-----------------|-------------------|-------------------| -/// | `.arrow` | Yes | Yes | Yes | Yes | -/// | `.ibeam` | Yes | Yes | Yes | Yes | -/// | `.crosshair` | Yes | Yes | Yes | Yes | -/// | `.pointing_hand` | Yes | Yes | Yes | Yes | -/// | `.resize_ew` | Yes | Yes | Yes | Yes | -/// | `.resize_ns` | Yes | Yes | Yes | Yes | -/// | `.resize_nwse` | Yes | Yes1 | Maybe2 | Maybe2 | -/// | `.resize_nesw` | Yes | Yes1 | Maybe2 | Maybe2 | -/// | `.resize_all` | Yes | Yes | Yes | Yes | -/// | `.not_allowed` | Yes | Yes | Maybe2 | Maybe2 | -/// -/// 1. This uses a private system API and may fail in the future. -/// 2. This uses a newer standard that not all cursor themes support. -/// -/// If the requested shape is not available, this function emits a CursorUnavailable error -/// Possible errors include glfw.ErrorCode.PlatformError and glfw.ErrorCode.CursorUnavailable. -/// null is returned in the event of an error. -/// -/// thread_safety: This function must only be called from the main thread. -/// -/// see also: cursor_object, glfwCreateCursor -pub inline fn createStandard(shape: Shape) ?Cursor { - internal_debug.assertInitialized(); - if (c.glfwCreateStandardCursor(@as(c_int, @intCast(@intFromEnum(shape))))) |cursor| return Cursor{ .ptr = cursor }; - return null; -} - -/// Destroys a cursor. -/// -/// This function destroys a cursor previously created with glfw.Cursor.create. Any remaining -/// cursors will be destroyed by glfw.terminate. -/// -/// If the specified cursor is current for any window, that window will be reverted to the default -/// cursor. This does not affect the cursor mode. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @reentrancy This function must not be called from a callback. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_object, glfw.createCursor -pub inline fn destroy(self: Cursor) void { - internal_debug.assertInitialized(); - c.glfwDestroyCursor(self.ptr); -} - -test "create" { - const allocator = testing.allocator; - - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const image = try Image.init(allocator, 32, 32, 32 * 32 * 4); - defer image.deinit(allocator); - - const cursor = glfw.Cursor.create(image, 0, 0); - if (cursor) |cur| cur.destroy(); -} - -test "createStandard" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const cursor = glfw.Cursor.createStandard(.ibeam); - if (cursor) |cur| cur.destroy(); -} diff --git a/pkg/glfw/GammaRamp.zig b/pkg/glfw/GammaRamp.zig deleted file mode 100644 index 7b5a1c3fd..000000000 --- a/pkg/glfw/GammaRamp.zig +++ /dev/null @@ -1,74 +0,0 @@ -//! Gamma ramp for monitors and related functions. -//! -//! It may be .owned (e.g. in the case of a gamma ramp initialized by you for passing into -//! glfw.Monitor.setGammaRamp) or not .owned (e.g. in the case of one gotten via -//! glfw.Monitor.getGammaRamp.) If it is .owned, deinit should be called to free the memory. It is -//! safe to call deinit even if not .owned. -//! -//! see also: monitor_gamma, glfw.Monitor.getGammaRamp - -const std = @import("std"); -const testing = std.testing; -const mem = std.mem; -const c = @import("c.zig").c; - -const GammaRamp = @This(); - -red: []u16, -green: []u16, -blue: []u16, -owned: ?[]u16, - -/// Initializes a new owned gamma ramp with the given array size and undefined values. -/// -/// see also: glfw.Monitor.getGammaRamp -pub inline fn init(allocator: mem.Allocator, size: usize) !GammaRamp { - const buf = try allocator.alloc(u16, size * 3); - return GammaRamp{ - .red = buf[size * 0 .. (size * 0) + size], - .green = buf[size * 1 .. (size * 1) + size], - .blue = buf[size * 2 .. (size * 2) + size], - .owned = buf, - }; -} - -/// Turns a GLFW / C gamma ramp into the nicer Zig type, and sets `.owned = false`. -/// -/// The returned memory is valid for as long as the GLFW C memory is valid. -pub inline fn fromC(native: c.GLFWgammaramp) GammaRamp { - return GammaRamp{ - .red = native.red[0..native.size], - .green = native.green[0..native.size], - .blue = native.blue[0..native.size], - .owned = null, - }; -} - -/// Turns the nicer Zig type into a GLFW / C gamma ramp, for passing into GLFW C functions. -/// -/// The returned memory is valid for as long as the Zig memory is valid. -pub inline fn toC(self: GammaRamp) c.GLFWgammaramp { - std.debug.assert(self.red.len == self.green.len); - std.debug.assert(self.red.len == self.blue.len); - return c.GLFWgammaramp{ - .red = &self.red[0], - .green = &self.green[0], - .blue = &self.blue[0], - .size = @as(c_uint, @intCast(self.red.len)), - }; -} - -/// Deinitializes the memory using the allocator iff `.owned = true`. -pub inline fn deinit(self: GammaRamp, allocator: mem.Allocator) void { - if (self.owned) |buf| allocator.free(buf); -} - -test "conversion" { - const allocator = testing.allocator; - - const ramp = try GammaRamp.init(allocator, 256); - defer ramp.deinit(allocator); - - const glfw = ramp.toC(); - _ = GammaRamp.fromC(glfw); -} diff --git a/pkg/glfw/Image.zig b/pkg/glfw/Image.zig deleted file mode 100644 index d32e5c310..000000000 --- a/pkg/glfw/Image.zig +++ /dev/null @@ -1,82 +0,0 @@ -//! Image data -//! -//! -//! This describes a single 2D image. See the documentation for each related function what the -//! expected pixel format is. -//! -//! see also: cursor_custom, window_icon -//! -//! It may be .owned (e.g. in the case of an image initialized by you for passing into glfw) or not -//! .owned (e.g. in the case of one gotten via glfw) If it is .owned, deinit should be called to -//! free the memory. It is safe to call deinit even if not .owned. - -const std = @import("std"); -const testing = std.testing; -const mem = std.mem; -const c = @import("c.zig").c; - -const Image = @This(); - -/// The width of this image, in pixels. -width: u32, - -/// The height of this image, in pixels. -height: u32, - -/// The pixel data of this image, arranged left-to-right, top-to-bottom. -pixels: []u8, - -/// Whether or not the pixels data is owned by you (true) or GLFW (false). -owned: bool, - -/// Initializes a new owned image with the given size and pixel_data_len of undefined .pixel values. -pub inline fn init(allocator: mem.Allocator, width: u32, height: u32, pixel_data_len: usize) !Image { - const buf = try allocator.alloc(u8, pixel_data_len); - return Image{ - .width = width, - .height = height, - .pixels = buf, - .owned = true, - }; -} - -/// Turns a GLFW / C image into the nicer Zig type, and sets `.owned = false`. -/// -/// The length of pixel data must be supplied, as GLFW's image type does not itself describe the -/// number of bytes required per pixel / the length of the pixel data array. -/// -/// The returned memory is valid for as long as the GLFW C memory is valid. -pub inline fn fromC(native: c.GLFWimage, pixel_data_len: usize) Image { - return Image{ - .width = @as(u32, @intCast(native.width)), - .height = @as(u32, @intCast(native.height)), - .pixels = native.pixels[0..pixel_data_len], - .owned = false, - }; -} - -/// Turns the nicer Zig type into a GLFW / C image, for passing into GLFW C functions. -/// -/// The returned memory is valid for as long as the Zig memory is valid. -pub inline fn toC(self: Image) c.GLFWimage { - return c.GLFWimage{ - .width = @as(c_int, @intCast(self.width)), - .height = @as(c_int, @intCast(self.height)), - .pixels = &self.pixels[0], - }; -} - -/// Deinitializes the memory using the allocator iff `.owned = true`. -pub inline fn deinit(self: Image, allocator: mem.Allocator) void { - if (self.owned) allocator.free(self.pixels); -} - -test "conversion" { - const allocator = testing.allocator; - - const image = try Image.init(allocator, 256, 256, 256 * 256 * 4); - defer image.deinit(allocator); - - const glfw = image.toC(); - _ = Image.fromC(glfw, image.width * image.height * 4); -} diff --git a/pkg/glfw/Joystick.zig b/pkg/glfw/Joystick.zig deleted file mode 100644 index a8152513e..000000000 --- a/pkg/glfw/Joystick.zig +++ /dev/null @@ -1,642 +0,0 @@ -//! Represents a Joystick or gamepad -//! -//! It can be manually crafted via e.g. `glfw.Joystick{.jid = .one}`, but more -//! typically you'll want to discover the joystick using `glfw.Joystick.setCallback`. - -const std = @import("std"); - -const c = @import("c.zig").c; -const Window = @import("Window.zig"); -const Action = @import("action.zig").Action; -const GamepadAxis = @import("gamepad_axis.zig").GamepadAxis; -const GamepadButton = @import("gamepad_button.zig").GamepadButton; -const Hat = @import("hat.zig").Hat; - -const internal_debug = @import("internal_debug.zig"); - -const Joystick = @This(); - -/// The GLFW joystick ID. -jid: Id, - -/// Joystick IDs. -/// -/// See glfw.Joystick.setCallback for how these are used. -pub const Id = enum(c_int) { - one = c.GLFW_JOYSTICK_1, - two = c.GLFW_JOYSTICK_2, - three = c.GLFW_JOYSTICK_3, - four = c.GLFW_JOYSTICK_4, - five = c.GLFW_JOYSTICK_5, - six = c.GLFW_JOYSTICK_6, - seven = c.GLFW_JOYSTICK_7, - eight = c.GLFW_JOYSTICK_8, - nine = c.GLFW_JOYSTICK_9, - ten = c.GLFW_JOYSTICK_10, - eleven = c.GLFW_JOYSTICK_11, - twelve = c.GLFW_JOYSTICK_12, - thirteen = c.GLFW_JOYSTICK_13, - fourteen = c.GLFW_JOYSTICK_14, - fifteen = c.GLFW_JOYSTICK_15, - sixteen = c.GLFW_JOYSTICK_16, - pub const last = @as(@This(), @enumFromInt(c.GLFW_JOYSTICK_LAST)); -}; - -/// Gamepad input state -/// -/// This describes the input state of a gamepad. -/// -/// see also: gamepad, glfwGetGamepadState -const GamepadState = extern struct { - /// The states of each gamepad button (see gamepad_buttons), `glfw.Action.press` or `glfw.Action.release`. - /// - /// Use the enumeration helper e.g. `.getButton(.dpad_up)` to access these indices. - buttons: [15]u8, - - /// The states of each gamepad axis (see gamepad_axes), in the range -1.0 to 1.0 inclusive. - /// - /// Use the enumeration helper e.g. `.getAxis(.left_x)` to access these indices. - axes: [6]f32, - - /// Returns the state of the specified gamepad button. - pub fn getButton(self: @This(), which: GamepadButton) Action { - return @as(Action, @enumFromInt(self.buttons[@as(u32, @intCast(@intFromEnum(which)))])); - } - - /// Returns the status of the specified gamepad axis, in the range -1.0 to 1.0 inclusive. - pub fn getAxis(self: @This(), which: GamepadAxis) f32 { - return self.axes[@as(u32, @intCast(@intFromEnum(which)))]; - } -}; - -/// Returns whether the specified joystick is present. -/// -/// This function returns whether the specified joystick is present. -/// -/// There is no need to call this function before other functions that accept a joystick ID, as -/// they all check for presence before performing any other work. -/// -/// @return `true` if the joystick is present, or `false` otherwise. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick -pub inline fn present(self: Joystick) bool { - internal_debug.assertInitialized(); - const is_present = c.glfwJoystickPresent(@intFromEnum(self.jid)); - return is_present == c.GLFW_TRUE; -} - -/// Returns the values of all axes of the specified joystick. -/// -/// This function returns the values of all axes of the specified joystick. Each element in the -/// array is a value between -1.0 and 1.0. -/// -/// If the specified joystick is not present this function will return null but will not generate -/// an error. This can be used instead of first calling glfw.Joystick.present. -/// -/// @return An array of axis values, or null if the joystick is not present. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// null is additionally returned in the event of an error. -/// -/// @pointer_lifetime The returned array is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected or the library is -/// terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick_axis -/// Replaces `glfwGetJoystickPos`. -pub inline fn getAxes(self: Joystick) ?[]const f32 { - internal_debug.assertInitialized(); - var count: c_int = undefined; - const axes = c.glfwGetJoystickAxes(@intFromEnum(self.jid), &count); - if (axes == null) return null; - return axes[0..@as(u32, @intCast(count))]; -} - -/// Returns the state of all buttons of the specified joystick. -/// -/// This function returns the state of all buttons of the specified joystick. Each element in the -/// array is either `glfw.Action.press` or `glfw.Action.release`. -/// -/// For backward compatibility with earlier versions that did not have glfw.Joystick.getHats, the -/// button array also includes all hats, each represented as four buttons. The hats are in the same -/// order as returned by glfw.Joystick.getHats and are in the order _up_, _right_, _down_ and -/// _left_. To disable these extra buttons, set the glfw.joystick_hat_buttons init hint before -/// initialization. -/// -/// If the specified joystick is not present this function will return null but will not generate an -/// error. This can be used instead of first calling glfw.Joystick.present. -/// -/// @return An array of button states, or null if the joystick is not present. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// null is additionally returned in the event of an error. -/// -/// @pointer_lifetime The returned array is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick_button -pub inline fn getButtons(self: Joystick) ?[]const u8 { - internal_debug.assertInitialized(); - var count: c_int = undefined; - const buttons = c.glfwGetJoystickButtons(@intFromEnum(self.jid), &count); - if (buttons == null) return null; - return buttons[0..@as(u32, @intCast(count))]; -} - -/// Returns the state of all hats of the specified joystick. -/// -/// This function returns the state of all hats of the specified joystick. Each element in the array -/// is one of the following values: -/// -/// | Name | Value | -/// |---------------------------|---------------------------------------------| -/// | `glfw.RawHats.centered` | 0 | -/// | `glfw.RawHats.up` | 1 | -/// | `glfw.RawHats.right` | 2 | -/// | `glfw.RawHats.down` | 4 | -/// | `glfw.RawHats.left` | 8 | -/// | `glfw.RawHats.right_up` | `glfw.RawHats.right` \| `glfw.RawHats.up` | -/// | `glfw.RawHats.right_down` | `glfw.RawHats.right` \| `glfw.RawHats.down` | -/// | `glfw.RawHats.left_up` | `glfw.RawHats.left` \| `glfw.RawHats.up` | -/// | `glfw.RawHats.left_down` | `glfw.RawHats.left` \| `glfw.RawHats.down` | -/// -/// The diagonal directions are bitwise combinations of the primary (up, right, down and left) -/// directions, since the Zig GLFW wrapper returns a packed struct it is trivial to test for these: -/// -/// ``` -/// if (hats.up and hats.right) { -/// // up-right! -/// } -/// ``` -/// -/// If the specified joystick is not present this function will return null but will not generate an -/// error. This can be used instead of first calling glfw.Joystick.present. -/// -/// @return An array of hat states, or null if the joystick is not present. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// null is additionally returned in the event of an error. -/// -/// @pointer_lifetime The returned array is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected, this function is called -/// again for that joystick or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick_hat -pub inline fn getHats(self: Joystick) ?[]const Hat { - internal_debug.assertInitialized(); - var count: c_int = undefined; - const hats = c.glfwGetJoystickHats(@intFromEnum(self.jid), &count); - if (hats == null) return null; - const slice = hats[0..@as(u32, @intCast(count))]; - return @as(*const []const Hat, @ptrCast(&slice)).*; -} - -/// Returns the name of the specified joystick. -/// -/// This function returns the name, encoded as UTF-8, of the specified joystick. The returned string -/// is allocated and freed by GLFW. You should not free it yourself. -/// -/// If the specified joystick is not present this function will return null but will not generate an -/// error. This can be used instead of first calling glfw.Joystick.present. -/// -/// @return The UTF-8 encoded name of the joystick, or null if the joystick is not present or an -/// error occurred. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// null is additionally returned in the event of an error. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick_name -pub inline fn getName(self: Joystick) ?[:0]const u8 { - internal_debug.assertInitialized(); - const name_opt = c.glfwGetJoystickName(@intFromEnum(self.jid)); - return if (name_opt) |name| - std.mem.span(@as([*:0]const u8, @ptrCast(name))) - else - null; -} - -/// Returns the SDL compatible GUID of the specified joystick. -/// -/// This function returns the SDL compatible GUID, as a UTF-8 encoded hexadecimal string, of the -/// specified joystick. The returned string is allocated and freed by GLFW. You should not free it -/// yourself. -/// -/// The GUID is what connects a joystick to a gamepad mapping. A connected joystick will always have -/// a GUID even if there is no gamepad mapping assigned to it. -/// -/// If the specified joystick is not present this function will return null but will not generate an -/// error. This can be used instead of first calling glfw.Joystick.present. -/// -/// The GUID uses the format introduced in SDL 2.0.5. This GUID tries to uniquely identify the make -/// and model of a joystick but does not identify a specific unit, e.g. all wired Xbox 360 -/// controllers will have the same GUID on that platform. The GUID for a unit may vary between -/// platforms depending on what hardware information the platform specific APIs provide. -/// -/// @return The UTF-8 encoded GUID of the joystick, or null if the joystick is not present. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// null is additionally returned in the event of an error. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: gamepad -pub inline fn getGUID(self: Joystick) ?[:0]const u8 { - internal_debug.assertInitialized(); - const guid_opt = c.glfwGetJoystickGUID(@intFromEnum(self.jid)); - return if (guid_opt) |guid| - std.mem.span(@as([*:0]const u8, @ptrCast(guid))) - else - null; -} - -/// Sets the user pointer of the specified joystick. -/// -/// This function sets the user-defined pointer of the specified joystick. The current value is -/// retained until the joystick is disconnected. The initial value is null. -/// -/// This function may be called from the joystick callback, even for a joystick that is being disconnected. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: joystick_userptr, glfw.Joystick.getUserPointer -pub inline fn setUserPointer(self: Joystick, comptime T: type, pointer: *T) void { - internal_debug.assertInitialized(); - c.glfwSetJoystickUserPointer(@intFromEnum(self.jid), @as(*anyopaque, @ptrCast(pointer))); -} - -/// Returns the user pointer of the specified joystick. -/// -/// This function returns the current value of the user-defined pointer of the specified joystick. -/// The initial value is null. -/// -/// This function may be called from the joystick callback, even for a joystick that is being -/// disconnected. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: joystick_userptr, glfw.Joystick.setUserPointer -pub inline fn getUserPointer(self: Joystick, comptime PointerType: type) ?PointerType { - internal_debug.assertInitialized(); - const ptr = c.glfwGetJoystickUserPointer(@intFromEnum(self.jid)); - if (ptr) |p| return @as(PointerType, @ptrCast(@alignCast(p))); - return null; -} - -/// Describes an event relating to a joystick. -pub const Event = enum(c_int) { - /// The device was connected. - connected = c.GLFW_CONNECTED, - - /// The device was disconnected. - disconnected = c.GLFW_DISCONNECTED, -}; - -/// Sets the joystick configuration callback. -/// -/// This function sets the joystick configuration callback, or removes the currently set callback. -/// This is called when a joystick is connected to or disconnected from the system. -/// -/// For joystick connection and disconnection events to be delivered on all platforms, you need to -/// call one of the event processing (see events) functions. Joystick disconnection may also be -/// detected and the callback called by joystick functions. The function will then return whatever -/// it returns if the joystick is not present. -/// -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param `jid` The joystick that was connected or disconnected. -/// @callback_param `event` One of `.connected` or `.disconnected`. Future releases may add -/// more events. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: joystick_event -pub inline fn setCallback(comptime callback: ?fn (joystick: Joystick, event: Event) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn joystickCallbackWrapper(jid: c_int, event: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - Joystick{ .jid = @as(Joystick.Id, @enumFromInt(jid)) }, - @as(Event, @enumFromInt(event)), - }); - } - }; - - if (c.glfwSetJoystickCallback(CWrapper.joystickCallbackWrapper) != null) return; - } else { - if (c.glfwSetJoystickCallback(null) != null) return; - } -} - -/// Adds the specified SDL_GameControllerDB gamepad mappings. -/// -/// This function parses the specified ASCII encoded string and updates the internal list with any -/// gamepad mappings it finds. This string may contain either a single gamepad mapping or many -/// mappings separated by newlines. The parser supports the full format of the `gamecontrollerdb.txt` -/// source file including empty lines and comments. -/// -/// See gamepad_mapping for a description of the format. -/// -/// If there is already a gamepad mapping for a given GUID in the internal list, it will be -/// replaced by the one passed to this function. If the library is terminated and re-initialized -/// the internal list will revert to the built-in default. -/// -/// @param[in] string The string containing the gamepad mappings. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue. -/// Returns a boolean indicating success. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: gamepad, glfw.Joystick.isGamepad, glfwGetGamepadName -/// -/// -/// @ingroup input -pub inline fn updateGamepadMappings(gamepad_mappings: [*:0]const u8) bool { - internal_debug.assertInitialized(); - return c.glfwUpdateGamepadMappings(gamepad_mappings) == c.GLFW_TRUE; -} - -/// Returns whether the specified joystick has a gamepad mapping. -/// -/// This function returns whether the specified joystick is both present and has a gamepad mapping. -/// -/// If the specified joystick is present but does not have a gamepad mapping this function will -/// return `false` but will not generate an error. Call glfw.Joystick.present to check if a -/// joystick is present regardless of whether it has a mapping. -/// -/// @return `true` if a joystick is both present and has a gamepad mapping, or `false` otherwise. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum. -/// Additionally returns false in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: gamepad, glfw.Joystick.getGamepadState -pub inline fn isGamepad(self: Joystick) bool { - internal_debug.assertInitialized(); - const is_gamepad = c.glfwJoystickIsGamepad(@intFromEnum(self.jid)); - return is_gamepad == c.GLFW_TRUE; -} - -/// Returns the human-readable gamepad name for the specified joystick. -/// -/// This function returns the human-readable name of the gamepad from the gamepad mapping assigned -/// to the specified joystick. -/// -/// If the specified joystick is not present or does not have a gamepad mapping this function will -/// return null, not an error. Call glfw.Joystick.present to check whether it is -/// present regardless of whether it has a mapping. -/// -/// @return The UTF-8 encoded name of the gamepad, or null if the joystick is not present or does -/// not have a mapping. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum. -/// Additionally returns null in the event of an error. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified joystick is disconnected, the gamepad mappings are -/// updated or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: gamepad, glfw.Joystick.isGamepad -pub inline fn getGamepadName(self: Joystick) ?[:0]const u8 { - internal_debug.assertInitialized(); - const name_opt = c.glfwGetGamepadName(@intFromEnum(self.jid)); - return if (name_opt) |name| - std.mem.span(@as([*:0]const u8, @ptrCast(name))) - else - null; -} - -/// Retrieves the state of the joystick remapped as a gamepad. -/// -/// This function retrieves the state of the joystick remapped to an Xbox-like gamepad. -/// -/// If the specified joystick is not present or does not have a gamepad mapping this function will -/// return `false`. Call glfw.joystickPresent to check whether it is present regardless of whether -/// it has a mapping. -/// -/// The Guide button may not be available for input as it is often hooked by the system or the -/// Steam client. -/// -/// Not all devices have all the buttons or axes provided by GamepadState. Unavailable buttons -/// and axes will always report `glfw.Action.release` and 0.0 respectively. -/// -/// @param[in] jid The joystick (see joysticks) to query. -/// @param[out] state The gamepad input state of the joystick. -/// @return the gamepad input state if successful, or null if no joystick is connected or it has no -/// gamepad mapping. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum. -/// Returns null in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: gamepad, glfw.UpdateGamepadMappings, glfw.Joystick.isGamepad -pub inline fn getGamepadState(self: Joystick) ?GamepadState { - internal_debug.assertInitialized(); - var state: GamepadState = undefined; - const success = c.glfwGetGamepadState(@intFromEnum(self.jid), @as(*c.GLFWgamepadstate, @ptrCast(&state))); - return if (success == c.GLFW_TRUE) state else null; -} - -test "present" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.present(); -} - -test "getAxes" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getAxes(); -} - -test "getButtons" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getButtons(); -} - -test "getHats" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - - if (joystick.getHats()) |hats| { - for (hats) |hat| { - if (hat.down and hat.up) { - // down-up! - } - } - } -} - -test "getName" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getName(); -} - -test "getGUID" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getGUID(); -} - -test "setUserPointer_syntax" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - - // Must be called from joystick callback, we cannot test it. - _ = joystick; - _ = setUserPointer; -} - -test "getUserPointer_syntax" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - - // Must be called from joystick callback, we cannot test it. - _ = joystick; - _ = getUserPointer; -} - -test "setCallback" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - glfw.Joystick.setCallback((struct { - pub fn callback(joystick: Joystick, event: Event) void { - _ = joystick; - _ = event; - } - }).callback); -} - -test "updateGamepadMappings_syntax" { - // We don't have a gamepad mapping to test with, just confirm the syntax is good. - _ = updateGamepadMappings; -} - -test "isGamepad" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.isGamepad(); -} - -test "getGamepadName" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getGamepadName(); -} - -test "getGamepadState" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const joystick = glfw.Joystick{ .jid = .one }; - _ = joystick.getGamepadState(); - _ = (std.mem.zeroes(GamepadState)).getAxis(.left_x); - _ = (std.mem.zeroes(GamepadState)).getButton(.dpad_up); -} diff --git a/pkg/glfw/LICENSE b/pkg/glfw/LICENSE deleted file mode 100644 index 8c422bd23..000000000 --- a/pkg/glfw/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2021 Hexops Contributors (given via the Git commit history). -Copyright (c) 2025 Mitchell Hashimoto, Ghostty contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/pkg/glfw/Monitor.zig b/pkg/glfw/Monitor.zig deleted file mode 100644 index 3b194965a..000000000 --- a/pkg/glfw/Monitor.zig +++ /dev/null @@ -1,599 +0,0 @@ -//! Monitor type and related functions - -const std = @import("std"); -const mem = std.mem; -const testing = std.testing; -const c = @import("c.zig").c; - -const GammaRamp = @import("GammaRamp.zig"); -const VideoMode = @import("VideoMode.zig"); - -const internal_debug = @import("internal_debug.zig"); - -const Monitor = @This(); - -handle: *c.GLFWmonitor, - -/// A monitor position, in screen coordinates, of the upper left corner of the monitor on the -/// virtual screen. -const Pos = struct { - /// The x coordinate. - x: u32, - /// The y coordinate. - y: u32, -}; - -/// Returns the position of the monitor's viewport on the virtual screen. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_properties -pub inline fn getPos(self: Monitor) Pos { - internal_debug.assertInitialized(); - var xpos: c_int = 0; - var ypos: c_int = 0; - c.glfwGetMonitorPos(self.handle, &xpos, &ypos); - return Pos{ .x = @as(u32, @intCast(xpos)), .y = @as(u32, @intCast(ypos)) }; -} - -/// The monitor workarea, in screen coordinates. -/// -/// This is the position of the upper-left corner of the work area of the monitor, along with the -/// work area size. The work area is defined as the area of the monitor not occluded by the -/// window system task bar where present. If no task bar exists then the work area is the -/// monitor resolution in screen coordinates. -const Workarea = struct { - x: u32, - y: u32, - width: u32, - height: u32, -}; - -/// Retrieves the work area of the monitor. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// A zero value is returned in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_workarea -pub inline fn getWorkarea(self: Monitor) Workarea { - internal_debug.assertInitialized(); - var xpos: c_int = 0; - var ypos: c_int = 0; - var width: c_int = 0; - var height: c_int = 0; - c.glfwGetMonitorWorkarea(self.handle, &xpos, &ypos, &width, &height); - return Workarea{ .x = @as(u32, @intCast(xpos)), .y = @as(u32, @intCast(ypos)), .width = @as(u32, @intCast(width)), .height = @as(u32, @intCast(height)) }; -} - -/// The physical size, in millimetres, of the display area of a monitor. -const PhysicalSize = struct { - width_mm: u32, - height_mm: u32, -}; - -/// Returns the physical size of the monitor. -/// -/// Some platforms do not provide accurate monitor size information, either because the monitor -/// [EDID](https://en.wikipedia.org/wiki/Extended_display_identification_data) -/// data is incorrect or because the driver does not report it accurately. -/// -/// win32: On Windows 8 and earlier the physical size is calculated from -/// the current resolution and system DPI instead of querying the monitor EDID data -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_properties -pub inline fn getPhysicalSize(self: Monitor) PhysicalSize { - internal_debug.assertInitialized(); - var width_mm: c_int = 0; - var height_mm: c_int = 0; - c.glfwGetMonitorPhysicalSize(self.handle, &width_mm, &height_mm); - return PhysicalSize{ .width_mm = @as(u32, @intCast(width_mm)), .height_mm = @as(u32, @intCast(height_mm)) }; -} - -/// The content scale for a monitor. -/// -/// This is the ratio between the current DPI and the platform's default DPI. This is especially -/// important for text and any UI elements. If the pixel dimensions of your UI scaled by this look -/// appropriate on your machine then it should appear at a reasonable size on other machines -/// regardless of their DPI and scaling settings. This relies on the system DPI and scaling -/// settings being somewhat correct. -/// -/// The content scale may depend on both the monitor resolution and pixel density and on users -/// settings. It may be very different from the raw DPI calculated from the physical size and -/// current resolution. -const ContentScale = struct { - x_scale: f32, - y_scale: f32, -}; - -/// Returns the content scale for the monitor. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// A zero value is returned in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_scale, glfw.Window.getContentScale -pub inline fn getContentScale(self: Monitor) ContentScale { - internal_debug.assertInitialized(); - var x_scale: f32 = 0; - var y_scale: f32 = 0; - c.glfwGetMonitorContentScale(self.handle, &x_scale, &y_scale); - return ContentScale{ .x_scale = @as(f32, @floatCast(x_scale)), .y_scale = @as(f32, @floatCast(y_scale)) }; -} - -/// Returns the name of the specified monitor. -/// -/// This function returns a human-readable name, encoded as UTF-8, of the specified monitor. The -/// name typically reflects the make and model of the monitor and is not guaranteed to be unique -/// among the connected monitors. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the specified monitor is disconnected or the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_properties -pub inline fn getName(self: Monitor) [*:0]const u8 { - internal_debug.assertInitialized(); - if (c.glfwGetMonitorName(self.handle)) |name| return @as([*:0]const u8, @ptrCast(name)); - // `glfwGetMonitorName` returns `null` only for errors, but the only error is unreachable - // (NotInitialized) - unreachable; -} - -/// Sets the user pointer of the specified monitor. -/// -/// This function sets the user-defined pointer of the specified monitor. The current value is -/// retained until the monitor is disconnected. -/// -/// This function may be called from the monitor callback, even for a monitor that is being -/// disconnected. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: monitor_userptr, glfw.Monitor.getUserPointer -pub inline fn setUserPointer(self: Monitor, comptime T: type, ptr: *T) void { - internal_debug.assertInitialized(); - c.glfwSetMonitorUserPointer(self.handle, ptr); -} - -/// Returns the user pointer of the specified monitor. -/// -/// This function returns the current value of the user-defined pointer of the specified monitor. -/// -/// This function may be called from the monitor callback, even for a monitor that is being -/// disconnected. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: monitor_userptr, glfw.Monitor.setUserPointer -pub inline fn getUserPointer(self: Monitor, comptime T: type) ?*T { - internal_debug.assertInitialized(); - const ptr = c.glfwGetMonitorUserPointer(self.handle); - if (ptr == null) return null; - return @as(*T, @ptrCast(@alignCast(ptr.?))); -} - -/// Returns the available video modes for the specified monitor. -/// -/// This function returns an array of all video modes supported by the monitor. The returned slice -/// is sorted in ascending order, first by color bit depth (the sum of all channel depths) and -/// then by resolution area (the product of width and height), then resolution width and finally -/// by refresh rate. -/// -/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable. -/// Returns null in the event of an error. -/// -/// The returned slice memory is owned by the caller. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_modes, glfw.Monitor.getVideoMode -/// -/// wayland: Gamma handling is privileged protocol, this function will thus never be implemented and -/// emits glfw.ErrorCode.FeatureUnavailable -/// -/// TODO(glfw): rewrite this to not require any allocation. -pub inline fn getVideoModes(self: Monitor, allocator: mem.Allocator) mem.Allocator.Error!?[]VideoMode { - internal_debug.assertInitialized(); - var count: c_int = 0; - if (c.glfwGetVideoModes(self.handle, &count)) |modes| { - const slice = try allocator.alloc(VideoMode, @as(u32, @intCast(count))); - var i: u32 = 0; - while (i < count) : (i += 1) { - slice[i] = VideoMode{ .handle = @as([*c]const c.GLFWvidmode, @ptrCast(modes))[i] }; - } - return slice; - } - return null; -} - -/// Returns the current mode of the specified monitor. -/// -/// This function returns the current video mode of the specified monitor. If you have created a -/// full screen window for that monitor, the return value will depend on whether that window is -/// iconified. -/// -/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable. -/// Additionally returns null in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// wayland: Gamma handling is a privileged protocol, this function will thus never be implemented -/// and will thus never be implemented and emits glfw.ErrorCode.FeatureUnavailable -/// -/// see also: monitor_modes, glfw.Monitor.getVideoModes -pub inline fn getVideoMode(self: Monitor) ?VideoMode { - internal_debug.assertInitialized(); - if (c.glfwGetVideoMode(self.handle)) |mode| return VideoMode{ .handle = mode.* }; - return null; -} - -/// Generates a gamma ramp and sets it for the specified monitor. -/// -/// This function generates an appropriately sized gamma ramp from the specified exponent and then -/// calls glfw.Monitor.setGammaRamp with it. The value must be a finite number greater than zero. -/// -/// The software controlled gamma ramp is applied _in addition_ to the hardware gamma correction, -/// which today is usually an approximation of sRGB gamma. This means that setting a perfectly -/// linear ramp, or gamma 1.0, will produce the default (usually sRGB-like) behavior. -/// -/// For gamma correct rendering with OpenGL or OpenGL ES, see the glfw.srgb_capable hint. -/// -/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable. -/// -/// wayland: Gamma handling is privileged protocol, this function will thus never be implemented and -/// emits glfw.ErrorCode.FeatureUnavailable -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_gamma -pub inline fn setGamma(self: Monitor, gamma: f32) void { - internal_debug.assertInitialized(); - - std.debug.assert(!std.math.isNan(gamma)); - std.debug.assert(gamma >= 0); - std.debug.assert(gamma <= std.math.f32_max); - - c.glfwSetGamma(self.handle, gamma); -} - -/// Returns the current gamma ramp for the specified monitor. -/// -/// This function returns the current gamma ramp of the specified monitor. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns null in the event of an error. -/// -/// wayland: Gamma handling is a privileged protocol, this function will thus never be implemented -/// and returns glfw.ErrorCode.FeatureUnavailable. -/// -/// The returned gamma ramp is `.owned = true` by GLFW, and is valid until the monitor is -/// disconnected, this function is called again, or `glfw.terminate()` is called. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_gamma -pub inline fn getGammaRamp(self: Monitor) ?GammaRamp { - internal_debug.assertInitialized(); - if (c.glfwGetGammaRamp(self.handle)) |ramp| return .fromC(ramp.*); - return null; -} - -/// Sets the current gamma ramp for the specified monitor. -/// -/// This function sets the current gamma ramp for the specified monitor. The original gamma ramp -/// for that monitor is saved by GLFW the first time this function is called and is restored by -/// `glfw.terminate()`. -/// -/// The software controlled gamma ramp is applied _in addition_ to the hardware gamma correction, -/// which today is usually an approximation of sRGB gamma. This means that setting a perfectly -/// linear ramp, or gamma 1.0, will produce the default (usually sRGB-like) behavior. -/// -/// For gamma correct rendering with OpenGL or OpenGL ES, see the glfw.srgb_capable hint. -/// -/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable. -/// -/// The size of the specified gamma ramp should match the size of the current ramp for that -/// monitor. On win32, the gamma ramp size must be 256. -/// -/// wayland: Gamma handling is a privileged protocol, this function will thus never be implemented -/// and returns glfw.ErrorCode.FeatureUnavailable. -/// -/// @pointer_lifetime The specified gamma ramp is copied before this function returns. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_gamma -pub inline fn setGammaRamp(self: Monitor, ramp: GammaRamp) void { - internal_debug.assertInitialized(); - c.glfwSetGammaRamp(self.handle, &ramp.toC()); -} - -/// Returns the currently connected monitors. -/// -/// This function returns a slice of all currently connected monitors. The primary monitor is -/// always first. If no monitors were found, this function returns an empty slice. -/// -/// The returned slice memory is owned by the caller. The underlying handles are owned by GLFW, and -/// are valid until the monitor configuration changes or `glfw.terminate` is called. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_monitors, monitor_event, glfw.monitor.getPrimary -pub inline fn getAll(allocator: mem.Allocator) mem.Allocator.Error![]Monitor { - internal_debug.assertInitialized(); - var count: c_int = 0; - if (c.glfwGetMonitors(&count)) |monitors| { - const slice = try allocator.alloc(Monitor, @as(u32, @intCast(count))); - var i: u32 = 0; - while (i < count) : (i += 1) { - slice[i] = Monitor{ .handle = @as([*c]const ?*c.GLFWmonitor, @ptrCast(monitors))[i].? }; - } - return slice; - } - // `glfwGetMonitors` returning null can be either an error or no monitors, but the only error is - // unreachable (NotInitialized) - return &[_]Monitor{}; -} - -/// Returns the primary monitor. -/// -/// This function returns the primary monitor. This is usually the monitor where elements like -/// the task bar or global menu bar are located. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_monitors, glfw.monitors.getAll -pub inline fn getPrimary() ?Monitor { - internal_debug.assertInitialized(); - if (c.glfwGetPrimaryMonitor()) |handle| return Monitor{ .handle = handle }; - return null; -} - -/// Describes an event relating to a monitor. -pub const Event = enum(c_int) { - /// The device was connected. - connected = c.GLFW_CONNECTED, - - /// The device was disconnected. - disconnected = c.GLFW_DISCONNECTED, -}; - -/// Sets the monitor configuration callback. -/// -/// This function sets the monitor configuration callback, or removes the currently set callback. -/// This is called when a monitor is connected to or disconnected from the system. Example: -/// -/// ``` -/// fn monitorCallback(monitor: glfw.Monitor, event: glfw.Monitor.Event, data: *MyData) void { -/// // data is the pointer you passed into setCallback. -/// // event is one of .connected or .disconnected -/// } -/// ... -/// glfw.Monitor.setCallback(MyData, &myData, monitorCallback) -/// ``` -/// -/// `event` may be one of .connected or .disconnected. More events may be added in the future. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: monitor_event -pub inline fn setCallback(comptime callback: ?fn (monitor: Monitor, event: Event) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn monitorCallbackWrapper(monitor: ?*c.GLFWmonitor, event: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - Monitor{ .handle = monitor.? }, - @as(Event, @enumFromInt(event)), - }); - } - }; - - if (c.glfwSetMonitorCallback(CWrapper.monitorCallbackWrapper) != null) return; - } else { - if (c.glfwSetMonitorCallback(null) != null) return; - } -} - -test "getAll" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const allocator = testing.allocator; - const monitors = try getAll(allocator); - defer allocator.free(monitors); -} - -test "getPrimary" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = getPrimary(); -} - -test "getPos" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getPos(); - } -} - -test "getWorkarea" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getWorkarea(); - } -} - -test "getPhysicalSize" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getPhysicalSize(); - } -} - -test "getContentScale" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getContentScale(); - } -} - -test "getName" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getName(); - } -} - -test "userPointer" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - var p = m.getUserPointer(u32); - try testing.expect(p == null); - var x: u32 = 5; - m.setUserPointer(u32, &x); - p = m.getUserPointer(u32); - try testing.expectEqual(p.?.*, 5); - } -} - -test "setCallback" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - setCallback(struct { - fn callback(monitor: Monitor, event: Event) void { - _ = monitor; - _ = event; - } - }.callback); -} - -test "getVideoModes" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - const allocator = testing.allocator; - const modes_maybe = try m.getVideoModes(allocator); - if (modes_maybe) |modes| { - defer allocator.free(modes); - } - } -} - -test "getVideoMode" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - _ = m.getVideoMode(); - } -} - -test "set_getGammaRamp" { - const allocator = testing.allocator; - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const monitor = getPrimary(); - if (monitor) |m| { - if (m.getGammaRamp()) |ramp| { - // Set it to the exact same value; if we do otherwise an our tests fail it wouldn't call - // terminate and our made-up gamma ramp would get stuck. - m.setGammaRamp(ramp); - - // technically not needed here / noop because GLFW owns this gamma ramp. - defer ramp.deinit(allocator); - } - } -} diff --git a/pkg/glfw/VideoMode.zig b/pkg/glfw/VideoMode.zig deleted file mode 100644 index f433b8d05..000000000 --- a/pkg/glfw/VideoMode.zig +++ /dev/null @@ -1,50 +0,0 @@ -//! Monitor video modes and related functions -//! -//! see also: glfw.Monitor.getVideoMode - -const std = @import("std"); -const c = @import("c.zig").c; - -const VideoMode = @This(); - -handle: c.GLFWvidmode, - -/// Returns the width of the video mode, in screen coordinates. -pub inline fn getWidth(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.width)); -} - -/// Returns the height of the video mode, in screen coordinates. -pub inline fn getHeight(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.height)); -} - -/// Returns the bit depth of the red channel of the video mode. -pub inline fn getRedBits(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.redBits)); -} - -/// Returns the bit depth of the green channel of the video mode. -pub inline fn getGreenBits(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.greenBits)); -} - -/// Returns the bit depth of the blue channel of the video mode. -pub inline fn getBlueBits(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.blueBits)); -} - -/// Returns the refresh rate of the video mode, in Hz. -pub inline fn getRefreshRate(self: VideoMode) u32 { - return @as(u32, @intCast(self.handle.refreshRate)); -} - -test "getters" { - const x = std.mem.zeroes(VideoMode); - _ = x.getWidth(); - _ = x.getHeight(); - _ = x.getRedBits(); - _ = x.getGreenBits(); - _ = x.getBlueBits(); - _ = x.getRefreshRate(); -} diff --git a/pkg/glfw/Window.zig b/pkg/glfw/Window.zig deleted file mode 100644 index 804184f0e..000000000 --- a/pkg/glfw/Window.zig +++ /dev/null @@ -1,3551 +0,0 @@ -//! Window type and related functions - -const std = @import("std"); -const testing = std.testing; -const mem = std.mem; -const c = @import("c.zig").c; - -const glfw = @import("main.zig"); -const Image = @import("Image.zig"); -const Monitor = @import("Monitor.zig"); -const Cursor = @import("Cursor.zig"); -const Key = @import("key.zig").Key; -const Action = @import("action.zig").Action; -const Mods = @import("mod.zig").Mods; -const MouseButton = @import("mouse_button.zig").MouseButton; - -const internal_debug = @import("internal_debug.zig"); - -const Window = @This(); - -handle: *c.GLFWwindow, - -/// Returns a Zig GLFW window from an underlying C GLFW window handle. -pub inline fn from(handle: *anyopaque) Window { - return Window{ .handle = @as(*c.GLFWwindow, @ptrCast(@alignCast(handle))) }; -} - -/// Resets all window hints to their default values. -/// -/// This function resets all window hints to their default values. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_hints, glfw.Window.hint, glfw.Window.hintString -pub inline fn defaultHints() void { - internal_debug.assertInitialized(); - c.glfwDefaultWindowHints(); -} - -/// Window hints -const Hint = enum(c_int) { - resizable = c.GLFW_RESIZABLE, - visible = c.GLFW_VISIBLE, - decorated = c.GLFW_DECORATED, - focused = c.GLFW_FOCUSED, - auto_iconify = c.GLFW_AUTO_ICONIFY, - floating = c.GLFW_FLOATING, - maximized = c.GLFW_MAXIMIZED, - center_cursor = c.GLFW_CENTER_CURSOR, - transparent_framebuffer = c.GLFW_TRANSPARENT_FRAMEBUFFER, - focus_on_show = c.GLFW_FOCUS_ON_SHOW, - mouse_passthrough = c.GLFW_MOUSE_PASSTHROUGH, - position_x = c.GLFW_POSITION_X, - position_y = c.GLFW_POSITION_Y, - scale_to_monitor = c.GLFW_SCALE_TO_MONITOR, - - /// Framebuffer hints - red_bits = c.GLFW_RED_BITS, - green_bits = c.GLFW_GREEN_BITS, - blue_bits = c.GLFW_BLUE_BITS, - alpha_bits = c.GLFW_ALPHA_BITS, - depth_bits = c.GLFW_DEPTH_BITS, - stencil_bits = c.GLFW_STENCIL_BITS, - accum_red_bits = c.GLFW_ACCUM_RED_BITS, - accum_green_bits = c.GLFW_ACCUM_GREEN_BITS, - accum_blue_bits = c.GLFW_ACCUM_BLUE_BITS, - accum_alpha_bits = c.GLFW_ACCUM_ALPHA_BITS, - aux_buffers = c.GLFW_AUX_BUFFERS, - - /// Framebuffer MSAA samples - samples = c.GLFW_SAMPLES, - - /// Monitor refresh rate - refresh_rate = c.GLFW_REFRESH_RATE, - - /// OpenGL stereoscopic rendering - stereo = c.GLFW_STEREO, - - /// Framebuffer sRGB - srgb_capable = c.GLFW_SRGB_CAPABLE, - - /// Framebuffer double buffering - doublebuffer = c.GLFW_DOUBLEBUFFER, - - client_api = c.GLFW_CLIENT_API, - context_creation_api = c.GLFW_CONTEXT_CREATION_API, - - context_version_major = c.GLFW_CONTEXT_VERSION_MAJOR, - context_version_minor = c.GLFW_CONTEXT_VERSION_MINOR, - - context_robustness = c.GLFW_CONTEXT_ROBUSTNESS, - context_release_behavior = c.GLFW_CONTEXT_RELEASE_BEHAVIOR, - context_no_error = c.GLFW_CONTEXT_NO_ERROR, - // NOTE: This supersedes opengl_debug_context / GLFW_OPENGL_DEBUG_CONTEXT - context_debug = c.GLFW_CONTEXT_DEBUG, - - opengl_forward_compat = c.GLFW_OPENGL_FORWARD_COMPAT, - opengl_profile = c.GLFW_OPENGL_PROFILE, - - /// macOS specific - cocoa_retina_framebuffer = c.GLFW_COCOA_RETINA_FRAMEBUFFER, - - /// macOS specific - cocoa_frame_name = c.GLFW_COCOA_FRAME_NAME, - - /// macOS specific - cocoa_graphics_switching = c.GLFW_COCOA_GRAPHICS_SWITCHING, - - /// X11 specific - x11_class_name = c.GLFW_X11_CLASS_NAME, - - /// X11 specific - x11_instance_name = c.GLFW_X11_INSTANCE_NAME, - - /// Windows specific - win32_keyboard_menu = c.GLFW_WIN32_KEYBOARD_MENU, - - /// Allows specification of the Wayland app_id. - wayland_app_id = c.GLFW_WAYLAND_APP_ID, -}; - -/// Window hints -pub const Hints = struct { - // Note: The defaults here are directly from the GLFW source of the glfwDefaultWindowHints function - resizable: bool = true, - visible: bool = true, - decorated: bool = true, - focused: bool = true, - auto_iconify: bool = true, - floating: bool = false, - maximized: bool = false, - center_cursor: bool = true, - transparent_framebuffer: bool = false, - focus_on_show: bool = true, - mouse_passthrough: bool = false, - position_x: c_int = @intFromEnum(Position.any), - position_y: c_int = @intFromEnum(Position.any), - - scale_to_monitor: bool = false, - - /// Framebuffer hints - red_bits: ?PositiveCInt = 8, - green_bits: ?PositiveCInt = 8, - blue_bits: ?PositiveCInt = 8, - alpha_bits: ?PositiveCInt = 8, - depth_bits: ?PositiveCInt = 24, - stencil_bits: ?PositiveCInt = 8, - accum_red_bits: ?PositiveCInt = 0, - accum_green_bits: ?PositiveCInt = 0, - accum_blue_bits: ?PositiveCInt = 0, - accum_alpha_bits: ?PositiveCInt = 0, - aux_buffers: ?PositiveCInt = 0, - - /// Framebuffer MSAA samples - samples: ?PositiveCInt = 0, - - /// Monitor refresh rate - refresh_rate: ?PositiveCInt = null, - - /// OpenGL stereoscopic rendering - stereo: bool = false, - - /// Framebuffer sRGB - srgb_capable: bool = false, - - /// Framebuffer double buffering - doublebuffer: bool = true, - - client_api: ClientAPI = .opengl_api, - context_creation_api: ContextCreationAPI = .native_context_api, - - context_version_major: c_int = 1, - context_version_minor: c_int = 0, - - context_robustness: ContextRobustness = .no_robustness, - context_release_behavior: ContextReleaseBehavior = .any_release_behavior, - - /// Note: disables the context creating errors, - /// instead turning them into undefined behavior. - context_no_error: bool = false, - context_debug: bool = false, - - opengl_forward_compat: bool = false, - - opengl_profile: OpenGLProfile = .opengl_any_profile, - - /// macOS specific - cocoa_retina_framebuffer: bool = true, - - /// macOS specific - cocoa_frame_name: [:0]const u8 = "", - - /// macOS specific - cocoa_graphics_switching: bool = false, - - /// X11 specific - x11_class_name: [:0]const u8 = "", - - /// X11 specific - x11_instance_name: [:0]const u8 = "", - - /// Windows specific - win32_keyboard_menu: bool = false, - - /// Allows specification of the Wayland app_id. - wayland_app_id: [:0]const u8 = "", - - pub const PositiveCInt = std.math.IntFittingRange(0, std.math.maxInt(c_int)); - - pub const ClientAPI = enum(c_int) { - opengl_api = c.GLFW_OPENGL_API, - opengl_es_api = c.GLFW_OPENGL_ES_API, - no_api = c.GLFW_NO_API, - }; - - pub const ContextCreationAPI = enum(c_int) { - native_context_api = c.GLFW_NATIVE_CONTEXT_API, - egl_context_api = c.GLFW_EGL_CONTEXT_API, - osmesa_context_api = c.GLFW_OSMESA_CONTEXT_API, - }; - - pub const ContextRobustness = enum(c_int) { - no_robustness = c.GLFW_NO_ROBUSTNESS, - no_reset_notification = c.GLFW_NO_RESET_NOTIFICATION, - lose_context_on_reset = c.GLFW_LOSE_CONTEXT_ON_RESET, - }; - - pub const ContextReleaseBehavior = enum(c_int) { - any_release_behavior = c.GLFW_ANY_RELEASE_BEHAVIOR, - release_behavior_flush = c.GLFW_RELEASE_BEHAVIOR_FLUSH, - release_behavior_none = c.GLFW_RELEASE_BEHAVIOR_NONE, - }; - - pub const OpenGLProfile = enum(c_int) { - opengl_any_profile = c.GLFW_OPENGL_ANY_PROFILE, - opengl_compat_profile = c.GLFW_OPENGL_COMPAT_PROFILE, - opengl_core_profile = c.GLFW_OPENGL_CORE_PROFILE, - }; - - pub const Position = enum(c_int) { - /// By default, newly created windows use the placement recommended by the window system, - /// - /// To create the window at a specific position, make it initially invisible using the - /// Window.Hint.visible hint, set its Window.Hint.position and then Window.hide() it. - /// - /// To create the window at a specific position, set the Window.Hint.position_x and - /// Window.Hint.position_y hints before creation. To restore the default behavior, set - /// either or both hints back to Window.Hints.Position.any - any = @bitCast(c.GLFW_ANY_POSITION), - }; - - fn set(hints: Hints) void { - internal_debug.assertInitialized(); - inline for (comptime std.meta.fieldNames(Hint)) |field_name| { - const hint_tag = @intFromEnum(@field(Hint, field_name)); - const hint_value = @field(hints, field_name); - switch (@TypeOf(hint_value)) { - bool => c.glfwWindowHint(hint_tag, @intFromBool(hint_value)), - ?PositiveCInt => c.glfwWindowHint(hint_tag, if (hint_value) |unwrapped| unwrapped else glfw.dont_care), - c_int => c.glfwWindowHint(hint_tag, hint_value), - - ClientAPI, - ContextCreationAPI, - ContextRobustness, - ContextReleaseBehavior, - OpenGLProfile, - Position, - => c.glfwWindowHint(hint_tag, @intFromEnum(hint_value)), - - [:0]const u8 => c.glfwWindowHintString(hint_tag, hint_value.ptr), - - else => unreachable, - } - } - } -}; - -/// Creates a window and its associated context. -/// -/// This function creates a window and its associated OpenGL or OpenGL ES context. Most of the -/// options controlling how the window and its context should be created are specified with window -/// hints using `glfw.Window.hint`. -/// -/// Successful creation does not change which context is current. Before you can use the newly -/// created context, you need to make it current using `glfw.makeContextCurrent`. For -/// information about the `share` parameter, see context_sharing. -/// -/// The created window, framebuffer and context may differ from what you requested, as not all -/// parameters and hints are hard constraints. This includes the size of the window, especially for -/// full screen windows. To query the actual attributes of the created window, framebuffer and -/// context, see glfw.Window.getAttrib, glfw.Window.getSize and glfw.window.getFramebufferSize. -/// -/// To create a full screen window, you need to specify the monitor the window will cover. If no -/// monitor is specified, the window will be windowed mode. Unless you have a way for the user to -/// choose a specific monitor, it is recommended that you pick the primary monitor. For more -/// information on how to query connected monitors, see @ref monitor_monitors. -/// -/// For full screen windows, the specified size becomes the resolution of the window's _desired -/// video mode_. As long as a full screen window is not iconified, the supported video mode most -/// closely matching the desired video mode is set for the specified monitor. For more information -/// about full screen windows, including the creation of so called _windowed full screen_ or -/// _borderless full screen_ windows, see window_windowed_full_screen. -/// -/// Once you have created the window, you can switch it between windowed and full screen mode with -/// glfw.Window.setMonitor. This will not affect its OpenGL or OpenGL ES context. -/// -/// By default, newly created windows use the placement recommended by the window system. To create -/// the window at a specific position, make it initially invisible using the `visible` window -/// hint, set its position and then show it. -/// -/// As long as at least one full screen window is not iconified, the screensaver is prohibited from -/// starting. -/// -/// Window systems put limits on window sizes. Very large or very small window dimensions may be -/// overridden by the window system on creation. Check the actual size after creation. -/// -/// The swap interval is not set during window creation and the initial value may vary depending on -/// driver settings and defaults. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum, glfw.ErrorCode.InvalidValue, -/// glfw.ErrorCode.APIUnavailable, glfw.ErrorCode.VersionUnavailable, glfw.ErrorCode.FormatUnavailable and -/// glfw.ErrorCode.PlatformError. -/// Returns null in the event of an error. -/// -/// Parameters are as follows: -/// -/// * `width` The desired width, in screen coordinates, of the window. -/// * `height` The desired height, in screen coordinates, of the window. -/// * `title` The initial, UTF-8 encoded window title. -/// * `monitor` The monitor to use for full screen mode, or `null` for windowed mode. -/// * `share` The window whose context to share resources with, or `null` to not share resources. -/// -/// win32: Window creation will fail if the Microsoft GDI software OpenGL implementation is the -/// only one available. -/// -/// win32: If the executable has an icon resource named `GLFW_ICON`, it will be set as the initial -/// icon for the window. If no such icon is present, the `IDI_APPLICATION` icon will be used -/// instead. To set a different icon, see glfw.Window.setIcon. -/// -/// win32: The context to share resources with must not be current on any other thread. -/// -/// macos: The OS only supports forward-compatible core profile contexts for OpenGL versions 3.2 -/// and later. Before creating an OpenGL context of version 3.2 or later you must set the -/// `glfw.opengl_forward_compat` and `glfw.opengl_profile` hints accordingly. OpenGL 3.0 and 3.1 -/// contexts are not supported at all on macOS. -/// -/// macos: The OS only supports core profile contexts for OpenGL versions 3.2 and later. Before -/// creating an OpenGL context of version 3.2 or later you must set the `glfw.opengl_profile` hint -/// accordingly. OpenGL 3.0 and 3.1 contexts are not supported at all on macOS. -/// -/// macos: The GLFW window has no icon, as it is not a document window, but the dock icon will be -/// the same as the application bundle's icon. For more information on bundles, see the -/// [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) -/// in the Mac Developer Library. -/// -/// macos: On OS X 10.10 and later the window frame will not be rendered at full resolution on -/// Retina displays unless the glfw.cocoa_retina_framebuffer hint is true (1) and the `NSHighResolutionCapable` -/// key is enabled in the application bundle's `Info.plist`. For more information, see -/// [High Resolution Guidelines for OS X](https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Explained/Explained.html) -/// in the Mac Developer Library. The GLFW test and example programs use a custom `Info.plist` -/// template for this, which can be found as `CMake/Info.plist.in` in the source tree. -/// -/// macos: When activating frame autosaving with glfw.cocoa_frame_name, the specified window size -/// and position may be overridden by previously saved values. -/// -/// x11: Some window managers will not respect the placement of initially hidden windows. -/// -/// x11: Due to the asynchronous nature of X11, it may take a moment for a window to reach its -/// requested state. This means you may not be able to query the final size, position or other -/// attributes directly after window creation. -/// -/// x11: The class part of the `WM_CLASS` window property will by default be set to the window title -/// passed to this function. The instance part will use the contents of the `RESOURCE_NAME` -/// environment variable, if present and not empty, or fall back to the window title. Set the glfw.x11_class_name -/// and glfw.x11_instance_name window hints to override this. -/// -/// wayland: Compositors should implement the xdg-decoration protocol for GLFW to decorate the -/// window properly. If this protocol isn't supported, or if the compositor prefers client-side -/// decorations, a very simple fallback frame will be drawn using the wp_viewporter protocol. A -/// compositor can still emit close, maximize or fullscreen events, using for instance a keybind -/// mechanism. If neither of these protocols is supported, the window won't be decorated. -/// -/// wayland: A full screen window will not attempt to change the mode, no matter what the -/// requested size or refresh rate. -/// -/// wayland: Screensaver inhibition requires the idle-inhibit protocol to be implemented in the -/// user's compositor. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_creation, glfw.Window.destroy -pub inline fn create( - width: u32, - height: u32, - title: [*:0]const u8, - monitor: ?Monitor, - share: ?Window, - hints: Hints, -) ?Window { - internal_debug.assertInitialized(); - const ignore_hints_struct = if (comptime @import("builtin").is_test) testing_ignore_window_hints_struct else false; - if (!ignore_hints_struct) hints.set(); - - if (c.glfwCreateWindow( - @as(c_int, @intCast(width)), - @as(c_int, @intCast(height)), - &title[0], - if (monitor) |m| m.handle else null, - if (share) |w| w.handle else null, - )) |handle| return from(handle); - return null; -} - -var testing_ignore_window_hints_struct = if (@import("builtin").is_test) false else @as(void, {}); - -/// Destroys the specified window and its context. -/// -/// This function destroys the specified window and its context. On calling this function, no -/// further callbacks will be called for that window. -/// -/// If the context of the specified window is current on the main thread, it is detached before -/// being destroyed. -/// -/// note: The context of the specified window must not be current on any other thread when this -/// function is called. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @reentrancy This function must not be called from a callback. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_creation, glfw.Window.create -pub inline fn destroy(self: Window) void { - internal_debug.assertInitialized(); - c.glfwDestroyWindow(self.handle); -} - -/// Checks the close flag of the specified window. -/// -/// This function returns the value of the close flag of the specified window. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: window_close -pub inline fn shouldClose(self: Window) bool { - internal_debug.assertInitialized(); - return c.glfwWindowShouldClose(self.handle) == c.GLFW_TRUE; -} - -/// Sets the close flag of the specified window. -/// -/// This function sets the value of the close flag of the specified window. This can be used to -/// override the user's attempt to close the window, or to signal that it should be closed. -/// -/// @thread_safety This function may be called from any thread. Access is not -/// synchronized. -/// -/// see also: window_close -pub inline fn setShouldClose(self: Window, value: bool) void { - internal_debug.assertInitialized(); - const boolean = if (value) c.GLFW_TRUE else c.GLFW_FALSE; - c.glfwSetWindowShouldClose(self.handle, boolean); -} - -/// Sets the UTF-8 encoded title of the specified window. -/// -/// This function sets the window title, encoded as UTF-8, of the specified window. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// macos: The window title will not be updated until the next time you process events. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_title -pub inline fn setTitle(self: Window, title: [*:0]const u8) void { - internal_debug.assertInitialized(); - c.glfwSetWindowTitle(self.handle, title); -} - -/// Sets the icon for the specified window. -/// -/// This function sets the icon of the specified window. If passed an array of candidate images, -/// those of or closest to the sizes desired by the system are selected. If no images are -/// specified, the window reverts to its default icon. -/// -/// The pixels are 32-bit, little-endian, non-premultiplied RGBA, i.e. eight bits per channel with -/// the red channel first. They are arranged canonically as packed sequential rows, starting from -/// the top-left corner. -/// -/// The desired image sizes varies depending on platform and system settings. The selected images -/// will be rescaled as needed. Good sizes include 16x16, 32x32 and 48x48. -/// -/// @pointer_lifetime The specified image data is copied before this function returns. -/// -/// macos: Regular windows do not have icons on macOS. This function will emit FeatureUnavailable. -/// The dock icon will be the same as the application bundle's icon. For more information on -/// bundles, see the [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) -/// in the Mac Developer Library. -/// -/// wayland: There is no existing protocol to change an icon, the window will thus inherit the one -/// defined in the application's desktop file. This function will emit glfw.ErrorCode.FeatureUnavailable. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue, glfw.ErrorCode.FeatureUnavailable -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_icon -pub inline fn setIcon(self: Window, allocator: mem.Allocator, images: ?[]const Image) mem.Allocator.Error!void { - internal_debug.assertInitialized(); - if (images) |im| { - const tmp = try allocator.alloc(c.GLFWimage, im.len); - defer allocator.free(tmp); - for (im, 0..) |img, index| tmp[index] = img.toC(); - c.glfwSetWindowIcon(self.handle, @as(c_int, @intCast(im.len)), &tmp[0]); - } else c.glfwSetWindowIcon(self.handle, 0, null); -} - -pub const Pos = struct { - x: i64, - y: i64, -}; - -/// Retrieves the position of the content area of the specified window. -/// -/// This function retrieves the position, in screen coordinates, of the upper-left corner of the -/// content area of the specified window. -/// -/// Possible errors include glfw.ErrorCode.FeatureUnavailable. -/// Additionally returns a zero value in the event of an error. -/// -/// wayland: There is no way for an application to retrieve the global position of its windows, -/// this function will always emit glfw.ErrorCode.FeatureUnavailable. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_pos glfw.Window.setPos -pub inline fn getPos(self: Window) Pos { - internal_debug.assertInitialized(); - var x: c_int = 0; - var y: c_int = 0; - c.glfwGetWindowPos(self.handle, &x, &y); - return Pos{ .x = @as(i64, @intCast(x)), .y = @as(i64, @intCast(y)) }; -} - -/// Sets the position of the content area of the specified window. -/// -/// This function sets the position, in screen coordinates, of the upper-left corner of the content -/// area of the specified windowed mode window. If the window is a full screen window, this -/// function does nothing. -/// -/// __Do not use this function__ to move an already visible window unless you have very good -/// reasons for doing so, as it will confuse and annoy the user. -/// -/// The window manager may put limits on what positions are allowed. GLFW cannot and should not -/// override these limits. -/// -/// Possible errors include glfw.ErrorCode.FeatureUnavailable. -/// -/// wayland: There is no way for an application to set the global position of its windows, this -/// function will always emit glfw.ErrorCode.FeatureUnavailable. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_pos, glfw.Window.getPos -pub inline fn setPos(self: Window, pos: Pos) void { - internal_debug.assertInitialized(); - c.glfwSetWindowPos(self.handle, @as(c_int, @intCast(pos.x)), @as(c_int, @intCast(pos.y))); -} - -pub const Size = struct { - width: u32, - height: u32, -}; - -/// Retrieves the size of the content area of the specified window. -/// -/// This function retrieves the size, in screen coordinates, of the content area of the specified -/// window. If you wish to retrieve the size of the framebuffer of the window in pixels, see -/// glfw.Window.getFramebufferSize. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_size, glfw.Window.setSize -pub inline fn getSize(self: Window) Size { - internal_debug.assertInitialized(); - var width: c_int = 0; - var height: c_int = 0; - c.glfwGetWindowSize(self.handle, &width, &height); - return Size{ .width = @as(u32, @intCast(width)), .height = @as(u32, @intCast(height)) }; -} - -/// Sets the size of the content area of the specified window. -/// -/// This function sets the size, in screen coordinates, of the content area of the specified window. -/// -/// For full screen windows, this function updates the resolution of its desired video mode and -/// switches to the video mode closest to it, without affecting the window's context. As the -/// context is unaffected, the bit depths of the framebuffer remain unchanged. -/// -/// If you wish to update the refresh rate of the desired video mode in addition to its resolution, -/// see glfw.Window.setMonitor. -/// -/// The window manager may put limits on what sizes are allowed. GLFW cannot and should not -/// override these limits. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// wayland: A full screen window will not attempt to change the mode, no matter what the requested -/// size. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_size, glfw.Window.getSize, glfw.Window.SetMonitor -pub inline fn setSize(self: Window, size: Size) void { - internal_debug.assertInitialized(); - c.glfwSetWindowSize(self.handle, @as(c_int, @intCast(size.width)), @as(c_int, @intCast(size.height))); -} - -/// A size with option width/height, used to represent e.g. constraints on a windows size while -/// allowing specific axis to be unconstrained (null) if desired. -pub const SizeOptional = struct { - width: ?u32 = null, - height: ?u32 = null, -}; - -/// Sets the size limits of the specified window's content area. -/// -/// This function sets the size limits of the content area of the specified window. If the window -/// is full screen, the size limits only take effect/ once it is made windowed. If the window is not -/// resizable, this function does nothing. -/// -/// The size limits are applied immediately to a windowed mode window and may cause it to be resized. -/// -/// The maximum dimensions must be greater than or equal to the minimum dimensions. glfw.dont_care -/// may be used for any width/height parameter. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue and glfw.ErrorCode.PlatformError. -/// -/// If you set size limits and an aspect ratio that conflict, the results are undefined. -/// -/// wayland: The size limits will not be applied until the window is actually resized, either by -/// the user or by the compositor. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_sizelimits, glfw.Window.setAspectRatio -pub inline fn setSizeLimits(self: Window, min: SizeOptional, max: SizeOptional) void { - internal_debug.assertInitialized(); - - if (min.width != null and max.width != null) { - std.debug.assert(min.width.? <= max.width.?); - } - if (min.height != null and max.height != null) { - std.debug.assert(min.height.? <= max.height.?); - } - - c.glfwSetWindowSizeLimits( - self.handle, - if (min.width) |min_width| @as(c_int, @intCast(min_width)) else glfw.dont_care, - if (min.height) |min_height| @as(c_int, @intCast(min_height)) else glfw.dont_care, - if (max.width) |max_width| @as(c_int, @intCast(max_width)) else glfw.dont_care, - if (max.height) |max_height| @as(c_int, @intCast(max_height)) else glfw.dont_care, - ); -} - -/// Sets the aspect ratio of the specified window. -/// -/// This function sets the required aspect ratio of the content area of the specified window. If -/// the window is full screen, the aspect ratio only takes effect once it is made windowed. If the -/// window is not resizable, this function does nothing. -/// -/// The aspect ratio is specified as a numerator and a denominator and both values must be greater -/// than zero. For example, the common 16:9 aspect ratio is specified as 16 and 9, respectively. -/// -/// If the numerator AND denominator is set to `glfw.dont_care` then the aspect ratio limit is -/// disabled. -/// -/// The aspect ratio is applied immediately to a windowed mode window and may cause it to be -/// resized. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue and glfw.ErrorCode.PlatformError. -/// -/// If you set size limits and an aspect ratio that conflict, the results are undefined. -/// -/// wayland: The aspect ratio will not be applied until the window is actually resized, either by -/// the user or by the compositor. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_sizelimits, glfw.Window.setSizeLimits -/// -/// WARNING: on wayland it will return glfw.ErrorCode.FeatureUnimplemented -pub inline fn setAspectRatio(self: Window, numerator: ?u32, denominator: ?u32) void { - internal_debug.assertInitialized(); - - if (numerator != null and denominator != null) { - std.debug.assert(numerator.? > 0); - std.debug.assert(denominator.? > 0); - } - - c.glfwSetWindowAspectRatio( - self.handle, - if (numerator) |numerator_unwrapped| @as(c_int, @intCast(numerator_unwrapped)) else glfw.dont_care, - if (denominator) |denominator_unwrapped| @as(c_int, @intCast(denominator_unwrapped)) else glfw.dont_care, - ); -} - -/// Retrieves the size of the framebuffer of the specified window. -/// -/// This function retrieves the size, in pixels, of the framebuffer of the specified window. If you -/// wish to retrieve the size of the window in screen coordinates, see @ref glfwGetWindowSize. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_fbsize, glfwWindow.setFramebufferSizeCallback -pub inline fn getFramebufferSize(self: Window) Size { - internal_debug.assertInitialized(); - var width: c_int = 0; - var height: c_int = 0; - c.glfwGetFramebufferSize(self.handle, &width, &height); - return Size{ .width = @as(u32, @intCast(width)), .height = @as(u32, @intCast(height)) }; -} - -pub const FrameSize = struct { - left: u32, - top: u32, - right: u32, - bottom: u32, -}; - -/// Retrieves the size of the frame of the window. -/// -/// This function retrieves the size, in screen coordinates, of each edge of the frame of the -/// specified window. This size includes the title bar, if the window has one. The size of the -/// frame may vary depending on the window-related hints used to create it. -/// -/// Because this function retrieves the size of each window frame edge and not the offset along a -/// particular coordinate axis, the retrieved values will always be zero or positive. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_size -pub inline fn getFrameSize(self: Window) FrameSize { - internal_debug.assertInitialized(); - var left: c_int = 0; - var top: c_int = 0; - var right: c_int = 0; - var bottom: c_int = 0; - c.glfwGetWindowFrameSize(self.handle, &left, &top, &right, &bottom); - return FrameSize{ - .left = @as(u32, @intCast(left)), - .top = @as(u32, @intCast(top)), - .right = @as(u32, @intCast(right)), - .bottom = @as(u32, @intCast(bottom)), - }; -} - -pub const ContentScale = struct { - x_scale: f32, - y_scale: f32, -}; - -/// Retrieves the content scale for the specified window. -/// -/// This function retrieves the content scale for the specified window. The content scale is the -/// ratio between the current DPI and the platform's default DPI. This is especially important for -/// text and any UI elements. If the pixel dimensions of your UI scaled by this look appropriate on -/// your machine then it should appear at a reasonable size on other machines regardless of their -/// DPI and scaling settings. This relies on the system DPI and scaling settings being somewhat -/// correct. -/// -/// On platforms where each monitors can have its own content scale, the window content scale will -/// depend on which monitor the system considers the window to be on. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_scale, glfwSetWindowContentScaleCallback, glfwGetMonitorContentScale -pub inline fn getContentScale(self: Window) ContentScale { - internal_debug.assertInitialized(); - var x_scale: f32 = 0; - var y_scale: f32 = 0; - c.glfwGetWindowContentScale(self.handle, &x_scale, &y_scale); - return ContentScale{ .x_scale = x_scale, .y_scale = y_scale }; -} - -/// Returns the opacity of the whole window. -/// -/// This function returns the opacity of the window, including any decorations. -/// -/// The opacity (or alpha) value is a positive finite number between zero and one, where zero is -/// fully transparent and one is fully opaque. If the system does not support whole window -/// transparency, this function always returns one. -/// -/// The initial opacity value for newly created windows is one. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_transparency, glfw.Window.setOpacity -pub inline fn getOpacity(self: Window) f32 { - internal_debug.assertInitialized(); - const opacity = c.glfwGetWindowOpacity(self.handle); - return opacity; -} - -/// Sets the opacity of the whole window. -/// -/// This function sets the opacity of the window, including any decorations. -/// -/// The opacity (or alpha) value is a positive finite number between zero and one, where zero is -/// fully transparent and one is fully opaque. -/// -/// The initial opacity value for newly created windows is one. -/// -/// A window created with framebuffer transparency may not use whole window transparency. The -/// results of doing this are undefined. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_transparency, glfw.Window.getOpacity -pub inline fn setOpacity(self: Window, opacity: f32) void { - internal_debug.assertInitialized(); - c.glfwSetWindowOpacity(self.handle, opacity); -} - -/// Iconifies the specified window. -/// -/// This function iconifies (minimizes) the specified window if it was previously restored. If the -/// window is already iconified, this function does nothing. -/// -/// If the specified window is a full screen window, GLFW restores the original video mode of the -/// monitor. The window's desired video mode is set again when the window is restored. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// wayland: Once a window is iconified, glfw.Window.restorebe able to restore it. This is a design -/// decision of the xdg-shell protocol. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_iconify, glfw.Window.restore, glfw.Window.maximize -pub inline fn iconify(self: Window) void { - internal_debug.assertInitialized(); - c.glfwIconifyWindow(self.handle); -} - -/// Restores the specified window. -/// -/// This function restores the specified window if it was previously iconified (minimized) or -/// maximized. If the window is already restored, this function does nothing. -/// -/// If the specified window is an iconified full screen window, its desired video mode is set -/// again for its monitor when the window is restored. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_iconify, glfw.Window.iconify, glfw.Window.maximize -pub inline fn restore(self: Window) void { - internal_debug.assertInitialized(); - c.glfwRestoreWindow(self.handle); -} - -/// Maximizes the specified window. -/// -/// This function maximizes the specified window if it was previously not maximized. If the window -/// is already maximized, this function does nothing. -/// -/// If the specified window is a full screen window, this function does nothing. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_iconify, glfw.Window.iconify, glfw.Window.restore -pub inline fn maximize(self: Window) void { - internal_debug.assertInitialized(); - c.glfwMaximizeWindow(self.handle); -} - -/// Makes the specified window visible. -/// -/// This function makes the specified window visible if it was previously hidden. If the window is -/// already visible or is in full screen mode, this function does nothing. -/// -/// By default, windowed mode windows are focused when shown Set the glfw.focus_on_show window hint -/// to change this behavior for all newly created windows, or change the -/// behavior for an existing window with glfw.Window.setAttrib. -/// -/// wayland: Because Wayland wants every frame of the desktop to be complete, this function does -/// not immediately make the window visible. Instead it will become visible the next time the window -/// framebuffer is updated after this call. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_hide, glfw.Window.hide -/// -/// WARNING: on wayland it will return glfw.ErrorCode.FeatureUnavailable -pub inline fn show(self: Window) void { - internal_debug.assertInitialized(); - c.glfwShowWindow(self.handle); -} - -/// Hides the specified window. -/// -/// This function hides the specified window if it was previously visible. If the window is already -/// hidden or is in full screen mode, this function does nothing. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_hide, glfw.Window.show -pub inline fn hide(self: Window) void { - internal_debug.assertInitialized(); - c.glfwHideWindow(self.handle); -} - -/// Brings the specified window to front and sets input focus. -/// -/// This function brings the specified window to front and sets input focus. The window should -/// already be visible and not iconified. -/// -/// By default, both windowed and full screen mode windows are focused when initially created. Set -/// the glfw.focused to disable this behavior. -/// -/// Also by default, windowed mode windows are focused when shown with glfw.Window.show. Set the -/// glfw.focus_on_show to disable this behavior. -/// -/// __Do not use this function__ to steal focus from other applications unless you are certain that -/// is what the user wants. Focus stealing can be extremely disruptive. -/// -/// For a less disruptive way of getting the user's attention, see [attention requests (window_attention). -/// -/// wayland It is not possible for an application to set the input focus. This function will emit -/// glfw.ErrorCode.FeatureUnavailable. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_focus, window_attention -pub inline fn focus(self: Window) void { - internal_debug.assertInitialized(); - c.glfwFocusWindow(self.handle); -} - -/// Requests user attention to the specified window. -/// -/// This function requests user attention to the specified window. On platforms where this is not -/// supported, attention is requested to the application as a whole. -/// -/// Once the user has given attention, usually by focusing the window or application, the system will end the request automatically. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// macos: Attention is requested to the application as a whole, not the -/// specific window. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_attention -/// -/// WARNING: on wayland it will return glfw.ErrorCode.FeatureUnimplemented -pub inline fn requestAttention(self: Window) void { - internal_debug.assertInitialized(); - c.glfwRequestWindowAttention(self.handle); -} - -/// Swaps the front and back buffers of the specified window. -/// -/// This function swaps the front and back buffers of the specified window when rendering with -/// OpenGL or OpenGL ES. If the swap interval is greater than zero, the GPU driver waits the -/// specified number of screen updates before swapping the buffers. -/// -/// The specified window must have an OpenGL or OpenGL ES context. Specifying a window without a -/// context will generate glfw.ErrorCode.NoWindowContext. -/// -/// This function does not apply to Vulkan. If you are rendering with Vulkan, see `vkQueuePresentKHR` -/// instead. -/// -/// @param[in] window The window whose buffers to swap. -/// -/// Possible errors include glfw.ErrorCode.NoWindowContext and glfw.ErrorCode.PlatformError. -/// -/// __EGL:__ The context of the specified window must be current on the calling thread. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: buffer_swap, glfwSwapInterval -pub inline fn swapBuffers(self: Window) void { - internal_debug.assertInitialized(); - c.glfwSwapBuffers(self.handle); -} - -/// Returns the monitor that the window uses for full screen mode. -/// -/// This function returns the handle of the monitor that the specified window is in full screen on. -/// -/// @return The monitor, or null if the window is in windowed mode. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_monitor, glfw.Window.setMonitor -pub inline fn getMonitor(self: Window) ?Monitor { - internal_debug.assertInitialized(); - if (c.glfwGetWindowMonitor(self.handle)) |monitor| return Monitor{ .handle = monitor }; - return null; -} - -/// Sets the mode, monitor, video mode and placement of a window. -/// -/// This function sets the monitor that the window uses for full screen mode or, if the monitor is -/// null, makes it windowed mode. -/// -/// When setting a monitor, this function updates the width, height and refresh rate of the desired -/// video mode and switches to the video mode closest to it. The window position is ignored when -/// setting a monitor. -/// -/// When the monitor is null, the position, width and height are used to place the window content -/// area. The refresh rate is ignored when no monitor is specified. -/// -/// If you only wish to update the resolution of a full screen window or the size of a windowed -/// mode window, see @ref glfwSetWindowSize. -/// -/// When a window transitions from full screen to windowed mode, this function restores any -/// previous window settings such as whether it is decorated, floating, resizable, has size or -/// aspect ratio limits, etc. -/// -/// @param[in] window The window whose monitor, size or video mode to set. -/// @param[in] monitor The desired monitor, or null to set windowed mode. -/// @param[in] xpos The desired x-coordinate of the upper-left corner of the content area. -/// @param[in] ypos The desired y-coordinate of the upper-left corner of the content area. -/// @param[in] width The desired with, in screen coordinates, of the content area or video mode. -/// @param[in] height The desired height, in screen coordinates, of the content area or video mode. -/// @param[in] refreshRate The desired refresh rate, in Hz, of the video mode, or `glfw.dont_care`. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// The OpenGL or OpenGL ES context will not be destroyed or otherwise affected by any resizing or -/// mode switching, although you may need to update your viewport if the framebuffer size has -/// changed. -/// -/// wayland: The desired window position is ignored, as there is no way for an application to set -/// this property. -/// -/// wayland: Setting the window to full screen will not attempt to change the mode, no matter what -/// the requested size or refresh rate. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_monitor, window_full_screen, glfw.Window.getMonitor, glfw.Window.setSize -pub inline fn setMonitor(self: Window, monitor: ?Monitor, xpos: i32, ypos: i32, width: u32, height: u32, refresh_rate: ?u32) void { - internal_debug.assertInitialized(); - c.glfwSetWindowMonitor( - self.handle, - if (monitor) |m| m.handle else null, - @as(c_int, @intCast(xpos)), - @as(c_int, @intCast(ypos)), - @as(c_int, @intCast(width)), - @as(c_int, @intCast(height)), - if (refresh_rate) |refresh_rate_unwrapped| @as(c_int, @intCast(refresh_rate_unwrapped)) else glfw.dont_care, - ); -} - -/// Window attributes -pub const Attrib = enum(c_int) { - iconified = c.GLFW_ICONIFIED, - resizable = c.GLFW_RESIZABLE, - visible = c.GLFW_VISIBLE, - decorated = c.GLFW_DECORATED, - focused = c.GLFW_FOCUSED, - auto_iconify = c.GLFW_AUTO_ICONIFY, - floating = c.GLFW_FLOATING, - maximized = c.GLFW_MAXIMIZED, - transparent_framebuffer = c.GLFW_TRANSPARENT_FRAMEBUFFER, - hovered = c.GLFW_HOVERED, - focus_on_show = c.GLFW_FOCUS_ON_SHOW, - mouse_passthrough = c.GLFW_MOUSE_PASSTHROUGH, - doublebuffer = c.GLFW_DOUBLEBUFFER, - - client_api = c.GLFW_CLIENT_API, - context_creation_api = c.GLFW_CONTEXT_CREATION_API, - context_version_major = c.GLFW_CONTEXT_VERSION_MAJOR, - context_version_minor = c.GLFW_CONTEXT_VERSION_MINOR, - context_revision = c.GLFW_CONTEXT_REVISION, - - context_robustness = c.GLFW_CONTEXT_ROBUSTNESS, - context_release_behavior = c.GLFW_CONTEXT_RELEASE_BEHAVIOR, - context_no_error = c.GLFW_CONTEXT_NO_ERROR, - context_debug = c.GLFW_CONTEXT_DEBUG, - - opengl_forward_compat = c.GLFW_OPENGL_FORWARD_COMPAT, - opengl_profile = c.GLFW_OPENGL_PROFILE, -}; - -/// Returns an attribute of the specified window. -/// -/// This function returns the value of an attribute of the specified window or its OpenGL or OpenGL -/// ES context. -/// -/// @param[in] attrib The window attribute (see window_attribs) whose value to return. -/// @return The value of the attribute, or zero if an error occurred. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// Framebuffer related hints are not window attributes. See window_attribs_fb for more information. -/// -/// Zero is a valid value for many window and context related attributes so you cannot use a return -/// value of zero as an indication of errors. However, this function should not fail as long as it -/// is passed valid arguments and the library has been initialized. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// wayland: The Wayland protocol provides no way to check whether a window is iconified, so -/// glfw.Window.Attrib.iconified always returns `false`. -/// -/// see also: window_attribs, glfw.Window.setAttrib -pub inline fn getAttrib(self: Window, attrib: Attrib) i32 { - internal_debug.assertInitialized(); - return c.glfwGetWindowAttrib(self.handle, @intFromEnum(attrib)); -} - -/// Sets an attribute of the specified window. -/// -/// This function sets the value of an attribute of the specified window. -/// -/// The supported attributes are glfw.decorated, glfw.resizable, glfw.floating, glfw.auto_iconify, -/// glfw.focus_on_show. -/// -/// Some of these attributes are ignored for full screen windows. The new value will take effect -/// if the window is later made windowed. -/// -/// Some of these attributes are ignored for windowed mode windows. The new value will take effect -/// if the window is later made full screen. -/// -/// @param[in] attrib A supported window attribute. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum, glfw.ErrorCode.InvalidValue, -/// glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable -/// -/// Calling glfw.Window.getAttrib will always return the latest -/// value, even if that value is ignored by the current mode of the window. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_attribs, glfw.Window.getAttrib -/// -pub inline fn setAttrib(self: Window, attrib: Attrib, value: bool) void { - internal_debug.assertInitialized(); - std.debug.assert(switch (attrib) { - .decorated, - .resizable, - .floating, - .auto_iconify, - .focus_on_show, - .mouse_passthrough, - .doublebuffer, - => true, - else => false, - }); - c.glfwSetWindowAttrib(self.handle, @intFromEnum(attrib), if (value) c.GLFW_TRUE else c.GLFW_FALSE); -} - -/// Sets the user pointer of the specified window. -/// -/// This function sets the user-defined pointer of the specified window. The current value is -/// retained until the window is destroyed. The initial value is null. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: window_userptr, glfw.Window.getUserPointer -pub inline fn setUserPointer(self: Window, pointer: ?*anyopaque) void { - internal_debug.assertInitialized(); - c.glfwSetWindowUserPointer(self.handle, pointer); -} - -/// Returns the user pointer of the specified window. -/// -/// This function returns the current value of the user-defined pointer of the specified window. -/// The initial value is null. -/// -/// @thread_safety This function may be called from any thread. Access is not synchronized. -/// -/// see also: window_userptr, glfw.Window.setUserPointer -pub inline fn getUserPointer(self: Window, comptime T: type) ?*T { - internal_debug.assertInitialized(); - if (c.glfwGetWindowUserPointer(self.handle)) |user_pointer| return @as(?*T, @ptrCast(@alignCast(user_pointer))); - return null; -} - -/// Sets the position callback for the specified window. -/// -/// This function sets the position callback of the specified window, which is called when the -/// window is moved. The callback is provided with the position, in screen coordinates, of the -/// upper-left corner of the content area of the window. -/// -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param `window` the window that moved. -/// @callback_param `xpos` the new x-coordinate, in screen coordinates, of the upper-left corner of -/// the content area of the window. -/// @callback_param `ypos` the new y-coordinate, in screen coordinates, of the upper-left corner of -/// the content area of the window. -/// -/// wayland: This callback will never be called, as there is no way for an application to know its -/// global position. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_pos -pub inline fn setPosCallback(self: Window, comptime callback: ?fn (window: Window, xpos: i32, ypos: i32) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn posCallbackWrapper(handle: ?*c.GLFWwindow, xpos: c_int, ypos: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(i32, @intCast(xpos)), - @as(i32, @intCast(ypos)), - }); - } - }; - - if (c.glfwSetWindowPosCallback(self.handle, CWrapper.posCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowPosCallback(self.handle, null) != null) return; - } -} - -/// Sets the size callback for the specified window. -/// -/// This function sets the size callback of the specified window, which is called when the window -/// is resized. The callback is provided with the size, in screen coordinates, of the content area -/// of the window. -/// -/// @callback_param `window` the window that was resized. -/// @callback_param `width` the new width, in screen coordinates, of the window. -/// @callback_param `height` the new height, in screen coordinates, of the window. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_size -pub inline fn setSizeCallback(self: Window, comptime callback: ?fn (window: Window, width: i32, height: i32) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn sizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(i32, @intCast(width)), - @as(i32, @intCast(height)), - }); - } - }; - - if (c.glfwSetWindowSizeCallback(self.handle, CWrapper.sizeCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowSizeCallback(self.handle, null) != null) return; - } -} - -/// Sets the close callback for the specified window. -/// -/// This function sets the close callback of the specified window, which is called when the user -/// attempts to close the window, for example by clicking the close widget in the title bar. -/// -/// The close flag is set before this callback is called, but you can modify it at any time with -/// glfw.Window.setShouldClose. -/// -/// The close callback is not triggered by glfw.Window.destroy. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param `window` the window that the user attempted to close. -/// -/// macos: Selecting Quit from the application menu will trigger the close callback for all -/// windows. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_close -pub inline fn setCloseCallback(self: Window, comptime callback: ?fn (window: Window) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn closeCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - }); - } - }; - - if (c.glfwSetWindowCloseCallback(self.handle, CWrapper.closeCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowCloseCallback(self.handle, null) != null) return; - } -} - -/// Sets the refresh callback for the specified window. -/// -/// This function sets the refresh callback of the specified window, which is -/// called when the content area of the window needs to be redrawn, for example -/// if the window has been exposed after having been covered by another window. -/// -/// On compositing window systems such as Aero, Compiz, Aqua or Wayland, where -/// the window contents are saved off-screen, this callback may be called only -/// very infrequently or never at all. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window whose content needs to be refreshed. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_refresh -pub inline fn setRefreshCallback(self: Window, comptime callback: ?fn (window: Window) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn refreshCallbackWrapper(handle: ?*c.GLFWwindow) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - }); - } - }; - - if (c.glfwSetWindowRefreshCallback(self.handle, CWrapper.refreshCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowRefreshCallback(self.handle, null) != null) return; - } -} - -/// Sets the focus callback for the specified window. -/// -/// This function sets the focus callback of the specified window, which is -/// called when the window gains or loses input focus. -/// -/// After the focus callback is called for a window that lost input focus, -/// synthetic key and mouse button release events will be generated for all such -/// that had been pressed. For more information, see @ref glfwSetKeyCallback -/// and @ref glfwSetMouseButtonCallback. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window whose input focus has changed. -/// @callback_param `focused` `true` if the window was given input focus, or `false` if it lost it. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_focus -pub inline fn setFocusCallback(self: Window, comptime callback: ?fn (window: Window, focused: bool) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn focusCallbackWrapper(handle: ?*c.GLFWwindow, focused: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - focused == c.GLFW_TRUE, - }); - } - }; - - if (c.glfwSetWindowFocusCallback(self.handle, CWrapper.focusCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowFocusCallback(self.handle, null) != null) return; - } -} - -/// Sets the iconify callback for the specified window. -/// -/// This function sets the iconification callback of the specified window, which -/// is called when the window is iconified or restored. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window which was iconified or restored. -/// @callback_param `iconified` `true` if the window was iconified, or `false` if it was restored. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_iconify -pub inline fn setIconifyCallback(self: Window, comptime callback: ?fn (window: Window, iconified: bool) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn iconifyCallbackWrapper(handle: ?*c.GLFWwindow, iconified: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - iconified == c.GLFW_TRUE, - }); - } - }; - - if (c.glfwSetWindowIconifyCallback(self.handle, CWrapper.iconifyCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowIconifyCallback(self.handle, null) != null) return; - } -} - -/// Sets the maximize callback for the specified window. -/// -/// This function sets the maximization callback of the specified window, which -/// is called when the window is maximized or restored. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window which was maximized or restored. -/// @callback_param `maximized` `true` if the window was maximized, or `false` if it was restored. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_maximize -// GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* window, GLFWwindowmaximizefun callback); -pub inline fn setMaximizeCallback(self: Window, comptime callback: ?fn (window: Window, maximized: bool) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn maximizeCallbackWrapper(handle: ?*c.GLFWwindow, maximized: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - maximized == c.GLFW_TRUE, - }); - } - }; - - if (c.glfwSetWindowMaximizeCallback(self.handle, CWrapper.maximizeCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowMaximizeCallback(self.handle, null) != null) return; - } -} - -/// Sets the framebuffer resize callback for the specified window. -/// -/// This function sets the framebuffer resize callback of the specified window, -/// which is called when the framebuffer of the specified window is resized. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window whose framebuffer was resized. -/// @callback_param `width` the new width, in pixels, of the framebuffer. -/// @callback_param `height` the new height, in pixels, of the framebuffer. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_fbsize -pub inline fn setFramebufferSizeCallback(self: Window, comptime callback: ?fn (window: Window, width: u32, height: u32) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn framebufferSizeCallbackWrapper(handle: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(u32, @intCast(width)), - @as(u32, @intCast(height)), - }); - } - }; - - if (c.glfwSetFramebufferSizeCallback(self.handle, CWrapper.framebufferSizeCallbackWrapper) != null) return; - } else { - if (c.glfwSetFramebufferSizeCallback(self.handle, null) != null) return; - } -} - -/// Sets the window content scale callback for the specified window. -/// -/// This function sets the window content scale callback of the specified window, -/// which is called when the content scale of the specified window changes. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set -/// callback. -/// -/// @callback_param `window` the window whose content scale changed. -/// @callback_param `xscale` the new x-axis content scale of the window. -/// @callback_param `yscale` the new y-axis content scale of the window. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_scale, glfw.Window.getContentScale -pub inline fn setContentScaleCallback(self: Window, comptime callback: ?fn (window: Window, xscale: f32, yscale: f32) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn windowScaleCallbackWrapper(handle: ?*c.GLFWwindow, xscale: f32, yscale: f32) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - xscale, - yscale, - }); - } - }; - - if (c.glfwSetWindowContentScaleCallback(self.handle, CWrapper.windowScaleCallbackWrapper) != null) return; - } else { - if (c.glfwSetWindowContentScaleCallback(self.handle, null) != null) return; - } -} - -pub const InputMode = enum(c_int) { - cursor = c.GLFW_CURSOR, - sticky_keys = c.GLFW_STICKY_KEYS, - sticky_mouse_buttons = c.GLFW_STICKY_MOUSE_BUTTONS, - lock_key_mods = c.GLFW_LOCK_KEY_MODS, - raw_mouse_motion = c.GLFW_RAW_MOUSE_MOTION, -}; - -/// A cursor input mode to be supplied to `glfw.Window.setInputModeCursor` -pub const InputModeCursor = enum(c_int) { - /// Makes the cursor visible and behaving normally. - normal = c.GLFW_CURSOR_NORMAL, - - /// Makes the cursor invisible when it is over the content area of the window but does not - /// restrict it from leaving. - hidden = c.GLFW_CURSOR_HIDDEN, - - /// Hides and grabs the cursor, providing virtual and unlimited cursor movement. This is useful - /// for implementing for example 3D camera controls. - disabled = c.GLFW_CURSOR_DISABLED, - - /// Makes the cursor visible but confines it to the content area of the window. - captured = c.GLFW_CURSOR_CAPTURED, -}; - -/// Sets the input mode of the cursor, whether it should behave normally, be hidden, or grabbed. -pub inline fn setInputModeCursor(self: Window, value: InputModeCursor) void { - return self.setInputMode(InputMode.cursor, value); -} - -/// Gets the current input mode of the cursor. -pub inline fn getInputModeCursor(self: Window) InputModeCursor { - return @as(InputModeCursor, @enumFromInt(self.getInputMode(InputMode.cursor))); -} - -/// Sets the input mode of sticky keys, if enabled a key press will ensure that `glfw.Window.getKey` -/// return `.press` the next time it is called even if the key had been released before the call. -/// -/// This is useful when you are only interested in whether keys have been pressed but not when or -/// in which order. -pub inline fn setInputModeStickyKeys(self: Window, enabled: bool) void { - return self.setInputMode(InputMode.sticky_keys, enabled); -} - -/// Tells if the sticky keys input mode is enabled. -pub inline fn getInputModeStickyKeys(self: Window) bool { - return self.getInputMode(InputMode.sticky_keys) == 1; -} - -/// Sets the input mode of sticky mouse buttons, if enabled a mouse button press will ensure that -/// `glfw.Window.getMouseButton` return `.press` the next time it is called even if the button had -/// been released before the call. -/// -/// This is useful when you are only interested in whether buttons have been pressed but not when -/// or in which order. -pub inline fn setInputModeStickyMouseButtons(self: Window, enabled: bool) void { - return self.setInputMode(InputMode.sticky_mouse_buttons, enabled); -} - -/// Tells if the sticky mouse buttons input mode is enabled. -pub inline fn getInputModeStickyMouseButtons(self: Window) bool { - return self.getInputMode(InputMode.sticky_mouse_buttons) == 1; -} - -/// Sets the input mode of locking key modifiers, if enabled callbacks that receive modifier bits -/// will also have the glfw.mod.caps_lock bit set when the event was generated with Caps Lock on, -/// and the glfw.mod.num_lock bit when Num Lock was on. -pub inline fn setInputModeLockKeyMods(self: Window, enabled: bool) void { - return self.setInputMode(InputMode.lock_key_mods, enabled); -} - -/// Tells if the locking key modifiers input mode is enabled. -pub inline fn getInputModeLockKeyMods(self: Window) bool { - return self.getInputMode(InputMode.lock_key_mods) == 1; -} - -/// Sets whether the raw mouse motion input mode is enabled, if enabled unscaled and unaccelerated -/// mouse motion events will be sent, otherwise standard mouse motion events respecting the user's -/// OS settings will be sent. -/// -/// If raw motion is not supported, attempting to set this will emit glfw.ErrorCode.FeatureUnavailable. -/// Call glfw.rawMouseMotionSupported to check for support. -pub inline fn setInputModeRawMouseMotion(self: Window, enabled: bool) void { - return self.setInputMode(InputMode.raw_mouse_motion, enabled); -} - -/// Tells if the raw mouse motion input mode is enabled. -pub inline fn getInputModeRawMouseMotion(self: Window) bool { - return self.getInputMode(InputMode.raw_mouse_motion) == 1; -} - -/// Returns the value of an input option for the specified window. -/// -/// Consider using one of the following variants instead, if applicable, as they'll give you a -/// typed return value: -/// -/// * `glfw.Window.getInputModeCursor` -/// * `glfw.Window.getInputModeStickyKeys` -/// * `glfw.Window.getInputModeStickyMouseButtons` -/// * `glfw.Window.getInputModeLockKeyMods` -/// * `glfw.Window.getInputModeRawMouseMotion` -/// -/// This function returns the value of an input option for the specified window. The mode must be -/// one of the `glfw.Window.InputMode` enumerations. -/// -/// Boolean values, such as for `glfw.Window.InputMode.raw_mouse_motion`, are returned as integers. -/// You may convert to a boolean using `== 1`. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: glfw.Window.setInputMode -pub inline fn getInputMode(self: Window, mode: InputMode) i32 { - internal_debug.assertInitialized(); - const value = c.glfwGetInputMode(self.handle, @intFromEnum(mode)); - return @as(i32, @intCast(value)); -} - -/// Sets an input option for the specified window. -/// -/// Consider using one of the following variants instead, if applicable, as they'll guide you to -/// the right input value via enumerations: -/// -/// * `glfw.Window.setInputModeCursor` -/// * `glfw.Window.setInputModeStickyKeys` -/// * `glfw.Window.setInputModeStickyMouseButtons` -/// * `glfw.Window.setInputModeLockKeyMods` -/// * `glfw.Window.setInputModeRawMouseMotion` -/// -/// @param[in] mode One of the `glfw.Window.InputMode` enumerations. -/// @param[in] value The new value of the specified input mode. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: glfw.Window.getInputMode -pub inline fn setInputMode(self: Window, mode: InputMode, value: anytype) void { - internal_debug.assertInitialized(); - const T = @TypeOf(value); - std.debug.assert(switch (mode) { - .cursor => switch (@import("shims.zig").typeInfo(T)) { - .@"enum" => T == InputModeCursor, - .enum_literal => @hasField(InputModeCursor, @tagName(value)), - else => false, - }, - .sticky_keys => T == bool, - .sticky_mouse_buttons => T == bool, - .lock_key_mods => T == bool, - .raw_mouse_motion => T == bool, - }); - const int_value: c_int = switch (@import("shims.zig").typeInfo(T)) { - .@"enum", - .enum_literal, - => @intFromEnum(@as(InputModeCursor, value)), - else => @intFromBool(value), - }; - c.glfwSetInputMode(self.handle, @intFromEnum(mode), int_value); -} - -/// Returns the last reported press state of a keyboard key for the specified window. -/// -/// This function returns the last press state reported for the specified key to the specified -/// window. The returned state is one of `true` (pressed) or `false` (released). -/// -/// * `glfw.Action.repeat` is only reported to the key callback. -/// -/// If the `glfw.sticky_keys` input mode is enabled, this function returns `glfw.Action.press` the -/// first time you call it for a key that was pressed, even if that key has already been released. -/// -/// The key functions deal with physical keys, with key tokens (see keys) named after their use on -/// the standard US keyboard layout. If you want to input text, use the Unicode character callback -/// instead. -/// -/// The modifier key bit masks (see mods) are not key tokens and cannot be used with this function. -/// -/// __Do not use this function__ to implement text input, use glfw.Window.setCharCallback instead. -/// -/// @param[in] window The desired window. -/// @param[in] key The desired keyboard key (see keys). `glfw.key.unknown` is not a valid key for -/// this function. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: input_key -pub inline fn getKey(self: Window, key: Key) Action { - internal_debug.assertInitialized(); - const state = c.glfwGetKey(self.handle, @intFromEnum(key)); - return @as(Action, @enumFromInt(state)); -} - -/// Returns the last reported state of a mouse button for the specified window. -/// -/// This function returns whether the specified mouse button is pressed or not. -/// -/// If the glfw.sticky_mouse_buttons input mode is enabled, this function returns `true` the first -/// time you call it for a mouse button that was pressed, even if that mouse button has already been -/// released. -/// -/// @param[in] button The desired mouse button. -/// @return One of `true` (if pressed) or `false` (if released) -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: input_mouse_button -pub inline fn getMouseButton(self: Window, button: MouseButton) Action { - internal_debug.assertInitialized(); - const state = c.glfwGetMouseButton(self.handle, @intFromEnum(button)); - return @as(Action, @enumFromInt(state)); -} - -pub const CursorPos = struct { - xpos: f64, - ypos: f64, -}; - -/// Retrieves the position of the cursor relative to the content area of the window. -/// -/// This function returns the position of the cursor, in screen coordinates, relative to the -/// upper-left corner of the content area of the specified window. -/// -/// If the cursor is disabled (with `glfw.cursor_disabled`) then the cursor position is unbounded -/// and limited only by the minimum and maximum values of a `f64`. -/// -/// The coordinate can be converted to their integer equivalents with the `floor` function. Casting -/// directly to an integer type works for positive coordinates, but fails for negative ones. -/// -/// Any or all of the position arguments may be null. If an error occurs, all non-null position -/// arguments will be set to zero. -/// -/// @param[in] window The desired window. -/// @param[out] xpos Where to store the cursor x-coordinate, relative to the left edge of the -/// content area, or null. -/// @param[out] ypos Where to store the cursor y-coordinate, relative to the to top edge of the -/// content area, or null. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// Additionally returns a zero value in the event of an error. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_pos, glfw.Window.setCursorPos -pub inline fn getCursorPos(self: Window) CursorPos { - internal_debug.assertInitialized(); - var pos: CursorPos = undefined; - c.glfwGetCursorPos(self.handle, &pos.xpos, &pos.ypos); - return pos; -} - -/// Sets the position of the cursor, relative to the content area of the window. -/// -/// This function sets the position, in screen coordinates, of the cursor relative to the upper-left -/// corner of the content area of the specified window. The window must have input focus. If the -/// window does not have input focus when this function is called, it fails silently. -/// -/// __Do not use this function__ to implement things like camera controls. GLFW already provides the -/// `glfw.cursor_disabled` cursor mode that hides the cursor, transparently re-centers it and -/// provides unconstrained cursor motion. See glfw.Window.setInputMode for more information. -/// -/// If the cursor mode is `glfw.cursor_disabled` then the cursor position is unconstrained and -/// limited only by the minimum and maximum values of a `double`. -/// -/// @param[in] window The desired window. -/// @param[in] xpos The desired x-coordinate, relative to the left edge of the content area. -/// @param[in] ypos The desired y-coordinate, relative to the top edge of the content area. -/// -/// Possible errors include glfw.ErrorCode.PlatformError, glfw.ErrorCode.FeatureUnavailable. -/// -/// wayland: This function will only work when the cursor mode is `glfw.cursor_disabled`, otherwise -/// it will do nothing. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_pos, glfw.Window.getCursorPos -pub inline fn setCursorPos(self: Window, xpos: f64, ypos: f64) void { - internal_debug.assertInitialized(); - c.glfwSetCursorPos(self.handle, xpos, ypos); -} - -/// Sets the cursor for the window. -/// -/// This function sets the cursor image to be used when the cursor is over the content area of the -/// specified window. The set cursor will only be visible when the cursor mode (see cursor_mode) of -/// the window is `glfw.Cursor.normal`. -/// -/// On some platforms, the set cursor may not be visible unless the window also has input focus. -/// -/// @param[in] cursor The cursor to set, or null to switch back to the default arrow cursor. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_object -pub inline fn setCursor(self: Window, cursor: ?Cursor) void { - internal_debug.assertInitialized(); - c.glfwSetCursor(self.handle, if (cursor) |cs| cs.ptr else null); -} - -/// Sets the key callback. -/// -/// This function sets the key callback of the specified window, which is called when a key is -/// pressed, repeated or released. -/// -/// The key functions deal with physical keys, with layout independent key tokens (see keys) named -/// after their values in the standard US keyboard layout. If you want to input text, use the -/// character callback (see glfw.Window.setCharCallback) instead. -/// -/// When a window loses input focus, it will generate synthetic key release events for all pressed -/// keys. You can tell these events from user-generated events by the fact that the synthetic ones -/// are generated after the focus loss event has been processed, i.e. after the window focus -/// callback (see glfw.Window.setFocusCallback) has been called. -/// -/// The scancode of a key is specific to that platform or sometimes even to that machine. Scancodes -/// are intended to allow users to bind keys that don't have a GLFW key token. Such keys have `key` -/// set to `glfw.key.unknown`, their state is not saved and so it cannot be queried with -/// glfw.Window.getKey. -/// -/// Sometimes GLFW needs to generate synthetic key events, in which case the scancode may be zero. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new key callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] key The keyboard key (see keys) that was pressed or released. -/// @callback_param[in] scancode The platform-specific scancode of the key. -/// @callback_param[in] action `glfw.Action.press`, `glfw.Action.release` or `glfw.Action.repeat`. -/// Future releases may add more actions. -/// @callback_param[in] mods Bit field describing which modifier keys (see mods) were held down. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: input_key -pub inline fn setKeyCallback(self: Window, comptime callback: ?fn (window: Window, key: Key, scancode: i32, action: Action, mods: Mods) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn keyCallbackWrapper(handle: ?*c.GLFWwindow, key: c_int, scancode: c_int, action: c_int, mods: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(Key, @enumFromInt(key)), - @as(i32, @intCast(scancode)), - @as(Action, @enumFromInt(action)), - Mods.fromInt(mods), - }); - } - }; - - if (c.glfwSetKeyCallback(self.handle, CWrapper.keyCallbackWrapper) != null) return; - } else { - if (c.glfwSetKeyCallback(self.handle, null) != null) return; - } -} - -/// Sets the Unicode character callback. -/// -/// This function sets the character callback of the specified window, which is called when a -/// Unicode character is input. -/// -/// The character callback is intended for Unicode text input. As it deals with characters, it is -/// keyboard layout dependent, whereas the key callback (see glfw.Window.setKeyCallback) is not. -/// Characters do not map 1:1 to physical keys, as a key may produce zero, one or more characters. -/// If you want to know whether a specific physical key was pressed or released, see the key -/// callback instead. -/// -/// The character callback behaves as system text input normally does and will not be called if -/// modifier keys are held down that would prevent normal text input on that platform, for example a -/// Super (Command) key on macOS or Alt key on Windows. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] codepoint The Unicode code point of the character. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: input_char -pub inline fn setCharCallback(self: Window, comptime callback: ?fn (window: Window, codepoint: u21) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn charCallbackWrapper(handle: ?*c.GLFWwindow, codepoint: c_uint) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(u21, @intCast(codepoint)), - }); - } - }; - - if (c.glfwSetCharCallback(self.handle, CWrapper.charCallbackWrapper) != null) return; - } else { - if (c.glfwSetCharCallback(self.handle, null) != null) return; - } -} - -/// Sets the mouse button callback. -/// -/// This function sets the mouse button callback of the specified window, which is called when a -/// mouse button is pressed or released. -/// -/// When a window loses input focus, it will generate synthetic mouse button release events for all -/// pressed mouse buttons. You can tell these events from user-generated events by the fact that the -/// synthetic ones are generated after the focus loss event has been processed, i.e. after the -/// window focus callback (see glfw.Window.setFocusCallback) has been called. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] button The mouse button that was pressed or released. -/// @callback_param[in] action One of `glfw.Action.press` or `glfw.Action.release`. Future releases -/// may add more actions. -/// @callback_param[in] mods Bit field describing which modifier keys (see mods) were held down. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: input_mouse_button -pub inline fn setMouseButtonCallback(self: Window, comptime callback: ?fn (window: Window, button: MouseButton, action: Action, mods: Mods) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn mouseButtonCallbackWrapper(handle: ?*c.GLFWwindow, button: c_int, action: c_int, mods: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as(MouseButton, @enumFromInt(button)), - @as(Action, @enumFromInt(action)), - Mods.fromInt(mods), - }); - } - }; - - if (c.glfwSetMouseButtonCallback(self.handle, CWrapper.mouseButtonCallbackWrapper) != null) return; - } else { - if (c.glfwSetMouseButtonCallback(self.handle, null) != null) return; - } -} - -/// Sets the cursor position callback. -/// -/// This function sets the cursor position callback of the specified window, which is called when -/// the cursor is moved. The callback is provided with the position, in screen coordinates, relative -/// to the upper-left corner of the content area of the window. -/// -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] xpos The new cursor x-coordinate, relative to the left edge of the content -/// area. -/// callback_@param[in] ypos The new cursor y-coordinate, relative to the top edge of the content -/// area. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_pos -pub inline fn setCursorPosCallback(self: Window, comptime callback: ?fn (window: Window, xpos: f64, ypos: f64) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn cursorPosCallbackWrapper(handle: ?*c.GLFWwindow, xpos: f64, ypos: f64) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - xpos, - ypos, - }); - } - }; - - if (c.glfwSetCursorPosCallback(self.handle, CWrapper.cursorPosCallbackWrapper) != null) return; - } else { - if (c.glfwSetCursorPosCallback(self.handle, null) != null) return; - } -} - -/// Sets the cursor enter/leave callback. -/// -/// This function sets the cursor boundary crossing callback of the specified window, which is -/// called when the cursor enters or leaves the content area of the window. -/// -/// @param[in] callback The new callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] entered `true` if the cursor entered the window's content area, or `false` -/// if it left it. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: cursor_enter -pub inline fn setCursorEnterCallback(self: Window, comptime callback: ?fn (window: Window, entered: bool) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn cursorEnterCallbackWrapper(handle: ?*c.GLFWwindow, entered: c_int) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - entered == c.GLFW_TRUE, - }); - } - }; - - if (c.glfwSetCursorEnterCallback(self.handle, CWrapper.cursorEnterCallbackWrapper) != null) return; - } else { - if (c.glfwSetCursorEnterCallback(self.handle, null) != null) return; - } -} - -/// Sets the scroll callback. -/// -/// This function sets the scroll callback of the specified window, which is called when a scrolling -/// device is used, such as a mouse wheel or scrolling area of a touchpad. -/// -/// The scroll callback receives all scrolling input, like that from a mouse wheel or a touchpad -/// scrolling area. -/// -/// @param[in] window The window whose callback to set. -/// @param[in] callback The new scroll callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] xoffset The scroll offset along the x-axis. -/// @callback_param[in] yoffset The scroll offset along the y-axis. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: scrolling -pub inline fn setScrollCallback(self: Window, comptime callback: ?fn (window: Window, xoffset: f64, yoffset: f64) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn scrollCallbackWrapper(handle: ?*c.GLFWwindow, xoffset: f64, yoffset: f64) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - xoffset, - yoffset, - }); - } - }; - - if (c.glfwSetScrollCallback(self.handle, CWrapper.scrollCallbackWrapper) != null) return; - } else { - if (c.glfwSetScrollCallback(self.handle, null) != null) return; - } -} - -/// Sets the path drop callback. -/// -/// This function sets the path drop callback of the specified window, which is called when one or -/// more dragged paths are dropped on the window. -/// -/// Because the path array and its strings may have been generated specifically for that event, they -/// are not guaranteed to be valid after the callback has returned. If you wish to use them after -/// the callback returns, you need to make a deep copy. -/// -/// @param[in] callback The new file drop callback, or null to remove the currently set callback. -/// -/// @callback_param[in] window The window that received the event. -/// @callback_param[in] path_count The number of dropped paths. -/// @callback_param[in] paths The UTF-8 encoded file and/or directory path names. -/// -/// @callback_pointer_lifetime The path array and its strings are valid until the callback function -/// returns. -/// -/// wayland: File drop is currently unimplemented. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: path_drop -pub inline fn setDropCallback(self: Window, comptime callback: ?fn (window: Window, paths: [][*:0]const u8) void) void { - internal_debug.assertInitialized(); - - if (callback) |user_callback| { - const CWrapper = struct { - pub fn dropCallbackWrapper(handle: ?*c.GLFWwindow, path_count: c_int, paths: [*c][*c]const u8) callconv(.c) void { - @call(.always_inline, user_callback, .{ - from(handle.?), - @as([*][*:0]const u8, @ptrCast(paths))[0..@as(u32, @intCast(path_count))], - }); - } - }; - - if (c.glfwSetDropCallback(self.handle, CWrapper.dropCallbackWrapper) != null) return; - } else { - if (c.glfwSetDropCallback(self.handle, null) != null) return; - } -} - -/// For testing purposes only; see glfw.Window.Hints and glfw.Window.create for the public API. -/// Sets the specified window hint to the desired value. -/// -/// This function sets hints for the next call to glfw.Window.create. The hints, once set, retain -/// their values until changed by a call to this function or glfw.window.defaultHints, or until the -/// library is terminated. -/// -/// This function does not check whether the specified hint values are valid. If you set hints to -/// invalid values this will instead be reported by the next call to glfw.createWindow. -/// -/// Some hints are platform specific. These may be set on any platform but they will only affect -/// their specific platform. Other platforms will ignore them. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum. -/// -/// @pointer_lifetime in the event that value is of a str type, the specified string is copied before this function returns. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: window_hints, glfw.Window.defaultHints -inline fn hint(h: Hint, value: anytype) void { - internal_debug.assertInitialized(); - const value_type = @TypeOf(value); - const value_type_info: @import("shims.zig").std.builtin.Type = @import("shims.zig").typeInfo(value_type); - - switch (value_type_info) { - .int, .comptime_int => { - c.glfwWindowHint(@intFromEnum(h), @as(c_int, @intCast(value))); - }, - .bool => { - const int_value = @intFromBool(value); - c.glfwWindowHint(@intFromEnum(h), @as(c_int, @intCast(int_value))); - }, - .@"enum" => { - const int_value = @intFromEnum(value); - c.glfwWindowHint(@intFromEnum(h), @as(c_int, @intCast(int_value))); - }, - .array => |arr_type| { - if (arr_type.child != u8) { - @compileError("expected array of u8, got " ++ @typeName(arr_type)); - } - c.glfwWindowHintString(@intFromEnum(h), &value[0]); - }, - .pointer => |pointer_info| { - const pointed_type = @import("shims.zig").typeInfo(pointer_info.child); - switch (pointed_type) { - .array => |arr_type| { - if (arr_type.child != u8) { - @compileError("expected pointer to array of u8, got " ++ @typeName(arr_type)); - } - }, - else => @compileError("expected pointer to array, got " ++ @typeName(pointed_type)), - } - - c.glfwWindowHintString(@intFromEnum(h), &value[0]); - }, - else => { - @compileError("expected a int, bool, enum, array, or pointer, got " ++ @typeName(value_type)); - }, - } -} - -test "defaultHints" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - defaultHints(); -} - -test "hint comptime int" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - hint(.focused, 1); - defaultHints(); -} - -test "hint int" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const focused: i32 = 1; - - hint(.focused, focused); - defaultHints(); -} - -test "hint bool" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - hint(.focused, true); - defaultHints(); -} - -test "hint enum(u1)" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const MyEnum = enum(u1) { - true = 1, - false = 0, - }; - - hint(.focused, MyEnum.true); - defaultHints(); -} - -test "hint enum(i32)" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const MyEnum = enum(i32) { - true = 1, - false = 0, - }; - - hint(.focused, MyEnum.true); - defaultHints(); -} - -test "hint array str" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const str_arr = [_]u8{ 'm', 'y', 'c', 'l', 'a', 's', 's' }; - - hint(.x11_class_name, str_arr); - defaultHints(); -} - -test "hint pointer str" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - hint(.x11_class_name, "myclass"); -} - -test "createWindow" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); -} - -test "setShouldClose" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - window.setShouldClose(true); - defer window.destroy(); -} - -test "setTitle" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setTitle("Updated title!"); -} - -test "setIcon" { - const allocator = testing.allocator; - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - // Create an all-red icon image. - const width: u32 = 48; - const height: u32 = 48; - const icon = try Image.init(allocator, width, height, width * height * 4); - var x: u32 = 0; - var y: u32 = 0; - while (y <= height) : (y += 1) { - while (x <= width) : (x += 1) { - icon.pixels[(x * y * 4) + 0] = 255; // red - icon.pixels[(x * y * 4) + 1] = 0; // green - icon.pixels[(x * y * 4) + 2] = 0; // blue - icon.pixels[(x * y * 4) + 3] = 255; // alpha - } - } - try window.setIcon(allocator, &[_]Image{icon}); - - icon.deinit(allocator); // glfw copies it. -} - -test "getPos" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getPos(); -} - -test "setPos" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.setPos(.{ .x = 0, .y = 0 }); -} - -test "getSize" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getSize(); -} - -test "setSize" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.setSize(.{ .width = 640, .height = 480 }); -} - -test "setSizeLimits" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setSizeLimits( - .{ .width = 720, .height = 480 }, - .{ .width = 1080, .height = 1920 }, - ); -} - -test "setAspectRatio" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setAspectRatio(4, 3); -} - -test "getFramebufferSize" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getFramebufferSize(); -} - -test "getFrameSize" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getFrameSize(); -} - -test "getContentScale" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getContentScale(); -} - -test "getOpacity" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getOpacity(); -} - -test "iconify" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.iconify(); -} - -test "restore" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.restore(); -} - -test "maximize" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.maximize(); -} - -test "show" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.show(); -} - -test "hide" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.hide(); -} - -test "focus" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.focus(); -} - -test "requestAttention" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.requestAttention(); -} - -test "swapBuffers" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.swapBuffers(); -} - -test "getMonitor" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getMonitor(); -} - -test "setMonitor" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setMonitor(null, 10, 10, 640, 480, 60); -} - -test "getAttrib" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getAttrib(.focused); -} - -test "setAttrib" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setAttrib(.decorated, false); -} - -test "setUserPointer" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - const T = struct { name: []const u8 }; - var my_value = T{ .name = "my window!" }; - - window.setUserPointer(&my_value); -} - -test "getUserPointer" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - const T = struct { name: []const u8 }; - var my_value = T{ .name = "my window!" }; - - window.setUserPointer(&my_value); - const got = window.getUserPointer(T); - std.debug.assert(&my_value == got); -} - -test "setPosCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setPosCallback((struct { - fn callback(_window: Window, xpos: i32, ypos: i32) void { - _ = _window; - _ = xpos; - _ = ypos; - } - }).callback); -} - -test "setSizeCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setSizeCallback((struct { - fn callback(_window: Window, width: i32, height: i32) void { - _ = _window; - _ = width; - _ = height; - } - }).callback); -} - -test "setCloseCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setCloseCallback((struct { - fn callback(_window: Window) void { - _ = _window; - } - }).callback); -} - -test "setRefreshCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setRefreshCallback((struct { - fn callback(_window: Window) void { - _ = _window; - } - }).callback); -} - -test "setFocusCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setFocusCallback((struct { - fn callback(_window: Window, focused: bool) void { - _ = _window; - _ = focused; - } - }).callback); -} - -test "setIconifyCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setIconifyCallback((struct { - fn callback(_window: Window, iconified: bool) void { - _ = _window; - _ = iconified; - } - }).callback); -} - -test "setMaximizeCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setMaximizeCallback((struct { - fn callback(_window: Window, maximized: bool) void { - _ = _window; - _ = maximized; - } - }).callback); -} - -test "setFramebufferSizeCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setFramebufferSizeCallback((struct { - fn callback(_window: Window, width: u32, height: u32) void { - _ = _window; - _ = width; - _ = height; - } - }).callback); -} - -test "setContentScaleCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setContentScaleCallback((struct { - fn callback(_window: Window, xscale: f32, yscale: f32) void { - _ = _window; - _ = xscale; - _ = yscale; - } - }).callback); -} - -test "setDropCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setDropCallback((struct { - fn callback(_window: Window, paths: [][*:0]const u8) void { - _ = _window; - _ = paths; - } - }).callback); -} - -test "getInputModeCursor" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputModeCursor(); -} - -test "setInputModeCursor" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setInputModeCursor(.hidden); -} - -test "getInputModeStickyKeys" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputModeStickyKeys(); -} - -test "setInputModeStickyKeys" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setInputModeStickyKeys(false); -} - -test "getInputModeStickyMouseButtons" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputModeStickyMouseButtons(); -} - -test "setInputModeStickyMouseButtons" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setInputModeStickyMouseButtons(false); -} - -test "getInputModeLockKeyMods" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputModeLockKeyMods(); -} - -test "setInputModeLockKeyMods" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setInputModeLockKeyMods(false); -} - -test "getInputModeRawMouseMotion" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputModeRawMouseMotion(); -} - -test "setInputModeRawMouseMotion" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setInputModeRawMouseMotion(false); -} - -test "getInputMode" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getInputMode(glfw.Window.InputMode.raw_mouse_motion) == 1; -} - -test "setInputMode" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - // Boolean values. - window.setInputMode(glfw.Window.InputMode.sticky_mouse_buttons, true); - - // Integer values. - window.setInputMode(glfw.Window.InputMode.cursor, glfw.Window.InputModeCursor.hidden); -} - -test "getKey" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getKey(glfw.Key.escape); -} - -test "getMouseButton" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getMouseButton(.left); -} - -test "getCursorPos" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - _ = window.getCursorPos(); -} - -test "setCursorPos" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setCursorPos(0, 0); -} - -test "setCursor" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - const cursor = glfw.Cursor.createStandard(.ibeam); - if (cursor) |cur| { - window.setCursor(cur); - defer cur.destroy(); - } -} - -test "setKeyCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setKeyCallback((struct { - fn callback(_window: Window, key: Key, scancode: i32, action: Action, mods: Mods) void { - _ = _window; - _ = key; - _ = scancode; - _ = action; - _ = mods; - } - }).callback); -} - -test "setCharCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setCharCallback((struct { - fn callback(_window: Window, codepoint: u21) void { - _ = _window; - _ = codepoint; - } - }).callback); -} - -test "setMouseButtonCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setMouseButtonCallback((struct { - fn callback(_window: Window, button: MouseButton, action: Action, mods: Mods) void { - _ = _window; - _ = button; - _ = action; - _ = mods; - } - }).callback); -} - -test "setCursorPosCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setCursorPosCallback((struct { - fn callback(_window: Window, xpos: f64, ypos: f64) void { - _ = _window; - _ = xpos; - _ = ypos; - } - }).callback); -} - -test "setCursorEnterCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setCursorEnterCallback((struct { - fn callback(_window: Window, entered: bool) void { - _ = _window; - _ = entered; - } - }).callback); -} - -test "setScrollCallback" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - window.setScrollCallback((struct { - fn callback(_window: Window, xoffset: f64, yoffset: f64) void { - _ = _window; - _ = xoffset; - _ = yoffset; - } - }).callback); -} - -test "hint-attribute default value parity" { - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - testing_ignore_window_hints_struct = true; - const window_a = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window_a: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window_a.destroy(); - - testing_ignore_window_hints_struct = false; - const window_b = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window_b: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window_b.destroy(); - - inline for (comptime std.enums.values(Window.Hint)) |hint_tag| { - if (@hasField(Window.Attrib, @tagName(hint_tag))) { - const attrib_tag = @field(Window.Attrib, @tagName(hint_tag)); - switch (attrib_tag) { - .resizable, - .visible, - .decorated, - .auto_iconify, - .floating, - .maximized, - .transparent_framebuffer, - .focus_on_show, - .mouse_passthrough, - .doublebuffer, - .client_api, - .context_creation_api, - .context_version_major, - .context_version_minor, - .context_robustness, - .context_release_behavior, - .context_no_error, // Note: at the time of writing this, GLFW does not list the default value for this hint in the documentation - .context_debug, - .opengl_forward_compat, - .opengl_profile, - => { - const expected = window_a.getAttrib(attrib_tag); - const actual = window_b.getAttrib(attrib_tag); - - testing.expectEqual(expected, actual) catch |err| { - std.debug.print("On attribute '{}'.\n", .{hint_tag}); - return err; - }; - }, - - // This attribute is based on a check for which window is currently in focus, - // and the default value, as of writing this comment, is 'true', which means - // that first window_a takes focus, and then window_b takes focus, meaning - // that we can't actually test for the default value. - .focused => continue, - - .iconified, - .hovered, - .context_revision, - => unreachable, - } - } - // Future: we could consider hint values that can't be retrieved via attributes: - // center_cursor - // mouse_passthrough - // scale_to_monitor - // red_bits - // green_bits - // blue_bits - // alpha_bits - // depth_bits - // stencil_bits - // accum_red_bits - // accum_green_bits - // accum_blue_bits - // accum_alpha_bits - // aux_buffers - // samples - - // refresh_rate - // stereo - // srgb_capable - // doublebuffer - - // platform specific, and thus not considered: - // cocoa_retina_framebuffer - // cocoa_frame_name - // cocoa_graphics_switching - } -} diff --git a/pkg/glfw/action.zig b/pkg/glfw/action.zig deleted file mode 100644 index 59314709f..000000000 --- a/pkg/glfw/action.zig +++ /dev/null @@ -1,13 +0,0 @@ -const c = @import("c.zig").c; - -/// Key and button actions -pub const Action = enum(c_int) { - /// The key or mouse button was released. - release = c.GLFW_RELEASE, - - /// The key or mouse button was pressed. - press = c.GLFW_PRESS, - - /// The key was held down until it repeated. - repeat = c.GLFW_REPEAT, -}; diff --git a/pkg/glfw/allocator.zig b/pkg/glfw/allocator.zig deleted file mode 100644 index d6517cf3e..000000000 --- a/pkg/glfw/allocator.zig +++ /dev/null @@ -1,143 +0,0 @@ -// TODO: implement custom allocator support - -// /*! @brief -// * -// * @sa @ref init_allocator -// * @sa @ref glfwInitAllocator -// * -// * @since Added in version 3.4. -// * -// * @ingroup init -// */ -// typedef struct GLFWallocator -// { -// GLFWallocatefun allocate; -// GLFWreallocatefun reallocate; -// GLFWdeallocatefun deallocate; -// void* user; -// } GLFWallocator; - -// /*! @brief The function pointer type for memory allocation callbacks. -// * -// * This is the function pointer type for memory allocation callbacks. A memory -// * allocation callback function has the following signature: -// * @code -// * void* function_name(size_t size, void* user) -// * @endcode -// * -// * This function must return either a memory block at least `size` bytes long, -// * or `NULL` if allocation failed. Note that not all parts of GLFW handle allocation -// * failures gracefully yet. -// * -// * This function may be called during @ref glfwInit but before the library is -// * flagged as initialized, as well as during @ref glfwTerminate after the -// * library is no longer flagged as initialized. -// * -// * Any memory allocated by this function will be deallocated during library -// * termination or earlier. -// * -// * The size will always be greater than zero. Allocations of size zero are filtered out -// * before reaching the custom allocator. -// * -// * @param[in] size The minimum size, in bytes, of the memory block. -// * @param[in] user The user-defined pointer from the allocator. -// * @return The address of the newly allocated memory block, or `NULL` if an -// * error occurred. -// * -// * @pointer_lifetime The returned memory block must be valid at least until it -// * is deallocated. -// * -// * @reentrancy This function should not call any GLFW function. -// * -// * @thread_safety This function may be called from any thread that calls GLFW functions. -// * -// * @sa @ref init_allocator -// * @sa @ref GLFWallocator -// * -// * @since Added in version 3.4. -// * -// * @ingroup init -// */ -// typedef void* (* GLFWallocatefun)(size_t size, void* user); - -// /*! @brief The function pointer type for memory reallocation callbacks. -// * -// * This is the function pointer type for memory reallocation callbacks. -// * A memory reallocation callback function has the following signature: -// * @code -// * void* function_name(void* block, size_t size, void* user) -// * @endcode -// * -// * This function must return a memory block at least `size` bytes long, or -// * `NULL` if allocation failed. Note that not all parts of GLFW handle allocation -// * failures gracefully yet. -// * -// * This function may be called during @ref glfwInit but before the library is -// * flagged as initialized, as well as during @ref glfwTerminate after the -// * library is no longer flagged as initialized. -// * -// * Any memory allocated by this function will be deallocated during library -// * termination or earlier. -// * -// * The block address will never be `NULL` and the size will always be greater than zero. -// * Reallocations of a block to size zero are converted into deallocations. Reallocations -// * of `NULL` to a non-zero size are converted into regular allocations. -// * -// * @param[in] block The address of the memory block to reallocate. -// * @param[in] size The new minimum size, in bytes, of the memory block. -// * @param[in] user The user-defined pointer from the allocator. -// * @return The address of the newly allocated or resized memory block, or -// * `NULL` if an error occurred. -// * -// * @pointer_lifetime The returned memory block must be valid at least until it -// * is deallocated. -// * -// * @reentrancy This function should not call any GLFW function. -// * -// * @thread_safety This function may be called from any thread that calls GLFW functions. -// * -// * @sa @ref init_allocator -// * @sa @ref GLFWallocator -// * -// * @since Added in version 3.4. -// * -// * @ingroup init -// */ -// typedef void* (* GLFWreallocatefun)(void* block, size_t size, void* user); - -// /*! @brief The function pointer type for memory deallocation callbacks. -// * -// * This is the function pointer type for memory deallocation callbacks. -// * A memory deallocation callback function has the following signature: -// * @code -// * void function_name(void* block, void* user) -// * @endcode -// * -// * This function may deallocate the specified memory block. This memory block -// * will have been allocated with the same allocator. -// * -// * This function may be called during @ref glfwInit but before the library is -// * flagged as initialized, as well as during @ref glfwTerminate after the -// * library is no longer flagged as initialized. -// * -// * The block address will never be `NULL`. Deallocations of `NULL` are filtered out -// * before reaching the custom allocator. -// * -// * @param[in] block The address of the memory block to deallocate. -// * @param[in] user The user-defined pointer from the allocator. -// * -// * @pointer_lifetime The specified memory block will not be accessed by GLFW -// * after this function is called. -// * -// * @reentrancy This function should not call any GLFW function. -// * -// * @thread_safety This function may be called from any thread that calls GLFW functions. -// * -// * @sa @ref init_allocator -// * @sa @ref GLFWallocator -// * -// * @since Added in version 3.4. -// * -// * @ingroup init -// */ -// typedef void (* GLFWdeallocatefun)(void* block, void* user); diff --git a/pkg/glfw/build.zig b/pkg/glfw/build.zig deleted file mode 100644 index 142a558da..000000000 --- a/pkg/glfw/build.zig +++ /dev/null @@ -1,271 +0,0 @@ -const std = @import("std"); -const apple_sdk = @import("apple_sdk"); - -pub fn build(b: *std.Build) !void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - - const module = b.addModule("glfw", .{ - .root_source_file = b.path("main.zig"), - .target = target, - .optimize = optimize, - }); - - const lib = try buildLib(b, module, .{ - .target = target, - .optimize = optimize, - }); - - const test_exe: ?*std.Build.Step.Compile = if (target.query.isNative()) exe: { - const exe = b.addTest(.{ - .name = "test", - .root_source_file = b.path("main.zig"), - .target = target, - .optimize = optimize, - }); - if (target.result.os.tag.isDarwin()) { - try apple_sdk.addPaths(b, exe); - } - - const tests_run = b.addRunArtifact(exe); - const test_step = b.step("test", "Run tests"); - test_step.dependOn(&tests_run.step); - - // Uncomment this if we're debugging tests - b.installArtifact(exe); - - break :exe exe; - } else null; - - if (b.systemIntegrationOption("glfw3", .{})) { - module.linkSystemLibrary("glfw3", dynamic_link_opts); - if (test_exe) |exe| exe.linkSystemLibrary2("glfw3", dynamic_link_opts); - } else { - module.linkLibrary(lib); - b.installArtifact(lib); - if (test_exe) |exe| exe.linkLibrary(lib); - } -} - -fn buildLib( - b: *std.Build, - module: *std.Build.Module, - options: anytype, -) !*std.Build.Step.Compile { - const target = options.target; - const optimize = options.optimize; - - const use_x11 = b.option( - bool, - "x11", - "Build with X11. Only useful on Linux", - ) orelse true; - const use_wl = b.option( - bool, - "wayland", - "Build with Wayland. Only useful on Linux", - ) orelse true; - - const use_opengl = b.option( - bool, - "opengl", - "Build with OpenGL; deprecated on MacOS", - ) orelse false; - const use_gles = b.option( - bool, - "gles", - "Build with GLES; not supported on MacOS", - ) orelse false; - const use_metal = b.option( - bool, - "metal", - "Build with Metal; only supported on MacOS", - ) orelse true; - - const lib = b.addStaticLibrary(.{ - .name = "glfw", - .target = target, - .optimize = optimize, - }); - lib.linkLibC(); - - const upstream = b.lazyDependency("glfw", .{}) orelse return lib; - lib.addIncludePath(upstream.path("include")); - module.addIncludePath(upstream.path("include")); - lib.installHeadersDirectory(upstream.path("include/GLFW"), "GLFW", .{}); - - switch (target.result.os.tag) { - .windows => { - lib.linkSystemLibrary("gdi32"); - lib.linkSystemLibrary("user32"); - lib.linkSystemLibrary("shell32"); - - if (use_opengl) { - lib.linkSystemLibrary("opengl32"); - } - - if (use_gles) { - lib.linkSystemLibrary("GLESv3"); - } - - const flags = [_][]const u8{"-D_GLFW_WIN32"}; - lib.addCSourceFiles(.{ - .root = upstream.path(""), - .files = &base_sources, - .flags = &flags, - }); - lib.addCSourceFiles(.{ - .root = upstream.path(""), - .files = &windows_sources, - .flags = &flags, - }); - }, - - .macos => { - try apple_sdk.addPaths(b, lib); - - // Transitive dependencies, explicit linkage of these works around - // ziglang/zig#17130 - lib.linkFramework("CFNetwork"); - lib.linkFramework("ApplicationServices"); - lib.linkFramework("ColorSync"); - lib.linkFramework("CoreText"); - lib.linkFramework("ImageIO"); - - // Direct dependencies - lib.linkSystemLibrary("objc"); - lib.linkFramework("IOKit"); - lib.linkFramework("CoreFoundation"); - lib.linkFramework("AppKit"); - lib.linkFramework("CoreServices"); - lib.linkFramework("CoreGraphics"); - lib.linkFramework("Foundation"); - - if (use_metal) { - lib.linkFramework("Metal"); - } - - if (use_opengl) { - lib.linkFramework("OpenGL"); - } - - const flags = [_][]const u8{"-D_GLFW_COCOA"}; - lib.addCSourceFiles(.{ - .root = upstream.path(""), - .files = &base_sources, - .flags = &flags, - }); - lib.addCSourceFiles(.{ - .root = upstream.path(""), - .files = &macos_sources, - .flags = &flags, - }); - }, - - // everything that isn't windows or mac is linux :P - else => { - var sources = std.BoundedArray([]const u8, 64).init(0) catch unreachable; - var flags = std.BoundedArray([]const u8, 16).init(0) catch unreachable; - - sources.appendSlice(&base_sources) catch unreachable; - sources.appendSlice(&linux_sources) catch unreachable; - - if (use_x11) { - lib.linkSystemLibrary2("X11", dynamic_link_opts); - lib.linkSystemLibrary2("xkbcommon", dynamic_link_opts); - sources.appendSlice(&linux_x11_sources) catch unreachable; - flags.append("-D_GLFW_X11") catch unreachable; - } - - if (use_wl) { - lib.linkSystemLibrary2("wayland-client", dynamic_link_opts); - - lib.root_module.addCMacro("WL_MARSHAL_FLAG_DESTROY", "1"); - lib.addIncludePath(b.path("wayland-headers")); - - sources.appendSlice(&linux_wl_sources) catch unreachable; - flags.append("-D_GLFW_WAYLAND") catch unreachable; - flags.append("-Wno-implicit-function-declaration") catch unreachable; - } - - lib.addCSourceFiles(.{ - .root = upstream.path(""), - .files = sources.slice(), - .flags = flags.slice(), - }); - }, - } - - return lib; -} - -// For dynamic linking, we prefer dynamic linking and to search by -// mode first. Mode first will search all paths for a dynamic library -// before falling back to static. -const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{ - .preferred_link_mode = .dynamic, - .search_strategy = .mode_first, -}; - -const base_sources = [_][]const u8{ - "src/context.c", - "src/egl_context.c", - "src/init.c", - "src/input.c", - "src/monitor.c", - "src/null_init.c", - "src/null_joystick.c", - "src/null_monitor.c", - "src/null_window.c", - "src/osmesa_context.c", - "src/platform.c", - "src/vulkan.c", - "src/window.c", -}; - -const linux_sources = [_][]const u8{ - "src/linux_joystick.c", - "src/posix_module.c", - "src/posix_poll.c", - "src/posix_thread.c", - "src/posix_time.c", - "src/xkb_unicode.c", -}; - -const linux_wl_sources = [_][]const u8{ - "src/wl_init.c", - "src/wl_monitor.c", - "src/wl_window.c", -}; - -const linux_x11_sources = [_][]const u8{ - "src/glx_context.c", - "src/x11_init.c", - "src/x11_monitor.c", - "src/x11_window.c", -}; - -const windows_sources = [_][]const u8{ - "src/wgl_context.c", - "src/win32_init.c", - "src/win32_joystick.c", - "src/win32_module.c", - "src/win32_monitor.c", - "src/win32_thread.c", - "src/win32_time.c", - "src/win32_window.c", -}; - -const macos_sources = [_][]const u8{ - // C sources - "src/cocoa_time.c", - "src/posix_module.c", - "src/posix_thread.c", - - // ObjC sources - "src/cocoa_init.m", - "src/cocoa_joystick.m", - "src/cocoa_monitor.m", - "src/cocoa_window.m", - "src/nsgl_context.m", -}; diff --git a/pkg/glfw/build.zig.zon b/pkg/glfw/build.zig.zon deleted file mode 100644 index 467f2c327..000000000 --- a/pkg/glfw/build.zig.zon +++ /dev/null @@ -1,15 +0,0 @@ -.{ - .name = .glfw, - .version = "3.4.0", - .fingerprint = 0x3bbe0a5c667e2c62, - .paths = .{""}, - .dependencies = .{ - .glfw = .{ - .url = "https://github.com/glfw/glfw/archive/e7ea71be039836da3a98cea55ae5569cb5eb885c.tar.gz", - .hash = "N-V-__8AAMrJSwAUGb9-vTzkNR-5LXS81MR__ZRVfF3tWgG6", - .lazy = true, - }, - - .apple_sdk = .{ .path = "../apple-sdk" }, - }, -} diff --git a/pkg/glfw/c.zig b/pkg/glfw/c.zig deleted file mode 100644 index 58599025b..000000000 --- a/pkg/glfw/c.zig +++ /dev/null @@ -1,6 +0,0 @@ -pub const c = @cImport({ - // Must be uncommented for vulkan.zig to work - // @cDefine("GLFW_INCLUDE_VULKAN", "1"); - @cDefine("GLFW_INCLUDE_NONE", "1"); - @cInclude("GLFW/glfw3.h"); -}); diff --git a/pkg/glfw/clipboard.zig b/pkg/glfw/clipboard.zig deleted file mode 100644 index a7e2d0e2f..000000000 --- a/pkg/glfw/clipboard.zig +++ /dev/null @@ -1,71 +0,0 @@ -const std = @import("std"); - -const c = @import("c.zig").c; - -const internal_debug = @import("internal_debug.zig"); - -/// Sets the clipboard to the specified string. -/// -/// This function sets the system clipboard to the specified, UTF-8 encoded string. -/// -/// @param[in] string A UTF-8 encoded string. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @pointer_lifetime The specified string is copied before this function returns. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: clipboard, glfwGetClipboardString -pub inline fn setClipboardString(value: [*:0]const u8) void { - internal_debug.assertInitialized(); - c.glfwSetClipboardString(null, value); -} - -/// Returns the contents of the clipboard as a string. -/// -/// This function returns the contents of the system clipboard, if it contains or is convertible to -/// a UTF-8 encoded string. If the clipboard is empty or if its contents cannot be converted, -/// glfw.ErrorCode.FormatUnavailable is returned. -/// -/// @return The contents of the clipboard as a UTF-8 encoded string. -/// -/// Possible errors include glfw.ErrorCode.FormatUnavailable and glfw.ErrorCode.PlatformError. -/// null is returned in the event of an error. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is valid until the next call to glfw.getClipboardString or glfw.setClipboardString -/// or until the library is terminated. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: clipboard, glfwSetClipboardString -pub inline fn getClipboardString() ?[:0]const u8 { - internal_debug.assertInitialized(); - if (c.glfwGetClipboardString(null)) |c_str| return std.mem.span(@as([*:0]const u8, @ptrCast(c_str))); - return null; -} - -test "setClipboardString" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - glfw.setClipboardString("hello mach"); -} - -test "getClipboardString" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.getClipboardString(); -} diff --git a/pkg/glfw/errors.zig b/pkg/glfw/errors.zig deleted file mode 100644 index b9721fd05..000000000 --- a/pkg/glfw/errors.zig +++ /dev/null @@ -1,338 +0,0 @@ -//! Errors - -const testing = @import("std").testing; -const mem = @import("std").mem; -const c = @import("c.zig").c; - -/// Errors that GLFW can produce. -pub const ErrorCode = error{ - /// GLFW has not been initialized. - /// - /// This occurs if a GLFW function was called that must not be called unless the library is - /// initialized. - NotInitialized, - - /// No context is current for this thread. - /// - /// This occurs if a GLFW function was called that needs and operates on the current OpenGL or - /// OpenGL ES context but no context is current on the calling thread. One such function is - /// glfw.SwapInterval. - NoCurrentContext, - - /// One of the arguments to the function was an invalid enum value. - /// - /// One of the arguments to the function was an invalid enum value, for example requesting - /// glfw.red_bits with glfw.getWindowAttrib. - InvalidEnum, - - /// One of the arguments to the function was an invalid value. - /// - /// One of the arguments to the function was an invalid value, for example requesting a - /// non-existent OpenGL or OpenGL ES version like 2.7. - /// - /// Requesting a valid but unavailable OpenGL or OpenGL ES version will instead result in a - /// glfw.ErrorCode.VersionUnavailable error. - InvalidValue, - - /// A memory allocation failed. - OutOfMemory, - - /// GLFW could not find support for the requested API on the system. - /// - /// The installed graphics driver does not support the requested API, or does not support it - /// via the chosen context creation API. Below are a few examples. - /// - /// Some pre-installed Windows graphics drivers do not support OpenGL. AMD only supports - /// OpenGL ES via EGL, while Nvidia and Intel only support it via a WGL or GLX extension. macOS - /// does not provide OpenGL ES at all. The Mesa EGL, OpenGL and OpenGL ES libraries do not - /// interface with the Nvidia binary driver. Older graphics drivers do not support Vulkan. - APIUnavailable, - - /// The requested OpenGL or OpenGL ES version (including any requested context or framebuffer - /// hints) is not available on this machine. - /// - /// The machine does not support your requirements. If your application is sufficiently - /// flexible, downgrade your requirements and try again. Otherwise, inform the user that their - /// machine does not match your requirements. - /// - /// Future invalid OpenGL and OpenGL ES versions, for example OpenGL 4.8 if 5.0 comes out - /// before the 4.x series gets that far, also fail with this error and not glfw.ErrorCode.InvalidValue, - /// because GLFW cannot know what future versions will exist. - VersionUnavailable, - - /// A platform-specific error occurred that does not match any of the more specific categories. - /// - /// A bug or configuration error in GLFW, the underlying operating system or its drivers, or a - /// lack of required resources. Report the issue to our [issue tracker](https://github.com/glfw/glfw/issues). - PlatformError, - - /// The requested format is not supported or available. - /// - /// If emitted during window creation, the requested pixel format is not supported. - /// - /// If emitted when querying the clipboard, the contents of the clipboard could not be - /// converted to the requested format. - /// - /// If emitted during window creation, one or more hard constraints did not match any of the - /// available pixel formats. If your application is sufficiently flexible, downgrade your - /// requirements and try again. Otherwise, inform the user that their machine does not match - /// your requirements. - /// - /// If emitted when querying the clipboard, ignore the error or report it to the user, as - /// appropriate. - FormatUnavailable, - - /// The specified window does not have an OpenGL or OpenGL ES context. - /// - /// A window that does not have an OpenGL or OpenGL ES context was passed to a function that - /// requires it to have one. - NoWindowContext, - - /// The specified cursor shape is not available. - /// - /// The specified standard cursor shape is not available, either because the - /// current platform cursor theme does not provide it or because it is not - /// available on the platform. - /// - /// analysis: Platform or system settings limitation. Pick another standard cursor shape or - /// create a custom cursor. - CursorUnavailable, - - /// The requested feature is not provided by the platform. - /// - /// The requested feature is not provided by the platform, so GLFW is unable to - /// implement it. The documentation for each function notes if it could emit - /// this error. - /// - /// analysis: Platform or platform version limitation. The error can be ignored - /// unless the feature is critical to the application. - /// - /// A function call that emits this error has no effect other than the error and - /// updating any existing out parameters. - /// - FeatureUnavailable, - - /// The requested feature is not implemented for the platform. - /// - /// The requested feature has not yet been implemented in GLFW for this platform. - /// - /// analysis: An incomplete implementation of GLFW for this platform, hopefully - /// fixed in a future release. The error can be ignored unless the feature is - /// critical to the application. - /// - /// A function call that emits this error has no effect other than the error and - /// updating any existing out parameters. - /// - FeatureUnimplemented, - - /// Platform unavailable or no matching platform was found. - /// - /// If emitted during initialization, no matching platform was found. If glfw.InitHint.platform - /// is set to `.any_platform`, GLFW could not detect any of the platforms supported by this - /// library binary, except for the Null platform. If set to a specific platform, it is either - /// not supported by this library binary or GLFW was not able to detect it. - /// - /// If emitted by a native access function, GLFW was initialized for a different platform - /// than the function is for. - /// - /// analysis: Failure to detect any platform usually only happens on non-macOS Unix - /// systems, either when no window system is running or the program was run from - /// a terminal that does not have the necessary environment variables. Fall back to - /// a different platform if possible or notify the user that no usable platform was - /// detected. - /// - /// Failure to detect a specific platform may have the same cause as above or be because - /// support for that platform was not compiled in. Call glfw.platformSupported to - /// check whether a specific platform is supported by a library binary. - /// - PlatformUnavailable, -}; - -/// An error produced by GLFW and the description associated with it. -pub const Error = struct { - error_code: ErrorCode, - description: [:0]const u8, -}; - -fn convertError(e: c_int) ErrorCode!void { - return switch (e) { - c.GLFW_NO_ERROR => {}, - c.GLFW_NOT_INITIALIZED => ErrorCode.NotInitialized, - c.GLFW_NO_CURRENT_CONTEXT => ErrorCode.NoCurrentContext, - c.GLFW_INVALID_ENUM => ErrorCode.InvalidEnum, - c.GLFW_INVALID_VALUE => ErrorCode.InvalidValue, - c.GLFW_OUT_OF_MEMORY => ErrorCode.OutOfMemory, - c.GLFW_API_UNAVAILABLE => ErrorCode.APIUnavailable, - c.GLFW_VERSION_UNAVAILABLE => ErrorCode.VersionUnavailable, - c.GLFW_PLATFORM_ERROR => ErrorCode.PlatformError, - c.GLFW_FORMAT_UNAVAILABLE => ErrorCode.FormatUnavailable, - c.GLFW_NO_WINDOW_CONTEXT => ErrorCode.NoWindowContext, - c.GLFW_CURSOR_UNAVAILABLE => ErrorCode.CursorUnavailable, - c.GLFW_FEATURE_UNAVAILABLE => ErrorCode.FeatureUnavailable, - c.GLFW_FEATURE_UNIMPLEMENTED => ErrorCode.FeatureUnimplemented, - c.GLFW_PLATFORM_UNAVAILABLE => ErrorCode.PlatformUnavailable, - else => unreachable, - }; -} - -/// Clears the last error and the error description pointer for the calling thread. Does nothing if -/// no error has occurred since the last call. -/// -/// @remark This function may be called before @ref glfwInit. -/// -/// @thread_safety This function may be called from any thread. -pub inline fn clearError() void { - _ = c.glfwGetError(null); -} - -/// Returns and clears the last error for the calling thread. -/// -/// This function returns and clears the error code of the last error that occurred on the calling -/// thread, along with a UTF-8 encoded human-readable description of it. If no error has occurred -/// since the last call, it returns GLFW_NO_ERROR (zero) and the description pointer is set to -/// `NULL`. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is guaranteed to be valid only until the next error occurs or the library is -/// terminated. -/// -/// @remark This function may be called before @ref glfwInit. -/// -/// @thread_safety This function may be called from any thread. -pub inline fn getError() ?Error { - var desc: [*c]const u8 = null; - convertError(c.glfwGetError(&desc)) catch |error_code| { - return .{ - .error_code = error_code, - .description = mem.sliceTo(desc, 0), - }; - }; - return null; -} - -pub inline fn mustGetError() Error { - return getError() orelse { - @panic("glfw: mustGetError called but no error is present"); - }; -} - -/// Returns and clears the last error for the calling thread. -/// -/// This function returns and clears the error code of the last error that occurred on the calling -/// thread. If no error has occurred since the last call, it returns GLFW_NO_ERROR (zero). -/// -/// @return The last error code for the calling thread, or @ref GLFW_NO_ERROR (zero). -/// -/// @remark This function may be called before @ref glfwInit. -/// -/// @thread_safety This function may be called from any thread. -pub inline fn getErrorCode() ErrorCode!void { - return convertError(c.glfwGetError(null)); -} - -/// Returns and clears the last error code for the calling thread. If no error is present, this -/// function panics. -pub inline fn mustGetErrorCode() ErrorCode { - try getErrorCode(); - @panic("glfw: mustGetErrorCode called but no error is present"); -} - -/// Returns and clears the last error description for the calling thread. -/// -/// This function returns a UTF-8 encoded human-readable description of the last error that occured -/// on the calling thread. If no error has occurred since the last call, it returns null. -/// -/// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it -/// yourself. It is guaranteed to be valid only until the next error occurs or the library is -/// terminated. -/// -/// @remark This function may be called before @ref glfwInit. -/// -/// @thread_safety This function may be called from any thread. -pub inline fn getErrorString() ?[:0]const u8 { - var desc: [*c]const u8 = null; - const error_code = c.glfwGetError(&desc); - if (error_code != c.GLFW_NO_ERROR) { - return mem.sliceTo(desc, 0); - } - return null; -} - -/// Returns and clears the last error description for the calling thread. If no error is present, -/// this function panics. -pub inline fn mustGetErrorString() [:0]const u8 { - return getErrorString() orelse { - @panic("glfw: mustGetErrorString called but no error is present"); - }; -} - -/// Sets the error callback. -/// -/// This function sets the error callback, which is called with an error code -/// and a human-readable description each time a GLFW error occurs. -/// -/// The error code is set before the callback is called. Calling @ref -/// glfwGetError from the error callback will return the same value as the error -/// code argument. -/// -/// The error callback is called on the thread where the error occurred. If you -/// are using GLFW from multiple threads, your error callback needs to be -/// written accordingly. -/// -/// Because the description string may have been generated specifically for that -/// error, it is not guaranteed to be valid after the callback has returned. If -/// you wish to use it after the callback returns, you need to make a copy. -/// -/// Once set, the error callback remains set even after the library has been -/// terminated. -/// -/// @param[in] callback The new callback, or `NULL` to remove the currently set -/// callback. -/// -/// @callback_param `error_code` An error code. Future releases may add more error codes. -/// @callback_param `description` A UTF-8 encoded string describing the error. -/// -/// @errors None. -/// -/// @remark This function may be called before @ref glfwInit. -/// -/// @thread_safety This function must only be called from the main thread. -pub fn setErrorCallback(comptime callback: ?fn (error_code: ErrorCode, description: [:0]const u8) void) void { - if (callback) |user_callback| { - const CWrapper = struct { - pub fn errorCallbackWrapper(err_int: c_int, c_description: [*c]const u8) callconv(.c) void { - convertError(err_int) catch |error_code| { - user_callback(error_code, mem.sliceTo(c_description, 0)); - }; - } - }; - - _ = c.glfwSetErrorCallback(CWrapper.errorCallbackWrapper); - return; - } - - _ = c.glfwSetErrorCallback(null); -} - -test "set error callback" { - const TestStruct = struct { - pub fn callback(_: ErrorCode, _: [:0]const u8) void {} - }; - setErrorCallback(TestStruct.callback); -} - -test "error string" { - try testing.expect(getErrorString() == null); -} - -test "error code" { - try getErrorCode(); -} - -test "error code and string" { - try testing.expect(getError() == null); -} - -test "clear error" { - clearError(); -} diff --git a/pkg/glfw/gamepad_axis.zig b/pkg/glfw/gamepad_axis.zig deleted file mode 100644 index 97fdf3748..000000000 --- a/pkg/glfw/gamepad_axis.zig +++ /dev/null @@ -1,16 +0,0 @@ -const c = @import("c.zig").c; - -/// Gamepad axes. -/// -/// See glfw.getGamepadState for how these are used. -pub const GamepadAxis = enum(c_int) { - left_x = c.GLFW_GAMEPAD_AXIS_LEFT_X, - left_y = c.GLFW_GAMEPAD_AXIS_LEFT_Y, - right_x = c.GLFW_GAMEPAD_AXIS_RIGHT_X, - right_y = c.GLFW_GAMEPAD_AXIS_RIGHT_Y, - left_trigger = c.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, - right_trigger = c.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, -}; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const last = GamepadAxis.right_trigger; diff --git a/pkg/glfw/gamepad_button.zig b/pkg/glfw/gamepad_button.zig deleted file mode 100644 index ac47ebea0..000000000 --- a/pkg/glfw/gamepad_button.zig +++ /dev/null @@ -1,37 +0,0 @@ -const c = @import("c.zig").c; - -/// Gamepad buttons. -/// -/// See glfw.getGamepadState for how these are used. -pub const GamepadButton = enum(c_int) { - a = c.GLFW_GAMEPAD_BUTTON_A, - b = c.GLFW_GAMEPAD_BUTTON_B, - x = c.GLFW_GAMEPAD_BUTTON_X, - y = c.GLFW_GAMEPAD_BUTTON_Y, - left_bumper = c.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, - right_bumper = c.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, - back = c.GLFW_GAMEPAD_BUTTON_BACK, - start = c.GLFW_GAMEPAD_BUTTON_START, - guide = c.GLFW_GAMEPAD_BUTTON_GUIDE, - left_thumb = c.GLFW_GAMEPAD_BUTTON_LEFT_THUMB, - right_thumb = c.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB, - dpad_up = c.GLFW_GAMEPAD_BUTTON_DPAD_UP, - dpad_right = c.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT, - dpad_down = c.GLFW_GAMEPAD_BUTTON_DPAD_DOWN, - dpad_left = c.GLFW_GAMEPAD_BUTTON_DPAD_LEFT, -}; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const last = GamepadButton.dpad_left; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const cross = GamepadButton.a; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const circle = GamepadButton.b; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const square = GamepadButton.x; - -/// Not in the GamepadAxis enumeration as it is a duplicate value which is forbidden. -pub const triangle = GamepadButton.y; diff --git a/pkg/glfw/hat.zig b/pkg/glfw/hat.zig deleted file mode 100644 index ffbb4a1c8..000000000 --- a/pkg/glfw/hat.zig +++ /dev/null @@ -1,100 +0,0 @@ -const c = @import("c.zig").c; - -// must be in sync with GLFW C constants in hat state group, search for "@defgroup hat_state Joystick hat states" -/// A bitmask of all Joystick hat states -/// -/// See glfw.Joystick.getHats for how these are used. -pub const Hat = packed struct(u8) { - up: bool = false, - right: bool = false, - down: bool = false, - left: bool = false, - _padding: u4 = 0, - - pub inline fn centered(self: Hat) bool { - return self.up == false and self.right == false and self.down == false and self.left == false; - } - - inline fn verifyIntType(comptime IntType: type) void { - comptime { - switch (@import("shims.zig").typeInfo(IntType)) { - .int => {}, - else => @compileError("Int was not of int type"), - } - } - } - - pub inline fn toInt(self: Hat, comptime IntType: type) IntType { - verifyIntType(IntType); - return @as(IntType, @intCast(@as(u8, @bitCast(self)))); - } - - pub inline fn fromInt(flags: anytype) Hat { - verifyIntType(@TypeOf(flags)); - return @as(Hat, @bitCast(@as(u8, @intCast(flags)))); - } -}; - -/// Holds all GLFW hat values in their raw form. -pub const RawHat = struct { - pub const centered = c.GLFW_HAT_CENTERED; - pub const up = c.GLFW_HAT_UP; - pub const right = c.GLFW_HAT_RIGHT; - pub const down = c.GLFW_HAT_DOWN; - pub const left = c.GLFW_HAT_LEFT; - - pub const right_up = right | up; - pub const right_down = right | down; - pub const left_up = left | up; - pub const left_down = left | down; -}; - -test "from int, single" { - const std = @import("std"); - - try std.testing.expectEqual(Hat{ - .up = true, - .right = false, - .down = false, - .left = false, - ._padding = 0, - }, Hat.fromInt(RawHat.up)); -} - -test "from int, multi" { - const std = @import("std"); - - try std.testing.expectEqual(Hat{ - .up = true, - .right = false, - .down = true, - .left = true, - ._padding = 0, - }, Hat.fromInt(RawHat.up | RawHat.down | RawHat.left)); -} - -test "to int, single" { - const std = @import("std"); - - var v = Hat{ - .up = true, - .right = false, - .down = false, - .left = false, - ._padding = 0, - }; - try std.testing.expectEqual(v.toInt(c_int), RawHat.up); -} - -test "to int, multi" { - const std = @import("std"); - - var v = Hat{ - .up = true, - .right = false, - .down = true, - .left = true, - ._padding = 0, - }; - try std.testing.expectEqual(v.toInt(c_int), RawHat.up | RawHat.down | RawHat.left); -} diff --git a/pkg/glfw/internal_debug.zig b/pkg/glfw/internal_debug.zig deleted file mode 100644 index 6e0ab4f1e..000000000 --- a/pkg/glfw/internal_debug.zig +++ /dev/null @@ -1,14 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const is_debug = builtin.mode == .Debug; -var glfw_initialized = if (is_debug) false else @as(void, {}); -pub inline fn toggleInitialized() void { - if (is_debug) glfw_initialized = !glfw_initialized; -} -pub inline fn assertInitialized() void { - if (is_debug) std.debug.assert(glfw_initialized); -} -pub inline fn assumeInitialized() void { - if (is_debug) glfw_initialized = true; -} diff --git a/pkg/glfw/key.zig b/pkg/glfw/key.zig deleted file mode 100644 index 27b13119c..000000000 --- a/pkg/glfw/key.zig +++ /dev/null @@ -1,266 +0,0 @@ -//! Keyboard key IDs. -//! -//! See glfw.setKeyCallback for how these are used. -//! -//! These key codes are inspired by the _USB HID Usage Tables v1.12_ (p. 53-60), but re-arranged to -//! map to 7-bit ASCII for printable keys (function keys are put in the 256+ range). -//! -//! The naming of the key codes follow these rules: -//! -//! - The US keyboard layout is used -//! - Names of printable alphanumeric characters are used (e.g. "a", "r", "three", etc.) -//! - For non-alphanumeric characters, Unicode:ish names are used (e.g. "comma", "left_bracket", -//! etc.). Note that some names do not correspond to the Unicode standard (usually for brevity) -//! - Keys that lack a clear US mapping are named "world_x" -//! - For non-printable keys, custom names are used (e.g. "F4", "backspace", etc.) - -const std = @import("std"); - -const cc = @import("c.zig").c; - -const internal_debug = @import("internal_debug.zig"); - -/// enum containing all glfw keys -pub const Key = enum(c_int) { - /// The unknown key - unknown = cc.GLFW_KEY_UNKNOWN, - - /// Printable keys - space = cc.GLFW_KEY_SPACE, - apostrophe = cc.GLFW_KEY_APOSTROPHE, - comma = cc.GLFW_KEY_COMMA, - minus = cc.GLFW_KEY_MINUS, - period = cc.GLFW_KEY_PERIOD, - slash = cc.GLFW_KEY_SLASH, - zero = cc.GLFW_KEY_0, - one = cc.GLFW_KEY_1, - two = cc.GLFW_KEY_2, - three = cc.GLFW_KEY_3, - four = cc.GLFW_KEY_4, - five = cc.GLFW_KEY_5, - six = cc.GLFW_KEY_6, - seven = cc.GLFW_KEY_7, - eight = cc.GLFW_KEY_8, - nine = cc.GLFW_KEY_9, - semicolon = cc.GLFW_KEY_SEMICOLON, - equal = cc.GLFW_KEY_EQUAL, - a = cc.GLFW_KEY_A, - b = cc.GLFW_KEY_B, - c = cc.GLFW_KEY_C, - d = cc.GLFW_KEY_D, - e = cc.GLFW_KEY_E, - f = cc.GLFW_KEY_F, - g = cc.GLFW_KEY_G, - h = cc.GLFW_KEY_H, - i = cc.GLFW_KEY_I, - j = cc.GLFW_KEY_J, - k = cc.GLFW_KEY_K, - l = cc.GLFW_KEY_L, - m = cc.GLFW_KEY_M, - n = cc.GLFW_KEY_N, - o = cc.GLFW_KEY_O, - p = cc.GLFW_KEY_P, - q = cc.GLFW_KEY_Q, - r = cc.GLFW_KEY_R, - s = cc.GLFW_KEY_S, - t = cc.GLFW_KEY_T, - u = cc.GLFW_KEY_U, - v = cc.GLFW_KEY_V, - w = cc.GLFW_KEY_W, - x = cc.GLFW_KEY_X, - y = cc.GLFW_KEY_Y, - z = cc.GLFW_KEY_Z, - left_bracket = cc.GLFW_KEY_LEFT_BRACKET, - backslash = cc.GLFW_KEY_BACKSLASH, - right_bracket = cc.GLFW_KEY_RIGHT_BRACKET, - grave_accent = cc.GLFW_KEY_GRAVE_ACCENT, - world_1 = cc.GLFW_KEY_WORLD_1, // non-US #1 - world_2 = cc.GLFW_KEY_WORLD_2, // non-US #2 - - // Function keys - escape = cc.GLFW_KEY_ESCAPE, - enter = cc.GLFW_KEY_ENTER, - tab = cc.GLFW_KEY_TAB, - backspace = cc.GLFW_KEY_BACKSPACE, - insert = cc.GLFW_KEY_INSERT, - delete = cc.GLFW_KEY_DELETE, - right = cc.GLFW_KEY_RIGHT, - left = cc.GLFW_KEY_LEFT, - down = cc.GLFW_KEY_DOWN, - up = cc.GLFW_KEY_UP, - page_up = cc.GLFW_KEY_PAGE_UP, - page_down = cc.GLFW_KEY_PAGE_DOWN, - home = cc.GLFW_KEY_HOME, - end = cc.GLFW_KEY_END, - caps_lock = cc.GLFW_KEY_CAPS_LOCK, - scroll_lock = cc.GLFW_KEY_SCROLL_LOCK, - num_lock = cc.GLFW_KEY_NUM_LOCK, - print_screen = cc.GLFW_KEY_PRINT_SCREEN, - pause = cc.GLFW_KEY_PAUSE, - F1 = cc.GLFW_KEY_F1, - F2 = cc.GLFW_KEY_F2, - F3 = cc.GLFW_KEY_F3, - F4 = cc.GLFW_KEY_F4, - F5 = cc.GLFW_KEY_F5, - F6 = cc.GLFW_KEY_F6, - F7 = cc.GLFW_KEY_F7, - F8 = cc.GLFW_KEY_F8, - F9 = cc.GLFW_KEY_F9, - F10 = cc.GLFW_KEY_F10, - F11 = cc.GLFW_KEY_F11, - F12 = cc.GLFW_KEY_F12, - F13 = cc.GLFW_KEY_F13, - F14 = cc.GLFW_KEY_F14, - F15 = cc.GLFW_KEY_F15, - F16 = cc.GLFW_KEY_F16, - F17 = cc.GLFW_KEY_F17, - F18 = cc.GLFW_KEY_F18, - F19 = cc.GLFW_KEY_F19, - F20 = cc.GLFW_KEY_F20, - F21 = cc.GLFW_KEY_F21, - F22 = cc.GLFW_KEY_F22, - F23 = cc.GLFW_KEY_F23, - F24 = cc.GLFW_KEY_F24, - F25 = cc.GLFW_KEY_F25, - kp_0 = cc.GLFW_KEY_KP_0, - kp_1 = cc.GLFW_KEY_KP_1, - kp_2 = cc.GLFW_KEY_KP_2, - kp_3 = cc.GLFW_KEY_KP_3, - kp_4 = cc.GLFW_KEY_KP_4, - kp_5 = cc.GLFW_KEY_KP_5, - kp_6 = cc.GLFW_KEY_KP_6, - kp_7 = cc.GLFW_KEY_KP_7, - kp_8 = cc.GLFW_KEY_KP_8, - kp_9 = cc.GLFW_KEY_KP_9, - kp_decimal = cc.GLFW_KEY_KP_DECIMAL, - kp_divide = cc.GLFW_KEY_KP_DIVIDE, - kp_multiply = cc.GLFW_KEY_KP_MULTIPLY, - kp_subtract = cc.GLFW_KEY_KP_SUBTRACT, - kp_add = cc.GLFW_KEY_KP_ADD, - kp_enter = cc.GLFW_KEY_KP_ENTER, - kp_equal = cc.GLFW_KEY_KP_EQUAL, - left_shift = cc.GLFW_KEY_LEFT_SHIFT, - left_control = cc.GLFW_KEY_LEFT_CONTROL, - left_alt = cc.GLFW_KEY_LEFT_ALT, - left_super = cc.GLFW_KEY_LEFT_SUPER, - right_shift = cc.GLFW_KEY_RIGHT_SHIFT, - right_control = cc.GLFW_KEY_RIGHT_CONTROL, - right_alt = cc.GLFW_KEY_RIGHT_ALT, - right_super = cc.GLFW_KEY_RIGHT_SUPER, - menu = cc.GLFW_KEY_MENU, - - pub inline fn last() Key { - return @as(Key, @enumFromInt(cc.GLFW_KEY_LAST)); - } - - /// Returns the layout-specific name of the specified printable key. - /// - /// This function returns the name of the specified printable key, encoded as UTF-8. This is - /// typically the character that key would produce without any modifier keys, intended for - /// displaying key bindings to the user. For dead keys, it is typically the diacritic it would add - /// to a character. - /// - /// __Do not use this function__ for text input (see input_char). You will break text input for many - /// languages even if it happens to work for yours. - /// - /// If the key is `glfw.key.unknown`, the scancode is used to identify the key, otherwise the - /// scancode is ignored. If you specify a non-printable key, or `glfw.key.unknown` and a scancode - /// that maps to a non-printable key, this function returns null but does not emit an error. - /// - /// This behavior allows you to always pass in the arguments in the key callback (see input_key) - /// without modification. - /// - /// The printable keys are: - /// - /// - `glfw.Key.apostrophe` - /// - `glfw.Key.comma` - /// - `glfw.Key.minus` - /// - `glfw.Key.period` - /// - `glfw.Key.slash` - /// - `glfw.Key.semicolon` - /// - `glfw.Key.equal` - /// - `glfw.Key.left_bracket` - /// - `glfw.Key.right_bracket` - /// - `glfw.Key.backslash` - /// - `glfw.Key.world_1` - /// - `glfw.Key.world_2` - /// - `glfw.Key.0` to `glfw.key.9` - /// - `glfw.Key.a` to `glfw.key.z` - /// - `glfw.Key.kp_0` to `glfw.key.kp_9` - /// - `glfw.Key.kp_decimal` - /// - `glfw.Key.kp_divide` - /// - `glfw.Key.kp_multiply` - /// - `glfw.Key.kp_subtract` - /// - `glfw.Key.kp_add` - /// - `glfw.Key.kp_equal` - /// - /// Names for printable keys depend on keyboard layout, while names for non-printable keys are the - /// same across layouts but depend on the application language and should be localized along with - /// other user interface text. - /// - /// @param[in] key The key to query, or `glfw.key.unknown`. - /// @param[in] scancode The scancode of the key to query. - /// @return The UTF-8 encoded, layout-specific name of the key, or null. - /// - /// Possible errors include glfw.ErrorCode.PlatformError. - /// Also returns null in the event of an error. - /// - /// The contents of the returned string may change when a keyboard layout change event is received. - /// - /// @pointer_lifetime The returned string is allocated and freed by GLFW. You should not free it - /// yourself. It is valid until the library is terminated. - /// - /// @thread_safety This function must only be called from the main thread. - /// - /// see also: input_key_name - pub inline fn getName(self: Key, scancode: i32) ?[:0]const u8 { - internal_debug.assertInitialized(); - const name_opt = cc.glfwGetKeyName(@intFromEnum(self), @as(c_int, @intCast(scancode))); - return if (name_opt) |name| - std.mem.span(@as([*:0]const u8, @ptrCast(name))) - else - null; - } - - /// Returns the platform-specific scancode of the specified key. - /// - /// This function returns the platform-specific scancode of the specified key. - /// - /// If the key is `glfw.key.UNKNOWN` or does not exist on the keyboard this method will return `-1`. - /// - /// @param[in] key Any named key (see keys). - /// @return The platform-specific scancode for the key. - /// - /// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.PlatformError. - /// Additionally returns -1 in the event of an error. - /// - /// @thread_safety This function may be called from any thread. - pub inline fn getScancode(self: Key) i32 { - internal_debug.assertInitialized(); - return cc.glfwGetKeyScancode(@intFromEnum(self)); - } -}; - -test "getName" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.Key.a.getName(0); -} - -test "getScancode" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.Key.a.getScancode(); -} diff --git a/pkg/glfw/main.zig b/pkg/glfw/main.zig deleted file mode 100644 index 329f17ed2..000000000 --- a/pkg/glfw/main.zig +++ /dev/null @@ -1,586 +0,0 @@ -const std = @import("std"); -const testing = std.testing; - -const c = @import("c.zig").c; - -const key = @import("key.zig"); -const errors = @import("errors.zig"); - -/// Possible value for various window hints, etc. -pub const dont_care = c.GLFW_DONT_CARE; - -pub const getError = errors.getError; -pub const mustGetError = errors.mustGetError; -pub const getErrorCode = errors.getErrorCode; -pub const mustGetErrorCode = errors.mustGetErrorCode; -pub const getErrorString = errors.getErrorString; -pub const mustGetErrorString = errors.mustGetErrorString; -pub const setErrorCallback = errors.setErrorCallback; -pub const clearError = errors.clearError; -pub const ErrorCode = errors.ErrorCode; -pub const Error = errors.Error; - -pub const Action = @import("action.zig").Action; -pub const GamepadAxis = @import("gamepad_axis.zig").GamepadAxis; -pub const GamepadButton = @import("gamepad_button.zig").GamepadButton; -pub const gamepad_axis = @import("gamepad_axis.zig"); -pub const gamepad_button = @import("gamepad_button.zig"); -pub const GammaRamp = @import("GammaRamp.zig"); -pub const Image = @import("Image.zig"); -pub const Joystick = @import("Joystick.zig"); -pub const Monitor = @import("Monitor.zig"); -pub const mouse_button = @import("mouse_button.zig"); -pub const MouseButton = mouse_button.MouseButton; -pub const version = @import("version.zig"); -pub const VideoMode = @import("VideoMode.zig"); -pub const Window = @import("Window.zig"); -pub const Cursor = @import("Cursor.zig"); -pub const Native = @import("native.zig").Native; -pub const BackendOptions = @import("native.zig").BackendOptions; -pub const Key = key.Key; -pub const setClipboardString = @import("clipboard.zig").setClipboardString; -pub const getClipboardString = @import("clipboard.zig").getClipboardString; -pub const makeContextCurrent = @import("opengl.zig").makeContextCurrent; -pub const getCurrentContext = @import("opengl.zig").getCurrentContext; -pub const swapInterval = @import("opengl.zig").swapInterval; -pub const extensionSupported = @import("opengl.zig").extensionSupported; -pub const GLProc = @import("opengl.zig").GLProc; -pub const getProcAddress = @import("opengl.zig").getProcAddress; -pub const getTime = @import("time.zig").getTime; -pub const setTime = @import("time.zig").setTime; -pub const getTimerValue = @import("time.zig").getTimerValue; -pub const getTimerFrequency = @import("time.zig").getTimerFrequency; -pub const Hat = @import("hat.zig").Hat; -pub const RawHat = @import("hat.zig").RawHat; -pub const Mods = @import("mod.zig").Mods; -pub const RawMods = @import("mod.zig").RawMods; - -const internal_debug = @import("internal_debug.zig"); - -/// If GLFW was already initialized in your program, e.g. you are embedding Zig code into an existing -/// program that has already called glfwInit via the C API for you - then you need to tell mach/glfw -/// that it has in fact been initialized already, otherwise when you call other methods mach/glfw -/// would panic thinking glfw.init has not been called yet. -pub fn assumeInitialized() void { - internal_debug.assumeInitialized(); -} - -/// Initializes the GLFW library. -/// -/// This function initializes the GLFW library. Before most GLFW functions can be used, GLFW must -/// be initialized, and before an application terminates GLFW should be terminated in order to free -/// any resources allocated during or after initialization. -/// -/// If this function fails, it calls glfw.Terminate before returning. If it succeeds, you should -/// call glfw.Terminate before the application exits. -/// -/// Additional calls to this function after successful initialization but before termination will -/// return immediately with no error. -/// -/// The glfw.InitHint.platform init hint controls which platforms are considered during -/// initialization. This also depends on which platforms the library was compiled to support. -/// -/// macos: This function will change the current directory of the application to the -/// `Contents/Resources` subdirectory of the application's bundle, if present. This can be disabled -/// with `glfw.InitHint.cocoa_chdir_resources`. -/// -/// macos: This function will create the main menu and dock icon for the application. If GLFW finds -/// a `MainMenu.nib` it is loaded and assumed to contain a menu bar. Otherwise a minimal menu bar is -/// created manually with common commands like Hide, Quit and About. The About entry opens a minimal -/// about dialog with information from the application's bundle. The menu bar and dock icon can be -/// disabled entirely with `glfw.InitHint.cocoa_menubar`. -/// -/// x11: This function will set the `LC_CTYPE` category of the application locale according to the -/// current environment if that category is still "C". This is because the "C" locale breaks -/// Unicode text input. -/// -/// Possible errors include glfw.ErrorCode.PlatformUnavailable, glfw.ErrorCode.PlatformError. -/// Returns a bool indicating success. -/// -/// @thread_safety This function must only be called from the main thread. -pub inline fn init(hints: InitHints) bool { - internal_debug.toggleInitialized(); - internal_debug.assertInitialized(); - errdefer { - internal_debug.assertInitialized(); - internal_debug.toggleInitialized(); - } - - inline for (comptime std.meta.fieldNames(InitHints)) |field_name| { - const init_hint = @field(InitHint, field_name); - const init_value = @field(hints, field_name); - if (@TypeOf(init_value) == PlatformType) { - initHint(init_hint, @intFromEnum(init_value)); - } else { - initHint(init_hint, init_value); - } - } - - return c.glfwInit() == c.GLFW_TRUE; -} - -// TODO: implement custom allocator support -// -// /*! @brief Sets the init allocator to the desired value. -// * -// * To use the default allocator, call this function with a `NULL` argument. -// * -// * If you specify an allocator struct, every member must be a valid function -// * pointer. If any member is `NULL`, this function emits @ref -// * GLFW_INVALID_VALUE and the init allocator is unchanged. -// * -// * @param[in] allocator The allocator to use at the next initialization, or -// * `NULL` to use the default one. -// * -// * @errors Possible errors include @ref GLFW_INVALID_VALUE. -// * -// * @pointer_lifetime The specified allocator is copied before this function -// * returns. -// * -// * @thread_safety This function must only be called from the main thread. -// * -// * @sa @ref init_allocator -// * @sa @ref glfwInit -// * -// * @since Added in version 3.4. -// * -// * @ingroup init -// */ -// GLFWAPI void glfwInitAllocator(const GLFWallocator* allocator); - -/// Terminates the GLFW library. -/// -/// This function destroys all remaining windows and cursors, restores any modified gamma ramps -/// and frees any other allocated resources. Once this function is called, you must again call -/// glfw.init successfully before you will be able to use most GLFW functions. -/// -/// If GLFW has been successfully initialized, this function should be called before the -/// application exits. If initialization fails, there is no need to call this function, as it is -/// called by glfw.init before it returns failure. -/// -/// This function has no effect if GLFW is not initialized. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// warning: The contexts of any remaining windows must not be current on any other thread when -/// this function is called. -/// -/// reentrancy: This function must not be called from a callback. -/// -/// thread_safety: This function must only be called from the main thread. -pub inline fn terminate() void { - internal_debug.assertInitialized(); - internal_debug.toggleInitialized(); - c.glfwTerminate(); -} - -/// Initialization hints for passing into glfw.init -pub const InitHints = struct { - /// Specifies whether to also expose joystick hats as buttons, for compatibility with earlier - /// versions of GLFW that did not have glfwGetJoystickHats. - joystick_hat_buttons: bool = true, - - /// macOS specific init hint. Ignored on other platforms. - /// - /// Specifies whether to set the current directory to the application to the Contents/Resources - /// subdirectory of the application's bundle, if present. - cocoa_chdir_resources: bool = true, - - /// macOS specific init hint. Ignored on other platforms. - /// - /// specifies whether to create a basic menu bar, either from a nib or manually, when the first - /// window is created, which is when AppKit is initialized. - cocoa_menubar: bool = true, - - /// Platform selection init hint. - /// - /// Possible values are `PlatformType` enums. - platform: PlatformType = .any, -}; - -/// Initialization hints for passing into glfw.initHint -const InitHint = enum(c_int) { - /// Specifies whether to also expose joystick hats as buttons, for compatibility with earlier - /// versions of GLFW that did not have glfwGetJoystickHats. - /// - /// Possible values are `true` and `false`. - joystick_hat_buttons = c.GLFW_JOYSTICK_HAT_BUTTONS, - - /// ANGLE rendering backend init hint. - /// - /// Possible values are `AnglePlatformType` enums. - angle_platform_type = c.GLFW_ANGLE_PLATFORM_TYPE, - - /// Platform selection init hint. - /// - /// Possible values are `PlatformType` enums. - platform = c.GLFW_PLATFORM, - - /// macOS specific init hint. Ignored on other platforms. - /// - /// Specifies whether to set the current directory to the application to the Contents/Resources - /// subdirectory of the application's bundle, if present. - /// - /// Possible values are `true` and `false`. - cocoa_chdir_resources = c.GLFW_COCOA_CHDIR_RESOURCES, - - /// macOS specific init hint. Ignored on other platforms. - /// - /// specifies whether to create a basic menu bar, either from a nib or manually, when the first - /// window is created, which is when AppKit is initialized. - /// - /// Possible values are `true` and `false`. - cocoa_menubar = c.GLFW_COCOA_MENUBAR, - - /// X11 specific init hint. - x11_xcb_vulkan_surface = c.GLFW_X11_XCB_VULKAN_SURFACE, - - /// Wayland specific init hint. - /// - /// Possible values are `WaylandLibdecorInitHint` enums. - wayland_libdecor = c.GLFW_WAYLAND_LIBDECOR, -}; - -/// Angle platform type hints for glfw.InitHint.angle_platform_type -pub const AnglePlatformType = enum(c_int) { - none = c.GLFW_ANGLE_PLATFORM_TYPE_NONE, - opengl = c.GLFW_ANGLE_PLATFORM_TYPE_OPENGL, - opengles = c.GLFW_ANGLE_PLATFORM_TYPE_OPENGLES, - d3d9 = c.GLFW_ANGLE_PLATFORM_TYPE_D3D9, - d3d11 = c.GLFW_ANGLE_PLATFORM_TYPE_D3D11, - vulkan = c.GLFW_ANGLE_PLATFORM_TYPE_VULKAN, - metal = c.GLFW_ANGLE_PLATFORM_TYPE_METAL, -}; - -/// Wayland libdecor hints for glfw.InitHint.wayland_libdecor -/// -/// libdecor is important for GNOME, since GNOME does not implement server side decorations on -/// wayland. libdecor is loaded dynamically at runtime, so in general enabling it is always -/// safe to do. It is enabled by default. -pub const WaylandLibdecorInitHint = enum(c_int) { - wayland_prefer_libdecor = c.GLFW_WAYLAND_PREFER_LIBDECOR, - wayland_disable_libdecor = c.GLFW_WAYLAND_DISABLE_LIBDECOR, -}; - -/// Platform type hints for glfw.InitHint.platform -pub const PlatformType = enum(c_int) { - /// Enables automatic platform detection. - /// Will default to X11 on wayland. - any = c.GLFW_ANY_PLATFORM, - win32 = c.GLFW_PLATFORM_WIN32, - cocoa = c.GLFW_PLATFORM_COCOA, - wayland = c.GLFW_PLATFORM_WAYLAND, - x11 = c.GLFW_PLATFORM_X11, - null = c.GLFW_PLATFORM_NULL, -}; - -/// Sets the specified init hint to the desired value. -/// -/// This function sets hints for the next initialization of GLFW. -/// -/// The values you set hints to are never reset by GLFW, but they only take effect during -/// initialization. Once GLFW has been initialized, any values you set will be ignored until the -/// library is terminated and initialized again. -/// -/// Some hints are platform specific. These may be set on any platform but they will only affect -/// their specific platform. Other platforms will ignore them. Setting these hints requires no -/// platform specific headers or functions. -/// -/// @param hint: The init hint to set. -/// @param value: The new value of the init hint. -/// -/// Possible errors include glfw.ErrorCode.InvalidEnum and glfw.ErrorCode.InvalidValue. -/// -/// @remarks This function may be called before glfw.init. -/// -/// @thread_safety This function must only be called from the main thread. -fn initHint(hint: InitHint, value: anytype) void { - switch (@import("shims.zig").typeInfo(@TypeOf(value))) { - .int, .comptime_int => { - c.glfwInitHint(@intFromEnum(hint), @as(c_int, @intCast(value))); - }, - .bool => c.glfwInitHint(@intFromEnum(hint), @as(c_int, @intCast(@intFromBool(value)))), - else => @compileError("expected a int or bool, got " ++ @typeName(@TypeOf(value))), - } -} - -/// Returns a string describing the compile-time configuration. -/// -/// This function returns the compile-time generated version string of the GLFW library binary. It -/// describes the version, platform, compiler and any platform or operating system specific -/// compile-time options. It should not be confused with the OpenGL or OpenGL ES version string, -/// queried with `glGetString`. -/// -/// __Do not use the version string__ to parse the GLFW library version. Use the glfw.version -/// constants instead. -/// -/// __Do not use the version string__ to parse what platforms are supported. The -/// `glfw.platformSupported` function lets you query platform support. -/// -/// returns: The ASCII encoded GLFW version string. -/// -/// remark: This function may be called before @ref glfw.Init. -/// -/// pointer_lifetime: The returned string is static and compile-time generated. -/// -/// thread_safety: This function may be called from any thread. -pub inline fn getVersionString() [:0]const u8 { - return std.mem.span(@as([*:0]const u8, @ptrCast(c.glfwGetVersionString()))); -} - -/// Returns the currently selected platform. -/// -/// This function returns the platform that was selected during initialization. The returned value -/// will be one of `glfw.PlatformType.win32`, `glfw.PlatformType.cocoa`, -/// `glfw.PlatformType.wayland`, `glfw.PlatformType.x11` or `glfw.PlatformType.null`. -/// -/// thread_safety: This function may be called from any thread. -pub fn getPlatform() PlatformType { - internal_debug.assertInitialized(); - return @as(PlatformType, @enumFromInt(c.glfwGetPlatform())); -} - -/// Returns whether the library includes support for the specified platform. -/// -/// This function returns whether the library was compiled with support for the specified platform. -/// The platform must be one of `glfw.PlatformType.win32`, `glfw.PlatformType.cocoa`, -/// `glfw.PlatformType.wayland`, `glfw.PlatformType.x11` or `glfw.PlatformType.null`. -/// -/// remark: This function may be called before glfw.Init. -/// -/// thread_safety: This function may be called from any thread. -pub fn platformSupported(platform: PlatformType) bool { - internal_debug.assertInitialized(); - return c.glfwPlatformSupported(@intFromEnum(platform)) == c.GLFW_TRUE; -} - -/// Processes all pending events. -/// -/// This function processes only those events that are already in the event queue and then returns -/// immediately. Processing events will cause the window and input callbacks associated with those -/// events to be called. -/// -/// On some platforms, a window move, resize or menu operation will cause event processing to -/// block. This is due to how event processing is designed on those platforms. You can use the -/// window refresh callback (see window_refresh) to redraw the contents of your window when -/// necessary during such operations. -/// -/// Do not assume that callbacks you set will _only_ be called in response to event processing -/// functions like this one. While it is necessary to poll for events, window systems that require -/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system -/// function calls. GLFW will pass those events on to the application callbacks before returning. -/// -/// Event processing is not required for joystick input to work. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @reentrancy This function must not be called from a callback. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: events, glfw.waitEvents, glfw.waitEventsTimeout -pub inline fn pollEvents() void { - internal_debug.assertInitialized(); - c.glfwPollEvents(); -} - -/// Waits until events are queued and processes them. -/// -/// This function puts the calling thread to sleep until at least one event is available in the -/// event queue. Once one or more events are available, it behaves exactly like glfw.pollEvents, -/// i.e. the events in the queue are processed and the function then returns immediately. -/// Processing events will cause the window and input callbacks associated with those events to be -/// called. -/// -/// Since not all events are associated with callbacks, this function may return without a callback -/// having been called even if you are monitoring all callbacks. -/// -/// On some platforms, a window move, resize or menu operation will cause event processing to -/// block. This is due to how event processing is designed on those platforms. You can use the -/// window refresh callback (see window_refresh) to redraw the contents of your window when -/// necessary during such operations. -/// -/// Do not assume that callbacks you set will _only_ be called in response to event processing -/// functions like this one. While it is necessary to poll for events, window systems that require -/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system -/// function calls. GLFW will pass those events on to the application callbacks before returning. -/// -/// Event processing is not required for joystick input to work. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @reentrancy This function must not be called from a callback. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: events, glfw.pollEvents, glfw.waitEventsTimeout -pub inline fn waitEvents() void { - internal_debug.assertInitialized(); - c.glfwWaitEvents(); -} - -/// Waits with timeout until events are queued and processes them. -/// -/// This function puts the calling thread to sleep until at least one event is available in the -/// event queue, or until the specified timeout is reached. If one or more events are available, it -/// behaves exactly like glfw.pollEvents, i.e. the events in the queue are processed and the -/// function then returns immediately. Processing events will cause the window and input callbacks -/// associated with those events to be called. -/// -/// The timeout value must be a positive finite number. -/// -/// Since not all events are associated with callbacks, this function may return without a callback -/// having been called even if you are monitoring all callbacks. -/// -/// On some platforms, a window move, resize or menu operation will cause event processing to -/// block. This is due to how event processing is designed on those platforms. You can use the -/// window refresh callback (see window_refresh) to redraw the contents of your window when -/// necessary during such operations. -/// -/// Do not assume that callbacks you set will _only_ be called in response to event processing -/// functions like this one. While it is necessary to poll for events, window systems that require -/// GLFW to register callbacks of its own can pass events to GLFW in response to many window system -/// function calls. GLFW will pass those events on to the application callbacks before returning. -/// -/// Event processing is not required for joystick input to work. -/// -/// @param[in] timeout The maximum amount of time, in seconds, to wait. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue and glfw.ErrorCode.PlatformError. -/// -/// @reentrancy This function must not be called from a callback. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: events, glfw.pollEvents, glfw.waitEvents -pub inline fn waitEventsTimeout(timeout: f64) void { - internal_debug.assertInitialized(); - std.debug.assert(!std.math.isNan(timeout)); - std.debug.assert(timeout >= 0); - std.debug.assert(timeout <= std.math.floatMax(f64)); - c.glfwWaitEventsTimeout(timeout); -} - -/// Posts an empty event to the event queue. -/// -/// This function posts an empty event from the current thread to the event queue, causing -/// glfw.waitEvents or glfw.waitEventsTimeout to return. -/// -/// Possible errors include glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: events, glfw.waitEvents, glfw.waitEventsTimeout -pub inline fn postEmptyEvent() void { - internal_debug.assertInitialized(); - c.glfwPostEmptyEvent(); -} - -/// Returns whether raw mouse motion is supported. -/// -/// This function returns whether raw mouse motion is supported on the current system. This status -/// does not change after GLFW has been initialized so you only need to check this once. If you -/// attempt to enable raw motion on a system that does not support it, glfw.ErrorCode.PlatformError -/// will be emitted. -/// -/// Raw mouse motion is closer to the actual motion of the mouse across a surface. It is not -/// affected by the scaling and acceleration applied to the motion of the desktop cursor. That -/// processing is suitable for a cursor while raw motion is better for controlling for example a 3D -/// camera. Because of this, raw mouse motion is only provided when the cursor is disabled. -/// -/// @return `true` if raw mouse motion is supported on the current machine, or `false` otherwise. -/// -/// @thread_safety This function must only be called from the main thread. -/// -/// see also: raw_mouse_motion, glfw.setInputMode -pub inline fn rawMouseMotionSupported() bool { - internal_debug.assertInitialized(); - return c.glfwRawMouseMotionSupported() == c.GLFW_TRUE; -} - -pub fn basicTest() !void { - defer clearError(); // clear any error we generate - if (!init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()}); - std.process.exit(1); - } - defer terminate(); - - const window = Window.create(640, 480, "GLFW example", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - const start = std.time.milliTimestamp(); - while (std.time.milliTimestamp() < start + 1000 and !window.shouldClose()) { - c.glfwPollEvents(); - } -} - -test { - std.testing.refAllDeclsRecursive(@This()); -} - -test "getVersionString" { - std.debug.print("\nGLFW version v{}.{}.{}\n", .{ version.major, version.minor, version.revision }); - std.debug.print("\nstring: {s}\n", .{getVersionString()}); -} - -test "init" { - _ = init(.{ .cocoa_chdir_resources = true }); - if (getErrorString()) |err| { - std.log.err("failed to initialize GLFW: {?s}", .{err}); - std.process.exit(1); - } - defer terminate(); -} - -test "pollEvents" { - defer clearError(); // clear any error we generate - if (!init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()}); - std.process.exit(1); - } - defer terminate(); - - pollEvents(); -} - -test "waitEventsTimeout" { - defer clearError(); // clear any error we generate - if (!init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()}); - std.process.exit(1); - } - defer terminate(); - - waitEventsTimeout(0.25); -} - -test "postEmptyEvent_and_waitEvents" { - defer clearError(); // clear any error we generate - if (!init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()}); - std.process.exit(1); - } - defer terminate(); - - postEmptyEvent(); - waitEvents(); -} - -test "rawMouseMotionSupported" { - defer clearError(); // clear any error we generate - if (!init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{getErrorString()}); - std.process.exit(1); - } - defer terminate(); - - _ = rawMouseMotionSupported(); -} - -test "basic" { - try basicTest(); -} diff --git a/pkg/glfw/mod.zig b/pkg/glfw/mod.zig deleted file mode 100644 index aa9c9371f..000000000 --- a/pkg/glfw/mod.zig +++ /dev/null @@ -1,167 +0,0 @@ -//! Modifier key flags -//! -//! See glfw.setKeyCallback for how these are used. - -const c = @import("c.zig").c; - -// must be in sync with GLFW C constants in modifier group, search for "@defgroup mods Modifier key flags" -/// A bitmask of all key modifiers -pub const Mods = packed struct(u8) { - shift: bool = false, - control: bool = false, - alt: bool = false, - super: bool = false, - caps_lock: bool = false, - num_lock: bool = false, - _padding: u2 = 0, - - inline fn verifyIntType(comptime IntType: type) void { - comptime { - switch (@import("shims.zig").typeInfo(IntType)) { - .int => {}, - else => @compileError("Int was not of int type"), - } - } - } - - pub inline fn toInt(self: Mods, comptime IntType: type) IntType { - verifyIntType(IntType); - return @as(IntType, @intCast(@as(u8, @bitCast(self)))); - } - - pub inline fn fromInt(flags: anytype) Mods { - verifyIntType(@TypeOf(flags)); - return @as(Mods, @bitCast(@as(u8, @intCast(flags)))); - } -}; - -/// Holds all GLFW mod values in their raw form. -pub const RawMods = struct { - /// If this bit is set one or more Shift keys were held down. - pub const shift = c.GLFW_MOD_SHIFT; - - /// If this bit is set one or more Control keys were held down. - pub const control = c.GLFW_MOD_CONTROL; - - /// If this bit is set one or more Alt keys were held down. - pub const alt = c.GLFW_MOD_ALT; - - /// If this bit is set one or more Super keys were held down. - pub const super = c.GLFW_MOD_SUPER; - - /// If this bit is set the Caps Lock key is enabled and the glfw.lock_key_mods input mode is set. - pub const caps_lock = c.GLFW_MOD_CAPS_LOCK; - - /// If this bit is set the Num Lock key is enabled and the glfw.lock_key_mods input mode is set. - pub const num_lock = c.GLFW_MOD_NUM_LOCK; -}; - -test "shift int to bitmask" { - const std = @import("std"); - - const int_mod = RawMods.shift; - const mod = Mods.fromInt(int_mod); - - try std.testing.expect(mod.shift == true); - try std.testing.expect(mod.control == false); - try std.testing.expect(mod.alt == false); - try std.testing.expect(mod.super == false); - try std.testing.expect(mod.caps_lock == false); - try std.testing.expect(mod.num_lock == false); -} - -test "shift int and alt to bitmask" { - const std = @import("std"); - - const int_mod = RawMods.shift | RawMods.alt; - const mod = Mods.fromInt(int_mod); - - try std.testing.expect(mod.shift == true); - try std.testing.expect(mod.control == false); - try std.testing.expect(mod.alt == true); - try std.testing.expect(mod.super == false); - try std.testing.expect(mod.caps_lock == false); - try std.testing.expect(mod.num_lock == false); -} - -test "super int to bitmask" { - const std = @import("std"); - - const int_mod = RawMods.super; - const mod = Mods.fromInt(int_mod); - - try std.testing.expect(mod.shift == false); - try std.testing.expect(mod.control == false); - try std.testing.expect(mod.alt == false); - try std.testing.expect(mod.super == true); - try std.testing.expect(mod.caps_lock == false); - try std.testing.expect(mod.num_lock == false); -} - -test "num lock int to bitmask" { - const std = @import("std"); - - const int_mod = RawMods.num_lock; - const mod = Mods.fromInt(int_mod); - - try std.testing.expect(mod.shift == false); - try std.testing.expect(mod.control == false); - try std.testing.expect(mod.alt == false); - try std.testing.expect(mod.super == false); - try std.testing.expect(mod.caps_lock == false); - try std.testing.expect(mod.num_lock == true); -} - -test "all int to bitmask" { - const std = @import("std"); - - const int_mod = RawMods.shift | RawMods.control | - RawMods.alt | RawMods.super | - RawMods.caps_lock | RawMods.num_lock; - const mod = Mods.fromInt(int_mod); - - try std.testing.expect(mod.shift == true); - try std.testing.expect(mod.control == true); - try std.testing.expect(mod.alt == true); - try std.testing.expect(mod.super == true); - try std.testing.expect(mod.caps_lock == true); - try std.testing.expect(mod.num_lock == true); -} - -test "shift bitmask to int" { - const std = @import("std"); - - const mod = Mods{ .shift = true }; - const int_mod = mod.toInt(c_int); - - try std.testing.expectEqual(int_mod, RawMods.shift); -} - -test "shift and alt bitmask to int" { - const std = @import("std"); - - const mod = Mods{ .shift = true, .alt = true }; - const int_mod = mod.toInt(c_int); - - try std.testing.expectEqual(int_mod, RawMods.shift | RawMods.alt); -} - -test "all bitmask to int" { - const std = @import("std"); - - const mod = Mods{ - .shift = true, - .control = true, - .alt = true, - .super = true, - .caps_lock = true, - .num_lock = true, - }; - const int_mod = mod.toInt(c_int); - - const expected = RawMods.shift | RawMods.control | - RawMods.alt | RawMods.super | - RawMods.caps_lock | RawMods.num_lock; - - try std.testing.expectEqual(int_mod, expected); -} diff --git a/pkg/glfw/mouse_button.zig b/pkg/glfw/mouse_button.zig deleted file mode 100644 index 847049f5e..000000000 --- a/pkg/glfw/mouse_button.zig +++ /dev/null @@ -1,23 +0,0 @@ -const c = @import("c.zig").c; - -/// Mouse button IDs. -/// -/// See glfw.setMouseButtonCallback for how these are used. -pub const MouseButton = enum(c_int) { - // We use left/right/middle aliases here because those are more common and we cannot have - // duplicate values in a Zig enum. - left = c.GLFW_MOUSE_BUTTON_1, - right = c.GLFW_MOUSE_BUTTON_2, - middle = c.GLFW_MOUSE_BUTTON_3, - four = c.GLFW_MOUSE_BUTTON_4, - five = c.GLFW_MOUSE_BUTTON_5, - six = c.GLFW_MOUSE_BUTTON_6, - seven = c.GLFW_MOUSE_BUTTON_7, - eight = c.GLFW_MOUSE_BUTTON_8, -}; - -/// Not in the MouseButton enumeration as it is a duplicate value which is forbidden. -pub const last = MouseButton.eight; -pub const one = MouseButton.left; -pub const two = MouseButton.right; -pub const three = MouseButton.middle; diff --git a/pkg/glfw/native.zig b/pkg/glfw/native.zig deleted file mode 100644 index 6b8f1831a..000000000 --- a/pkg/glfw/native.zig +++ /dev/null @@ -1,393 +0,0 @@ -//! Native access functions -const std = @import("std"); - -const Window = @import("Window.zig"); -const Monitor = @import("Monitor.zig"); - -const internal_debug = @import("internal_debug.zig"); - -pub const BackendOptions = struct { - win32: bool = false, - wgl: bool = false, - cocoa: bool = false, - nsgl: bool = false, - x11: bool = false, - glx: bool = false, - wayland: bool = false, - egl: bool = false, - osmesa: bool = false, -}; - -/// This function returns a type which allows provides an interface to access -/// the native handles based on backends selected. -/// -/// The available window API options are: -/// * win32 -/// * cocoa -/// * x11 -/// * wayland -/// -/// The available context API options are: -/// -/// * wgl -/// * nsgl -/// * glx -/// * egl -/// * osmesa -/// -/// The chosen backends must match those the library was compiled for. Failure to do so -/// will cause a link-time error. -pub fn Native(comptime options: BackendOptions) type { - const native = @cImport({ - // @cDefine("GLFW_INCLUDE_VULKAN", "1"); - @cDefine("GLFW_INCLUDE_NONE", "1"); - if (options.win32) @cDefine("GLFW_EXPOSE_NATIVE_WIN32", "1"); - if (options.wgl) @cDefine("GLFW_EXPOSE_NATIVE_WGL", "1"); - if (options.cocoa) @cDefine("GLFW_EXPOSE_NATIVE_COCOA", "1"); - if (options.nsgl) @cDefine("GLFW_EXPOSE_NATIVE_NGSL", "1"); - if (options.x11) @cDefine("GLFW_EXPOSE_NATIVE_X11", "1"); - if (options.glx) @cDefine("GLFW_EXPOSE_NATIVE_GLX", "1"); - if (options.wayland) @cDefine("GLFW_EXPOSE_NATIVE_WAYLAND", "1"); - if (options.egl) @cDefine("GLFW_EXPOSE_NATIVE_EGL", "1"); - if (options.osmesa) @cDefine("GLFW_EXPOSE_NATIVE_OSMESA", "1"); - @cDefine("__kernel_ptr_semantics", ""); - @cInclude("GLFW/glfw3.h"); - @cInclude("GLFW/glfw3native.h"); - }); - - return struct { - /// Returns the adapter device name of the specified monitor. - /// - /// return: The UTF-8 encoded adapter device name (for example `\\.\DISPLAY1`) of the - /// specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWin32Adapter(monitor: Monitor) [*:0]const u8 { - internal_debug.assertInitialized(); - if (native.glfwGetWin32Adapter(@as(*native.GLFWmonitor, @ptrCast(monitor.handle)))) |adapter| return adapter; - // `glfwGetWin32Adapter` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the display device name of the specified monitor. - /// - /// return: The UTF-8 encoded display device name (for example `\\.\DISPLAY1\Monitor0`) - /// of the specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWin32Monitor(monitor: Monitor) [*:0]const u8 { - internal_debug.assertInitialized(); - if (native.glfwGetWin32Monitor(@as(*native.GLFWmonitor, @ptrCast(monitor.handle)))) |mon| return mon; - // `glfwGetWin32Monitor` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `HWND` of the specified window. - /// - /// The `HDC` associated with the window can be queried with the - /// [GetDC](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdc) - /// function. - /// ``` - /// const dc = std.os.windows.user32.GetDC(native.getWin32Window(window)); - /// ``` - /// This DC is private and does not need to be released. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWin32Window(window: Window) std.os.windows.HWND { - internal_debug.assertInitialized(); - if (native.glfwGetWin32Window(@as(*native.GLFWwindow, @ptrCast(window.handle)))) |win| - return @as(std.os.windows.HWND, @ptrCast(win)); - // `glfwGetWin32Window` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `HGLRC` of the specified window. - /// - /// The `HDC` associated with the window can be queried with the - /// [GetDC](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdc) - /// function. - /// ``` - /// const dc = std.os.windows.user32.GetDC(native.getWin32Window(window)); - /// ``` - /// This DC is private and does not need to be released. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext - /// null is returned in the event of an error. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWGLContext(window: Window) ?std.os.windows.HGLRC { - internal_debug.assertInitialized(); - if (native.glfwGetWGLContext(@as(*native.GLFWwindow, @ptrCast(window.handle)))) |context| return context; - return null; - } - - /// Returns the `CGDirectDisplayID` of the specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getCocoaMonitor(monitor: Monitor) u32 { - internal_debug.assertInitialized(); - const mon = native.glfwGetCocoaMonitor(@as(*native.GLFWmonitor, @ptrCast(monitor.handle))); - if (mon != native.kCGNullDirectDisplay) return mon; - // `glfwGetCocoaMonitor` returns `kCGNullDirectDisplay` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `NSWindow` of the specified window. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getCocoaWindow(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - return native.glfwGetCocoaWindow(@as(*native.GLFWwindow, @ptrCast(window.handle))); - } - - /// Returns the `NSWindow` of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getNSGLContext(window: Window) u32 { - internal_debug.assertInitialized(); - return native.glfwGetNSGLContext(@as(*native.GLFWwindow, @ptrCast(window.handle))); - } - - /// Returns the `Display` used by GLFW. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getX11Display() *anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetX11Display()) |display| return @as(*anyopaque, @ptrCast(display)); - // `glfwGetX11Display` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `RRCrtc` of the specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getX11Adapter(monitor: Monitor) u32 { - internal_debug.assertInitialized(); - const adapter = native.glfwGetX11Adapter(@as(*native.GLFWMonitor, @ptrCast(monitor.handle))); - if (adapter != 0) return adapter; - // `glfwGetX11Adapter` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `RROutput` of the specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getX11Monitor(monitor: Monitor) u32 { - internal_debug.assertInitialized(); - const mon = native.glfwGetX11Monitor(@as(*native.GLFWmonitor, @ptrCast(monitor.handle))); - if (mon != 0) return mon; - // `glfwGetX11Monitor` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `Window` of the specified window. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getX11Window(window: Window) u32 { - internal_debug.assertInitialized(); - const win = native.glfwGetX11Window(@as(*native.GLFWwindow, @ptrCast(window.handle))); - if (win != 0) return @as(u32, @intCast(win)); - // `glfwGetX11Window` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Sets the current primary selection to the specified string. - /// - /// Possible errors include glfw.ErrorCode.PlatformError. - /// - /// The specified string is copied before this function returns. - /// - /// thread_safety: This function must only be called from the main thread. - pub fn setX11SelectionString(string: [*:0]const u8) void { - internal_debug.assertInitialized(); - native.glfwSetX11SelectionString(string); - } - - /// Returns the contents of the current primary selection as a string. - /// - /// Possible errors include glfw.ErrorCode.PlatformError. - /// Returns null in the event of an error. - /// - /// The returned string is allocated and freed by GLFW. You should not free it - /// yourself. It is valid until the next call to getX11SelectionString or - /// setX11SelectionString, or until the library is terminated. - /// - /// thread_safety: This function must only be called from the main thread. - pub fn getX11SelectionString() ?[*:0]const u8 { - internal_debug.assertInitialized(); - if (native.glfwGetX11SelectionString()) |str| return str; - return null; - } - - /// Returns the `GLXContext` of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext. - /// Returns null in the event of an error. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getGLXContext(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetGLXContext(@as(*native.GLFWwindow, @ptrCast(window.handle)))) |context| return @as(*anyopaque, @ptrCast(context)); - return null; - } - - /// Returns the `GLXWindow` of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext. - /// Returns null in the event of an error. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getGLXWindow(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - const win = native.glfwGetGLXWindow(@as(*native.GLFWwindow, @ptrCast(window.handle))); - if (win != 0) return @as(*anyopaque, @ptrCast(win)); - return null; - } - - /// Returns the `*wl_display` used by GLFW. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWaylandDisplay() *anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetWaylandDisplay()) |display| return @as(*anyopaque, @ptrCast(display)); - // `glfwGetWaylandDisplay` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `*wl_output` of the specified monitor. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWaylandMonitor(monitor: Monitor) *anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetWaylandMonitor(@as(*native.GLFWmonitor, @ptrCast(monitor.handle)))) |mon| return @as(*anyopaque, @ptrCast(mon)); - // `glfwGetWaylandMonitor` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `*wl_surface` of the specified window. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getWaylandWindow(window: Window) *anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetWaylandWindow(@as(*native.GLFWwindow, @ptrCast(window.handle)))) |win| return @as(*anyopaque, @ptrCast(win)); - // `glfwGetWaylandWindow` returns `null` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `EGLDisplay` used by GLFW. - /// - /// remark: Because EGL is initialized on demand, this function will return `EGL_NO_DISPLAY` - /// until the first context has been created via EGL. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getEGLDisplay() *anyopaque { - internal_debug.assertInitialized(); - const display = native.glfwGetEGLDisplay(); - if (display != native.EGL_NO_DISPLAY) return @as(*anyopaque, @ptrCast(display)); - // `glfwGetEGLDisplay` returns `EGL_NO_DISPLAY` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; - } - - /// Returns the `EGLContext` of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext. - /// Returns null in the event of an error. - /// - /// thread_safety This function may be called from any thread. Access is not synchronized. - pub fn getEGLContext(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - const context = native.glfwGetEGLContext(@as(*native.GLFWwindow, @ptrCast(window.handle))); - if (context != native.EGL_NO_CONTEXT) return @as(*anyopaque, @ptrCast(context)); - return null; - } - - /// Returns the `EGLSurface` of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NotInitalized and glfw.ErrorCode.NoWindowContext. - /// - /// thread_safety This function may be called from any thread. Access is not synchronized. - pub fn getEGLSurface(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - const surface = native.glfwGetEGLSurface(@as(*native.GLFWwindow, @ptrCast(window.handle))); - if (surface != native.EGL_NO_SURFACE) return @as(*anyopaque, @ptrCast(surface)); - return null; - } - - pub const OSMesaColorBuffer = struct { - width: c_int, - height: c_int, - format: c_int, - buffer: *anyopaque, - }; - - /// Retrieves the color buffer associated with the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext and glfw.ErrorCode.PlatformError. - /// Returns null in the event of an error. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getOSMesaColorBuffer(window: Window) ?OSMesaColorBuffer { - internal_debug.assertInitialized(); - var buf: OSMesaColorBuffer = undefined; - if (native.glfwGetOSMesaColorBuffer( - @as(*native.GLFWwindow, @ptrCast(window.handle)), - &buf.width, - &buf.height, - &buf.format, - &buf.buffer, - ) == native.GLFW_TRUE) return buf; - return null; - } - - pub const OSMesaDepthBuffer = struct { - width: c_int, - height: c_int, - bytes_per_value: c_int, - buffer: *anyopaque, - }; - - /// Retrieves the depth buffer associated with the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext and glfw.ErrorCode.PlatformError. - /// Returns null in the event of an error. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getOSMesaDepthBuffer(window: Window) ?OSMesaDepthBuffer { - internal_debug.assertInitialized(); - var buf: OSMesaDepthBuffer = undefined; - if (native.glfwGetOSMesaDepthBuffer( - @as(*native.GLFWwindow, @ptrCast(window.handle)), - &buf.width, - &buf.height, - &buf.bytes_per_value, - &buf.buffer, - ) == native.GLFW_TRUE) return buf; - return null; - } - - /// Returns the 'OSMesaContext' of the specified window. - /// - /// Possible errors include glfw.ErrorCode.NoWindowContext. - /// - /// thread_safety: This function may be called from any thread. Access is not synchronized. - pub fn getOSMesaContext(window: Window) ?*anyopaque { - internal_debug.assertInitialized(); - if (native.glfwGetOSMesaContext(@as(*native.GLFWwindow, @ptrCast(window.handle)))) |context| return @as(*anyopaque, @ptrCast(context)); - return null; - } - }; -} diff --git a/pkg/glfw/opengl.zig b/pkg/glfw/opengl.zig deleted file mode 100644 index 8fe2efbed..000000000 --- a/pkg/glfw/opengl.zig +++ /dev/null @@ -1,256 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const c = @import("c.zig").c; -const Window = @import("Window.zig"); - -const internal_debug = @import("internal_debug.zig"); - -/// Makes the context of the specified window current for the calling thread. -/// -/// This function makes the OpenGL or OpenGL ES context of the specified window current on the -/// calling thread. A context must only be made current on a single thread at a time and each -/// thread can have only a single current context at a time. -/// -/// When moving a context between threads, you must make it non-current on the old thread before -/// making it current on the new one. -/// -/// By default, making a context non-current implicitly forces a pipeline flush. On machines that -/// support `GL_KHR_context_flush_control`, you can control whether a context performs this flush -/// by setting the glfw.context_release_behavior hint. -/// -/// The specified window must have an OpenGL or OpenGL ES context. Specifying a window without a -/// context will generate glfw.ErrorCode.NoWindowContext. -/// -/// @param[in] window The window whose context to make current, or null to -/// detach the current context. -/// -/// Possible errors include glfw.ErrorCode.NoWindowContext and glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: context_current, glfwGetCurrentContext -pub inline fn makeContextCurrent(window: ?Window) void { - internal_debug.assertInitialized(); - if (window) |w| c.glfwMakeContextCurrent(w.handle) else c.glfwMakeContextCurrent(null); -} - -/// Returns the window whose context is current on the calling thread. -/// -/// This function returns the window whose OpenGL or OpenGL ES context is current on the calling -/// thread. -/// -/// Returns he window whose context is current, or null if no window's context is current. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: context_current, glfwMakeContextCurrent -pub inline fn getCurrentContext() ?Window { - internal_debug.assertInitialized(); - if (c.glfwGetCurrentContext()) |handle| return .from(handle); - return null; -} - -/// Sets the swap interval for the current context. -/// -/// This function sets the swap interval for the current OpenGL or OpenGL ES context, i.e. the -/// number of screen updates to wait from the time glfw.SwapBuffers was called before swapping the -/// buffers and returning. This is sometimes called _vertical synchronization_, _vertical retrace -/// synchronization_ or just _vsync_. -/// -/// A context that supports either of the `WGL_EXT_swap_control_tear` and `GLX_EXT_swap_control_tear` -/// extensions also accepts _negative_ swap intervals, which allows the driver to swap immediately -/// even if a frame arrives a little bit late. You can check for these extensions with glfw.extensionSupported. -/// -/// A context must be current on the calling thread. Calling this function without a current context -/// will cause glfw.ErrorCode.NoCurrentContext. -/// -/// This function does not apply to Vulkan. If you are rendering with Vulkan, see the present mode -/// of your swapchain instead. -/// -/// @param[in] interval The minimum number of screen updates to wait for until the buffers are -/// swapped by glfw.swapBuffers. -/// -/// Possible errors include glfw.ErrorCode.NoCurrentContext and glfw.ErrorCode.PlatformError. -/// -/// This function is not called during context creation, leaving the swap interval set to whatever -/// is the default for that API. This is done because some swap interval extensions used by -/// GLFW do not allow the swap interval to be reset to zero once it has been set to a non-zero -/// value. -/// -/// Some GPU drivers do not honor the requested swap interval, either because of a user setting -/// that overrides the application's request or due to bugs in the driver. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: buffer_swap, glfwSwapBuffers -pub inline fn swapInterval(interval: i32) void { - internal_debug.assertInitialized(); - c.glfwSwapInterval(@as(c_int, @intCast(interval))); -} - -/// Returns whether the specified extension is available. -/// -/// This function returns whether the specified API extension (see context_glext) is supported by -/// the current OpenGL or OpenGL ES context. It searches both for client API extension and context -/// creation API extensions. -/// -/// A context must be current on the calling thread. Calling this function without a current -/// context will cause glfw.ErrorCode.NoCurrentContext. -/// -/// As this functions retrieves and searches one or more extension strings each call, it is -/// recommended that you cache its results if it is going to be used frequently. The extension -/// strings will not change during the lifetime of a context, so there is no danger in doing this. -/// -/// This function does not apply to Vulkan. If you are using Vulkan, see glfw.getRequiredInstanceExtensions, -/// `vkEnumerateInstanceExtensionProperties` and `vkEnumerateDeviceExtensionProperties` instead. -/// -/// @param[in] extension The ASCII encoded name of the extension. -/// @return `true` if the extension is available, or `false` otherwise. -/// -/// Possible errors include glfw.ErrorCode.NoCurrentContext, glfw.ErrorCode.InvalidValue -/// and glfw.ErrorCode.PlatformError. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: context_glext, glfw.getProcAddress -pub inline fn extensionSupported(extension: [:0]const u8) bool { - internal_debug.assertInitialized(); - - std.debug.assert(extension.len != 0); - std.debug.assert(extension[0] != 0); - - return c.glfwExtensionSupported(extension.ptr) == c.GLFW_TRUE; -} - -/// Client API function pointer type. -/// -/// Generic function pointer used for returning client API function pointers. -/// -/// see also: context_glext, glfwGetProcAddress -pub const GLProc = *const fn () callconv(if (builtin.os.tag == .windows and builtin.cpu.arch == .x86) .Stdcall else .C) void; - -/// Returns the address of the specified function for the current context. -/// -/// This function returns the address of the specified OpenGL or OpenGL ES core or extension -/// function (see context_glext), if it is supported by the current context. -/// -/// A context must be current on the calling thread. Calling this function without a current -/// context will cause glfw.ErrorCode.NoCurrentContext. -/// -/// This function does not apply to Vulkan. If you are rendering with Vulkan, see glfw.getInstanceProcAddress, -/// `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` instead. -/// -/// @param[in] procname The ASCII encoded name of the function. -/// @return The address of the function, or null if an error occurred. -/// -/// To maintain ABI compatability with the C glfwGetProcAddress, as it is commonly passed into -/// libraries expecting that exact ABI, this function does not return an error. Instead, if -/// glfw.ErrorCode.NotInitialized, glfw.ErrorCode.NoCurrentContext, or glfw.ErrorCode.PlatformError -/// would occur this function will panic. You should ensure a valid OpenGL context exists and the -/// GLFW is initialized before calling this function. -/// -/// The address of a given function is not guaranteed to be the same between contexts. -/// -/// This function may return a non-null address despite the associated version or extension -/// not being available. Always check the context version or extension string first. -/// -/// @pointer_lifetime The returned function pointer is valid until the context is destroyed or the -/// library is terminated. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: context_glext, glfwExtensionSupported -pub fn getProcAddress(proc_name: [*:0]const u8) callconv(.c) ?GLProc { - internal_debug.assertInitialized(); - if (c.glfwGetProcAddress(proc_name)) |proc_address| return @ptrCast(proc_address); - return null; -} - -test "makeContextCurrent" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - glfw.makeContextCurrent(window); -} - -test "getCurrentContext" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const current_context = glfw.getCurrentContext(); - std.debug.assert(current_context == null); -} - -test "swapInterval" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - glfw.makeContextCurrent(window); - glfw.swapInterval(1); -} - -test "getProcAddress" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - glfw.makeContextCurrent(window); - _ = glfw.getProcAddress("foobar"); -} - -test "extensionSupported" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - const window = Window.create(640, 480, "Hello, Zig!", null, null, .{}) orelse { - std.log.warn("failed to create window: {?s}", .{glfw.getErrorString()}); - return error.SkipZigTest; // note: we don't exit(1) here because our CI can't open windows - }; - defer window.destroy(); - - glfw.makeContextCurrent(window); - _ = glfw.extensionSupported("foobar"); -} diff --git a/pkg/glfw/shims.zig b/pkg/glfw/shims.zig deleted file mode 100644 index c731bb910..000000000 --- a/pkg/glfw/shims.zig +++ /dev/null @@ -1,84 +0,0 @@ -// Zig 0.14.0-dev changed the names of all 'std.builtin.Type' fields. -const old_std_builtin_type_field_names = @hasField(@import("std").builtin.Type, "Type"); - -pub const std = struct { - pub const builtin = struct { - pub const Type = if (old_std_builtin_type_field_names) union(enum) { - type: void, - void: void, - bool: void, - noreturn: void, - int: Int, - float: Float, - pointer: Pointer, - array: Array, - @"struct": Struct, - comptime_float: void, - comptime_int: void, - undefined: void, - null: void, - optional: Optional, - error_union: ErrorUnion, - error_set: ErrorSet, - @"enum": Enum, - @"union": Union, - @"fn": Fn, - @"opaque": Opaque, - frame: Frame, - @"anyframe": AnyFrame, - vector: Vector, - enum_literal: void, - - pub const Int = @import("std").builtin.Type.Int; - pub const Float = @import("std").builtin.Type.Float; - pub const Pointer = @import("std").builtin.Type.Pointer; - pub const Array = @import("std").builtin.Type.Array; - pub const ContainerLayout = @import("std").builtin.Type.ContainerLayout; - pub const StructField = @import("std").builtin.Type.StructField; - pub const Struct = @import("std").builtin.Type.Struct; - pub const Optional = @import("std").builtin.Type.Optional; - pub const ErrorUnion = @import("std").builtin.Type.ErrorUnion; - pub const Error = @import("std").builtin.Type.Error; - pub const ErrorSet = @import("std").builtin.Type.ErrorSet; - pub const EnumField = @import("std").builtin.Type.EnumField; - pub const Enum = @import("std").builtin.Type.Enum; - pub const UnionField = @import("std").builtin.Type.UnionField; - pub const Union = @import("std").builtin.Type.Union; - pub const Fn = @import("std").builtin.Type.Fn; - pub const Opaque = @import("std").builtin.Type.Opaque; - pub const Frame = @import("std").builtin.Type.Frame; - pub const AnyFrame = @import("std").builtin.Type.AnyFrame; - pub const Vector = @import("std").builtin.Type.Vector; - pub const Declaration = @import("std").builtin.Type.Declaration; - } else @import("std").builtin.Type; - }; -}; - -pub fn typeInfo(comptime T: type) std.builtin.Type { - return if (old_std_builtin_type_field_names) switch (@typeInfo(T)) { - .Type => .type, - .Void => .void, - .Bool => .bool, - .NoReturn => .noreturn, - .Int => |x| .{ .int = x }, - .Float => |x| .{ .float = x }, - .Pointer => |x| .{ .pointer = x }, - .Array => |x| .{ .array = x }, - .Struct => |x| .{ .@"struct" = x }, - .ComptimeFloat => .comptime_float, - .ComptimeInt => .comptime_int, - .Undefined => .undefined, - .Null => .null, - .Optional => |x| .{ .optional = x }, - .ErrorUnion => |x| .{ .error_union = x }, - .ErrorSet => |x| .{ .error_set = x }, - .Enum => |x| .{ .@"enum" = x }, - .Union => |x| .{ .@"union" = x }, - .Fn => |x| .{ .@"fn" = x }, - .Opaque => |x| .{ .@"opaque" = x }, - .Frame => |x| .{ .frame = x }, - .AnyFrame => .@"anyframe", - .Vector => |x| .{ .vector = x }, - .EnumLiteral => .enum_literal, - } else @typeInfo(T); -} diff --git a/pkg/glfw/time.zig b/pkg/glfw/time.zig deleted file mode 100644 index c3432b105..000000000 --- a/pkg/glfw/time.zig +++ /dev/null @@ -1,153 +0,0 @@ -const std = @import("std"); - -const c = @import("c.zig").c; - -const internal_debug = @import("internal_debug.zig"); - -/// Returns the GLFW time. -/// -/// This function returns the current GLFW time, in seconds. Unless the time -/// has been set using @ref glfwSetTime it measures time elapsed since GLFW was -/// initialized. -/// -/// This function and @ref glfwSetTime are helper functions on top of glfw.getTimerFrequency -/// and glfw.getTimerValue. -/// -/// The resolution of the timer is system dependent, but is usually on the order -/// of a few micro- or nanoseconds. It uses the highest-resolution monotonic -/// time source on each supported operating system. -/// -/// @return The current time, in seconds, or zero if an -/// error occurred. -/// -/// @thread_safety This function may be called from any thread. Reading and -/// writing of the internal base time is not atomic, so it needs to be -/// externally synchronized with calls to @ref glfwSetTime. -/// -/// see also: time -pub inline fn getTime() f64 { - internal_debug.assertInitialized(); - const time = c.glfwGetTime(); - if (time != 0) return time; - // `glfwGetTime` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; -} - -/// Sets the GLFW time. -/// -/// This function sets the current GLFW time, in seconds. The value must be a positive finite -/// number less than or equal to 18446744073.0, which is approximately 584.5 years. -/// -/// This function and @ref glfwGetTime are helper functions on top of glfw.getTimerFrequency and -/// glfw.getTimerValue. -/// -/// @param[in] time The new value, in seconds. -/// -/// Possible errors include glfw.ErrorCode.InvalidValue. -/// -/// The upper limit of GLFW time is calculated as `floor((2^64 - 1) / 10^9)` and is due to -/// implementations storing nanoseconds in 64 bits. The limit may be increased in the future. -/// -/// @thread_safety This function may be called from any thread. Reading and writing of the internal -/// base time is not atomic, so it needs to be externally synchronized with calls to glfw.getTime. -/// -/// see also: time -pub inline fn setTime(time: f64) void { - internal_debug.assertInitialized(); - - std.debug.assert(!std.math.isNan(time)); - std.debug.assert(time >= 0); - // assert time is lteq to largest number of seconds representable by u64 with nanosecond precision - std.debug.assert(time <= max_time: { - const @"2^64" = std.math.maxInt(u64); - break :max_time @divTrunc(@"2^64", std.time.ns_per_s); - }); - - c.glfwSetTime(time); -} - -/// Returns the current value of the raw timer. -/// -/// This function returns the current value of the raw timer, measured in `1/frequency` seconds. To -/// get the frequency, call glfw.getTimerFrequency. -/// -/// @return The value of the timer, or zero if an error occurred. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: time, glfw.getTimerFrequency -pub inline fn getTimerValue() u64 { - internal_debug.assertInitialized(); - const value = c.glfwGetTimerValue(); - if (value != 0) return value; - // `glfwGetTimerValue` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; -} - -/// Returns the frequency, in Hz, of the raw timer. -/// -/// This function returns the frequency, in Hz, of the raw timer. -/// -/// @return The frequency of the timer, in Hz, or zero if an error occurred. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: time, glfw.getTimerValue -pub inline fn getTimerFrequency() u64 { - internal_debug.assertInitialized(); - const frequency = c.glfwGetTimerFrequency(); - if (frequency != 0) return frequency; - // `glfwGetTimerFrequency` returns `0` only for errors - // but the only potential error is unreachable (NotInitialized) - unreachable; -} - -test "getTime" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = getTime(); -} - -test "setTime" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.setTime(1234); -} - -test "getTimerValue" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.getTimerValue(); -} - -test "getTimerFrequency" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.getTimerFrequency(); -} diff --git a/pkg/glfw/version.zig b/pkg/glfw/version.zig deleted file mode 100644 index e83a4d708..000000000 --- a/pkg/glfw/version.zig +++ /dev/null @@ -1,18 +0,0 @@ -//! GLFW version info - -const c = @import("c.zig").c; - -/// The major version number of the GLFW library. -/// -/// This is incremented when the API is changed in non-compatible ways. -pub const major = c.GLFW_VERSION_MAJOR; - -/// The minor version number of the GLFW library. -/// -/// This is incremented when features are added to the API but it remains backward-compatible. -pub const minor = c.GLFW_VERSION_MINOR; - -/// The revision number of the GLFW library. -/// -/// This is incremented when a bug fix release is made that does not contain any API changes. -pub const revision = c.GLFW_VERSION_REVISION; diff --git a/pkg/glfw/vulkan.zig b/pkg/glfw/vulkan.zig deleted file mode 100644 index 1b84145d5..000000000 --- a/pkg/glfw/vulkan.zig +++ /dev/null @@ -1,290 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const c = @import("c.zig").c; -const Window = @import("Window.zig"); - -const internal_debug = @import("internal_debug.zig"); - -/// Sets the desired Vulkan `vkGetInstanceProcAddr` function. -/// -/// This function sets the `vkGetInstanceProcAddr` function that GLFW will use for all -/// Vulkan related entry point queries. -/// -/// This feature is mostly useful on macOS, if your copy of the Vulkan loader is in -/// a location where GLFW cannot find it through dynamic loading, or if you are still -/// using the static library version of the loader. -/// -/// If set to `NULL`, GLFW will try to load the Vulkan loader dynamically by its standard -/// name and get this function from there. This is the default behavior. -/// -/// The standard name of the loader is `vulkan-1.dll` on Windows, `libvulkan.so.1` on -/// Linux and other Unix-like systems and `libvulkan.1.dylib` on macOS. If your code is -/// also loading it via these names then you probably don't need to use this function. -/// -/// The function address you set is never reset by GLFW, but it only takes effect during -/// initialization. Once GLFW has been initialized, any updates will be ignored until the -/// library is terminated and initialized again. -/// -/// remark: This function may be called before glfw.Init. -/// -/// thread_safety: This function must only be called from the main thread. -pub fn initVulkanLoader(loader_function: ?VKGetInstanceProcAddr) void { - c.glfwInitVulkanLoader(loader_function orelse null); -} - -pub const VKGetInstanceProcAddr = *const fn (vk_instance: c.VkInstance, name: [*c]const u8) callconv(.c) ?VKProc; - -/// Returns whether the Vulkan loader and an ICD have been found. -/// -/// This function returns whether the Vulkan loader and any minimally functional ICD have been -/// found. -/// -/// The availability of a Vulkan loader and even an ICD does not by itself guarantee that surface -/// creation or even instance creation is possible. Call glfw.getRequiredInstanceExtensions -/// to check whether the extensions necessary for Vulkan surface creation are available and -/// glfw.getPhysicalDevicePresentationSupport to check whether a queue family of a physical device -/// supports image presentation. -/// -/// @return `true` if Vulkan is minimally available, or `false` otherwise. -/// -/// @thread_safety This function may be called from any thread. -pub inline fn vulkanSupported() bool { - internal_debug.assertInitialized(); - const supported = c.glfwVulkanSupported(); - return supported == c.GLFW_TRUE; -} - -/// Returns the Vulkan instance extensions required by GLFW. -/// -/// This function returns an array of names of Vulkan instance extensions required by GLFW for -/// creating Vulkan surfaces for GLFW windows. If successful, the list will always contain -/// `VK_KHR_surface`, so if you don't require any additional extensions you can pass this list -/// directly to the `VkInstanceCreateInfo` struct. -/// -/// If Vulkan is not available on the machine, this function returns null and generates a -/// glfw.ErrorCode.APIUnavailable error. Call glfw.vulkanSupported to check whether Vulkan is at -/// least minimally available. -/// -/// If Vulkan is available but no set of extensions allowing window surface creation was found, -/// this function returns null. You may still use Vulkan for off-screen rendering and compute work. -/// -/// Possible errors include glfw.ErrorCode.APIUnavailable. -/// Returns null in the event of an error. -/// -/// Additional extensions may be required by future versions of GLFW. You should check if any -/// extensions you wish to enable are already in the returned array, as it is an error to specify -/// an extension more than once in the `VkInstanceCreateInfo` struct. -/// -/// @pointer_lifetime The returned array is allocated and freed by GLFW. You should not free it -/// yourself. It is guaranteed to be valid only until the library is terminated. -/// -/// @thread_safety This function may be called from any thread. -/// -/// see also: vulkan_ext, glfwCreateWindowSurface -pub inline fn getRequiredInstanceExtensions() ?[][*:0]const u8 { - internal_debug.assertInitialized(); - var count: u32 = 0; - if (c.glfwGetRequiredInstanceExtensions(&count)) |extensions| return @as([*][*:0]const u8, @ptrCast(extensions))[0..count]; - return null; -} - -/// Vulkan API function pointer type. -/// -/// Generic function pointer used for returning Vulkan API function pointers. -/// -/// see also: vulkan_proc, glfw.getInstanceProcAddress -pub const VKProc = *const fn () callconv(if (builtin.os.tag == .windows and builtin.cpu.arch == .x86) .Stdcall else .C) void; - -/// Returns the address of the specified Vulkan instance function. -/// -/// This function returns the address of the specified Vulkan core or extension function for the -/// specified instance. If instance is set to null it can return any function exported from the -/// Vulkan loader, including at least the following functions: -/// -/// - `vkEnumerateInstanceExtensionProperties` -/// - `vkEnumerateInstanceLayerProperties` -/// - `vkCreateInstance` -/// - `vkGetInstanceProcAddr` -/// -/// If Vulkan is not available on the machine, this function returns null and generates a -/// glfw.ErrorCode.APIUnavailable error. Call glfw.vulkanSupported to check whether Vulkan is at -/// least minimally available. -/// -/// This function is equivalent to calling `vkGetInstanceProcAddr` with a platform-specific query -/// of the Vulkan loader as a fallback. -/// -/// @param[in] instance The Vulkan instance to query, or null to retrieve functions related to -/// instance creation. -/// @param[in] procname The ASCII encoded name of the function. -/// @return The address of the function, or null if an error occurred. -/// -/// To maintain ABI compatability with the C glfwGetInstanceProcAddress, as it is commonly passed -/// into libraries expecting that exact ABI, this function does not return an error. Instead, if -/// glfw.ErrorCode.NotInitialized or glfw.ErrorCode.APIUnavailable would occur this function will panic. -/// You may check glfw.vulkanSupported prior to invoking this function. -/// -/// @pointer_lifetime The returned function pointer is valid until the library is terminated. -/// -/// @thread_safety This function may be called from any thread. -pub fn getInstanceProcAddress(vk_instance: ?*anyopaque, proc_name: [*:0]const u8) callconv(.c) ?VKProc { - internal_debug.assertInitialized(); - if (c.glfwGetInstanceProcAddress(if (vk_instance) |v| @as(c.VkInstance, @ptrCast(v)) else null, proc_name)) |proc_address| return proc_address; - return null; -} - -/// Returns whether the specified queue family can present images. -/// -/// This function returns whether the specified queue family of the specified physical device -/// supports presentation to the platform GLFW was built for. -/// -/// If Vulkan or the required window surface creation instance extensions are not available on the -/// machine, or if the specified instance was not created with the required extensions, this -/// function returns `GLFW_FALSE` and generates a glfw.ErrorCode.APIUnavailable error. Call -/// glfw.vulkanSupported to check whether Vulkan is at least minimally available and -/// glfw.getRequiredInstanceExtensions to check what instance extensions are required. -/// -/// @param[in] instance The instance that the physical device belongs to. -/// @param[in] device The physical device that the queue family belongs to. -/// @param[in] queuefamily The index of the queue family to query. -/// @return `true` if the queue family supports presentation, or `false` otherwise. -/// -/// Possible errors include glfw.ErrorCode.APIUnavailable and glfw.ErrorCode.PlatformError. -/// Returns false in the event of an error. -/// -/// macos: This function currently always returns `true`, as the `VK_MVK_macos_surface` and -/// 'VK_EXT_metal_surface' extension does not provide a `vkGetPhysicalDevice*PresentationSupport` type function. -/// -/// @thread_safety This function may be called from any thread. For synchronization details of -/// Vulkan objects, see the Vulkan specification. -/// -/// see also: vulkan_present -pub inline fn getPhysicalDevicePresentationSupport( - vk_instance: *anyopaque, - vk_physical_device: *anyopaque, - queue_family: u32, -) bool { - internal_debug.assertInitialized(); - return c.glfwGetPhysicalDevicePresentationSupport( - @as(c.VkInstance, @ptrCast(vk_instance)), - @as(c.VkPhysicalDevice, @ptrCast(vk_physical_device)), - queue_family, - ) == c.GLFW_TRUE; -} - -/// Creates a Vulkan surface for the specified window. -/// -/// This function creates a Vulkan surface for the specified window. -/// -/// If the Vulkan loader or at least one minimally functional ICD were not found, this function -/// returns `VK_ERROR_INITIALIZATION_FAILED` and generates a glfw.ErrorCode.APIUnavailable error. Call -/// glfw.vulkanSupported to check whether Vulkan is at least minimally available. -/// -/// If the required window surface creation instance extensions are not available or if the -/// specified instance was not created with these extensions enabled, this function returns `VK_ERROR_EXTENSION_NOT_PRESENT` -/// and generates a glfw.ErrorCode.APIUnavailable error. Call glfw.getRequiredInstanceExtensions to -/// check what instance extensions are required. -/// -/// The window surface cannot be shared with another API so the window must have been created with -/// the client api hint set to `GLFW_NO_API` otherwise it generates a glfw.ErrorCode.InvalidValue error -/// and returns `VK_ERROR_NATIVE_WINDOW_IN_USE_KHR`. -/// -/// The window surface must be destroyed before the specified Vulkan instance. It is the -/// responsibility of the caller to destroy the window surface. GLFW does not destroy it for you. -/// Call `vkDestroySurfaceKHR` to destroy the surface. -/// -/// @param[in] vk_instance The Vulkan instance to create the surface in. -/// @param[in] window The window to create the surface for. -/// @param[in] vk_allocation_callbacks The allocator to use, or null to use the default -/// allocator. -/// @param[out] surface Where to store the handle of the surface. This is set -/// to `VK_NULL_HANDLE` if an error occurred. -/// @return `VkResult` type, `VK_SUCCESS` if successful, or a Vulkan error code if an -/// error occurred. -/// -/// Possible errors include glfw.ErrorCode.APIUnavailable, glfw.ErrorCode.PlatformError and glfw.ErrorCode.InvalidValue -/// Returns a bool indicating success. -/// -/// If an error occurs before the creation call is made, GLFW returns the Vulkan error code most -/// appropriate for the error. Appropriate use of glfw.vulkanSupported and glfw.getRequiredInstanceExtensions -/// should eliminate almost all occurrences of these errors. -/// -/// macos: GLFW prefers the `VK_EXT_metal_surface` extension, with the `VK_MVK_macos_surface` -/// extension as a fallback. The name of the selected extension, if any, is included in the array -/// returned by glfw.getRequiredInstanceExtensions. -/// -/// macos: This function currently only supports the `VK_MVK_macos_surface` extension from MoltenVK. -/// -/// macos: This function creates and sets a `CAMetalLayer` instance for the window content view, -/// which is required for MoltenVK to function. -/// -/// x11: By default GLFW prefers the `VK_KHR_xcb_surface` extension, with the `VK_KHR_xlib_surface` -/// extension as a fallback. You can make `VK_KHR_xlib_surface` the preferred extension by setting -/// glfw.InitHints.x11_xcb_vulkan_surface. The name of the selected extension, if any, is included -/// in the array returned by glfw.getRequiredInstanceExtensions. -/// -/// @thread_safety This function may be called from any thread. For synchronization details of -/// Vulkan objects, see the Vulkan specification. -/// -/// see also: vulkan_surface, glfw.getRequiredInstanceExtensions -pub inline fn createWindowSurface(vk_instance: anytype, window: Window, vk_allocation_callbacks: anytype, vk_surface_khr: anytype) i32 { - internal_debug.assertInitialized(); - // zig-vulkan uses enums to represent opaque pointers: - // pub const Instance = enum(usize) { null_handle = 0, _ }; - const instance: c.VkInstance = switch (@import("shims.zig").typeInfo(@TypeOf(vk_instance))) { - .@"enum" => @as(c.VkInstance, @ptrFromInt(@intFromEnum(vk_instance))), - else => @as(c.VkInstance, @ptrCast(vk_instance)), - }; - - return c.glfwCreateWindowSurface( - instance, - window.handle, - if (vk_allocation_callbacks == null) null else @as(*const c.VkAllocationCallbacks, @ptrCast(@alignCast(vk_allocation_callbacks))), - @as(*c.VkSurfaceKHR, @ptrCast(@alignCast(vk_surface_khr))), - ); -} - -test "vulkanSupported" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.vulkanSupported(); -} - -test "getRequiredInstanceExtensions" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - _ = glfw.getRequiredInstanceExtensions(); -} - -test "getInstanceProcAddress" { - const glfw = @import("main.zig"); - defer glfw.clearError(); // clear any error we generate - if (!glfw.init(.{})) { - std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()}); - std.process.exit(1); - } - defer glfw.terminate(); - - // syntax check only, we don't have a real vulkan instance and so this function would panic. - _ = glfw.getInstanceProcAddress; -} - -test "syntax" { - // Best we can do for these two functions in terms of testing in lieu of an actual Vulkan - // context. - _ = getPhysicalDevicePresentationSupport; - _ = createWindowSurface; - _ = initVulkanLoader; -} diff --git a/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol-code.h deleted file mode 100644 index f847c1eaa..000000000 --- a/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol-code.h +++ /dev/null @@ -1,74 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2022 Kenny Levinsen - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface wp_fractional_scale_v1_interface; - -static const struct wl_interface *fractional_scale_v1_types[] = { - NULL, - &wp_fractional_scale_v1_interface, - &wl_surface_interface, -}; - -static const struct wl_message wp_fractional_scale_manager_v1_requests[] = { - { "destroy", "", fractional_scale_v1_types + 0 }, - { "get_fractional_scale", "no", fractional_scale_v1_types + 1 }, -}; - -WL_PRIVATE const struct wl_interface wp_fractional_scale_manager_v1_interface = { - "wp_fractional_scale_manager_v1", 1, - 2, wp_fractional_scale_manager_v1_requests, - 0, NULL, -}; - -static const struct wl_message wp_fractional_scale_v1_requests[] = { - { "destroy", "", fractional_scale_v1_types + 0 }, -}; - -static const struct wl_message wp_fractional_scale_v1_events[] = { - { "preferred_scale", "u", fractional_scale_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wp_fractional_scale_v1_interface = { - "wp_fractional_scale_v1", 1, - 1, wp_fractional_scale_v1_requests, - 1, wp_fractional_scale_v1_events, -}; - diff --git a/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol.h b/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol.h deleted file mode 100644 index 8df7558a7..000000000 --- a/pkg/glfw/wayland-headers/fractional-scale-v1-client-protocol.h +++ /dev/null @@ -1,264 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef FRACTIONAL_SCALE_V1_CLIENT_PROTOCOL_H -#define FRACTIONAL_SCALE_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_fractional_scale_v1 The fractional_scale_v1 protocol - * Protocol for requesting fractional surface scales - * - * @section page_desc_fractional_scale_v1 Description - * - * This protocol allows a compositor to suggest for surfaces to render at - * fractional scales. - * - * A client can submit scaled content by utilizing wp_viewport. This is done by - * creating a wp_viewport object for the surface and setting the destination - * rectangle to the surface size before the scale factor is applied. - * - * The buffer size is calculated by multiplying the surface size by the - * intended scale. - * - * The wl_surface buffer scale should remain set to 1. - * - * If a surface has a surface-local size of 100 px by 50 px and wishes to - * submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should - * be used and the wp_viewport destination rectangle should be 100 px by 50 px. - * - * For toplevel surfaces, the size is rounded halfway away from zero. The - * rounding algorithm for subsurface position and size is not defined. - * - * @section page_ifaces_fractional_scale_v1 Interfaces - * - @subpage page_iface_wp_fractional_scale_manager_v1 - fractional surface scale information - * - @subpage page_iface_wp_fractional_scale_v1 - fractional scale interface to a wl_surface - * @section page_copyright_fractional_scale_v1 Copyright - *
- *
- * Copyright © 2022 Kenny Levinsen
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_surface; -struct wp_fractional_scale_manager_v1; -struct wp_fractional_scale_v1; - -#ifndef WP_FRACTIONAL_SCALE_MANAGER_V1_INTERFACE -#define WP_FRACTIONAL_SCALE_MANAGER_V1_INTERFACE -/** - * @page page_iface_wp_fractional_scale_manager_v1 wp_fractional_scale_manager_v1 - * @section page_iface_wp_fractional_scale_manager_v1_desc Description - * - * A global interface for requesting surfaces to use fractional scales. - * @section page_iface_wp_fractional_scale_manager_v1_api API - * See @ref iface_wp_fractional_scale_manager_v1. - */ -/** - * @defgroup iface_wp_fractional_scale_manager_v1 The wp_fractional_scale_manager_v1 interface - * - * A global interface for requesting surfaces to use fractional scales. - */ -extern const struct wl_interface wp_fractional_scale_manager_v1_interface; -#endif -#ifndef WP_FRACTIONAL_SCALE_V1_INTERFACE -#define WP_FRACTIONAL_SCALE_V1_INTERFACE -/** - * @page page_iface_wp_fractional_scale_v1 wp_fractional_scale_v1 - * @section page_iface_wp_fractional_scale_v1_desc Description - * - * An additional interface to a wl_surface object which allows the compositor - * to inform the client of the preferred scale. - * @section page_iface_wp_fractional_scale_v1_api API - * See @ref iface_wp_fractional_scale_v1. - */ -/** - * @defgroup iface_wp_fractional_scale_v1 The wp_fractional_scale_v1 interface - * - * An additional interface to a wl_surface object which allows the compositor - * to inform the client of the preferred scale. - */ -extern const struct wl_interface wp_fractional_scale_v1_interface; -#endif - -#ifndef WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM -#define WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM -enum wp_fractional_scale_manager_v1_error { - /** - * the surface already has a fractional_scale object associated - */ - WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_FRACTIONAL_SCALE_EXISTS = 0, -}; -#endif /* WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM */ - -#define WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY 0 -#define WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE 1 - - -/** - * @ingroup iface_wp_fractional_scale_manager_v1 - */ -#define WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wp_fractional_scale_manager_v1 - */ -#define WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE_SINCE_VERSION 1 - -/** @ingroup iface_wp_fractional_scale_manager_v1 */ -static inline void -wp_fractional_scale_manager_v1_set_user_data(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wp_fractional_scale_manager_v1, user_data); -} - -/** @ingroup iface_wp_fractional_scale_manager_v1 */ -static inline void * -wp_fractional_scale_manager_v1_get_user_data(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wp_fractional_scale_manager_v1); -} - -static inline uint32_t -wp_fractional_scale_manager_v1_get_version(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_manager_v1); -} - -/** - * @ingroup iface_wp_fractional_scale_manager_v1 - * - * Informs the server that the client will not be using this protocol - * object anymore. This does not affect any other objects, - * wp_fractional_scale_v1 objects included. - */ -static inline void -wp_fractional_scale_manager_v1_destroy(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_fractional_scale_manager_v1, - WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_manager_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wp_fractional_scale_manager_v1 - * - * Create an add-on object for the the wl_surface to let the compositor - * request fractional scales. If the given wl_surface already has a - * wp_fractional_scale_v1 object associated, the fractional_scale_exists - * protocol error is raised. - */ -static inline struct wp_fractional_scale_v1 * -wp_fractional_scale_manager_v1_get_fractional_scale(struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wp_fractional_scale_manager_v1, - WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE, &wp_fractional_scale_v1_interface, wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_manager_v1), 0, NULL, surface); - - return (struct wp_fractional_scale_v1 *) id; -} - -/** - * @ingroup iface_wp_fractional_scale_v1 - * @struct wp_fractional_scale_v1_listener - */ -struct wp_fractional_scale_v1_listener { - /** - * notify of new preferred scale - * - * Notification of a new preferred scale for this surface that - * the compositor suggests that the client should use. - * - * The sent scale is the numerator of a fraction with a denominator - * of 120. - * @param scale the new preferred scale - */ - void (*preferred_scale)(void *data, - struct wp_fractional_scale_v1 *wp_fractional_scale_v1, - uint32_t scale); -}; - -/** - * @ingroup iface_wp_fractional_scale_v1 - */ -static inline int -wp_fractional_scale_v1_add_listener(struct wp_fractional_scale_v1 *wp_fractional_scale_v1, - const struct wp_fractional_scale_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wp_fractional_scale_v1, - (void (**)(void)) listener, data); -} - -#define WP_FRACTIONAL_SCALE_V1_DESTROY 0 - -/** - * @ingroup iface_wp_fractional_scale_v1 - */ -#define WP_FRACTIONAL_SCALE_V1_PREFERRED_SCALE_SINCE_VERSION 1 - -/** - * @ingroup iface_wp_fractional_scale_v1 - */ -#define WP_FRACTIONAL_SCALE_V1_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_wp_fractional_scale_v1 */ -static inline void -wp_fractional_scale_v1_set_user_data(struct wp_fractional_scale_v1 *wp_fractional_scale_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wp_fractional_scale_v1, user_data); -} - -/** @ingroup iface_wp_fractional_scale_v1 */ -static inline void * -wp_fractional_scale_v1_get_user_data(struct wp_fractional_scale_v1 *wp_fractional_scale_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wp_fractional_scale_v1); -} - -static inline uint32_t -wp_fractional_scale_v1_get_version(struct wp_fractional_scale_v1 *wp_fractional_scale_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_v1); -} - -/** - * @ingroup iface_wp_fractional_scale_v1 - * - * Destroy the fractional scale object. When this object is destroyed, - * preferred_scale events will no longer be sent. - */ -static inline void -wp_fractional_scale_v1_destroy(struct wp_fractional_scale_v1 *wp_fractional_scale_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_fractional_scale_v1, - WP_FRACTIONAL_SCALE_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_fractional_scale_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol-code.h deleted file mode 100644 index 0399e11ae..000000000 --- a/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol-code.h +++ /dev/null @@ -1,69 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2015 Samsung Electronics Co., Ltd - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface zwp_idle_inhibitor_v1_interface; - -static const struct wl_interface *idle_inhibit_unstable_v1_types[] = { - &zwp_idle_inhibitor_v1_interface, - &wl_surface_interface, -}; - -static const struct wl_message zwp_idle_inhibit_manager_v1_requests[] = { - { "destroy", "", idle_inhibit_unstable_v1_types + 0 }, - { "create_inhibitor", "no", idle_inhibit_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwp_idle_inhibit_manager_v1_interface = { - "zwp_idle_inhibit_manager_v1", 1, - 2, zwp_idle_inhibit_manager_v1_requests, - 0, NULL, -}; - -static const struct wl_message zwp_idle_inhibitor_v1_requests[] = { - { "destroy", "", idle_inhibit_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwp_idle_inhibitor_v1_interface = { - "zwp_idle_inhibitor_v1", 1, - 1, zwp_idle_inhibitor_v1_requests, - 0, NULL, -}; - diff --git a/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol.h b/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol.h deleted file mode 100644 index ef97ceb86..000000000 --- a/pkg/glfw/wayland-headers/idle-inhibit-unstable-v1-client-protocol.h +++ /dev/null @@ -1,232 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef IDLE_INHIBIT_UNSTABLE_V1_CLIENT_PROTOCOL_H -#define IDLE_INHIBIT_UNSTABLE_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_idle_inhibit_unstable_v1 The idle_inhibit_unstable_v1 protocol - * @section page_ifaces_idle_inhibit_unstable_v1 Interfaces - * - @subpage page_iface_zwp_idle_inhibit_manager_v1 - control behavior when display idles - * - @subpage page_iface_zwp_idle_inhibitor_v1 - context object for inhibiting idle behavior - * @section page_copyright_idle_inhibit_unstable_v1 Copyright - *
- *
- * Copyright © 2015 Samsung Electronics Co., Ltd
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_surface; -struct zwp_idle_inhibit_manager_v1; -struct zwp_idle_inhibitor_v1; - -#ifndef ZWP_IDLE_INHIBIT_MANAGER_V1_INTERFACE -#define ZWP_IDLE_INHIBIT_MANAGER_V1_INTERFACE -/** - * @page page_iface_zwp_idle_inhibit_manager_v1 zwp_idle_inhibit_manager_v1 - * @section page_iface_zwp_idle_inhibit_manager_v1_desc Description - * - * This interface permits inhibiting the idle behavior such as screen - * blanking, locking, and screensaving. The client binds the idle manager - * globally, then creates idle-inhibitor objects for each surface. - * - * Warning! The protocol described in this file is experimental and - * backward incompatible changes may be made. Backward compatible changes - * may be added together with the corresponding interface version bump. - * Backward incompatible changes are done by bumping the version number in - * the protocol and interface names and resetting the interface version. - * Once the protocol is to be declared stable, the 'z' prefix and the - * version number in the protocol and interface names are removed and the - * interface version number is reset. - * @section page_iface_zwp_idle_inhibit_manager_v1_api API - * See @ref iface_zwp_idle_inhibit_manager_v1. - */ -/** - * @defgroup iface_zwp_idle_inhibit_manager_v1 The zwp_idle_inhibit_manager_v1 interface - * - * This interface permits inhibiting the idle behavior such as screen - * blanking, locking, and screensaving. The client binds the idle manager - * globally, then creates idle-inhibitor objects for each surface. - * - * Warning! The protocol described in this file is experimental and - * backward incompatible changes may be made. Backward compatible changes - * may be added together with the corresponding interface version bump. - * Backward incompatible changes are done by bumping the version number in - * the protocol and interface names and resetting the interface version. - * Once the protocol is to be declared stable, the 'z' prefix and the - * version number in the protocol and interface names are removed and the - * interface version number is reset. - */ -extern const struct wl_interface zwp_idle_inhibit_manager_v1_interface; -#endif -#ifndef ZWP_IDLE_INHIBITOR_V1_INTERFACE -#define ZWP_IDLE_INHIBITOR_V1_INTERFACE -/** - * @page page_iface_zwp_idle_inhibitor_v1 zwp_idle_inhibitor_v1 - * @section page_iface_zwp_idle_inhibitor_v1_desc Description - * - * An idle inhibitor prevents the output that the associated surface is - * visible on from being set to a state where it is not visually usable due - * to lack of user interaction (e.g. blanked, dimmed, locked, set to power - * save, etc.) Any screensaver processes are also blocked from displaying. - * - * If the surface is destroyed, unmapped, becomes occluded, loses - * visibility, or otherwise becomes not visually relevant for the user, the - * idle inhibitor will not be honored by the compositor; if the surface - * subsequently regains visibility the inhibitor takes effect once again. - * Likewise, the inhibitor isn't honored if the system was already idled at - * the time the inhibitor was established, although if the system later - * de-idles and re-idles the inhibitor will take effect. - * @section page_iface_zwp_idle_inhibitor_v1_api API - * See @ref iface_zwp_idle_inhibitor_v1. - */ -/** - * @defgroup iface_zwp_idle_inhibitor_v1 The zwp_idle_inhibitor_v1 interface - * - * An idle inhibitor prevents the output that the associated surface is - * visible on from being set to a state where it is not visually usable due - * to lack of user interaction (e.g. blanked, dimmed, locked, set to power - * save, etc.) Any screensaver processes are also blocked from displaying. - * - * If the surface is destroyed, unmapped, becomes occluded, loses - * visibility, or otherwise becomes not visually relevant for the user, the - * idle inhibitor will not be honored by the compositor; if the surface - * subsequently regains visibility the inhibitor takes effect once again. - * Likewise, the inhibitor isn't honored if the system was already idled at - * the time the inhibitor was established, although if the system later - * de-idles and re-idles the inhibitor will take effect. - */ -extern const struct wl_interface zwp_idle_inhibitor_v1_interface; -#endif - -#define ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY 0 -#define ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR 1 - - -/** - * @ingroup iface_zwp_idle_inhibit_manager_v1 - */ -#define ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_idle_inhibit_manager_v1 - */ -#define ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR_SINCE_VERSION 1 - -/** @ingroup iface_zwp_idle_inhibit_manager_v1 */ -static inline void -zwp_idle_inhibit_manager_v1_set_user_data(struct zwp_idle_inhibit_manager_v1 *zwp_idle_inhibit_manager_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_idle_inhibit_manager_v1, user_data); -} - -/** @ingroup iface_zwp_idle_inhibit_manager_v1 */ -static inline void * -zwp_idle_inhibit_manager_v1_get_user_data(struct zwp_idle_inhibit_manager_v1 *zwp_idle_inhibit_manager_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_idle_inhibit_manager_v1); -} - -static inline uint32_t -zwp_idle_inhibit_manager_v1_get_version(struct zwp_idle_inhibit_manager_v1 *zwp_idle_inhibit_manager_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_idle_inhibit_manager_v1); -} - -/** - * @ingroup iface_zwp_idle_inhibit_manager_v1 - * - * Destroy the inhibit manager. - */ -static inline void -zwp_idle_inhibit_manager_v1_destroy(struct zwp_idle_inhibit_manager_v1 *zwp_idle_inhibit_manager_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_idle_inhibit_manager_v1, - ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_idle_inhibit_manager_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zwp_idle_inhibit_manager_v1 - * - * Create a new inhibitor object associated with the given surface. - */ -static inline struct zwp_idle_inhibitor_v1 * -zwp_idle_inhibit_manager_v1_create_inhibitor(struct zwp_idle_inhibit_manager_v1 *zwp_idle_inhibit_manager_v1, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_idle_inhibit_manager_v1, - ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR, &zwp_idle_inhibitor_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_idle_inhibit_manager_v1), 0, NULL, surface); - - return (struct zwp_idle_inhibitor_v1 *) id; -} - -#define ZWP_IDLE_INHIBITOR_V1_DESTROY 0 - - -/** - * @ingroup iface_zwp_idle_inhibitor_v1 - */ -#define ZWP_IDLE_INHIBITOR_V1_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_zwp_idle_inhibitor_v1 */ -static inline void -zwp_idle_inhibitor_v1_set_user_data(struct zwp_idle_inhibitor_v1 *zwp_idle_inhibitor_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_idle_inhibitor_v1, user_data); -} - -/** @ingroup iface_zwp_idle_inhibitor_v1 */ -static inline void * -zwp_idle_inhibitor_v1_get_user_data(struct zwp_idle_inhibitor_v1 *zwp_idle_inhibitor_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_idle_inhibitor_v1); -} - -static inline uint32_t -zwp_idle_inhibitor_v1_get_version(struct zwp_idle_inhibitor_v1 *zwp_idle_inhibitor_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_idle_inhibitor_v1); -} - -/** - * @ingroup iface_zwp_idle_inhibitor_v1 - * - * Remove the inhibitor effect from the associated wl_surface. - */ -static inline void -zwp_idle_inhibitor_v1_destroy(struct zwp_idle_inhibitor_v1 *zwp_idle_inhibitor_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_idle_inhibitor_v1, - ZWP_IDLE_INHIBITOR_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_idle_inhibitor_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol-code.h deleted file mode 100644 index 4184538d5..000000000 --- a/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol-code.h +++ /dev/null @@ -1,109 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2014 Jonas Ådahl - * Copyright © 2015 Red Hat Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_pointer_interface; -extern const struct wl_interface wl_region_interface; -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface zwp_confined_pointer_v1_interface; -extern const struct wl_interface zwp_locked_pointer_v1_interface; - -static const struct wl_interface *pointer_constraints_unstable_v1_types[] = { - NULL, - NULL, - &zwp_locked_pointer_v1_interface, - &wl_surface_interface, - &wl_pointer_interface, - &wl_region_interface, - NULL, - &zwp_confined_pointer_v1_interface, - &wl_surface_interface, - &wl_pointer_interface, - &wl_region_interface, - NULL, - &wl_region_interface, - &wl_region_interface, -}; - -static const struct wl_message zwp_pointer_constraints_v1_requests[] = { - { "destroy", "", pointer_constraints_unstable_v1_types + 0 }, - { "lock_pointer", "noo?ou", pointer_constraints_unstable_v1_types + 2 }, - { "confine_pointer", "noo?ou", pointer_constraints_unstable_v1_types + 7 }, -}; - -WL_PRIVATE const struct wl_interface zwp_pointer_constraints_v1_interface = { - "zwp_pointer_constraints_v1", 1, - 3, zwp_pointer_constraints_v1_requests, - 0, NULL, -}; - -static const struct wl_message zwp_locked_pointer_v1_requests[] = { - { "destroy", "", pointer_constraints_unstable_v1_types + 0 }, - { "set_cursor_position_hint", "ff", pointer_constraints_unstable_v1_types + 0 }, - { "set_region", "?o", pointer_constraints_unstable_v1_types + 12 }, -}; - -static const struct wl_message zwp_locked_pointer_v1_events[] = { - { "locked", "", pointer_constraints_unstable_v1_types + 0 }, - { "unlocked", "", pointer_constraints_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwp_locked_pointer_v1_interface = { - "zwp_locked_pointer_v1", 1, - 3, zwp_locked_pointer_v1_requests, - 2, zwp_locked_pointer_v1_events, -}; - -static const struct wl_message zwp_confined_pointer_v1_requests[] = { - { "destroy", "", pointer_constraints_unstable_v1_types + 0 }, - { "set_region", "?o", pointer_constraints_unstable_v1_types + 13 }, -}; - -static const struct wl_message zwp_confined_pointer_v1_events[] = { - { "confined", "", pointer_constraints_unstable_v1_types + 0 }, - { "unconfined", "", pointer_constraints_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwp_confined_pointer_v1_interface = { - "zwp_confined_pointer_v1", 1, - 2, zwp_confined_pointer_v1_requests, - 2, zwp_confined_pointer_v1_events, -}; - diff --git a/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol.h b/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol.h deleted file mode 100644 index 09c05ea8c..000000000 --- a/pkg/glfw/wayland-headers/pointer-constraints-unstable-v1-client-protocol.h +++ /dev/null @@ -1,667 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef POINTER_CONSTRAINTS_UNSTABLE_V1_CLIENT_PROTOCOL_H -#define POINTER_CONSTRAINTS_UNSTABLE_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_pointer_constraints_unstable_v1 The pointer_constraints_unstable_v1 protocol - * protocol for constraining pointer motions - * - * @section page_desc_pointer_constraints_unstable_v1 Description - * - * This protocol specifies a set of interfaces used for adding constraints to - * the motion of a pointer. Possible constraints include confining pointer - * motions to a given region, or locking it to its current position. - * - * In order to constrain the pointer, a client must first bind the global - * interface "wp_pointer_constraints" which, if a compositor supports pointer - * constraints, is exposed by the registry. Using the bound global object, the - * client uses the request that corresponds to the type of constraint it wants - * to make. See wp_pointer_constraints for more details. - * - * Warning! The protocol described in this file is experimental and backward - * incompatible changes may be made. Backward compatible changes may be added - * together with the corresponding interface version bump. Backward - * incompatible changes are done by bumping the version number in the protocol - * and interface names and resetting the interface version. Once the protocol - * is to be declared stable, the 'z' prefix and the version number in the - * protocol and interface names are removed and the interface version number is - * reset. - * - * @section page_ifaces_pointer_constraints_unstable_v1 Interfaces - * - @subpage page_iface_zwp_pointer_constraints_v1 - constrain the movement of a pointer - * - @subpage page_iface_zwp_locked_pointer_v1 - receive relative pointer motion events - * - @subpage page_iface_zwp_confined_pointer_v1 - confined pointer object - * @section page_copyright_pointer_constraints_unstable_v1 Copyright - *
- *
- * Copyright © 2014      Jonas Ådahl
- * Copyright © 2015      Red Hat Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_pointer; -struct wl_region; -struct wl_surface; -struct zwp_confined_pointer_v1; -struct zwp_locked_pointer_v1; -struct zwp_pointer_constraints_v1; - -#ifndef ZWP_POINTER_CONSTRAINTS_V1_INTERFACE -#define ZWP_POINTER_CONSTRAINTS_V1_INTERFACE -/** - * @page page_iface_zwp_pointer_constraints_v1 zwp_pointer_constraints_v1 - * @section page_iface_zwp_pointer_constraints_v1_desc Description - * - * The global interface exposing pointer constraining functionality. It - * exposes two requests: lock_pointer for locking the pointer to its - * position, and confine_pointer for locking the pointer to a region. - * - * The lock_pointer and confine_pointer requests create the objects - * wp_locked_pointer and wp_confined_pointer respectively, and the client can - * use these objects to interact with the lock. - * - * For any surface, only one lock or confinement may be active across all - * wl_pointer objects of the same seat. If a lock or confinement is requested - * when another lock or confinement is active or requested on the same surface - * and with any of the wl_pointer objects of the same seat, an - * 'already_constrained' error will be raised. - * @section page_iface_zwp_pointer_constraints_v1_api API - * See @ref iface_zwp_pointer_constraints_v1. - */ -/** - * @defgroup iface_zwp_pointer_constraints_v1 The zwp_pointer_constraints_v1 interface - * - * The global interface exposing pointer constraining functionality. It - * exposes two requests: lock_pointer for locking the pointer to its - * position, and confine_pointer for locking the pointer to a region. - * - * The lock_pointer and confine_pointer requests create the objects - * wp_locked_pointer and wp_confined_pointer respectively, and the client can - * use these objects to interact with the lock. - * - * For any surface, only one lock or confinement may be active across all - * wl_pointer objects of the same seat. If a lock or confinement is requested - * when another lock or confinement is active or requested on the same surface - * and with any of the wl_pointer objects of the same seat, an - * 'already_constrained' error will be raised. - */ -extern const struct wl_interface zwp_pointer_constraints_v1_interface; -#endif -#ifndef ZWP_LOCKED_POINTER_V1_INTERFACE -#define ZWP_LOCKED_POINTER_V1_INTERFACE -/** - * @page page_iface_zwp_locked_pointer_v1 zwp_locked_pointer_v1 - * @section page_iface_zwp_locked_pointer_v1_desc Description - * - * The wp_locked_pointer interface represents a locked pointer state. - * - * While the lock of this object is active, the wl_pointer objects of the - * associated seat will not emit any wl_pointer.motion events. - * - * This object will send the event 'locked' when the lock is activated. - * Whenever the lock is activated, it is guaranteed that the locked surface - * will already have received pointer focus and that the pointer will be - * within the region passed to the request creating this object. - * - * To unlock the pointer, send the destroy request. This will also destroy - * the wp_locked_pointer object. - * - * If the compositor decides to unlock the pointer the unlocked event is - * sent. See wp_locked_pointer.unlock for details. - * - * When unlocking, the compositor may warp the cursor position to the set - * cursor position hint. If it does, it will not result in any relative - * motion events emitted via wp_relative_pointer. - * - * If the surface the lock was requested on is destroyed and the lock is not - * yet activated, the wp_locked_pointer object is now defunct and must be - * destroyed. - * @section page_iface_zwp_locked_pointer_v1_api API - * See @ref iface_zwp_locked_pointer_v1. - */ -/** - * @defgroup iface_zwp_locked_pointer_v1 The zwp_locked_pointer_v1 interface - * - * The wp_locked_pointer interface represents a locked pointer state. - * - * While the lock of this object is active, the wl_pointer objects of the - * associated seat will not emit any wl_pointer.motion events. - * - * This object will send the event 'locked' when the lock is activated. - * Whenever the lock is activated, it is guaranteed that the locked surface - * will already have received pointer focus and that the pointer will be - * within the region passed to the request creating this object. - * - * To unlock the pointer, send the destroy request. This will also destroy - * the wp_locked_pointer object. - * - * If the compositor decides to unlock the pointer the unlocked event is - * sent. See wp_locked_pointer.unlock for details. - * - * When unlocking, the compositor may warp the cursor position to the set - * cursor position hint. If it does, it will not result in any relative - * motion events emitted via wp_relative_pointer. - * - * If the surface the lock was requested on is destroyed and the lock is not - * yet activated, the wp_locked_pointer object is now defunct and must be - * destroyed. - */ -extern const struct wl_interface zwp_locked_pointer_v1_interface; -#endif -#ifndef ZWP_CONFINED_POINTER_V1_INTERFACE -#define ZWP_CONFINED_POINTER_V1_INTERFACE -/** - * @page page_iface_zwp_confined_pointer_v1 zwp_confined_pointer_v1 - * @section page_iface_zwp_confined_pointer_v1_desc Description - * - * The wp_confined_pointer interface represents a confined pointer state. - * - * This object will send the event 'confined' when the confinement is - * activated. Whenever the confinement is activated, it is guaranteed that - * the surface the pointer is confined to will already have received pointer - * focus and that the pointer will be within the region passed to the request - * creating this object. It is up to the compositor to decide whether this - * requires some user interaction and if the pointer will warp to within the - * passed region if outside. - * - * To unconfine the pointer, send the destroy request. This will also destroy - * the wp_confined_pointer object. - * - * If the compositor decides to unconfine the pointer the unconfined event is - * sent. The wp_confined_pointer object is at this point defunct and should - * be destroyed. - * @section page_iface_zwp_confined_pointer_v1_api API - * See @ref iface_zwp_confined_pointer_v1. - */ -/** - * @defgroup iface_zwp_confined_pointer_v1 The zwp_confined_pointer_v1 interface - * - * The wp_confined_pointer interface represents a confined pointer state. - * - * This object will send the event 'confined' when the confinement is - * activated. Whenever the confinement is activated, it is guaranteed that - * the surface the pointer is confined to will already have received pointer - * focus and that the pointer will be within the region passed to the request - * creating this object. It is up to the compositor to decide whether this - * requires some user interaction and if the pointer will warp to within the - * passed region if outside. - * - * To unconfine the pointer, send the destroy request. This will also destroy - * the wp_confined_pointer object. - * - * If the compositor decides to unconfine the pointer the unconfined event is - * sent. The wp_confined_pointer object is at this point defunct and should - * be destroyed. - */ -extern const struct wl_interface zwp_confined_pointer_v1_interface; -#endif - -#ifndef ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM -#define ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM -/** - * @ingroup iface_zwp_pointer_constraints_v1 - * wp_pointer_constraints error values - * - * These errors can be emitted in response to wp_pointer_constraints - * requests. - */ -enum zwp_pointer_constraints_v1_error { - /** - * pointer constraint already requested on that surface - */ - ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED = 1, -}; -#endif /* ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM */ - -#ifndef ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM -#define ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM -/** - * @ingroup iface_zwp_pointer_constraints_v1 - * constraint lifetime - * - * These values represent different lifetime semantics. They are passed - * as arguments to the factory requests to specify how the constraint - * lifetimes should be managed. - */ -enum zwp_pointer_constraints_v1_lifetime { - /** - * the pointer constraint is defunct once deactivated - * - * A oneshot pointer constraint will never reactivate once it has - * been deactivated. See the corresponding deactivation event - * (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) - * for details. - */ - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT = 1, - /** - * the pointer constraint may reactivate - * - * A persistent pointer constraint may again reactivate once it - * has been deactivated. See the corresponding deactivation event - * (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) - * for details. - */ - ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT = 2, -}; -#endif /* ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM */ - -#define ZWP_POINTER_CONSTRAINTS_V1_DESTROY 0 -#define ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER 1 -#define ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER 2 - - -/** - * @ingroup iface_zwp_pointer_constraints_v1 - */ -#define ZWP_POINTER_CONSTRAINTS_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_pointer_constraints_v1 - */ -#define ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_pointer_constraints_v1 - */ -#define ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER_SINCE_VERSION 1 - -/** @ingroup iface_zwp_pointer_constraints_v1 */ -static inline void -zwp_pointer_constraints_v1_set_user_data(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_pointer_constraints_v1, user_data); -} - -/** @ingroup iface_zwp_pointer_constraints_v1 */ -static inline void * -zwp_pointer_constraints_v1_get_user_data(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_pointer_constraints_v1); -} - -static inline uint32_t -zwp_pointer_constraints_v1_get_version(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_pointer_constraints_v1); -} - -/** - * @ingroup iface_zwp_pointer_constraints_v1 - * - * Used by the client to notify the server that it will no longer use this - * pointer constraints object. - */ -static inline void -zwp_pointer_constraints_v1_destroy(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_pointer_constraints_v1, - ZWP_POINTER_CONSTRAINTS_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_pointer_constraints_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zwp_pointer_constraints_v1 - * - * The lock_pointer request lets the client request to disable movements of - * the virtual pointer (i.e. the cursor), effectively locking the pointer - * to a position. This request may not take effect immediately; in the - * future, when the compositor deems implementation-specific constraints - * are satisfied, the pointer lock will be activated and the compositor - * sends a locked event. - * - * The protocol provides no guarantee that the constraints are ever - * satisfied, and does not require the compositor to send an error if the - * constraints cannot ever be satisfied. It is thus possible to request a - * lock that will never activate. - * - * There may not be another pointer constraint of any kind requested or - * active on the surface for any of the wl_pointer objects of the seat of - * the passed pointer when requesting a lock. If there is, an error will be - * raised. See general pointer lock documentation for more details. - * - * The intersection of the region passed with this request and the input - * region of the surface is used to determine where the pointer must be - * in order for the lock to activate. It is up to the compositor whether to - * warp the pointer or require some kind of user interaction for the lock - * to activate. If the region is null the surface input region is used. - * - * A surface may receive pointer focus without the lock being activated. - * - * The request creates a new object wp_locked_pointer which is used to - * interact with the lock as well as receive updates about its state. See - * the the description of wp_locked_pointer for further information. - * - * Note that while a pointer is locked, the wl_pointer objects of the - * corresponding seat will not emit any wl_pointer.motion events, but - * relative motion events will still be emitted via wp_relative_pointer - * objects of the same seat. wl_pointer.axis and wl_pointer.button events - * are unaffected. - */ -static inline struct zwp_locked_pointer_v1 * -zwp_pointer_constraints_v1_lock_pointer(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1, struct wl_surface *surface, struct wl_pointer *pointer, struct wl_region *region, uint32_t lifetime) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_pointer_constraints_v1, - ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER, &zwp_locked_pointer_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_pointer_constraints_v1), 0, NULL, surface, pointer, region, lifetime); - - return (struct zwp_locked_pointer_v1 *) id; -} - -/** - * @ingroup iface_zwp_pointer_constraints_v1 - * - * The confine_pointer request lets the client request to confine the - * pointer cursor to a given region. This request may not take effect - * immediately; in the future, when the compositor deems implementation- - * specific constraints are satisfied, the pointer confinement will be - * activated and the compositor sends a confined event. - * - * The intersection of the region passed with this request and the input - * region of the surface is used to determine where the pointer must be - * in order for the confinement to activate. It is up to the compositor - * whether to warp the pointer or require some kind of user interaction for - * the confinement to activate. If the region is null the surface input - * region is used. - * - * The request will create a new object wp_confined_pointer which is used - * to interact with the confinement as well as receive updates about its - * state. See the the description of wp_confined_pointer for further - * information. - */ -static inline struct zwp_confined_pointer_v1 * -zwp_pointer_constraints_v1_confine_pointer(struct zwp_pointer_constraints_v1 *zwp_pointer_constraints_v1, struct wl_surface *surface, struct wl_pointer *pointer, struct wl_region *region, uint32_t lifetime) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_pointer_constraints_v1, - ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER, &zwp_confined_pointer_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_pointer_constraints_v1), 0, NULL, surface, pointer, region, lifetime); - - return (struct zwp_confined_pointer_v1 *) id; -} - -/** - * @ingroup iface_zwp_locked_pointer_v1 - * @struct zwp_locked_pointer_v1_listener - */ -struct zwp_locked_pointer_v1_listener { - /** - * lock activation event - * - * Notification that the pointer lock of the seat's pointer is - * activated. - */ - void (*locked)(void *data, - struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1); - /** - * lock deactivation event - * - * Notification that the pointer lock of the seat's pointer is no - * longer active. If this is a oneshot pointer lock (see - * wp_pointer_constraints.lifetime) this object is now defunct and - * should be destroyed. If this is a persistent pointer lock (see - * wp_pointer_constraints.lifetime) this pointer lock may again - * reactivate in the future. - */ - void (*unlocked)(void *data, - struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1); -}; - -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -static inline int -zwp_locked_pointer_v1_add_listener(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1, - const struct zwp_locked_pointer_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) zwp_locked_pointer_v1, - (void (**)(void)) listener, data); -} - -#define ZWP_LOCKED_POINTER_V1_DESTROY 0 -#define ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT 1 -#define ZWP_LOCKED_POINTER_V1_SET_REGION 2 - -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -#define ZWP_LOCKED_POINTER_V1_LOCKED_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -#define ZWP_LOCKED_POINTER_V1_UNLOCKED_SINCE_VERSION 1 - -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -#define ZWP_LOCKED_POINTER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -#define ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_locked_pointer_v1 - */ -#define ZWP_LOCKED_POINTER_V1_SET_REGION_SINCE_VERSION 1 - -/** @ingroup iface_zwp_locked_pointer_v1 */ -static inline void -zwp_locked_pointer_v1_set_user_data(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_locked_pointer_v1, user_data); -} - -/** @ingroup iface_zwp_locked_pointer_v1 */ -static inline void * -zwp_locked_pointer_v1_get_user_data(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_locked_pointer_v1); -} - -static inline uint32_t -zwp_locked_pointer_v1_get_version(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_locked_pointer_v1); -} - -/** - * @ingroup iface_zwp_locked_pointer_v1 - * - * Destroy the locked pointer object. If applicable, the compositor will - * unlock the pointer. - */ -static inline void -zwp_locked_pointer_v1_destroy(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_locked_pointer_v1, - ZWP_LOCKED_POINTER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_locked_pointer_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zwp_locked_pointer_v1 - * - * Set the cursor position hint relative to the top left corner of the - * surface. - * - * If the client is drawing its own cursor, it should update the position - * hint to the position of its own cursor. A compositor may use this - * information to warp the pointer upon unlock in order to avoid pointer - * jumps. - * - * The cursor position hint is double buffered. The new hint will only take - * effect when the associated surface gets it pending state applied. See - * wl_surface.commit for details. - */ -static inline void -zwp_locked_pointer_v1_set_cursor_position_hint(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1, wl_fixed_t surface_x, wl_fixed_t surface_y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_locked_pointer_v1, - ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_locked_pointer_v1), 0, surface_x, surface_y); -} - -/** - * @ingroup iface_zwp_locked_pointer_v1 - * - * Set a new region used to lock the pointer. - * - * The new lock region is double-buffered. The new lock region will - * only take effect when the associated surface gets its pending state - * applied. See wl_surface.commit for details. - * - * For details about the lock region, see wp_locked_pointer. - */ -static inline void -zwp_locked_pointer_v1_set_region(struct zwp_locked_pointer_v1 *zwp_locked_pointer_v1, struct wl_region *region) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_locked_pointer_v1, - ZWP_LOCKED_POINTER_V1_SET_REGION, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_locked_pointer_v1), 0, region); -} - -/** - * @ingroup iface_zwp_confined_pointer_v1 - * @struct zwp_confined_pointer_v1_listener - */ -struct zwp_confined_pointer_v1_listener { - /** - * pointer confined - * - * Notification that the pointer confinement of the seat's - * pointer is activated. - */ - void (*confined)(void *data, - struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1); - /** - * pointer unconfined - * - * Notification that the pointer confinement of the seat's - * pointer is no longer active. If this is a oneshot pointer - * confinement (see wp_pointer_constraints.lifetime) this object is - * now defunct and should be destroyed. If this is a persistent - * pointer confinement (see wp_pointer_constraints.lifetime) this - * pointer confinement may again reactivate in the future. - */ - void (*unconfined)(void *data, - struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1); -}; - -/** - * @ingroup iface_zwp_confined_pointer_v1 - */ -static inline int -zwp_confined_pointer_v1_add_listener(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1, - const struct zwp_confined_pointer_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) zwp_confined_pointer_v1, - (void (**)(void)) listener, data); -} - -#define ZWP_CONFINED_POINTER_V1_DESTROY 0 -#define ZWP_CONFINED_POINTER_V1_SET_REGION 1 - -/** - * @ingroup iface_zwp_confined_pointer_v1 - */ -#define ZWP_CONFINED_POINTER_V1_CONFINED_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_confined_pointer_v1 - */ -#define ZWP_CONFINED_POINTER_V1_UNCONFINED_SINCE_VERSION 1 - -/** - * @ingroup iface_zwp_confined_pointer_v1 - */ -#define ZWP_CONFINED_POINTER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_confined_pointer_v1 - */ -#define ZWP_CONFINED_POINTER_V1_SET_REGION_SINCE_VERSION 1 - -/** @ingroup iface_zwp_confined_pointer_v1 */ -static inline void -zwp_confined_pointer_v1_set_user_data(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_confined_pointer_v1, user_data); -} - -/** @ingroup iface_zwp_confined_pointer_v1 */ -static inline void * -zwp_confined_pointer_v1_get_user_data(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_confined_pointer_v1); -} - -static inline uint32_t -zwp_confined_pointer_v1_get_version(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_confined_pointer_v1); -} - -/** - * @ingroup iface_zwp_confined_pointer_v1 - * - * Destroy the confined pointer object. If applicable, the compositor will - * unconfine the pointer. - */ -static inline void -zwp_confined_pointer_v1_destroy(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_confined_pointer_v1, - ZWP_CONFINED_POINTER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_confined_pointer_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zwp_confined_pointer_v1 - * - * Set a new region used to confine the pointer. - * - * The new confine region is double-buffered. The new confine region will - * only take effect when the associated surface gets its pending state - * applied. See wl_surface.commit for details. - * - * If the confinement is active when the new confinement region is applied - * and the pointer ends up outside of newly applied region, the pointer may - * warped to a position within the new confinement region. If warped, a - * wl_pointer.motion event will be emitted, but no - * wp_relative_pointer.relative_motion event. - * - * The compositor may also, instead of using the new region, unconfine the - * pointer. - * - * For details about the confine region, see wp_confined_pointer. - */ -static inline void -zwp_confined_pointer_v1_set_region(struct zwp_confined_pointer_v1 *zwp_confined_pointer_v1, struct wl_region *region) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_confined_pointer_v1, - ZWP_CONFINED_POINTER_V1_SET_REGION, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_confined_pointer_v1), 0, region); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol-code.h deleted file mode 100644 index 605149b2a..000000000 --- a/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol-code.h +++ /dev/null @@ -1,80 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2014 Jonas Ådahl - * Copyright © 2015 Red Hat Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_pointer_interface; -extern const struct wl_interface zwp_relative_pointer_v1_interface; - -static const struct wl_interface *relative_pointer_unstable_v1_types[] = { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - &zwp_relative_pointer_v1_interface, - &wl_pointer_interface, -}; - -static const struct wl_message zwp_relative_pointer_manager_v1_requests[] = { - { "destroy", "", relative_pointer_unstable_v1_types + 0 }, - { "get_relative_pointer", "no", relative_pointer_unstable_v1_types + 6 }, -}; - -WL_PRIVATE const struct wl_interface zwp_relative_pointer_manager_v1_interface = { - "zwp_relative_pointer_manager_v1", 1, - 2, zwp_relative_pointer_manager_v1_requests, - 0, NULL, -}; - -static const struct wl_message zwp_relative_pointer_v1_requests[] = { - { "destroy", "", relative_pointer_unstable_v1_types + 0 }, -}; - -static const struct wl_message zwp_relative_pointer_v1_events[] = { - { "relative_motion", "uuffff", relative_pointer_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwp_relative_pointer_v1_interface = { - "zwp_relative_pointer_v1", 1, - 1, zwp_relative_pointer_v1_requests, - 1, zwp_relative_pointer_v1_events, -}; - diff --git a/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol.h b/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol.h deleted file mode 100644 index 5c79482f9..000000000 --- a/pkg/glfw/wayland-headers/relative-pointer-unstable-v1-client-protocol.h +++ /dev/null @@ -1,297 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef RELATIVE_POINTER_UNSTABLE_V1_CLIENT_PROTOCOL_H -#define RELATIVE_POINTER_UNSTABLE_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_relative_pointer_unstable_v1 The relative_pointer_unstable_v1 protocol - * protocol for relative pointer motion events - * - * @section page_desc_relative_pointer_unstable_v1 Description - * - * This protocol specifies a set of interfaces used for making clients able to - * receive relative pointer events not obstructed by barriers (such as the - * monitor edge or other pointer barriers). - * - * To start receiving relative pointer events, a client must first bind the - * global interface "wp_relative_pointer_manager" which, if a compositor - * supports relative pointer motion events, is exposed by the registry. After - * having created the relative pointer manager proxy object, the client uses - * it to create the actual relative pointer object using the - * "get_relative_pointer" request given a wl_pointer. The relative pointer - * motion events will then, when applicable, be transmitted via the proxy of - * the newly created relative pointer object. See the documentation of the - * relative pointer interface for more details. - * - * Warning! The protocol described in this file is experimental and backward - * incompatible changes may be made. Backward compatible changes may be added - * together with the corresponding interface version bump. Backward - * incompatible changes are done by bumping the version number in the protocol - * and interface names and resetting the interface version. Once the protocol - * is to be declared stable, the 'z' prefix and the version number in the - * protocol and interface names are removed and the interface version number is - * reset. - * - * @section page_ifaces_relative_pointer_unstable_v1 Interfaces - * - @subpage page_iface_zwp_relative_pointer_manager_v1 - get relative pointer objects - * - @subpage page_iface_zwp_relative_pointer_v1 - relative pointer object - * @section page_copyright_relative_pointer_unstable_v1 Copyright - *
- *
- * Copyright © 2014      Jonas Ådahl
- * Copyright © 2015      Red Hat Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_pointer; -struct zwp_relative_pointer_manager_v1; -struct zwp_relative_pointer_v1; - -#ifndef ZWP_RELATIVE_POINTER_MANAGER_V1_INTERFACE -#define ZWP_RELATIVE_POINTER_MANAGER_V1_INTERFACE -/** - * @page page_iface_zwp_relative_pointer_manager_v1 zwp_relative_pointer_manager_v1 - * @section page_iface_zwp_relative_pointer_manager_v1_desc Description - * - * A global interface used for getting the relative pointer object for a - * given pointer. - * @section page_iface_zwp_relative_pointer_manager_v1_api API - * See @ref iface_zwp_relative_pointer_manager_v1. - */ -/** - * @defgroup iface_zwp_relative_pointer_manager_v1 The zwp_relative_pointer_manager_v1 interface - * - * A global interface used for getting the relative pointer object for a - * given pointer. - */ -extern const struct wl_interface zwp_relative_pointer_manager_v1_interface; -#endif -#ifndef ZWP_RELATIVE_POINTER_V1_INTERFACE -#define ZWP_RELATIVE_POINTER_V1_INTERFACE -/** - * @page page_iface_zwp_relative_pointer_v1 zwp_relative_pointer_v1 - * @section page_iface_zwp_relative_pointer_v1_desc Description - * - * A wp_relative_pointer object is an extension to the wl_pointer interface - * used for emitting relative pointer events. It shares the same focus as - * wl_pointer objects of the same seat and will only emit events when it has - * focus. - * @section page_iface_zwp_relative_pointer_v1_api API - * See @ref iface_zwp_relative_pointer_v1. - */ -/** - * @defgroup iface_zwp_relative_pointer_v1 The zwp_relative_pointer_v1 interface - * - * A wp_relative_pointer object is an extension to the wl_pointer interface - * used for emitting relative pointer events. It shares the same focus as - * wl_pointer objects of the same seat and will only emit events when it has - * focus. - */ -extern const struct wl_interface zwp_relative_pointer_v1_interface; -#endif - -#define ZWP_RELATIVE_POINTER_MANAGER_V1_DESTROY 0 -#define ZWP_RELATIVE_POINTER_MANAGER_V1_GET_RELATIVE_POINTER 1 - - -/** - * @ingroup iface_zwp_relative_pointer_manager_v1 - */ -#define ZWP_RELATIVE_POINTER_MANAGER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zwp_relative_pointer_manager_v1 - */ -#define ZWP_RELATIVE_POINTER_MANAGER_V1_GET_RELATIVE_POINTER_SINCE_VERSION 1 - -/** @ingroup iface_zwp_relative_pointer_manager_v1 */ -static inline void -zwp_relative_pointer_manager_v1_set_user_data(struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_relative_pointer_manager_v1, user_data); -} - -/** @ingroup iface_zwp_relative_pointer_manager_v1 */ -static inline void * -zwp_relative_pointer_manager_v1_get_user_data(struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_relative_pointer_manager_v1); -} - -static inline uint32_t -zwp_relative_pointer_manager_v1_get_version(struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_relative_pointer_manager_v1); -} - -/** - * @ingroup iface_zwp_relative_pointer_manager_v1 - * - * Used by the client to notify the server that it will no longer use this - * relative pointer manager object. - */ -static inline void -zwp_relative_pointer_manager_v1_destroy(struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_relative_pointer_manager_v1, - ZWP_RELATIVE_POINTER_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_relative_pointer_manager_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zwp_relative_pointer_manager_v1 - * - * Create a relative pointer interface given a wl_pointer object. See the - * wp_relative_pointer interface for more details. - */ -static inline struct zwp_relative_pointer_v1 * -zwp_relative_pointer_manager_v1_get_relative_pointer(struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager_v1, struct wl_pointer *pointer) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zwp_relative_pointer_manager_v1, - ZWP_RELATIVE_POINTER_MANAGER_V1_GET_RELATIVE_POINTER, &zwp_relative_pointer_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwp_relative_pointer_manager_v1), 0, NULL, pointer); - - return (struct zwp_relative_pointer_v1 *) id; -} - -/** - * @ingroup iface_zwp_relative_pointer_v1 - * @struct zwp_relative_pointer_v1_listener - */ -struct zwp_relative_pointer_v1_listener { - /** - * relative pointer motion - * - * Relative x/y pointer motion from the pointer of the seat - * associated with this object. - * - * A relative motion is in the same dimension as regular wl_pointer - * motion events, except they do not represent an absolute - * position. For example, moving a pointer from (x, y) to (x', y') - * would have the equivalent relative motion (x' - x, y' - y). If a - * pointer motion caused the absolute pointer position to be - * clipped by for example the edge of the monitor, the relative - * motion is unaffected by the clipping and will represent the - * unclipped motion. - * - * This event also contains non-accelerated motion deltas. The - * non-accelerated delta is, when applicable, the regular pointer - * motion delta as it was before having applied motion acceleration - * and other transformations such as normalization. - * - * Note that the non-accelerated delta does not represent 'raw' - * events as they were read from some device. Pointer motion - * acceleration is device- and configuration-specific and - * non-accelerated deltas and accelerated deltas may have the same - * value on some devices. - * - * Relative motions are not coupled to wl_pointer.motion events, - * and can be sent in combination with such events, but also - * independently. There may also be scenarios where - * wl_pointer.motion is sent, but there is no relative motion. The - * order of an absolute and relative motion event originating from - * the same physical motion is not guaranteed. - * - * If the client needs button events or focus state, it can receive - * them from a wl_pointer object of the same seat that the - * wp_relative_pointer object is associated with. - * @param utime_hi high 32 bits of a 64 bit timestamp with microsecond granularity - * @param utime_lo low 32 bits of a 64 bit timestamp with microsecond granularity - * @param dx the x component of the motion vector - * @param dy the y component of the motion vector - * @param dx_unaccel the x component of the unaccelerated motion vector - * @param dy_unaccel the y component of the unaccelerated motion vector - */ - void (*relative_motion)(void *data, - struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1, - uint32_t utime_hi, - uint32_t utime_lo, - wl_fixed_t dx, - wl_fixed_t dy, - wl_fixed_t dx_unaccel, - wl_fixed_t dy_unaccel); -}; - -/** - * @ingroup iface_zwp_relative_pointer_v1 - */ -static inline int -zwp_relative_pointer_v1_add_listener(struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1, - const struct zwp_relative_pointer_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) zwp_relative_pointer_v1, - (void (**)(void)) listener, data); -} - -#define ZWP_RELATIVE_POINTER_V1_DESTROY 0 - -/** - * @ingroup iface_zwp_relative_pointer_v1 - */ -#define ZWP_RELATIVE_POINTER_V1_RELATIVE_MOTION_SINCE_VERSION 1 - -/** - * @ingroup iface_zwp_relative_pointer_v1 - */ -#define ZWP_RELATIVE_POINTER_V1_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_zwp_relative_pointer_v1 */ -static inline void -zwp_relative_pointer_v1_set_user_data(struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwp_relative_pointer_v1, user_data); -} - -/** @ingroup iface_zwp_relative_pointer_v1 */ -static inline void * -zwp_relative_pointer_v1_get_user_data(struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwp_relative_pointer_v1); -} - -static inline uint32_t -zwp_relative_pointer_v1_get_version(struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwp_relative_pointer_v1); -} - -/** - * @ingroup iface_zwp_relative_pointer_v1 - */ -static inline void -zwp_relative_pointer_v1_destroy(struct zwp_relative_pointer_v1 *zwp_relative_pointer_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwp_relative_pointer_v1, - ZWP_RELATIVE_POINTER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwp_relative_pointer_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/viewporter-client-protocol-code.h b/pkg/glfw/wayland-headers/viewporter-client-protocol-code.h deleted file mode 100644 index d6858580e..000000000 --- a/pkg/glfw/wayland-headers/viewporter-client-protocol-code.h +++ /dev/null @@ -1,75 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2013-2016 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface wp_viewport_interface; - -static const struct wl_interface *viewporter_types[] = { - NULL, - NULL, - NULL, - NULL, - &wp_viewport_interface, - &wl_surface_interface, -}; - -static const struct wl_message wp_viewporter_requests[] = { - { "destroy", "", viewporter_types + 0 }, - { "get_viewport", "no", viewporter_types + 4 }, -}; - -WL_PRIVATE const struct wl_interface wp_viewporter_interface = { - "wp_viewporter", 1, - 2, wp_viewporter_requests, - 0, NULL, -}; - -static const struct wl_message wp_viewport_requests[] = { - { "destroy", "", viewporter_types + 0 }, - { "set_source", "ffff", viewporter_types + 0 }, - { "set_destination", "ii", viewporter_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wp_viewport_interface = { - "wp_viewport", 1, - 3, wp_viewport_requests, - 0, NULL, -}; - diff --git a/pkg/glfw/wayland-headers/viewporter-client-protocol.h b/pkg/glfw/wayland-headers/viewporter-client-protocol.h deleted file mode 100644 index afe60ef59..000000000 --- a/pkg/glfw/wayland-headers/viewporter-client-protocol.h +++ /dev/null @@ -1,398 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef VIEWPORTER_CLIENT_PROTOCOL_H -#define VIEWPORTER_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_viewporter The viewporter protocol - * @section page_ifaces_viewporter Interfaces - * - @subpage page_iface_wp_viewporter - surface cropping and scaling - * - @subpage page_iface_wp_viewport - crop and scale interface to a wl_surface - * @section page_copyright_viewporter Copyright - *
- *
- * Copyright © 2013-2016 Collabora, Ltd.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_surface; -struct wp_viewport; -struct wp_viewporter; - -#ifndef WP_VIEWPORTER_INTERFACE -#define WP_VIEWPORTER_INTERFACE -/** - * @page page_iface_wp_viewporter wp_viewporter - * @section page_iface_wp_viewporter_desc Description - * - * The global interface exposing surface cropping and scaling - * capabilities is used to instantiate an interface extension for a - * wl_surface object. This extended interface will then allow - * cropping and scaling the surface contents, effectively - * disconnecting the direct relationship between the buffer and the - * surface size. - * @section page_iface_wp_viewporter_api API - * See @ref iface_wp_viewporter. - */ -/** - * @defgroup iface_wp_viewporter The wp_viewporter interface - * - * The global interface exposing surface cropping and scaling - * capabilities is used to instantiate an interface extension for a - * wl_surface object. This extended interface will then allow - * cropping and scaling the surface contents, effectively - * disconnecting the direct relationship between the buffer and the - * surface size. - */ -extern const struct wl_interface wp_viewporter_interface; -#endif -#ifndef WP_VIEWPORT_INTERFACE -#define WP_VIEWPORT_INTERFACE -/** - * @page page_iface_wp_viewport wp_viewport - * @section page_iface_wp_viewport_desc Description - * - * An additional interface to a wl_surface object, which allows the - * client to specify the cropping and scaling of the surface - * contents. - * - * This interface works with two concepts: the source rectangle (src_x, - * src_y, src_width, src_height), and the destination size (dst_width, - * dst_height). The contents of the source rectangle are scaled to the - * destination size, and content outside the source rectangle is ignored. - * This state is double-buffered, and is applied on the next - * wl_surface.commit. - * - * The two parts of crop and scale state are independent: the source - * rectangle, and the destination size. Initially both are unset, that - * is, no scaling is applied. The whole of the current wl_buffer is - * used as the source, and the surface size is as defined in - * wl_surface.attach. - * - * If the destination size is set, it causes the surface size to become - * dst_width, dst_height. The source (rectangle) is scaled to exactly - * this size. This overrides whatever the attached wl_buffer size is, - * unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface - * has no content and therefore no size. Otherwise, the size is always - * at least 1x1 in surface local coordinates. - * - * If the source rectangle is set, it defines what area of the wl_buffer is - * taken as the source. If the source rectangle is set and the destination - * size is not set, then src_width and src_height must be integers, and the - * surface size becomes the source rectangle size. This results in cropping - * without scaling. If src_width or src_height are not integers and - * destination size is not set, the bad_size protocol error is raised when - * the surface state is applied. - * - * The coordinate transformations from buffer pixel coordinates up to - * the surface-local coordinates happen in the following order: - * 1. buffer_transform (wl_surface.set_buffer_transform) - * 2. buffer_scale (wl_surface.set_buffer_scale) - * 3. crop and scale (wp_viewport.set*) - * This means, that the source rectangle coordinates of crop and scale - * are given in the coordinates after the buffer transform and scale, - * i.e. in the coordinates that would be the surface-local coordinates - * if the crop and scale was not applied. - * - * If src_x or src_y are negative, the bad_value protocol error is raised. - * Otherwise, if the source rectangle is partially or completely outside of - * the non-NULL wl_buffer, then the out_of_buffer protocol error is raised - * when the surface state is applied. A NULL wl_buffer does not raise the - * out_of_buffer error. - * - * If the wl_surface associated with the wp_viewport is destroyed, - * all wp_viewport requests except 'destroy' raise the protocol error - * no_surface. - * - * If the wp_viewport object is destroyed, the crop and scale - * state is removed from the wl_surface. The change will be applied - * on the next wl_surface.commit. - * @section page_iface_wp_viewport_api API - * See @ref iface_wp_viewport. - */ -/** - * @defgroup iface_wp_viewport The wp_viewport interface - * - * An additional interface to a wl_surface object, which allows the - * client to specify the cropping and scaling of the surface - * contents. - * - * This interface works with two concepts: the source rectangle (src_x, - * src_y, src_width, src_height), and the destination size (dst_width, - * dst_height). The contents of the source rectangle are scaled to the - * destination size, and content outside the source rectangle is ignored. - * This state is double-buffered, and is applied on the next - * wl_surface.commit. - * - * The two parts of crop and scale state are independent: the source - * rectangle, and the destination size. Initially both are unset, that - * is, no scaling is applied. The whole of the current wl_buffer is - * used as the source, and the surface size is as defined in - * wl_surface.attach. - * - * If the destination size is set, it causes the surface size to become - * dst_width, dst_height. The source (rectangle) is scaled to exactly - * this size. This overrides whatever the attached wl_buffer size is, - * unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface - * has no content and therefore no size. Otherwise, the size is always - * at least 1x1 in surface local coordinates. - * - * If the source rectangle is set, it defines what area of the wl_buffer is - * taken as the source. If the source rectangle is set and the destination - * size is not set, then src_width and src_height must be integers, and the - * surface size becomes the source rectangle size. This results in cropping - * without scaling. If src_width or src_height are not integers and - * destination size is not set, the bad_size protocol error is raised when - * the surface state is applied. - * - * The coordinate transformations from buffer pixel coordinates up to - * the surface-local coordinates happen in the following order: - * 1. buffer_transform (wl_surface.set_buffer_transform) - * 2. buffer_scale (wl_surface.set_buffer_scale) - * 3. crop and scale (wp_viewport.set*) - * This means, that the source rectangle coordinates of crop and scale - * are given in the coordinates after the buffer transform and scale, - * i.e. in the coordinates that would be the surface-local coordinates - * if the crop and scale was not applied. - * - * If src_x or src_y are negative, the bad_value protocol error is raised. - * Otherwise, if the source rectangle is partially or completely outside of - * the non-NULL wl_buffer, then the out_of_buffer protocol error is raised - * when the surface state is applied. A NULL wl_buffer does not raise the - * out_of_buffer error. - * - * If the wl_surface associated with the wp_viewport is destroyed, - * all wp_viewport requests except 'destroy' raise the protocol error - * no_surface. - * - * If the wp_viewport object is destroyed, the crop and scale - * state is removed from the wl_surface. The change will be applied - * on the next wl_surface.commit. - */ -extern const struct wl_interface wp_viewport_interface; -#endif - -#ifndef WP_VIEWPORTER_ERROR_ENUM -#define WP_VIEWPORTER_ERROR_ENUM -enum wp_viewporter_error { - /** - * the surface already has a viewport object associated - */ - WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS = 0, -}; -#endif /* WP_VIEWPORTER_ERROR_ENUM */ - -#define WP_VIEWPORTER_DESTROY 0 -#define WP_VIEWPORTER_GET_VIEWPORT 1 - - -/** - * @ingroup iface_wp_viewporter - */ -#define WP_VIEWPORTER_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wp_viewporter - */ -#define WP_VIEWPORTER_GET_VIEWPORT_SINCE_VERSION 1 - -/** @ingroup iface_wp_viewporter */ -static inline void -wp_viewporter_set_user_data(struct wp_viewporter *wp_viewporter, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wp_viewporter, user_data); -} - -/** @ingroup iface_wp_viewporter */ -static inline void * -wp_viewporter_get_user_data(struct wp_viewporter *wp_viewporter) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wp_viewporter); -} - -static inline uint32_t -wp_viewporter_get_version(struct wp_viewporter *wp_viewporter) -{ - return wl_proxy_get_version((struct wl_proxy *) wp_viewporter); -} - -/** - * @ingroup iface_wp_viewporter - * - * Informs the server that the client will not be using this - * protocol object anymore. This does not affect any other objects, - * wp_viewport objects included. - */ -static inline void -wp_viewporter_destroy(struct wp_viewporter *wp_viewporter) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_viewporter, - WP_VIEWPORTER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewporter), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wp_viewporter - * - * Instantiate an interface extension for the given wl_surface to - * crop and scale its content. If the given wl_surface already has - * a wp_viewport object associated, the viewport_exists - * protocol error is raised. - */ -static inline struct wp_viewport * -wp_viewporter_get_viewport(struct wp_viewporter *wp_viewporter, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wp_viewporter, - WP_VIEWPORTER_GET_VIEWPORT, &wp_viewport_interface, wl_proxy_get_version((struct wl_proxy *) wp_viewporter), 0, NULL, surface); - - return (struct wp_viewport *) id; -} - -#ifndef WP_VIEWPORT_ERROR_ENUM -#define WP_VIEWPORT_ERROR_ENUM -enum wp_viewport_error { - /** - * negative or zero values in width or height - */ - WP_VIEWPORT_ERROR_BAD_VALUE = 0, - /** - * destination size is not integer - */ - WP_VIEWPORT_ERROR_BAD_SIZE = 1, - /** - * source rectangle extends outside of the content area - */ - WP_VIEWPORT_ERROR_OUT_OF_BUFFER = 2, - /** - * the wl_surface was destroyed - */ - WP_VIEWPORT_ERROR_NO_SURFACE = 3, -}; -#endif /* WP_VIEWPORT_ERROR_ENUM */ - -#define WP_VIEWPORT_DESTROY 0 -#define WP_VIEWPORT_SET_SOURCE 1 -#define WP_VIEWPORT_SET_DESTINATION 2 - - -/** - * @ingroup iface_wp_viewport - */ -#define WP_VIEWPORT_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wp_viewport - */ -#define WP_VIEWPORT_SET_SOURCE_SINCE_VERSION 1 -/** - * @ingroup iface_wp_viewport - */ -#define WP_VIEWPORT_SET_DESTINATION_SINCE_VERSION 1 - -/** @ingroup iface_wp_viewport */ -static inline void -wp_viewport_set_user_data(struct wp_viewport *wp_viewport, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wp_viewport, user_data); -} - -/** @ingroup iface_wp_viewport */ -static inline void * -wp_viewport_get_user_data(struct wp_viewport *wp_viewport) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wp_viewport); -} - -static inline uint32_t -wp_viewport_get_version(struct wp_viewport *wp_viewport) -{ - return wl_proxy_get_version((struct wl_proxy *) wp_viewport); -} - -/** - * @ingroup iface_wp_viewport - * - * The associated wl_surface's crop and scale state is removed. - * The change is applied on the next wl_surface.commit. - */ -static inline void -wp_viewport_destroy(struct wp_viewport *wp_viewport) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_viewport, - WP_VIEWPORT_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewport), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wp_viewport - * - * Set the source rectangle of the associated wl_surface. See - * wp_viewport for the description, and relation to the wl_buffer - * size. - * - * If all of x, y, width and height are -1.0, the source rectangle is - * unset instead. Any other set of values where width or height are zero - * or negative, or x or y are negative, raise the bad_value protocol - * error. - * - * The crop and scale state is double-buffered state, and will be - * applied on the next wl_surface.commit. - */ -static inline void -wp_viewport_set_source(struct wp_viewport *wp_viewport, wl_fixed_t x, wl_fixed_t y, wl_fixed_t width, wl_fixed_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_viewport, - WP_VIEWPORT_SET_SOURCE, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewport), 0, x, y, width, height); -} - -/** - * @ingroup iface_wp_viewport - * - * Set the destination size of the associated wl_surface. See - * wp_viewport for the description, and relation to the wl_buffer - * size. - * - * If width is -1 and height is -1, the destination size is unset - * instead. Any other pair of values for width and height that - * contains zero or negative values raises the bad_value protocol - * error. - * - * The crop and scale state is double-buffered state, and will be - * applied on the next wl_surface.commit. - */ -static inline void -wp_viewport_set_destination(struct wp_viewport *wp_viewport, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wp_viewport, - WP_VIEWPORT_SET_DESTINATION, NULL, wl_proxy_get_version((struct wl_proxy *) wp_viewport), 0, width, height); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/wayland-client-protocol-code.h b/pkg/glfw/wayland-headers/wayland-client-protocol-code.h deleted file mode 100644 index 7ea8e7c66..000000000 --- a/pkg/glfw/wayland-headers/wayland-client-protocol-code.h +++ /dev/null @@ -1,525 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2008-2011 Kristian Høgsberg - * Copyright © 2010-2011 Intel Corporation - * Copyright © 2012-2013 Collabora, Ltd. - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice (including the - * next paragraph) shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_buffer_interface; -extern const struct wl_interface wl_callback_interface; -extern const struct wl_interface wl_data_device_interface; -extern const struct wl_interface wl_data_offer_interface; -extern const struct wl_interface wl_data_source_interface; -extern const struct wl_interface wl_keyboard_interface; -extern const struct wl_interface wl_output_interface; -extern const struct wl_interface wl_pointer_interface; -extern const struct wl_interface wl_region_interface; -extern const struct wl_interface wl_registry_interface; -extern const struct wl_interface wl_seat_interface; -extern const struct wl_interface wl_shell_surface_interface; -extern const struct wl_interface wl_shm_pool_interface; -extern const struct wl_interface wl_subsurface_interface; -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface wl_touch_interface; - -static const struct wl_interface *wayland_types[] = { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - &wl_callback_interface, - &wl_registry_interface, - &wl_surface_interface, - &wl_region_interface, - &wl_buffer_interface, - NULL, - NULL, - NULL, - NULL, - NULL, - &wl_shm_pool_interface, - NULL, - NULL, - &wl_data_source_interface, - &wl_surface_interface, - &wl_surface_interface, - NULL, - &wl_data_source_interface, - NULL, - &wl_data_offer_interface, - NULL, - &wl_surface_interface, - NULL, - NULL, - &wl_data_offer_interface, - &wl_data_offer_interface, - &wl_data_source_interface, - &wl_data_device_interface, - &wl_seat_interface, - &wl_shell_surface_interface, - &wl_surface_interface, - &wl_seat_interface, - NULL, - &wl_seat_interface, - NULL, - NULL, - &wl_surface_interface, - NULL, - NULL, - NULL, - NULL, - NULL, - &wl_output_interface, - &wl_seat_interface, - NULL, - &wl_surface_interface, - NULL, - NULL, - NULL, - &wl_output_interface, - &wl_buffer_interface, - NULL, - NULL, - &wl_callback_interface, - &wl_region_interface, - &wl_region_interface, - &wl_output_interface, - &wl_output_interface, - &wl_pointer_interface, - &wl_keyboard_interface, - &wl_touch_interface, - NULL, - &wl_surface_interface, - NULL, - NULL, - NULL, - &wl_surface_interface, - NULL, - NULL, - NULL, - &wl_surface_interface, - NULL, - &wl_surface_interface, - NULL, - NULL, - &wl_surface_interface, - NULL, - NULL, - &wl_surface_interface, - NULL, - NULL, - NULL, - &wl_subsurface_interface, - &wl_surface_interface, - &wl_surface_interface, - &wl_surface_interface, - &wl_surface_interface, -}; - -static const struct wl_message wl_display_requests[] = { - { "sync", "n", wayland_types + 8 }, - { "get_registry", "n", wayland_types + 9 }, -}; - -static const struct wl_message wl_display_events[] = { - { "error", "ous", wayland_types + 0 }, - { "delete_id", "u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_display_interface = { - "wl_display", 1, - 2, wl_display_requests, - 2, wl_display_events, -}; - -static const struct wl_message wl_registry_requests[] = { - { "bind", "usun", wayland_types + 0 }, -}; - -static const struct wl_message wl_registry_events[] = { - { "global", "usu", wayland_types + 0 }, - { "global_remove", "u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_registry_interface = { - "wl_registry", 1, - 1, wl_registry_requests, - 2, wl_registry_events, -}; - -static const struct wl_message wl_callback_events[] = { - { "done", "u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_callback_interface = { - "wl_callback", 1, - 0, NULL, - 1, wl_callback_events, -}; - -static const struct wl_message wl_compositor_requests[] = { - { "create_surface", "n", wayland_types + 10 }, - { "create_region", "n", wayland_types + 11 }, -}; - -WL_PRIVATE const struct wl_interface wl_compositor_interface = { - "wl_compositor", 6, - 2, wl_compositor_requests, - 0, NULL, -}; - -static const struct wl_message wl_shm_pool_requests[] = { - { "create_buffer", "niiiiu", wayland_types + 12 }, - { "destroy", "", wayland_types + 0 }, - { "resize", "i", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_shm_pool_interface = { - "wl_shm_pool", 1, - 3, wl_shm_pool_requests, - 0, NULL, -}; - -static const struct wl_message wl_shm_requests[] = { - { "create_pool", "nhi", wayland_types + 18 }, -}; - -static const struct wl_message wl_shm_events[] = { - { "format", "u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_shm_interface = { - "wl_shm", 1, - 1, wl_shm_requests, - 1, wl_shm_events, -}; - -static const struct wl_message wl_buffer_requests[] = { - { "destroy", "", wayland_types + 0 }, -}; - -static const struct wl_message wl_buffer_events[] = { - { "release", "", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_buffer_interface = { - "wl_buffer", 1, - 1, wl_buffer_requests, - 1, wl_buffer_events, -}; - -static const struct wl_message wl_data_offer_requests[] = { - { "accept", "u?s", wayland_types + 0 }, - { "receive", "sh", wayland_types + 0 }, - { "destroy", "", wayland_types + 0 }, - { "finish", "3", wayland_types + 0 }, - { "set_actions", "3uu", wayland_types + 0 }, -}; - -static const struct wl_message wl_data_offer_events[] = { - { "offer", "s", wayland_types + 0 }, - { "source_actions", "3u", wayland_types + 0 }, - { "action", "3u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_data_offer_interface = { - "wl_data_offer", 3, - 5, wl_data_offer_requests, - 3, wl_data_offer_events, -}; - -static const struct wl_message wl_data_source_requests[] = { - { "offer", "s", wayland_types + 0 }, - { "destroy", "", wayland_types + 0 }, - { "set_actions", "3u", wayland_types + 0 }, -}; - -static const struct wl_message wl_data_source_events[] = { - { "target", "?s", wayland_types + 0 }, - { "send", "sh", wayland_types + 0 }, - { "cancelled", "", wayland_types + 0 }, - { "dnd_drop_performed", "3", wayland_types + 0 }, - { "dnd_finished", "3", wayland_types + 0 }, - { "action", "3u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_data_source_interface = { - "wl_data_source", 3, - 3, wl_data_source_requests, - 6, wl_data_source_events, -}; - -static const struct wl_message wl_data_device_requests[] = { - { "start_drag", "?oo?ou", wayland_types + 21 }, - { "set_selection", "?ou", wayland_types + 25 }, - { "release", "2", wayland_types + 0 }, -}; - -static const struct wl_message wl_data_device_events[] = { - { "data_offer", "n", wayland_types + 27 }, - { "enter", "uoff?o", wayland_types + 28 }, - { "leave", "", wayland_types + 0 }, - { "motion", "uff", wayland_types + 0 }, - { "drop", "", wayland_types + 0 }, - { "selection", "?o", wayland_types + 33 }, -}; - -WL_PRIVATE const struct wl_interface wl_data_device_interface = { - "wl_data_device", 3, - 3, wl_data_device_requests, - 6, wl_data_device_events, -}; - -static const struct wl_message wl_data_device_manager_requests[] = { - { "create_data_source", "n", wayland_types + 34 }, - { "get_data_device", "no", wayland_types + 35 }, -}; - -WL_PRIVATE const struct wl_interface wl_data_device_manager_interface = { - "wl_data_device_manager", 3, - 2, wl_data_device_manager_requests, - 0, NULL, -}; - -static const struct wl_message wl_shell_requests[] = { - { "get_shell_surface", "no", wayland_types + 37 }, -}; - -WL_PRIVATE const struct wl_interface wl_shell_interface = { - "wl_shell", 1, - 1, wl_shell_requests, - 0, NULL, -}; - -static const struct wl_message wl_shell_surface_requests[] = { - { "pong", "u", wayland_types + 0 }, - { "move", "ou", wayland_types + 39 }, - { "resize", "ouu", wayland_types + 41 }, - { "set_toplevel", "", wayland_types + 0 }, - { "set_transient", "oiiu", wayland_types + 44 }, - { "set_fullscreen", "uu?o", wayland_types + 48 }, - { "set_popup", "ouoiiu", wayland_types + 51 }, - { "set_maximized", "?o", wayland_types + 57 }, - { "set_title", "s", wayland_types + 0 }, - { "set_class", "s", wayland_types + 0 }, -}; - -static const struct wl_message wl_shell_surface_events[] = { - { "ping", "u", wayland_types + 0 }, - { "configure", "uii", wayland_types + 0 }, - { "popup_done", "", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_shell_surface_interface = { - "wl_shell_surface", 1, - 10, wl_shell_surface_requests, - 3, wl_shell_surface_events, -}; - -static const struct wl_message wl_surface_requests[] = { - { "destroy", "", wayland_types + 0 }, - { "attach", "?oii", wayland_types + 58 }, - { "damage", "iiii", wayland_types + 0 }, - { "frame", "n", wayland_types + 61 }, - { "set_opaque_region", "?o", wayland_types + 62 }, - { "set_input_region", "?o", wayland_types + 63 }, - { "commit", "", wayland_types + 0 }, - { "set_buffer_transform", "2i", wayland_types + 0 }, - { "set_buffer_scale", "3i", wayland_types + 0 }, - { "damage_buffer", "4iiii", wayland_types + 0 }, - { "offset", "5ii", wayland_types + 0 }, -}; - -static const struct wl_message wl_surface_events[] = { - { "enter", "o", wayland_types + 64 }, - { "leave", "o", wayland_types + 65 }, - { "preferred_buffer_scale", "6i", wayland_types + 0 }, - { "preferred_buffer_transform", "6u", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_surface_interface = { - "wl_surface", 6, - 11, wl_surface_requests, - 4, wl_surface_events, -}; - -static const struct wl_message wl_seat_requests[] = { - { "get_pointer", "n", wayland_types + 66 }, - { "get_keyboard", "n", wayland_types + 67 }, - { "get_touch", "n", wayland_types + 68 }, - { "release", "5", wayland_types + 0 }, -}; - -static const struct wl_message wl_seat_events[] = { - { "capabilities", "u", wayland_types + 0 }, - { "name", "2s", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_seat_interface = { - "wl_seat", 9, - 4, wl_seat_requests, - 2, wl_seat_events, -}; - -static const struct wl_message wl_pointer_requests[] = { - { "set_cursor", "u?oii", wayland_types + 69 }, - { "release", "3", wayland_types + 0 }, -}; - -static const struct wl_message wl_pointer_events[] = { - { "enter", "uoff", wayland_types + 73 }, - { "leave", "uo", wayland_types + 77 }, - { "motion", "uff", wayland_types + 0 }, - { "button", "uuuu", wayland_types + 0 }, - { "axis", "uuf", wayland_types + 0 }, - { "frame", "5", wayland_types + 0 }, - { "axis_source", "5u", wayland_types + 0 }, - { "axis_stop", "5uu", wayland_types + 0 }, - { "axis_discrete", "5ui", wayland_types + 0 }, - { "axis_value120", "8ui", wayland_types + 0 }, - { "axis_relative_direction", "9uu", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_pointer_interface = { - "wl_pointer", 9, - 2, wl_pointer_requests, - 11, wl_pointer_events, -}; - -static const struct wl_message wl_keyboard_requests[] = { - { "release", "3", wayland_types + 0 }, -}; - -static const struct wl_message wl_keyboard_events[] = { - { "keymap", "uhu", wayland_types + 0 }, - { "enter", "uoa", wayland_types + 79 }, - { "leave", "uo", wayland_types + 82 }, - { "key", "uuuu", wayland_types + 0 }, - { "modifiers", "uuuuu", wayland_types + 0 }, - { "repeat_info", "4ii", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_keyboard_interface = { - "wl_keyboard", 9, - 1, wl_keyboard_requests, - 6, wl_keyboard_events, -}; - -static const struct wl_message wl_touch_requests[] = { - { "release", "3", wayland_types + 0 }, -}; - -static const struct wl_message wl_touch_events[] = { - { "down", "uuoiff", wayland_types + 84 }, - { "up", "uui", wayland_types + 0 }, - { "motion", "uiff", wayland_types + 0 }, - { "frame", "", wayland_types + 0 }, - { "cancel", "", wayland_types + 0 }, - { "shape", "6iff", wayland_types + 0 }, - { "orientation", "6if", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_touch_interface = { - "wl_touch", 9, - 1, wl_touch_requests, - 7, wl_touch_events, -}; - -static const struct wl_message wl_output_requests[] = { - { "release", "3", wayland_types + 0 }, -}; - -static const struct wl_message wl_output_events[] = { - { "geometry", "iiiiissi", wayland_types + 0 }, - { "mode", "uiii", wayland_types + 0 }, - { "done", "2", wayland_types + 0 }, - { "scale", "2i", wayland_types + 0 }, - { "name", "4s", wayland_types + 0 }, - { "description", "4s", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_output_interface = { - "wl_output", 4, - 1, wl_output_requests, - 6, wl_output_events, -}; - -static const struct wl_message wl_region_requests[] = { - { "destroy", "", wayland_types + 0 }, - { "add", "iiii", wayland_types + 0 }, - { "subtract", "iiii", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_region_interface = { - "wl_region", 1, - 3, wl_region_requests, - 0, NULL, -}; - -static const struct wl_message wl_subcompositor_requests[] = { - { "destroy", "", wayland_types + 0 }, - { "get_subsurface", "noo", wayland_types + 90 }, -}; - -WL_PRIVATE const struct wl_interface wl_subcompositor_interface = { - "wl_subcompositor", 1, - 2, wl_subcompositor_requests, - 0, NULL, -}; - -static const struct wl_message wl_subsurface_requests[] = { - { "destroy", "", wayland_types + 0 }, - { "set_position", "ii", wayland_types + 0 }, - { "place_above", "o", wayland_types + 93 }, - { "place_below", "o", wayland_types + 94 }, - { "set_sync", "", wayland_types + 0 }, - { "set_desync", "", wayland_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface wl_subsurface_interface = { - "wl_subsurface", 1, - 6, wl_subsurface_requests, - 0, NULL, -}; - diff --git a/pkg/glfw/wayland-headers/wayland-client-protocol.h b/pkg/glfw/wayland-headers/wayland-client-protocol.h deleted file mode 100644 index 764c5958c..000000000 --- a/pkg/glfw/wayland-headers/wayland-client-protocol.h +++ /dev/null @@ -1,6236 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef WAYLAND_CLIENT_PROTOCOL_H -#define WAYLAND_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_wayland The wayland protocol - * @section page_ifaces_wayland Interfaces - * - @subpage page_iface_wl_display - core global object - * - @subpage page_iface_wl_registry - global registry object - * - @subpage page_iface_wl_callback - callback object - * - @subpage page_iface_wl_compositor - the compositor singleton - * - @subpage page_iface_wl_shm_pool - a shared memory pool - * - @subpage page_iface_wl_shm - shared memory support - * - @subpage page_iface_wl_buffer - content for a wl_surface - * - @subpage page_iface_wl_data_offer - offer to transfer data - * - @subpage page_iface_wl_data_source - offer to transfer data - * - @subpage page_iface_wl_data_device - data transfer device - * - @subpage page_iface_wl_data_device_manager - data transfer interface - * - @subpage page_iface_wl_shell - create desktop-style surfaces - * - @subpage page_iface_wl_shell_surface - desktop-style metadata interface - * - @subpage page_iface_wl_surface - an onscreen surface - * - @subpage page_iface_wl_seat - group of input devices - * - @subpage page_iface_wl_pointer - pointer input device - * - @subpage page_iface_wl_keyboard - keyboard input device - * - @subpage page_iface_wl_touch - touchscreen input device - * - @subpage page_iface_wl_output - compositor output region - * - @subpage page_iface_wl_region - region interface - * - @subpage page_iface_wl_subcompositor - sub-surface compositing - * - @subpage page_iface_wl_subsurface - sub-surface interface to a wl_surface - * @section page_copyright_wayland Copyright - *
- *
- * Copyright © 2008-2011 Kristian Høgsberg
- * Copyright © 2010-2011 Intel Corporation
- * Copyright © 2012-2013 Collabora, Ltd.
- *
- * Permission is hereby granted, free of charge, to any person
- * obtaining a copy of this software and associated documentation files
- * (the "Software"), to deal in the Software without restriction,
- * including without limitation the rights to use, copy, modify, merge,
- * publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the
- * next paragraph) shall be included in all copies or substantial
- * portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- * 
- */ -struct wl_buffer; -struct wl_callback; -struct wl_compositor; -struct wl_data_device; -struct wl_data_device_manager; -struct wl_data_offer; -struct wl_data_source; -struct wl_display; -struct wl_keyboard; -struct wl_output; -struct wl_pointer; -struct wl_region; -struct wl_registry; -struct wl_seat; -struct wl_shell; -struct wl_shell_surface; -struct wl_shm; -struct wl_shm_pool; -struct wl_subcompositor; -struct wl_subsurface; -struct wl_surface; -struct wl_touch; - -#ifndef WL_DISPLAY_INTERFACE -#define WL_DISPLAY_INTERFACE -/** - * @page page_iface_wl_display wl_display - * @section page_iface_wl_display_desc Description - * - * The core global object. This is a special singleton object. It - * is used for internal Wayland protocol features. - * @section page_iface_wl_display_api API - * See @ref iface_wl_display. - */ -/** - * @defgroup iface_wl_display The wl_display interface - * - * The core global object. This is a special singleton object. It - * is used for internal Wayland protocol features. - */ -extern const struct wl_interface wl_display_interface; -#endif -#ifndef WL_REGISTRY_INTERFACE -#define WL_REGISTRY_INTERFACE -/** - * @page page_iface_wl_registry wl_registry - * @section page_iface_wl_registry_desc Description - * - * The singleton global registry object. The server has a number of - * global objects that are available to all clients. These objects - * typically represent an actual object in the server (for example, - * an input device) or they are singleton objects that provide - * extension functionality. - * - * When a client creates a registry object, the registry object - * will emit a global event for each global currently in the - * registry. Globals come and go as a result of device or - * monitor hotplugs, reconfiguration or other events, and the - * registry will send out global and global_remove events to - * keep the client up to date with the changes. To mark the end - * of the initial burst of events, the client can use the - * wl_display.sync request immediately after calling - * wl_display.get_registry. - * - * A client can bind to a global object by using the bind - * request. This creates a client-side handle that lets the object - * emit events to the client and lets the client invoke requests on - * the object. - * @section page_iface_wl_registry_api API - * See @ref iface_wl_registry. - */ -/** - * @defgroup iface_wl_registry The wl_registry interface - * - * The singleton global registry object. The server has a number of - * global objects that are available to all clients. These objects - * typically represent an actual object in the server (for example, - * an input device) or they are singleton objects that provide - * extension functionality. - * - * When a client creates a registry object, the registry object - * will emit a global event for each global currently in the - * registry. Globals come and go as a result of device or - * monitor hotplugs, reconfiguration or other events, and the - * registry will send out global and global_remove events to - * keep the client up to date with the changes. To mark the end - * of the initial burst of events, the client can use the - * wl_display.sync request immediately after calling - * wl_display.get_registry. - * - * A client can bind to a global object by using the bind - * request. This creates a client-side handle that lets the object - * emit events to the client and lets the client invoke requests on - * the object. - */ -extern const struct wl_interface wl_registry_interface; -#endif -#ifndef WL_CALLBACK_INTERFACE -#define WL_CALLBACK_INTERFACE -/** - * @page page_iface_wl_callback wl_callback - * @section page_iface_wl_callback_desc Description - * - * Clients can handle the 'done' event to get notified when - * the related request is done. - * - * Note, because wl_callback objects are created from multiple independent - * factory interfaces, the wl_callback interface is frozen at version 1. - * @section page_iface_wl_callback_api API - * See @ref iface_wl_callback. - */ -/** - * @defgroup iface_wl_callback The wl_callback interface - * - * Clients can handle the 'done' event to get notified when - * the related request is done. - * - * Note, because wl_callback objects are created from multiple independent - * factory interfaces, the wl_callback interface is frozen at version 1. - */ -extern const struct wl_interface wl_callback_interface; -#endif -#ifndef WL_COMPOSITOR_INTERFACE -#define WL_COMPOSITOR_INTERFACE -/** - * @page page_iface_wl_compositor wl_compositor - * @section page_iface_wl_compositor_desc Description - * - * A compositor. This object is a singleton global. The - * compositor is in charge of combining the contents of multiple - * surfaces into one displayable output. - * @section page_iface_wl_compositor_api API - * See @ref iface_wl_compositor. - */ -/** - * @defgroup iface_wl_compositor The wl_compositor interface - * - * A compositor. This object is a singleton global. The - * compositor is in charge of combining the contents of multiple - * surfaces into one displayable output. - */ -extern const struct wl_interface wl_compositor_interface; -#endif -#ifndef WL_SHM_POOL_INTERFACE -#define WL_SHM_POOL_INTERFACE -/** - * @page page_iface_wl_shm_pool wl_shm_pool - * @section page_iface_wl_shm_pool_desc Description - * - * The wl_shm_pool object encapsulates a piece of memory shared - * between the compositor and client. Through the wl_shm_pool - * object, the client can allocate shared memory wl_buffer objects. - * All objects created through the same pool share the same - * underlying mapped memory. Reusing the mapped memory avoids the - * setup/teardown overhead and is useful when interactively resizing - * a surface or for many small buffers. - * @section page_iface_wl_shm_pool_api API - * See @ref iface_wl_shm_pool. - */ -/** - * @defgroup iface_wl_shm_pool The wl_shm_pool interface - * - * The wl_shm_pool object encapsulates a piece of memory shared - * between the compositor and client. Through the wl_shm_pool - * object, the client can allocate shared memory wl_buffer objects. - * All objects created through the same pool share the same - * underlying mapped memory. Reusing the mapped memory avoids the - * setup/teardown overhead and is useful when interactively resizing - * a surface or for many small buffers. - */ -extern const struct wl_interface wl_shm_pool_interface; -#endif -#ifndef WL_SHM_INTERFACE -#define WL_SHM_INTERFACE -/** - * @page page_iface_wl_shm wl_shm - * @section page_iface_wl_shm_desc Description - * - * A singleton global object that provides support for shared - * memory. - * - * Clients can create wl_shm_pool objects using the create_pool - * request. - * - * On binding the wl_shm object one or more format events - * are emitted to inform clients about the valid pixel formats - * that can be used for buffers. - * @section page_iface_wl_shm_api API - * See @ref iface_wl_shm. - */ -/** - * @defgroup iface_wl_shm The wl_shm interface - * - * A singleton global object that provides support for shared - * memory. - * - * Clients can create wl_shm_pool objects using the create_pool - * request. - * - * On binding the wl_shm object one or more format events - * are emitted to inform clients about the valid pixel formats - * that can be used for buffers. - */ -extern const struct wl_interface wl_shm_interface; -#endif -#ifndef WL_BUFFER_INTERFACE -#define WL_BUFFER_INTERFACE -/** - * @page page_iface_wl_buffer wl_buffer - * @section page_iface_wl_buffer_desc Description - * - * A buffer provides the content for a wl_surface. Buffers are - * created through factory interfaces such as wl_shm, wp_linux_buffer_params - * (from the linux-dmabuf protocol extension) or similar. It has a width and - * a height and can be attached to a wl_surface, but the mechanism by which a - * client provides and updates the contents is defined by the buffer factory - * interface. - * - * If the buffer uses a format that has an alpha channel, the alpha channel - * is assumed to be premultiplied in the color channels unless otherwise - * specified. - * - * Note, because wl_buffer objects are created from multiple independent - * factory interfaces, the wl_buffer interface is frozen at version 1. - * @section page_iface_wl_buffer_api API - * See @ref iface_wl_buffer. - */ -/** - * @defgroup iface_wl_buffer The wl_buffer interface - * - * A buffer provides the content for a wl_surface. Buffers are - * created through factory interfaces such as wl_shm, wp_linux_buffer_params - * (from the linux-dmabuf protocol extension) or similar. It has a width and - * a height and can be attached to a wl_surface, but the mechanism by which a - * client provides and updates the contents is defined by the buffer factory - * interface. - * - * If the buffer uses a format that has an alpha channel, the alpha channel - * is assumed to be premultiplied in the color channels unless otherwise - * specified. - * - * Note, because wl_buffer objects are created from multiple independent - * factory interfaces, the wl_buffer interface is frozen at version 1. - */ -extern const struct wl_interface wl_buffer_interface; -#endif -#ifndef WL_DATA_OFFER_INTERFACE -#define WL_DATA_OFFER_INTERFACE -/** - * @page page_iface_wl_data_offer wl_data_offer - * @section page_iface_wl_data_offer_desc Description - * - * A wl_data_offer represents a piece of data offered for transfer - * by another client (the source client). It is used by the - * copy-and-paste and drag-and-drop mechanisms. The offer - * describes the different mime types that the data can be - * converted to and provides the mechanism for transferring the - * data directly from the source client. - * @section page_iface_wl_data_offer_api API - * See @ref iface_wl_data_offer. - */ -/** - * @defgroup iface_wl_data_offer The wl_data_offer interface - * - * A wl_data_offer represents a piece of data offered for transfer - * by another client (the source client). It is used by the - * copy-and-paste and drag-and-drop mechanisms. The offer - * describes the different mime types that the data can be - * converted to and provides the mechanism for transferring the - * data directly from the source client. - */ -extern const struct wl_interface wl_data_offer_interface; -#endif -#ifndef WL_DATA_SOURCE_INTERFACE -#define WL_DATA_SOURCE_INTERFACE -/** - * @page page_iface_wl_data_source wl_data_source - * @section page_iface_wl_data_source_desc Description - * - * The wl_data_source object is the source side of a wl_data_offer. - * It is created by the source client in a data transfer and - * provides a way to describe the offered data and a way to respond - * to requests to transfer the data. - * @section page_iface_wl_data_source_api API - * See @ref iface_wl_data_source. - */ -/** - * @defgroup iface_wl_data_source The wl_data_source interface - * - * The wl_data_source object is the source side of a wl_data_offer. - * It is created by the source client in a data transfer and - * provides a way to describe the offered data and a way to respond - * to requests to transfer the data. - */ -extern const struct wl_interface wl_data_source_interface; -#endif -#ifndef WL_DATA_DEVICE_INTERFACE -#define WL_DATA_DEVICE_INTERFACE -/** - * @page page_iface_wl_data_device wl_data_device - * @section page_iface_wl_data_device_desc Description - * - * There is one wl_data_device per seat which can be obtained - * from the global wl_data_device_manager singleton. - * - * A wl_data_device provides access to inter-client data transfer - * mechanisms such as copy-and-paste and drag-and-drop. - * @section page_iface_wl_data_device_api API - * See @ref iface_wl_data_device. - */ -/** - * @defgroup iface_wl_data_device The wl_data_device interface - * - * There is one wl_data_device per seat which can be obtained - * from the global wl_data_device_manager singleton. - * - * A wl_data_device provides access to inter-client data transfer - * mechanisms such as copy-and-paste and drag-and-drop. - */ -extern const struct wl_interface wl_data_device_interface; -#endif -#ifndef WL_DATA_DEVICE_MANAGER_INTERFACE -#define WL_DATA_DEVICE_MANAGER_INTERFACE -/** - * @page page_iface_wl_data_device_manager wl_data_device_manager - * @section page_iface_wl_data_device_manager_desc Description - * - * The wl_data_device_manager is a singleton global object that - * provides access to inter-client data transfer mechanisms such as - * copy-and-paste and drag-and-drop. These mechanisms are tied to - * a wl_seat and this interface lets a client get a wl_data_device - * corresponding to a wl_seat. - * - * Depending on the version bound, the objects created from the bound - * wl_data_device_manager object will have different requirements for - * functioning properly. See wl_data_source.set_actions, - * wl_data_offer.accept and wl_data_offer.finish for details. - * @section page_iface_wl_data_device_manager_api API - * See @ref iface_wl_data_device_manager. - */ -/** - * @defgroup iface_wl_data_device_manager The wl_data_device_manager interface - * - * The wl_data_device_manager is a singleton global object that - * provides access to inter-client data transfer mechanisms such as - * copy-and-paste and drag-and-drop. These mechanisms are tied to - * a wl_seat and this interface lets a client get a wl_data_device - * corresponding to a wl_seat. - * - * Depending on the version bound, the objects created from the bound - * wl_data_device_manager object will have different requirements for - * functioning properly. See wl_data_source.set_actions, - * wl_data_offer.accept and wl_data_offer.finish for details. - */ -extern const struct wl_interface wl_data_device_manager_interface; -#endif -#ifndef WL_SHELL_INTERFACE -#define WL_SHELL_INTERFACE -/** - * @page page_iface_wl_shell wl_shell - * @section page_iface_wl_shell_desc Description - * - * This interface is implemented by servers that provide - * desktop-style user interfaces. - * - * It allows clients to associate a wl_shell_surface with - * a basic surface. - * - * Note! This protocol is deprecated and not intended for production use. - * For desktop-style user interfaces, use xdg_shell. Compositors and clients - * should not implement this interface. - * @section page_iface_wl_shell_api API - * See @ref iface_wl_shell. - */ -/** - * @defgroup iface_wl_shell The wl_shell interface - * - * This interface is implemented by servers that provide - * desktop-style user interfaces. - * - * It allows clients to associate a wl_shell_surface with - * a basic surface. - * - * Note! This protocol is deprecated and not intended for production use. - * For desktop-style user interfaces, use xdg_shell. Compositors and clients - * should not implement this interface. - */ -extern const struct wl_interface wl_shell_interface; -#endif -#ifndef WL_SHELL_SURFACE_INTERFACE -#define WL_SHELL_SURFACE_INTERFACE -/** - * @page page_iface_wl_shell_surface wl_shell_surface - * @section page_iface_wl_shell_surface_desc Description - * - * An interface that may be implemented by a wl_surface, for - * implementations that provide a desktop-style user interface. - * - * It provides requests to treat surfaces like toplevel, fullscreen - * or popup windows, move, resize or maximize them, associate - * metadata like title and class, etc. - * - * On the server side the object is automatically destroyed when - * the related wl_surface is destroyed. On the client side, - * wl_shell_surface_destroy() must be called before destroying - * the wl_surface object. - * @section page_iface_wl_shell_surface_api API - * See @ref iface_wl_shell_surface. - */ -/** - * @defgroup iface_wl_shell_surface The wl_shell_surface interface - * - * An interface that may be implemented by a wl_surface, for - * implementations that provide a desktop-style user interface. - * - * It provides requests to treat surfaces like toplevel, fullscreen - * or popup windows, move, resize or maximize them, associate - * metadata like title and class, etc. - * - * On the server side the object is automatically destroyed when - * the related wl_surface is destroyed. On the client side, - * wl_shell_surface_destroy() must be called before destroying - * the wl_surface object. - */ -extern const struct wl_interface wl_shell_surface_interface; -#endif -#ifndef WL_SURFACE_INTERFACE -#define WL_SURFACE_INTERFACE -/** - * @page page_iface_wl_surface wl_surface - * @section page_iface_wl_surface_desc Description - * - * A surface is a rectangular area that may be displayed on zero - * or more outputs, and shown any number of times at the compositor's - * discretion. They can present wl_buffers, receive user input, and - * define a local coordinate system. - * - * The size of a surface (and relative positions on it) is described - * in surface-local coordinates, which may differ from the buffer - * coordinates of the pixel content, in case a buffer_transform - * or a buffer_scale is used. - * - * A surface without a "role" is fairly useless: a compositor does - * not know where, when or how to present it. The role is the - * purpose of a wl_surface. Examples of roles are a cursor for a - * pointer (as set by wl_pointer.set_cursor), a drag icon - * (wl_data_device.start_drag), a sub-surface - * (wl_subcompositor.get_subsurface), and a window as defined by a - * shell protocol (e.g. wl_shell.get_shell_surface). - * - * A surface can have only one role at a time. Initially a - * wl_surface does not have a role. Once a wl_surface is given a - * role, it is set permanently for the whole lifetime of the - * wl_surface object. Giving the current role again is allowed, - * unless explicitly forbidden by the relevant interface - * specification. - * - * Surface roles are given by requests in other interfaces such as - * wl_pointer.set_cursor. The request should explicitly mention - * that this request gives a role to a wl_surface. Often, this - * request also creates a new protocol object that represents the - * role and adds additional functionality to wl_surface. When a - * client wants to destroy a wl_surface, they must destroy this role - * object before the wl_surface, otherwise a defunct_role_object error is - * sent. - * - * Destroying the role object does not remove the role from the - * wl_surface, but it may stop the wl_surface from "playing the role". - * For instance, if a wl_subsurface object is destroyed, the wl_surface - * it was created for will be unmapped and forget its position and - * z-order. It is allowed to create a wl_subsurface for the same - * wl_surface again, but it is not allowed to use the wl_surface as - * a cursor (cursor is a different role than sub-surface, and role - * switching is not allowed). - * @section page_iface_wl_surface_api API - * See @ref iface_wl_surface. - */ -/** - * @defgroup iface_wl_surface The wl_surface interface - * - * A surface is a rectangular area that may be displayed on zero - * or more outputs, and shown any number of times at the compositor's - * discretion. They can present wl_buffers, receive user input, and - * define a local coordinate system. - * - * The size of a surface (and relative positions on it) is described - * in surface-local coordinates, which may differ from the buffer - * coordinates of the pixel content, in case a buffer_transform - * or a buffer_scale is used. - * - * A surface without a "role" is fairly useless: a compositor does - * not know where, when or how to present it. The role is the - * purpose of a wl_surface. Examples of roles are a cursor for a - * pointer (as set by wl_pointer.set_cursor), a drag icon - * (wl_data_device.start_drag), a sub-surface - * (wl_subcompositor.get_subsurface), and a window as defined by a - * shell protocol (e.g. wl_shell.get_shell_surface). - * - * A surface can have only one role at a time. Initially a - * wl_surface does not have a role. Once a wl_surface is given a - * role, it is set permanently for the whole lifetime of the - * wl_surface object. Giving the current role again is allowed, - * unless explicitly forbidden by the relevant interface - * specification. - * - * Surface roles are given by requests in other interfaces such as - * wl_pointer.set_cursor. The request should explicitly mention - * that this request gives a role to a wl_surface. Often, this - * request also creates a new protocol object that represents the - * role and adds additional functionality to wl_surface. When a - * client wants to destroy a wl_surface, they must destroy this role - * object before the wl_surface, otherwise a defunct_role_object error is - * sent. - * - * Destroying the role object does not remove the role from the - * wl_surface, but it may stop the wl_surface from "playing the role". - * For instance, if a wl_subsurface object is destroyed, the wl_surface - * it was created for will be unmapped and forget its position and - * z-order. It is allowed to create a wl_subsurface for the same - * wl_surface again, but it is not allowed to use the wl_surface as - * a cursor (cursor is a different role than sub-surface, and role - * switching is not allowed). - */ -extern const struct wl_interface wl_surface_interface; -#endif -#ifndef WL_SEAT_INTERFACE -#define WL_SEAT_INTERFACE -/** - * @page page_iface_wl_seat wl_seat - * @section page_iface_wl_seat_desc Description - * - * A seat is a group of keyboards, pointer and touch devices. This - * object is published as a global during start up, or when such a - * device is hot plugged. A seat typically has a pointer and - * maintains a keyboard focus and a pointer focus. - * @section page_iface_wl_seat_api API - * See @ref iface_wl_seat. - */ -/** - * @defgroup iface_wl_seat The wl_seat interface - * - * A seat is a group of keyboards, pointer and touch devices. This - * object is published as a global during start up, or when such a - * device is hot plugged. A seat typically has a pointer and - * maintains a keyboard focus and a pointer focus. - */ -extern const struct wl_interface wl_seat_interface; -#endif -#ifndef WL_POINTER_INTERFACE -#define WL_POINTER_INTERFACE -/** - * @page page_iface_wl_pointer wl_pointer - * @section page_iface_wl_pointer_desc Description - * - * The wl_pointer interface represents one or more input devices, - * such as mice, which control the pointer location and pointer_focus - * of a seat. - * - * The wl_pointer interface generates motion, enter and leave - * events for the surfaces that the pointer is located over, - * and button and axis events for button presses, button releases - * and scrolling. - * @section page_iface_wl_pointer_api API - * See @ref iface_wl_pointer. - */ -/** - * @defgroup iface_wl_pointer The wl_pointer interface - * - * The wl_pointer interface represents one or more input devices, - * such as mice, which control the pointer location and pointer_focus - * of a seat. - * - * The wl_pointer interface generates motion, enter and leave - * events for the surfaces that the pointer is located over, - * and button and axis events for button presses, button releases - * and scrolling. - */ -extern const struct wl_interface wl_pointer_interface; -#endif -#ifndef WL_KEYBOARD_INTERFACE -#define WL_KEYBOARD_INTERFACE -/** - * @page page_iface_wl_keyboard wl_keyboard - * @section page_iface_wl_keyboard_desc Description - * - * The wl_keyboard interface represents one or more keyboards - * associated with a seat. - * @section page_iface_wl_keyboard_api API - * See @ref iface_wl_keyboard. - */ -/** - * @defgroup iface_wl_keyboard The wl_keyboard interface - * - * The wl_keyboard interface represents one or more keyboards - * associated with a seat. - */ -extern const struct wl_interface wl_keyboard_interface; -#endif -#ifndef WL_TOUCH_INTERFACE -#define WL_TOUCH_INTERFACE -/** - * @page page_iface_wl_touch wl_touch - * @section page_iface_wl_touch_desc Description - * - * The wl_touch interface represents a touchscreen - * associated with a seat. - * - * Touch interactions can consist of one or more contacts. - * For each contact, a series of events is generated, starting - * with a down event, followed by zero or more motion events, - * and ending with an up event. Events relating to the same - * contact point can be identified by the ID of the sequence. - * @section page_iface_wl_touch_api API - * See @ref iface_wl_touch. - */ -/** - * @defgroup iface_wl_touch The wl_touch interface - * - * The wl_touch interface represents a touchscreen - * associated with a seat. - * - * Touch interactions can consist of one or more contacts. - * For each contact, a series of events is generated, starting - * with a down event, followed by zero or more motion events, - * and ending with an up event. Events relating to the same - * contact point can be identified by the ID of the sequence. - */ -extern const struct wl_interface wl_touch_interface; -#endif -#ifndef WL_OUTPUT_INTERFACE -#define WL_OUTPUT_INTERFACE -/** - * @page page_iface_wl_output wl_output - * @section page_iface_wl_output_desc Description - * - * An output describes part of the compositor geometry. The - * compositor works in the 'compositor coordinate system' and an - * output corresponds to a rectangular area in that space that is - * actually visible. This typically corresponds to a monitor that - * displays part of the compositor space. This object is published - * as global during start up, or when a monitor is hotplugged. - * @section page_iface_wl_output_api API - * See @ref iface_wl_output. - */ -/** - * @defgroup iface_wl_output The wl_output interface - * - * An output describes part of the compositor geometry. The - * compositor works in the 'compositor coordinate system' and an - * output corresponds to a rectangular area in that space that is - * actually visible. This typically corresponds to a monitor that - * displays part of the compositor space. This object is published - * as global during start up, or when a monitor is hotplugged. - */ -extern const struct wl_interface wl_output_interface; -#endif -#ifndef WL_REGION_INTERFACE -#define WL_REGION_INTERFACE -/** - * @page page_iface_wl_region wl_region - * @section page_iface_wl_region_desc Description - * - * A region object describes an area. - * - * Region objects are used to describe the opaque and input - * regions of a surface. - * @section page_iface_wl_region_api API - * See @ref iface_wl_region. - */ -/** - * @defgroup iface_wl_region The wl_region interface - * - * A region object describes an area. - * - * Region objects are used to describe the opaque and input - * regions of a surface. - */ -extern const struct wl_interface wl_region_interface; -#endif -#ifndef WL_SUBCOMPOSITOR_INTERFACE -#define WL_SUBCOMPOSITOR_INTERFACE -/** - * @page page_iface_wl_subcompositor wl_subcompositor - * @section page_iface_wl_subcompositor_desc Description - * - * The global interface exposing sub-surface compositing capabilities. - * A wl_surface, that has sub-surfaces associated, is called the - * parent surface. Sub-surfaces can be arbitrarily nested and create - * a tree of sub-surfaces. - * - * The root surface in a tree of sub-surfaces is the main - * surface. The main surface cannot be a sub-surface, because - * sub-surfaces must always have a parent. - * - * A main surface with its sub-surfaces forms a (compound) window. - * For window management purposes, this set of wl_surface objects is - * to be considered as a single window, and it should also behave as - * such. - * - * The aim of sub-surfaces is to offload some of the compositing work - * within a window from clients to the compositor. A prime example is - * a video player with decorations and video in separate wl_surface - * objects. This should allow the compositor to pass YUV video buffer - * processing to dedicated overlay hardware when possible. - * @section page_iface_wl_subcompositor_api API - * See @ref iface_wl_subcompositor. - */ -/** - * @defgroup iface_wl_subcompositor The wl_subcompositor interface - * - * The global interface exposing sub-surface compositing capabilities. - * A wl_surface, that has sub-surfaces associated, is called the - * parent surface. Sub-surfaces can be arbitrarily nested and create - * a tree of sub-surfaces. - * - * The root surface in a tree of sub-surfaces is the main - * surface. The main surface cannot be a sub-surface, because - * sub-surfaces must always have a parent. - * - * A main surface with its sub-surfaces forms a (compound) window. - * For window management purposes, this set of wl_surface objects is - * to be considered as a single window, and it should also behave as - * such. - * - * The aim of sub-surfaces is to offload some of the compositing work - * within a window from clients to the compositor. A prime example is - * a video player with decorations and video in separate wl_surface - * objects. This should allow the compositor to pass YUV video buffer - * processing to dedicated overlay hardware when possible. - */ -extern const struct wl_interface wl_subcompositor_interface; -#endif -#ifndef WL_SUBSURFACE_INTERFACE -#define WL_SUBSURFACE_INTERFACE -/** - * @page page_iface_wl_subsurface wl_subsurface - * @section page_iface_wl_subsurface_desc Description - * - * An additional interface to a wl_surface object, which has been - * made a sub-surface. A sub-surface has one parent surface. A - * sub-surface's size and position are not limited to that of the parent. - * Particularly, a sub-surface is not automatically clipped to its - * parent's area. - * - * A sub-surface becomes mapped, when a non-NULL wl_buffer is applied - * and the parent surface is mapped. The order of which one happens - * first is irrelevant. A sub-surface is hidden if the parent becomes - * hidden, or if a NULL wl_buffer is applied. These rules apply - * recursively through the tree of surfaces. - * - * The behaviour of a wl_surface.commit request on a sub-surface - * depends on the sub-surface's mode. The possible modes are - * synchronized and desynchronized, see methods - * wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized - * mode caches the wl_surface state to be applied when the parent's - * state gets applied, and desynchronized mode applies the pending - * wl_surface state directly. A sub-surface is initially in the - * synchronized mode. - * - * Sub-surfaces also have another kind of state, which is managed by - * wl_subsurface requests, as opposed to wl_surface requests. This - * state includes the sub-surface position relative to the parent - * surface (wl_subsurface.set_position), and the stacking order of - * the parent and its sub-surfaces (wl_subsurface.place_above and - * .place_below). This state is applied when the parent surface's - * wl_surface state is applied, regardless of the sub-surface's mode. - * As the exception, set_sync and set_desync are effective immediately. - * - * The main surface can be thought to be always in desynchronized mode, - * since it does not have a parent in the sub-surfaces sense. - * - * Even if a sub-surface is in desynchronized mode, it will behave as - * in synchronized mode, if its parent surface behaves as in - * synchronized mode. This rule is applied recursively throughout the - * tree of surfaces. This means, that one can set a sub-surface into - * synchronized mode, and then assume that all its child and grand-child - * sub-surfaces are synchronized, too, without explicitly setting them. - * - * Destroying a sub-surface takes effect immediately. If you need to - * synchronize the removal of a sub-surface to the parent surface update, - * unmap the sub-surface first by attaching a NULL wl_buffer, update parent, - * and then destroy the sub-surface. - * - * If the parent wl_surface object is destroyed, the sub-surface is - * unmapped. - * @section page_iface_wl_subsurface_api API - * See @ref iface_wl_subsurface. - */ -/** - * @defgroup iface_wl_subsurface The wl_subsurface interface - * - * An additional interface to a wl_surface object, which has been - * made a sub-surface. A sub-surface has one parent surface. A - * sub-surface's size and position are not limited to that of the parent. - * Particularly, a sub-surface is not automatically clipped to its - * parent's area. - * - * A sub-surface becomes mapped, when a non-NULL wl_buffer is applied - * and the parent surface is mapped. The order of which one happens - * first is irrelevant. A sub-surface is hidden if the parent becomes - * hidden, or if a NULL wl_buffer is applied. These rules apply - * recursively through the tree of surfaces. - * - * The behaviour of a wl_surface.commit request on a sub-surface - * depends on the sub-surface's mode. The possible modes are - * synchronized and desynchronized, see methods - * wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized - * mode caches the wl_surface state to be applied when the parent's - * state gets applied, and desynchronized mode applies the pending - * wl_surface state directly. A sub-surface is initially in the - * synchronized mode. - * - * Sub-surfaces also have another kind of state, which is managed by - * wl_subsurface requests, as opposed to wl_surface requests. This - * state includes the sub-surface position relative to the parent - * surface (wl_subsurface.set_position), and the stacking order of - * the parent and its sub-surfaces (wl_subsurface.place_above and - * .place_below). This state is applied when the parent surface's - * wl_surface state is applied, regardless of the sub-surface's mode. - * As the exception, set_sync and set_desync are effective immediately. - * - * The main surface can be thought to be always in desynchronized mode, - * since it does not have a parent in the sub-surfaces sense. - * - * Even if a sub-surface is in desynchronized mode, it will behave as - * in synchronized mode, if its parent surface behaves as in - * synchronized mode. This rule is applied recursively throughout the - * tree of surfaces. This means, that one can set a sub-surface into - * synchronized mode, and then assume that all its child and grand-child - * sub-surfaces are synchronized, too, without explicitly setting them. - * - * Destroying a sub-surface takes effect immediately. If you need to - * synchronize the removal of a sub-surface to the parent surface update, - * unmap the sub-surface first by attaching a NULL wl_buffer, update parent, - * and then destroy the sub-surface. - * - * If the parent wl_surface object is destroyed, the sub-surface is - * unmapped. - */ -extern const struct wl_interface wl_subsurface_interface; -#endif - -#ifndef WL_DISPLAY_ERROR_ENUM -#define WL_DISPLAY_ERROR_ENUM -/** - * @ingroup iface_wl_display - * global error values - * - * These errors are global and can be emitted in response to any - * server request. - */ -enum wl_display_error { - /** - * server couldn't find object - */ - WL_DISPLAY_ERROR_INVALID_OBJECT = 0, - /** - * method doesn't exist on the specified interface or malformed request - */ - WL_DISPLAY_ERROR_INVALID_METHOD = 1, - /** - * server is out of memory - */ - WL_DISPLAY_ERROR_NO_MEMORY = 2, - /** - * implementation error in compositor - */ - WL_DISPLAY_ERROR_IMPLEMENTATION = 3, -}; -#endif /* WL_DISPLAY_ERROR_ENUM */ - -/** - * @ingroup iface_wl_display - * @struct wl_display_listener - */ -struct wl_display_listener { - /** - * fatal error event - * - * The error event is sent out when a fatal (non-recoverable) - * error has occurred. The object_id argument is the object where - * the error occurred, most often in response to a request to that - * object. The code identifies the error and is defined by the - * object interface. As such, each interface defines its own set of - * error codes. The message is a brief description of the error, - * for (debugging) convenience. - * @param object_id object where the error occurred - * @param code error code - * @param message error description - */ - void (*error)(void *data, - struct wl_display *wl_display, - void *object_id, - uint32_t code, - const char *message); - /** - * acknowledge object ID deletion - * - * This event is used internally by the object ID management - * logic. When a client deletes an object that it had created, the - * server will send this event to acknowledge that it has seen the - * delete request. When the client receives this event, it will - * know that it can safely reuse the object ID. - * @param id deleted object ID - */ - void (*delete_id)(void *data, - struct wl_display *wl_display, - uint32_t id); -}; - -/** - * @ingroup iface_wl_display - */ -static inline int -wl_display_add_listener(struct wl_display *wl_display, - const struct wl_display_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_display, - (void (**)(void)) listener, data); -} - -#define WL_DISPLAY_SYNC 0 -#define WL_DISPLAY_GET_REGISTRY 1 - -/** - * @ingroup iface_wl_display - */ -#define WL_DISPLAY_ERROR_SINCE_VERSION 1 -/** - * @ingroup iface_wl_display - */ -#define WL_DISPLAY_DELETE_ID_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_display - */ -#define WL_DISPLAY_SYNC_SINCE_VERSION 1 -/** - * @ingroup iface_wl_display - */ -#define WL_DISPLAY_GET_REGISTRY_SINCE_VERSION 1 - -/** @ingroup iface_wl_display */ -static inline void -wl_display_set_user_data(struct wl_display *wl_display, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_display, user_data); -} - -/** @ingroup iface_wl_display */ -static inline void * -wl_display_get_user_data(struct wl_display *wl_display) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_display); -} - -static inline uint32_t -wl_display_get_version(struct wl_display *wl_display) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_display); -} - -/** - * @ingroup iface_wl_display - * - * The sync request asks the server to emit the 'done' event - * on the returned wl_callback object. Since requests are - * handled in-order and events are delivered in-order, this can - * be used as a barrier to ensure all previous requests and the - * resulting events have been handled. - * - * The object returned by this request will be destroyed by the - * compositor after the callback is fired and as such the client must not - * attempt to use it after that point. - * - * The callback_data passed in the callback is the event serial. - */ -static inline struct wl_callback * -wl_display_sync(struct wl_display *wl_display) -{ - struct wl_proxy *callback; - - callback = wl_proxy_marshal_flags((struct wl_proxy *) wl_display, - WL_DISPLAY_SYNC, &wl_callback_interface, wl_proxy_get_version((struct wl_proxy *) wl_display), 0, NULL); - - return (struct wl_callback *) callback; -} - -/** - * @ingroup iface_wl_display - * - * This request creates a registry object that allows the client - * to list and bind the global objects available from the - * compositor. - * - * It should be noted that the server side resources consumed in - * response to a get_registry request can only be released when the - * client disconnects, not when the client side proxy is destroyed. - * Therefore, clients should invoke get_registry as infrequently as - * possible to avoid wasting memory. - */ -static inline struct wl_registry * -wl_display_get_registry(struct wl_display *wl_display) -{ - struct wl_proxy *registry; - - registry = wl_proxy_marshal_flags((struct wl_proxy *) wl_display, - WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, wl_proxy_get_version((struct wl_proxy *) wl_display), 0, NULL); - - return (struct wl_registry *) registry; -} - -/** - * @ingroup iface_wl_registry - * @struct wl_registry_listener - */ -struct wl_registry_listener { - /** - * announce global object - * - * Notify the client of global objects. - * - * The event notifies the client that a global object with the - * given name is now available, and it implements the given version - * of the given interface. - * @param name numeric name of the global object - * @param interface interface implemented by the object - * @param version interface version - */ - void (*global)(void *data, - struct wl_registry *wl_registry, - uint32_t name, - const char *interface, - uint32_t version); - /** - * announce removal of global object - * - * Notify the client of removed global objects. - * - * This event notifies the client that the global identified by - * name is no longer available. If the client bound to the global - * using the bind request, the client should now destroy that - * object. - * - * The object remains valid and requests to the object will be - * ignored until the client destroys it, to avoid races between the - * global going away and a client sending a request to it. - * @param name numeric name of the global object - */ - void (*global_remove)(void *data, - struct wl_registry *wl_registry, - uint32_t name); -}; - -/** - * @ingroup iface_wl_registry - */ -static inline int -wl_registry_add_listener(struct wl_registry *wl_registry, - const struct wl_registry_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_registry, - (void (**)(void)) listener, data); -} - -#define WL_REGISTRY_BIND 0 - -/** - * @ingroup iface_wl_registry - */ -#define WL_REGISTRY_GLOBAL_SINCE_VERSION 1 -/** - * @ingroup iface_wl_registry - */ -#define WL_REGISTRY_GLOBAL_REMOVE_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_registry - */ -#define WL_REGISTRY_BIND_SINCE_VERSION 1 - -/** @ingroup iface_wl_registry */ -static inline void -wl_registry_set_user_data(struct wl_registry *wl_registry, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_registry, user_data); -} - -/** @ingroup iface_wl_registry */ -static inline void * -wl_registry_get_user_data(struct wl_registry *wl_registry) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_registry); -} - -static inline uint32_t -wl_registry_get_version(struct wl_registry *wl_registry) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_registry); -} - -/** @ingroup iface_wl_registry */ -static inline void -wl_registry_destroy(struct wl_registry *wl_registry) -{ - wl_proxy_destroy((struct wl_proxy *) wl_registry); -} - -/** - * @ingroup iface_wl_registry - * - * Binds a new, client-created object to the server using the - * specified name as the identifier. - */ -static inline void * -wl_registry_bind(struct wl_registry *wl_registry, uint32_t name, const struct wl_interface *interface, uint32_t version) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_registry, - WL_REGISTRY_BIND, interface, version, 0, name, interface->name, version, NULL); - - return (void *) id; -} - -/** - * @ingroup iface_wl_callback - * @struct wl_callback_listener - */ -struct wl_callback_listener { - /** - * done event - * - * Notify the client when the related request is done. - * @param callback_data request-specific data for the callback - */ - void (*done)(void *data, - struct wl_callback *wl_callback, - uint32_t callback_data); -}; - -/** - * @ingroup iface_wl_callback - */ -static inline int -wl_callback_add_listener(struct wl_callback *wl_callback, - const struct wl_callback_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_callback, - (void (**)(void)) listener, data); -} - -/** - * @ingroup iface_wl_callback - */ -#define WL_CALLBACK_DONE_SINCE_VERSION 1 - - -/** @ingroup iface_wl_callback */ -static inline void -wl_callback_set_user_data(struct wl_callback *wl_callback, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_callback, user_data); -} - -/** @ingroup iface_wl_callback */ -static inline void * -wl_callback_get_user_data(struct wl_callback *wl_callback) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_callback); -} - -static inline uint32_t -wl_callback_get_version(struct wl_callback *wl_callback) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_callback); -} - -/** @ingroup iface_wl_callback */ -static inline void -wl_callback_destroy(struct wl_callback *wl_callback) -{ - wl_proxy_destroy((struct wl_proxy *) wl_callback); -} - -#define WL_COMPOSITOR_CREATE_SURFACE 0 -#define WL_COMPOSITOR_CREATE_REGION 1 - - -/** - * @ingroup iface_wl_compositor - */ -#define WL_COMPOSITOR_CREATE_SURFACE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_compositor - */ -#define WL_COMPOSITOR_CREATE_REGION_SINCE_VERSION 1 - -/** @ingroup iface_wl_compositor */ -static inline void -wl_compositor_set_user_data(struct wl_compositor *wl_compositor, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_compositor, user_data); -} - -/** @ingroup iface_wl_compositor */ -static inline void * -wl_compositor_get_user_data(struct wl_compositor *wl_compositor) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_compositor); -} - -static inline uint32_t -wl_compositor_get_version(struct wl_compositor *wl_compositor) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_compositor); -} - -/** @ingroup iface_wl_compositor */ -static inline void -wl_compositor_destroy(struct wl_compositor *wl_compositor) -{ - wl_proxy_destroy((struct wl_proxy *) wl_compositor); -} - -/** - * @ingroup iface_wl_compositor - * - * Ask the compositor to create a new surface. - */ -static inline struct wl_surface * -wl_compositor_create_surface(struct wl_compositor *wl_compositor) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_compositor, - WL_COMPOSITOR_CREATE_SURFACE, &wl_surface_interface, wl_proxy_get_version((struct wl_proxy *) wl_compositor), 0, NULL); - - return (struct wl_surface *) id; -} - -/** - * @ingroup iface_wl_compositor - * - * Ask the compositor to create a new region. - */ -static inline struct wl_region * -wl_compositor_create_region(struct wl_compositor *wl_compositor) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_compositor, - WL_COMPOSITOR_CREATE_REGION, &wl_region_interface, wl_proxy_get_version((struct wl_proxy *) wl_compositor), 0, NULL); - - return (struct wl_region *) id; -} - -#define WL_SHM_POOL_CREATE_BUFFER 0 -#define WL_SHM_POOL_DESTROY 1 -#define WL_SHM_POOL_RESIZE 2 - - -/** - * @ingroup iface_wl_shm_pool - */ -#define WL_SHM_POOL_CREATE_BUFFER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shm_pool - */ -#define WL_SHM_POOL_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shm_pool - */ -#define WL_SHM_POOL_RESIZE_SINCE_VERSION 1 - -/** @ingroup iface_wl_shm_pool */ -static inline void -wl_shm_pool_set_user_data(struct wl_shm_pool *wl_shm_pool, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_shm_pool, user_data); -} - -/** @ingroup iface_wl_shm_pool */ -static inline void * -wl_shm_pool_get_user_data(struct wl_shm_pool *wl_shm_pool) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_shm_pool); -} - -static inline uint32_t -wl_shm_pool_get_version(struct wl_shm_pool *wl_shm_pool) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_shm_pool); -} - -/** - * @ingroup iface_wl_shm_pool - * - * Create a wl_buffer object from the pool. - * - * The buffer is created offset bytes into the pool and has - * width and height as specified. The stride argument specifies - * the number of bytes from the beginning of one row to the beginning - * of the next. The format is the pixel format of the buffer and - * must be one of those advertised through the wl_shm.format event. - * - * A buffer will keep a reference to the pool it was created from - * so it is valid to destroy the pool immediately after creating - * a buffer from it. - */ -static inline struct wl_buffer * -wl_shm_pool_create_buffer(struct wl_shm_pool *wl_shm_pool, int32_t offset, int32_t width, int32_t height, int32_t stride, uint32_t format) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_shm_pool, - WL_SHM_POOL_CREATE_BUFFER, &wl_buffer_interface, wl_proxy_get_version((struct wl_proxy *) wl_shm_pool), 0, NULL, offset, width, height, stride, format); - - return (struct wl_buffer *) id; -} - -/** - * @ingroup iface_wl_shm_pool - * - * Destroy the shared memory pool. - * - * The mmapped memory will be released when all - * buffers that have been created from this pool - * are gone. - */ -static inline void -wl_shm_pool_destroy(struct wl_shm_pool *wl_shm_pool) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shm_pool, - WL_SHM_POOL_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shm_pool), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_shm_pool - * - * This request will cause the server to remap the backing memory - * for the pool from the file descriptor passed when the pool was - * created, but using the new size. This request can only be - * used to make the pool bigger. - * - * This request only changes the amount of bytes that are mmapped - * by the server and does not touch the file corresponding to the - * file descriptor passed at creation time. It is the client's - * responsibility to ensure that the file is at least as big as - * the new pool size. - */ -static inline void -wl_shm_pool_resize(struct wl_shm_pool *wl_shm_pool, int32_t size) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shm_pool, - WL_SHM_POOL_RESIZE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shm_pool), 0, size); -} - -#ifndef WL_SHM_ERROR_ENUM -#define WL_SHM_ERROR_ENUM -/** - * @ingroup iface_wl_shm - * wl_shm error values - * - * These errors can be emitted in response to wl_shm requests. - */ -enum wl_shm_error { - /** - * buffer format is not known - */ - WL_SHM_ERROR_INVALID_FORMAT = 0, - /** - * invalid size or stride during pool or buffer creation - */ - WL_SHM_ERROR_INVALID_STRIDE = 1, - /** - * mmapping the file descriptor failed - */ - WL_SHM_ERROR_INVALID_FD = 2, -}; -#endif /* WL_SHM_ERROR_ENUM */ - -#ifndef WL_SHM_FORMAT_ENUM -#define WL_SHM_FORMAT_ENUM -/** - * @ingroup iface_wl_shm - * pixel formats - * - * This describes the memory layout of an individual pixel. - * - * All renderers should support argb8888 and xrgb8888 but any other - * formats are optional and may not be supported by the particular - * renderer in use. - * - * The drm format codes match the macros defined in drm_fourcc.h, except - * argb8888 and xrgb8888. The formats actually supported by the compositor - * will be reported by the format event. - * - * For all wl_shm formats and unless specified in another protocol - * extension, pre-multiplied alpha is used for pixel values. - */ -enum wl_shm_format { - /** - * 32-bit ARGB format, [31:0] A:R:G:B 8:8:8:8 little endian - */ - WL_SHM_FORMAT_ARGB8888 = 0, - /** - * 32-bit RGB format, [31:0] x:R:G:B 8:8:8:8 little endian - */ - WL_SHM_FORMAT_XRGB8888 = 1, - /** - * 8-bit color index format, [7:0] C - */ - WL_SHM_FORMAT_C8 = 0x20203843, - /** - * 8-bit RGB format, [7:0] R:G:B 3:3:2 - */ - WL_SHM_FORMAT_RGB332 = 0x38424752, - /** - * 8-bit BGR format, [7:0] B:G:R 2:3:3 - */ - WL_SHM_FORMAT_BGR233 = 0x38524742, - /** - * 16-bit xRGB format, [15:0] x:R:G:B 4:4:4:4 little endian - */ - WL_SHM_FORMAT_XRGB4444 = 0x32315258, - /** - * 16-bit xBGR format, [15:0] x:B:G:R 4:4:4:4 little endian - */ - WL_SHM_FORMAT_XBGR4444 = 0x32314258, - /** - * 16-bit RGBx format, [15:0] R:G:B:x 4:4:4:4 little endian - */ - WL_SHM_FORMAT_RGBX4444 = 0x32315852, - /** - * 16-bit BGRx format, [15:0] B:G:R:x 4:4:4:4 little endian - */ - WL_SHM_FORMAT_BGRX4444 = 0x32315842, - /** - * 16-bit ARGB format, [15:0] A:R:G:B 4:4:4:4 little endian - */ - WL_SHM_FORMAT_ARGB4444 = 0x32315241, - /** - * 16-bit ABGR format, [15:0] A:B:G:R 4:4:4:4 little endian - */ - WL_SHM_FORMAT_ABGR4444 = 0x32314241, - /** - * 16-bit RBGA format, [15:0] R:G:B:A 4:4:4:4 little endian - */ - WL_SHM_FORMAT_RGBA4444 = 0x32314152, - /** - * 16-bit BGRA format, [15:0] B:G:R:A 4:4:4:4 little endian - */ - WL_SHM_FORMAT_BGRA4444 = 0x32314142, - /** - * 16-bit xRGB format, [15:0] x:R:G:B 1:5:5:5 little endian - */ - WL_SHM_FORMAT_XRGB1555 = 0x35315258, - /** - * 16-bit xBGR 1555 format, [15:0] x:B:G:R 1:5:5:5 little endian - */ - WL_SHM_FORMAT_XBGR1555 = 0x35314258, - /** - * 16-bit RGBx 5551 format, [15:0] R:G:B:x 5:5:5:1 little endian - */ - WL_SHM_FORMAT_RGBX5551 = 0x35315852, - /** - * 16-bit BGRx 5551 format, [15:0] B:G:R:x 5:5:5:1 little endian - */ - WL_SHM_FORMAT_BGRX5551 = 0x35315842, - /** - * 16-bit ARGB 1555 format, [15:0] A:R:G:B 1:5:5:5 little endian - */ - WL_SHM_FORMAT_ARGB1555 = 0x35315241, - /** - * 16-bit ABGR 1555 format, [15:0] A:B:G:R 1:5:5:5 little endian - */ - WL_SHM_FORMAT_ABGR1555 = 0x35314241, - /** - * 16-bit RGBA 5551 format, [15:0] R:G:B:A 5:5:5:1 little endian - */ - WL_SHM_FORMAT_RGBA5551 = 0x35314152, - /** - * 16-bit BGRA 5551 format, [15:0] B:G:R:A 5:5:5:1 little endian - */ - WL_SHM_FORMAT_BGRA5551 = 0x35314142, - /** - * 16-bit RGB 565 format, [15:0] R:G:B 5:6:5 little endian - */ - WL_SHM_FORMAT_RGB565 = 0x36314752, - /** - * 16-bit BGR 565 format, [15:0] B:G:R 5:6:5 little endian - */ - WL_SHM_FORMAT_BGR565 = 0x36314742, - /** - * 24-bit RGB format, [23:0] R:G:B little endian - */ - WL_SHM_FORMAT_RGB888 = 0x34324752, - /** - * 24-bit BGR format, [23:0] B:G:R little endian - */ - WL_SHM_FORMAT_BGR888 = 0x34324742, - /** - * 32-bit xBGR format, [31:0] x:B:G:R 8:8:8:8 little endian - */ - WL_SHM_FORMAT_XBGR8888 = 0x34324258, - /** - * 32-bit RGBx format, [31:0] R:G:B:x 8:8:8:8 little endian - */ - WL_SHM_FORMAT_RGBX8888 = 0x34325852, - /** - * 32-bit BGRx format, [31:0] B:G:R:x 8:8:8:8 little endian - */ - WL_SHM_FORMAT_BGRX8888 = 0x34325842, - /** - * 32-bit ABGR format, [31:0] A:B:G:R 8:8:8:8 little endian - */ - WL_SHM_FORMAT_ABGR8888 = 0x34324241, - /** - * 32-bit RGBA format, [31:0] R:G:B:A 8:8:8:8 little endian - */ - WL_SHM_FORMAT_RGBA8888 = 0x34324152, - /** - * 32-bit BGRA format, [31:0] B:G:R:A 8:8:8:8 little endian - */ - WL_SHM_FORMAT_BGRA8888 = 0x34324142, - /** - * 32-bit xRGB format, [31:0] x:R:G:B 2:10:10:10 little endian - */ - WL_SHM_FORMAT_XRGB2101010 = 0x30335258, - /** - * 32-bit xBGR format, [31:0] x:B:G:R 2:10:10:10 little endian - */ - WL_SHM_FORMAT_XBGR2101010 = 0x30334258, - /** - * 32-bit RGBx format, [31:0] R:G:B:x 10:10:10:2 little endian - */ - WL_SHM_FORMAT_RGBX1010102 = 0x30335852, - /** - * 32-bit BGRx format, [31:0] B:G:R:x 10:10:10:2 little endian - */ - WL_SHM_FORMAT_BGRX1010102 = 0x30335842, - /** - * 32-bit ARGB format, [31:0] A:R:G:B 2:10:10:10 little endian - */ - WL_SHM_FORMAT_ARGB2101010 = 0x30335241, - /** - * 32-bit ABGR format, [31:0] A:B:G:R 2:10:10:10 little endian - */ - WL_SHM_FORMAT_ABGR2101010 = 0x30334241, - /** - * 32-bit RGBA format, [31:0] R:G:B:A 10:10:10:2 little endian - */ - WL_SHM_FORMAT_RGBA1010102 = 0x30334152, - /** - * 32-bit BGRA format, [31:0] B:G:R:A 10:10:10:2 little endian - */ - WL_SHM_FORMAT_BGRA1010102 = 0x30334142, - /** - * packed YCbCr format, [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian - */ - WL_SHM_FORMAT_YUYV = 0x56595559, - /** - * packed YCbCr format, [31:0] Cb0:Y1:Cr0:Y0 8:8:8:8 little endian - */ - WL_SHM_FORMAT_YVYU = 0x55595659, - /** - * packed YCbCr format, [31:0] Y1:Cr0:Y0:Cb0 8:8:8:8 little endian - */ - WL_SHM_FORMAT_UYVY = 0x59565955, - /** - * packed YCbCr format, [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian - */ - WL_SHM_FORMAT_VYUY = 0x59555956, - /** - * packed AYCbCr format, [31:0] A:Y:Cb:Cr 8:8:8:8 little endian - */ - WL_SHM_FORMAT_AYUV = 0x56555941, - /** - * 2 plane YCbCr Cr:Cb format, 2x2 subsampled Cr:Cb plane - */ - WL_SHM_FORMAT_NV12 = 0x3231564e, - /** - * 2 plane YCbCr Cb:Cr format, 2x2 subsampled Cb:Cr plane - */ - WL_SHM_FORMAT_NV21 = 0x3132564e, - /** - * 2 plane YCbCr Cr:Cb format, 2x1 subsampled Cr:Cb plane - */ - WL_SHM_FORMAT_NV16 = 0x3631564e, - /** - * 2 plane YCbCr Cb:Cr format, 2x1 subsampled Cb:Cr plane - */ - WL_SHM_FORMAT_NV61 = 0x3136564e, - /** - * 3 plane YCbCr format, 4x4 subsampled Cb (1) and Cr (2) planes - */ - WL_SHM_FORMAT_YUV410 = 0x39565559, - /** - * 3 plane YCbCr format, 4x4 subsampled Cr (1) and Cb (2) planes - */ - WL_SHM_FORMAT_YVU410 = 0x39555659, - /** - * 3 plane YCbCr format, 4x1 subsampled Cb (1) and Cr (2) planes - */ - WL_SHM_FORMAT_YUV411 = 0x31315559, - /** - * 3 plane YCbCr format, 4x1 subsampled Cr (1) and Cb (2) planes - */ - WL_SHM_FORMAT_YVU411 = 0x31315659, - /** - * 3 plane YCbCr format, 2x2 subsampled Cb (1) and Cr (2) planes - */ - WL_SHM_FORMAT_YUV420 = 0x32315559, - /** - * 3 plane YCbCr format, 2x2 subsampled Cr (1) and Cb (2) planes - */ - WL_SHM_FORMAT_YVU420 = 0x32315659, - /** - * 3 plane YCbCr format, 2x1 subsampled Cb (1) and Cr (2) planes - */ - WL_SHM_FORMAT_YUV422 = 0x36315559, - /** - * 3 plane YCbCr format, 2x1 subsampled Cr (1) and Cb (2) planes - */ - WL_SHM_FORMAT_YVU422 = 0x36315659, - /** - * 3 plane YCbCr format, non-subsampled Cb (1) and Cr (2) planes - */ - WL_SHM_FORMAT_YUV444 = 0x34325559, - /** - * 3 plane YCbCr format, non-subsampled Cr (1) and Cb (2) planes - */ - WL_SHM_FORMAT_YVU444 = 0x34325659, - /** - * [7:0] R - */ - WL_SHM_FORMAT_R8 = 0x20203852, - /** - * [15:0] R little endian - */ - WL_SHM_FORMAT_R16 = 0x20363152, - /** - * [15:0] R:G 8:8 little endian - */ - WL_SHM_FORMAT_RG88 = 0x38384752, - /** - * [15:0] G:R 8:8 little endian - */ - WL_SHM_FORMAT_GR88 = 0x38385247, - /** - * [31:0] R:G 16:16 little endian - */ - WL_SHM_FORMAT_RG1616 = 0x32334752, - /** - * [31:0] G:R 16:16 little endian - */ - WL_SHM_FORMAT_GR1616 = 0x32335247, - /** - * [63:0] x:R:G:B 16:16:16:16 little endian - */ - WL_SHM_FORMAT_XRGB16161616F = 0x48345258, - /** - * [63:0] x:B:G:R 16:16:16:16 little endian - */ - WL_SHM_FORMAT_XBGR16161616F = 0x48344258, - /** - * [63:0] A:R:G:B 16:16:16:16 little endian - */ - WL_SHM_FORMAT_ARGB16161616F = 0x48345241, - /** - * [63:0] A:B:G:R 16:16:16:16 little endian - */ - WL_SHM_FORMAT_ABGR16161616F = 0x48344241, - /** - * [31:0] X:Y:Cb:Cr 8:8:8:8 little endian - */ - WL_SHM_FORMAT_XYUV8888 = 0x56555958, - /** - * [23:0] Cr:Cb:Y 8:8:8 little endian - */ - WL_SHM_FORMAT_VUY888 = 0x34325556, - /** - * Y followed by U then V, 10:10:10. Non-linear modifier only - */ - WL_SHM_FORMAT_VUY101010 = 0x30335556, - /** - * [63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 10:6:10:6:10:6:10:6 little endian per 2 Y pixels - */ - WL_SHM_FORMAT_Y210 = 0x30313259, - /** - * [63:0] Cr0:0:Y1:0:Cb0:0:Y0:0 12:4:12:4:12:4:12:4 little endian per 2 Y pixels - */ - WL_SHM_FORMAT_Y212 = 0x32313259, - /** - * [63:0] Cr0:Y1:Cb0:Y0 16:16:16:16 little endian per 2 Y pixels - */ - WL_SHM_FORMAT_Y216 = 0x36313259, - /** - * [31:0] A:Cr:Y:Cb 2:10:10:10 little endian - */ - WL_SHM_FORMAT_Y410 = 0x30313459, - /** - * [63:0] A:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian - */ - WL_SHM_FORMAT_Y412 = 0x32313459, - /** - * [63:0] A:Cr:Y:Cb 16:16:16:16 little endian - */ - WL_SHM_FORMAT_Y416 = 0x36313459, - /** - * [31:0] X:Cr:Y:Cb 2:10:10:10 little endian - */ - WL_SHM_FORMAT_XVYU2101010 = 0x30335658, - /** - * [63:0] X:0:Cr:0:Y:0:Cb:0 12:4:12:4:12:4:12:4 little endian - */ - WL_SHM_FORMAT_XVYU12_16161616 = 0x36335658, - /** - * [63:0] X:Cr:Y:Cb 16:16:16:16 little endian - */ - WL_SHM_FORMAT_XVYU16161616 = 0x38345658, - /** - * [63:0] A3:A2:Y3:0:Cr0:0:Y2:0:A1:A0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian - */ - WL_SHM_FORMAT_Y0L0 = 0x304c3059, - /** - * [63:0] X3:X2:Y3:0:Cr0:0:Y2:0:X1:X0:Y1:0:Cb0:0:Y0:0 1:1:8:2:8:2:8:2:1:1:8:2:8:2:8:2 little endian - */ - WL_SHM_FORMAT_X0L0 = 0x304c3058, - /** - * [63:0] A3:A2:Y3:Cr0:Y2:A1:A0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian - */ - WL_SHM_FORMAT_Y0L2 = 0x324c3059, - /** - * [63:0] X3:X2:Y3:Cr0:Y2:X1:X0:Y1:Cb0:Y0 1:1:10:10:10:1:1:10:10:10 little endian - */ - WL_SHM_FORMAT_X0L2 = 0x324c3058, - WL_SHM_FORMAT_YUV420_8BIT = 0x38305559, - WL_SHM_FORMAT_YUV420_10BIT = 0x30315559, - WL_SHM_FORMAT_XRGB8888_A8 = 0x38415258, - WL_SHM_FORMAT_XBGR8888_A8 = 0x38414258, - WL_SHM_FORMAT_RGBX8888_A8 = 0x38415852, - WL_SHM_FORMAT_BGRX8888_A8 = 0x38415842, - WL_SHM_FORMAT_RGB888_A8 = 0x38413852, - WL_SHM_FORMAT_BGR888_A8 = 0x38413842, - WL_SHM_FORMAT_RGB565_A8 = 0x38413552, - WL_SHM_FORMAT_BGR565_A8 = 0x38413542, - /** - * non-subsampled Cr:Cb plane - */ - WL_SHM_FORMAT_NV24 = 0x3432564e, - /** - * non-subsampled Cb:Cr plane - */ - WL_SHM_FORMAT_NV42 = 0x3234564e, - /** - * 2x1 subsampled Cr:Cb plane, 10 bit per channel - */ - WL_SHM_FORMAT_P210 = 0x30313250, - /** - * 2x2 subsampled Cr:Cb plane 10 bits per channel - */ - WL_SHM_FORMAT_P010 = 0x30313050, - /** - * 2x2 subsampled Cr:Cb plane 12 bits per channel - */ - WL_SHM_FORMAT_P012 = 0x32313050, - /** - * 2x2 subsampled Cr:Cb plane 16 bits per channel - */ - WL_SHM_FORMAT_P016 = 0x36313050, - /** - * [63:0] A:x:B:x:G:x:R:x 10:6:10:6:10:6:10:6 little endian - */ - WL_SHM_FORMAT_AXBXGXRX106106106106 = 0x30314241, - /** - * 2x2 subsampled Cr:Cb plane - */ - WL_SHM_FORMAT_NV15 = 0x3531564e, - WL_SHM_FORMAT_Q410 = 0x30313451, - WL_SHM_FORMAT_Q401 = 0x31303451, - /** - * [63:0] x:R:G:B 16:16:16:16 little endian - */ - WL_SHM_FORMAT_XRGB16161616 = 0x38345258, - /** - * [63:0] x:B:G:R 16:16:16:16 little endian - */ - WL_SHM_FORMAT_XBGR16161616 = 0x38344258, - /** - * [63:0] A:R:G:B 16:16:16:16 little endian - */ - WL_SHM_FORMAT_ARGB16161616 = 0x38345241, - /** - * [63:0] A:B:G:R 16:16:16:16 little endian - */ - WL_SHM_FORMAT_ABGR16161616 = 0x38344241, -}; -#endif /* WL_SHM_FORMAT_ENUM */ - -/** - * @ingroup iface_wl_shm - * @struct wl_shm_listener - */ -struct wl_shm_listener { - /** - * pixel format description - * - * Informs the client about a valid pixel format that can be used - * for buffers. Known formats include argb8888 and xrgb8888. - * @param format buffer pixel format - */ - void (*format)(void *data, - struct wl_shm *wl_shm, - uint32_t format); -}; - -/** - * @ingroup iface_wl_shm - */ -static inline int -wl_shm_add_listener(struct wl_shm *wl_shm, - const struct wl_shm_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_shm, - (void (**)(void)) listener, data); -} - -#define WL_SHM_CREATE_POOL 0 - -/** - * @ingroup iface_wl_shm - */ -#define WL_SHM_FORMAT_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_shm - */ -#define WL_SHM_CREATE_POOL_SINCE_VERSION 1 - -/** @ingroup iface_wl_shm */ -static inline void -wl_shm_set_user_data(struct wl_shm *wl_shm, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_shm, user_data); -} - -/** @ingroup iface_wl_shm */ -static inline void * -wl_shm_get_user_data(struct wl_shm *wl_shm) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_shm); -} - -static inline uint32_t -wl_shm_get_version(struct wl_shm *wl_shm) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_shm); -} - -/** @ingroup iface_wl_shm */ -static inline void -wl_shm_destroy(struct wl_shm *wl_shm) -{ - wl_proxy_destroy((struct wl_proxy *) wl_shm); -} - -/** - * @ingroup iface_wl_shm - * - * Create a new wl_shm_pool object. - * - * The pool can be used to create shared memory based buffer - * objects. The server will mmap size bytes of the passed file - * descriptor, to use as backing memory for the pool. - */ -static inline struct wl_shm_pool * -wl_shm_create_pool(struct wl_shm *wl_shm, int32_t fd, int32_t size) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_shm, - WL_SHM_CREATE_POOL, &wl_shm_pool_interface, wl_proxy_get_version((struct wl_proxy *) wl_shm), 0, NULL, fd, size); - - return (struct wl_shm_pool *) id; -} - -/** - * @ingroup iface_wl_buffer - * @struct wl_buffer_listener - */ -struct wl_buffer_listener { - /** - * compositor releases buffer - * - * Sent when this wl_buffer is no longer used by the compositor. - * The client is now free to reuse or destroy this buffer and its - * backing storage. - * - * If a client receives a release event before the frame callback - * requested in the same wl_surface.commit that attaches this - * wl_buffer to a surface, then the client is immediately free to - * reuse the buffer and its backing storage, and does not need a - * second buffer for the next surface content update. Typically - * this is possible, when the compositor maintains a copy of the - * wl_surface contents, e.g. as a GL texture. This is an important - * optimization for GL(ES) compositors with wl_shm clients. - */ - void (*release)(void *data, - struct wl_buffer *wl_buffer); -}; - -/** - * @ingroup iface_wl_buffer - */ -static inline int -wl_buffer_add_listener(struct wl_buffer *wl_buffer, - const struct wl_buffer_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_buffer, - (void (**)(void)) listener, data); -} - -#define WL_BUFFER_DESTROY 0 - -/** - * @ingroup iface_wl_buffer - */ -#define WL_BUFFER_RELEASE_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_buffer - */ -#define WL_BUFFER_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_wl_buffer */ -static inline void -wl_buffer_set_user_data(struct wl_buffer *wl_buffer, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_buffer, user_data); -} - -/** @ingroup iface_wl_buffer */ -static inline void * -wl_buffer_get_user_data(struct wl_buffer *wl_buffer) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_buffer); -} - -static inline uint32_t -wl_buffer_get_version(struct wl_buffer *wl_buffer) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_buffer); -} - -/** - * @ingroup iface_wl_buffer - * - * Destroy a buffer. If and how you need to release the backing - * storage is defined by the buffer factory interface. - * - * For possible side-effects to a surface, see wl_surface.attach. - */ -static inline void -wl_buffer_destroy(struct wl_buffer *wl_buffer) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_buffer, - WL_BUFFER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_buffer), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef WL_DATA_OFFER_ERROR_ENUM -#define WL_DATA_OFFER_ERROR_ENUM -enum wl_data_offer_error { - /** - * finish request was called untimely - */ - WL_DATA_OFFER_ERROR_INVALID_FINISH = 0, - /** - * action mask contains invalid values - */ - WL_DATA_OFFER_ERROR_INVALID_ACTION_MASK = 1, - /** - * action argument has an invalid value - */ - WL_DATA_OFFER_ERROR_INVALID_ACTION = 2, - /** - * offer doesn't accept this request - */ - WL_DATA_OFFER_ERROR_INVALID_OFFER = 3, -}; -#endif /* WL_DATA_OFFER_ERROR_ENUM */ - -/** - * @ingroup iface_wl_data_offer - * @struct wl_data_offer_listener - */ -struct wl_data_offer_listener { - /** - * advertise offered mime type - * - * Sent immediately after creating the wl_data_offer object. One - * event per offered mime type. - * @param mime_type offered mime type - */ - void (*offer)(void *data, - struct wl_data_offer *wl_data_offer, - const char *mime_type); - /** - * notify the source-side available actions - * - * This event indicates the actions offered by the data source. - * It will be sent immediately after creating the wl_data_offer - * object, or anytime the source side changes its offered actions - * through wl_data_source.set_actions. - * @param source_actions actions offered by the data source - * @since 3 - */ - void (*source_actions)(void *data, - struct wl_data_offer *wl_data_offer, - uint32_t source_actions); - /** - * notify the selected action - * - * This event indicates the action selected by the compositor - * after matching the source/destination side actions. Only one - * action (or none) will be offered here. - * - * This event can be emitted multiple times during the - * drag-and-drop operation in response to destination side action - * changes through wl_data_offer.set_actions. - * - * This event will no longer be emitted after wl_data_device.drop - * happened on the drag-and-drop destination, the client must honor - * the last action received, or the last preferred one set through - * wl_data_offer.set_actions when handling an "ask" action. - * - * Compositors may also change the selected action on the fly, - * mainly in response to keyboard modifier changes during the - * drag-and-drop operation. - * - * The most recent action received is always the valid one. Prior - * to receiving wl_data_device.drop, the chosen action may change - * (e.g. due to keyboard modifiers being pressed). At the time of - * receiving wl_data_device.drop the drag-and-drop destination must - * honor the last action received. - * - * Action changes may still happen after wl_data_device.drop, - * especially on "ask" actions, where the drag-and-drop destination - * may choose another action afterwards. Action changes happening - * at this stage are always the result of inter-client negotiation, - * the compositor shall no longer be able to induce a different - * action. - * - * Upon "ask" actions, it is expected that the drag-and-drop - * destination may potentially choose a different action and/or - * mime type, based on wl_data_offer.source_actions and finally - * chosen by the user (e.g. popping up a menu with the available - * options). The final wl_data_offer.set_actions and - * wl_data_offer.accept requests must happen before the call to - * wl_data_offer.finish. - * @param dnd_action action selected by the compositor - * @since 3 - */ - void (*action)(void *data, - struct wl_data_offer *wl_data_offer, - uint32_t dnd_action); -}; - -/** - * @ingroup iface_wl_data_offer - */ -static inline int -wl_data_offer_add_listener(struct wl_data_offer *wl_data_offer, - const struct wl_data_offer_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_data_offer, - (void (**)(void)) listener, data); -} - -#define WL_DATA_OFFER_ACCEPT 0 -#define WL_DATA_OFFER_RECEIVE 1 -#define WL_DATA_OFFER_DESTROY 2 -#define WL_DATA_OFFER_FINISH 3 -#define WL_DATA_OFFER_SET_ACTIONS 4 - -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_OFFER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_SOURCE_ACTIONS_SINCE_VERSION 3 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_ACTION_SINCE_VERSION 3 - -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_ACCEPT_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_RECEIVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_FINISH_SINCE_VERSION 3 -/** - * @ingroup iface_wl_data_offer - */ -#define WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION 3 - -/** @ingroup iface_wl_data_offer */ -static inline void -wl_data_offer_set_user_data(struct wl_data_offer *wl_data_offer, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_data_offer, user_data); -} - -/** @ingroup iface_wl_data_offer */ -static inline void * -wl_data_offer_get_user_data(struct wl_data_offer *wl_data_offer) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_data_offer); -} - -static inline uint32_t -wl_data_offer_get_version(struct wl_data_offer *wl_data_offer) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_data_offer); -} - -/** - * @ingroup iface_wl_data_offer - * - * Indicate that the client can accept the given mime type, or - * NULL for not accepted. - * - * For objects of version 2 or older, this request is used by the - * client to give feedback whether the client can receive the given - * mime type, or NULL if none is accepted; the feedback does not - * determine whether the drag-and-drop operation succeeds or not. - * - * For objects of version 3 or newer, this request determines the - * final result of the drag-and-drop operation. If the end result - * is that no mime types were accepted, the drag-and-drop operation - * will be cancelled and the corresponding drag source will receive - * wl_data_source.cancelled. Clients may still use this event in - * conjunction with wl_data_source.action for feedback. - */ -static inline void -wl_data_offer_accept(struct wl_data_offer *wl_data_offer, uint32_t serial, const char *mime_type) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, - WL_DATA_OFFER_ACCEPT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0, serial, mime_type); -} - -/** - * @ingroup iface_wl_data_offer - * - * To transfer the offered data, the client issues this request - * and indicates the mime type it wants to receive. The transfer - * happens through the passed file descriptor (typically created - * with the pipe system call). The source client writes the data - * in the mime type representation requested and then closes the - * file descriptor. - * - * The receiving client reads from the read end of the pipe until - * EOF and then closes its end, at which point the transfer is - * complete. - * - * This request may happen multiple times for different mime types, - * both before and after wl_data_device.drop. Drag-and-drop destination - * clients may preemptively fetch data or examine it more closely to - * determine acceptance. - */ -static inline void -wl_data_offer_receive(struct wl_data_offer *wl_data_offer, const char *mime_type, int32_t fd) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, - WL_DATA_OFFER_RECEIVE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0, mime_type, fd); -} - -/** - * @ingroup iface_wl_data_offer - * - * Destroy the data offer. - */ -static inline void -wl_data_offer_destroy(struct wl_data_offer *wl_data_offer) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, - WL_DATA_OFFER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_data_offer - * - * Notifies the compositor that the drag destination successfully - * finished the drag-and-drop operation. - * - * Upon receiving this request, the compositor will emit - * wl_data_source.dnd_finished on the drag source client. - * - * It is a client error to perform other requests than - * wl_data_offer.destroy after this one. It is also an error to perform - * this request after a NULL mime type has been set in - * wl_data_offer.accept or no action was received through - * wl_data_offer.action. - * - * If wl_data_offer.finish request is received for a non drag and drop - * operation, the invalid_finish protocol error is raised. - */ -static inline void -wl_data_offer_finish(struct wl_data_offer *wl_data_offer) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, - WL_DATA_OFFER_FINISH, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0); -} - -/** - * @ingroup iface_wl_data_offer - * - * Sets the actions that the destination side client supports for - * this operation. This request may trigger the emission of - * wl_data_source.action and wl_data_offer.action events if the compositor - * needs to change the selected action. - * - * This request can be called multiple times throughout the - * drag-and-drop operation, typically in response to wl_data_device.enter - * or wl_data_device.motion events. - * - * This request determines the final result of the drag-and-drop - * operation. If the end result is that no action is accepted, - * the drag source will receive wl_data_source.cancelled. - * - * The dnd_actions argument must contain only values expressed in the - * wl_data_device_manager.dnd_actions enum, and the preferred_action - * argument must only contain one of those values set, otherwise it - * will result in a protocol error. - * - * While managing an "ask" action, the destination drag-and-drop client - * may perform further wl_data_offer.receive requests, and is expected - * to perform one last wl_data_offer.set_actions request with a preferred - * action other than "ask" (and optionally wl_data_offer.accept) before - * requesting wl_data_offer.finish, in order to convey the action selected - * by the user. If the preferred action is not in the - * wl_data_offer.source_actions mask, an error will be raised. - * - * If the "ask" action is dismissed (e.g. user cancellation), the client - * is expected to perform wl_data_offer.destroy right away. - * - * This request can only be made on drag-and-drop offers, a protocol error - * will be raised otherwise. - */ -static inline void -wl_data_offer_set_actions(struct wl_data_offer *wl_data_offer, uint32_t dnd_actions, uint32_t preferred_action) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_offer, - WL_DATA_OFFER_SET_ACTIONS, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_offer), 0, dnd_actions, preferred_action); -} - -#ifndef WL_DATA_SOURCE_ERROR_ENUM -#define WL_DATA_SOURCE_ERROR_ENUM -enum wl_data_source_error { - /** - * action mask contains invalid values - */ - WL_DATA_SOURCE_ERROR_INVALID_ACTION_MASK = 0, - /** - * source doesn't accept this request - */ - WL_DATA_SOURCE_ERROR_INVALID_SOURCE = 1, -}; -#endif /* WL_DATA_SOURCE_ERROR_ENUM */ - -/** - * @ingroup iface_wl_data_source - * @struct wl_data_source_listener - */ -struct wl_data_source_listener { - /** - * a target accepts an offered mime type - * - * Sent when a target accepts pointer_focus or motion events. If - * a target does not accept any of the offered types, type is NULL. - * - * Used for feedback during drag-and-drop. - * @param mime_type mime type accepted by the target - */ - void (*target)(void *data, - struct wl_data_source *wl_data_source, - const char *mime_type); - /** - * send the data - * - * Request for data from the client. Send the data as the - * specified mime type over the passed file descriptor, then close - * it. - * @param mime_type mime type for the data - * @param fd file descriptor for the data - */ - void (*send)(void *data, - struct wl_data_source *wl_data_source, - const char *mime_type, - int32_t fd); - /** - * selection was cancelled - * - * This data source is no longer valid. There are several reasons - * why this could happen: - * - * - The data source has been replaced by another data source. - - * The drag-and-drop operation was performed, but the drop - * destination did not accept any of the mime types offered through - * wl_data_source.target. - The drag-and-drop operation was - * performed, but the drop destination did not select any of the - * actions present in the mask offered through - * wl_data_source.action. - The drag-and-drop operation was - * performed but didn't happen over a surface. - The compositor - * cancelled the drag-and-drop operation (e.g. compositor dependent - * timeouts to avoid stale drag-and-drop transfers). - * - * The client should clean up and destroy this data source. - * - * For objects of version 2 or older, wl_data_source.cancelled will - * only be emitted if the data source was replaced by another data - * source. - */ - void (*cancelled)(void *data, - struct wl_data_source *wl_data_source); - /** - * the drag-and-drop operation physically finished - * - * The user performed the drop action. This event does not - * indicate acceptance, wl_data_source.cancelled may still be - * emitted afterwards if the drop destination does not accept any - * mime type. - * - * However, this event might however not be received if the - * compositor cancelled the drag-and-drop operation before this - * event could happen. - * - * Note that the data_source may still be used in the future and - * should not be destroyed here. - * @since 3 - */ - void (*dnd_drop_performed)(void *data, - struct wl_data_source *wl_data_source); - /** - * the drag-and-drop operation concluded - * - * The drop destination finished interoperating with this data - * source, so the client is now free to destroy this data source - * and free all associated data. - * - * If the action used to perform the operation was "move", the - * source can now delete the transferred data. - * @since 3 - */ - void (*dnd_finished)(void *data, - struct wl_data_source *wl_data_source); - /** - * notify the selected action - * - * This event indicates the action selected by the compositor - * after matching the source/destination side actions. Only one - * action (or none) will be offered here. - * - * This event can be emitted multiple times during the - * drag-and-drop operation, mainly in response to destination side - * changes through wl_data_offer.set_actions, and as the data - * device enters/leaves surfaces. - * - * It is only possible to receive this event after - * wl_data_source.dnd_drop_performed if the drag-and-drop operation - * ended in an "ask" action, in which case the final - * wl_data_source.action event will happen immediately before - * wl_data_source.dnd_finished. - * - * Compositors may also change the selected action on the fly, - * mainly in response to keyboard modifier changes during the - * drag-and-drop operation. - * - * The most recent action received is always the valid one. The - * chosen action may change alongside negotiation (e.g. an "ask" - * action can turn into a "move" operation), so the effects of the - * final action must always be applied in - * wl_data_offer.dnd_finished. - * - * Clients can trigger cursor surface changes from this point, so - * they reflect the current action. - * @param dnd_action action selected by the compositor - * @since 3 - */ - void (*action)(void *data, - struct wl_data_source *wl_data_source, - uint32_t dnd_action); -}; - -/** - * @ingroup iface_wl_data_source - */ -static inline int -wl_data_source_add_listener(struct wl_data_source *wl_data_source, - const struct wl_data_source_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_data_source, - (void (**)(void)) listener, data); -} - -#define WL_DATA_SOURCE_OFFER 0 -#define WL_DATA_SOURCE_DESTROY 1 -#define WL_DATA_SOURCE_SET_ACTIONS 2 - -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_TARGET_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_SEND_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_CANCELLED_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_DND_DROP_PERFORMED_SINCE_VERSION 3 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_DND_FINISHED_SINCE_VERSION 3 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_ACTION_SINCE_VERSION 3 - -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_OFFER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_source - */ -#define WL_DATA_SOURCE_SET_ACTIONS_SINCE_VERSION 3 - -/** @ingroup iface_wl_data_source */ -static inline void -wl_data_source_set_user_data(struct wl_data_source *wl_data_source, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_data_source, user_data); -} - -/** @ingroup iface_wl_data_source */ -static inline void * -wl_data_source_get_user_data(struct wl_data_source *wl_data_source) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_data_source); -} - -static inline uint32_t -wl_data_source_get_version(struct wl_data_source *wl_data_source) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_data_source); -} - -/** - * @ingroup iface_wl_data_source - * - * This request adds a mime type to the set of mime types - * advertised to targets. Can be called several times to offer - * multiple types. - */ -static inline void -wl_data_source_offer(struct wl_data_source *wl_data_source, const char *mime_type) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_source, - WL_DATA_SOURCE_OFFER, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_source), 0, mime_type); -} - -/** - * @ingroup iface_wl_data_source - * - * Destroy the data source. - */ -static inline void -wl_data_source_destroy(struct wl_data_source *wl_data_source) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_source, - WL_DATA_SOURCE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_source), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_data_source - * - * Sets the actions that the source side client supports for this - * operation. This request may trigger wl_data_source.action and - * wl_data_offer.action events if the compositor needs to change the - * selected action. - * - * The dnd_actions argument must contain only values expressed in the - * wl_data_device_manager.dnd_actions enum, otherwise it will result - * in a protocol error. - * - * This request must be made once only, and can only be made on sources - * used in drag-and-drop, so it must be performed before - * wl_data_device.start_drag. Attempting to use the source other than - * for drag-and-drop will raise a protocol error. - */ -static inline void -wl_data_source_set_actions(struct wl_data_source *wl_data_source, uint32_t dnd_actions) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_source, - WL_DATA_SOURCE_SET_ACTIONS, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_source), 0, dnd_actions); -} - -#ifndef WL_DATA_DEVICE_ERROR_ENUM -#define WL_DATA_DEVICE_ERROR_ENUM -enum wl_data_device_error { - /** - * given wl_surface has another role - */ - WL_DATA_DEVICE_ERROR_ROLE = 0, -}; -#endif /* WL_DATA_DEVICE_ERROR_ENUM */ - -/** - * @ingroup iface_wl_data_device - * @struct wl_data_device_listener - */ -struct wl_data_device_listener { - /** - * introduce a new wl_data_offer - * - * The data_offer event introduces a new wl_data_offer object, - * which will subsequently be used in either the data_device.enter - * event (for drag-and-drop) or the data_device.selection event - * (for selections). Immediately following the - * data_device.data_offer event, the new data_offer object will - * send out data_offer.offer events to describe the mime types it - * offers. - * @param id the new data_offer object - */ - void (*data_offer)(void *data, - struct wl_data_device *wl_data_device, - struct wl_data_offer *id); - /** - * initiate drag-and-drop session - * - * This event is sent when an active drag-and-drop pointer enters - * a surface owned by the client. The position of the pointer at - * enter time is provided by the x and y arguments, in - * surface-local coordinates. - * @param serial serial number of the enter event - * @param surface client surface entered - * @param x surface-local x coordinate - * @param y surface-local y coordinate - * @param id source data_offer object - */ - void (*enter)(void *data, - struct wl_data_device *wl_data_device, - uint32_t serial, - struct wl_surface *surface, - wl_fixed_t x, - wl_fixed_t y, - struct wl_data_offer *id); - /** - * end drag-and-drop session - * - * This event is sent when the drag-and-drop pointer leaves the - * surface and the session ends. The client must destroy the - * wl_data_offer introduced at enter time at this point. - */ - void (*leave)(void *data, - struct wl_data_device *wl_data_device); - /** - * drag-and-drop session motion - * - * This event is sent when the drag-and-drop pointer moves within - * the currently focused surface. The new position of the pointer - * is provided by the x and y arguments, in surface-local - * coordinates. - * @param time timestamp with millisecond granularity - * @param x surface-local x coordinate - * @param y surface-local y coordinate - */ - void (*motion)(void *data, - struct wl_data_device *wl_data_device, - uint32_t time, - wl_fixed_t x, - wl_fixed_t y); - /** - * end drag-and-drop session successfully - * - * The event is sent when a drag-and-drop operation is ended - * because the implicit grab is removed. - * - * The drag-and-drop destination is expected to honor the last - * action received through wl_data_offer.action, if the resulting - * action is "copy" or "move", the destination can still perform - * wl_data_offer.receive requests, and is expected to end all - * transfers with a wl_data_offer.finish request. - * - * If the resulting action is "ask", the action will not be - * considered final. The drag-and-drop destination is expected to - * perform one last wl_data_offer.set_actions request, or - * wl_data_offer.destroy in order to cancel the operation. - */ - void (*drop)(void *data, - struct wl_data_device *wl_data_device); - /** - * advertise new selection - * - * The selection event is sent out to notify the client of a new - * wl_data_offer for the selection for this device. The - * data_device.data_offer and the data_offer.offer events are sent - * out immediately before this event to introduce the data offer - * object. The selection event is sent to a client immediately - * before receiving keyboard focus and when a new selection is set - * while the client has keyboard focus. The data_offer is valid - * until a new data_offer or NULL is received or until the client - * loses keyboard focus. Switching surface with keyboard focus - * within the same client doesn't mean a new selection will be - * sent. The client must destroy the previous selection data_offer, - * if any, upon receiving this event. - * @param id selection data_offer object - */ - void (*selection)(void *data, - struct wl_data_device *wl_data_device, - struct wl_data_offer *id); -}; - -/** - * @ingroup iface_wl_data_device - */ -static inline int -wl_data_device_add_listener(struct wl_data_device *wl_data_device, - const struct wl_data_device_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_data_device, - (void (**)(void)) listener, data); -} - -#define WL_DATA_DEVICE_START_DRAG 0 -#define WL_DATA_DEVICE_SET_SELECTION 1 -#define WL_DATA_DEVICE_RELEASE 2 - -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_DATA_OFFER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_ENTER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_LEAVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_MOTION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_DROP_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_SELECTION_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_START_DRAG_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_SET_SELECTION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device - */ -#define WL_DATA_DEVICE_RELEASE_SINCE_VERSION 2 - -/** @ingroup iface_wl_data_device */ -static inline void -wl_data_device_set_user_data(struct wl_data_device *wl_data_device, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_data_device, user_data); -} - -/** @ingroup iface_wl_data_device */ -static inline void * -wl_data_device_get_user_data(struct wl_data_device *wl_data_device) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_data_device); -} - -static inline uint32_t -wl_data_device_get_version(struct wl_data_device *wl_data_device) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_data_device); -} - -/** @ingroup iface_wl_data_device */ -static inline void -wl_data_device_destroy(struct wl_data_device *wl_data_device) -{ - wl_proxy_destroy((struct wl_proxy *) wl_data_device); -} - -/** - * @ingroup iface_wl_data_device - * - * This request asks the compositor to start a drag-and-drop - * operation on behalf of the client. - * - * The source argument is the data source that provides the data - * for the eventual data transfer. If source is NULL, enter, leave - * and motion events are sent only to the client that initiated the - * drag and the client is expected to handle the data passing - * internally. If source is destroyed, the drag-and-drop session will be - * cancelled. - * - * The origin surface is the surface where the drag originates and - * the client must have an active implicit grab that matches the - * serial. - * - * The icon surface is an optional (can be NULL) surface that - * provides an icon to be moved around with the cursor. Initially, - * the top-left corner of the icon surface is placed at the cursor - * hotspot, but subsequent wl_surface.attach request can move the - * relative position. Attach requests must be confirmed with - * wl_surface.commit as usual. The icon surface is given the role of - * a drag-and-drop icon. If the icon surface already has another role, - * it raises a protocol error. - * - * The input region is ignored for wl_surfaces with the role of a - * drag-and-drop icon. - */ -static inline void -wl_data_device_start_drag(struct wl_data_device *wl_data_device, struct wl_data_source *source, struct wl_surface *origin, struct wl_surface *icon, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device, - WL_DATA_DEVICE_START_DRAG, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_device), 0, source, origin, icon, serial); -} - -/** - * @ingroup iface_wl_data_device - * - * This request asks the compositor to set the selection - * to the data from the source on behalf of the client. - * - * To unset the selection, set the source to NULL. - */ -static inline void -wl_data_device_set_selection(struct wl_data_device *wl_data_device, struct wl_data_source *source, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device, - WL_DATA_DEVICE_SET_SELECTION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_device), 0, source, serial); -} - -/** - * @ingroup iface_wl_data_device - * - * This request destroys the data device. - */ -static inline void -wl_data_device_release(struct wl_data_device *wl_data_device) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device, - WL_DATA_DEVICE_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_data_device), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM -#define WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM -/** - * @ingroup iface_wl_data_device_manager - * drag and drop actions - * - * This is a bitmask of the available/preferred actions in a - * drag-and-drop operation. - * - * In the compositor, the selected action is a result of matching the - * actions offered by the source and destination sides. "action" events - * with a "none" action will be sent to both source and destination if - * there is no match. All further checks will effectively happen on - * (source actions ∩ destination actions). - * - * In addition, compositors may also pick different actions in - * reaction to key modifiers being pressed. One common design that - * is used in major toolkits (and the behavior recommended for - * compositors) is: - * - * - If no modifiers are pressed, the first match (in bit order) - * will be used. - * - Pressing Shift selects "move", if enabled in the mask. - * - Pressing Control selects "copy", if enabled in the mask. - * - * Behavior beyond that is considered implementation-dependent. - * Compositors may for example bind other modifiers (like Alt/Meta) - * or drags initiated with other buttons than BTN_LEFT to specific - * actions (e.g. "ask"). - */ -enum wl_data_device_manager_dnd_action { - /** - * no action - */ - WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE = 0, - /** - * copy action - */ - WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY = 1, - /** - * move action - */ - WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE = 2, - /** - * ask action - */ - WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK = 4, -}; -#endif /* WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM */ - -#define WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE 0 -#define WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE 1 - - -/** - * @ingroup iface_wl_data_device_manager - */ -#define WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_data_device_manager - */ -#define WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE_SINCE_VERSION 1 - -/** @ingroup iface_wl_data_device_manager */ -static inline void -wl_data_device_manager_set_user_data(struct wl_data_device_manager *wl_data_device_manager, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_data_device_manager, user_data); -} - -/** @ingroup iface_wl_data_device_manager */ -static inline void * -wl_data_device_manager_get_user_data(struct wl_data_device_manager *wl_data_device_manager) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_data_device_manager); -} - -static inline uint32_t -wl_data_device_manager_get_version(struct wl_data_device_manager *wl_data_device_manager) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_data_device_manager); -} - -/** @ingroup iface_wl_data_device_manager */ -static inline void -wl_data_device_manager_destroy(struct wl_data_device_manager *wl_data_device_manager) -{ - wl_proxy_destroy((struct wl_proxy *) wl_data_device_manager); -} - -/** - * @ingroup iface_wl_data_device_manager - * - * Create a new data source. - */ -static inline struct wl_data_source * -wl_data_device_manager_create_data_source(struct wl_data_device_manager *wl_data_device_manager) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device_manager, - WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE, &wl_data_source_interface, wl_proxy_get_version((struct wl_proxy *) wl_data_device_manager), 0, NULL); - - return (struct wl_data_source *) id; -} - -/** - * @ingroup iface_wl_data_device_manager - * - * Create a new data device for a given seat. - */ -static inline struct wl_data_device * -wl_data_device_manager_get_data_device(struct wl_data_device_manager *wl_data_device_manager, struct wl_seat *seat) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_data_device_manager, - WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE, &wl_data_device_interface, wl_proxy_get_version((struct wl_proxy *) wl_data_device_manager), 0, NULL, seat); - - return (struct wl_data_device *) id; -} - -#ifndef WL_SHELL_ERROR_ENUM -#define WL_SHELL_ERROR_ENUM -enum wl_shell_error { - /** - * given wl_surface has another role - */ - WL_SHELL_ERROR_ROLE = 0, -}; -#endif /* WL_SHELL_ERROR_ENUM */ - -#define WL_SHELL_GET_SHELL_SURFACE 0 - - -/** - * @ingroup iface_wl_shell - */ -#define WL_SHELL_GET_SHELL_SURFACE_SINCE_VERSION 1 - -/** @ingroup iface_wl_shell */ -static inline void -wl_shell_set_user_data(struct wl_shell *wl_shell, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_shell, user_data); -} - -/** @ingroup iface_wl_shell */ -static inline void * -wl_shell_get_user_data(struct wl_shell *wl_shell) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_shell); -} - -static inline uint32_t -wl_shell_get_version(struct wl_shell *wl_shell) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_shell); -} - -/** @ingroup iface_wl_shell */ -static inline void -wl_shell_destroy(struct wl_shell *wl_shell) -{ - wl_proxy_destroy((struct wl_proxy *) wl_shell); -} - -/** - * @ingroup iface_wl_shell - * - * Create a shell surface for an existing surface. This gives - * the wl_surface the role of a shell surface. If the wl_surface - * already has another role, it raises a protocol error. - * - * Only one shell surface can be associated with a given surface. - */ -static inline struct wl_shell_surface * -wl_shell_get_shell_surface(struct wl_shell *wl_shell, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_shell, - WL_SHELL_GET_SHELL_SURFACE, &wl_shell_surface_interface, wl_proxy_get_version((struct wl_proxy *) wl_shell), 0, NULL, surface); - - return (struct wl_shell_surface *) id; -} - -#ifndef WL_SHELL_SURFACE_RESIZE_ENUM -#define WL_SHELL_SURFACE_RESIZE_ENUM -/** - * @ingroup iface_wl_shell_surface - * edge values for resizing - * - * These values are used to indicate which edge of a surface - * is being dragged in a resize operation. The server may - * use this information to adapt its behavior, e.g. choose - * an appropriate cursor image. - */ -enum wl_shell_surface_resize { - /** - * no edge - */ - WL_SHELL_SURFACE_RESIZE_NONE = 0, - /** - * top edge - */ - WL_SHELL_SURFACE_RESIZE_TOP = 1, - /** - * bottom edge - */ - WL_SHELL_SURFACE_RESIZE_BOTTOM = 2, - /** - * left edge - */ - WL_SHELL_SURFACE_RESIZE_LEFT = 4, - /** - * top and left edges - */ - WL_SHELL_SURFACE_RESIZE_TOP_LEFT = 5, - /** - * bottom and left edges - */ - WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT = 6, - /** - * right edge - */ - WL_SHELL_SURFACE_RESIZE_RIGHT = 8, - /** - * top and right edges - */ - WL_SHELL_SURFACE_RESIZE_TOP_RIGHT = 9, - /** - * bottom and right edges - */ - WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT = 10, -}; -#endif /* WL_SHELL_SURFACE_RESIZE_ENUM */ - -#ifndef WL_SHELL_SURFACE_TRANSIENT_ENUM -#define WL_SHELL_SURFACE_TRANSIENT_ENUM -/** - * @ingroup iface_wl_shell_surface - * details of transient behaviour - * - * These flags specify details of the expected behaviour - * of transient surfaces. Used in the set_transient request. - */ -enum wl_shell_surface_transient { - /** - * do not set keyboard focus - */ - WL_SHELL_SURFACE_TRANSIENT_INACTIVE = 0x1, -}; -#endif /* WL_SHELL_SURFACE_TRANSIENT_ENUM */ - -#ifndef WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM -#define WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM -/** - * @ingroup iface_wl_shell_surface - * different method to set the surface fullscreen - * - * Hints to indicate to the compositor how to deal with a conflict - * between the dimensions of the surface and the dimensions of the - * output. The compositor is free to ignore this parameter. - */ -enum wl_shell_surface_fullscreen_method { - /** - * no preference, apply default policy - */ - WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT = 0, - /** - * scale, preserve the surface's aspect ratio and center on output - */ - WL_SHELL_SURFACE_FULLSCREEN_METHOD_SCALE = 1, - /** - * switch output mode to the smallest mode that can fit the surface, add black borders to compensate size mismatch - */ - WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER = 2, - /** - * no upscaling, center on output and add black borders to compensate size mismatch - */ - WL_SHELL_SURFACE_FULLSCREEN_METHOD_FILL = 3, -}; -#endif /* WL_SHELL_SURFACE_FULLSCREEN_METHOD_ENUM */ - -/** - * @ingroup iface_wl_shell_surface - * @struct wl_shell_surface_listener - */ -struct wl_shell_surface_listener { - /** - * ping client - * - * Ping a client to check if it is receiving events and sending - * requests. A client is expected to reply with a pong request. - * @param serial serial number of the ping - */ - void (*ping)(void *data, - struct wl_shell_surface *wl_shell_surface, - uint32_t serial); - /** - * suggest resize - * - * The configure event asks the client to resize its surface. - * - * The size is a hint, in the sense that the client is free to - * ignore it if it doesn't resize, pick a smaller size (to satisfy - * aspect ratio or resize in steps of NxM pixels). - * - * The edges parameter provides a hint about how the surface was - * resized. The client may use this information to decide how to - * adjust its content to the new size (e.g. a scrolling area might - * adjust its content position to leave the viewable content - * unmoved). - * - * The client is free to dismiss all but the last configure event - * it received. - * - * The width and height arguments specify the size of the window in - * surface-local coordinates. - * @param edges how the surface was resized - * @param width new width of the surface - * @param height new height of the surface - */ - void (*configure)(void *data, - struct wl_shell_surface *wl_shell_surface, - uint32_t edges, - int32_t width, - int32_t height); - /** - * popup interaction is done - * - * The popup_done event is sent out when a popup grab is broken, - * that is, when the user clicks a surface that doesn't belong to - * the client owning the popup surface. - */ - void (*popup_done)(void *data, - struct wl_shell_surface *wl_shell_surface); -}; - -/** - * @ingroup iface_wl_shell_surface - */ -static inline int -wl_shell_surface_add_listener(struct wl_shell_surface *wl_shell_surface, - const struct wl_shell_surface_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_shell_surface, - (void (**)(void)) listener, data); -} - -#define WL_SHELL_SURFACE_PONG 0 -#define WL_SHELL_SURFACE_MOVE 1 -#define WL_SHELL_SURFACE_RESIZE 2 -#define WL_SHELL_SURFACE_SET_TOPLEVEL 3 -#define WL_SHELL_SURFACE_SET_TRANSIENT 4 -#define WL_SHELL_SURFACE_SET_FULLSCREEN 5 -#define WL_SHELL_SURFACE_SET_POPUP 6 -#define WL_SHELL_SURFACE_SET_MAXIMIZED 7 -#define WL_SHELL_SURFACE_SET_TITLE 8 -#define WL_SHELL_SURFACE_SET_CLASS 9 - -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_PING_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_CONFIGURE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_POPUP_DONE_SINCE_VERSION 1 - -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_PONG_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_MOVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_RESIZE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_TOPLEVEL_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_TRANSIENT_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_FULLSCREEN_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_POPUP_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_MAXIMIZED_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_TITLE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_shell_surface - */ -#define WL_SHELL_SURFACE_SET_CLASS_SINCE_VERSION 1 - -/** @ingroup iface_wl_shell_surface */ -static inline void -wl_shell_surface_set_user_data(struct wl_shell_surface *wl_shell_surface, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_shell_surface, user_data); -} - -/** @ingroup iface_wl_shell_surface */ -static inline void * -wl_shell_surface_get_user_data(struct wl_shell_surface *wl_shell_surface) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_shell_surface); -} - -static inline uint32_t -wl_shell_surface_get_version(struct wl_shell_surface *wl_shell_surface) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_shell_surface); -} - -/** @ingroup iface_wl_shell_surface */ -static inline void -wl_shell_surface_destroy(struct wl_shell_surface *wl_shell_surface) -{ - wl_proxy_destroy((struct wl_proxy *) wl_shell_surface); -} - -/** - * @ingroup iface_wl_shell_surface - * - * A client must respond to a ping event with a pong request or - * the client may be deemed unresponsive. - */ -static inline void -wl_shell_surface_pong(struct wl_shell_surface *wl_shell_surface, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_PONG, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, serial); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Start a pointer-driven move of the surface. - * - * This request must be used in response to a button press event. - * The server may ignore move requests depending on the state of - * the surface (e.g. fullscreen or maximized). - */ -static inline void -wl_shell_surface_move(struct wl_shell_surface *wl_shell_surface, struct wl_seat *seat, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_MOVE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, seat, serial); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Start a pointer-driven resizing of the surface. - * - * This request must be used in response to a button press event. - * The server may ignore resize requests depending on the state of - * the surface (e.g. fullscreen or maximized). - */ -static inline void -wl_shell_surface_resize(struct wl_shell_surface *wl_shell_surface, struct wl_seat *seat, uint32_t serial, uint32_t edges) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_RESIZE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, seat, serial, edges); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Map the surface as a toplevel surface. - * - * A toplevel surface is not fullscreen, maximized or transient. - */ -static inline void -wl_shell_surface_set_toplevel(struct wl_shell_surface *wl_shell_surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_TOPLEVEL, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Map the surface relative to an existing surface. - * - * The x and y arguments specify the location of the upper left - * corner of the surface relative to the upper left corner of the - * parent surface, in surface-local coordinates. - * - * The flags argument controls details of the transient behaviour. - */ -static inline void -wl_shell_surface_set_transient(struct wl_shell_surface *wl_shell_surface, struct wl_surface *parent, int32_t x, int32_t y, uint32_t flags) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_TRANSIENT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, parent, x, y, flags); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Map the surface as a fullscreen surface. - * - * If an output parameter is given then the surface will be made - * fullscreen on that output. If the client does not specify the - * output then the compositor will apply its policy - usually - * choosing the output on which the surface has the biggest surface - * area. - * - * The client may specify a method to resolve a size conflict - * between the output size and the surface size - this is provided - * through the method parameter. - * - * The framerate parameter is used only when the method is set - * to "driver", to indicate the preferred framerate. A value of 0 - * indicates that the client does not care about framerate. The - * framerate is specified in mHz, that is framerate of 60000 is 60Hz. - * - * A method of "scale" or "driver" implies a scaling operation of - * the surface, either via a direct scaling operation or a change of - * the output mode. This will override any kind of output scaling, so - * that mapping a surface with a buffer size equal to the mode can - * fill the screen independent of buffer_scale. - * - * A method of "fill" means we don't scale up the buffer, however - * any output scale is applied. This means that you may run into - * an edge case where the application maps a buffer with the same - * size of the output mode but buffer_scale 1 (thus making a - * surface larger than the output). In this case it is allowed to - * downscale the results to fit the screen. - * - * The compositor must reply to this request with a configure event - * with the dimensions for the output on which the surface will - * be made fullscreen. - */ -static inline void -wl_shell_surface_set_fullscreen(struct wl_shell_surface *wl_shell_surface, uint32_t method, uint32_t framerate, struct wl_output *output) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, method, framerate, output); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Map the surface as a popup. - * - * A popup surface is a transient surface with an added pointer - * grab. - * - * An existing implicit grab will be changed to owner-events mode, - * and the popup grab will continue after the implicit grab ends - * (i.e. releasing the mouse button does not cause the popup to - * be unmapped). - * - * The popup grab continues until the window is destroyed or a - * mouse button is pressed in any other client's window. A click - * in any of the client's surfaces is reported as normal, however, - * clicks in other clients' surfaces will be discarded and trigger - * the callback. - * - * The x and y arguments specify the location of the upper left - * corner of the surface relative to the upper left corner of the - * parent surface, in surface-local coordinates. - */ -static inline void -wl_shell_surface_set_popup(struct wl_shell_surface *wl_shell_surface, struct wl_seat *seat, uint32_t serial, struct wl_surface *parent, int32_t x, int32_t y, uint32_t flags) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_POPUP, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, seat, serial, parent, x, y, flags); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Map the surface as a maximized surface. - * - * If an output parameter is given then the surface will be - * maximized on that output. If the client does not specify the - * output then the compositor will apply its policy - usually - * choosing the output on which the surface has the biggest surface - * area. - * - * The compositor will reply with a configure event telling - * the expected new surface size. The operation is completed - * on the next buffer attach to this surface. - * - * A maximized surface typically fills the entire output it is - * bound to, except for desktop elements such as panels. This is - * the main difference between a maximized shell surface and a - * fullscreen shell surface. - * - * The details depend on the compositor implementation. - */ -static inline void -wl_shell_surface_set_maximized(struct wl_shell_surface *wl_shell_surface, struct wl_output *output) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, output); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Set a short title for the surface. - * - * This string may be used to identify the surface in a task bar, - * window list, or other user interface elements provided by the - * compositor. - * - * The string must be encoded in UTF-8. - */ -static inline void -wl_shell_surface_set_title(struct wl_shell_surface *wl_shell_surface, const char *title) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_TITLE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, title); -} - -/** - * @ingroup iface_wl_shell_surface - * - * Set a class for the surface. - * - * The surface class identifies the general class of applications - * to which the surface belongs. A common convention is to use the - * file name (or the full path if it is a non-standard location) of - * the application's .desktop file as the class. - */ -static inline void -wl_shell_surface_set_class(struct wl_shell_surface *wl_shell_surface, const char *class_) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_shell_surface, - WL_SHELL_SURFACE_SET_CLASS, NULL, wl_proxy_get_version((struct wl_proxy *) wl_shell_surface), 0, class_); -} - -#ifndef WL_SURFACE_ERROR_ENUM -#define WL_SURFACE_ERROR_ENUM -/** - * @ingroup iface_wl_surface - * wl_surface error values - * - * These errors can be emitted in response to wl_surface requests. - */ -enum wl_surface_error { - /** - * buffer scale value is invalid - */ - WL_SURFACE_ERROR_INVALID_SCALE = 0, - /** - * buffer transform value is invalid - */ - WL_SURFACE_ERROR_INVALID_TRANSFORM = 1, - /** - * buffer size is invalid - */ - WL_SURFACE_ERROR_INVALID_SIZE = 2, - /** - * buffer offset is invalid - */ - WL_SURFACE_ERROR_INVALID_OFFSET = 3, - /** - * surface was destroyed before its role object - */ - WL_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT = 4, -}; -#endif /* WL_SURFACE_ERROR_ENUM */ - -/** - * @ingroup iface_wl_surface - * @struct wl_surface_listener - */ -struct wl_surface_listener { - /** - * surface enters an output - * - * This is emitted whenever a surface's creation, movement, or - * resizing results in some part of it being within the scanout - * region of an output. - * - * Note that a surface may be overlapping with zero or more - * outputs. - * @param output output entered by the surface - */ - void (*enter)(void *data, - struct wl_surface *wl_surface, - struct wl_output *output); - /** - * surface leaves an output - * - * This is emitted whenever a surface's creation, movement, or - * resizing results in it no longer having any part of it within - * the scanout region of an output. - * - * Clients should not use the number of outputs the surface is on - * for frame throttling purposes. The surface might be hidden even - * if no leave event has been sent, and the compositor might expect - * new surface content updates even if no enter event has been - * sent. The frame event should be used instead. - * @param output output left by the surface - */ - void (*leave)(void *data, - struct wl_surface *wl_surface, - struct wl_output *output); - /** - * preferred buffer scale for the surface - * - * This event indicates the preferred buffer scale for this - * surface. It is sent whenever the compositor's preference - * changes. - * - * It is intended that scaling aware clients use this event to - * scale their content and use wl_surface.set_buffer_scale to - * indicate the scale they have rendered with. This allows clients - * to supply a higher detail buffer. - * @param factor preferred scaling factor - * @since 6 - */ - void (*preferred_buffer_scale)(void *data, - struct wl_surface *wl_surface, - int32_t factor); - /** - * preferred buffer transform for the surface - * - * This event indicates the preferred buffer transform for this - * surface. It is sent whenever the compositor's preference - * changes. - * - * It is intended that transform aware clients use this event to - * apply the transform to their content and use - * wl_surface.set_buffer_transform to indicate the transform they - * have rendered with. - * @param transform preferred transform - * @since 6 - */ - void (*preferred_buffer_transform)(void *data, - struct wl_surface *wl_surface, - uint32_t transform); -}; - -/** - * @ingroup iface_wl_surface - */ -static inline int -wl_surface_add_listener(struct wl_surface *wl_surface, - const struct wl_surface_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_surface, - (void (**)(void)) listener, data); -} - -#define WL_SURFACE_DESTROY 0 -#define WL_SURFACE_ATTACH 1 -#define WL_SURFACE_DAMAGE 2 -#define WL_SURFACE_FRAME 3 -#define WL_SURFACE_SET_OPAQUE_REGION 4 -#define WL_SURFACE_SET_INPUT_REGION 5 -#define WL_SURFACE_COMMIT 6 -#define WL_SURFACE_SET_BUFFER_TRANSFORM 7 -#define WL_SURFACE_SET_BUFFER_SCALE 8 -#define WL_SURFACE_DAMAGE_BUFFER 9 -#define WL_SURFACE_OFFSET 10 - -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_ENTER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_LEAVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_PREFERRED_BUFFER_SCALE_SINCE_VERSION 6 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_PREFERRED_BUFFER_TRANSFORM_SINCE_VERSION 6 - -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_ATTACH_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_DAMAGE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_FRAME_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_SET_OPAQUE_REGION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_SET_INPUT_REGION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_COMMIT_SINCE_VERSION 1 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_SET_BUFFER_TRANSFORM_SINCE_VERSION 2 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION 3 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION 4 -/** - * @ingroup iface_wl_surface - */ -#define WL_SURFACE_OFFSET_SINCE_VERSION 5 - -/** @ingroup iface_wl_surface */ -static inline void -wl_surface_set_user_data(struct wl_surface *wl_surface, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_surface, user_data); -} - -/** @ingroup iface_wl_surface */ -static inline void * -wl_surface_get_user_data(struct wl_surface *wl_surface) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_surface); -} - -static inline uint32_t -wl_surface_get_version(struct wl_surface *wl_surface) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_surface); -} - -/** - * @ingroup iface_wl_surface - * - * Deletes the surface and invalidates its object ID. - */ -static inline void -wl_surface_destroy(struct wl_surface *wl_surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_surface - * - * Set a buffer as the content of this surface. - * - * The new size of the surface is calculated based on the buffer - * size transformed by the inverse buffer_transform and the - * inverse buffer_scale. This means that at commit time the supplied - * buffer size must be an integer multiple of the buffer_scale. If - * that's not the case, an invalid_size error is sent. - * - * The x and y arguments specify the location of the new pending - * buffer's upper left corner, relative to the current buffer's upper - * left corner, in surface-local coordinates. In other words, the - * x and y, combined with the new surface size define in which - * directions the surface's size changes. Setting anything other than 0 - * as x and y arguments is discouraged, and should instead be replaced - * with using the separate wl_surface.offset request. - * - * When the bound wl_surface version is 5 or higher, passing any - * non-zero x or y is a protocol violation, and will result in an - * 'invalid_offset' error being raised. The x and y arguments are ignored - * and do not change the pending state. To achieve equivalent semantics, - * use wl_surface.offset. - * - * Surface contents are double-buffered state, see wl_surface.commit. - * - * The initial surface contents are void; there is no content. - * wl_surface.attach assigns the given wl_buffer as the pending - * wl_buffer. wl_surface.commit makes the pending wl_buffer the new - * surface contents, and the size of the surface becomes the size - * calculated from the wl_buffer, as described above. After commit, - * there is no pending buffer until the next attach. - * - * Committing a pending wl_buffer allows the compositor to read the - * pixels in the wl_buffer. The compositor may access the pixels at - * any time after the wl_surface.commit request. When the compositor - * will not access the pixels anymore, it will send the - * wl_buffer.release event. Only after receiving wl_buffer.release, - * the client may reuse the wl_buffer. A wl_buffer that has been - * attached and then replaced by another attach instead of committed - * will not receive a release event, and is not used by the - * compositor. - * - * If a pending wl_buffer has been committed to more than one wl_surface, - * the delivery of wl_buffer.release events becomes undefined. A well - * behaved client should not rely on wl_buffer.release events in this - * case. Alternatively, a client could create multiple wl_buffer objects - * from the same backing storage or use wp_linux_buffer_release. - * - * Destroying the wl_buffer after wl_buffer.release does not change - * the surface contents. Destroying the wl_buffer before wl_buffer.release - * is allowed as long as the underlying buffer storage isn't re-used (this - * can happen e.g. on client process termination). However, if the client - * destroys the wl_buffer before receiving the wl_buffer.release event and - * mutates the underlying buffer storage, the surface contents become - * undefined immediately. - * - * If wl_surface.attach is sent with a NULL wl_buffer, the - * following wl_surface.commit will remove the surface content. - */ -static inline void -wl_surface_attach(struct wl_surface *wl_surface, struct wl_buffer *buffer, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_ATTACH, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, buffer, x, y); -} - -/** - * @ingroup iface_wl_surface - * - * This request is used to describe the regions where the pending - * buffer is different from the current surface contents, and where - * the surface therefore needs to be repainted. The compositor - * ignores the parts of the damage that fall outside of the surface. - * - * Damage is double-buffered state, see wl_surface.commit. - * - * The damage rectangle is specified in surface-local coordinates, - * where x and y specify the upper left corner of the damage rectangle. - * - * The initial value for pending damage is empty: no damage. - * wl_surface.damage adds pending damage: the new pending damage - * is the union of old pending damage and the given rectangle. - * - * wl_surface.commit assigns pending damage as the current damage, - * and clears pending damage. The server will clear the current - * damage as it repaints the surface. - * - * Note! New clients should not use this request. Instead damage can be - * posted with wl_surface.damage_buffer which uses buffer coordinates - * instead of surface coordinates. - */ -static inline void -wl_surface_damage(struct wl_surface *wl_surface, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_DAMAGE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, x, y, width, height); -} - -/** - * @ingroup iface_wl_surface - * - * Request a notification when it is a good time to start drawing a new - * frame, by creating a frame callback. This is useful for throttling - * redrawing operations, and driving animations. - * - * When a client is animating on a wl_surface, it can use the 'frame' - * request to get notified when it is a good time to draw and commit the - * next frame of animation. If the client commits an update earlier than - * that, it is likely that some updates will not make it to the display, - * and the client is wasting resources by drawing too often. - * - * The frame request will take effect on the next wl_surface.commit. - * The notification will only be posted for one frame unless - * requested again. For a wl_surface, the notifications are posted in - * the order the frame requests were committed. - * - * The server must send the notifications so that a client - * will not send excessive updates, while still allowing - * the highest possible update rate for clients that wait for the reply - * before drawing again. The server should give some time for the client - * to draw and commit after sending the frame callback events to let it - * hit the next output refresh. - * - * A server should avoid signaling the frame callbacks if the - * surface is not visible in any way, e.g. the surface is off-screen, - * or completely obscured by other opaque surfaces. - * - * The object returned by this request will be destroyed by the - * compositor after the callback is fired and as such the client must not - * attempt to use it after that point. - * - * The callback_data passed in the callback is the current time, in - * milliseconds, with an undefined base. - */ -static inline struct wl_callback * -wl_surface_frame(struct wl_surface *wl_surface) -{ - struct wl_proxy *callback; - - callback = wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_FRAME, &wl_callback_interface, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, NULL); - - return (struct wl_callback *) callback; -} - -/** - * @ingroup iface_wl_surface - * - * This request sets the region of the surface that contains - * opaque content. - * - * The opaque region is an optimization hint for the compositor - * that lets it optimize the redrawing of content behind opaque - * regions. Setting an opaque region is not required for correct - * behaviour, but marking transparent content as opaque will result - * in repaint artifacts. - * - * The opaque region is specified in surface-local coordinates. - * - * The compositor ignores the parts of the opaque region that fall - * outside of the surface. - * - * Opaque region is double-buffered state, see wl_surface.commit. - * - * wl_surface.set_opaque_region changes the pending opaque region. - * wl_surface.commit copies the pending region to the current region. - * Otherwise, the pending and current regions are never changed. - * - * The initial value for an opaque region is empty. Setting the pending - * opaque region has copy semantics, and the wl_region object can be - * destroyed immediately. A NULL wl_region causes the pending opaque - * region to be set to empty. - */ -static inline void -wl_surface_set_opaque_region(struct wl_surface *wl_surface, struct wl_region *region) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_SET_OPAQUE_REGION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, region); -} - -/** - * @ingroup iface_wl_surface - * - * This request sets the region of the surface that can receive - * pointer and touch events. - * - * Input events happening outside of this region will try the next - * surface in the server surface stack. The compositor ignores the - * parts of the input region that fall outside of the surface. - * - * The input region is specified in surface-local coordinates. - * - * Input region is double-buffered state, see wl_surface.commit. - * - * wl_surface.set_input_region changes the pending input region. - * wl_surface.commit copies the pending region to the current region. - * Otherwise the pending and current regions are never changed, - * except cursor and icon surfaces are special cases, see - * wl_pointer.set_cursor and wl_data_device.start_drag. - * - * The initial value for an input region is infinite. That means the - * whole surface will accept input. Setting the pending input region - * has copy semantics, and the wl_region object can be destroyed - * immediately. A NULL wl_region causes the input region to be set - * to infinite. - */ -static inline void -wl_surface_set_input_region(struct wl_surface *wl_surface, struct wl_region *region) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_SET_INPUT_REGION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, region); -} - -/** - * @ingroup iface_wl_surface - * - * Surface state (input, opaque, and damage regions, attached buffers, - * etc.) is double-buffered. Protocol requests modify the pending state, - * as opposed to the current state in use by the compositor. A commit - * request atomically applies all pending state, replacing the current - * state. After commit, the new pending state is as documented for each - * related request. - * - * On commit, a pending wl_buffer is applied first, and all other state - * second. This means that all coordinates in double-buffered state are - * relative to the new wl_buffer coming into use, except for - * wl_surface.attach itself. If there is no pending wl_buffer, the - * coordinates are relative to the current surface contents. - * - * All requests that need a commit to become effective are documented - * to affect double-buffered state. - * - * Other interfaces may add further double-buffered surface state. - */ -static inline void -wl_surface_commit(struct wl_surface *wl_surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0); -} - -/** - * @ingroup iface_wl_surface - * - * This request sets an optional transformation on how the compositor - * interprets the contents of the buffer attached to the surface. The - * accepted values for the transform parameter are the values for - * wl_output.transform. - * - * Buffer transform is double-buffered state, see wl_surface.commit. - * - * A newly created surface has its buffer transformation set to normal. - * - * wl_surface.set_buffer_transform changes the pending buffer - * transformation. wl_surface.commit copies the pending buffer - * transformation to the current one. Otherwise, the pending and current - * values are never changed. - * - * The purpose of this request is to allow clients to render content - * according to the output transform, thus permitting the compositor to - * use certain optimizations even if the display is rotated. Using - * hardware overlays and scanning out a client buffer for fullscreen - * surfaces are examples of such optimizations. Those optimizations are - * highly dependent on the compositor implementation, so the use of this - * request should be considered on a case-by-case basis. - * - * Note that if the transform value includes 90 or 270 degree rotation, - * the width of the buffer will become the surface height and the height - * of the buffer will become the surface width. - * - * If transform is not one of the values from the - * wl_output.transform enum the invalid_transform protocol error - * is raised. - */ -static inline void -wl_surface_set_buffer_transform(struct wl_surface *wl_surface, int32_t transform) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_SET_BUFFER_TRANSFORM, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, transform); -} - -/** - * @ingroup iface_wl_surface - * - * This request sets an optional scaling factor on how the compositor - * interprets the contents of the buffer attached to the window. - * - * Buffer scale is double-buffered state, see wl_surface.commit. - * - * A newly created surface has its buffer scale set to 1. - * - * wl_surface.set_buffer_scale changes the pending buffer scale. - * wl_surface.commit copies the pending buffer scale to the current one. - * Otherwise, the pending and current values are never changed. - * - * The purpose of this request is to allow clients to supply higher - * resolution buffer data for use on high resolution outputs. It is - * intended that you pick the same buffer scale as the scale of the - * output that the surface is displayed on. This means the compositor - * can avoid scaling when rendering the surface on that output. - * - * Note that if the scale is larger than 1, then you have to attach - * a buffer that is larger (by a factor of scale in each dimension) - * than the desired surface size. - * - * If scale is not positive the invalid_scale protocol error is - * raised. - */ -static inline void -wl_surface_set_buffer_scale(struct wl_surface *wl_surface, int32_t scale) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_SET_BUFFER_SCALE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, scale); -} - -/** - * @ingroup iface_wl_surface - * - * This request is used to describe the regions where the pending - * buffer is different from the current surface contents, and where - * the surface therefore needs to be repainted. The compositor - * ignores the parts of the damage that fall outside of the surface. - * - * Damage is double-buffered state, see wl_surface.commit. - * - * The damage rectangle is specified in buffer coordinates, - * where x and y specify the upper left corner of the damage rectangle. - * - * The initial value for pending damage is empty: no damage. - * wl_surface.damage_buffer adds pending damage: the new pending - * damage is the union of old pending damage and the given rectangle. - * - * wl_surface.commit assigns pending damage as the current damage, - * and clears pending damage. The server will clear the current - * damage as it repaints the surface. - * - * This request differs from wl_surface.damage in only one way - it - * takes damage in buffer coordinates instead of surface-local - * coordinates. While this generally is more intuitive than surface - * coordinates, it is especially desirable when using wp_viewport - * or when a drawing library (like EGL) is unaware of buffer scale - * and buffer transform. - * - * Note: Because buffer transformation changes and damage requests may - * be interleaved in the protocol stream, it is impossible to determine - * the actual mapping between surface and buffer damage until - * wl_surface.commit time. Therefore, compositors wishing to take both - * kinds of damage into account will have to accumulate damage from the - * two requests separately and only transform from one to the other - * after receiving the wl_surface.commit. - */ -static inline void -wl_surface_damage_buffer(struct wl_surface *wl_surface, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_DAMAGE_BUFFER, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, x, y, width, height); -} - -/** - * @ingroup iface_wl_surface - * - * The x and y arguments specify the location of the new pending - * buffer's upper left corner, relative to the current buffer's upper - * left corner, in surface-local coordinates. In other words, the - * x and y, combined with the new surface size define in which - * directions the surface's size changes. - * - * Surface location offset is double-buffered state, see - * wl_surface.commit. - * - * This request is semantically equivalent to and the replaces the x and y - * arguments in the wl_surface.attach request in wl_surface versions prior - * to 5. See wl_surface.attach for details. - */ -static inline void -wl_surface_offset(struct wl_surface *wl_surface, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_surface, - WL_SURFACE_OFFSET, NULL, wl_proxy_get_version((struct wl_proxy *) wl_surface), 0, x, y); -} - -#ifndef WL_SEAT_CAPABILITY_ENUM -#define WL_SEAT_CAPABILITY_ENUM -/** - * @ingroup iface_wl_seat - * seat capability bitmask - * - * This is a bitmask of capabilities this seat has; if a member is - * set, then it is present on the seat. - */ -enum wl_seat_capability { - /** - * the seat has pointer devices - */ - WL_SEAT_CAPABILITY_POINTER = 1, - /** - * the seat has one or more keyboards - */ - WL_SEAT_CAPABILITY_KEYBOARD = 2, - /** - * the seat has touch devices - */ - WL_SEAT_CAPABILITY_TOUCH = 4, -}; -#endif /* WL_SEAT_CAPABILITY_ENUM */ - -#ifndef WL_SEAT_ERROR_ENUM -#define WL_SEAT_ERROR_ENUM -/** - * @ingroup iface_wl_seat - * wl_seat error values - * - * These errors can be emitted in response to wl_seat requests. - */ -enum wl_seat_error { - /** - * get_pointer, get_keyboard or get_touch called on seat without the matching capability - */ - WL_SEAT_ERROR_MISSING_CAPABILITY = 0, -}; -#endif /* WL_SEAT_ERROR_ENUM */ - -/** - * @ingroup iface_wl_seat - * @struct wl_seat_listener - */ -struct wl_seat_listener { - /** - * seat capabilities changed - * - * This is emitted whenever a seat gains or loses the pointer, - * keyboard or touch capabilities. The argument is a capability - * enum containing the complete set of capabilities this seat has. - * - * When the pointer capability is added, a client may create a - * wl_pointer object using the wl_seat.get_pointer request. This - * object will receive pointer events until the capability is - * removed in the future. - * - * When the pointer capability is removed, a client should destroy - * the wl_pointer objects associated with the seat where the - * capability was removed, using the wl_pointer.release request. No - * further pointer events will be received on these objects. - * - * In some compositors, if a seat regains the pointer capability - * and a client has a previously obtained wl_pointer object of - * version 4 or less, that object may start sending pointer events - * again. This behavior is considered a misinterpretation of the - * intended behavior and must not be relied upon by the client. - * wl_pointer objects of version 5 or later must not send events if - * created before the most recent event notifying the client of an - * added pointer capability. - * - * The above behavior also applies to wl_keyboard and wl_touch with - * the keyboard and touch capabilities, respectively. - * @param capabilities capabilities of the seat - */ - void (*capabilities)(void *data, - struct wl_seat *wl_seat, - uint32_t capabilities); - /** - * unique identifier for this seat - * - * In a multi-seat configuration the seat name can be used by - * clients to help identify which physical devices the seat - * represents. - * - * The seat name is a UTF-8 string with no convention defined for - * its contents. Each name is unique among all wl_seat globals. The - * name is only guaranteed to be unique for the current compositor - * instance. - * - * The same seat names are used for all clients. Thus, the name can - * be shared across processes to refer to a specific wl_seat - * global. - * - * The name event is sent after binding to the seat global. This - * event is only sent once per seat object, and the name does not - * change over the lifetime of the wl_seat global. - * - * Compositors may re-use the same seat name if the wl_seat global - * is destroyed and re-created later. - * @param name seat identifier - * @since 2 - */ - void (*name)(void *data, - struct wl_seat *wl_seat, - const char *name); -}; - -/** - * @ingroup iface_wl_seat - */ -static inline int -wl_seat_add_listener(struct wl_seat *wl_seat, - const struct wl_seat_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_seat, - (void (**)(void)) listener, data); -} - -#define WL_SEAT_GET_POINTER 0 -#define WL_SEAT_GET_KEYBOARD 1 -#define WL_SEAT_GET_TOUCH 2 -#define WL_SEAT_RELEASE 3 - -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_CAPABILITIES_SINCE_VERSION 1 -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_NAME_SINCE_VERSION 2 - -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_GET_POINTER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_GET_KEYBOARD_SINCE_VERSION 1 -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_GET_TOUCH_SINCE_VERSION 1 -/** - * @ingroup iface_wl_seat - */ -#define WL_SEAT_RELEASE_SINCE_VERSION 5 - -/** @ingroup iface_wl_seat */ -static inline void -wl_seat_set_user_data(struct wl_seat *wl_seat, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_seat, user_data); -} - -/** @ingroup iface_wl_seat */ -static inline void * -wl_seat_get_user_data(struct wl_seat *wl_seat) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_seat); -} - -static inline uint32_t -wl_seat_get_version(struct wl_seat *wl_seat) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_seat); -} - -/** @ingroup iface_wl_seat */ -static inline void -wl_seat_destroy(struct wl_seat *wl_seat) -{ - wl_proxy_destroy((struct wl_proxy *) wl_seat); -} - -/** - * @ingroup iface_wl_seat - * - * The ID provided will be initialized to the wl_pointer interface - * for this seat. - * - * This request only takes effect if the seat has the pointer - * capability, or has had the pointer capability in the past. - * It is a protocol violation to issue this request on a seat that has - * never had the pointer capability. The missing_capability error will - * be sent in this case. - */ -static inline struct wl_pointer * -wl_seat_get_pointer(struct wl_seat *wl_seat) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, - WL_SEAT_GET_POINTER, &wl_pointer_interface, wl_proxy_get_version((struct wl_proxy *) wl_seat), 0, NULL); - - return (struct wl_pointer *) id; -} - -/** - * @ingroup iface_wl_seat - * - * The ID provided will be initialized to the wl_keyboard interface - * for this seat. - * - * This request only takes effect if the seat has the keyboard - * capability, or has had the keyboard capability in the past. - * It is a protocol violation to issue this request on a seat that has - * never had the keyboard capability. The missing_capability error will - * be sent in this case. - */ -static inline struct wl_keyboard * -wl_seat_get_keyboard(struct wl_seat *wl_seat) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, - WL_SEAT_GET_KEYBOARD, &wl_keyboard_interface, wl_proxy_get_version((struct wl_proxy *) wl_seat), 0, NULL); - - return (struct wl_keyboard *) id; -} - -/** - * @ingroup iface_wl_seat - * - * The ID provided will be initialized to the wl_touch interface - * for this seat. - * - * This request only takes effect if the seat has the touch - * capability, or has had the touch capability in the past. - * It is a protocol violation to issue this request on a seat that has - * never had the touch capability. The missing_capability error will - * be sent in this case. - */ -static inline struct wl_touch * -wl_seat_get_touch(struct wl_seat *wl_seat) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, - WL_SEAT_GET_TOUCH, &wl_touch_interface, wl_proxy_get_version((struct wl_proxy *) wl_seat), 0, NULL); - - return (struct wl_touch *) id; -} - -/** - * @ingroup iface_wl_seat - * - * Using this request a client can tell the server that it is not going to - * use the seat object anymore. - */ -static inline void -wl_seat_release(struct wl_seat *wl_seat) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_seat, - WL_SEAT_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_seat), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef WL_POINTER_ERROR_ENUM -#define WL_POINTER_ERROR_ENUM -enum wl_pointer_error { - /** - * given wl_surface has another role - */ - WL_POINTER_ERROR_ROLE = 0, -}; -#endif /* WL_POINTER_ERROR_ENUM */ - -#ifndef WL_POINTER_BUTTON_STATE_ENUM -#define WL_POINTER_BUTTON_STATE_ENUM -/** - * @ingroup iface_wl_pointer - * physical button state - * - * Describes the physical state of a button that produced the button - * event. - */ -enum wl_pointer_button_state { - /** - * the button is not pressed - */ - WL_POINTER_BUTTON_STATE_RELEASED = 0, - /** - * the button is pressed - */ - WL_POINTER_BUTTON_STATE_PRESSED = 1, -}; -#endif /* WL_POINTER_BUTTON_STATE_ENUM */ - -#ifndef WL_POINTER_AXIS_ENUM -#define WL_POINTER_AXIS_ENUM -/** - * @ingroup iface_wl_pointer - * axis types - * - * Describes the axis types of scroll events. - */ -enum wl_pointer_axis { - /** - * vertical axis - */ - WL_POINTER_AXIS_VERTICAL_SCROLL = 0, - /** - * horizontal axis - */ - WL_POINTER_AXIS_HORIZONTAL_SCROLL = 1, -}; -#endif /* WL_POINTER_AXIS_ENUM */ - -#ifndef WL_POINTER_AXIS_SOURCE_ENUM -#define WL_POINTER_AXIS_SOURCE_ENUM -/** - * @ingroup iface_wl_pointer - * axis source types - * - * Describes the source types for axis events. This indicates to the - * client how an axis event was physically generated; a client may - * adjust the user interface accordingly. For example, scroll events - * from a "finger" source may be in a smooth coordinate space with - * kinetic scrolling whereas a "wheel" source may be in discrete steps - * of a number of lines. - * - * The "continuous" axis source is a device generating events in a - * continuous coordinate space, but using something other than a - * finger. One example for this source is button-based scrolling where - * the vertical motion of a device is converted to scroll events while - * a button is held down. - * - * The "wheel tilt" axis source indicates that the actual device is a - * wheel but the scroll event is not caused by a rotation but a - * (usually sideways) tilt of the wheel. - */ -enum wl_pointer_axis_source { - /** - * a physical wheel rotation - */ - WL_POINTER_AXIS_SOURCE_WHEEL = 0, - /** - * finger on a touch surface - */ - WL_POINTER_AXIS_SOURCE_FINGER = 1, - /** - * continuous coordinate space - */ - WL_POINTER_AXIS_SOURCE_CONTINUOUS = 2, - /** - * a physical wheel tilt - * @since 6 - */ - WL_POINTER_AXIS_SOURCE_WHEEL_TILT = 3, -}; -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_SOURCE_WHEEL_TILT_SINCE_VERSION 6 -#endif /* WL_POINTER_AXIS_SOURCE_ENUM */ - -#ifndef WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM -#define WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM -/** - * @ingroup iface_wl_pointer - * axis relative direction - * - * This specifies the direction of the physical motion that caused a - * wl_pointer.axis event, relative to the wl_pointer.axis direction. - */ -enum wl_pointer_axis_relative_direction { - /** - * physical motion matches axis direction - */ - WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL = 0, - /** - * physical motion is the inverse of the axis direction - */ - WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED = 1, -}; -#endif /* WL_POINTER_AXIS_RELATIVE_DIRECTION_ENUM */ - -/** - * @ingroup iface_wl_pointer - * @struct wl_pointer_listener - */ -struct wl_pointer_listener { - /** - * enter event - * - * Notification that this seat's pointer is focused on a certain - * surface. - * - * When a seat's focus enters a surface, the pointer image is - * undefined and a client should respond to this event by setting - * an appropriate pointer image with the set_cursor request. - * @param serial serial number of the enter event - * @param surface surface entered by the pointer - * @param surface_x surface-local x coordinate - * @param surface_y surface-local y coordinate - */ - void (*enter)(void *data, - struct wl_pointer *wl_pointer, - uint32_t serial, - struct wl_surface *surface, - wl_fixed_t surface_x, - wl_fixed_t surface_y); - /** - * leave event - * - * Notification that this seat's pointer is no longer focused on - * a certain surface. - * - * The leave notification is sent before the enter notification for - * the new focus. - * @param serial serial number of the leave event - * @param surface surface left by the pointer - */ - void (*leave)(void *data, - struct wl_pointer *wl_pointer, - uint32_t serial, - struct wl_surface *surface); - /** - * pointer motion event - * - * Notification of pointer location change. The arguments - * surface_x and surface_y are the location relative to the focused - * surface. - * @param time timestamp with millisecond granularity - * @param surface_x surface-local x coordinate - * @param surface_y surface-local y coordinate - */ - void (*motion)(void *data, - struct wl_pointer *wl_pointer, - uint32_t time, - wl_fixed_t surface_x, - wl_fixed_t surface_y); - /** - * pointer button event - * - * Mouse button click and release notifications. - * - * The location of the click is given by the last motion or enter - * event. The time argument is a timestamp with millisecond - * granularity, with an undefined base. - * - * The button is a button code as defined in the Linux kernel's - * linux/input-event-codes.h header file, e.g. BTN_LEFT. - * - * Any 16-bit button code value is reserved for future additions to - * the kernel's event code list. All other button codes above - * 0xFFFF are currently undefined but may be used in future - * versions of this protocol. - * @param serial serial number of the button event - * @param time timestamp with millisecond granularity - * @param button button that produced the event - * @param state physical state of the button - */ - void (*button)(void *data, - struct wl_pointer *wl_pointer, - uint32_t serial, - uint32_t time, - uint32_t button, - uint32_t state); - /** - * axis event - * - * Scroll and other axis notifications. - * - * For scroll events (vertical and horizontal scroll axes), the - * value parameter is the length of a vector along the specified - * axis in a coordinate space identical to those of motion events, - * representing a relative movement along the specified axis. - * - * For devices that support movements non-parallel to axes multiple - * axis events will be emitted. - * - * When applicable, for example for touch pads, the server can - * choose to emit scroll events where the motion vector is - * equivalent to a motion event vector. - * - * When applicable, a client can transform its content relative to - * the scroll distance. - * @param time timestamp with millisecond granularity - * @param axis axis type - * @param value length of vector in surface-local coordinate space - */ - void (*axis)(void *data, - struct wl_pointer *wl_pointer, - uint32_t time, - uint32_t axis, - wl_fixed_t value); - /** - * end of a pointer event sequence - * - * Indicates the end of a set of events that logically belong - * together. A client is expected to accumulate the data in all - * events within the frame before proceeding. - * - * All wl_pointer events before a wl_pointer.frame event belong - * logically together. For example, in a diagonal scroll motion the - * compositor will send an optional wl_pointer.axis_source event, - * two wl_pointer.axis events (horizontal and vertical) and finally - * a wl_pointer.frame event. The client may use this information to - * calculate a diagonal vector for scrolling. - * - * When multiple wl_pointer.axis events occur within the same - * frame, the motion vector is the combined motion of all events. - * When a wl_pointer.axis and a wl_pointer.axis_stop event occur - * within the same frame, this indicates that axis movement in one - * axis has stopped but continues in the other axis. When multiple - * wl_pointer.axis_stop events occur within the same frame, this - * indicates that these axes stopped in the same instance. - * - * A wl_pointer.frame event is sent for every logical event group, - * even if the group only contains a single wl_pointer event. - * Specifically, a client may get a sequence: motion, frame, - * button, frame, axis, frame, axis_stop, frame. - * - * The wl_pointer.enter and wl_pointer.leave events are logical - * events generated by the compositor and not the hardware. These - * events are also grouped by a wl_pointer.frame. When a pointer - * moves from one surface to another, a compositor should group the - * wl_pointer.leave event within the same wl_pointer.frame. - * However, a client must not rely on wl_pointer.leave and - * wl_pointer.enter being in the same wl_pointer.frame. - * Compositor-specific policies may require the wl_pointer.leave - * and wl_pointer.enter event being split across multiple - * wl_pointer.frame groups. - * @since 5 - */ - void (*frame)(void *data, - struct wl_pointer *wl_pointer); - /** - * axis source event - * - * Source information for scroll and other axes. - * - * This event does not occur on its own. It is sent before a - * wl_pointer.frame event and carries the source information for - * all events within that frame. - * - * The source specifies how this event was generated. If the source - * is wl_pointer.axis_source.finger, a wl_pointer.axis_stop event - * will be sent when the user lifts the finger off the device. - * - * If the source is wl_pointer.axis_source.wheel, - * wl_pointer.axis_source.wheel_tilt or - * wl_pointer.axis_source.continuous, a wl_pointer.axis_stop event - * may or may not be sent. Whether a compositor sends an axis_stop - * event for these sources is hardware-specific and - * implementation-dependent; clients must not rely on receiving an - * axis_stop event for these scroll sources and should treat scroll - * sequences from these scroll sources as unterminated by default. - * - * This event is optional. If the source is unknown for a - * particular axis event sequence, no event is sent. Only one - * wl_pointer.axis_source event is permitted per frame. - * - * The order of wl_pointer.axis_discrete and wl_pointer.axis_source - * is not guaranteed. - * @param axis_source source of the axis event - * @since 5 - */ - void (*axis_source)(void *data, - struct wl_pointer *wl_pointer, - uint32_t axis_source); - /** - * axis stop event - * - * Stop notification for scroll and other axes. - * - * For some wl_pointer.axis_source types, a wl_pointer.axis_stop - * event is sent to notify a client that the axis sequence has - * terminated. This enables the client to implement kinetic - * scrolling. See the wl_pointer.axis_source documentation for - * information on when this event may be generated. - * - * Any wl_pointer.axis events with the same axis_source after this - * event should be considered as the start of a new axis motion. - * - * The timestamp is to be interpreted identical to the timestamp in - * the wl_pointer.axis event. The timestamp value may be the same - * as a preceding wl_pointer.axis event. - * @param time timestamp with millisecond granularity - * @param axis the axis stopped with this event - * @since 5 - */ - void (*axis_stop)(void *data, - struct wl_pointer *wl_pointer, - uint32_t time, - uint32_t axis); - /** - * axis click event - * - * Discrete step information for scroll and other axes. - * - * This event carries the axis value of the wl_pointer.axis event - * in discrete steps (e.g. mouse wheel clicks). - * - * This event is deprecated with wl_pointer version 8 - this event - * is not sent to clients supporting version 8 or later. - * - * This event does not occur on its own, it is coupled with a - * wl_pointer.axis event that represents this axis value on a - * continuous scale. The protocol guarantees that each - * axis_discrete event is always followed by exactly one axis event - * with the same axis number within the same wl_pointer.frame. Note - * that the protocol allows for other events to occur between the - * axis_discrete and its coupled axis event, including other - * axis_discrete or axis events. A wl_pointer.frame must not - * contain more than one axis_discrete event per axis type. - * - * This event is optional; continuous scrolling devices like - * two-finger scrolling on touchpads do not have discrete steps and - * do not generate this event. - * - * The discrete value carries the directional information. e.g. a - * value of -2 is two steps towards the negative direction of this - * axis. - * - * The axis number is identical to the axis number in the - * associated axis event. - * - * The order of wl_pointer.axis_discrete and wl_pointer.axis_source - * is not guaranteed. - * @param axis axis type - * @param discrete number of steps - * @since 5 - */ - void (*axis_discrete)(void *data, - struct wl_pointer *wl_pointer, - uint32_t axis, - int32_t discrete); - /** - * axis high-resolution scroll event - * - * Discrete high-resolution scroll information. - * - * This event carries high-resolution wheel scroll information, - * with each multiple of 120 representing one logical scroll step - * (a wheel detent). For example, an axis_value120 of 30 is one - * quarter of a logical scroll step in the positive direction, a - * value120 of -240 are two logical scroll steps in the negative - * direction within the same hardware event. Clients that rely on - * discrete scrolling should accumulate the value120 to multiples - * of 120 before processing the event. - * - * The value120 must not be zero. - * - * This event replaces the wl_pointer.axis_discrete event in - * clients supporting wl_pointer version 8 or later. - * - * Where a wl_pointer.axis_source event occurs in the same - * wl_pointer.frame, the axis source applies to this event. - * - * The order of wl_pointer.axis_value120 and wl_pointer.axis_source - * is not guaranteed. - * @param axis axis type - * @param value120 scroll distance as fraction of 120 - * @since 8 - */ - void (*axis_value120)(void *data, - struct wl_pointer *wl_pointer, - uint32_t axis, - int32_t value120); - /** - * axis relative physical direction event - * - * Relative directional information of the entity causing the - * axis motion. - * - * For a wl_pointer.axis event, the - * wl_pointer.axis_relative_direction event specifies the movement - * direction of the entity causing the wl_pointer.axis event. For - * example: - if a user's fingers on a touchpad move down and this - * causes a wl_pointer.axis vertical_scroll down event, the - * physical direction is 'identical' - if a user's fingers on a - * touchpad move down and this causes a wl_pointer.axis - * vertical_scroll up scroll up event ('natural scrolling'), the - * physical direction is 'inverted'. - * - * A client may use this information to adjust scroll motion of - * components. Specifically, enabling natural scrolling causes the - * content to change direction compared to traditional scrolling. - * Some widgets like volume control sliders should usually match - * the physical direction regardless of whether natural scrolling - * is active. This event enables clients to match the scroll - * direction of a widget to the physical direction. - * - * This event does not occur on its own, it is coupled with a - * wl_pointer.axis event that represents this axis value. The - * protocol guarantees that each axis_relative_direction event is - * always followed by exactly one axis event with the same axis - * number within the same wl_pointer.frame. Note that the protocol - * allows for other events to occur between the - * axis_relative_direction and its coupled axis event. - * - * The axis number is identical to the axis number in the - * associated axis event. - * - * The order of wl_pointer.axis_relative_direction, - * wl_pointer.axis_discrete and wl_pointer.axis_source is not - * guaranteed. - * @param axis axis type - * @param direction physical direction relative to axis motion - * @since 9 - */ - void (*axis_relative_direction)(void *data, - struct wl_pointer *wl_pointer, - uint32_t axis, - uint32_t direction); -}; - -/** - * @ingroup iface_wl_pointer - */ -static inline int -wl_pointer_add_listener(struct wl_pointer *wl_pointer, - const struct wl_pointer_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_pointer, - (void (**)(void)) listener, data); -} - -#define WL_POINTER_SET_CURSOR 0 -#define WL_POINTER_RELEASE 1 - -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_ENTER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_LEAVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_MOTION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_BUTTON_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_FRAME_SINCE_VERSION 5 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_SOURCE_SINCE_VERSION 5 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_STOP_SINCE_VERSION 5 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_DISCRETE_SINCE_VERSION 5 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_VALUE120_SINCE_VERSION 8 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_AXIS_RELATIVE_DIRECTION_SINCE_VERSION 9 - -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_SET_CURSOR_SINCE_VERSION 1 -/** - * @ingroup iface_wl_pointer - */ -#define WL_POINTER_RELEASE_SINCE_VERSION 3 - -/** @ingroup iface_wl_pointer */ -static inline void -wl_pointer_set_user_data(struct wl_pointer *wl_pointer, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_pointer, user_data); -} - -/** @ingroup iface_wl_pointer */ -static inline void * -wl_pointer_get_user_data(struct wl_pointer *wl_pointer) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_pointer); -} - -static inline uint32_t -wl_pointer_get_version(struct wl_pointer *wl_pointer) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_pointer); -} - -/** @ingroup iface_wl_pointer */ -static inline void -wl_pointer_destroy(struct wl_pointer *wl_pointer) -{ - wl_proxy_destroy((struct wl_proxy *) wl_pointer); -} - -/** - * @ingroup iface_wl_pointer - * - * Set the pointer surface, i.e., the surface that contains the - * pointer image (cursor). This request gives the surface the role - * of a cursor. If the surface already has another role, it raises - * a protocol error. - * - * The cursor actually changes only if the pointer - * focus for this device is one of the requesting client's surfaces - * or the surface parameter is the current pointer surface. If - * there was a previous surface set with this request it is - * replaced. If surface is NULL, the pointer image is hidden. - * - * The parameters hotspot_x and hotspot_y define the position of - * the pointer surface relative to the pointer location. Its - * top-left corner is always at (x, y) - (hotspot_x, hotspot_y), - * where (x, y) are the coordinates of the pointer location, in - * surface-local coordinates. - * - * On surface.attach requests to the pointer surface, hotspot_x - * and hotspot_y are decremented by the x and y parameters - * passed to the request. Attach must be confirmed by - * wl_surface.commit as usual. - * - * The hotspot can also be updated by passing the currently set - * pointer surface to this request with new values for hotspot_x - * and hotspot_y. - * - * The input region is ignored for wl_surfaces with the role of - * a cursor. When the use as a cursor ends, the wl_surface is - * unmapped. - * - * The serial parameter must match the latest wl_pointer.enter - * serial number sent to the client. Otherwise the request will be - * ignored. - */ -static inline void -wl_pointer_set_cursor(struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, int32_t hotspot_x, int32_t hotspot_y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_pointer, - WL_POINTER_SET_CURSOR, NULL, wl_proxy_get_version((struct wl_proxy *) wl_pointer), 0, serial, surface, hotspot_x, hotspot_y); -} - -/** - * @ingroup iface_wl_pointer - * - * Using this request a client can tell the server that it is not going to - * use the pointer object anymore. - * - * This request destroys the pointer proxy object, so clients must not call - * wl_pointer_destroy() after using this request. - */ -static inline void -wl_pointer_release(struct wl_pointer *wl_pointer) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_pointer, - WL_POINTER_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_pointer), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef WL_KEYBOARD_KEYMAP_FORMAT_ENUM -#define WL_KEYBOARD_KEYMAP_FORMAT_ENUM -/** - * @ingroup iface_wl_keyboard - * keyboard mapping format - * - * This specifies the format of the keymap provided to the - * client with the wl_keyboard.keymap event. - */ -enum wl_keyboard_keymap_format { - /** - * no keymap; client must understand how to interpret the raw keycode - */ - WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP = 0, - /** - * libxkbcommon compatible, null-terminated string; to determine the xkb keycode, clients must add 8 to the key event keycode - */ - WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 = 1, -}; -#endif /* WL_KEYBOARD_KEYMAP_FORMAT_ENUM */ - -#ifndef WL_KEYBOARD_KEY_STATE_ENUM -#define WL_KEYBOARD_KEY_STATE_ENUM -/** - * @ingroup iface_wl_keyboard - * physical key state - * - * Describes the physical state of a key that produced the key event. - */ -enum wl_keyboard_key_state { - /** - * key is not pressed - */ - WL_KEYBOARD_KEY_STATE_RELEASED = 0, - /** - * key is pressed - */ - WL_KEYBOARD_KEY_STATE_PRESSED = 1, -}; -#endif /* WL_KEYBOARD_KEY_STATE_ENUM */ - -/** - * @ingroup iface_wl_keyboard - * @struct wl_keyboard_listener - */ -struct wl_keyboard_listener { - /** - * keyboard mapping - * - * This event provides a file descriptor to the client which can - * be memory-mapped in read-only mode to provide a keyboard mapping - * description. - * - * From version 7 onwards, the fd must be mapped with MAP_PRIVATE - * by the recipient, as MAP_SHARED may fail. - * @param format keymap format - * @param fd keymap file descriptor - * @param size keymap size, in bytes - */ - void (*keymap)(void *data, - struct wl_keyboard *wl_keyboard, - uint32_t format, - int32_t fd, - uint32_t size); - /** - * enter event - * - * Notification that this seat's keyboard focus is on a certain - * surface. - * - * The compositor must send the wl_keyboard.modifiers event after - * this event. - * @param serial serial number of the enter event - * @param surface surface gaining keyboard focus - * @param keys the currently pressed keys - */ - void (*enter)(void *data, - struct wl_keyboard *wl_keyboard, - uint32_t serial, - struct wl_surface *surface, - struct wl_array *keys); - /** - * leave event - * - * Notification that this seat's keyboard focus is no longer on a - * certain surface. - * - * The leave notification is sent before the enter notification for - * the new focus. - * - * After this event client must assume that all keys, including - * modifiers, are lifted and also it must stop key repeating if - * there's some going on. - * @param serial serial number of the leave event - * @param surface surface that lost keyboard focus - */ - void (*leave)(void *data, - struct wl_keyboard *wl_keyboard, - uint32_t serial, - struct wl_surface *surface); - /** - * key event - * - * A key was pressed or released. The time argument is a - * timestamp with millisecond granularity, with an undefined base. - * - * The key is a platform-specific key code that can be interpreted - * by feeding it to the keyboard mapping (see the keymap event). - * - * If this event produces a change in modifiers, then the resulting - * wl_keyboard.modifiers event must be sent after this event. - * @param serial serial number of the key event - * @param time timestamp with millisecond granularity - * @param key key that produced the event - * @param state physical state of the key - */ - void (*key)(void *data, - struct wl_keyboard *wl_keyboard, - uint32_t serial, - uint32_t time, - uint32_t key, - uint32_t state); - /** - * modifier and group state - * - * Notifies clients that the modifier and/or group state has - * changed, and it should update its local state. - * @param serial serial number of the modifiers event - * @param mods_depressed depressed modifiers - * @param mods_latched latched modifiers - * @param mods_locked locked modifiers - * @param group keyboard layout - */ - void (*modifiers)(void *data, - struct wl_keyboard *wl_keyboard, - uint32_t serial, - uint32_t mods_depressed, - uint32_t mods_latched, - uint32_t mods_locked, - uint32_t group); - /** - * repeat rate and delay - * - * Informs the client about the keyboard's repeat rate and delay. - * - * This event is sent as soon as the wl_keyboard object has been - * created, and is guaranteed to be received by the client before - * any key press event. - * - * Negative values for either rate or delay are illegal. A rate of - * zero will disable any repeating (regardless of the value of - * delay). - * - * This event can be sent later on as well with a new value if - * necessary, so clients should continue listening for the event - * past the creation of wl_keyboard. - * @param rate the rate of repeating keys in characters per second - * @param delay delay in milliseconds since key down until repeating starts - * @since 4 - */ - void (*repeat_info)(void *data, - struct wl_keyboard *wl_keyboard, - int32_t rate, - int32_t delay); -}; - -/** - * @ingroup iface_wl_keyboard - */ -static inline int -wl_keyboard_add_listener(struct wl_keyboard *wl_keyboard, - const struct wl_keyboard_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_keyboard, - (void (**)(void)) listener, data); -} - -#define WL_KEYBOARD_RELEASE 0 - -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_KEYMAP_SINCE_VERSION 1 -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_ENTER_SINCE_VERSION 1 -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_LEAVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_KEY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_MODIFIERS_SINCE_VERSION 1 -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION 4 - -/** - * @ingroup iface_wl_keyboard - */ -#define WL_KEYBOARD_RELEASE_SINCE_VERSION 3 - -/** @ingroup iface_wl_keyboard */ -static inline void -wl_keyboard_set_user_data(struct wl_keyboard *wl_keyboard, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_keyboard, user_data); -} - -/** @ingroup iface_wl_keyboard */ -static inline void * -wl_keyboard_get_user_data(struct wl_keyboard *wl_keyboard) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_keyboard); -} - -static inline uint32_t -wl_keyboard_get_version(struct wl_keyboard *wl_keyboard) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_keyboard); -} - -/** @ingroup iface_wl_keyboard */ -static inline void -wl_keyboard_destroy(struct wl_keyboard *wl_keyboard) -{ - wl_proxy_destroy((struct wl_proxy *) wl_keyboard); -} - -/** - * @ingroup iface_wl_keyboard - */ -static inline void -wl_keyboard_release(struct wl_keyboard *wl_keyboard) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_keyboard, - WL_KEYBOARD_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_keyboard), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_touch - * @struct wl_touch_listener - */ -struct wl_touch_listener { - /** - * touch down event and beginning of a touch sequence - * - * A new touch point has appeared on the surface. This touch - * point is assigned a unique ID. Future events from this touch - * point reference this ID. The ID ceases to be valid after a touch - * up event and may be reused in the future. - * @param serial serial number of the touch down event - * @param time timestamp with millisecond granularity - * @param surface surface touched - * @param id the unique ID of this touch point - * @param x surface-local x coordinate - * @param y surface-local y coordinate - */ - void (*down)(void *data, - struct wl_touch *wl_touch, - uint32_t serial, - uint32_t time, - struct wl_surface *surface, - int32_t id, - wl_fixed_t x, - wl_fixed_t y); - /** - * end of a touch event sequence - * - * The touch point has disappeared. No further events will be - * sent for this touch point and the touch point's ID is released - * and may be reused in a future touch down event. - * @param serial serial number of the touch up event - * @param time timestamp with millisecond granularity - * @param id the unique ID of this touch point - */ - void (*up)(void *data, - struct wl_touch *wl_touch, - uint32_t serial, - uint32_t time, - int32_t id); - /** - * update of touch point coordinates - * - * A touch point has changed coordinates. - * @param time timestamp with millisecond granularity - * @param id the unique ID of this touch point - * @param x surface-local x coordinate - * @param y surface-local y coordinate - */ - void (*motion)(void *data, - struct wl_touch *wl_touch, - uint32_t time, - int32_t id, - wl_fixed_t x, - wl_fixed_t y); - /** - * end of touch frame event - * - * Indicates the end of a set of events that logically belong - * together. A client is expected to accumulate the data in all - * events within the frame before proceeding. - * - * A wl_touch.frame terminates at least one event but otherwise no - * guarantee is provided about the set of events within a frame. A - * client must assume that any state not updated in a frame is - * unchanged from the previously known state. - */ - void (*frame)(void *data, - struct wl_touch *wl_touch); - /** - * touch session cancelled - * - * Sent if the compositor decides the touch stream is a global - * gesture. No further events are sent to the clients from that - * particular gesture. Touch cancellation applies to all touch - * points currently active on this client's surface. The client is - * responsible for finalizing the touch points, future touch points - * on this surface may reuse the touch point ID. - */ - void (*cancel)(void *data, - struct wl_touch *wl_touch); - /** - * update shape of touch point - * - * Sent when a touchpoint has changed its shape. - * - * This event does not occur on its own. It is sent before a - * wl_touch.frame event and carries the new shape information for - * any previously reported, or new touch points of that frame. - * - * Other events describing the touch point such as wl_touch.down, - * wl_touch.motion or wl_touch.orientation may be sent within the - * same wl_touch.frame. A client should treat these events as a - * single logical touch point update. The order of wl_touch.shape, - * wl_touch.orientation and wl_touch.motion is not guaranteed. A - * wl_touch.down event is guaranteed to occur before the first - * wl_touch.shape event for this touch ID but both events may occur - * within the same wl_touch.frame. - * - * A touchpoint shape is approximated by an ellipse through the - * major and minor axis length. The major axis length describes the - * longer diameter of the ellipse, while the minor axis length - * describes the shorter diameter. Major and minor are orthogonal - * and both are specified in surface-local coordinates. The center - * of the ellipse is always at the touchpoint location as reported - * by wl_touch.down or wl_touch.move. - * - * This event is only sent by the compositor if the touch device - * supports shape reports. The client has to make reasonable - * assumptions about the shape if it did not receive this event. - * @param id the unique ID of this touch point - * @param major length of the major axis in surface-local coordinates - * @param minor length of the minor axis in surface-local coordinates - * @since 6 - */ - void (*shape)(void *data, - struct wl_touch *wl_touch, - int32_t id, - wl_fixed_t major, - wl_fixed_t minor); - /** - * update orientation of touch point - * - * Sent when a touchpoint has changed its orientation. - * - * This event does not occur on its own. It is sent before a - * wl_touch.frame event and carries the new shape information for - * any previously reported, or new touch points of that frame. - * - * Other events describing the touch point such as wl_touch.down, - * wl_touch.motion or wl_touch.shape may be sent within the same - * wl_touch.frame. A client should treat these events as a single - * logical touch point update. The order of wl_touch.shape, - * wl_touch.orientation and wl_touch.motion is not guaranteed. A - * wl_touch.down event is guaranteed to occur before the first - * wl_touch.orientation event for this touch ID but both events may - * occur within the same wl_touch.frame. - * - * The orientation describes the clockwise angle of a touchpoint's - * major axis to the positive surface y-axis and is normalized to - * the -180 to +180 degree range. The granularity of orientation - * depends on the touch device, some devices only support binary - * rotation values between 0 and 90 degrees. - * - * This event is only sent by the compositor if the touch device - * supports orientation reports. - * @param id the unique ID of this touch point - * @param orientation angle between major axis and positive surface y-axis in degrees - * @since 6 - */ - void (*orientation)(void *data, - struct wl_touch *wl_touch, - int32_t id, - wl_fixed_t orientation); -}; - -/** - * @ingroup iface_wl_touch - */ -static inline int -wl_touch_add_listener(struct wl_touch *wl_touch, - const struct wl_touch_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_touch, - (void (**)(void)) listener, data); -} - -#define WL_TOUCH_RELEASE 0 - -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_DOWN_SINCE_VERSION 1 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_UP_SINCE_VERSION 1 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_MOTION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_FRAME_SINCE_VERSION 1 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_CANCEL_SINCE_VERSION 1 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_SHAPE_SINCE_VERSION 6 -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_ORIENTATION_SINCE_VERSION 6 - -/** - * @ingroup iface_wl_touch - */ -#define WL_TOUCH_RELEASE_SINCE_VERSION 3 - -/** @ingroup iface_wl_touch */ -static inline void -wl_touch_set_user_data(struct wl_touch *wl_touch, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_touch, user_data); -} - -/** @ingroup iface_wl_touch */ -static inline void * -wl_touch_get_user_data(struct wl_touch *wl_touch) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_touch); -} - -static inline uint32_t -wl_touch_get_version(struct wl_touch *wl_touch) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_touch); -} - -/** @ingroup iface_wl_touch */ -static inline void -wl_touch_destroy(struct wl_touch *wl_touch) -{ - wl_proxy_destroy((struct wl_proxy *) wl_touch); -} - -/** - * @ingroup iface_wl_touch - */ -static inline void -wl_touch_release(struct wl_touch *wl_touch) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_touch, - WL_TOUCH_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_touch), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef WL_OUTPUT_SUBPIXEL_ENUM -#define WL_OUTPUT_SUBPIXEL_ENUM -/** - * @ingroup iface_wl_output - * subpixel geometry information - * - * This enumeration describes how the physical - * pixels on an output are laid out. - */ -enum wl_output_subpixel { - /** - * unknown geometry - */ - WL_OUTPUT_SUBPIXEL_UNKNOWN = 0, - /** - * no geometry - */ - WL_OUTPUT_SUBPIXEL_NONE = 1, - /** - * horizontal RGB - */ - WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB = 2, - /** - * horizontal BGR - */ - WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR = 3, - /** - * vertical RGB - */ - WL_OUTPUT_SUBPIXEL_VERTICAL_RGB = 4, - /** - * vertical BGR - */ - WL_OUTPUT_SUBPIXEL_VERTICAL_BGR = 5, -}; -#endif /* WL_OUTPUT_SUBPIXEL_ENUM */ - -#ifndef WL_OUTPUT_TRANSFORM_ENUM -#define WL_OUTPUT_TRANSFORM_ENUM -/** - * @ingroup iface_wl_output - * transform from framebuffer to output - * - * This describes the transform that a compositor will apply to a - * surface to compensate for the rotation or mirroring of an - * output device. - * - * The flipped values correspond to an initial flip around a - * vertical axis followed by rotation. - * - * The purpose is mainly to allow clients to render accordingly and - * tell the compositor, so that for fullscreen surfaces, the - * compositor will still be able to scan out directly from client - * surfaces. - */ -enum wl_output_transform { - /** - * no transform - */ - WL_OUTPUT_TRANSFORM_NORMAL = 0, - /** - * 90 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_90 = 1, - /** - * 180 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_180 = 2, - /** - * 270 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_270 = 3, - /** - * 180 degree flip around a vertical axis - */ - WL_OUTPUT_TRANSFORM_FLIPPED = 4, - /** - * flip and rotate 90 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_FLIPPED_90 = 5, - /** - * flip and rotate 180 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_FLIPPED_180 = 6, - /** - * flip and rotate 270 degrees counter-clockwise - */ - WL_OUTPUT_TRANSFORM_FLIPPED_270 = 7, -}; -#endif /* WL_OUTPUT_TRANSFORM_ENUM */ - -#ifndef WL_OUTPUT_MODE_ENUM -#define WL_OUTPUT_MODE_ENUM -/** - * @ingroup iface_wl_output - * mode information - * - * These flags describe properties of an output mode. - * They are used in the flags bitfield of the mode event. - */ -enum wl_output_mode { - /** - * indicates this is the current mode - */ - WL_OUTPUT_MODE_CURRENT = 0x1, - /** - * indicates this is the preferred mode - */ - WL_OUTPUT_MODE_PREFERRED = 0x2, -}; -#endif /* WL_OUTPUT_MODE_ENUM */ - -/** - * @ingroup iface_wl_output - * @struct wl_output_listener - */ -struct wl_output_listener { - /** - * properties of the output - * - * The geometry event describes geometric properties of the - * output. The event is sent when binding to the output object and - * whenever any of the properties change. - * - * The physical size can be set to zero if it doesn't make sense - * for this output (e.g. for projectors or virtual outputs). - * - * The geometry event will be followed by a done event (starting - * from version 2). - * - * Note: wl_output only advertises partial information about the - * output position and identification. Some compositors, for - * instance those not implementing a desktop-style output layout or - * those exposing virtual outputs, might fake this information. - * Instead of using x and y, clients should use - * xdg_output.logical_position. Instead of using make and model, - * clients should use name and description. - * @param x x position within the global compositor space - * @param y y position within the global compositor space - * @param physical_width width in millimeters of the output - * @param physical_height height in millimeters of the output - * @param subpixel subpixel orientation of the output - * @param make textual description of the manufacturer - * @param model textual description of the model - * @param transform transform that maps framebuffer to output - */ - void (*geometry)(void *data, - struct wl_output *wl_output, - int32_t x, - int32_t y, - int32_t physical_width, - int32_t physical_height, - int32_t subpixel, - const char *make, - const char *model, - int32_t transform); - /** - * advertise available modes for the output - * - * The mode event describes an available mode for the output. - * - * The event is sent when binding to the output object and there - * will always be one mode, the current mode. The event is sent - * again if an output changes mode, for the mode that is now - * current. In other words, the current mode is always the last - * mode that was received with the current flag set. - * - * Non-current modes are deprecated. A compositor can decide to - * only advertise the current mode and never send other modes. - * Clients should not rely on non-current modes. - * - * The size of a mode is given in physical hardware units of the - * output device. This is not necessarily the same as the output - * size in the global compositor space. For instance, the output - * may be scaled, as described in wl_output.scale, or transformed, - * as described in wl_output.transform. Clients willing to retrieve - * the output size in the global compositor space should use - * xdg_output.logical_size instead. - * - * The vertical refresh rate can be set to zero if it doesn't make - * sense for this output (e.g. for virtual outputs). - * - * The mode event will be followed by a done event (starting from - * version 2). - * - * Clients should not use the refresh rate to schedule frames. - * Instead, they should use the wl_surface.frame event or the - * presentation-time protocol. - * - * Note: this information is not always meaningful for all outputs. - * Some compositors, such as those exposing virtual outputs, might - * fake the refresh rate or the size. - * @param flags bitfield of mode flags - * @param width width of the mode in hardware units - * @param height height of the mode in hardware units - * @param refresh vertical refresh rate in mHz - */ - void (*mode)(void *data, - struct wl_output *wl_output, - uint32_t flags, - int32_t width, - int32_t height, - int32_t refresh); - /** - * sent all information about output - * - * This event is sent after all other properties have been sent - * after binding to the output object and after any other property - * changes done after that. This allows changes to the output - * properties to be seen as atomic, even if they happen via - * multiple events. - * @since 2 - */ - void (*done)(void *data, - struct wl_output *wl_output); - /** - * output scaling properties - * - * This event contains scaling geometry information that is not - * in the geometry event. It may be sent after binding the output - * object or if the output scale changes later. If it is not sent, - * the client should assume a scale of 1. - * - * A scale larger than 1 means that the compositor will - * automatically scale surface buffers by this amount when - * rendering. This is used for very high resolution displays where - * applications rendering at the native resolution would be too - * small to be legible. - * - * It is intended that scaling aware clients track the current - * output of a surface, and if it is on a scaled output it should - * use wl_surface.set_buffer_scale with the scale of the output. - * That way the compositor can avoid scaling the surface, and the - * client can supply a higher detail image. - * - * The scale event will be followed by a done event. - * @param factor scaling factor of output - * @since 2 - */ - void (*scale)(void *data, - struct wl_output *wl_output, - int32_t factor); - /** - * name of this output - * - * Many compositors will assign user-friendly names to their - * outputs, show them to the user, allow the user to refer to an - * output, etc. The client may wish to know this name as well to - * offer the user similar behaviors. - * - * The name is a UTF-8 string with no convention defined for its - * contents. Each name is unique among all wl_output globals. The - * name is only guaranteed to be unique for the compositor - * instance. - * - * The same output name is used for all clients for a given - * wl_output global. Thus, the name can be shared across processes - * to refer to a specific wl_output global. - * - * The name is not guaranteed to be persistent across sessions, - * thus cannot be used to reliably identify an output in e.g. - * configuration files. - * - * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. - * However, do not assume that the name is a reflection of an - * underlying DRM connector, X11 connection, etc. - * - * The name event is sent after binding the output object. This - * event is only sent once per output object, and the name does not - * change over the lifetime of the wl_output global. - * - * Compositors may re-use the same output name if the wl_output - * global is destroyed and re-created later. Compositors should - * avoid re-using the same name if possible. - * - * The name event will be followed by a done event. - * @param name output name - * @since 4 - */ - void (*name)(void *data, - struct wl_output *wl_output, - const char *name); - /** - * human-readable description of this output - * - * Many compositors can produce human-readable descriptions of - * their outputs. The client may wish to know this description as - * well, e.g. for output selection purposes. - * - * The description is a UTF-8 string with no convention defined for - * its contents. The description is not guaranteed to be unique - * among all wl_output globals. Examples might include 'Foocorp 11" - * Display' or 'Virtual X11 output via :1'. - * - * The description event is sent after binding the output object - * and whenever the description changes. The description is - * optional, and may not be sent at all. - * - * The description event will be followed by a done event. - * @param description output description - * @since 4 - */ - void (*description)(void *data, - struct wl_output *wl_output, - const char *description); -}; - -/** - * @ingroup iface_wl_output - */ -static inline int -wl_output_add_listener(struct wl_output *wl_output, - const struct wl_output_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) wl_output, - (void (**)(void)) listener, data); -} - -#define WL_OUTPUT_RELEASE 0 - -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_GEOMETRY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_MODE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_DONE_SINCE_VERSION 2 -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_SCALE_SINCE_VERSION 2 -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_NAME_SINCE_VERSION 4 -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_DESCRIPTION_SINCE_VERSION 4 - -/** - * @ingroup iface_wl_output - */ -#define WL_OUTPUT_RELEASE_SINCE_VERSION 3 - -/** @ingroup iface_wl_output */ -static inline void -wl_output_set_user_data(struct wl_output *wl_output, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_output, user_data); -} - -/** @ingroup iface_wl_output */ -static inline void * -wl_output_get_user_data(struct wl_output *wl_output) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_output); -} - -static inline uint32_t -wl_output_get_version(struct wl_output *wl_output) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_output); -} - -/** @ingroup iface_wl_output */ -static inline void -wl_output_destroy(struct wl_output *wl_output) -{ - wl_proxy_destroy((struct wl_proxy *) wl_output); -} - -/** - * @ingroup iface_wl_output - * - * Using this request a client can tell the server that it is not going to - * use the output object anymore. - */ -static inline void -wl_output_release(struct wl_output *wl_output) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_output, - WL_OUTPUT_RELEASE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_output), WL_MARSHAL_FLAG_DESTROY); -} - -#define WL_REGION_DESTROY 0 -#define WL_REGION_ADD 1 -#define WL_REGION_SUBTRACT 2 - - -/** - * @ingroup iface_wl_region - */ -#define WL_REGION_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_region - */ -#define WL_REGION_ADD_SINCE_VERSION 1 -/** - * @ingroup iface_wl_region - */ -#define WL_REGION_SUBTRACT_SINCE_VERSION 1 - -/** @ingroup iface_wl_region */ -static inline void -wl_region_set_user_data(struct wl_region *wl_region, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_region, user_data); -} - -/** @ingroup iface_wl_region */ -static inline void * -wl_region_get_user_data(struct wl_region *wl_region) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_region); -} - -static inline uint32_t -wl_region_get_version(struct wl_region *wl_region) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_region); -} - -/** - * @ingroup iface_wl_region - * - * Destroy the region. This will invalidate the object ID. - */ -static inline void -wl_region_destroy(struct wl_region *wl_region) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_region, - WL_REGION_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_region), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_region - * - * Add the specified rectangle to the region. - */ -static inline void -wl_region_add(struct wl_region *wl_region, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_region, - WL_REGION_ADD, NULL, wl_proxy_get_version((struct wl_proxy *) wl_region), 0, x, y, width, height); -} - -/** - * @ingroup iface_wl_region - * - * Subtract the specified rectangle from the region. - */ -static inline void -wl_region_subtract(struct wl_region *wl_region, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_region, - WL_REGION_SUBTRACT, NULL, wl_proxy_get_version((struct wl_proxy *) wl_region), 0, x, y, width, height); -} - -#ifndef WL_SUBCOMPOSITOR_ERROR_ENUM -#define WL_SUBCOMPOSITOR_ERROR_ENUM -enum wl_subcompositor_error { - /** - * the to-be sub-surface is invalid - */ - WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE = 0, - /** - * the to-be sub-surface parent is invalid - */ - WL_SUBCOMPOSITOR_ERROR_BAD_PARENT = 1, -}; -#endif /* WL_SUBCOMPOSITOR_ERROR_ENUM */ - -#define WL_SUBCOMPOSITOR_DESTROY 0 -#define WL_SUBCOMPOSITOR_GET_SUBSURFACE 1 - - -/** - * @ingroup iface_wl_subcompositor - */ -#define WL_SUBCOMPOSITOR_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subcompositor - */ -#define WL_SUBCOMPOSITOR_GET_SUBSURFACE_SINCE_VERSION 1 - -/** @ingroup iface_wl_subcompositor */ -static inline void -wl_subcompositor_set_user_data(struct wl_subcompositor *wl_subcompositor, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_subcompositor, user_data); -} - -/** @ingroup iface_wl_subcompositor */ -static inline void * -wl_subcompositor_get_user_data(struct wl_subcompositor *wl_subcompositor) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_subcompositor); -} - -static inline uint32_t -wl_subcompositor_get_version(struct wl_subcompositor *wl_subcompositor) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_subcompositor); -} - -/** - * @ingroup iface_wl_subcompositor - * - * Informs the server that the client will not be using this - * protocol object anymore. This does not affect any other - * objects, wl_subsurface objects included. - */ -static inline void -wl_subcompositor_destroy(struct wl_subcompositor *wl_subcompositor) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subcompositor, - WL_SUBCOMPOSITOR_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subcompositor), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_subcompositor - * - * Create a sub-surface interface for the given surface, and - * associate it with the given parent surface. This turns a - * plain wl_surface into a sub-surface. - * - * The to-be sub-surface must not already have another role, and it - * must not have an existing wl_subsurface object. Otherwise the - * bad_surface protocol error is raised. - * - * Adding sub-surfaces to a parent is a double-buffered operation on the - * parent (see wl_surface.commit). The effect of adding a sub-surface - * becomes visible on the next time the state of the parent surface is - * applied. - * - * The parent surface must not be one of the child surface's descendants, - * and the parent must be different from the child surface, otherwise the - * bad_parent protocol error is raised. - * - * This request modifies the behaviour of wl_surface.commit request on - * the sub-surface, see the documentation on wl_subsurface interface. - */ -static inline struct wl_subsurface * -wl_subcompositor_get_subsurface(struct wl_subcompositor *wl_subcompositor, struct wl_surface *surface, struct wl_surface *parent) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) wl_subcompositor, - WL_SUBCOMPOSITOR_GET_SUBSURFACE, &wl_subsurface_interface, wl_proxy_get_version((struct wl_proxy *) wl_subcompositor), 0, NULL, surface, parent); - - return (struct wl_subsurface *) id; -} - -#ifndef WL_SUBSURFACE_ERROR_ENUM -#define WL_SUBSURFACE_ERROR_ENUM -enum wl_subsurface_error { - /** - * wl_surface is not a sibling or the parent - */ - WL_SUBSURFACE_ERROR_BAD_SURFACE = 0, -}; -#endif /* WL_SUBSURFACE_ERROR_ENUM */ - -#define WL_SUBSURFACE_DESTROY 0 -#define WL_SUBSURFACE_SET_POSITION 1 -#define WL_SUBSURFACE_PLACE_ABOVE 2 -#define WL_SUBSURFACE_PLACE_BELOW 3 -#define WL_SUBSURFACE_SET_SYNC 4 -#define WL_SUBSURFACE_SET_DESYNC 5 - - -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_SET_POSITION_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_PLACE_ABOVE_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_PLACE_BELOW_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_SET_SYNC_SINCE_VERSION 1 -/** - * @ingroup iface_wl_subsurface - */ -#define WL_SUBSURFACE_SET_DESYNC_SINCE_VERSION 1 - -/** @ingroup iface_wl_subsurface */ -static inline void -wl_subsurface_set_user_data(struct wl_subsurface *wl_subsurface, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) wl_subsurface, user_data); -} - -/** @ingroup iface_wl_subsurface */ -static inline void * -wl_subsurface_get_user_data(struct wl_subsurface *wl_subsurface) -{ - return wl_proxy_get_user_data((struct wl_proxy *) wl_subsurface); -} - -static inline uint32_t -wl_subsurface_get_version(struct wl_subsurface *wl_subsurface) -{ - return wl_proxy_get_version((struct wl_proxy *) wl_subsurface); -} - -/** - * @ingroup iface_wl_subsurface - * - * The sub-surface interface is removed from the wl_surface object - * that was turned into a sub-surface with a - * wl_subcompositor.get_subsurface request. The wl_surface's association - * to the parent is deleted. The wl_surface is unmapped immediately. - */ -static inline void -wl_subsurface_destroy(struct wl_subsurface *wl_subsurface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_wl_subsurface - * - * This schedules a sub-surface position change. - * The sub-surface will be moved so that its origin (top left - * corner pixel) will be at the location x, y of the parent surface - * coordinate system. The coordinates are not restricted to the parent - * surface area. Negative values are allowed. - * - * The scheduled coordinates will take effect whenever the state of the - * parent surface is applied. When this happens depends on whether the - * parent surface is in synchronized mode or not. See - * wl_subsurface.set_sync and wl_subsurface.set_desync for details. - * - * If more than one set_position request is invoked by the client before - * the commit of the parent surface, the position of a new request always - * replaces the scheduled position from any previous request. - * - * The initial position is 0, 0. - */ -static inline void -wl_subsurface_set_position(struct wl_subsurface *wl_subsurface, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_SET_POSITION, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0, x, y); -} - -/** - * @ingroup iface_wl_subsurface - * - * This sub-surface is taken from the stack, and put back just - * above the reference surface, changing the z-order of the sub-surfaces. - * The reference surface must be one of the sibling surfaces, or the - * parent surface. Using any other surface, including this sub-surface, - * will cause a protocol error. - * - * The z-order is double-buffered. Requests are handled in order and - * applied immediately to a pending state. The final pending state is - * copied to the active state the next time the state of the parent - * surface is applied. When this happens depends on whether the parent - * surface is in synchronized mode or not. See wl_subsurface.set_sync and - * wl_subsurface.set_desync for details. - * - * A new sub-surface is initially added as the top-most in the stack - * of its siblings and parent. - */ -static inline void -wl_subsurface_place_above(struct wl_subsurface *wl_subsurface, struct wl_surface *sibling) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_PLACE_ABOVE, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0, sibling); -} - -/** - * @ingroup iface_wl_subsurface - * - * The sub-surface is placed just below the reference surface. - * See wl_subsurface.place_above. - */ -static inline void -wl_subsurface_place_below(struct wl_subsurface *wl_subsurface, struct wl_surface *sibling) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_PLACE_BELOW, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0, sibling); -} - -/** - * @ingroup iface_wl_subsurface - * - * Change the commit behaviour of the sub-surface to synchronized - * mode, also described as the parent dependent mode. - * - * In synchronized mode, wl_surface.commit on a sub-surface will - * accumulate the committed state in a cache, but the state will - * not be applied and hence will not change the compositor output. - * The cached state is applied to the sub-surface immediately after - * the parent surface's state is applied. This ensures atomic - * updates of the parent and all its synchronized sub-surfaces. - * Applying the cached state will invalidate the cache, so further - * parent surface commits do not (re-)apply old state. - * - * See wl_subsurface for the recursive effect of this mode. - */ -static inline void -wl_subsurface_set_sync(struct wl_subsurface *wl_subsurface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_SET_SYNC, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0); -} - -/** - * @ingroup iface_wl_subsurface - * - * Change the commit behaviour of the sub-surface to desynchronized - * mode, also described as independent or freely running mode. - * - * In desynchronized mode, wl_surface.commit on a sub-surface will - * apply the pending state directly, without caching, as happens - * normally with a wl_surface. Calling wl_surface.commit on the - * parent surface has no effect on the sub-surface's wl_surface - * state. This mode allows a sub-surface to be updated on its own. - * - * If cached state exists when wl_surface.commit is called in - * desynchronized mode, the pending state is added to the cached - * state, and applied as a whole. This invalidates the cache. - * - * Note: even if a sub-surface is set to desynchronized, a parent - * sub-surface may override it to behave as synchronized. For details, - * see wl_subsurface. - * - * If a surface's parent surface behaves as desynchronized, then - * the cached state is applied on set_desync. - */ -static inline void -wl_subsurface_set_desync(struct wl_subsurface *wl_subsurface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) wl_subsurface, - WL_SUBSURFACE_SET_DESYNC, NULL, wl_proxy_get_version((struct wl_proxy *) wl_subsurface), 0); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol-code.h deleted file mode 100644 index ceece5a2b..000000000 --- a/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol-code.h +++ /dev/null @@ -1,85 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2020 Aleix Pol Gonzalez - * Copyright © 2020 Carlos Garnacho - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_seat_interface; -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface xdg_activation_token_v1_interface; - -static const struct wl_interface *xdg_activation_v1_types[] = { - NULL, - &xdg_activation_token_v1_interface, - NULL, - &wl_surface_interface, - NULL, - &wl_seat_interface, - &wl_surface_interface, -}; - -static const struct wl_message xdg_activation_v1_requests[] = { - { "destroy", "", xdg_activation_v1_types + 0 }, - { "get_activation_token", "n", xdg_activation_v1_types + 1 }, - { "activate", "so", xdg_activation_v1_types + 2 }, -}; - -WL_PRIVATE const struct wl_interface xdg_activation_v1_interface = { - "xdg_activation_v1", 1, - 3, xdg_activation_v1_requests, - 0, NULL, -}; - -static const struct wl_message xdg_activation_token_v1_requests[] = { - { "set_serial", "uo", xdg_activation_v1_types + 4 }, - { "set_app_id", "s", xdg_activation_v1_types + 0 }, - { "set_surface", "o", xdg_activation_v1_types + 6 }, - { "commit", "", xdg_activation_v1_types + 0 }, - { "destroy", "", xdg_activation_v1_types + 0 }, -}; - -static const struct wl_message xdg_activation_token_v1_events[] = { - { "done", "s", xdg_activation_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_activation_token_v1_interface = { - "xdg_activation_token_v1", 1, - 5, xdg_activation_token_v1_requests, - 1, xdg_activation_token_v1_events, -}; - diff --git a/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol.h b/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol.h deleted file mode 100644 index b26c548c9..000000000 --- a/pkg/glfw/wayland-headers/xdg-activation-v1-client-protocol.h +++ /dev/null @@ -1,415 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef XDG_ACTIVATION_V1_CLIENT_PROTOCOL_H -#define XDG_ACTIVATION_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_xdg_activation_v1 The xdg_activation_v1 protocol - * Protocol for requesting activation of surfaces - * - * @section page_desc_xdg_activation_v1 Description - * - * The way for a client to pass focus to another toplevel is as follows. - * - * The client that intends to activate another toplevel uses the - * xdg_activation_v1.get_activation_token request to get an activation token. - * This token is then forwarded to the client, which is supposed to activate - * one of its surfaces, through a separate band of communication. - * - * One established way of doing this is through the XDG_ACTIVATION_TOKEN - * environment variable of a newly launched child process. The child process - * should unset the environment variable again right after reading it out in - * order to avoid propagating it to other child processes. - * - * Another established way exists for Applications implementing the D-Bus - * interface org.freedesktop.Application, which should get their token under - * activation-token on their platform_data. - * - * In general activation tokens may be transferred across clients through - * means not described in this protocol. - * - * The client to be activated will then pass the token - * it received to the xdg_activation_v1.activate request. The compositor can - * then use this token to decide how to react to the activation request. - * - * The token the activating client gets may be ineffective either already at - * the time it receives it, for example if it was not focused, for focus - * stealing prevention. The activating client will have no way to discover - * the validity of the token, and may still forward it to the to be activated - * client. - * - * The created activation token may optionally get information attached to it - * that can be used by the compositor to identify the application that we - * intend to activate. This can for example be used to display a visual hint - * about what application is being started. - * - * Warning! The protocol described in this file is currently in the testing - * phase. Backward compatible changes may be added together with the - * corresponding interface version bump. Backward incompatible changes can - * only be done by creating a new major version of the extension. - * - * @section page_ifaces_xdg_activation_v1 Interfaces - * - @subpage page_iface_xdg_activation_v1 - interface for activating surfaces - * - @subpage page_iface_xdg_activation_token_v1 - an exported activation handle - * @section page_copyright_xdg_activation_v1 Copyright - *
- *
- * Copyright © 2020 Aleix Pol Gonzalez 
- * Copyright © 2020 Carlos Garnacho 
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_seat; -struct wl_surface; -struct xdg_activation_token_v1; -struct xdg_activation_v1; - -#ifndef XDG_ACTIVATION_V1_INTERFACE -#define XDG_ACTIVATION_V1_INTERFACE -/** - * @page page_iface_xdg_activation_v1 xdg_activation_v1 - * @section page_iface_xdg_activation_v1_desc Description - * - * A global interface used for informing the compositor about applications - * being activated or started, or for applications to request to be - * activated. - * @section page_iface_xdg_activation_v1_api API - * See @ref iface_xdg_activation_v1. - */ -/** - * @defgroup iface_xdg_activation_v1 The xdg_activation_v1 interface - * - * A global interface used for informing the compositor about applications - * being activated or started, or for applications to request to be - * activated. - */ -extern const struct wl_interface xdg_activation_v1_interface; -#endif -#ifndef XDG_ACTIVATION_TOKEN_V1_INTERFACE -#define XDG_ACTIVATION_TOKEN_V1_INTERFACE -/** - * @page page_iface_xdg_activation_token_v1 xdg_activation_token_v1 - * @section page_iface_xdg_activation_token_v1_desc Description - * - * An object for setting up a token and receiving a token handle that can - * be passed as an activation token to another client. - * - * The object is created using the xdg_activation_v1.get_activation_token - * request. This object should then be populated with the app_id, surface - * and serial information and committed. The compositor shall then issue a - * done event with the token. In case the request's parameters are invalid, - * the compositor will provide an invalid token. - * @section page_iface_xdg_activation_token_v1_api API - * See @ref iface_xdg_activation_token_v1. - */ -/** - * @defgroup iface_xdg_activation_token_v1 The xdg_activation_token_v1 interface - * - * An object for setting up a token and receiving a token handle that can - * be passed as an activation token to another client. - * - * The object is created using the xdg_activation_v1.get_activation_token - * request. This object should then be populated with the app_id, surface - * and serial information and committed. The compositor shall then issue a - * done event with the token. In case the request's parameters are invalid, - * the compositor will provide an invalid token. - */ -extern const struct wl_interface xdg_activation_token_v1_interface; -#endif - -#define XDG_ACTIVATION_V1_DESTROY 0 -#define XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN 1 -#define XDG_ACTIVATION_V1_ACTIVATE 2 - - -/** - * @ingroup iface_xdg_activation_v1 - */ -#define XDG_ACTIVATION_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_v1 - */ -#define XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_v1 - */ -#define XDG_ACTIVATION_V1_ACTIVATE_SINCE_VERSION 1 - -/** @ingroup iface_xdg_activation_v1 */ -static inline void -xdg_activation_v1_set_user_data(struct xdg_activation_v1 *xdg_activation_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_activation_v1, user_data); -} - -/** @ingroup iface_xdg_activation_v1 */ -static inline void * -xdg_activation_v1_get_user_data(struct xdg_activation_v1 *xdg_activation_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_activation_v1); -} - -static inline uint32_t -xdg_activation_v1_get_version(struct xdg_activation_v1 *xdg_activation_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1); -} - -/** - * @ingroup iface_xdg_activation_v1 - * - * Notify the compositor that the xdg_activation object will no longer be - * used. - * - * The child objects created via this interface are unaffected and should - * be destroyed separately. - */ -static inline void -xdg_activation_v1_destroy(struct xdg_activation_v1 *xdg_activation_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_v1, - XDG_ACTIVATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_activation_v1 - * - * Creates an xdg_activation_token_v1 object that will provide - * the initiating client with a unique token for this activation. This - * token should be offered to the clients to be activated. - */ -static inline struct xdg_activation_token_v1 * -xdg_activation_v1_get_activation_token(struct xdg_activation_v1 *xdg_activation_v1) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_v1, - XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN, &xdg_activation_token_v1_interface, wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1), 0, NULL); - - return (struct xdg_activation_token_v1 *) id; -} - -/** - * @ingroup iface_xdg_activation_v1 - * - * Requests surface activation. It's up to the compositor to display - * this information as desired, for example by placing the surface above - * the rest. - * - * The compositor may know who requested this by checking the activation - * token and might decide not to follow through with the activation if it's - * considered unwanted. - * - * Compositors can ignore unknown activation tokens when an invalid - * token is passed. - */ -static inline void -xdg_activation_v1_activate(struct xdg_activation_v1 *xdg_activation_v1, const char *token, struct wl_surface *surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_v1, - XDG_ACTIVATION_V1_ACTIVATE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_v1), 0, token, surface); -} - -#ifndef XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM -#define XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM -enum xdg_activation_token_v1_error { - /** - * The token has already been used previously - */ - XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED = 0, -}; -#endif /* XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM */ - -/** - * @ingroup iface_xdg_activation_token_v1 - * @struct xdg_activation_token_v1_listener - */ -struct xdg_activation_token_v1_listener { - /** - * the exported activation token - * - * The 'done' event contains the unique token of this activation - * request and notifies that the provider is done. - * @param token the exported activation token - */ - void (*done)(void *data, - struct xdg_activation_token_v1 *xdg_activation_token_v1, - const char *token); -}; - -/** - * @ingroup iface_xdg_activation_token_v1 - */ -static inline int -xdg_activation_token_v1_add_listener(struct xdg_activation_token_v1 *xdg_activation_token_v1, - const struct xdg_activation_token_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_activation_token_v1, - (void (**)(void)) listener, data); -} - -#define XDG_ACTIVATION_TOKEN_V1_SET_SERIAL 0 -#define XDG_ACTIVATION_TOKEN_V1_SET_APP_ID 1 -#define XDG_ACTIVATION_TOKEN_V1_SET_SURFACE 2 -#define XDG_ACTIVATION_TOKEN_V1_COMMIT 3 -#define XDG_ACTIVATION_TOKEN_V1_DESTROY 4 - -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_DONE_SINCE_VERSION 1 - -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_SET_SERIAL_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_SET_APP_ID_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_SET_SURFACE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_COMMIT_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_activation_token_v1 - */ -#define XDG_ACTIVATION_TOKEN_V1_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_xdg_activation_token_v1 */ -static inline void -xdg_activation_token_v1_set_user_data(struct xdg_activation_token_v1 *xdg_activation_token_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_activation_token_v1, user_data); -} - -/** @ingroup iface_xdg_activation_token_v1 */ -static inline void * -xdg_activation_token_v1_get_user_data(struct xdg_activation_token_v1 *xdg_activation_token_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_activation_token_v1); -} - -static inline uint32_t -xdg_activation_token_v1_get_version(struct xdg_activation_token_v1 *xdg_activation_token_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1); -} - -/** - * @ingroup iface_xdg_activation_token_v1 - * - * Provides information about the seat and serial event that requested the - * token. - * - * The serial can come from an input or focus event. For instance, if a - * click triggers the launch of a third-party client, the launcher client - * should send a set_serial request with the serial and seat from the - * wl_pointer.button event. - * - * Some compositors might refuse to activate toplevels when the token - * doesn't have a valid and recent enough event serial. - * - * Must be sent before commit. This information is optional. - */ -static inline void -xdg_activation_token_v1_set_serial(struct xdg_activation_token_v1 *xdg_activation_token_v1, uint32_t serial, struct wl_seat *seat) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1, - XDG_ACTIVATION_TOKEN_V1_SET_SERIAL, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0, serial, seat); -} - -/** - * @ingroup iface_xdg_activation_token_v1 - * - * The requesting client can specify an app_id to associate the token - * being created with it. - * - * Must be sent before commit. This information is optional. - */ -static inline void -xdg_activation_token_v1_set_app_id(struct xdg_activation_token_v1 *xdg_activation_token_v1, const char *app_id) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1, - XDG_ACTIVATION_TOKEN_V1_SET_APP_ID, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0, app_id); -} - -/** - * @ingroup iface_xdg_activation_token_v1 - * - * This request sets the surface requesting the activation. Note, this is - * different from the surface that will be activated. - * - * Some compositors might refuse to activate toplevels when the token - * doesn't have a requesting surface. - * - * Must be sent before commit. This information is optional. - */ -static inline void -xdg_activation_token_v1_set_surface(struct xdg_activation_token_v1 *xdg_activation_token_v1, struct wl_surface *surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1, - XDG_ACTIVATION_TOKEN_V1_SET_SURFACE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0, surface); -} - -/** - * @ingroup iface_xdg_activation_token_v1 - * - * Requests an activation token based on the different parameters that - * have been offered through set_serial, set_surface and set_app_id. - */ -static inline void -xdg_activation_token_v1_commit(struct xdg_activation_token_v1 *xdg_activation_token_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1, - XDG_ACTIVATION_TOKEN_V1_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), 0); -} - -/** - * @ingroup iface_xdg_activation_token_v1 - * - * Notify the compositor that the xdg_activation_token_v1 object will no - * longer be used. The received token stays valid. - */ -static inline void -xdg_activation_token_v1_destroy(struct xdg_activation_token_v1 *xdg_activation_token_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_activation_token_v1, - XDG_ACTIVATION_TOKEN_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_activation_token_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol-code.h b/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol-code.h deleted file mode 100644 index 85496afa3..000000000 --- a/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol-code.h +++ /dev/null @@ -1,76 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2018 Simon Ser - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface xdg_toplevel_interface; -extern const struct wl_interface zxdg_toplevel_decoration_v1_interface; - -static const struct wl_interface *xdg_decoration_unstable_v1_types[] = { - NULL, - &zxdg_toplevel_decoration_v1_interface, - &xdg_toplevel_interface, -}; - -static const struct wl_message zxdg_decoration_manager_v1_requests[] = { - { "destroy", "", xdg_decoration_unstable_v1_types + 0 }, - { "get_toplevel_decoration", "no", xdg_decoration_unstable_v1_types + 1 }, -}; - -WL_PRIVATE const struct wl_interface zxdg_decoration_manager_v1_interface = { - "zxdg_decoration_manager_v1", 1, - 2, zxdg_decoration_manager_v1_requests, - 0, NULL, -}; - -static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = { - { "destroy", "", xdg_decoration_unstable_v1_types + 0 }, - { "set_mode", "u", xdg_decoration_unstable_v1_types + 0 }, - { "unset_mode", "", xdg_decoration_unstable_v1_types + 0 }, -}; - -static const struct wl_message zxdg_toplevel_decoration_v1_events[] = { - { "configure", "u", xdg_decoration_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zxdg_toplevel_decoration_v1_interface = { - "zxdg_toplevel_decoration_v1", 1, - 3, zxdg_toplevel_decoration_v1_requests, - 1, zxdg_toplevel_decoration_v1_events, -}; - diff --git a/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol.h b/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol.h deleted file mode 100644 index 1aa6a0db1..000000000 --- a/pkg/glfw/wayland-headers/xdg-decoration-unstable-v1-client-protocol.h +++ /dev/null @@ -1,378 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H -#define XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_xdg_decoration_unstable_v1 The xdg_decoration_unstable_v1 protocol - * @section page_ifaces_xdg_decoration_unstable_v1 Interfaces - * - @subpage page_iface_zxdg_decoration_manager_v1 - window decoration manager - * - @subpage page_iface_zxdg_toplevel_decoration_v1 - decoration object for a toplevel surface - * @section page_copyright_xdg_decoration_unstable_v1 Copyright - *
- *
- * Copyright © 2018 Simon Ser
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct xdg_toplevel; -struct zxdg_decoration_manager_v1; -struct zxdg_toplevel_decoration_v1; - -#ifndef ZXDG_DECORATION_MANAGER_V1_INTERFACE -#define ZXDG_DECORATION_MANAGER_V1_INTERFACE -/** - * @page page_iface_zxdg_decoration_manager_v1 zxdg_decoration_manager_v1 - * @section page_iface_zxdg_decoration_manager_v1_desc Description - * - * This interface allows a compositor to announce support for server-side - * decorations. - * - * A window decoration is a set of window controls as deemed appropriate by - * the party managing them, such as user interface components used to move, - * resize and change a window's state. - * - * A client can use this protocol to request being decorated by a supporting - * compositor. - * - * If compositor and client do not negotiate the use of a server-side - * decoration using this protocol, clients continue to self-decorate as they - * see fit. - * - * Warning! The protocol described in this file is experimental and - * backward incompatible changes may be made. Backward compatible changes - * may be added together with the corresponding interface version bump. - * Backward incompatible changes are done by bumping the version number in - * the protocol and interface names and resetting the interface version. - * Once the protocol is to be declared stable, the 'z' prefix and the - * version number in the protocol and interface names are removed and the - * interface version number is reset. - * @section page_iface_zxdg_decoration_manager_v1_api API - * See @ref iface_zxdg_decoration_manager_v1. - */ -/** - * @defgroup iface_zxdg_decoration_manager_v1 The zxdg_decoration_manager_v1 interface - * - * This interface allows a compositor to announce support for server-side - * decorations. - * - * A window decoration is a set of window controls as deemed appropriate by - * the party managing them, such as user interface components used to move, - * resize and change a window's state. - * - * A client can use this protocol to request being decorated by a supporting - * compositor. - * - * If compositor and client do not negotiate the use of a server-side - * decoration using this protocol, clients continue to self-decorate as they - * see fit. - * - * Warning! The protocol described in this file is experimental and - * backward incompatible changes may be made. Backward compatible changes - * may be added together with the corresponding interface version bump. - * Backward incompatible changes are done by bumping the version number in - * the protocol and interface names and resetting the interface version. - * Once the protocol is to be declared stable, the 'z' prefix and the - * version number in the protocol and interface names are removed and the - * interface version number is reset. - */ -extern const struct wl_interface zxdg_decoration_manager_v1_interface; -#endif -#ifndef ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE -#define ZXDG_TOPLEVEL_DECORATION_V1_INTERFACE -/** - * @page page_iface_zxdg_toplevel_decoration_v1 zxdg_toplevel_decoration_v1 - * @section page_iface_zxdg_toplevel_decoration_v1_desc Description - * - * The decoration object allows the compositor to toggle server-side window - * decorations for a toplevel surface. The client can request to switch to - * another mode. - * - * The xdg_toplevel_decoration object must be destroyed before its - * xdg_toplevel. - * @section page_iface_zxdg_toplevel_decoration_v1_api API - * See @ref iface_zxdg_toplevel_decoration_v1. - */ -/** - * @defgroup iface_zxdg_toplevel_decoration_v1 The zxdg_toplevel_decoration_v1 interface - * - * The decoration object allows the compositor to toggle server-side window - * decorations for a toplevel surface. The client can request to switch to - * another mode. - * - * The xdg_toplevel_decoration object must be destroyed before its - * xdg_toplevel. - */ -extern const struct wl_interface zxdg_toplevel_decoration_v1_interface; -#endif - -#define ZXDG_DECORATION_MANAGER_V1_DESTROY 0 -#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION 1 - - -/** - * @ingroup iface_zxdg_decoration_manager_v1 - */ -#define ZXDG_DECORATION_MANAGER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zxdg_decoration_manager_v1 - */ -#define ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION_SINCE_VERSION 1 - -/** @ingroup iface_zxdg_decoration_manager_v1 */ -static inline void -zxdg_decoration_manager_v1_set_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zxdg_decoration_manager_v1, user_data); -} - -/** @ingroup iface_zxdg_decoration_manager_v1 */ -static inline void * -zxdg_decoration_manager_v1_get_user_data(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zxdg_decoration_manager_v1); -} - -static inline uint32_t -zxdg_decoration_manager_v1_get_version(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1); -} - -/** - * @ingroup iface_zxdg_decoration_manager_v1 - * - * Destroy the decoration manager. This doesn't destroy objects created - * with the manager. - */ -static inline void -zxdg_decoration_manager_v1_destroy(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zxdg_decoration_manager_v1, - ZXDG_DECORATION_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zxdg_decoration_manager_v1 - * - * Create a new decoration object associated with the given toplevel. - * - * Creating an xdg_toplevel_decoration from an xdg_toplevel which has a - * buffer attached or committed is a client error, and any attempts by a - * client to attach or manipulate a buffer prior to the first - * xdg_toplevel_decoration.configure event must also be treated as - * errors. - */ -static inline struct zxdg_toplevel_decoration_v1 * -zxdg_decoration_manager_v1_get_toplevel_decoration(struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1, struct xdg_toplevel *toplevel) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_decoration_manager_v1, - ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, &zxdg_toplevel_decoration_v1_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_decoration_manager_v1), 0, NULL, toplevel); - - return (struct zxdg_toplevel_decoration_v1 *) id; -} - -#ifndef ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM -#define ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM -enum zxdg_toplevel_decoration_v1_error { - /** - * xdg_toplevel has a buffer attached before configure - */ - ZXDG_TOPLEVEL_DECORATION_V1_ERROR_UNCONFIGURED_BUFFER = 0, - /** - * xdg_toplevel already has a decoration object - */ - ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ALREADY_CONSTRUCTED = 1, - /** - * xdg_toplevel destroyed before the decoration object - */ - ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ORPHANED = 2, -}; -#endif /* ZXDG_TOPLEVEL_DECORATION_V1_ERROR_ENUM */ - -#ifndef ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM -#define ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - * window decoration modes - * - * These values describe window decoration modes. - */ -enum zxdg_toplevel_decoration_v1_mode { - /** - * no server-side window decoration - */ - ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE = 1, - /** - * server-side window decoration - */ - ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE = 2, -}; -#endif /* ZXDG_TOPLEVEL_DECORATION_V1_MODE_ENUM */ - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - * @struct zxdg_toplevel_decoration_v1_listener - */ -struct zxdg_toplevel_decoration_v1_listener { - /** - * suggest a surface change - * - * The configure event asks the client to change its decoration - * mode. The configured state should not be applied immediately. - * Clients must send an ack_configure in response to this event. - * See xdg_surface.configure and xdg_surface.ack_configure for - * details. - * - * A configure event can be sent at any time. The specified mode - * must be obeyed by the client. - * @param mode the decoration mode - */ - void (*configure)(void *data, - struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, - uint32_t mode); -}; - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - */ -static inline int -zxdg_toplevel_decoration_v1_add_listener(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, - const struct zxdg_toplevel_decoration_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) zxdg_toplevel_decoration_v1, - (void (**)(void)) listener, data); -} - -#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY 0 -#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE 1 -#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE 2 - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - */ -#define ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE_SINCE_VERSION 1 - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - */ -#define ZXDG_TOPLEVEL_DECORATION_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - */ -#define ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE_SINCE_VERSION 1 -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - */ -#define ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE_SINCE_VERSION 1 - -/** @ingroup iface_zxdg_toplevel_decoration_v1 */ -static inline void -zxdg_toplevel_decoration_v1_set_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1, user_data); -} - -/** @ingroup iface_zxdg_toplevel_decoration_v1 */ -static inline void * -zxdg_toplevel_decoration_v1_get_user_data(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zxdg_toplevel_decoration_v1); -} - -static inline uint32_t -zxdg_toplevel_decoration_v1_get_version(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1); -} - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - * - * Switch back to a mode without any server-side decorations at the next - * commit. - */ -static inline void -zxdg_toplevel_decoration_v1_destroy(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1, - ZXDG_TOPLEVEL_DECORATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - * - * Set the toplevel surface decoration mode. This informs the compositor - * that the client prefers the provided decoration mode. - * - * After requesting a decoration mode, the compositor will respond by - * emitting an xdg_surface.configure event. The client should then update - * its content, drawing it without decorations if the received mode is - * server-side decorations. The client must also acknowledge the configure - * when committing the new content (see xdg_surface.ack_configure). - * - * The compositor can decide not to use the client's mode and enforce a - * different mode instead. - * - * Clients whose decoration mode depend on the xdg_toplevel state may send - * a set_mode request in response to an xdg_surface.configure event and wait - * for the next xdg_surface.configure event to prevent unwanted state. - * Such clients are responsible for preventing configure loops and must - * make sure not to send multiple successive set_mode requests with the - * same decoration mode. - */ -static inline void -zxdg_toplevel_decoration_v1_set_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1, - ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), 0, mode); -} - -/** - * @ingroup iface_zxdg_toplevel_decoration_v1 - * - * Unset the toplevel surface decoration mode. This informs the compositor - * that the client doesn't prefer a particular decoration mode. - * - * This request has the same semantics as set_mode. - */ -static inline void -zxdg_toplevel_decoration_v1_unset_mode(struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zxdg_toplevel_decoration_v1, - ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_toplevel_decoration_v1), 0); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/glfw/wayland-headers/xdg-shell-client-protocol-code.h b/pkg/glfw/wayland-headers/xdg-shell-client-protocol-code.h deleted file mode 100644 index d698c2ca5..000000000 --- a/pkg/glfw/wayland-headers/xdg-shell-client-protocol-code.h +++ /dev/null @@ -1,184 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -/* - * Copyright © 2008-2013 Kristian Høgsberg - * Copyright © 2013 Rafael Antognolli - * Copyright © 2013 Jasper St. Pierre - * Copyright © 2010-2013 Intel Corporation - * Copyright © 2015-2017 Samsung Electronics Co., Ltd - * Copyright © 2015-2017 Red Hat Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_output_interface; -extern const struct wl_interface wl_seat_interface; -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface xdg_popup_interface; -extern const struct wl_interface xdg_positioner_interface; -extern const struct wl_interface xdg_surface_interface; -extern const struct wl_interface xdg_toplevel_interface; - -static const struct wl_interface *xdg_shell_types[] = { - NULL, - NULL, - NULL, - NULL, - &xdg_positioner_interface, - &xdg_surface_interface, - &wl_surface_interface, - &xdg_toplevel_interface, - &xdg_popup_interface, - &xdg_surface_interface, - &xdg_positioner_interface, - &xdg_toplevel_interface, - &wl_seat_interface, - NULL, - NULL, - NULL, - &wl_seat_interface, - NULL, - &wl_seat_interface, - NULL, - NULL, - &wl_output_interface, - &wl_seat_interface, - NULL, - &xdg_positioner_interface, - NULL, -}; - -static const struct wl_message xdg_wm_base_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "create_positioner", "n", xdg_shell_types + 4 }, - { "get_xdg_surface", "no", xdg_shell_types + 5 }, - { "pong", "u", xdg_shell_types + 0 }, -}; - -static const struct wl_message xdg_wm_base_events[] = { - { "ping", "u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_wm_base_interface = { - "xdg_wm_base", 6, - 4, xdg_wm_base_requests, - 1, xdg_wm_base_events, -}; - -static const struct wl_message xdg_positioner_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "set_size", "ii", xdg_shell_types + 0 }, - { "set_anchor_rect", "iiii", xdg_shell_types + 0 }, - { "set_anchor", "u", xdg_shell_types + 0 }, - { "set_gravity", "u", xdg_shell_types + 0 }, - { "set_constraint_adjustment", "u", xdg_shell_types + 0 }, - { "set_offset", "ii", xdg_shell_types + 0 }, - { "set_reactive", "3", xdg_shell_types + 0 }, - { "set_parent_size", "3ii", xdg_shell_types + 0 }, - { "set_parent_configure", "3u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_positioner_interface = { - "xdg_positioner", 6, - 10, xdg_positioner_requests, - 0, NULL, -}; - -static const struct wl_message xdg_surface_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "get_toplevel", "n", xdg_shell_types + 7 }, - { "get_popup", "n?oo", xdg_shell_types + 8 }, - { "set_window_geometry", "iiii", xdg_shell_types + 0 }, - { "ack_configure", "u", xdg_shell_types + 0 }, -}; - -static const struct wl_message xdg_surface_events[] = { - { "configure", "u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_surface_interface = { - "xdg_surface", 6, - 5, xdg_surface_requests, - 1, xdg_surface_events, -}; - -static const struct wl_message xdg_toplevel_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "set_parent", "?o", xdg_shell_types + 11 }, - { "set_title", "s", xdg_shell_types + 0 }, - { "set_app_id", "s", xdg_shell_types + 0 }, - { "show_window_menu", "ouii", xdg_shell_types + 12 }, - { "move", "ou", xdg_shell_types + 16 }, - { "resize", "ouu", xdg_shell_types + 18 }, - { "set_max_size", "ii", xdg_shell_types + 0 }, - { "set_min_size", "ii", xdg_shell_types + 0 }, - { "set_maximized", "", xdg_shell_types + 0 }, - { "unset_maximized", "", xdg_shell_types + 0 }, - { "set_fullscreen", "?o", xdg_shell_types + 21 }, - { "unset_fullscreen", "", xdg_shell_types + 0 }, - { "set_minimized", "", xdg_shell_types + 0 }, -}; - -static const struct wl_message xdg_toplevel_events[] = { - { "configure", "iia", xdg_shell_types + 0 }, - { "close", "", xdg_shell_types + 0 }, - { "configure_bounds", "4ii", xdg_shell_types + 0 }, - { "wm_capabilities", "5a", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_toplevel_interface = { - "xdg_toplevel", 6, - 14, xdg_toplevel_requests, - 4, xdg_toplevel_events, -}; - -static const struct wl_message xdg_popup_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "grab", "ou", xdg_shell_types + 22 }, - { "reposition", "3ou", xdg_shell_types + 24 }, -}; - -static const struct wl_message xdg_popup_events[] = { - { "configure", "iiii", xdg_shell_types + 0 }, - { "popup_done", "", xdg_shell_types + 0 }, - { "repositioned", "3u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_popup_interface = { - "xdg_popup", 6, - 3, xdg_popup_requests, - 3, xdg_popup_events, -}; - diff --git a/pkg/glfw/wayland-headers/xdg-shell-client-protocol.h b/pkg/glfw/wayland-headers/xdg-shell-client-protocol.h deleted file mode 100644 index 1e5a9664b..000000000 --- a/pkg/glfw/wayland-headers/xdg-shell-client-protocol.h +++ /dev/null @@ -1,2307 +0,0 @@ -/* Generated by wayland-scanner 1.23.1 */ - -#ifndef XDG_SHELL_CLIENT_PROTOCOL_H -#define XDG_SHELL_CLIENT_PROTOCOL_H - -#include -#include -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_xdg_shell The xdg_shell protocol - * @section page_ifaces_xdg_shell Interfaces - * - @subpage page_iface_xdg_wm_base - create desktop-style surfaces - * - @subpage page_iface_xdg_positioner - child surface positioner - * - @subpage page_iface_xdg_surface - desktop user interface surface base interface - * - @subpage page_iface_xdg_toplevel - toplevel surface - * - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus - * @section page_copyright_xdg_shell Copyright - *
- *
- * Copyright © 2008-2013 Kristian Høgsberg
- * Copyright © 2013      Rafael Antognolli
- * Copyright © 2013      Jasper St. Pierre
- * Copyright © 2010-2013 Intel Corporation
- * Copyright © 2015-2017 Samsung Electronics Co., Ltd
- * Copyright © 2015-2017 Red Hat Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * 
- */ -struct wl_output; -struct wl_seat; -struct wl_surface; -struct xdg_popup; -struct xdg_positioner; -struct xdg_surface; -struct xdg_toplevel; -struct xdg_wm_base; - -#ifndef XDG_WM_BASE_INTERFACE -#define XDG_WM_BASE_INTERFACE -/** - * @page page_iface_xdg_wm_base xdg_wm_base - * @section page_iface_xdg_wm_base_desc Description - * - * The xdg_wm_base interface is exposed as a global object enabling clients - * to turn their wl_surfaces into windows in a desktop environment. It - * defines the basic functionality needed for clients and the compositor to - * create windows that can be dragged, resized, maximized, etc, as well as - * creating transient windows such as popup menus. - * @section page_iface_xdg_wm_base_api API - * See @ref iface_xdg_wm_base. - */ -/** - * @defgroup iface_xdg_wm_base The xdg_wm_base interface - * - * The xdg_wm_base interface is exposed as a global object enabling clients - * to turn their wl_surfaces into windows in a desktop environment. It - * defines the basic functionality needed for clients and the compositor to - * create windows that can be dragged, resized, maximized, etc, as well as - * creating transient windows such as popup menus. - */ -extern const struct wl_interface xdg_wm_base_interface; -#endif -#ifndef XDG_POSITIONER_INTERFACE -#define XDG_POSITIONER_INTERFACE -/** - * @page page_iface_xdg_positioner xdg_positioner - * @section page_iface_xdg_positioner_desc Description - * - * The xdg_positioner provides a collection of rules for the placement of a - * child surface relative to a parent surface. Rules can be defined to ensure - * the child surface remains within the visible area's borders, and to - * specify how the child surface changes its position, such as sliding along - * an axis, or flipping around a rectangle. These positioner-created rules are - * constrained by the requirement that a child surface must intersect with or - * be at least partially adjacent to its parent surface. - * - * See the various requests for details about possible rules. - * - * At the time of the request, the compositor makes a copy of the rules - * specified by the xdg_positioner. Thus, after the request is complete the - * xdg_positioner object can be destroyed or reused; further changes to the - * object will have no effect on previous usages. - * - * For an xdg_positioner object to be considered complete, it must have a - * non-zero size set by set_size, and a non-zero anchor rectangle set by - * set_anchor_rect. Passing an incomplete xdg_positioner object when - * positioning a surface raises an invalid_positioner error. - * @section page_iface_xdg_positioner_api API - * See @ref iface_xdg_positioner. - */ -/** - * @defgroup iface_xdg_positioner The xdg_positioner interface - * - * The xdg_positioner provides a collection of rules for the placement of a - * child surface relative to a parent surface. Rules can be defined to ensure - * the child surface remains within the visible area's borders, and to - * specify how the child surface changes its position, such as sliding along - * an axis, or flipping around a rectangle. These positioner-created rules are - * constrained by the requirement that a child surface must intersect with or - * be at least partially adjacent to its parent surface. - * - * See the various requests for details about possible rules. - * - * At the time of the request, the compositor makes a copy of the rules - * specified by the xdg_positioner. Thus, after the request is complete the - * xdg_positioner object can be destroyed or reused; further changes to the - * object will have no effect on previous usages. - * - * For an xdg_positioner object to be considered complete, it must have a - * non-zero size set by set_size, and a non-zero anchor rectangle set by - * set_anchor_rect. Passing an incomplete xdg_positioner object when - * positioning a surface raises an invalid_positioner error. - */ -extern const struct wl_interface xdg_positioner_interface; -#endif -#ifndef XDG_SURFACE_INTERFACE -#define XDG_SURFACE_INTERFACE -/** - * @page page_iface_xdg_surface xdg_surface - * @section page_iface_xdg_surface_desc Description - * - * An interface that may be implemented by a wl_surface, for - * implementations that provide a desktop-style user interface. - * - * It provides a base set of functionality required to construct user - * interface elements requiring management by the compositor, such as - * toplevel windows, menus, etc. The types of functionality are split into - * xdg_surface roles. - * - * Creating an xdg_surface does not set the role for a wl_surface. In order - * to map an xdg_surface, the client must create a role-specific object - * using, e.g., get_toplevel, get_popup. The wl_surface for any given - * xdg_surface can have at most one role, and may not be assigned any role - * not based on xdg_surface. - * - * A role must be assigned before any other requests are made to the - * xdg_surface object. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_surface state to take effect. - * - * Creating an xdg_surface from a wl_surface which has a buffer attached or - * committed is a client error, and any attempts by a client to attach or - * manipulate a buffer prior to the first xdg_surface.configure call must - * also be treated as errors. - * - * After creating a role-specific object and setting it up, the client must - * perform an initial commit without any buffer attached. The compositor - * will reply with initial wl_surface state such as - * wl_surface.preferred_buffer_scale followed by an xdg_surface.configure - * event. The client must acknowledge it and is then allowed to attach a - * buffer to map the surface. - * - * Mapping an xdg_surface-based role surface is defined as making it - * possible for the surface to be shown by the compositor. Note that - * a mapped surface is not guaranteed to be visible once it is mapped. - * - * For an xdg_surface to be mapped by the compositor, the following - * conditions must be met: - * (1) the client has assigned an xdg_surface-based role to the surface - * (2) the client has set and committed the xdg_surface state and the - * role-dependent state to the surface - * (3) the client has committed a buffer to the surface - * - * A newly-unmapped surface is considered to have met condition (1) out - * of the 3 required conditions for mapping a surface if its role surface - * has not been destroyed, i.e. the client must perform the initial commit - * again before attaching a buffer. - * @section page_iface_xdg_surface_api API - * See @ref iface_xdg_surface. - */ -/** - * @defgroup iface_xdg_surface The xdg_surface interface - * - * An interface that may be implemented by a wl_surface, for - * implementations that provide a desktop-style user interface. - * - * It provides a base set of functionality required to construct user - * interface elements requiring management by the compositor, such as - * toplevel windows, menus, etc. The types of functionality are split into - * xdg_surface roles. - * - * Creating an xdg_surface does not set the role for a wl_surface. In order - * to map an xdg_surface, the client must create a role-specific object - * using, e.g., get_toplevel, get_popup. The wl_surface for any given - * xdg_surface can have at most one role, and may not be assigned any role - * not based on xdg_surface. - * - * A role must be assigned before any other requests are made to the - * xdg_surface object. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_surface state to take effect. - * - * Creating an xdg_surface from a wl_surface which has a buffer attached or - * committed is a client error, and any attempts by a client to attach or - * manipulate a buffer prior to the first xdg_surface.configure call must - * also be treated as errors. - * - * After creating a role-specific object and setting it up, the client must - * perform an initial commit without any buffer attached. The compositor - * will reply with initial wl_surface state such as - * wl_surface.preferred_buffer_scale followed by an xdg_surface.configure - * event. The client must acknowledge it and is then allowed to attach a - * buffer to map the surface. - * - * Mapping an xdg_surface-based role surface is defined as making it - * possible for the surface to be shown by the compositor. Note that - * a mapped surface is not guaranteed to be visible once it is mapped. - * - * For an xdg_surface to be mapped by the compositor, the following - * conditions must be met: - * (1) the client has assigned an xdg_surface-based role to the surface - * (2) the client has set and committed the xdg_surface state and the - * role-dependent state to the surface - * (3) the client has committed a buffer to the surface - * - * A newly-unmapped surface is considered to have met condition (1) out - * of the 3 required conditions for mapping a surface if its role surface - * has not been destroyed, i.e. the client must perform the initial commit - * again before attaching a buffer. - */ -extern const struct wl_interface xdg_surface_interface; -#endif -#ifndef XDG_TOPLEVEL_INTERFACE -#define XDG_TOPLEVEL_INTERFACE -/** - * @page page_iface_xdg_toplevel xdg_toplevel - * @section page_iface_xdg_toplevel_desc Description - * - * This interface defines an xdg_surface role which allows a surface to, - * among other things, set window-like properties such as maximize, - * fullscreen, and minimize, set application-specific metadata like title and - * id, and well as trigger user interactive operations such as interactive - * resize and move. - * - * Unmapping an xdg_toplevel means that the surface cannot be shown - * by the compositor until it is explicitly mapped again. - * All active operations (e.g., move, resize) are canceled and all - * attributes (e.g. title, state, stacking, ...) are discarded for - * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to - * the state it had right after xdg_surface.get_toplevel. The client - * can re-map the toplevel by perfoming a commit without any buffer - * attached, waiting for a configure event and handling it as usual (see - * xdg_surface description). - * - * Attaching a null buffer to a toplevel unmaps the surface. - * @section page_iface_xdg_toplevel_api API - * See @ref iface_xdg_toplevel. - */ -/** - * @defgroup iface_xdg_toplevel The xdg_toplevel interface - * - * This interface defines an xdg_surface role which allows a surface to, - * among other things, set window-like properties such as maximize, - * fullscreen, and minimize, set application-specific metadata like title and - * id, and well as trigger user interactive operations such as interactive - * resize and move. - * - * Unmapping an xdg_toplevel means that the surface cannot be shown - * by the compositor until it is explicitly mapped again. - * All active operations (e.g., move, resize) are canceled and all - * attributes (e.g. title, state, stacking, ...) are discarded for - * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to - * the state it had right after xdg_surface.get_toplevel. The client - * can re-map the toplevel by perfoming a commit without any buffer - * attached, waiting for a configure event and handling it as usual (see - * xdg_surface description). - * - * Attaching a null buffer to a toplevel unmaps the surface. - */ -extern const struct wl_interface xdg_toplevel_interface; -#endif -#ifndef XDG_POPUP_INTERFACE -#define XDG_POPUP_INTERFACE -/** - * @page page_iface_xdg_popup xdg_popup - * @section page_iface_xdg_popup_desc Description - * - * A popup surface is a short-lived, temporary surface. It can be used to - * implement for example menus, popovers, tooltips and other similar user - * interface concepts. - * - * A popup can be made to take an explicit grab. See xdg_popup.grab for - * details. - * - * When the popup is dismissed, a popup_done event will be sent out, and at - * the same time the surface will be unmapped. See the xdg_popup.popup_done - * event for details. - * - * Explicitly destroying the xdg_popup object will also dismiss the popup and - * unmap the surface. Clients that want to dismiss the popup when another - * surface of their own is clicked should dismiss the popup using the destroy - * request. - * - * A newly created xdg_popup will be stacked on top of all previously created - * xdg_popup surfaces associated with the same xdg_toplevel. - * - * The parent of an xdg_popup must be mapped (see the xdg_surface - * description) before the xdg_popup itself. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_popup state to take effect. - * @section page_iface_xdg_popup_api API - * See @ref iface_xdg_popup. - */ -/** - * @defgroup iface_xdg_popup The xdg_popup interface - * - * A popup surface is a short-lived, temporary surface. It can be used to - * implement for example menus, popovers, tooltips and other similar user - * interface concepts. - * - * A popup can be made to take an explicit grab. See xdg_popup.grab for - * details. - * - * When the popup is dismissed, a popup_done event will be sent out, and at - * the same time the surface will be unmapped. See the xdg_popup.popup_done - * event for details. - * - * Explicitly destroying the xdg_popup object will also dismiss the popup and - * unmap the surface. Clients that want to dismiss the popup when another - * surface of their own is clicked should dismiss the popup using the destroy - * request. - * - * A newly created xdg_popup will be stacked on top of all previously created - * xdg_popup surfaces associated with the same xdg_toplevel. - * - * The parent of an xdg_popup must be mapped (see the xdg_surface - * description) before the xdg_popup itself. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_popup state to take effect. - */ -extern const struct wl_interface xdg_popup_interface; -#endif - -#ifndef XDG_WM_BASE_ERROR_ENUM -#define XDG_WM_BASE_ERROR_ENUM -enum xdg_wm_base_error { - /** - * given wl_surface has another role - */ - XDG_WM_BASE_ERROR_ROLE = 0, - /** - * xdg_wm_base was destroyed before children - */ - XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1, - /** - * the client tried to map or destroy a non-topmost popup - */ - XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2, - /** - * the client specified an invalid popup parent surface - */ - XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3, - /** - * the client provided an invalid surface state - */ - XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4, - /** - * the client provided an invalid positioner - */ - XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5, - /** - * the client didn’t respond to a ping event in time - */ - XDG_WM_BASE_ERROR_UNRESPONSIVE = 6, -}; -#endif /* XDG_WM_BASE_ERROR_ENUM */ - -/** - * @ingroup iface_xdg_wm_base - * @struct xdg_wm_base_listener - */ -struct xdg_wm_base_listener { - /** - * check if the client is alive - * - * The ping event asks the client if it's still alive. Pass the - * serial specified in the event back to the compositor by sending - * a "pong" request back with the specified serial. See - * xdg_wm_base.pong. - * - * Compositors can use this to determine if the client is still - * alive. It's unspecified what will happen if the client doesn't - * respond to the ping request, or in what timeframe. Clients - * should try to respond in a reasonable amount of time. The - * “unresponsive” error is provided for compositors that wish - * to disconnect unresponsive clients. - * - * A compositor is free to ping in any way it wants, but a client - * must always respond to any xdg_wm_base object it created. - * @param serial pass this to the pong request - */ - void (*ping)(void *data, - struct xdg_wm_base *xdg_wm_base, - uint32_t serial); -}; - -/** - * @ingroup iface_xdg_wm_base - */ -static inline int -xdg_wm_base_add_listener(struct xdg_wm_base *xdg_wm_base, - const struct xdg_wm_base_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_wm_base, - (void (**)(void)) listener, data); -} - -#define XDG_WM_BASE_DESTROY 0 -#define XDG_WM_BASE_CREATE_POSITIONER 1 -#define XDG_WM_BASE_GET_XDG_SURFACE 2 -#define XDG_WM_BASE_PONG 3 - -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_PING_SINCE_VERSION 1 - -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_PONG_SINCE_VERSION 1 - -/** @ingroup iface_xdg_wm_base */ -static inline void -xdg_wm_base_set_user_data(struct xdg_wm_base *xdg_wm_base, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_wm_base, user_data); -} - -/** @ingroup iface_xdg_wm_base */ -static inline void * -xdg_wm_base_get_user_data(struct xdg_wm_base *xdg_wm_base) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_wm_base); -} - -static inline uint32_t -xdg_wm_base_get_version(struct xdg_wm_base *xdg_wm_base) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_wm_base); -} - -/** - * @ingroup iface_xdg_wm_base - * - * Destroy this xdg_wm_base object. - * - * Destroying a bound xdg_wm_base object while there are surfaces - * still alive created by this xdg_wm_base object instance is illegal - * and will result in a defunct_surfaces error. - */ -static inline void -xdg_wm_base_destroy(struct xdg_wm_base *xdg_wm_base) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_wm_base - * - * Create a positioner object. A positioner object is used to position - * surfaces relative to some parent surface. See the interface description - * and xdg_surface.get_popup for details. - */ -static inline struct xdg_positioner * -xdg_wm_base_create_positioner(struct xdg_wm_base *xdg_wm_base) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_CREATE_POSITIONER, &xdg_positioner_interface, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, NULL); - - return (struct xdg_positioner *) id; -} - -/** - * @ingroup iface_xdg_wm_base - * - * This creates an xdg_surface for the given surface. While xdg_surface - * itself is not a role, the corresponding surface may only be assigned - * a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is - * illegal to create an xdg_surface for a wl_surface which already has an - * assigned role and this will result in a role error. - * - * This creates an xdg_surface for the given surface. An xdg_surface is - * used as basis to define a role to a given surface, such as xdg_toplevel - * or xdg_popup. It also manages functionality shared between xdg_surface - * based surface roles. - * - * See the documentation of xdg_surface for more details about what an - * xdg_surface is and how it is used. - */ -static inline struct xdg_surface * -xdg_wm_base_get_xdg_surface(struct xdg_wm_base *xdg_wm_base, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_GET_XDG_SURFACE, &xdg_surface_interface, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, NULL, surface); - - return (struct xdg_surface *) id; -} - -/** - * @ingroup iface_xdg_wm_base - * - * A client must respond to a ping event with a pong request or - * the client may be deemed unresponsive. See xdg_wm_base.ping - * and xdg_wm_base.error.unresponsive. - */ -static inline void -xdg_wm_base_pong(struct xdg_wm_base *xdg_wm_base, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_PONG, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, serial); -} - -#ifndef XDG_POSITIONER_ERROR_ENUM -#define XDG_POSITIONER_ERROR_ENUM -enum xdg_positioner_error { - /** - * invalid input provided - */ - XDG_POSITIONER_ERROR_INVALID_INPUT = 0, -}; -#endif /* XDG_POSITIONER_ERROR_ENUM */ - -#ifndef XDG_POSITIONER_ANCHOR_ENUM -#define XDG_POSITIONER_ANCHOR_ENUM -enum xdg_positioner_anchor { - XDG_POSITIONER_ANCHOR_NONE = 0, - XDG_POSITIONER_ANCHOR_TOP = 1, - XDG_POSITIONER_ANCHOR_BOTTOM = 2, - XDG_POSITIONER_ANCHOR_LEFT = 3, - XDG_POSITIONER_ANCHOR_RIGHT = 4, - XDG_POSITIONER_ANCHOR_TOP_LEFT = 5, - XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6, - XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7, - XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8, -}; -#endif /* XDG_POSITIONER_ANCHOR_ENUM */ - -#ifndef XDG_POSITIONER_GRAVITY_ENUM -#define XDG_POSITIONER_GRAVITY_ENUM -enum xdg_positioner_gravity { - XDG_POSITIONER_GRAVITY_NONE = 0, - XDG_POSITIONER_GRAVITY_TOP = 1, - XDG_POSITIONER_GRAVITY_BOTTOM = 2, - XDG_POSITIONER_GRAVITY_LEFT = 3, - XDG_POSITIONER_GRAVITY_RIGHT = 4, - XDG_POSITIONER_GRAVITY_TOP_LEFT = 5, - XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6, - XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7, - XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8, -}; -#endif /* XDG_POSITIONER_GRAVITY_ENUM */ - -#ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM -#define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM -/** - * @ingroup iface_xdg_positioner - * constraint adjustments - * - * The constraint adjustment value define ways the compositor will adjust - * the position of the surface, if the unadjusted position would result - * in the surface being partly constrained. - * - * Whether a surface is considered 'constrained' is left to the compositor - * to determine. For example, the surface may be partly outside the - * compositor's defined 'work area', thus necessitating the child surface's - * position be adjusted until it is entirely inside the work area. - * - * The adjustments can be combined, according to a defined precedence: 1) - * Flip, 2) Slide, 3) Resize. - */ -enum xdg_positioner_constraint_adjustment { - /** - * don't move the child surface when constrained - * - * Don't alter the surface position even if it is constrained on - * some axis, for example partially outside the edge of an output. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0, - /** - * move along the x axis until unconstrained - * - * Slide the surface along the x axis until it is no longer - * constrained. - * - * First try to slide towards the direction of the gravity on the x - * axis until either the edge in the opposite direction of the - * gravity is unconstrained or the edge in the direction of the - * gravity is constrained. - * - * Then try to slide towards the opposite direction of the gravity - * on the x axis until either the edge in the direction of the - * gravity is unconstrained or the edge in the opposite direction - * of the gravity is constrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1, - /** - * move along the y axis until unconstrained - * - * Slide the surface along the y axis until it is no longer - * constrained. - * - * First try to slide towards the direction of the gravity on the y - * axis until either the edge in the opposite direction of the - * gravity is unconstrained or the edge in the direction of the - * gravity is constrained. - * - * Then try to slide towards the opposite direction of the gravity - * on the y axis until either the edge in the direction of the - * gravity is unconstrained or the edge in the opposite direction - * of the gravity is constrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2, - /** - * invert the anchor and gravity on the x axis - * - * Invert the anchor and gravity on the x axis if the surface is - * constrained on the x axis. For example, if the left edge of the - * surface is constrained, the gravity is 'left' and the anchor is - * 'left', change the gravity to 'right' and the anchor to 'right'. - * - * If the adjusted position also ends up being constrained, the - * resulting position of the flip_x adjustment will be the one - * before the adjustment. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4, - /** - * invert the anchor and gravity on the y axis - * - * Invert the anchor and gravity on the y axis if the surface is - * constrained on the y axis. For example, if the bottom edge of - * the surface is constrained, the gravity is 'bottom' and the - * anchor is 'bottom', change the gravity to 'top' and the anchor - * to 'top'. - * - * The adjusted position is calculated given the original anchor - * rectangle and offset, but with the new flipped anchor and - * gravity values. - * - * If the adjusted position also ends up being constrained, the - * resulting position of the flip_y adjustment will be the one - * before the adjustment. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8, - /** - * horizontally resize the surface - * - * Resize the surface horizontally so that it is completely - * unconstrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16, - /** - * vertically resize the surface - * - * Resize the surface vertically so that it is completely - * unconstrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32, -}; -#endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */ - -#define XDG_POSITIONER_DESTROY 0 -#define XDG_POSITIONER_SET_SIZE 1 -#define XDG_POSITIONER_SET_ANCHOR_RECT 2 -#define XDG_POSITIONER_SET_ANCHOR 3 -#define XDG_POSITIONER_SET_GRAVITY 4 -#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT 5 -#define XDG_POSITIONER_SET_OFFSET 6 -#define XDG_POSITIONER_SET_REACTIVE 7 -#define XDG_POSITIONER_SET_PARENT_SIZE 8 -#define XDG_POSITIONER_SET_PARENT_CONFIGURE 9 - - -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION 3 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_PARENT_SIZE_SINCE_VERSION 3 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION 3 - -/** @ingroup iface_xdg_positioner */ -static inline void -xdg_positioner_set_user_data(struct xdg_positioner *xdg_positioner, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_positioner, user_data); -} - -/** @ingroup iface_xdg_positioner */ -static inline void * -xdg_positioner_get_user_data(struct xdg_positioner *xdg_positioner) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_positioner); -} - -static inline uint32_t -xdg_positioner_get_version(struct xdg_positioner *xdg_positioner) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_positioner); -} - -/** - * @ingroup iface_xdg_positioner - * - * Notify the compositor that the xdg_positioner will no longer be used. - */ -static inline void -xdg_positioner_destroy(struct xdg_positioner *xdg_positioner) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_positioner - * - * Set the size of the surface that is to be positioned with the positioner - * object. The size is in surface-local coordinates and corresponds to the - * window geometry. See xdg_surface.set_window_geometry. - * - * If a zero or negative size is set the invalid_input error is raised. - */ -static inline void -xdg_positioner_set_size(struct xdg_positioner *xdg_positioner, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, width, height); -} - -/** - * @ingroup iface_xdg_positioner - * - * Specify the anchor rectangle within the parent surface that the child - * surface will be placed relative to. The rectangle is relative to the - * window geometry as defined by xdg_surface.set_window_geometry of the - * parent surface. - * - * When the xdg_positioner object is used to position a child surface, the - * anchor rectangle may not extend outside the window geometry of the - * positioned child's parent surface. - * - * If a negative size is set the invalid_input error is raised. - */ -static inline void -xdg_positioner_set_anchor_rect(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_ANCHOR_RECT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, x, y, width, height); -} - -/** - * @ingroup iface_xdg_positioner - * - * Defines the anchor point for the anchor rectangle. The specified anchor - * is used derive an anchor point that the child surface will be - * positioned relative to. If a corner anchor is set (e.g. 'top_left' or - * 'bottom_right'), the anchor point will be at the specified corner; - * otherwise, the derived anchor point will be centered on the specified - * edge, or in the center of the anchor rectangle if no edge is specified. - */ -static inline void -xdg_positioner_set_anchor(struct xdg_positioner *xdg_positioner, uint32_t anchor) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_ANCHOR, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, anchor); -} - -/** - * @ingroup iface_xdg_positioner - * - * Defines in what direction a surface should be positioned, relative to - * the anchor point of the parent surface. If a corner gravity is - * specified (e.g. 'bottom_right' or 'top_left'), then the child surface - * will be placed towards the specified gravity; otherwise, the child - * surface will be centered over the anchor point on any axis that had no - * gravity specified. If the gravity is not in the ‘gravity’ enum, an - * invalid_input error is raised. - */ -static inline void -xdg_positioner_set_gravity(struct xdg_positioner *xdg_positioner, uint32_t gravity) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_GRAVITY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, gravity); -} - -/** - * @ingroup iface_xdg_positioner - * - * Specify how the window should be positioned if the originally intended - * position caused the surface to be constrained, meaning at least - * partially outside positioning boundaries set by the compositor. The - * adjustment is set by constructing a bitmask describing the adjustment to - * be made when the surface is constrained on that axis. - * - * If no bit for one axis is set, the compositor will assume that the child - * surface should not change its position on that axis when constrained. - * - * If more than one bit for one axis is set, the order of how adjustments - * are applied is specified in the corresponding adjustment descriptions. - * - * The default adjustment is none. - */ -static inline void -xdg_positioner_set_constraint_adjustment(struct xdg_positioner *xdg_positioner, uint32_t constraint_adjustment) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, constraint_adjustment); -} - -/** - * @ingroup iface_xdg_positioner - * - * Specify the surface position offset relative to the position of the - * anchor on the anchor rectangle and the anchor on the surface. For - * example if the anchor of the anchor rectangle is at (x, y), the surface - * has the gravity bottom|right, and the offset is (ox, oy), the calculated - * surface position will be (x + ox, y + oy). The offset position of the - * surface is the one used for constraint testing. See - * set_constraint_adjustment. - * - * An example use case is placing a popup menu on top of a user interface - * element, while aligning the user interface element of the parent surface - * with some user interface element placed somewhere in the popup surface. - */ -static inline void -xdg_positioner_set_offset(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_OFFSET, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, x, y); -} - -/** - * @ingroup iface_xdg_positioner - * - * When set reactive, the surface is reconstrained if the conditions used - * for constraining changed, e.g. the parent window moved. - * - * If the conditions changed and the popup was reconstrained, an - * xdg_popup.configure event is sent with updated geometry, followed by an - * xdg_surface.configure event. - */ -static inline void -xdg_positioner_set_reactive(struct xdg_positioner *xdg_positioner) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_REACTIVE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0); -} - -/** - * @ingroup iface_xdg_positioner - * - * Set the parent window geometry the compositor should use when - * positioning the popup. The compositor may use this information to - * determine the future state the popup should be constrained using. If - * this doesn't match the dimension of the parent the popup is eventually - * positioned against, the behavior is undefined. - * - * The arguments are given in the surface-local coordinate space. - */ -static inline void -xdg_positioner_set_parent_size(struct xdg_positioner *xdg_positioner, int32_t parent_width, int32_t parent_height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_PARENT_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, parent_width, parent_height); -} - -/** - * @ingroup iface_xdg_positioner - * - * Set the serial of an xdg_surface.configure event this positioner will be - * used in response to. The compositor may use this information together - * with set_parent_size to determine what future state the popup should be - * constrained using. - */ -static inline void -xdg_positioner_set_parent_configure(struct xdg_positioner *xdg_positioner, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_PARENT_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, serial); -} - -#ifndef XDG_SURFACE_ERROR_ENUM -#define XDG_SURFACE_ERROR_ENUM -enum xdg_surface_error { - /** - * Surface was not fully constructed - */ - XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1, - /** - * Surface was already constructed - */ - XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2, - /** - * Attaching a buffer to an unconfigured surface - */ - XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3, - /** - * Invalid serial number when acking a configure event - */ - XDG_SURFACE_ERROR_INVALID_SERIAL = 4, - /** - * Width or height was zero or negative - */ - XDG_SURFACE_ERROR_INVALID_SIZE = 5, - /** - * Surface was destroyed before its role object - */ - XDG_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT = 6, -}; -#endif /* XDG_SURFACE_ERROR_ENUM */ - -/** - * @ingroup iface_xdg_surface - * @struct xdg_surface_listener - */ -struct xdg_surface_listener { - /** - * suggest a surface change - * - * The configure event marks the end of a configure sequence. A - * configure sequence is a set of one or more events configuring - * the state of the xdg_surface, including the final - * xdg_surface.configure event. - * - * Where applicable, xdg_surface surface roles will during a - * configure sequence extend this event as a latched state sent as - * events before the xdg_surface.configure event. Such events - * should be considered to make up a set of atomically applied - * configuration states, where the xdg_surface.configure commits - * the accumulated state. - * - * Clients should arrange their surface for the new states, and - * then send an ack_configure request with the serial sent in this - * configure event at some point before committing the new surface. - * - * If the client receives multiple configure events before it can - * respond to one, it is free to discard all but the last event it - * received. - * @param serial serial of the configure event - */ - void (*configure)(void *data, - struct xdg_surface *xdg_surface, - uint32_t serial); -}; - -/** - * @ingroup iface_xdg_surface - */ -static inline int -xdg_surface_add_listener(struct xdg_surface *xdg_surface, - const struct xdg_surface_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_surface, - (void (**)(void)) listener, data); -} - -#define XDG_SURFACE_DESTROY 0 -#define XDG_SURFACE_GET_TOPLEVEL 1 -#define XDG_SURFACE_GET_POPUP 2 -#define XDG_SURFACE_SET_WINDOW_GEOMETRY 3 -#define XDG_SURFACE_ACK_CONFIGURE 4 - -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1 - -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1 - -/** @ingroup iface_xdg_surface */ -static inline void -xdg_surface_set_user_data(struct xdg_surface *xdg_surface, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_surface, user_data); -} - -/** @ingroup iface_xdg_surface */ -static inline void * -xdg_surface_get_user_data(struct xdg_surface *xdg_surface) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_surface); -} - -static inline uint32_t -xdg_surface_get_version(struct xdg_surface *xdg_surface) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_surface); -} - -/** - * @ingroup iface_xdg_surface - * - * Destroy the xdg_surface object. An xdg_surface must only be destroyed - * after its role object has been destroyed, otherwise - * a defunct_role_object error is raised. - */ -static inline void -xdg_surface_destroy(struct xdg_surface *xdg_surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_surface - * - * This creates an xdg_toplevel object for the given xdg_surface and gives - * the associated wl_surface the xdg_toplevel role. - * - * See the documentation of xdg_toplevel for more details about what an - * xdg_toplevel is and how it is used. - */ -static inline struct xdg_toplevel * -xdg_surface_get_toplevel(struct xdg_surface *xdg_surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_GET_TOPLEVEL, &xdg_toplevel_interface, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, NULL); - - return (struct xdg_toplevel *) id; -} - -/** - * @ingroup iface_xdg_surface - * - * This creates an xdg_popup object for the given xdg_surface and gives - * the associated wl_surface the xdg_popup role. - * - * If null is passed as a parent, a parent surface must be specified using - * some other protocol, before committing the initial state. - * - * See the documentation of xdg_popup for more details about what an - * xdg_popup is and how it is used. - */ -static inline struct xdg_popup * -xdg_surface_get_popup(struct xdg_surface *xdg_surface, struct xdg_surface *parent, struct xdg_positioner *positioner) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_GET_POPUP, &xdg_popup_interface, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, NULL, parent, positioner); - - return (struct xdg_popup *) id; -} - -/** - * @ingroup iface_xdg_surface - * - * The window geometry of a surface is its "visible bounds" from the - * user's perspective. Client-side decorations often have invisible - * portions like drop-shadows which should be ignored for the - * purposes of aligning, placing and constraining windows. - * - * The window geometry is double buffered, and will be applied at the - * time wl_surface.commit of the corresponding wl_surface is called. - * - * When maintaining a position, the compositor should treat the (x, y) - * coordinate of the window geometry as the top left corner of the window. - * A client changing the (x, y) window geometry coordinate should in - * general not alter the position of the window. - * - * Once the window geometry of the surface is set, it is not possible to - * unset it, and it will remain the same until set_window_geometry is - * called again, even if a new subsurface or buffer is attached. - * - * If never set, the value is the full bounds of the surface, - * including any subsurfaces. This updates dynamically on every - * commit. This unset is meant for extremely simple clients. - * - * The arguments are given in the surface-local coordinate space of - * the wl_surface associated with this xdg_surface, and may extend outside - * of the wl_surface itself to mark parts of the subsurface tree as part of - * the window geometry. - * - * When applied, the effective window geometry will be the set window - * geometry clamped to the bounding rectangle of the combined - * geometry of the surface of the xdg_surface and the associated - * subsurfaces. - * - * The effective geometry will not be recalculated unless a new call to - * set_window_geometry is done and the new pending surface state is - * subsequently applied. - * - * The width and height of the effective window geometry must be - * greater than zero. Setting an invalid size will raise an - * invalid_size error. - */ -static inline void -xdg_surface_set_window_geometry(struct xdg_surface *xdg_surface, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_SET_WINDOW_GEOMETRY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, x, y, width, height); -} - -/** - * @ingroup iface_xdg_surface - * - * When a configure event is received, if a client commits the - * surface in response to the configure event, then the client - * must make an ack_configure request sometime before the commit - * request, passing along the serial of the configure event. - * - * For instance, for toplevel surfaces the compositor might use this - * information to move a surface to the top left only when the client has - * drawn itself for the maximized or fullscreen state. - * - * If the client receives multiple configure events before it - * can respond to one, it only has to ack the last configure event. - * Acking a configure event that was never sent raises an invalid_serial - * error. - * - * A client is not required to commit immediately after sending - * an ack_configure request - it may even ack_configure several times - * before its next surface commit. - * - * A client may send multiple ack_configure requests before committing, but - * only the last request sent before a commit indicates which configure - * event the client really is responding to. - * - * Sending an ack_configure request consumes the serial number sent with - * the request, as well as serial numbers sent by all configure events - * sent on this xdg_surface prior to the configure event referenced by - * the committed serial. - * - * It is an error to issue multiple ack_configure requests referencing a - * serial from the same configure event, or to issue an ack_configure - * request referencing a serial from a configure event issued before the - * event identified by the last ack_configure request for the same - * xdg_surface. Doing so will raise an invalid_serial error. - */ -static inline void -xdg_surface_ack_configure(struct xdg_surface *xdg_surface, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_ACK_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, serial); -} - -#ifndef XDG_TOPLEVEL_ERROR_ENUM -#define XDG_TOPLEVEL_ERROR_ENUM -enum xdg_toplevel_error { - /** - * provided value is not a valid variant of the resize_edge enum - */ - XDG_TOPLEVEL_ERROR_INVALID_RESIZE_EDGE = 0, - /** - * invalid parent toplevel - */ - XDG_TOPLEVEL_ERROR_INVALID_PARENT = 1, - /** - * client provided an invalid min or max size - */ - XDG_TOPLEVEL_ERROR_INVALID_SIZE = 2, -}; -#endif /* XDG_TOPLEVEL_ERROR_ENUM */ - -#ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM -#define XDG_TOPLEVEL_RESIZE_EDGE_ENUM -/** - * @ingroup iface_xdg_toplevel - * edge values for resizing - * - * These values are used to indicate which edge of a surface - * is being dragged in a resize operation. - */ -enum xdg_toplevel_resize_edge { - XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0, - XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1, - XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2, - XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4, - XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5, - XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6, - XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8, - XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9, - XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10, -}; -#endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */ - -#ifndef XDG_TOPLEVEL_STATE_ENUM -#define XDG_TOPLEVEL_STATE_ENUM -/** - * @ingroup iface_xdg_toplevel - * types of state on the surface - * - * The different state values used on the surface. This is designed for - * state values like maximized, fullscreen. It is paired with the - * configure event to ensure that both the client and the compositor - * setting the state can be synchronized. - * - * States set in this way are double-buffered. They will get applied on - * the next commit. - */ -enum xdg_toplevel_state { - /** - * the surface is maximized - * the surface is maximized - * - * The surface is maximized. The window geometry specified in the - * configure event must be obeyed by the client, or the - * xdg_wm_base.invalid_surface_state error is raised. - * - * The client should draw without shadow or other decoration - * outside of the window geometry. - */ - XDG_TOPLEVEL_STATE_MAXIMIZED = 1, - /** - * the surface is fullscreen - * the surface is fullscreen - * - * The surface is fullscreen. The window geometry specified in - * the configure event is a maximum; the client cannot resize - * beyond it. For a surface to cover the whole fullscreened area, - * the geometry dimensions must be obeyed by the client. For more - * details, see xdg_toplevel.set_fullscreen. - */ - XDG_TOPLEVEL_STATE_FULLSCREEN = 2, - /** - * the surface is being resized - * the surface is being resized - * - * The surface is being resized. The window geometry specified in - * the configure event is a maximum; the client cannot resize - * beyond it. Clients that have aspect ratio or cell sizing - * configuration can use a smaller size, however. - */ - XDG_TOPLEVEL_STATE_RESIZING = 3, - /** - * the surface is now activated - * the surface is now activated - * - * Client window decorations should be painted as if the window - * is active. Do not assume this means that the window actually has - * keyboard or pointer focus. - */ - XDG_TOPLEVEL_STATE_ACTIVATED = 4, - /** - * the surface’s left edge is tiled - * - * The window is currently in a tiled layout and the left edge is - * considered to be adjacent to another part of the tiling grid. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_LEFT = 5, - /** - * the surface’s right edge is tiled - * - * The window is currently in a tiled layout and the right edge - * is considered to be adjacent to another part of the tiling grid. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_RIGHT = 6, - /** - * the surface’s top edge is tiled - * - * The window is currently in a tiled layout and the top edge is - * considered to be adjacent to another part of the tiling grid. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_TOP = 7, - /** - * the surface’s bottom edge is tiled - * - * The window is currently in a tiled layout and the bottom edge - * is considered to be adjacent to another part of the tiling grid. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8, - /** - * surface repaint is suspended - * - * The surface is currently not ordinarily being repainted; for - * example because its content is occluded by another window, or - * its outputs are switched off due to screen locking. - * @since 6 - */ - XDG_TOPLEVEL_STATE_SUSPENDED = 9, -}; -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION 6 -#endif /* XDG_TOPLEVEL_STATE_ENUM */ - -#ifndef XDG_TOPLEVEL_WM_CAPABILITIES_ENUM -#define XDG_TOPLEVEL_WM_CAPABILITIES_ENUM -enum xdg_toplevel_wm_capabilities { - /** - * show_window_menu is available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU = 1, - /** - * set_maximized and unset_maximized are available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE = 2, - /** - * set_fullscreen and unset_fullscreen are available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN = 3, - /** - * set_minimized is available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE = 4, -}; -#endif /* XDG_TOPLEVEL_WM_CAPABILITIES_ENUM */ - -/** - * @ingroup iface_xdg_toplevel - * @struct xdg_toplevel_listener - */ -struct xdg_toplevel_listener { - /** - * suggest a surface change - * - * This configure event asks the client to resize its toplevel - * surface or to change its state. The configured state should not - * be applied immediately. See xdg_surface.configure for details. - * - * The width and height arguments specify a hint to the window - * about how its surface should be resized in window geometry - * coordinates. See set_window_geometry. - * - * If the width or height arguments are zero, it means the client - * should decide its own window dimension. This may happen when the - * compositor needs to configure the state of the surface but - * doesn't have any information about any previous or expected - * dimension. - * - * The states listed in the event specify how the width/height - * arguments should be interpreted, and possibly how it should be - * drawn. - * - * Clients must send an ack_configure in response to this event. - * See xdg_surface.configure and xdg_surface.ack_configure for - * details. - */ - void (*configure)(void *data, - struct xdg_toplevel *xdg_toplevel, - int32_t width, - int32_t height, - struct wl_array *states); - /** - * surface wants to be closed - * - * The close event is sent by the compositor when the user wants - * the surface to be closed. This should be equivalent to the user - * clicking the close button in client-side decorations, if your - * application has any. - * - * This is only a request that the user intends to close the - * window. The client may choose to ignore this request, or show a - * dialog to ask the user to save their data, etc. - */ - void (*close)(void *data, - struct xdg_toplevel *xdg_toplevel); - /** - * recommended window geometry bounds - * - * The configure_bounds event may be sent prior to a - * xdg_toplevel.configure event to communicate the bounds a window - * geometry size is recommended to constrain to. - * - * The passed width and height are in surface coordinate space. If - * width and height are 0, it means bounds is unknown and - * equivalent to as if no configure_bounds event was ever sent for - * this surface. - * - * The bounds can for example correspond to the size of a monitor - * excluding any panels or other shell components, so that a - * surface isn't created in a way that it cannot fit. - * - * The bounds may change at any point, and in such a case, a new - * xdg_toplevel.configure_bounds will be sent, followed by - * xdg_toplevel.configure and xdg_surface.configure. - * @since 4 - */ - void (*configure_bounds)(void *data, - struct xdg_toplevel *xdg_toplevel, - int32_t width, - int32_t height); - /** - * compositor capabilities - * - * This event advertises the capabilities supported by the - * compositor. If a capability isn't supported, clients should hide - * or disable the UI elements that expose this functionality. For - * instance, if the compositor doesn't advertise support for - * minimized toplevels, a button triggering the set_minimized - * request should not be displayed. - * - * The compositor will ignore requests it doesn't support. For - * instance, a compositor which doesn't advertise support for - * minimized will ignore set_minimized requests. - * - * Compositors must send this event once before the first - * xdg_surface.configure event. When the capabilities change, - * compositors must send this event again and then send an - * xdg_surface.configure event. - * - * The configured state should not be applied immediately. See - * xdg_surface.configure for details. - * - * The capabilities are sent as an array of 32-bit unsigned - * integers in native endianness. - * @param capabilities array of 32-bit capabilities - * @since 5 - */ - void (*wm_capabilities)(void *data, - struct xdg_toplevel *xdg_toplevel, - struct wl_array *capabilities); -}; - -/** - * @ingroup iface_xdg_toplevel - */ -static inline int -xdg_toplevel_add_listener(struct xdg_toplevel *xdg_toplevel, - const struct xdg_toplevel_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_toplevel, - (void (**)(void)) listener, data); -} - -#define XDG_TOPLEVEL_DESTROY 0 -#define XDG_TOPLEVEL_SET_PARENT 1 -#define XDG_TOPLEVEL_SET_TITLE 2 -#define XDG_TOPLEVEL_SET_APP_ID 3 -#define XDG_TOPLEVEL_SHOW_WINDOW_MENU 4 -#define XDG_TOPLEVEL_MOVE 5 -#define XDG_TOPLEVEL_RESIZE 6 -#define XDG_TOPLEVEL_SET_MAX_SIZE 7 -#define XDG_TOPLEVEL_SET_MIN_SIZE 8 -#define XDG_TOPLEVEL_SET_MAXIMIZED 9 -#define XDG_TOPLEVEL_UNSET_MAXIMIZED 10 -#define XDG_TOPLEVEL_SET_FULLSCREEN 11 -#define XDG_TOPLEVEL_UNSET_FULLSCREEN 12 -#define XDG_TOPLEVEL_SET_MINIMIZED 13 - -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION 4 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION 5 - -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1 - -/** @ingroup iface_xdg_toplevel */ -static inline void -xdg_toplevel_set_user_data(struct xdg_toplevel *xdg_toplevel, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_toplevel, user_data); -} - -/** @ingroup iface_xdg_toplevel */ -static inline void * -xdg_toplevel_get_user_data(struct xdg_toplevel *xdg_toplevel) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_toplevel); -} - -static inline uint32_t -xdg_toplevel_get_version(struct xdg_toplevel *xdg_toplevel) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_toplevel); -} - -/** - * @ingroup iface_xdg_toplevel - * - * This request destroys the role surface and unmaps the surface; - * see "Unmapping" behavior in interface section for details. - */ -static inline void -xdg_toplevel_destroy(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set the "parent" of this surface. This surface should be stacked - * above the parent surface and all other ancestor surfaces. - * - * Parent surfaces should be set on dialogs, toolboxes, or other - * "auxiliary" surfaces, so that the parent is raised when the dialog - * is raised. - * - * Setting a null parent for a child surface unsets its parent. Setting - * a null parent for a surface which currently has no parent is a no-op. - * - * Only mapped surfaces can have child surfaces. Setting a parent which - * is not mapped is equivalent to setting a null parent. If a surface - * becomes unmapped, its children's parent is set to the parent of - * the now-unmapped surface. If the now-unmapped surface has no parent, - * its children's parent is unset. If the now-unmapped surface becomes - * mapped again, its parent-child relationship is not restored. - * - * The parent toplevel must not be one of the child toplevel's - * descendants, and the parent must be different from the child toplevel, - * otherwise the invalid_parent protocol error is raised. - */ -static inline void -xdg_toplevel_set_parent(struct xdg_toplevel *xdg_toplevel, struct xdg_toplevel *parent) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_PARENT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, parent); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set a short title for the surface. - * - * This string may be used to identify the surface in a task bar, - * window list, or other user interface elements provided by the - * compositor. - * - * The string must be encoded in UTF-8. - */ -static inline void -xdg_toplevel_set_title(struct xdg_toplevel *xdg_toplevel, const char *title) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_TITLE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, title); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set an application identifier for the surface. - * - * The app ID identifies the general class of applications to which - * the surface belongs. The compositor can use this to group multiple - * surfaces together, or to determine how to launch a new application. - * - * For D-Bus activatable applications, the app ID is used as the D-Bus - * service name. - * - * The compositor shell will try to group application surfaces together - * by their app ID. As a best practice, it is suggested to select app - * ID's that match the basename of the application's .desktop file. - * For example, "org.freedesktop.FooViewer" where the .desktop file is - * "org.freedesktop.FooViewer.desktop". - * - * Like other properties, a set_app_id request can be sent after the - * xdg_toplevel has been mapped to update the property. - * - * See the desktop-entry specification [0] for more details on - * application identifiers and how they relate to well-known D-Bus - * names and .desktop files. - * - * [0] https://standards.freedesktop.org/desktop-entry-spec/ - */ -static inline void -xdg_toplevel_set_app_id(struct xdg_toplevel *xdg_toplevel, const char *app_id) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_APP_ID, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, app_id); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Clients implementing client-side decorations might want to show - * a context menu when right-clicking on the decorations, giving the - * user a menu that they can use to maximize or minimize the window. - * - * This request asks the compositor to pop up such a window menu at - * the given position, relative to the local surface coordinates of - * the parent surface. There are no guarantees as to what menu items - * the window menu contains, or even if a window menu will be drawn - * at all. - * - * This request must be used in response to some sort of user action - * like a button press, key press, or touch down event. - */ -static inline void -xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SHOW_WINDOW_MENU, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial, x, y); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Start an interactive, user-driven move of the surface. - * - * This request must be used in response to some sort of user action - * like a button press, key press, or touch down event. The passed - * serial is used to determine the type of interactive move (touch, - * pointer, etc). - * - * The server may ignore move requests depending on the state of - * the surface (e.g. fullscreen or maximized), or if the passed serial - * is no longer valid. - * - * If triggered, the surface will lose the focus of the device - * (wl_pointer, wl_touch, etc) used for the move. It is up to the - * compositor to visually indicate that the move is taking place, such as - * updating a pointer cursor, during the move. There is no guarantee - * that the device focus will return when the move is completed. - */ -static inline void -xdg_toplevel_move(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_MOVE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Start a user-driven, interactive resize of the surface. - * - * This request must be used in response to some sort of user action - * like a button press, key press, or touch down event. The passed - * serial is used to determine the type of interactive resize (touch, - * pointer, etc). - * - * The server may ignore resize requests depending on the state of - * the surface (e.g. fullscreen or maximized). - * - * If triggered, the client will receive configure events with the - * "resize" state enum value and the expected sizes. See the "resize" - * enum value for more details about what is required. The client - * must also acknowledge configure events using "ack_configure". After - * the resize is completed, the client will receive another "configure" - * event without the resize state. - * - * If triggered, the surface also will lose the focus of the device - * (wl_pointer, wl_touch, etc) used for the resize. It is up to the - * compositor to visually indicate that the resize is taking place, - * such as updating a pointer cursor, during the resize. There is no - * guarantee that the device focus will return when the resize is - * completed. - * - * The edges parameter specifies how the surface should be resized, and - * is one of the values of the resize_edge enum. Values not matching - * a variant of the enum will cause the invalid_resize_edge protocol error. - * The compositor may use this information to update the surface position - * for example when dragging the top left corner. The compositor may also - * use this information to adapt its behavior, e.g. choose an appropriate - * cursor image. - */ -static inline void -xdg_toplevel_resize(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, uint32_t edges) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_RESIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial, edges); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set a maximum size for the window. - * - * The client can specify a maximum size so that the compositor does - * not try to configure the window beyond this size. - * - * The width and height arguments are in window geometry coordinates. - * See xdg_surface.set_window_geometry. - * - * Values set in this way are double-buffered. They will get applied - * on the next commit. - * - * The compositor can use this information to allow or disallow - * different states like maximize or fullscreen and draw accurate - * animations. - * - * Similarly, a tiling window manager may use this information to - * place and resize client windows in a more effective way. - * - * The client should not rely on the compositor to obey the maximum - * size. The compositor may decide to ignore the values set by the - * client and request a larger size. - * - * If never set, or a value of zero in the request, means that the - * client has no expected maximum size in the given dimension. - * As a result, a client wishing to reset the maximum size - * to an unspecified state can use zero for width and height in the - * request. - * - * Requesting a maximum size to be smaller than the minimum size of - * a surface is illegal and will result in an invalid_size error. - * - * The width and height must be greater than or equal to zero. Using - * strictly negative values for width or height will result in a - * invalid_size error. - */ -static inline void -xdg_toplevel_set_max_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MAX_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, width, height); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set a minimum size for the window. - * - * The client can specify a minimum size so that the compositor does - * not try to configure the window below this size. - * - * The width and height arguments are in window geometry coordinates. - * See xdg_surface.set_window_geometry. - * - * Values set in this way are double-buffered. They will get applied - * on the next commit. - * - * The compositor can use this information to allow or disallow - * different states like maximize or fullscreen and draw accurate - * animations. - * - * Similarly, a tiling window manager may use this information to - * place and resize client windows in a more effective way. - * - * The client should not rely on the compositor to obey the minimum - * size. The compositor may decide to ignore the values set by the - * client and request a smaller size. - * - * If never set, or a value of zero in the request, means that the - * client has no expected minimum size in the given dimension. - * As a result, a client wishing to reset the minimum size - * to an unspecified state can use zero for width and height in the - * request. - * - * Requesting a minimum size to be larger than the maximum size of - * a surface is illegal and will result in an invalid_size error. - * - * The width and height must be greater than or equal to zero. Using - * strictly negative values for width and height will result in a - * invalid_size error. - */ -static inline void -xdg_toplevel_set_min_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MIN_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, width, height); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Maximize the surface. - * - * After requesting that the surface should be maximized, the compositor - * will respond by emitting a configure event. Whether this configure - * actually sets the window maximized is subject to compositor policies. - * The client must then update its content, drawing in the configured - * state. The client must also acknowledge the configure when committing - * the new content (see ack_configure). - * - * It is up to the compositor to decide how and where to maximize the - * surface, for example which output and what region of the screen should - * be used. - * - * If the surface was already maximized, the compositor will still emit - * a configure event with the "maximized" state. - * - * If the surface is in a fullscreen state, this request has no direct - * effect. It may alter the state the surface is returned to when - * unmaximized unless overridden by the compositor. - */ -static inline void -xdg_toplevel_set_maximized(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Unmaximize the surface. - * - * After requesting that the surface should be unmaximized, the compositor - * will respond by emitting a configure event. Whether this actually - * un-maximizes the window is subject to compositor policies. - * If available and applicable, the compositor will include the window - * geometry dimensions the window had prior to being maximized in the - * configure event. The client must then update its content, drawing it in - * the configured state. The client must also acknowledge the configure - * when committing the new content (see ack_configure). - * - * It is up to the compositor to position the surface after it was - * unmaximized; usually the position the surface had before maximizing, if - * applicable. - * - * If the surface was already not maximized, the compositor will still - * emit a configure event without the "maximized" state. - * - * If the surface is in a fullscreen state, this request has no direct - * effect. It may alter the state the surface is returned to when - * unmaximized unless overridden by the compositor. - */ -static inline void -xdg_toplevel_unset_maximized(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_UNSET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Make the surface fullscreen. - * - * After requesting that the surface should be fullscreened, the - * compositor will respond by emitting a configure event. Whether the - * client is actually put into a fullscreen state is subject to compositor - * policies. The client must also acknowledge the configure when - * committing the new content (see ack_configure). - * - * The output passed by the request indicates the client's preference as - * to which display it should be set fullscreen on. If this value is NULL, - * it's up to the compositor to choose which display will be used to map - * this surface. - * - * If the surface doesn't cover the whole output, the compositor will - * position the surface in the center of the output and compensate with - * with border fill covering the rest of the output. The content of the - * border fill is undefined, but should be assumed to be in some way that - * attempts to blend into the surrounding area (e.g. solid black). - * - * If the fullscreened surface is not opaque, the compositor must make - * sure that other screen content not part of the same surface tree (made - * up of subsurfaces, popups or similarly coupled surfaces) are not - * visible below the fullscreened surface. - */ -static inline void -xdg_toplevel_set_fullscreen(struct xdg_toplevel *xdg_toplevel, struct wl_output *output) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, output); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Make the surface no longer fullscreen. - * - * After requesting that the surface should be unfullscreened, the - * compositor will respond by emitting a configure event. - * Whether this actually removes the fullscreen state of the client is - * subject to compositor policies. - * - * Making a surface unfullscreen sets states for the surface based on the following: - * * the state(s) it may have had before becoming fullscreen - * * any state(s) decided by the compositor - * * any state(s) requested by the client while the surface was fullscreen - * - * The compositor may include the previous window geometry dimensions in - * the configure event, if applicable. - * - * The client must also acknowledge the configure when committing the new - * content (see ack_configure). - */ -static inline void -xdg_toplevel_unset_fullscreen(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_UNSET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Request that the compositor minimize your surface. There is no - * way to know if the surface is currently minimized, nor is there - * any way to unset minimization on this surface. - * - * If you are looking to throttle redrawing when minimized, please - * instead use the wl_surface.frame event for this, as this will - * also work with live previews on windows in Alt-Tab, Expose or - * similar compositor features. - */ -static inline void -xdg_toplevel_set_minimized(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MINIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -#ifndef XDG_POPUP_ERROR_ENUM -#define XDG_POPUP_ERROR_ENUM -enum xdg_popup_error { - /** - * tried to grab after being mapped - */ - XDG_POPUP_ERROR_INVALID_GRAB = 0, -}; -#endif /* XDG_POPUP_ERROR_ENUM */ - -/** - * @ingroup iface_xdg_popup - * @struct xdg_popup_listener - */ -struct xdg_popup_listener { - /** - * configure the popup surface - * - * This event asks the popup surface to configure itself given - * the configuration. The configured state should not be applied - * immediately. See xdg_surface.configure for details. - * - * The x and y arguments represent the position the popup was - * placed at given the xdg_positioner rule, relative to the upper - * left corner of the window geometry of the parent surface. - * - * For version 2 or older, the configure event for an xdg_popup is - * only ever sent once for the initial configuration. Starting with - * version 3, it may be sent again if the popup is setup with an - * xdg_positioner with set_reactive requested, or in response to - * xdg_popup.reposition requests. - * @param x x position relative to parent surface window geometry - * @param y y position relative to parent surface window geometry - * @param width window geometry width - * @param height window geometry height - */ - void (*configure)(void *data, - struct xdg_popup *xdg_popup, - int32_t x, - int32_t y, - int32_t width, - int32_t height); - /** - * popup interaction is done - * - * The popup_done event is sent out when a popup is dismissed by - * the compositor. The client should destroy the xdg_popup object - * at this point. - */ - void (*popup_done)(void *data, - struct xdg_popup *xdg_popup); - /** - * signal the completion of a repositioned request - * - * The repositioned event is sent as part of a popup - * configuration sequence, together with xdg_popup.configure and - * lastly xdg_surface.configure to notify the completion of a - * reposition request. - * - * The repositioned event is to notify about the completion of a - * xdg_popup.reposition request. The token argument is the token - * passed in the xdg_popup.reposition request. - * - * Immediately after this event is emitted, xdg_popup.configure and - * xdg_surface.configure will be sent with the updated size and - * position, as well as a new configure serial. - * - * The client should optionally update the content of the popup, - * but must acknowledge the new popup configuration for the new - * position to take effect. See xdg_surface.ack_configure for - * details. - * @param token reposition request token - * @since 3 - */ - void (*repositioned)(void *data, - struct xdg_popup *xdg_popup, - uint32_t token); -}; - -/** - * @ingroup iface_xdg_popup - */ -static inline int -xdg_popup_add_listener(struct xdg_popup *xdg_popup, - const struct xdg_popup_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_popup, - (void (**)(void)) listener, data); -} - -#define XDG_POPUP_DESTROY 0 -#define XDG_POPUP_GRAB 1 -#define XDG_POPUP_REPOSITION 2 - -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_CONFIGURE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_REPOSITIONED_SINCE_VERSION 3 - -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_GRAB_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_REPOSITION_SINCE_VERSION 3 - -/** @ingroup iface_xdg_popup */ -static inline void -xdg_popup_set_user_data(struct xdg_popup *xdg_popup, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_popup, user_data); -} - -/** @ingroup iface_xdg_popup */ -static inline void * -xdg_popup_get_user_data(struct xdg_popup *xdg_popup) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_popup); -} - -static inline uint32_t -xdg_popup_get_version(struct xdg_popup *xdg_popup) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_popup); -} - -/** - * @ingroup iface_xdg_popup - * - * This destroys the popup. Explicitly destroying the xdg_popup - * object will also dismiss the popup, and unmap the surface. - * - * If this xdg_popup is not the "topmost" popup, the - * xdg_wm_base.not_the_topmost_popup protocol error will be sent. - */ -static inline void -xdg_popup_destroy(struct xdg_popup *xdg_popup) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, - XDG_POPUP_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_popup - * - * This request makes the created popup take an explicit grab. An explicit - * grab will be dismissed when the user dismisses the popup, or when the - * client destroys the xdg_popup. This can be done by the user clicking - * outside the surface, using the keyboard, or even locking the screen - * through closing the lid or a timeout. - * - * If the compositor denies the grab, the popup will be immediately - * dismissed. - * - * This request must be used in response to some sort of user action like a - * button press, key press, or touch down event. The serial number of the - * event should be passed as 'serial'. - * - * The parent of a grabbing popup must either be an xdg_toplevel surface or - * another xdg_popup with an explicit grab. If the parent is another - * xdg_popup it means that the popups are nested, with this popup now being - * the topmost popup. - * - * Nested popups must be destroyed in the reverse order they were created - * in, e.g. the only popup you are allowed to destroy at all times is the - * topmost one. - * - * When compositors choose to dismiss a popup, they may dismiss every - * nested grabbing popup as well. When a compositor dismisses popups, it - * will follow the same dismissing order as required from the client. - * - * If the topmost grabbing popup is destroyed, the grab will be returned to - * the parent of the popup, if that parent previously had an explicit grab. - * - * If the parent is a grabbing popup which has already been dismissed, this - * popup will be immediately dismissed. If the parent is a popup that did - * not take an explicit grab, an error will be raised. - * - * During a popup grab, the client owning the grab will receive pointer - * and touch events for all their surfaces as normal (similar to an - * "owner-events" grab in X11 parlance), while the top most grabbing popup - * will always have keyboard focus. - */ -static inline void -xdg_popup_grab(struct xdg_popup *xdg_popup, struct wl_seat *seat, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, - XDG_POPUP_GRAB, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), 0, seat, serial); -} - -/** - * @ingroup iface_xdg_popup - * - * Reposition an already-mapped popup. The popup will be placed given the - * details in the passed xdg_positioner object, and a - * xdg_popup.repositioned followed by xdg_popup.configure and - * xdg_surface.configure will be emitted in response. Any parameters set - * by the previous positioner will be discarded. - * - * The passed token will be sent in the corresponding - * xdg_popup.repositioned event. The new popup position will not take - * effect until the corresponding configure event is acknowledged by the - * client. See xdg_popup.repositioned for details. The token itself is - * opaque, and has no other special meaning. - * - * If multiple reposition requests are sent, the compositor may skip all - * but the last one. - * - * If the popup is repositioned in response to a configure event for its - * parent, the client should send an xdg_positioner.set_parent_configure - * and possibly an xdg_positioner.set_parent_size request to allow the - * compositor to properly constrain the popup. - * - * If the popup is repositioned together with a parent that is being - * resized, but not in response to a configure event, the client should - * send an xdg_positioner.set_parent_size request. - */ -static inline void -xdg_popup_reposition(struct xdg_popup *xdg_popup, struct xdg_positioner *positioner, uint32_t token) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, - XDG_POPUP_REPOSITION, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), 0, positioner, token); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pkg/macos/graphics/context.zig b/pkg/macos/graphics/context.zig index d1c6c943f..77e4344e0 100644 --- a/pkg/macos/graphics/context.zig +++ b/pkg/macos/graphics/context.zig @@ -141,6 +141,22 @@ pub fn Context(comptime T: type) type { @bitCast(rect), ); } + + pub fn scaleCTM(self: *T, sx: c.CGFloat, sy: c.CGFloat) void { + c.CGContextScaleCTM( + @ptrCast(self), + sx, + sy, + ); + } + + pub fn translateCTM(self: *T, tx: c.CGFloat, ty: c.CGFloat) void { + c.CGContextTranslateCTM( + @ptrCast(self), + tx, + ty, + ); + } }; } diff --git a/pkg/opengl/Texture.zig b/pkg/opengl/Texture.zig index 2c8e05eff..03e794855 100644 --- a/pkg/opengl/Texture.zig +++ b/pkg/opengl/Texture.zig @@ -92,6 +92,30 @@ pub const Format = enum(c_uint) { _, }; +/// Minification filter for textures. +pub const MinFilter = enum(c_int) { + nearest = c.GL_NEAREST, + linear = c.GL_LINEAR, + nearest_mipmap_nearest = c.GL_NEAREST_MIPMAP_NEAREST, + linear_mipmap_nearest = c.GL_LINEAR_MIPMAP_NEAREST, + nearest_mipmap_linear = c.GL_NEAREST_MIPMAP_LINEAR, + linear_mipmap_linear = c.GL_LINEAR_MIPMAP_LINEAR, +}; + +/// Magnification filter for textures. +pub const MagFilter = enum(c_int) { + nearest = c.GL_NEAREST, + linear = c.GL_LINEAR, +}; + +/// Texture coordinate wrapping mode. +pub const Wrap = enum(c_int) { + clamp_to_edge = c.GL_CLAMP_TO_EDGE, + clamp_to_border = c.GL_CLAMP_TO_BORDER, + mirrored_repeat = c.GL_MIRRORED_REPEAT, + repeat = c.GL_REPEAT, +}; + /// Data type for texture images. pub const DataType = enum(c_uint) { UnsignedByte = c.GL_UNSIGNED_BYTE, diff --git a/po/README_TRANSLATORS.md b/po/README_TRANSLATORS.md index ca1e45faa..c02a5bd48 100644 --- a/po/README_TRANSLATORS.md +++ b/po/README_TRANSLATORS.md @@ -148,6 +148,18 @@ const locales = [_][]const u8{ You should then be able to run `zig build` and see your translations in action. +Before opening a pull request with the new translation file, you should also add +your locale to the `CODEOWNERS` file. Find the `# Localization` section near the +bottom and add a line like so (where `xx_YY` is your locale): + +```diff + # Localization + /po/README_TRANSLATORS.md @ghostty-org/localization + /po/com.mitchellh.ghostty.pot @ghostty-org/localization + /po/zh_CN.UTF-8.po @ghostty-org/zh_CN ++/po/xx_YY.UTF-8.po @ghostty-org/xx_YY +``` + ## Style Guide These are general style guidelines for translations. Naturally, the specific diff --git a/po/bg_BG.UTF-8.po b/po/bg_BG.UTF-8.po new file mode 100644 index 000000000..18cadddf5 --- /dev/null +++ b/po/bg_BG.UTF-8.po @@ -0,0 +1,275 @@ +# Bulgarian translations for com.mitchellh.ghostty package. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Damyan Bogoev , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-04-23 16:58+0800\n" +"PO-Revision-Date: 2025-05-19 11:34+0300\n" +"Last-Translator: Damyan Bogoev \n" +"Language-Team: Bulgarian \n" +"Language: bg\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "Промяна на заглавието на терминала" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "Оставете празно за възстановяване на заглавието по подразбиране." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "Отказ" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "ОК" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "Грешки в конфигурацията" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors " +"below, and either reload your configuration or ignore these errors." +msgstr "Открити са една или повече грешки в конфигурацията. Моля, прегледайте грешките по-долу и или презаредете конфигурацията си, или ги игнорирайте." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "Игнорирай" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100 +msgid "Reload Configuration" +msgstr "Презареди конфигурацията" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "Раздели нагоре" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "Раздели надолу" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "Раздели наляво" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "Раздели надясно" + +#: src/apprt/gtk/ui/1.5/command-palette.blp:16 +msgid "Execute a command…" +msgstr "Изпълни команда…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "Копирай" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "Постави" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "Изчисти" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "Нулирай" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "Раздели" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "Промени заглавие…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "Раздел" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:255 +msgid "New Tab" +msgstr "Нов раздел" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "Затвори раздел" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "Прозорец" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "Нов прозорец" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "Затвори прозорец" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "Конфигурация" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Open Configuration" +msgstr "Отвори конфигурацията" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Command Palette" +msgstr "Командна палитра" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Terminal Inspector" +msgstr "Инспектор на терминала" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +#: src/apprt/gtk/Window.zig:1024 +msgid "About Ghostty" +msgstr "За Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112 +msgid "Quit" +msgstr "Изход" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "Разрешаване на достъп до клипборда" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "Приложение се опитва да чете от клипборда. Текущото съдържание на клипборда е показано по-долу." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "Откажи" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "Позволи" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "Приложение се опитва да запише в клипборда. Текущото съдържание на клипборда е показано по-долу." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "Предупреждение: Потенциално опасно поставяне" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "Поставянето на този текст в терминала може да е опасно, тъй като изглежда, че може да бъдат изпълнени някои команди." + +#: src/apprt/gtk/Window.zig:208 +msgid "Main Menu" +msgstr "Главно меню" + +#: src/apprt/gtk/Window.zig:229 +msgid "View Open Tabs" +msgstr "Преглед на отворените раздели" + +#: src/apprt/gtk/Window.zig:256 +msgid "New Split" +msgstr "Ново разделяне" + +#: src/apprt/gtk/Window.zig:319 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ Използвате дебъг версия на Ghostty! Производителността ще бъде намалена." + +#: src/apprt/gtk/Window.zig:765 +msgid "Reloaded the configuration" +msgstr "Конфигурацията е презаредена" + +#: src/apprt/gtk/Window.zig:1005 +msgid "Ghostty Developers" +msgstr "Разработчици на Ghostty" + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: Инспектор на терминала" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "Затвори" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Изход от Ghostty?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "Затваряне на прозореца?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "Затваряне на раздела?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "Затваряне на разделянето?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "Всички терминални сесии ще бъдат прекратени." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "Всички терминални сесии в този прозорец ще бъдат прекратени." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "Всички терминални сесии в този раздел ще бъдат прекратени." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "Текущият процес в това разделяне ще бъде прекратен." + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "Копирано в клипборда" diff --git a/po/he_IL.UTF-8.po b/po/he_IL.UTF-8.po new file mode 100644 index 000000000..636bf46e3 --- /dev/null +++ b/po/he_IL.UTF-8.po @@ -0,0 +1,298 @@ +# Hebrew translations for com.mitchellh.ghostty. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Sl (Shahaf Levi), Sl's Repository Ltd , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-06-28 17:01+0200\n" +"PO-Revision-Date: 2025-03-13 00:00+0000\n" +"Last-Translator: Sl (Shahaf Levi), Sl's Repository Ltd \n" +"Language-Team: Hebrew \n" +"Language: he\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "שינוי כותרת המסוף" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "השאר/י ריק כדי לשחזר את כותרת ברירת המחדל." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/ui/1.2/ccw-paste.blp:10 +#: src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "ביטול" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "אישור" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "שגיאות בהגדרות" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "נמצאו אחת או יותר שגיאות בהגדרות. אנא בדוק/י את השגיאות המופיעות מטה ולאחר מכן טען/י את ההגדרות מחדש או התעלם/י מהשגיאות." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "התעלמות" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:100 +#: src/apprt/gtk/ui/1.2/config-errors-dialog.blp:10 +msgid "Reload Configuration" +msgstr "טעינה מחדש של ההגדרות" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "פיצול למעלה" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "פיצול למטה" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:16 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "פיצול שמאלה" + +#: src/apprt/gtk/ui/1.0/menu-headerbar-split_menu.blp:21 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "פיצול ימינה" + +#: src/apprt/gtk/ui/1.5/command-palette.blp:16 +msgid "Execute a command…" +msgstr "הרץ/י פקודה…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "העתקה" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 src/apprt/gtk/ui/1.2/ccw-paste.blp:11 +msgid "Paste" +msgstr "הדבקה" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "ניקוי" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "איפוס" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "פיצול" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "שינוי כותרת…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "כרטיסייה" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:263 +msgid "New Tab" +msgstr "כרטיסייה חדשה" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "סגור/י כרטיסייה" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "חלון" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "חלון חדש" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "סגור/י חלון" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "הגדרות" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Open Configuration" +msgstr "פתיחת ההגדרות" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Command Palette" +msgstr "לוח פקודות" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Terminal Inspector" +msgstr "בודק המסוף" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +#: src/apprt/gtk/Window.zig:1036 +msgid "About Ghostty" +msgstr "אודות Ghostty" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:112 +msgid "Quit" +msgstr "יציאה" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "אשר/י גישה ללוח ההעתקה" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "יש אפליקציה שמנסה לקרוא מלוח ההעתקה. התוכן הנוכחי של הלוח מופיע למטה." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "דחייה" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "אישור" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:81 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:77 +msgid "Remember choice for this split" +msgstr "זכור/י את הבחירה עבור פיצול זה" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:82 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:78 +msgid "Reload configuration to show this prompt again" +msgstr "טען/י את ההגדרות מחדש כדי להציג את הבקשה הזו שוב" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +#: src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "יש אפליקציה שמנסה לכתוב לתוך לוח ההעתקה. התוכן הנוכחי של הלוח מופיע למטה." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 src/apprt/gtk/ui/1.2/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "אזהרה: ההדבקה עלולה להיות מסוכנת" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 src/apprt/gtk/ui/1.2/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "הדבקת טקסט זה במסוף עלולה להיות מסוכנת, מכיוון שככל הנראה היא תוביל להרצה של פקודות מסוימות." + +#: src/apprt/gtk/Window.zig:216 +msgid "Main Menu" +msgstr "תפריט ראשי" + +#: src/apprt/gtk/Window.zig:238 +msgid "View Open Tabs" +msgstr "הצג/י כרטיסיות פתוחות" + +#: src/apprt/gtk/Window.zig:264 +msgid "New Split" +msgstr "פיצול חדש" + +#: src/apprt/gtk/Window.zig:327 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ את/ה מריץ/ה גרסת ניפוי שגיאות של Ghostty! הביצועים יהיו ירודים." + +#: src/apprt/gtk/Window.zig:773 +msgid "Reloaded the configuration" +msgstr "ההגדרות הוטענו מחדש" + +#: src/apprt/gtk/Window.zig:1017 +msgid "Ghostty Developers" +msgstr "המפתחים של Ghostty" + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: בודק המסוף" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "סגירה" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "לצאת מGhostty?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "לסגור את החלון?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "לסגור את הכרטיסייה?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "לסגור את הפיצול?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "כל הפעלות המסוף יסתיימו." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "כל הפעלות המסוף בחלון זה יסתיימו." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "כל הפעלות המסוף בכרטיסייה זו יסתיימו." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "התהליך שרץ כרגע בפיצול זה יסתיים." + +#: src/apprt/gtk/Surface.zig:1257 +msgid "Copied to clipboard" +msgstr "הועתק ללוח ההעתקה" diff --git a/po/ko_KR.UTF-8.po b/po/ko_KR.UTF-8.po new file mode 100644 index 000000000..42cb2682f --- /dev/null +++ b/po/ko_KR.UTF-8.po @@ -0,0 +1,259 @@ +# Korean translations for com.mitchellh.ghostty package. +# Copyright (C) 2025 Mitchell Hashimoto +# This file is distributed under the same license as the com.mitchellh.ghostty package. +# Ruben Engelbrecht , 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: com.mitchellh.ghostty\n" +"Report-Msgid-Bugs-To: m@mitchellh.com\n" +"POT-Creation-Date: 2025-03-19 08:54-0700\n" +"PO-Revision-Date: 2025-03-31 03:08+0200\n" +"Last-Translator: Ruben Engelbrecht \n" +"Language-Team: Korean \n" +"Language: ko\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:5 +msgid "Change Terminal Title" +msgstr "터미널 제목 변경" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:6 +msgid "Leave blank to restore the default title." +msgstr "제목란을 비워 두면 기본값으로 복원됩니다." + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:9 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:10 src/apprt/gtk/CloseDialog.zig:44 +msgid "Cancel" +msgstr "취소" + +#: src/apprt/gtk/ui/1.5/prompt-title-dialog.blp:10 +msgid "OK" +msgstr "확인" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:5 +msgid "Configuration Errors" +msgstr "설정 오류" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:6 +msgid "" +"One or more configuration errors were found. Please review the errors below, " +"and either reload your configuration or ignore these errors." +msgstr "설정에 하나 이상의 문제가 발견되었습니다. 아래 오류(를)들을 확인한 후 설정을 다시 불러오거나 무시하세요." + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:9 +msgid "Ignore" +msgstr "무시" + +#: src/apprt/gtk/ui/1.5/config-errors-dialog.blp:10 +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:97 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:95 +msgid "Reload Configuration" +msgstr "설정 값 다시 불러오기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:6 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:6 +msgid "Copy" +msgstr "복사" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:11 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:11 +msgid "Paste" +msgstr "붙여넣기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:18 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:73 +msgid "Clear" +msgstr "지우기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:23 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:78 +msgid "Reset" +msgstr "초기화" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:30 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:42 +msgid "Split" +msgstr "나누기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:33 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:45 +msgid "Change Title…" +msgstr "제목 변경…" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:38 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:50 +msgid "Split Up" +msgstr "위로 창 나누기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:43 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:55 +msgid "Split Down" +msgstr "아래로 창 나누기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:48 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:60 +msgid "Split Left" +msgstr "왼쪽으로 창 나누기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:53 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:65 +msgid "Split Right" +msgstr "오른쪽으로 창 나누기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:59 +msgid "Tab" +msgstr "탭" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:62 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:30 +#: src/apprt/gtk/Window.zig:246 +msgid "New Tab" +msgstr "새 탭" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:67 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:35 +msgid "Close Tab" +msgstr "탭 닫기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:73 +msgid "Window" +msgstr "창" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:76 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:18 +msgid "New Window" +msgstr "새 창" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:81 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:23 +msgid "Close Window" +msgstr "창 닫기" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:89 +msgid "Config" +msgstr "설정" + +#: src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp:92 +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:90 +msgid "Open Configuration" +msgstr "설정 열기" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:85 +msgid "Terminal Inspector" +msgstr "터미널 인스펙터" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:102 +#: src/apprt/gtk/Window.zig:960 +msgid "About Ghostty" +msgstr "Ghostty 정보" + +#: src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp:107 +msgid "Quit" +msgstr "종료" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:6 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:6 +msgid "Authorize Clipboard Access" +msgstr "클립보드 액세스 권한 부여" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:7 +msgid "" +"An application is attempting to read from the clipboard. The current " +"clipboard contents are shown below." +msgstr "응용 프로그램이 클립보드에서 읽기를 시도하고 있습니다. 현재 클립보드 내용은 아래와 같습니다." + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:10 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:10 +msgid "Deny" +msgstr "거부" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp:11 +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:11 +msgid "Allow" +msgstr "허용" + +#: src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp:7 +msgid "" +"An application is attempting to write to the clipboard. The current " +"clipboard contents are shown below." +msgstr "응용 프로그램이 클립보드에 쓰기를 시도하고 있습니다. 현재 클립보드 내용은 아래와 같습니다." + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:6 +msgid "Warning: Potentially Unsafe Paste" +msgstr "경고: 잠재적으로 안전하지 않은 붙여넣기" + +#: src/apprt/gtk/ui/1.5/ccw-paste.blp:7 +msgid "" +"Pasting this text into the terminal may be dangerous as it looks like some " +"commands may be executed." +msgstr "이 텍스트를 터미널에 붙여넣는 것은 위험할 수 있습니다. 일부 명령이 실행될 수 있는 것으로 보입니다." + +#: src/apprt/gtk/inspector.zig:144 +msgid "Ghostty: Terminal Inspector" +msgstr "Ghostty: 터미널 인스펙터" + +#: src/apprt/gtk/Surface.zig:1243 +msgid "Copied to clipboard" +msgstr "클립보드에 복사됨" + +#: src/apprt/gtk/CloseDialog.zig:47 +msgid "Close" +msgstr "닫기" + +#: src/apprt/gtk/CloseDialog.zig:87 +msgid "Quit Ghostty?" +msgstr "Ghostty를 종료하시겠습니까?" + +#: src/apprt/gtk/CloseDialog.zig:88 +msgid "Close Window?" +msgstr "창을 닫으시겠습니까?" + +#: src/apprt/gtk/CloseDialog.zig:89 +msgid "Close Tab?" +msgstr "탭을 닫으시겠습니까?" + +#: src/apprt/gtk/CloseDialog.zig:90 +msgid "Close Split?" +msgstr "분할을 닫으시겠습니까?" + +#: src/apprt/gtk/CloseDialog.zig:96 +msgid "All terminal sessions will be terminated." +msgstr "모든 터미널 세션이 종료됩니다." + +#: src/apprt/gtk/CloseDialog.zig:97 +msgid "All terminal sessions in this window will be terminated." +msgstr "이 창의 모든 터미널 세션이 종료됩니다." + +#: src/apprt/gtk/CloseDialog.zig:98 +msgid "All terminal sessions in this tab will be terminated." +msgstr "이 탭의 모든 터미널 세션이 종료됩니다." + +#: src/apprt/gtk/CloseDialog.zig:99 +msgid "The currently running process in this split will be terminated." +msgstr "이 분할에서 현재 실행 중인 프로세스가 종료됩니다." + +#: src/apprt/gtk/Window.zig:200 +msgid "Main Menu" +msgstr "메인 메뉴" + +#: src/apprt/gtk/Window.zig:221 +msgid "View Open Tabs" +msgstr "열린 탭 보기" + +#: src/apprt/gtk/Window.zig:295 +msgid "" +"⚠️ You're running a debug build of Ghostty! Performance will be degraded." +msgstr "⚠️ Ghostty 디버그 빌드로 실행 중입니다! 성능이 저하됩니다." + +#: src/apprt/gtk/Window.zig:725 +msgid "Reloaded the configuration" +msgstr "설정값을 다시 불러왔습니다" + +#: src/apprt/gtk/Window.zig:941 +msgid "Ghostty Developers" +msgstr "Ghostty 개발자들" diff --git a/src/Command.zig b/src/Command.zig index 7ed026efe..1bddf8b82 100644 --- a/src/Command.zig +++ b/src/Command.zig @@ -188,10 +188,31 @@ fn startPosix(self: *Command, arena: Allocator) !void { // Finally, replace our process. // Note: we must use the "p"-variant of exec here because we // do not guarantee our command is looked up already in the path. - _ = posix.execvpeZ(self.path, argsZ, envp) catch null; + const err = posix.execvpeZ(self.path, argsZ, envp); - // If we are executing this code, the exec failed. In that scenario, - // we return a very specific error that can be detected to determine + // If we are executing this code, the exec failed. We're in the + // child process so there isn't much we can do. We try to output + // something reasonable. Its important to note we MUST NOT return + // any other error condition from here on out. + const stderr = std.io.getStdErr().writer(); + switch (err) { + error.FileNotFound => stderr.print( + \\Requested executable not found. Please verify the command is on + \\the PATH and try again. + \\ + , + .{}, + ) catch {}, + + else => stderr.print( + \\exec syscall failed with unexpected error: {} + \\ + , + .{err}, + ) catch {}, + } + + // We return a very specific error that can be detected to determine // we're in the child. return error.ExecFailedInChild; } diff --git a/src/Surface.zig b/src/Surface.zig index 390adf91b..a4a8d46df 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -270,6 +270,7 @@ const DerivedConfig = struct { title: ?[:0]const u8, title_report: bool, links: []Link, + link_previews: configpkg.LinkPreviews, const Link = struct { regex: oni.Regex, @@ -336,6 +337,7 @@ const DerivedConfig = struct { .title = config.title, .title_report = config.@"title-report", .links = links, + .link_previews = config.@"link-previews", // Assignments happen sequentially so we have to do this last // so that the memory is captured from allocs above. @@ -1002,10 +1004,11 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void { if (info.runtime_ms <= self.config.abnormal_command_exit_runtime_ms) runtime: { // On macOS, our exit code detection doesn't work, possibly // because of our `login` wrapper. More investigation required. - if (comptime builtin.target.os.tag.isDarwin()) break :runtime; + if (comptime !builtin.target.os.tag.isDarwin()) { + // If the exit code is 0 then it was a good exit. + if (info.exit_code == 0) break :runtime; + } - // If the exit code is 0 then we it was a good exit. - if (info.exit_code == 0) break :runtime; log.warn("abnormal process exit detected, showing error message", .{}); // Update our terminal to note the abnormal exit. In the future we @@ -1033,6 +1036,12 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void { t.printString("Process exited. Press any key to close the terminal.") catch break :terminal; t.modes.set(.cursor_visible, false); + + // We also want to ensure that normal keyboard encoding is on + // so that we can close the terminal. We close the terminal on + // any key press that encodes a character. + t.modes.set(.disable_keyboard, false); + t.screen.kitty_keyboard.set(.set, .{}); } // Waiting after command we stop here. The terminal is updated, our @@ -1235,7 +1244,7 @@ fn mouseRefreshLinks( // Get our link at the current position. This returns null if there // isn't a link OR if we shouldn't be showing links for some reason // (see further comments for cases). - const link_: ?apprt.action.MouseOverLink = link: { + const link_: ?apprt.action.MouseOverLink, const preview: bool = link: { // If we clicked and our mouse moved cells then we never // highlight links until the mouse is unclicked. This follows // standard macOS and Linux behavior where a click and drag cancels @@ -1250,18 +1259,21 @@ fn mouseRefreshLinks( if (!click_pt.coord().eql(pos_vp)) { log.debug("mouse moved while left click held, ignoring link hover", .{}); - break :link null; + break :link .{ null, false }; } } - const link = (try self.linkAtPos(pos)) orelse break :link null; + const link = (try self.linkAtPos(pos)) orelse break :link .{ null, false }; switch (link[0]) { .open => { const str = try self.io.terminal.screen.selectionString(alloc, .{ .sel = link[1], .trim = false, }); - break :link .{ .url = str }; + break :link .{ + .{ .url = str }, + self.config.link_previews == .true, + }; }, ._open_osc8 => { @@ -1269,9 +1281,14 @@ fn mouseRefreshLinks( const pin = link[1].start(); const uri = self.osc8URI(pin) orelse { log.warn("failed to get URI for OSC8 hyperlink", .{}); - break :link null; + break :link .{ null, false }; + }; + break :link .{ + .{ + .url = uri, + }, + self.config.link_previews != .false, }; - break :link .{ .url = uri }; }, } }; @@ -1287,11 +1304,15 @@ fn mouseRefreshLinks( .mouse_shape, .pointer, ); - _ = try self.rt_app.performAction( - .{ .surface = self }, - .mouse_over_link, - link, - ); + + if (preview) { + _ = try self.rt_app.performAction( + .{ .surface = self }, + .mouse_over_link, + link, + ); + } + try self.queueRender(); return; } @@ -2128,14 +2149,6 @@ pub fn keyCallback( if (self.io.terminal.modes.get(.disable_keyboard)) return .consumed; } - // If our process is exited and we press a key then we close the - // surface. We may want to eventually move this to the apprt rather - // than in core. - if (self.child_exited and event.action == .press) { - self.close(); - return .closed; - } - // If this input event has text, then we hide the mouse if configured. // We only do this on pressed events to avoid hiding the mouse when we // change focus due to a keybinding (i.e. switching tabs). @@ -2230,6 +2243,14 @@ pub fn keyCallback( event, if (insp_ev) |*ev| ev else null, )) |write_req| { + // If our process is exited and we press a key that results in + // an encoded value, we close the surface. We want to eventually + // move this behavior to the apprt probably. + if (self.child_exited) { + self.close(); + return .closed; + } + errdefer write_req.deinit(); self.io.queueMessage(switch (write_req) { .small => |v| .{ .write_small = v }, @@ -3703,7 +3724,7 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool { .trim = false, }); defer self.alloc.free(str); - try internal_os.open(self.alloc, .unknown, str); + try self.openUrl(.{ .kind = .unknown, .url = str }); }, ._open_osc8 => { @@ -3711,13 +3732,35 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool { log.warn("failed to get URI for OSC8 hyperlink", .{}); return false; }; - try internal_os.open(self.alloc, .unknown, uri); + try self.openUrl(.{ .kind = .unknown, .url = uri }); }, } return true; } +fn openUrl( + self: *Surface, + action: apprt.action.OpenUrl, +) !void { + // If the apprt handles it then we're done. + if (try self.rt_app.performAction( + .{ .surface = self }, + .open_url, + action, + )) return; + + // apprt didn't handle it, fallback to our simple cross-platform + // URL opener. We log a warning because we want well-behaved + // apprts to handle this themselves. + log.warn("apprt did not handle open URL action, falling back to default opener", .{}); + try internal_os.open( + self.alloc, + action.kind, + action.url, + ); +} + /// Return the URI for an OSC8 hyperlink at the given position or null /// if there is no hyperlink. fn osc8URI(self: *Surface, pin: terminal.Pin) ?[]const u8 { @@ -4436,6 +4479,18 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool return false; }, + .copy_title_to_clipboard => { + const title = self.rt_surface.getTitle() orelse return false; + if (title.len == 0) return false; + + self.rt_surface.setClipboardString(title, .standard, false) catch |err| { + log.err("error copying title to clipboard err={}", .{err}); + return true; + }; + + return true; + }, + .paste_from_clipboard => try self.startClipboardRequest( .standard, .{ .paste = {} }, @@ -4477,6 +4532,14 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool try self.setFontSize(size); }, + .set_font_size => |points| { + log.debug("set font size={d}", .{points}); + + var size = self.font_size; + size.points = std.math.clamp(points, 1.0, 255.0); + try self.setFontSize(size); + }, + .prompt_surface_title => return try self.rt_app.performAction( .{ .surface = self }, .prompt_title, @@ -4916,7 +4979,7 @@ fn writeScreenFile( defer self.alloc.free(pathZ); try self.rt_surface.setClipboardString(pathZ, .standard, false); }, - .open => try internal_os.open(self.alloc, .text, path), + .open => try self.openUrl(.{ .kind = .text, .url = path }), .paste => self.io.queueMessage(try termio.Message.writeReq( self.alloc, path, diff --git a/src/apprt.zig b/src/apprt.zig index dd726b3f2..cb542875e 100644 --- a/src/apprt.zig +++ b/src/apprt.zig @@ -3,7 +3,7 @@ //! getting user input (mouse/keyboard), etc. //! //! This enables compile-time interfaces to be built to swap out the underlying -//! application runtime. For example: glfw, pure macOS Cocoa, GTK+, browser, etc. +//! application runtime. For example: pure macOS Cocoa, GTK+, browser, etc. //! //! The goal is to have different implementations share as much of the core //! logic as possible, and to only reach out to platform-specific implementation @@ -15,7 +15,6 @@ const build_config = @import("build_config.zig"); const structs = @import("apprt/structs.zig"); pub const action = @import("apprt/action.zig"); -pub const glfw = @import("apprt/glfw.zig"); pub const gtk = @import("apprt/gtk.zig"); pub const none = @import("apprt/none.zig"); pub const browser = @import("apprt/browser.zig"); @@ -42,7 +41,6 @@ pub const SurfaceSize = structs.SurfaceSize; pub const runtime = switch (build_config.artifact) { .exe => switch (build_config.app_runtime) { .none => none, - .glfw => glfw, .gtk => gtk, }, .lib => embedded, @@ -53,18 +51,12 @@ pub const App = runtime.App; pub const Surface = runtime.Surface; /// Runtime is the runtime to use for Ghostty. All runtimes do not provide -/// equivalent feature sets. For example, GTK offers tabbing and more features -/// that glfw does not provide. However, glfw may require many less -/// dependencies. +/// equivalent feature sets. pub const Runtime = enum { /// Will not produce an executable at all when `zig build` is called. /// This is only useful if you're only interested in the lib only (macOS). none, - /// Glfw-backed. Very simple. Glfw is statically linked. Tabbing and - /// other rich windowing features are not supported. - glfw, - /// GTK-backed. Rich windowed application. GTK is dynamically linked. gtk, @@ -72,12 +64,8 @@ pub const Runtime = enum { // The Linux default is GTK because it is full featured. if (target.os.tag == .linux) return .gtk; - // Windows we currently only support glfw - if (target.os.tag == .windows) return .glfw; - - // Otherwise, we do NONE so we don't create an exe. The GLFW - // build is opt-in because it is missing so many features compared - // to the other builds that are impossible due to the GLFW interface. + // Otherwise, we do NONE so we don't create an exe and we + // create libghostty. return .none; } }; diff --git a/src/apprt/action.zig b/src/apprt/action.zig index b4c5164c2..1c3c7c72c 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -267,6 +267,11 @@ pub const Action = union(Key) { check_for_updates, + /// Open a URL using the native OS mechanisms. On macOS this might be `open` + /// or on Linux this might be `xdg-open`. The exact mechanism is up to the + /// apprt. + open_url: OpenUrl, + /// Sync with: ghostty_action_tag_e pub const Key = enum(c_int) { quit, @@ -317,6 +322,7 @@ pub const Action = union(Key) { undo, redo, check_for_updates, + open_url, }; /// Sync with: ghostty_action_u @@ -357,7 +363,11 @@ pub const Action = union(Key) { // For ABI compatibility, we expect that this is our union size. // At the time of writing, we don't promise ABI compatibility // so we can change this but I want to be aware of it. - assert(@sizeOf(CValue) == 16); + assert(@sizeOf(CValue) == switch (@sizeOf(usize)) { + 4 => 16, + 8 => 24, + else => unreachable, + }); } /// Returns the value type for the given key. @@ -614,3 +624,44 @@ pub const ConfigChange = struct { }; } }; + +/// Open a URL +pub const OpenUrl = struct { + /// The type of data that the URL refers to. + kind: Kind, + + /// The URL. + url: []const u8, + + /// The type of the data at the URL to open. This is used as a hint to + /// potentially open the URL in a different way. + /// + /// Sync with: ghostty_action_open_url_kind_e + pub const Kind = enum(c_int) { + /// The type is unknown. This is the default and apprts should + /// open the URL in the most generic way possible. For example, + /// on macOS this would be the equivalent of `open` or on Linux + /// this would be `xdg-open`. + unknown, + + /// The URL is known to be a text file. In this case, the apprt + /// should try to open the URL in a text editor or viewer or + /// some equivalent, if possible. + text, + }; + + // Sync with: ghostty_action_open_url_s + pub const C = extern struct { + kind: Kind, + url: [*]const u8, + len: usize, + }; + + pub fn cval(self: OpenUrl) C { + return .{ + .kind = self.kind, + .url = self.url.ptr, + .len = self.url.len, + }; + } +}; diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index dec1e4135..30a2d9ff6 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -236,7 +236,7 @@ pub const App = struct { var surface = try self.core_app.alloc.create(Surface); errdefer self.core_app.alloc.destroy(surface); - // Create the surface -- because windows are surfaces for glfw. + // Create the surface try surface.init(self, opts); errdefer surface.deinit(); @@ -884,7 +884,7 @@ pub const Surface = struct { } // Remove this so that running `ghostty` within Ghostty works. - env.remove("GHOSTTY_MAC_APP"); + env.remove("GHOSTTY_MAC_LAUNCH_SOURCE"); // If we were launched from the desktop then we want to // remove the LANGUAGE env var so that we don't inherit diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig deleted file mode 100644 index b82771d75..000000000 --- a/src/apprt/glfw.zig +++ /dev/null @@ -1,1266 +0,0 @@ -//! Application runtime implementation that uses GLFW (https://www.glfw.org/). -//! -//! This works on macOS and Linux with OpenGL and Metal. -//! (The above sentence may be out of date). - -const std = @import("std"); -const builtin = @import("builtin"); -const build_config = @import("../build_config.zig"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; -const glfw = @import("glfw"); -const macos = @import("macos"); -const objc = @import("objc"); -const cli = @import("../cli.zig"); -const input = @import("../input.zig"); -const internal_os = @import("../os/main.zig"); -const renderer = @import("../renderer.zig"); -const terminal = @import("../terminal/main.zig"); -const Renderer = renderer.Renderer; -const apprt = @import("../apprt.zig"); -const CoreApp = @import("../App.zig"); -const CoreSurface = @import("../Surface.zig"); -const configpkg = @import("../config.zig"); -const Config = @import("../config.zig").Config; - -// Get native API access on certain platforms so we can do more customization. -const glfwNative = glfw.Native(.{ - .cocoa = builtin.target.os.tag.isDarwin(), - .x11 = builtin.os.tag == .linux, -}); - -/// True if darwin-specific logic is enabled -const darwin_enabled = builtin.target.os.tag.isDarwin() and - build_config.artifact == .exe; - -const log = std.log.scoped(.glfw); - -pub const resourcesDir = internal_os.resourcesDir; - -pub const App = struct { - app: *CoreApp, - config: Config, - - /// Flips to true to quit on the next event loop tick. This - /// never goes false and forces the event loop to exit. - quit: bool = false, - - /// Mac-specific state. - darwin: if (darwin_enabled) Darwin else void, - - pub const Options = struct {}; - - pub fn init(self: *App, core_app: *CoreApp, _: Options) !void { - if (comptime builtin.target.os.tag.isDarwin()) { - log.warn("WARNING WARNING WARNING: GLFW ON MAC HAS BUGS.", .{}); - log.warn("You should use the AppKit-based app instead. The official download", .{}); - log.warn("is properly built and available from GitHub. If you're building from", .{}); - log.warn("source, see the README for details on how to build the AppKit app.", .{}); - } - - if (!glfw.init(.{})) { - if (glfw.getError()) |err| { - log.err("error initializing GLFW err={} msg={s}", .{ - err.error_code, - err.description, - }); - return err.error_code; - } - - return error.GlfwInitFailedUnknownReason; - } - glfw.setErrorCallback(glfwErrorCallback); - - // Mac-specific state. For example, on Mac we enable window tabbing. - var darwin = if (darwin_enabled) try Darwin.init() else {}; - errdefer if (darwin_enabled) darwin.deinit(); - - // Load our configuration - var config = try Config.load(core_app.alloc); - errdefer config.deinit(); - - // If we had configuration errors, then log them. - if (!config._diagnostics.empty()) { - var buf = std.ArrayList(u8).init(core_app.alloc); - defer buf.deinit(); - for (config._diagnostics.items()) |diag| { - try diag.write(buf.writer()); - log.warn("configuration error: {s}", .{buf.items}); - buf.clearRetainingCapacity(); - } - - // If we have any CLI errors, exit. - if (config._diagnostics.containsLocation(.cli)) { - log.warn("CLI errors detected, exiting", .{}); - _ = core_app.mailbox.push(.{ - .quit = {}, - }, .{ .forever = {} }); - } - } - - // Queue a single new window that starts on launch - // Note: above we may send a quit so this may never happen - _ = core_app.mailbox.push(.{ - .new_window = .{}, - }, .{ .forever = {} }); - - // We want the event loop to wake up instantly so we can process our tick. - glfw.postEmptyEvent(); - - self.* = .{ - .app = core_app, - .config = config, - .darwin = darwin, - }; - } - - pub fn terminate(self: *App) void { - self.config.deinit(); - glfw.terminate(); - } - - /// Run the event loop. This doesn't return until the app exits. - pub fn run(self: *App) !void { - while (true) { - // Wait for any events from the app event loop. wakeup will post - // an empty event so that this will return. - // - // Warning: a known issue on macOS is that this will block while - // a resize event is actively happening, which will prevent the - // app tick from happening. I don't know know a way around this - // but its not a big deal since we don't use glfw for the official - // mac app, but noting it in case anyone builds for macos using - // glfw. - glfw.waitEvents(); - - // Tick the terminal app - try self.app.tick(self); - - // If the tick caused us to quit, then we're done. - if (self.quit or self.app.surfaces.items.len == 0) { - for (self.app.surfaces.items) |surface| { - surface.close(false); - } - - return; - } - } - } - - /// Wakeup the event loop. This should be able to be called from any thread. - pub fn wakeup(self: *const App) void { - _ = self; - glfw.postEmptyEvent(); - } - - /// Perform a given action. Returns `true` if the action was able to be - /// performed, `false` otherwise. - pub fn performAction( - self: *App, - target: apprt.Target, - comptime action: apprt.Action.Key, - value: apprt.Action.Value(action), - ) !bool { - switch (action) { - .quit => self.quit = true, - - .new_window => _ = try self.newSurface(switch (target) { - .app => null, - .surface => |v| v, - }), - - .new_tab => try self.newTab(switch (target) { - .app => null, - .surface => |v| v, - }), - - .size_limit => switch (target) { - .app => {}, - .surface => |surface| try surface.rt_surface.setSizeLimits(.{ - .width = value.min_width, - .height = value.min_height, - }, if (value.max_width > 0) .{ - .width = value.max_width, - .height = value.max_height, - } else null), - }, - - .initial_size => switch (target) { - .app => {}, - .surface => |surface| try surface.rt_surface.setInitialWindowSize( - value.width, - value.height, - ), - }, - - .toggle_fullscreen => self.toggleFullscreen(target), - - .open_config => try configpkg.edit.open(self.app.alloc), - - .set_title => switch (target) { - .app => {}, - .surface => |surface| try surface.rt_surface.setTitle(value.title), - }, - - .mouse_shape => switch (target) { - .app => {}, - .surface => |surface| try surface.rt_surface.setMouseShape(value), - }, - - .mouse_visibility => switch (target) { - .app => {}, - .surface => |surface| surface.rt_surface.setMouseVisibility(switch (value) { - .visible => true, - .hidden => false, - }), - }, - - .reload_config => try self.reloadConfig(target, value), - - // Unimplemented - .new_split, - .goto_split, - .resize_split, - .equalize_splits, - .toggle_split_zoom, - .present_terminal, - .close_all_windows, - .close_window, - .close_tab, - .toggle_tab_overview, - .toggle_window_decorations, - .toggle_quick_terminal, - .toggle_command_palette, - .toggle_visibility, - .goto_tab, - .move_tab, - .inspector, - .render_inspector, - .quit_timer, - .float_window, - .secure_input, - .key_sequence, - .desktop_notification, - .mouse_over_link, - .cell_size, - .renderer_health, - .color_change, - .pwd, - .config_change, - .toggle_maximize, - .prompt_title, - .reset_window_size, - .ring_bell, - .check_for_updates, - .undo, - .redo, - .show_gtk_inspector, - => { - log.info("unimplemented action={}", .{action}); - return false; - }, - } - - return true; - } - - /// Reload the configuration. This should return the new configuration. - /// The old value can be freed immediately at this point assuming a - /// successful return. - /// - /// The returned pointer value is only valid for a stable self pointer. - fn reloadConfig( - self: *App, - target: apprt.action.Target, - opts: apprt.action.ReloadConfig, - ) !void { - if (opts.soft) { - switch (target) { - .app => try self.app.updateConfig(self, &self.config), - .surface => |core_surface| try core_surface.updateConfig( - &self.config, - ), - } - return; - } - - // Load our configuration - var config = try Config.load(self.app.alloc); - errdefer config.deinit(); - - // Call into our app to update - switch (target) { - .app => try self.app.updateConfig(self, &config), - .surface => |core_surface| try core_surface.updateConfig(&config), - } - - // Update the existing config, be sure to clean up the old one. - self.config.deinit(); - self.config = config; - } - - /// Toggle the window to fullscreen mode. - fn toggleFullscreen(self: *App, target: apprt.Target) void { - _ = self; - const surface: *Surface = switch (target) { - .app => return, - .surface => |v| v.rt_surface, - }; - const win = surface.window; - - if (surface.isFullscreen()) { - win.setMonitor( - null, - @intCast(surface.monitor_dims.position_x), - @intCast(surface.monitor_dims.position_y), - surface.monitor_dims.width, - surface.monitor_dims.height, - 0, - ); - return; - } - - const monitor = win.getMonitor() orelse monitor: { - log.warn("window had null monitor, getting primary monitor", .{}); - break :monitor glfw.Monitor.getPrimary() orelse { - log.warn("window could not get any monitor. will not perform action", .{}); - return; - }; - }; - - const video_mode = monitor.getVideoMode() orelse { - log.warn("failed to get video mode. will not perform action", .{}); - return; - }; - - const position = win.getPos(); - const size = surface.getSize() catch { - log.warn("failed to get window size. will not perform fullscreen action", .{}); - return; - }; - - surface.monitor_dims = .{ - .width = size.width, - .height = size.height, - .position_x = position.x, - .position_y = position.y, - }; - - win.setMonitor(monitor, 0, 0, video_mode.getWidth(), video_mode.getHeight(), 0); - } - - /// Create a new tab in the parent surface. - fn newTab(self: *App, parent_: ?*CoreSurface) !void { - if (comptime !darwin_enabled) { - log.warn("tabbing is not supported on this platform", .{}); - return; - } - - const parent = parent_ orelse { - _ = try self.newSurface(null); - return; - }; - - // Create the new window - const window = try self.newSurface(parent); - - // Add the new window the parent window - const parent_win = glfwNative.getCocoaWindow(parent.rt_surface.window).?; - const other_win = glfwNative.getCocoaWindow(window.window).?; - const NSWindowOrderingMode = enum(isize) { below = -1, out = 0, above = 1 }; - const nswindow = objc.Object.fromId(parent_win); - nswindow.msgSend(void, objc.sel("addTabbedWindow:ordered:"), .{ - objc.Object.fromId(other_win), - NSWindowOrderingMode.above, - }); - - // Adding a new tab can cause the tab bar to appear which changes - // our viewport size. We need to call the size callback in order to - // update values. For example, we need this to set the proper mouse selection - // point in the grid. - const size = parent.rt_surface.getSize() catch |err| { - log.err("error querying window size for size callback on new tab err={}", .{err}); - return; - }; - parent.sizeCallback(size) catch |err| { - log.err("error in size callback from new tab err={}", .{err}); - return; - }; - } - - fn newSurface(self: *App, parent_: ?*CoreSurface) !*Surface { - // Grab a surface allocation because we're going to need it. - var surface = try self.app.alloc.create(Surface); - errdefer self.app.alloc.destroy(surface); - - // Create the surface -- because windows are surfaces for glfw. - try surface.init(self); - errdefer surface.deinit(); - - // If we have a parent, inherit some properties - if (self.config.@"window-inherit-font-size") { - if (parent_) |parent| { - try surface.core_surface.setFontSize(parent.font_size); - } - } - - return surface; - } - - /// Close the given surface. - pub fn closeSurface(self: *App, surface: *Surface) void { - surface.deinit(); - self.app.alloc.destroy(surface); - } - - pub fn redrawSurface(self: *App, surface: *Surface) void { - _ = self; - _ = surface; - - @panic("This should never be called for GLFW."); - } - - pub fn redrawInspector(self: *App, surface: *Surface) void { - _ = self; - _ = surface; - - // GLFW doesn't support the inspector - } - - fn glfwErrorCallback(code: glfw.ErrorCode, desc: [:0]const u8) void { - std.log.warn("glfw error={} message={s}", .{ code, desc }); - - // Workaround for: https://github.com/ocornut/imgui/issues/5908 - // If we get an invalid value with "scancode" in the message we assume - // it is from the glfw key callback that imgui sets and we clear the - // error so that our future code doesn't crash. - if (code == glfw.ErrorCode.InvalidValue and - std.mem.indexOf(u8, desc, "scancode") != null) - { - _ = glfw.getError(); - } - } - - pub fn keyboardLayout(self: *const App) input.KeyboardLayout { - _ = self; - - // Not supported by glfw - return .unknown; - } - - /// Mac-specific settings. This is only enabled when the target is - /// Mac and the artifact is a standalone exe. We don't target libs because - /// the embedded API doesn't do windowing. - const Darwin = struct { - tabbing_id: *macos.foundation.String, - - pub fn init() !Darwin { - const NSWindow = objc.getClass("NSWindow").?; - NSWindow.msgSend(void, objc.sel("setAllowsAutomaticWindowTabbing:"), .{true}); - - // Our tabbing ID allows all of our windows to group together - const tabbing_id = try macos.foundation.String.createWithBytes( - "com.mitchellh.ghostty.window", - .utf8, - false, - ); - errdefer tabbing_id.release(); - - // Setup our Mac settings - return .{ .tabbing_id = tabbing_id }; - } - - pub fn deinit(self: *Darwin) void { - self.tabbing_id.release(); - self.* = undefined; - } - }; -}; - -/// These are used to keep track of the original monitor values so that we can -/// safely toggle on and off of fullscreen. -const MonitorDimensions = struct { - width: u32, - height: u32, - position_x: i64, - position_y: i64, -}; - -/// Surface represents the drawable surface for glfw. In glfw, a surface -/// is always a window because that is the only abstraction that glfw exposes. -/// -/// This means that there is no way for the glfw runtime to support tabs, -/// splits, etc. without considerable effort. In fact, on Darwin, we do -/// support tabs because the minimal tabbing interface is a window abstraction, -/// but this is a bit of a hack. The native Swift runtime should be used instead -/// which uses real native tabbing. -/// -/// Other runtimes a surface usually represents the equivalent of a "view" -/// or "widget" level granularity. -pub const Surface = struct { - /// The glfw window handle - window: glfw.Window, - - /// The glfw mouse cursor handle. - cursor: ?glfw.Cursor, - - /// The app we're part of - app: *App, - - /// A core surface - core_surface: CoreSurface, - - /// This is the key event that was processed in keyCallback. This is only - /// non-null if the event was NOT consumed in keyCallback. This lets us - /// know in charCallback whether we should populate it and call it again. - /// (GLFW guarantees that charCallback is called after keyCallback). - key_event: ?input.KeyEvent = null, - - /// The monitor dimensions so we can toggle fullscreen on and off. - monitor_dims: MonitorDimensions, - - /// Save the title text so that we can return it later when requested. - /// This is allocated from the heap so it must be freed when we deinit the - /// surface. - title_text: ?[:0]const u8 = null, - - pub const Options = struct {}; - - /// Initialize the surface into the given self pointer. This gives a - /// stable pointer to the destination that can be used for callbacks. - pub fn init(self: *Surface, app: *App) !void { - // Create our window - const win = glfw.Window.create( - 640, - 480, - "ghostty", - if (app.config.fullscreen) glfw.Monitor.getPrimary() else null, - null, - Renderer.glfwWindowHints(&app.config), - ) orelse return glfw.mustGetErrorCode(); - errdefer win.destroy(); - - // Setup our - setInitialWindowPosition( - win, - app.config.@"window-position-x", - app.config.@"window-position-y", - ); - - // Get our physical DPI - debug only because we don't have a use for - // this but the logging of it may be useful - if (builtin.mode == .Debug) { - const monitor = win.getMonitor() orelse monitor: { - log.warn("window had null monitor, getting primary monitor", .{}); - break :monitor glfw.Monitor.getPrimary().?; - }; - const video_mode = monitor.getVideoMode() orelse return glfw.mustGetErrorCode(); - const physical_size = monitor.getPhysicalSize(); - const physical_x_dpi = @as(f32, @floatFromInt(video_mode.getWidth())) / (@as(f32, @floatFromInt(physical_size.width_mm)) / 25.4); - const physical_y_dpi = @as(f32, @floatFromInt(video_mode.getHeight())) / (@as(f32, @floatFromInt(physical_size.height_mm)) / 25.4); - log.debug("physical dpi x={} y={}", .{ - physical_x_dpi, - physical_y_dpi, - }); - } - - // On Mac, enable window tabbing - if (comptime darwin_enabled) { - const NSWindowTabbingMode = enum(usize) { automatic = 0, preferred = 1, disallowed = 2 }; - const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(win).?); - - // Tabbing mode enables tabbing at all - nswindow.setProperty("tabbingMode", NSWindowTabbingMode.automatic); - - // All windows within a tab bar must have a matching tabbing ID. - // The app sets this up for us. - nswindow.setProperty("tabbingIdentifier", app.darwin.tabbing_id); - } - - // Set our callbacks - win.setUserPointer(&self.core_surface); - win.setSizeCallback(sizeCallback); - win.setCharCallback(charCallback); - win.setKeyCallback(keyCallback); - win.setFocusCallback(focusCallback); - win.setRefreshCallback(refreshCallback); - win.setScrollCallback(scrollCallback); - win.setCursorPosCallback(cursorPosCallback); - win.setMouseButtonCallback(mouseButtonCallback); - win.setDropCallback(dropCallback); - - const dimensions: MonitorDimensions = dimensions: { - const pos = win.getPos(); - const size = win.getFramebufferSize(); - break :dimensions .{ - .width = size.width, - .height = size.height, - .position_x = pos.x, - .position_y = pos.y, - }; - }; - - // Build our result - self.* = .{ - .app = app, - .window = win, - .cursor = null, - .core_surface = undefined, - .monitor_dims = dimensions, - }; - errdefer self.* = undefined; - - // Initialize our cursor - try self.setMouseShape(.text); - - // Add ourselves to the list of surfaces on the app. - try app.app.addSurface(self); - errdefer app.app.deleteSurface(self); - - // Get our new surface config - var config = try apprt.surface.newConfig(app.app, &app.config); - defer config.deinit(); - - // Initialize our surface now that we have the stable pointer. - try self.core_surface.init( - app.app.alloc, - &config, - app.app, - app, - self, - ); - errdefer self.core_surface.deinit(); - } - - pub fn deinit(self: *Surface) void { - if (self.title_text) |t| self.core_surface.alloc.free(t); - - // Remove ourselves from the list of known surfaces in the app. - self.app.app.deleteSurface(self); - - // Clean up our core surface so that all the rendering and IO stop. - self.core_surface.deinit(); - - if (comptime darwin_enabled) { - const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(self.window).?); - const tabgroup = nswindow.getProperty(objc.Object, "tabGroup"); - const windows = tabgroup.getProperty(objc.Object, "windows"); - switch (windows.getProperty(usize, "count")) { - // If we're going down to one window our tab bar is going to be - // destroyed so unset it so that the later logic doesn't try to - // use it. - 1 => {}, - - // If our tab bar is visible and we are going down to 1 window, - // hide the tab bar. The check is "2" because our current window - // is still present. - 2 => if (tabgroup.getProperty(bool, "tabBarVisible")) { - nswindow.msgSend(void, objc.sel("toggleTabBar:"), .{nswindow.value}); - }, - - else => {}, - } - } - - // We can now safely destroy our windows. We have to do this BEFORE - // setting up the new focused window below. - self.window.destroy(); - if (self.cursor) |c| { - c.destroy(); - self.cursor = null; - } - } - - /// Checks if the glfw window is in fullscreen. - pub fn isFullscreen(self: *Surface) bool { - return self.window.getMonitor() != null; - } - - /// Close this surface. - pub fn close(self: *Surface, processActive: bool) void { - _ = processActive; - self.setShouldClose(); - self.deinit(); - self.app.app.alloc.destroy(self); - } - - /// Set the initial window size. This is called exactly once at - /// surface initialization time. This may be called before "self" - /// is fully initialized. - fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void { - const monitor = self.window.getMonitor() orelse glfw.Monitor.getPrimary() orelse { - log.warn("window is not on a monitor, not setting initial size", .{}); - return; - }; - - const workarea = monitor.getWorkarea(); - self.window.setSize(.{ - .width = @min(width, workarea.width), - .height = @min(height, workarea.height), - }); - } - - /// Set the initial window position. This is called exactly once at - /// surface initialization time. This may be called before "self" - /// is fully initialized. - fn setInitialWindowPosition(win: glfw.Window, x: ?i16, y: ?i16) void { - const start_position_x = x orelse return; - const start_position_y = y orelse return; - - log.debug("setting initial window position ({},{})", .{ start_position_x, start_position_y }); - win.setPos(.{ .x = start_position_x, .y = start_position_y }); - } - - /// Set the size limits of the window. - /// Note: this interface is not good, we should redo it if we plan - /// to use this more. i.e. you can't set max width but no max height, - /// or no mins. - fn setSizeLimits(self: *Surface, min: apprt.SurfaceSize, max_: ?apprt.SurfaceSize) !void { - self.window.setSizeLimits(.{ - .width = min.width, - .height = min.height, - }, if (max_) |max| .{ - .width = max.width, - .height = max.height, - } else .{ - .width = null, - .height = null, - }); - } - - /// Returns the content scale for the created window. - pub fn getContentScale(self: *const Surface) !apprt.ContentScale { - const scale = self.window.getContentScale(); - return apprt.ContentScale{ .x = scale.x_scale, .y = scale.y_scale }; - } - - /// Returns the size of the window in pixels. The pixel size may - /// not match screen coordinate size but we should be able to convert - /// back and forth using getContentScale. - pub fn getSize(self: *const Surface) !apprt.SurfaceSize { - const size = self.window.getFramebufferSize(); - return apprt.SurfaceSize{ .width = size.width, .height = size.height }; - } - - /// Returns the cursor position in scaled pixels relative to the - /// upper-left of the window. - pub fn getCursorPos(self: *const Surface) !apprt.CursorPos { - const unscaled_pos = self.window.getCursorPos(); - const pos = try self.cursorPosToPixels(unscaled_pos); - return apprt.CursorPos{ - .x = @floatCast(pos.xpos), - .y = @floatCast(pos.ypos), - }; - } - - /// Set the flag that notes this window should be closed for the next - /// iteration of the event loop. - pub fn setShouldClose(self: *Surface) void { - self.window.setShouldClose(true); - } - - /// Returns true if the window is flagged to close. - pub fn shouldClose(self: *const Surface) bool { - return self.window.shouldClose(); - } - - /// Set the title of the window. - fn setTitle(self: *Surface, slice: [:0]const u8) !void { - if (self.title_text) |t| self.core_surface.alloc.free(t); - self.title_text = try self.core_surface.alloc.dupeZ(u8, slice); - self.window.setTitle(self.title_text.?.ptr); - } - - /// Return the title of the window. - pub fn getTitle(self: *Surface) ?[:0]const u8 { - return self.title_text; - } - - /// Set the shape of the cursor. - fn setMouseShape(self: *Surface, shape: terminal.MouseShape) !void { - if ((comptime builtin.target.os.tag.isDarwin()) and - !internal_os.macos.isAtLeastVersion(13, 0, 0)) - { - // We only set our cursor if we're NOT on Mac, or if we are then the - // macOS version is >= 13 (Ventura). On prior versions, glfw crashes - // since we use a tab group. - return; - } - - const new = glfw.Cursor.createStandard(switch (shape) { - .default => .arrow, - .text => .ibeam, - .crosshair => .crosshair, - .pointer => .pointing_hand, - .ew_resize => .resize_ew, - .ns_resize => .resize_ns, - .nwse_resize => .resize_nwse, - .nesw_resize => .resize_nesw, - .all_scroll => .resize_all, - .not_allowed => .not_allowed, - else => return, // unsupported, ignore - }) orelse { - const err = glfw.mustGetErrorCode(); - log.warn("error creating cursor: {}", .{err}); - return; - }; - errdefer new.destroy(); - - // Set our cursor before we destroy the old one - self.window.setCursor(new); - - if (self.cursor) |c| c.destroy(); - self.cursor = new; - } - - /// Set the visibility of the mouse cursor. - fn setMouseVisibility(self: *Surface, visible: bool) void { - self.window.setInputModeCursor(if (visible) .normal else .hidden); - } - - pub fn supportsClipboard( - self: *const Surface, - clipboard_type: apprt.Clipboard, - ) bool { - _ = self; - return switch (clipboard_type) { - .standard => true, - .selection, .primary => comptime builtin.os.tag == .linux, - }; - } - - /// Start an async clipboard request. - pub fn clipboardRequest( - self: *Surface, - clipboard_type: apprt.Clipboard, - state: apprt.ClipboardRequest, - ) !void { - // GLFW can read clipboards immediately so just do that. - const str: [:0]const u8 = switch (clipboard_type) { - .standard => glfw.getClipboardString() orelse return glfw.mustGetErrorCode(), - .selection, .primary => selection: { - // Not supported except on Linux - if (comptime builtin.os.tag != .linux) break :selection ""; - - const raw = glfwNative.getX11SelectionString() orelse - return glfw.mustGetErrorCode(); - break :selection std.mem.span(raw); - }, - }; - - // Complete our request. We always allow unsafe because we don't - // want to deal with user confirmation in this runtime. - try self.core_surface.completeClipboardRequest(state, str, true); - } - - /// Set the clipboard. - pub fn setClipboardString( - self: *const Surface, - val: [:0]const u8, - clipboard_type: apprt.Clipboard, - confirm: bool, - ) !void { - _ = confirm; - _ = self; - switch (clipboard_type) { - .standard => glfw.setClipboardString(val), - .selection, .primary => { - // Not supported except on Linux - if (comptime builtin.os.tag != .linux) return; - glfwNative.setX11SelectionString(val.ptr); - }, - } - } - - /// The cursor position from glfw directly is in screen coordinates but - /// all our interface works in pixels. - fn cursorPosToPixels(self: *const Surface, pos: glfw.Window.CursorPos) !glfw.Window.CursorPos { - // The cursor position is in screen coordinates but we - // want it in pixels. we need to get both the size of the - // window in both to get the ratio to make the conversion. - const size = self.window.getSize(); - const fb_size = self.window.getFramebufferSize(); - - // If our framebuffer and screen are the same, then there is no scaling - // happening and we can short-circuit by returning the pos as-is. - if (fb_size.width == size.width and fb_size.height == size.height) - return pos; - - const x_scale = @as(f64, @floatFromInt(fb_size.width)) / @as(f64, @floatFromInt(size.width)); - const y_scale = @as(f64, @floatFromInt(fb_size.height)) / @as(f64, @floatFromInt(size.height)); - return .{ - .xpos = pos.xpos * x_scale, - .ypos = pos.ypos * y_scale, - }; - } - - pub fn defaultTermioEnv(self: *Surface) !std.process.EnvMap { - return try internal_os.getEnvMap(self.app.app.alloc); - } - - fn sizeCallback(window: glfw.Window, width: i32, height: i32) void { - _ = width; - _ = height; - - // Get the size. We are given a width/height but this is in screen - // coordinates and we want raw pixels. The core window uses the content - // scale to scale appropriately. - const core_win = window.getUserPointer(CoreSurface) orelse return; - const size = core_win.rt_surface.getSize() catch |err| { - log.err("error querying window size for size callback err={}", .{err}); - return; - }; - - // Call the primary callback. - core_win.sizeCallback(size) catch |err| { - log.err("error in size callback err={}", .{err}); - return; - }; - } - - fn charCallback(window: glfw.Window, codepoint: u21) void { - const core_win = window.getUserPointer(CoreSurface) orelse return; - - // We need a key event in order to process the charcallback. If it - // isn't set then the key event was consumed. - var key_event = core_win.rt_surface.key_event orelse return; - core_win.rt_surface.key_event = null; - - // Populate the utf8 value for the event - var buf: [4]u8 = undefined; - const len = std.unicode.utf8Encode(codepoint, &buf) catch |err| { - log.err("error encoding codepoint={} err={}", .{ codepoint, err }); - return; - }; - key_event.utf8 = buf[0..len]; - - // On macOS we need to also disable some modifiers because - // alt+key consumes the alt. - if (comptime builtin.target.os.tag.isDarwin()) { - // For GLFW, we say we always consume alt because - // GLFW doesn't have a way to disable the alt key. - key_event.consumed_mods.alt = true; - } - - _ = core_win.keyCallback(key_event) catch |err| { - log.err("error in key callback err={}", .{err}); - return; - }; - } - - fn keyCallback( - window: glfw.Window, - glfw_key: glfw.Key, - scancode: i32, - glfw_action: glfw.Action, - glfw_mods: glfw.Mods, - ) void { - _ = scancode; - - const core_win = window.getUserPointer(CoreSurface) orelse return; - - // Convert our glfw types into our input types - const mods: input.Mods = .{ - .shift = glfw_mods.shift, - .ctrl = glfw_mods.control, - .alt = glfw_mods.alt, - .super = glfw_mods.super, - }; - const action: input.Action = switch (glfw_action) { - .release => .release, - .press => .press, - .repeat => .repeat, - }; - const key: input.Key = switch (glfw_key) { - .a => .key_a, - .b => .key_b, - .c => .key_c, - .d => .key_d, - .e => .key_e, - .f => .key_f, - .g => .key_g, - .h => .key_h, - .i => .key_i, - .j => .key_j, - .k => .key_k, - .l => .key_l, - .m => .key_m, - .n => .key_n, - .o => .key_o, - .p => .key_p, - .q => .key_q, - .r => .key_r, - .s => .key_s, - .t => .key_t, - .u => .key_u, - .v => .key_v, - .w => .key_w, - .x => .key_x, - .y => .key_y, - .z => .key_z, - .zero => .digit_0, - .one => .digit_1, - .two => .digit_2, - .three => .digit_3, - .four => .digit_4, - .five => .digit_5, - .six => .digit_6, - .seven => .digit_7, - .eight => .digit_8, - .nine => .digit_9, - .up => .arrow_up, - .down => .arrow_down, - .right => .arrow_right, - .left => .arrow_left, - .home => .home, - .end => .end, - .page_up => .page_up, - .page_down => .page_down, - .escape => .escape, - .F1 => .f1, - .F2 => .f2, - .F3 => .f3, - .F4 => .f4, - .F5 => .f5, - .F6 => .f6, - .F7 => .f7, - .F8 => .f8, - .F9 => .f9, - .F10 => .f10, - .F11 => .f11, - .F12 => .f12, - .F13 => .f13, - .F14 => .f14, - .F15 => .f15, - .F16 => .f16, - .F17 => .f17, - .F18 => .f18, - .F19 => .f19, - .F20 => .f20, - .F21 => .f21, - .F22 => .f22, - .F23 => .f23, - .F24 => .f24, - .F25 => .f25, - .kp_0 => .numpad_0, - .kp_1 => .numpad_1, - .kp_2 => .numpad_2, - .kp_3 => .numpad_3, - .kp_4 => .numpad_4, - .kp_5 => .numpad_5, - .kp_6 => .numpad_6, - .kp_7 => .numpad_7, - .kp_8 => .numpad_8, - .kp_9 => .numpad_9, - .kp_decimal => .numpad_decimal, - .kp_divide => .numpad_divide, - .kp_multiply => .numpad_multiply, - .kp_subtract => .numpad_subtract, - .kp_add => .numpad_add, - .kp_enter => .numpad_enter, - .kp_equal => .numpad_equal, - .grave_accent => .backquote, - .minus => .minus, - .equal => .equal, - .space => .space, - .semicolon => .semicolon, - .apostrophe => .quote, - .comma => .comma, - .period => .period, - .slash => .slash, - .left_bracket => .bracket_left, - .right_bracket => .bracket_right, - .backslash => .backslash, - .enter => .enter, - .tab => .tab, - .backspace => .backspace, - .insert => .insert, - .delete => .delete, - .caps_lock => .caps_lock, - .scroll_lock => .scroll_lock, - .num_lock => .num_lock, - .print_screen => .print_screen, - .pause => .pause, - .left_shift => .shift_left, - .left_control => .control_left, - .left_alt => .alt_left, - .left_super => .meta_left, - .right_shift => .shift_right, - .right_control => .control_right, - .right_alt => .alt_right, - .right_super => .meta_right, - .menu => .context_menu, - - .world_1, - .world_2, - .unknown, - => .unidentified, - }; - - // This is a hack for GLFW. We require our apprts to send both - // the UTF8 encoding AND the keypress at the same time. Its critical - // for things like ctrl sequences to work. However, GLFW doesn't - // provide this information all at once. So we just infer based on - // the key press. This isn't portable but GLFW is only for testing. - const utf8 = switch (key) { - inline else => |k| utf8: { - if (mods.shift) break :utf8 ""; - const cp = k.codepoint() orelse break :utf8 ""; - const byte = std.math.cast(u8, cp) orelse break :utf8 ""; - break :utf8 &.{byte}; - }, - }; - - const key_event: input.KeyEvent = .{ - .action = action, - .key = key, - .mods = mods, - .consumed_mods = .{}, - .composing = false, - .utf8 = utf8, - .unshifted_codepoint = if (utf8.len > 0) @intCast(utf8[0]) else 0, - }; - - const effect = core_win.keyCallback(key_event) catch |err| { - log.err("error in key callback err={}", .{err}); - return; - }; - - // Surface closed. - if (effect == .closed) return; - - // If it wasn't consumed, we set it on our self so that charcallback - // can make another attempt. Otherwise, we set null so the charcallback - // is ignored. - core_win.rt_surface.key_event = null; - if (effect == .ignored and - (action == .press or action == .repeat)) - { - core_win.rt_surface.key_event = key_event; - } - } - - fn focusCallback(window: glfw.Window, focused: bool) void { - const core_win = window.getUserPointer(CoreSurface) orelse return; - core_win.focusCallback(focused) catch |err| { - log.err("error in focus callback err={}", .{err}); - return; - }; - } - - fn refreshCallback(window: glfw.Window) void { - const core_win = window.getUserPointer(CoreSurface) orelse return; - core_win.refreshCallback() catch |err| { - log.err("error in refresh callback err={}", .{err}); - return; - }; - } - - fn scrollCallback(window: glfw.Window, xoff: f64, yoff: f64) void { - // Glfw doesn't support any of the scroll mods. - const scroll_mods: input.ScrollMods = .{}; - - const core_win = window.getUserPointer(CoreSurface) orelse return; - core_win.scrollCallback(xoff, yoff, scroll_mods) catch |err| { - log.err("error in scroll callback err={}", .{err}); - return; - }; - } - - fn cursorPosCallback( - window: glfw.Window, - unscaled_xpos: f64, - unscaled_ypos: f64, - ) void { - const core_win = window.getUserPointer(CoreSurface) orelse return; - - // Convert our unscaled x/y to scaled. - const pos = core_win.rt_surface.cursorPosToPixels(.{ - .xpos = unscaled_xpos, - .ypos = unscaled_ypos, - }) catch |err| { - log.err( - "error converting cursor pos to scaled pixels in cursor pos callback err={}", - .{err}, - ); - return; - }; - - core_win.cursorPosCallback(.{ - .x = @floatCast(pos.xpos), - .y = @floatCast(pos.ypos), - }, null) catch |err| { - log.err("error in cursor pos callback err={}", .{err}); - return; - }; - } - - fn mouseButtonCallback( - window: glfw.Window, - glfw_button: glfw.MouseButton, - glfw_action: glfw.Action, - glfw_mods: glfw.Mods, - ) void { - const core_win = window.getUserPointer(CoreSurface) orelse return; - - // Convert glfw button to input button - const mods: input.Mods = .{ - .shift = glfw_mods.shift, - .ctrl = glfw_mods.control, - .alt = glfw_mods.alt, - .super = glfw_mods.super, - }; - const button: input.MouseButton = switch (glfw_button) { - .left => .left, - .right => .right, - .middle => .middle, - .four => .four, - .five => .five, - .six => .six, - .seven => .seven, - .eight => .eight, - }; - const action: input.MouseButtonState = switch (glfw_action) { - .press => .press, - .release => .release, - else => unreachable, - }; - - _ = core_win.mouseButtonCallback(action, button, mods) catch |err| { - log.err("error in scroll callback err={}", .{err}); - return; - }; - } - - fn dropCallback(window: glfw.Window, paths: [][*:0]const u8) void { - const surface = window.getUserPointer(CoreSurface) orelse return; - - var list = std.ArrayList(u8).init(surface.alloc); - defer list.deinit(); - - for (paths) |path| { - const path_slice = std.mem.span(path); - - // preallocate worst case of escaping every char + space - list.ensureTotalCapacity(path_slice.len * 2 + 1) catch |err| { - log.err("error in drop callback err={}", .{err}); - return; - }; - - const writer = list.writer(); - for (path_slice) |c| { - if (std.mem.indexOfScalar(u8, "\\ ()[]{}<>\"'`!#$&;|*?\t", c)) |_| { - writer.print("\\{c}", .{c}) catch unreachable; // memory preallocated - } else writer.writeByte(c) catch unreachable; // same here - } - writer.writeByte(' ') catch unreachable; // separate paths - - surface.textCallback(list.items) catch |err| { - log.err("error in drop callback err={}", .{err}); - return; - }; - - list.clearRetainingCapacity(); // avoid unnecessary reallocations - } - } -}; diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 7786f976a..907f3a36d 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -373,6 +373,13 @@ pub fn init(self: *App, core_app: *CoreApp, opts: Options) !void { .{}, ); + // Setup a listener for SIGUSR2 to reload the configuration. + _ = glib.unixSignalAdd( + std.posix.SIG.USR2, + sigusr2, + self, + ); + // We don't use g_application_run, we want to manually control the // loop so we have to do the same things the run function does: // https://github.com/GNOME/glib/blob/a8e8b742e7926e33eb635a8edceac74cf239d6ed/gio/gapplication.c#L2533 @@ -489,7 +496,7 @@ pub fn performAction( .resize_split => self.resizeSplit(target, value), .equalize_splits => self.equalizeSplits(target), .goto_split => return self.gotoSplit(target, value), - .open_config => try configpkg.edit.open(self.core_app.alloc), + .open_config => return self.openConfig(), .config_change => self.configChange(target, value.config), .reload_config => try self.reloadConfig(target, value), .inspector => self.controlInspector(target, value), @@ -512,6 +519,7 @@ pub fn performAction( .secure_input => self.setSecureInput(target, value), .ring_bell => try self.ringBell(target), .toggle_command_palette => try self.toggleCommandPalette(target), + .open_url => self.openUrl(value), // Unimplemented .close_all_windows, @@ -1508,6 +1516,22 @@ pub fn quitNow(self: *App) void { self.running = false; } +// SIGUSR2 signal handler via g_unix_signal_add +fn sigusr2(ud: ?*anyopaque) callconv(.c) c_int { + const self: *App = @ptrCast(@alignCast(ud orelse + return @intFromBool(glib.SOURCE_CONTINUE))); + + log.info("received SIGUSR2, reloading configuration", .{}); + self.reloadConfig(.app, .{ .soft = false }) catch |err| { + log.err( + "error reloading configuration for SIGUSR2: {}", + .{err}, + ); + }; + + return @intFromBool(glib.SOURCE_CONTINUE); +} + /// This is called by the `activate` signal. This is sent on program startup and /// also when a secondary instance launches and requests a new window. fn gtkActivate(_: *adw.Application, core_app: *CoreApp) callconv(.c) void { @@ -1734,3 +1758,34 @@ fn initActions(self: *App) void { action_map.addAction(action.as(gio.Action)); } } + +fn openConfig(self: *App) !bool { + // Get the config file path + const alloc = self.core_app.alloc; + const path = configpkg.edit.openPath(alloc) catch |err| { + log.warn("error getting config file path: {}", .{err}); + return false; + }; + defer alloc.free(path); + + // Open it using openURL. "path" isn't actually a URL but + // at the time of writing that works just fine for GTK. + self.openUrl(.{ .kind = .text, .url = path }); + return true; +} + +fn openUrl( + app: *App, + value: apprt.action.OpenUrl, +) void { + // TODO: use https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.OpenURI.html + + // Fallback to the minimal cross-platform way of opening a URL. + // This is always a safe fallback and enables for example Windows + // to open URLs (GTK on Windows via WSL is a thing). + internal_os.open( + app.core_app.alloc, + value.kind, + value.url, + ) catch |err| log.warn("unable to open url: {}", .{err}); +} diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 555edb1e4..e6b502c80 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -214,6 +214,7 @@ pub fn init(self: *Window, app: *App) !void { { const btn = gtk.MenuButton.new(); btn.as(gtk.Widget).setTooltipText(i18n._("Main Menu")); + btn.as(gtk.Widget).setCanFocus(0); btn.setIconName("open-menu-symbolic"); btn.setPopover(self.titlebar_menu.asWidget()); _ = gobject.Object.signals.notify.connect( @@ -253,6 +254,7 @@ pub fn init(self: *Window, app: *App) !void { }, }; + btn.setCanFocus(0); btn.setFocusOnClick(0); self.headerbar.packEnd(btn); } diff --git a/src/build/Config.zig b/src/build/Config.zig index 5f8780af9..a9a79fb53 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -9,6 +9,7 @@ const apprt = @import("../apprt.zig"); const font = @import("../font/main.zig"); const rendererpkg = @import("../renderer.zig"); const Command = @import("../Command.zig"); +const XCFramework = @import("GhosttyXCFramework.zig"); const WasmTarget = @import("../os/wasm/target.zig").Target; const gtk = @import("gtk.zig"); @@ -24,6 +25,7 @@ const app_version: std.SemanticVersion = .{ .major = 1, .minor = 1, .patch = 4 } /// Standard build configuration options. optimize: std.builtin.OptimizeMode, target: std.Build.ResolvedTarget, +xcframework_target: XCFramework.Target = .universal, wasm_target: WasmTarget, /// Comptime interfaces @@ -48,14 +50,15 @@ patch_rpath: ?[]const u8 = null, /// Artifacts flatpak: bool = false, -emit_test_exe: bool = false, emit_bench: bool = false, -emit_helpgen: bool = false, emit_docs: bool = false, -emit_webdata: bool = false, -emit_xcframework: bool = false, +emit_helpgen: bool = false, +emit_macos_app: bool = false, emit_terminfo: bool = false, emit_termcap: bool = false, +emit_test_exe: bool = false, +emit_xcframework: bool = false, +emit_webdata: bool = false, /// Environmental properties env: std.process.EnvMap, @@ -109,6 +112,14 @@ pub fn init(b: *std.Build) !Config { .env = env, }; + //--------------------------------------------------------------- + // Target-specific properties + config.xcframework_target = b.option( + XCFramework.Target, + "xcframework-target", + "The target for the xcframework.", + ) orelse .universal; + //--------------------------------------------------------------- // Comptime Interfaces config.font_backend = b.option( @@ -340,6 +351,12 @@ pub fn init(b: *std.Build) !Config { !config.emit_test_exe and !config.emit_helpgen); + config.emit_macos_app = b.option( + bool, + "emit-macos-app", + "Build and install the macOS app bundle.", + ) orelse config.emit_xcframework; + //--------------------------------------------------------------- // System Packages @@ -378,11 +395,6 @@ pub fn init(b: *std.Build) !Config { "glslang", "spirv-cross", "simdutf", - - // This is default false because it is used for testing - // primarily and not official packaging. The packaging - // guide advises against building the GLFW backend. - "glfw3", }) |dep| { _ = b.systemIntegrationOption(dep, .{ .default = false }); } diff --git a/src/build/GhosttyDocs.zig b/src/build/GhosttyDocs.zig index 4b5dbfd92..b95b56f74 100644 --- a/src/build/GhosttyDocs.zig +++ b/src/build/GhosttyDocs.zig @@ -93,5 +93,32 @@ pub fn init( pub fn install(self: *const GhosttyDocs) void { const b = self.steps[0].owner; - for (self.steps) |step| b.getInstallStep().dependOn(step); + self.addStepDependencies(b.getInstallStep()); +} + +pub fn addStepDependencies( + self: *const GhosttyDocs, + other_step: *std.Build.Step, +) void { + for (self.steps) |step| other_step.dependOn(step); +} + +/// Installs some dummy files to satisfy the folder structure of docs +/// without actually generating any documentation. This is useful +/// when the `emit-docs` option is not set to true, but we still +/// need the rough directory structure to exist, such as for the macOS +/// app. +pub fn installDummy(self: *const GhosttyDocs, step: *std.Build.Step) void { + _ = self; + + const b = step.owner; + var wf = b.addWriteFiles(); + const path = "share/man/.placeholder"; + step.dependOn(&b.addInstallFile( + wf.add( + path, + "emit-docs not true so no man pages", + ), + path, + ).step); } diff --git a/src/build/GhosttyI18n.zig b/src/build/GhosttyI18n.zig index e0f6b5611..7667d30c3 100644 --- a/src/build/GhosttyI18n.zig +++ b/src/build/GhosttyI18n.zig @@ -50,7 +50,14 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyI18n { } pub fn install(self: *const GhosttyI18n) void { - for (self.steps) |step| self.owner.getInstallStep().dependOn(step); + self.addStepDependencies(self.owner.getInstallStep()); +} + +pub fn addStepDependencies( + self: *const GhosttyI18n, + other_step: *std.Build.Step, +) void { + for (self.steps) |step| other_step.dependOn(step); } fn createUpdateStep(b: *std.Build) !*std.Build.Step { diff --git a/src/build/GhosttyResources.zig b/src/build/GhosttyResources.zig index 34b5e35f8..ef04b21fd 100644 --- a/src/build/GhosttyResources.zig +++ b/src/build/GhosttyResources.zig @@ -397,5 +397,12 @@ fn addLinuxAppResources( pub fn install(self: *const GhosttyResources) void { const b = self.steps[0].owner; - for (self.steps) |step| b.getInstallStep().dependOn(step); + self.addStepDependencies(b.getInstallStep()); +} + +pub fn addStepDependencies( + self: *const GhosttyResources, + other_step: *std.Build.Step, +) void { + for (self.steps) |step| other_step.dependOn(step); } diff --git a/src/build/GhosttyXCFramework.zig b/src/build/GhosttyXCFramework.zig index 0dc4f5762..7debd6906 100644 --- a/src/build/GhosttyXCFramework.zig +++ b/src/build/GhosttyXCFramework.zig @@ -7,11 +7,23 @@ const GhosttyLib = @import("GhosttyLib.zig"); const XCFrameworkStep = @import("XCFrameworkStep.zig"); xcframework: *XCFrameworkStep, -macos: GhosttyLib, +target: Target, -pub fn init(b: *std.Build, deps: *const SharedDeps) !GhosttyXCFramework { - // Create our universal macOS static library. - const macos = try GhosttyLib.initMacOSUniversal(b, deps); +pub const Target = enum { native, universal }; + +pub fn init( + b: *std.Build, + deps: *const SharedDeps, + target: Target, +) !GhosttyXCFramework { + // Universal macOS build + const macos_universal = try GhosttyLib.initMacOSUniversal(b, deps); + + // Native macOS build + const macos_native = try GhosttyLib.initStatic(b, &try deps.retarget( + b, + Config.genericMacOSTarget(b, null), + )); // iOS const ios = try GhosttyLib.initStatic(b, &try deps.retarget( @@ -47,29 +59,43 @@ pub fn init(b: *std.Build, deps: *const SharedDeps) !GhosttyXCFramework { const xcframework = XCFrameworkStep.create(b, .{ .name = "GhosttyKit", .out_path = "macos/GhosttyKit.xcframework", - .libraries = &.{ - .{ - .library = macos.output, - .headers = b.path("include"), + .libraries = switch (target) { + .universal => &.{ + .{ + .library = macos_universal.output, + .headers = b.path("include"), + }, + .{ + .library = ios.output, + .headers = b.path("include"), + }, + .{ + .library = ios_sim.output, + .headers = b.path("include"), + }, }, - .{ - .library = ios.output, + + .native => &.{.{ + .library = macos_native.output, .headers = b.path("include"), - }, - .{ - .library = ios_sim.output, - .headers = b.path("include"), - }, + }}, }, }); return .{ .xcframework = xcframework, - .macos = macos, + .target = target, }; } pub fn install(self: *const GhosttyXCFramework) void { const b = self.xcframework.step.owner; - b.getInstallStep().dependOn(self.xcframework.step); + self.addStepDependencies(b.getInstallStep()); +} + +pub fn addStepDependencies( + self: *const GhosttyXCFramework, + other_step: *std.Build.Step, +) void { + other_step.dependOn(self.xcframework.step); } diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig new file mode 100644 index 000000000..7fa2d2f95 --- /dev/null +++ b/src/build/GhosttyXcodebuild.zig @@ -0,0 +1,157 @@ +const Ghostty = @This(); + +const std = @import("std"); +const builtin = @import("builtin"); +const RunStep = std.Build.Step.Run; +const Config = @import("Config.zig"); +const Docs = @import("GhosttyDocs.zig"); +const I18n = @import("GhosttyI18n.zig"); +const Resources = @import("GhosttyResources.zig"); +const XCFramework = @import("GhosttyXCFramework.zig"); + +build: *std.Build.Step.Run, +open: *std.Build.Step.Run, +copy: *std.Build.Step.Run, + +pub const Deps = struct { + xcframework: *const XCFramework, + docs: *const Docs, + i18n: *const I18n, + resources: *const Resources, +}; + +pub fn init( + b: *std.Build, + config: *const Config, + deps: Deps, +) !Ghostty { + const xc_config = switch (config.optimize) { + .Debug => "Debug", + .ReleaseSafe, + .ReleaseSmall, + .ReleaseFast, + => "Release", + }; + + const app_path = b.fmt("macos/build/{s}/Ghostty.app", .{xc_config}); + + // Our step to build the Ghostty macOS app. + const build = build: { + // External environment variables can mess up xcodebuild, so + // we create a new empty environment. + const env_map = try b.allocator.create(std.process.EnvMap); + env_map.* = .init(b.allocator); + + const build = RunStep.create(b, "xcodebuild"); + build.has_side_effects = true; + build.cwd = b.path("macos"); + build.env_map = env_map; + build.addArgs(&.{ + "xcodebuild", + "-target", + "Ghostty", + "-configuration", + xc_config, + }); + + switch (deps.xcframework.target) { + // Universal is our default target, so we don't have to + // add anything. + .universal => {}, + + // Native we need to override the architecture in the Xcode + // project with the -arch flag. + .native => build.addArgs(&.{ + "-arch", + switch (builtin.cpu.arch) { + .aarch64 => "arm64", + .x86_64 => "x86_64", + else => @panic("unsupported macOS arch"), + }, + }), + } + + // We need the xcframework + deps.xcframework.addStepDependencies(&build.step); + + // We also need all these resources because the xcode project + // references them via symlinks. + deps.resources.addStepDependencies(&build.step); + deps.i18n.addStepDependencies(&build.step); + deps.docs.installDummy(&build.step); + + // Expect success + build.expectExitCode(0); + + break :build build; + }; + + // Our step to open the resulting Ghostty app. + const open = open: { + const disable_save_state = RunStep.create(b, "disable save state"); + disable_save_state.has_side_effects = true; + disable_save_state.addArgs(&.{ + "/usr/libexec/PlistBuddy", + "-c", + // We'll have to change this to `Set` if we ever put this + // into our Info.plist. + "Add :NSQuitAlwaysKeepsWindows bool false", + b.fmt("{s}/Contents/Info.plist", .{app_path}), + }); + disable_save_state.expectExitCode(0); + disable_save_state.step.dependOn(&build.step); + + const open = RunStep.create(b, "run Ghostty app"); + open.has_side_effects = true; + open.cwd = b.path(""); + open.addArgs(&.{b.fmt( + "{s}/Contents/MacOS/ghostty", + .{app_path}, + )}); + + // Open depends on the app + open.step.dependOn(&build.step); + open.step.dependOn(&disable_save_state.step); + + // This overrides our default behavior and forces logs to show + // up on stderr (in addition to the centralized macOS log). + open.setEnvironmentVariable("GHOSTTY_LOG", "1"); + + // Configure how we're launching + open.setEnvironmentVariable("GHOSTTY_MAC_LAUNCH_SOURCE", "zig_run"); + + if (b.args) |args| { + open.addArgs(args); + } + + break :open open; + }; + + // Our step to copy the app bundle to the install path. + // We have to use `cp -R` because there are symlinks in the + // bundle. + const copy = copy: { + const step = RunStep.create(b, "copy app bundle"); + step.addArgs(&.{ "cp", "-R" }); + step.addFileArg(b.path(app_path)); + step.addArg(b.fmt("{s}", .{b.install_path})); + step.step.dependOn(&build.step); + break :copy step; + }; + + return .{ + .build = build, + .open = open, + .copy = copy, + }; +} + +pub fn install(self: *const Ghostty) void { + const b = self.copy.step.owner; + b.getInstallStep().dependOn(&self.copy.step); +} + +pub fn installXcframework(self: *const Ghostty) void { + const b = self.build.step.owner; + b.getInstallStep().dependOn(&self.build.step); +} diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index ec97a9c9f..0aab5ecf8 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -405,12 +405,11 @@ pub fn add( })) |dep| { step.root_module.addImport("xev", dep.module("xev")); } - if (b.lazyDependency("z2d", .{})) |dep| { - step.root_module.addImport("z2d", b.addModule("z2d", .{ - .root_source_file = dep.path("src/z2d.zig"), - .target = target, - .optimize = optimize, - })); + if (b.lazyDependency("z2d", .{ + .target = target, + .optimize = optimize, + })) |dep| { + step.root_module.addImport("z2d", dep.module("z2d")); } if (b.lazyDependency("ziglyph", .{ .target = target, @@ -501,6 +500,43 @@ pub fn add( try static_libs.append(utfcpp_dep.artifact("utfcpp").getEmittedBin()); } + // Fonts + { + // JetBrains Mono + const jb_mono = b.dependency("jetbrains_mono", .{}); + step.root_module.addAnonymousImport( + "jetbrains_mono_regular", + .{ .root_source_file = jb_mono.path("fonts/ttf/JetBrainsMono-Regular.ttf") }, + ); + step.root_module.addAnonymousImport( + "jetbrains_mono_bold", + .{ .root_source_file = jb_mono.path("fonts/ttf/JetBrainsMono-Bold.ttf") }, + ); + step.root_module.addAnonymousImport( + "jetbrains_mono_italic", + .{ .root_source_file = jb_mono.path("fonts/ttf/JetBrainsMono-Italic.ttf") }, + ); + step.root_module.addAnonymousImport( + "jetbrains_mono_bold_italic", + .{ .root_source_file = jb_mono.path("fonts/ttf/JetBrainsMono-BoldItalic.ttf") }, + ); + step.root_module.addAnonymousImport( + "jetbrains_mono_variable", + .{ .root_source_file = jb_mono.path("fonts/variable/JetBrainsMono[wght].ttf") }, + ); + step.root_module.addAnonymousImport( + "jetbrains_mono_variable_italic", + .{ .root_source_file = jb_mono.path("fonts/variable/JetBrainsMono-Italic[wght].ttf") }, + ); + + // Symbols-only nerd font + const nf_symbols = b.dependency("nerd_fonts_symbols_only", .{}); + step.root_module.addAnonymousImport( + "nerd_fonts_symbols_only", + .{ .root_source_file = nf_symbols.path("SymbolsNerdFontMono-Regular.ttf") }, + ); + } + // If we're building an exe then we have additional dependencies. if (step.kind != .lib) { // We always statically compile glad @@ -516,17 +552,6 @@ pub fn add( switch (self.config.app_runtime) { .none => {}, - - .glfw => if (b.lazyDependency("glfw", .{ - .target = target, - .optimize = optimize, - })) |glfw_dep| { - step.root_module.addImport( - "glfw", - glfw_dep.module("glfw"), - ); - }, - .gtk => try self.addGTK(step), } } diff --git a/src/build/XCFrameworkStep.zig b/src/build/XCFrameworkStep.zig index 823e5aac4..8a0d5dc67 100644 --- a/src/build/XCFrameworkStep.zig +++ b/src/build/XCFrameworkStep.zig @@ -55,6 +55,9 @@ pub fn create(b: *std.Build, opts: Options) *XCFrameworkStep { } run.addArg("-output"); run.addArg(opts.out_path); + run.expectExitCode(0); + _ = run.captureStdOut(); + _ = run.captureStdErr(); break :run run; }; run_create.step.dependOn(&run_delete.step); diff --git a/src/build/main.zig b/src/build/main.zig index 3154d395f..f25ce1c23 100644 --- a/src/build/main.zig +++ b/src/build/main.zig @@ -15,6 +15,7 @@ pub const GhosttyFrameData = @import("GhosttyFrameData.zig"); pub const GhosttyLib = @import("GhosttyLib.zig"); pub const GhosttyResources = @import("GhosttyResources.zig"); pub const GhosttyI18n = @import("GhosttyI18n.zig"); +pub const GhosttyXcodebuild = @import("GhosttyXcodebuild.zig"); pub const GhosttyXCFramework = @import("GhosttyXCFramework.zig"); pub const GhosttyWebdata = @import("GhosttyWebdata.zig"); pub const HelpStrings = @import("HelpStrings.zig"); diff --git a/src/cli/version.zig b/src/cli/version.zig index a27d1050d..22608fa88 100644 --- a/src/cli/version.zig +++ b/src/cli/version.zig @@ -15,8 +15,6 @@ pub const Options = struct {}; /// The `version` command is used to display information about Ghostty. Recognized as /// either `+version` or `--version`. pub fn run(alloc: Allocator) !u8 { - _ = alloc; - const stdout = std.io.getStdOut().writer(); const tty = std.io.getStdOut().isTty(); @@ -34,32 +32,37 @@ pub fn run(alloc: Allocator) !u8 { try stdout.print(" - channel: {s}\n", .{@tagName(build_config.release_channel)}); try stdout.print("Build Config\n", .{}); - try stdout.print(" - Zig version: {s}\n", .{builtin.zig_version_string}); - try stdout.print(" - build mode : {}\n", .{builtin.mode}); - try stdout.print(" - app runtime: {}\n", .{build_config.app_runtime}); - try stdout.print(" - font engine: {}\n", .{build_config.font_backend}); - try stdout.print(" - renderer : {}\n", .{renderer.Renderer}); - try stdout.print(" - libxev : {s}\n", .{@tagName(xev.backend)}); + try stdout.print(" - Zig version : {s}\n", .{builtin.zig_version_string}); + try stdout.print(" - build mode : {}\n", .{builtin.mode}); + try stdout.print(" - app runtime : {}\n", .{build_config.app_runtime}); + try stdout.print(" - font engine : {}\n", .{build_config.font_backend}); + try stdout.print(" - renderer : {}\n", .{renderer.Renderer}); + try stdout.print(" - libxev : {s}\n", .{@tagName(xev.backend)}); if (comptime build_config.app_runtime == .gtk) { - try stdout.print(" - desktop env: {s}\n", .{@tagName(internal_os.desktopEnvironment())}); - try stdout.print(" - GTK version:\n", .{}); - try stdout.print(" build : {}\n", .{gtk_version.comptime_version}); - try stdout.print(" runtime : {}\n", .{gtk_version.getRuntimeVersion()}); - try stdout.print(" - libadwaita : enabled\n", .{}); - try stdout.print(" build : {}\n", .{adw_version.comptime_version}); - try stdout.print(" runtime : {}\n", .{adw_version.getRuntimeVersion()}); + if (comptime builtin.os.tag == .linux) { + const kernel_info = internal_os.getKernelInfo(alloc); + defer if (kernel_info) |k| alloc.free(k); + try stdout.print(" - kernel version: {s}\n", .{kernel_info orelse "Kernel information unavailable"}); + } + try stdout.print(" - desktop env : {s}\n", .{@tagName(internal_os.desktopEnvironment())}); + try stdout.print(" - GTK version :\n", .{}); + try stdout.print(" build : {}\n", .{gtk_version.comptime_version}); + try stdout.print(" runtime : {}\n", .{gtk_version.getRuntimeVersion()}); + try stdout.print(" - libadwaita : enabled\n", .{}); + try stdout.print(" build : {}\n", .{adw_version.comptime_version}); + try stdout.print(" runtime : {}\n", .{adw_version.getRuntimeVersion()}); if (comptime build_options.x11) { - try stdout.print(" - libX11 : enabled\n", .{}); + try stdout.print(" - libX11 : enabled\n", .{}); } else { - try stdout.print(" - libX11 : disabled\n", .{}); + try stdout.print(" - libX11 : disabled\n", .{}); } // We say `libwayland` since it is possible to build Ghostty without // Wayland integration but with Wayland-enabled GTK if (comptime build_options.wayland) { - try stdout.print(" - libwayland : enabled\n", .{}); + try stdout.print(" - libwayland : enabled\n", .{}); } else { - try stdout.print(" - libwayland : disabled\n", .{}); + try stdout.print(" - libwayland : disabled\n", .{}); } } return 0; diff --git a/src/config.zig b/src/config.zig index ac38eb89c..efc9fd973 100644 --- a/src/config.zig +++ b/src/config.zig @@ -14,6 +14,7 @@ pub const entryFormatter = formatter.entryFormatter; pub const formatEntry = formatter.formatEntry; // Field types +pub const BoldColor = Config.BoldColor; pub const ClipboardAccess = Config.ClipboardAccess; pub const Command = Config.Command; pub const ConfirmCloseSurface = Config.ConfirmCloseSurface; @@ -37,6 +38,7 @@ pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures; pub const WindowPaddingColor = Config.WindowPaddingColor; pub const BackgroundImagePosition = Config.BackgroundImagePosition; pub const BackgroundImageFit = Config.BackgroundImageFit; +pub const LinkPreviews = Config.LinkPreviews; // Alternate APIs pub const CAPI = @import("config/CAPI.zig"); diff --git a/src/config/CAPI.zig b/src/config/CAPI.zig index 0b7108a59..bdc59797a 100644 --- a/src/config/CAPI.zig +++ b/src/config/CAPI.zig @@ -1,7 +1,9 @@ const std = @import("std"); +const assert = std.debug.assert; const cli = @import("../cli.zig"); const inputpkg = @import("../input.zig"); -const global = &@import("../global.zig").state; +const state = &@import("../global.zig").state; +const c = @import("../main_c.zig"); const Config = @import("Config.zig"); const c_get = @import("c_get.zig"); @@ -12,14 +14,14 @@ const log = std.log.scoped(.config); /// Create a new configuration filled with the initial default values. export fn ghostty_config_new() ?*Config { - const result = global.alloc.create(Config) catch |err| { + const result = state.alloc.create(Config) catch |err| { log.err("error allocating config err={}", .{err}); return null; }; - result.* = Config.default(global.alloc) catch |err| { + result.* = Config.default(state.alloc) catch |err| { log.err("error creating config err={}", .{err}); - global.alloc.destroy(result); + state.alloc.destroy(result); return null; }; @@ -29,20 +31,20 @@ export fn ghostty_config_new() ?*Config { export fn ghostty_config_free(ptr: ?*Config) void { if (ptr) |v| { v.deinit(); - global.alloc.destroy(v); + state.alloc.destroy(v); } } /// Deep clone the configuration. export fn ghostty_config_clone(self: *Config) ?*Config { - const result = global.alloc.create(Config) catch |err| { + const result = state.alloc.create(Config) catch |err| { log.err("error allocating config err={}", .{err}); return null; }; - result.* = self.clone(global.alloc) catch |err| { + result.* = self.clone(state.alloc) catch |err| { log.err("error cloning config err={}", .{err}); - global.alloc.destroy(result); + state.alloc.destroy(result); return null; }; @@ -51,7 +53,7 @@ export fn ghostty_config_clone(self: *Config) ?*Config { /// Load the configuration from the CLI args. export fn ghostty_config_load_cli_args(self: *Config) void { - self.loadCliArgs(global.alloc) catch |err| { + self.loadCliArgs(state.alloc) catch |err| { log.err("error loading config err={}", .{err}); }; } @@ -60,7 +62,7 @@ export fn ghostty_config_load_cli_args(self: *Config) void { /// is usually done first. The default file locations are locations /// such as the home directory. export fn ghostty_config_load_default_files(self: *Config) void { - self.loadDefaultFiles(global.alloc) catch |err| { + self.loadDefaultFiles(state.alloc) catch |err| { log.err("error loading config err={}", .{err}); }; } @@ -69,7 +71,7 @@ export fn ghostty_config_load_default_files(self: *Config) void { /// file locations in the previously loaded configuration. This will /// recursively continue to load up to a built-in limit. export fn ghostty_config_load_recursive_files(self: *Config) void { - self.loadRecursiveFiles(global.alloc) catch |err| { + self.loadRecursiveFiles(state.alloc) catch |err| { log.err("error loading config err={}", .{err}); }; } @@ -122,10 +124,13 @@ export fn ghostty_config_get_diagnostic(self: *Config, idx: u32) Diagnostic { return .{ .message = message.ptr }; } -export fn ghostty_config_open() void { - edit.open(global.alloc) catch |err| { +export fn ghostty_config_open_path() c.String { + const path = edit.openPath(state.alloc) catch |err| { log.err("error opening config in editor err={}", .{err}); + return .empty; }; + + return .fromSlice(path); } /// Sync with ghostty_diagnostic_s diff --git a/src/config/Config.zig b/src/config/Config.zig index f461338f6..3957ad08f 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -62,6 +62,17 @@ pub const compatibility = std.StaticStringMap( // Ghostty 1.2 removed the `hidden` value from `gtk-tabs-location` and // moved it to `window-show-tab-bar`. .{ "gtk-tabs-location", compatGtkTabsLocation }, + + // Ghostty 1.2 lets you set `cell-foreground` and `cell-background` + // to match the cell foreground and background colors, respectively. + // This can be used with `cursor-color` and `cursor-text` to recreate + // this behavior. This applies to selection too. + .{ "cursor-invert-fg-bg", compatCursorInvertFgBg }, + .{ "selection-invert-fg-bg", compatSelectionInvertFgBg }, + + // Ghostty 1.2 merged `bold-is-bright` into the new `bold-color` + // by setting the value to "bright". + .{ "bold-is-bright", compatBoldIsBright }, }); /// The font families to use. @@ -425,13 +436,16 @@ pub const compatibility = std.StaticStringMap( /// /// Available flags: /// -/// * `hinting` - Enable or disable hinting, enabled by default. -/// * `force-autohint` - Use the freetype auto-hinter rather than the -/// font's native hinter. Enabled by default. -/// * `monochrome` - Instructs renderer to use 1-bit monochrome -/// rendering. This option doesn't impact the hinter. -/// Enabled by default. -/// * `autohint` - Use the freetype auto-hinter. Enabled by default. +/// * `hinting` - Enable or disable hinting. Enabled by default. +/// +/// * `force-autohint` - Always use the freetype auto-hinter instead of +/// the font's native hinter. Disabled by default. +/// +/// * `monochrome` - Instructs renderer to use 1-bit monochrome rendering. +/// This will disable anti-aliasing, and probably not look very good unless +/// you're using a pixel font. Disabled by default. +/// +/// * `autohint` - Enable the freetype auto-hinter. Enabled by default. /// /// Example: `hinting`, `no-hinting`, `force-autohint`, `no-force-autohint` @"freetype-load-flags": FreetypeLoadFlags = .{}, @@ -591,16 +605,11 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, /// the selection color is just the inverted window background and foreground /// (note: not to be confused with the cell bg/fg). /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -@"selection-foreground": ?Color = null, -@"selection-background": ?Color = null, - -/// Swap the foreground and background colors of cells for selection. This -/// option overrides the `selection-foreground` and `selection-background` -/// options. -/// -/// If you select across cells with differing foregrounds and backgrounds, the -/// selection color will vary across the selection. -@"selection-invert-fg-bg": bool = false, +/// Since version 1.2.0, this can also be set to `cell-foreground` to match +/// the cell foreground color, or `cell-background` to match the cell +/// background color. +@"selection-foreground": ?TerminalColor = null, +@"selection-background": ?TerminalColor = null, /// Whether to clear selected text when typing. This defaults to `true`. /// This is typical behavior for most terminal emulators as well as @@ -644,12 +653,20 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF }, palette: Palette = .{}, /// The color of the cursor. If this is not set, a default will be chosen. -/// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -@"cursor-color": ?Color = null, - -/// Swap the foreground and background colors of the cell under the cursor. This -/// option overrides the `cursor-color` and `cursor-text` options. -@"cursor-invert-fg-bg": bool = false, +/// +/// Direct colors can be specified as either hex (`#RRGGBB` or `RRGGBB`) +/// or a named X11 color. +/// +/// Additionally, special values can be used to set the color to match +/// other colors at runtime: +/// +/// * `cell-foreground` - Match the cell foreground color. +/// (Available since version 1.2.0) +/// +/// * `cell-background` - Match the cell background color. +/// (Available since version 1.2.0) +/// +@"cursor-color": ?TerminalColor = null, /// The opacity level (opposite of transparency) of the cursor. A value of 1 /// is fully opaque and a value of 0 is fully transparent. A value less than 0 @@ -699,7 +716,10 @@ palette: Palette = .{}, /// The color of the text under the cursor. If this is not set, a default will /// be chosen. /// Specified as either hex (`#RRGGBB` or `RRGGBB`) or a named X11 color. -@"cursor-text": ?Color = null, +/// Since version 1.2.0, this can also be set to `cell-foreground` to match +/// the cell foreground color, or `cell-background` to match the cell +/// background color. +@"cursor-text": ?TerminalColor = null, /// Enables the ability to move the cursor at prompts by using `alt+click` on /// Linux and `option+click` on macOS. @@ -1030,6 +1050,14 @@ link: RepeatableLink = .{}, /// `link`). If you want to customize URL matching, use `link` and disable this. @"link-url": bool = true, +/// Show link previews for a matched URL. +/// +/// When true, link previews are shown for all matched URLs. When false, link +/// previews are never shown. When set to "osc8", link previews are only shown +/// for hyperlinks created with the OSC 8 sequence (in this case, the link text +/// can differ from the link destination). +@"link-previews": LinkPreviews = .true, + /// Whether to start the window in a maximized state. This setting applies /// to new windows and does not apply to tabs, splits, etc. However, this setting /// will apply to all new windows, not just the first one. @@ -1569,9 +1597,9 @@ keybind: Keybinds = .{}, /// the visible screen area. This means that if the menu bar is visible, the /// window will be placed below the menu bar. /// -/// Note: this is only supported on macOS and Linux GLFW builds. The GTK -/// runtime does not support setting the window position, as windows are -/// only allowed position themselves in X11 and not Wayland. +/// Note: this is only supported on macOS. The GTK runtime does not support +/// setting the window position, as windows are only allowed position +/// themselves in X11 and not Wayland. @"window-position-x": ?i16 = null, @"window-position-y": ?i16 = null, @@ -2491,8 +2519,6 @@ keybind: Keybinds = .{}, /// /// The values `left` or `right` enable this for the left or right *Option* /// key, respectively. -/// -/// This does not work with GLFW builds. @"macos-option-as-alt": ?OptionAsAlt = null, /// Whether to enable the macOS window shadow. The default value is true. @@ -2761,14 +2787,14 @@ else /// /// GTK CSS documentation can be found at the following links: /// -/// * - An overview of GTK CSS. -/// * - A comprehensive list +/// * https://docs.gtk.org/gtk4/css-overview.html - An overview of GTK CSS. +/// * https://docs.gtk.org/gtk4/css-properties.html - A comprehensive list /// of supported CSS properties. /// /// Launch Ghostty with `env GTK_DEBUG=interactive ghostty` to tweak Ghostty's /// CSS in real time using the GTK Inspector. Errors in your CSS files would /// also be reported in the terminal you started Ghostty from. See -/// for more +/// https://developer.gnome.org/documentation/tools/inspector.html for more /// information about the GTK Inspector. /// /// This configuration can be repeated multiple times to load multiple files. @@ -2782,8 +2808,24 @@ else /// notifications using certain escape sequences such as OSC 9 or OSC 777. @"desktop-notifications": bool = true, -/// If `true`, the bold text will use the bright color palette. -@"bold-is-bright": bool = false, +/// Modifies the color used for bold text in the terminal. +/// +/// This can be set to a specific color, using the same format as +/// `background` or `foreground` (e.g. `#RRGGBB` but other formats +/// are also supported; see the aforementioned documentation). If a +/// specific color is set, this color will always be used for all +/// bold text regardless of the terminal's color scheme. +/// +/// This can also be set to `bright`, which uses the bright color palette +/// for bold text. For example, if the text is red, then the bold will +/// use the bright red color. The terminal palette is set with `palette` +/// but can also be overridden by the terminal application itself using +/// escape sequences such as OSC 4. (Since Ghostty 1.2.0, the previous +/// configuration `bold-is-bright` is deprecated and replaced by this +/// usage). +/// +/// Available since Ghostty 1.2.0. +@"bold-color": ?BoldColor = null, /// This will be used to set the `TERM` environment variable. /// HACK: We set this with an `xterm` prefix because vim uses that to enable key @@ -3826,10 +3868,6 @@ pub fn parseManuallyHook( return true; } -/// parseFieldManuallyFallback is a fallback called only when -/// parsing the field directly failed. It can be used to implement -/// backward compatibility. Since this is only called when parsing -/// fails, it doesn't impact happy-path performance. fn compatGtkTabsLocation( self: *Config, alloc: Allocator, @@ -3847,6 +3885,68 @@ fn compatGtkTabsLocation( return false; } +fn compatCursorInvertFgBg( + self: *Config, + alloc: Allocator, + key: []const u8, + value_: ?[]const u8, +) bool { + _ = alloc; + assert(std.mem.eql(u8, key, "cursor-invert-fg-bg")); + + // We don't do anything if the value is unset, which is technically + // not EXACTLY the same as prior behavior since it would fallback + // to doing whatever cursor-color/cursor-text were set to, but + // I don't want to store what that is separately so this is close + // enough. + // + // Realistically, these fields were mutually exclusive so anyone + // relying on that behavior should just upgrade to the new + // cursor-color/cursor-text fields. + const set = cli.args.parseBool(value_ orelse "t") catch return false; + if (set) { + self.@"cursor-color" = .@"cell-foreground"; + self.@"cursor-text" = .@"cell-background"; + } + + return true; +} + +fn compatSelectionInvertFgBg( + self: *Config, + alloc: Allocator, + key: []const u8, + value_: ?[]const u8, +) bool { + _ = alloc; + assert(std.mem.eql(u8, key, "selection-invert-fg-bg")); + + const set = cli.args.parseBool(value_ orelse "t") catch return false; + if (set) { + self.@"selection-foreground" = .@"cell-background"; + self.@"selection-background" = .@"cell-foreground"; + } + + return true; +} + +fn compatBoldIsBright( + self: *Config, + alloc: Allocator, + key: []const u8, + value_: ?[]const u8, +) bool { + _ = alloc; + assert(std.mem.eql(u8, key, "bold-is-bright")); + + const set = cli.args.parseBool(value_ orelse "t") catch return false; + if (set) { + self.@"bold-color" = .bright; + } + + return true; +} + /// Create a shallow copy of this config. This will share all the memory /// allocated with the previous config but will have a new arena for /// any changes or new allocations. The config should have `deinit` @@ -4271,6 +4371,12 @@ pub const WindowSubtitle = enum { @"working-directory", }; +pub const LinkPreviews = enum { + false, + true, + osc8, +}; + /// Color represents a color using RGB. /// /// This is a packed struct so that the C API to read color values just @@ -4409,6 +4515,117 @@ pub const Color = struct { } }; +/// Represents color values that can also reference special color +/// values such as "cell-foreground" or "cell-background". +pub const TerminalColor = union(enum) { + color: Color, + @"cell-foreground", + @"cell-background", + + pub fn parseCLI(input_: ?[]const u8) !TerminalColor { + const input = input_ orelse return error.ValueRequired; + if (std.mem.eql(u8, input, "cell-foreground")) return .@"cell-foreground"; + if (std.mem.eql(u8, input, "cell-background")) return .@"cell-background"; + return .{ .color = try Color.parseCLI(input) }; + } + + /// Used by Formatter + pub fn formatEntry(self: TerminalColor, formatter: anytype) !void { + switch (self) { + .color => try self.color.formatEntry(formatter), + + .@"cell-foreground", + .@"cell-background", + => try formatter.formatEntry([:0]const u8, @tagName(self)), + } + } + + test "parseCLI" { + const testing = std.testing; + + try testing.expectEqual( + TerminalColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, + try TerminalColor.parseCLI("#4e2a84"), + ); + try testing.expectEqual( + TerminalColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, + try TerminalColor.parseCLI("black"), + ); + try testing.expectEqual( + TerminalColor.@"cell-foreground", + try TerminalColor.parseCLI("cell-foreground"), + ); + try testing.expectEqual( + TerminalColor.@"cell-background", + try TerminalColor.parseCLI("cell-background"), + ); + + try testing.expectError(error.InvalidValue, TerminalColor.parseCLI("a")); + } + + test "formatConfig" { + const testing = std.testing; + var buf = std.ArrayList(u8).init(testing.allocator); + defer buf.deinit(); + + var sc: TerminalColor = .@"cell-foreground"; + try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); + try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.items); + } +}; + +/// Represents color values that can be used for bold. See `bold-color`. +pub const BoldColor = union(enum) { + color: Color, + bright, + + pub fn parseCLI(input_: ?[]const u8) !BoldColor { + const input = input_ orelse return error.ValueRequired; + if (std.mem.eql(u8, input, "bright")) return .bright; + return .{ .color = try Color.parseCLI(input) }; + } + + /// Used by Formatter + pub fn formatEntry(self: BoldColor, formatter: anytype) !void { + switch (self) { + .color => try self.color.formatEntry(formatter), + .bright => try formatter.formatEntry( + [:0]const u8, + @tagName(self), + ), + } + } + + test "parseCLI" { + const testing = std.testing; + + try testing.expectEqual( + BoldColor{ .color = Color{ .r = 78, .g = 42, .b = 132 } }, + try BoldColor.parseCLI("#4e2a84"), + ); + try testing.expectEqual( + BoldColor{ .color = Color{ .r = 0, .g = 0, .b = 0 } }, + try BoldColor.parseCLI("black"), + ); + try testing.expectEqual( + BoldColor.bright, + try BoldColor.parseCLI("bright"), + ); + + try testing.expectError(error.InvalidValue, BoldColor.parseCLI("a")); + } + + test "formatConfig" { + const testing = std.testing; + var buf = std.ArrayList(u8).init(testing.allocator); + defer buf.deinit(); + + var sc: BoldColor = .bright; + try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); + try testing.expectEqualSlices(u8, "a = bright\n", buf.items); + } +}; + pub const ColorList = struct { const Self = @This(); @@ -5342,7 +5559,14 @@ pub const Keybinds = struct { .mods = mods, }, .{ .goto_tab = (i - start) + 1 }, - .{ .performable = true }, + .{ + // On macOS we keep this not performable so that the + // keyboard shortcuts in tabs work. In the future the + // correct fix is to fix the reverse mapping lookup + // to allow us to lookup performable keybinds + // conditionally. + .performable = !builtin.target.os.tag.isDarwin(), + }, ); } try self.set.putFlags( @@ -5352,7 +5576,10 @@ pub const Keybinds = struct { .mods = mods, }, .{ .last_tab = {} }, - .{ .performable = true }, + .{ + // See comment above with the numeric goto_tab + .performable = !builtin.target.os.tag.isDarwin(), + }, ); } @@ -6407,8 +6634,9 @@ pub const RepeatableCommand = struct { try list.parseCLI(alloc, "title:Foo,action:ignore"); try list.parseCLI(alloc, "title:Bar,description:bobr,action:text:ale bydle"); try list.parseCLI(alloc, "title:Quux,description:boo,action:increase_font_size:2.5"); + try list.parseCLI(alloc, "title:Baz,description:Raspberry Pie,action:set_font_size:3.14"); - try testing.expectEqual(@as(usize, 3), list.value.items.len); + try testing.expectEqual(@as(usize, 4), list.value.items.len); try testing.expectEqual(inputpkg.Binding.Action.ignore, list.value.items[0].action); try testing.expectEqualStrings("Foo", list.value.items[0].title); @@ -6425,6 +6653,13 @@ pub const RepeatableCommand = struct { try testing.expectEqualStrings("Quux", list.value.items[2].title); try testing.expectEqualStrings("boo", list.value.items[2].description); + try testing.expectEqual( + inputpkg.Binding.Action{ .set_font_size = 3.14 }, + list.value.items[3].action, + ); + try testing.expectEqualStrings("Baz", list.value.items[3].title); + try testing.expectEqualStrings("Raspberry Pie", list.value.items[3].description); + try list.parseCLI(alloc, ""); try testing.expectEqual(@as(usize, 0), list.value.items.len); } @@ -6996,8 +7231,8 @@ pub const FreetypeLoadFlags = packed struct { // for Freetype itself. Ghostty hasn't made any opinionated changes // to these defaults. hinting: bool = true, - @"force-autohint": bool = true, - monochrome: bool = true, + @"force-autohint": bool = false, + monochrome: bool = false, autohint: bool = true, }; @@ -8078,3 +8313,71 @@ test "theme specifying light/dark sets theme usage in conditional state" { try testing.expect(cfg._conditional_set.contains(.theme)); } } + +test "compatibility: removed cursor-invert-fg-bg" { + const testing = std.testing; + const alloc = testing.allocator; + + { + var cfg = try Config.default(alloc); + defer cfg.deinit(); + var it: TestIterator = .{ .data = &.{ + "--cursor-invert-fg-bg", + } }; + try cfg.loadIter(alloc, &it); + try cfg.finalize(); + + try testing.expectEqual( + TerminalColor.@"cell-foreground", + cfg.@"cursor-color", + ); + try testing.expectEqual( + TerminalColor.@"cell-background", + cfg.@"cursor-text", + ); + } +} + +test "compatibility: removed selection-invert-fg-bg" { + const testing = std.testing; + const alloc = testing.allocator; + + { + var cfg = try Config.default(alloc); + defer cfg.deinit(); + var it: TestIterator = .{ .data = &.{ + "--selection-invert-fg-bg", + } }; + try cfg.loadIter(alloc, &it); + try cfg.finalize(); + + try testing.expectEqual( + TerminalColor.@"cell-background", + cfg.@"selection-foreground", + ); + try testing.expectEqual( + TerminalColor.@"cell-foreground", + cfg.@"selection-background", + ); + } +} + +test "compatibility: removed bold-is-bright" { + const testing = std.testing; + const alloc = testing.allocator; + + { + var cfg = try Config.default(alloc); + defer cfg.deinit(); + var it: TestIterator = .{ .data = &.{ + "--bold-is-bright", + } }; + try cfg.loadIter(alloc, &it); + try cfg.finalize(); + + try testing.expectEqual( + BoldColor.bright, + cfg.@"bold-color", + ); + } +} diff --git a/src/config/edit.zig b/src/config/edit.zig index ae4394942..38dc98169 100644 --- a/src/config/edit.zig +++ b/src/config/edit.zig @@ -5,18 +5,19 @@ const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; const internal_os = @import("../os/main.zig"); -/// Open the configuration in the OS default editor according to the default -/// paths the main config file could be in. +/// The path to the configuration that should be opened for editing. /// -/// On Linux, this will open the file at the XDG config path. This is the +/// On Linux, this will use the file at the XDG config path. This is the /// only valid path for Linux so we don't need to check for other paths. /// /// On macOS, both XDG and AppSupport paths are valid. Because Ghostty -/// prioritizes AppSupport over XDG, we will open AppSupport if it exists, +/// prioritizes AppSupport over XDG, we will use AppSupport if it exists, /// followed by XDG if it exists, and finally AppSupport if neither exist. /// For the existence check, we also prefer non-empty files over empty /// files. -pub fn open(alloc_gpa: Allocator) !void { +/// +/// The returned value is allocated using the provided allocator. +pub fn openPath(alloc_gpa: Allocator) ![:0]const u8 { // Use an arena to make memory management easier in here. var arena = ArenaAllocator.init(alloc_gpa); defer arena.deinit(); @@ -41,7 +42,7 @@ pub fn open(alloc_gpa: Allocator) !void { } }; - try internal_os.open(alloc_gpa, .text, config_path); + return try alloc_gpa.dupeZ(u8, config_path); } /// Returns the config path to use for open for the current OS. diff --git a/src/font/Atlas.zig b/src/font/Atlas.zig index 969318943..7b31e2794 100644 --- a/src/font/Atlas.zig +++ b/src/font/Atlas.zig @@ -251,6 +251,37 @@ pub fn set(self: *Atlas, reg: Region, data: []const u8) void { _ = self.modified.fetchAdd(1, .monotonic); } +/// Like `set` but allows specifying a width for the source data and an +/// offset x and y, so that a section of a larger buffer may be copied +/// in to the atlas. +pub fn setFromLarger( + self: *Atlas, + reg: Region, + src: []const u8, + src_width: u32, + src_x: u32, + src_y: u32, +) void { + assert(reg.x < (self.size - 1)); + assert((reg.x + reg.width) <= (self.size - 1)); + assert(reg.y < (self.size - 1)); + assert((reg.y + reg.height) <= (self.size - 1)); + + const depth = self.format.depth(); + var i: u32 = 0; + while (i < reg.height) : (i += 1) { + const tex_offset = (((reg.y + i) * self.size) + reg.x) * depth; + const src_offset = (((src_y + i) * src_width) + src_x) * depth; + fastmem.copy( + u8, + self.data[tex_offset..], + src[src_offset .. src_offset + (reg.width * depth)], + ); + } + + _ = self.modified.fetchAdd(1, .monotonic); +} + // Grow the texture to the new size, preserving all previously written data. pub fn grow(self: *Atlas, alloc: Allocator, size_new: u32) Allocator.Error!void { assert(size_new >= self.size); @@ -556,6 +587,35 @@ test "writing data" { try testing.expectEqual(@as(u8, 4), atlas.data[66]); } +test "writing data from a larger source" { + const alloc = testing.allocator; + var atlas = try init(alloc, 32, .grayscale); + defer atlas.deinit(alloc); + + const reg = try atlas.reserve(alloc, 2, 2); + const old = atlas.modified.load(.monotonic); + // zig fmt: off + atlas.setFromLarger(reg, &[_]u8{ + 8, 8, 8, 8, 8, + 8, 8, 1, 2, 8, + 8, 8, 3, 4, 8, + 8, 8, 8, 8, 8, + }, 5, 2, 1); + // zig fmt: on + const new = atlas.modified.load(.monotonic); + try testing.expect(new > old); + + // 33 because of the 1px border and so on + try testing.expectEqual(@as(u8, 1), atlas.data[33]); + try testing.expectEqual(@as(u8, 2), atlas.data[34]); + try testing.expectEqual(@as(u8, 3), atlas.data[65]); + try testing.expectEqual(@as(u8, 4), atlas.data[66]); + + // None of the `8`s from the source data outside of the + // specified region should have made it on to the atlas. + try testing.expectEqual(null, std.mem.indexOfScalar(u8, atlas.data, 8)); +} + test "grow" { const alloc = testing.allocator; var atlas = try init(alloc, 4, .grayscale); // +2 for 1px border diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 8533331bc..1d85d8a28 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -69,10 +69,14 @@ pub fn deinit(self: *Collection, alloc: Allocator) void { if (self.load_options) |*v| v.deinit(alloc); } -pub const AddError = Allocator.Error || error{ - CollectionFull, - DeferredLoadingUnavailable, -}; +pub const AddError = + Allocator.Error || + AdjustSizeError || + error{ + CollectionFull, + DeferredLoadingUnavailable, + SetSizeFailed, + }; /// Add a face to the collection for the given style. This face will be added /// next in priority if others exist already, i.e. it'll be the _last_ to be @@ -81,10 +85,9 @@ pub const AddError = Allocator.Error || error{ /// If no error is encountered then the collection takes ownership of the face, /// in which case face will be deallocated when the collection is deallocated. /// -/// If a loaded face is added to the collection, it should be the same -/// size as all the other faces in the collection. This function will not -/// verify or modify the size until the size of the entire collection is -/// changed. +/// If a loaded face is added to the collection, its size will be changed to +/// match the size specified in load_options, adjusted for harmonization with +/// the primary face. pub fn add( self: *Collection, alloc: Allocator, @@ -103,9 +106,107 @@ pub fn add( return error.DeferredLoadingUnavailable; try list.append(alloc, face); + + var owned: *Entry = list.at(idx); + + // If the face is already loaded, apply font size adjustment + // now, otherwise we'll apply it whenever we do load it. + if (owned.getLoaded()) |loaded| { + if (try self.adjustedSize(loaded)) |opts| { + loaded.setSize(opts.faceOptions()) catch return error.SetSizeFailed; + } + } + return .{ .style = style, .idx = @intCast(idx) }; } +pub const AdjustSizeError = font.Face.GetMetricsError; + +// Calculate a size for the provided face that will match it with the primary +// font, metrically, to improve consistency with fallback fonts. Right now we +// match the font based on the ex height, or the ideograph width if the font +// has ideographs in it. +// +// This returns null if load options is null or if self.load_options is null. +// +// This is very much like the `font-size-adjust` CSS property in how it works. +// ref: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust +// +// TODO: In the future, provide config options that allow the user to select +// which metric should be matched for fallback fonts, instead of hard +// coding it as ex height. +pub fn adjustedSize( + self: *Collection, + face: *Face, +) AdjustSizeError!?LoadOptions { + const load_options = self.load_options orelse return null; + + // We silently do nothing if we can't get the primary + // face, because this might be the primary face itself. + const primary_face = self.getFace(.{ .idx = 0 }) catch return null; + + // We do nothing if the primary face and this face are the same. + if (@intFromPtr(primary_face) == @intFromPtr(face)) return null; + + const primary_metrics = try primary_face.getMetrics(); + const face_metrics = try face.getMetrics(); + + // We use the ex height to match our font sizes, so that the height of + // lower-case letters matches between all fonts in the fallback chain. + // + // We estimate ex height as 0.75 * cap height if it's not specifically + // provided, and we estimate cap height as 0.75 * ascent in the same case. + // + // If the fallback font has an ic_width we prefer that, for normalization + // of CJK font sizes when mixed with latin fonts. + // + // We estimate the ic_width as twice the cell width if it isn't provided. + var primary_cap = primary_metrics.cap_height orelse 0.0; + if (primary_cap <= 0) primary_cap = primary_metrics.ascent * 0.75; + + var primary_ex = primary_metrics.ex_height orelse 0.0; + if (primary_ex <= 0) primary_ex = primary_cap * 0.75; + + var primary_ic = primary_metrics.ic_width orelse 0.0; + if (primary_ic <= 0) primary_ic = primary_metrics.cell_width * 2; + + var face_cap = face_metrics.cap_height orelse 0.0; + if (face_cap <= 0) face_cap = face_metrics.ascent * 0.75; + + var face_ex = face_metrics.ex_height orelse 0.0; + if (face_ex <= 0) face_ex = face_cap * 0.75; + + var face_ic = face_metrics.ic_width orelse 0.0; + if (face_ic <= 0) face_ic = face_metrics.cell_width * 2; + + // If the line height of the scaled font would be larger than + // the line height of the primary font, we don't want that, so + // we take the minimum between matching the ic/ex and the line + // height. + // + // NOTE: We actually allow the line height to be up to 1.2 + // times the primary line height because empirically + // this is usually fine and is better for CJK. + // + // TODO: We should probably provide a config option that lets + // the user pick what metric to use for size adjustment. + const scale = @min( + 1.2 * primary_metrics.lineHeight() / face_metrics.lineHeight(), + if (face_metrics.ic_width != null) + primary_ic / face_ic + else + primary_ex / face_ex, + ); + + // Make a copy of our load options, set the size to the size of + // the provided face, and then multiply that by our scaling factor. + var opts = load_options; + opts.size = face.size; + opts.size.points *= @as(f32, @floatCast(scale)); + + return opts; +} + /// Return the Face represented by a given Index. The returned pointer /// is only valid as long as this collection is not modified. /// @@ -129,21 +230,38 @@ pub fn getFace(self: *Collection, index: Index) !*Face { break :item item; }; - return try self.getFaceFromEntry(item); + const face = try self.getFaceFromEntry( + item, + // We only want to adjust the size if this isn't the primary face. + index.style != .regular or index.idx > 0, + ); + + return face; } /// Get the face from an entry. /// /// This entry must not be an alias. -fn getFaceFromEntry(self: *Collection, entry: *Entry) !*Face { +fn getFaceFromEntry( + self: *Collection, + entry: *Entry, + /// Whether to adjust the font size to match the primary face after loading. + adjust: bool, +) !*Face { assert(entry.* != .alias); return switch (entry.*) { inline .deferred, .fallback_deferred => |*d, tag| deferred: { const opts = self.load_options orelse return error.DeferredLoadingUnavailable; - const face = try d.load(opts.library, opts.faceOptions()); + var face = try d.load(opts.library, opts.faceOptions()); d.deinit(); + + // If we need to adjust the size, do so. + if (adjust) if (try self.adjustedSize(&face)) |new_opts| { + try face.setSize(new_opts.faceOptions()); + }; + entry.* = switch (tag) { .deferred => .{ .loaded = face }, .fallback_deferred => .{ .fallback_loaded = face }, @@ -247,7 +365,7 @@ pub fn completeStyles( while (it.next()) |entry| { // Load our face. If we fail to load it, we just skip it and // continue on to try the next one. - const face = self.getFaceFromEntry(entry) catch |err| { + const face = self.getFaceFromEntry(entry, false) catch |err| { log.warn("error loading regular entry={d} err={}", .{ it.index - 1, err, @@ -371,7 +489,7 @@ fn syntheticBold(self: *Collection, entry: *Entry) !Face { const opts = self.load_options orelse return error.DeferredLoadingUnavailable; // Try to bold it. - const regular = try self.getFaceFromEntry(entry); + const regular = try self.getFaceFromEntry(entry, false); const face = try regular.syntheticBold(opts.faceOptions()); var buf: [256]u8 = undefined; @@ -391,7 +509,7 @@ fn syntheticItalic(self: *Collection, entry: *Entry) !Face { const opts = self.load_options orelse return error.DeferredLoadingUnavailable; // Try to italicize it. - const regular = try self.getFaceFromEntry(entry); + const regular = try self.getFaceFromEntry(entry, false); const face = try regular.syntheticItalic(opts.faceOptions()); var buf: [256]u8 = undefined; @@ -420,9 +538,12 @@ pub fn setSize(self: *Collection, size: DesiredSize) !void { while (it.next()) |array| { var entry_it = array.value.iterator(0); while (entry_it.next()) |entry| switch (entry.*) { - .loaded, .fallback_loaded => |*f| try f.setSize( - opts.faceOptions(), - ), + .loaded, + .fallback_loaded, + => |*f| { + const new_opts = try self.adjustedSize(f) orelse opts.*; + try f.setSize(new_opts.faceOptions()); + }, // Deferred aren't loaded so we don't need to set their size. // The size for when they're loaded is set since `opts` changed. @@ -549,6 +670,16 @@ pub const Entry = union(enum) { } } + /// If this face is loaded, or is an alias to a loaded face, + /// then this returns the `Face`, otherwise returns null. + pub fn getLoaded(self: *Entry) ?*Face { + return switch (self.*) { + .deferred, .fallback_deferred => null, + .loaded, .fallback_loaded => |*face| face, + .alias => |v| v.getLoaded(), + }; + } + /// True if the entry is deferred. fn isDeferred(self: Entry) bool { return switch (self) { @@ -906,12 +1037,13 @@ test "metrics" { var c = init(); defer c.deinit(alloc); - c.load_options = .{ .library = lib }; + const size: DesiredSize = .{ .points = 12, .xdpi = 96, .ydpi = 96 }; + c.load_options = .{ .library = lib, .size = size }; _ = try c.add(alloc, .regular, .{ .loaded = try .init( lib, testFont, - .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } }, + .{ .size = size }, ) }); try c.updateMetrics(); @@ -958,3 +1090,62 @@ test "metrics" { .cursor_height = 34, }, c.metrics); } + +// TODO: Also test CJK fallback sizing, we don't currently have a CJK test font. +test "adjusted sizes" { + const testing = std.testing; + const alloc = testing.allocator; + const testFont = font.embedded.inconsolata; + const fallback = font.embedded.monaspace_neon; + + var lib = try Library.init(alloc); + defer lib.deinit(); + + var c = init(); + defer c.deinit(alloc); + const size: DesiredSize = .{ .points = 12, .xdpi = 96, .ydpi = 96 }; + c.load_options = .{ .library = lib, .size = size }; + + // Add our primary face. + _ = try c.add(alloc, .regular, .{ .loaded = try .init( + lib, + testFont, + .{ .size = size }, + ) }); + + try c.updateMetrics(); + + // Add the fallback face. + const fallback_idx = try c.add(alloc, .regular, .{ .loaded = try .init( + lib, + fallback, + .{ .size = size }, + ) }); + + // The ex heights should match. + { + const primary_metrics = try (try c.getFace(.{ .idx = 0 })).getMetrics(); + const fallback_metrics = try (try c.getFace(fallback_idx)).getMetrics(); + + try std.testing.expectApproxEqAbs( + primary_metrics.ex_height.?, + fallback_metrics.ex_height.?, + // We accept anything within half a pixel. + 0.5, + ); + } + + // Resize should keep that relationship. + try c.setSize(.{ .points = 37, .xdpi = 96, .ydpi = 96 }); + { + const primary_metrics = try (try c.getFace(.{ .idx = 0 })).getMetrics(); + const fallback_metrics = try (try c.getFace(fallback_idx)).getMetrics(); + + try std.testing.expectApproxEqAbs( + primary_metrics.ex_height.?, + fallback_metrics.ex_height.?, + // We accept anything within half a pixel. + 0.5, + ); + } +} diff --git a/src/font/Glyph.zig b/src/font/Glyph.zig index 5449e2440..f99370271 100644 --- a/src/font/Glyph.zig +++ b/src/font/Glyph.zig @@ -17,6 +17,3 @@ offset_y: i32, /// be normalized to be between 0 and 1 prior to use in shaders. atlas_x: u32, atlas_y: u32, - -/// horizontal position to increase drawing position for strings -advance_x: f32, diff --git a/src/font/Metrics.zig b/src/font/Metrics.zig index bf527a021..f96d753b3 100644 --- a/src/font/Metrics.zig +++ b/src/font/Metrics.zig @@ -107,6 +107,19 @@ pub const FaceMetrics = struct { /// a provided ex height metric or measured from the height of the /// lowercase x glyph. ex_height: ?f64 = null, + + /// The width of the character "水" (CJK water ideograph, U+6C34), + /// if present. This is used for font size adjustment, to normalize + /// the width of CJK fonts mixed with latin fonts. + /// + /// NOTE: IC = Ideograph Character + ic_width: ?f64 = null, + + /// Convenience function for getting the line height + /// (ascent - descent + line_gap). + pub inline fn lineHeight(self: FaceMetrics) f64 { + return self.ascent - self.descent + self.line_gap; + } }; /// Calculate our metrics based on values extracted from a font. diff --git a/src/font/SharedGrid.zig b/src/font/SharedGrid.zig index dcfa0a551..3ccac7fa1 100644 --- a/src/font/SharedGrid.zig +++ b/src/font/SharedGrid.zig @@ -265,13 +265,35 @@ pub fn renderGlyph( .emoji => &self.atlas_color, }; + var render_opts = opts; + + // Always use these constraints for emoji. + if (p == .emoji) { + render_opts.constraint = .{ + // Make the emoji as wide as possible, scaling proportionally, + // but then scale it down as necessary if its new size exceeds + // the cell height. + .size_horizontal = .cover, + .size_vertical = .fit, + + // Center the emoji in its cells. + .align_horizontal = .center, + .align_vertical = .center, + + // Add a small bit of padding so the emoji + // doesn't quite touch the edges of the cells. + .pad_left = 0.025, + .pad_right = 0.025, + }; + } + // Render into the atlas const glyph = self.resolver.renderGlyph( alloc, atlas, index, glyph_index, - opts, + render_opts, ) catch |err| switch (err) { // If the atlas is full, we resize it error.AtlasFull => blk: { @@ -281,7 +303,7 @@ pub fn renderGlyph( atlas, index, glyph_index, - opts, + render_opts, ); }, @@ -325,7 +347,8 @@ const GlyphKey = struct { cell_width: u2, thicken: bool, thicken_strength: u8, - _padding: u5 = 0, + constraint_width: u2, + _padding: u3 = 0, }, inline fn from(key: GlyphKey) Packed { @@ -336,6 +359,7 @@ const GlyphKey = struct { .cell_width = key.opts.cell_width orelse 0, .thicken = key.opts.thicken, .thicken_strength = key.opts.thicken_strength, + .constraint_width = key.opts.constraint_width, }, }; } diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index e3e61907b..b77b44f23 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -260,34 +260,51 @@ fn collection( .regular, .{ .fallback_loaded = try .init( self.font_lib, - font.embedded.regular, + font.embedded.variable, load_options.faceOptions(), ) }, ); - _ = try c.add( + try (try c.getFace(try c.add( self.alloc, .bold, .{ .fallback_loaded = try .init( self.font_lib, - font.embedded.bold, + font.embedded.variable, load_options.faceOptions(), ) }, + ))).setVariations( + &.{.{ .id = .init("wght"), .value = 700 }}, + load_options.faceOptions(), ); _ = try c.add( self.alloc, .italic, .{ .fallback_loaded = try .init( self.font_lib, - font.embedded.italic, + font.embedded.variable_italic, load_options.faceOptions(), ) }, ); - _ = try c.add( + try (try c.getFace(try c.add( self.alloc, .bold_italic, .{ .fallback_loaded = try .init( self.font_lib, - font.embedded.bold_italic, + font.embedded.variable_italic, + load_options.faceOptions(), + ) }, + ))).setVariations( + &.{.{ .id = .init("wght"), .value = 700 }}, + load_options.faceOptions(), + ); + + // Nerd-font symbols fallback. + _ = try c.add( + self.alloc, + .regular, + .{ .fallback_loaded = try Face.init( + self.font_lib, + font.embedded.symbols_nerd_font, load_options.faceOptions(), ) }, ); diff --git a/src/font/discovery.zig b/src/font/discovery.zig index 9284f9486..6f51379b4 100644 --- a/src/font/discovery.zig +++ b/src/font/discovery.zig @@ -831,6 +831,9 @@ pub const CoreText = struct { i: usize, pub fn deinit(self: *DiscoverIterator) void { + for (self.list) |desc| { + desc.release(); + } self.alloc.free(self.list); self.* = undefined; } diff --git a/src/font/embedded.zig b/src/font/embedded.zig index 31b07ff31..1e496075d 100644 --- a/src/font/embedded.zig +++ b/src/font/embedded.zig @@ -6,19 +6,29 @@ //! redistribution and include their license as necessary. /// Default fonts that we prefer for Ghostty. -pub const regular = @embedFile("res/JetBrainsMonoNerdFont-Regular.ttf"); -pub const bold = @embedFile("res/JetBrainsMonoNerdFont-Bold.ttf"); -pub const italic = @embedFile("res/JetBrainsMonoNerdFont-Italic.ttf"); -pub const bold_italic = @embedFile("res/JetBrainsMonoNerdFont-BoldItalic.ttf"); +pub const variable = @embedFile("jetbrains_mono_variable"); +pub const variable_italic = @embedFile("jetbrains_mono_variable_italic"); + +/// Symbols-only nerd font. +pub const symbols_nerd_font = @embedFile("nerd_fonts_symbols_only"); + +/// Static jetbrains mono faces, currently unused. +pub const regular = @embedFile("jetbrains_mono_regular"); +pub const bold = @embedFile("jetbrains_mono_bold"); +pub const italic = @embedFile("jetbrains_mono_italic"); +pub const bold_italic = @embedFile("jetbrains_mono_bold_italic"); + +/// Emoji fonts pub const emoji = @embedFile("res/NotoColorEmoji.ttf"); pub const emoji_text = @embedFile("res/NotoEmoji-Regular.ttf"); +// Fonts below are ONLY used for testing. + /// Fonts with general properties pub const arabic = @embedFile("res/KawkabMono-Regular.ttf"); -pub const variable = @embedFile("res/Lilex-VF.ttf"); -/// Font with nerd fonts embedded. -pub const nerd_font = @embedFile("res/JetBrainsMonoNerdFont-Regular.ttf"); +/// A font for testing which is patched with nerd font symbols. +pub const test_nerd_font = @embedFile("res/JetBrainsMonoNerdFont-Regular.ttf"); /// Specific font families below: pub const code_new_roman = @embedFile("res/CodeNewRoman-Regular.otf"); diff --git a/src/font/face.zig b/src/font/face.zig index 6355578db..245edcf4b 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -94,6 +94,17 @@ pub const RenderOptions = struct { /// optionally by the rasterizer to better layout the glyph. cell_width: ?u2 = null, + /// Constraint and alignment properties for the glyph. The rasterizer + /// should call the `constrain` function on this with the original size + /// and bearings of the glyph to get remapped values that the glyph + /// should be scaled/moved to. + constraint: Constraint = .none, + + /// The number of cells, horizontally that the glyph is free to take up + /// when resized and aligned by `constraint`. This is usually 1, but if + /// there's whitespace to the right of the cell then it can be 2. + constraint_width: u2 = 1, + /// Thicken the glyph. This draws the glyph with a thicker stroke width. /// This is purely an aesthetic setting. /// @@ -108,6 +119,211 @@ pub const RenderOptions = struct { /// /// CoreText only. thicken_strength: u8 = 255, + + /// See the `constraint` field. + pub const Constraint = struct { + /// Don't constrain the glyph in any way. + pub const none: Constraint = .{}; + + /// Vertical sizing rule. + size_vertical: Size = .none, + /// Horizontal sizing rule. + size_horizontal: Size = .none, + + /// Vertical alignment rule. + align_vertical: Align = .none, + /// Horizontal alignment rule. + align_horizontal: Align = .none, + + /// Top padding when resizing. + pad_top: f64 = 0.0, + /// Left padding when resizing. + pad_left: f64 = 0.0, + /// Right padding when resizing. + pad_right: f64 = 0.0, + /// Bottom padding when resizing. + pad_bottom: f64 = 0.0, + + /// Maximum ratio of width to height when resizing. + max_xy_ratio: ?f64 = null, + + /// Maximum number of cells horizontally to use. + max_constraint_width: u2 = 2, + + pub const Size = enum { + /// Don't change the size of this glyph. + none, + /// Move the glyph and optionally scale it down + /// proportionally to fit within the given axis. + fit, + /// Move and resize the glyph proportionally to + /// cover the given axis. + cover, + /// Same as `cover` but not proportional. + stretch, + }; + + pub const Align = enum { + /// Don't move the glyph on this axis. + none, + /// Move the glyph so that its leading (bottom/left) + /// edge aligns with the leading edge of the axis. + start, + /// Move the glyph so that its trailing (top/right) + /// edge aligns with the trailing edge of the axis. + end, + /// Move the glyph so that it is centered on this axis. + center, + }; + + /// The size and position of a glyph. + pub const GlyphSize = struct { + width: f64, + height: f64, + x: f64, + y: f64, + }; + + /// Apply this constraint to the provided glyph + /// size, given the available width and height. + pub fn constrain( + self: Constraint, + glyph: GlyphSize, + /// Width of one cell. + cell_width: f64, + /// Height of one cell. + cell_height: f64, + /// Number of cells horizontally available for this glyph. + constraint_width: u2, + ) GlyphSize { + var g = glyph; + + const available_width = + cell_width * @as(f64, @floatFromInt( + @min( + self.max_constraint_width, + constraint_width, + ), + )); + + const w = available_width - + self.pad_left * available_width - + self.pad_right * available_width; + const h = cell_height - + self.pad_top * cell_height - + self.pad_bottom * cell_height; + + // Subtract padding from the bearings so that our + // alignment and sizing code works correctly. We + // re-add before returning. + g.x -= self.pad_left * available_width; + g.y -= self.pad_bottom * cell_height; + + switch (self.size_horizontal) { + .none => {}, + .fit => if (g.width > w) { + const orig_height = g.height; + // Adjust our height and width to proportionally + // scale them to fit the glyph to the cell width. + g.height *= w / g.width; + g.width = w; + // Set our x to 0 since anything else would mean + // the glyph extends outside of the cell width. + g.x = 0; + // Compensate our y to keep things vertically + // centered as they're scaled down. + g.y += (orig_height - g.height) / 2; + } else if (g.width + g.x > w) { + // If the width of the glyph can fit in the cell but + // is currently outside due to the left bearing, then + // we reduce the left bearing just enough to fit it + // back in the cell. + g.x = w - g.width; + } else if (g.x < 0) { + g.x = 0; + }, + .cover => { + const orig_height = g.height; + + g.height *= w / g.width; + g.width = w; + + g.x = 0; + + g.y += (orig_height - g.height) / 2; + }, + .stretch => { + g.width = w; + g.x = 0; + }, + } + + switch (self.size_vertical) { + .none => {}, + .fit => if (g.height > h) { + const orig_width = g.width; + // Adjust our height and width to proportionally + // scale them to fit the glyph to the cell height. + g.width *= h / g.height; + g.height = h; + // Set our y to 0 since anything else would mean + // the glyph extends outside of the cell height. + g.y = 0; + // Compensate our x to keep things horizontally + // centered as they're scaled down. + g.x += (orig_width - g.width) / 2; + } else if (g.height + g.y > h) { + // If the height of the glyph can fit in the cell but + // is currently outside due to the bottom bearing, then + // we reduce the bottom bearing just enough to fit it + // back in the cell. + g.y = h - g.height; + } else if (g.y < 0) { + g.y = 0; + }, + .cover => { + const orig_width = g.width; + + g.width *= h / g.height; + g.height = h; + + g.y = 0; + + g.x += (orig_width - g.width) / 2; + }, + .stretch => { + g.height = h; + g.y = 0; + }, + } + + if (self.max_xy_ratio) |ratio| if (g.width > g.height * ratio) { + const orig_width = g.width; + g.width = g.height * ratio; + g.x += (orig_width - g.width) / 2; + }; + + switch (self.align_horizontal) { + .none => {}, + .start => g.x = 0, + .end => g.x = w - g.width, + .center => g.x = (w - g.width) / 2, + } + + switch (self.align_vertical) { + .none => {}, + .start => g.y = 0, + .end => g.y = h - g.height, + .center => g.y = (h - g.height) / 2, + } + + // Re-add our padding before returning. + g.x += self.pad_left * available_width; + g.y += self.pad_bottom * cell_height; + + return g; + } + }; }; test { diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 06bba661f..00cc31b26 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -31,6 +31,9 @@ pub const Face = struct { /// tables). color: ?ColorState = null, + /// The current size this font is set to. + size: font.face.DesiredSize, + /// True if our build is using Harfbuzz. If we're not, we can avoid /// some Harfbuzz-specific code paths. const harfbuzz_shaper = font.options.backend.hasHarfbuzz(); @@ -106,6 +109,7 @@ pub const Face = struct { .font = ct_font, .hb_font = hb_font, .color = color, + .size = opts.size, }; result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); @@ -291,22 +295,29 @@ pub const Face = struct { // in the bottom left and +Y pointing up. var rect = self.font.getBoundingRectsForGlyphs(.horizontal, &glyphs, null); + // Determine whether this is a color glyph. + const is_color = self.isColorGlyph(glyph_index); + // And whether it's (probably) a bitmap (sbix). + const sbix = is_color and self.color != null and self.color.?.sbix; + // If we're rendering a synthetic bold then we will gain 50% of // the line width on every edge, which means we should increase // our width and height by the line width and subtract half from // our origin points. - if (self.synthetic_bold) |line_width| { + // + // We don't add extra size if it's a sbix color font though, + // since bitmaps aren't affected by synthetic bold. + if (!sbix) if (self.synthetic_bold) |line_width| { rect.size.width += line_width; rect.size.height += line_width; rect.origin.x -= line_width / 2; rect.origin.y -= line_width / 2; - } + }; // We make an assumption that font smoothing ("thicken") // adds no more than 1 extra pixel to any edge. We don't // add extra size if it's a sbix color font though, since // bitmaps aren't affected by smoothing. - const sbix = self.color != null and self.color.?.sbix; if (opts.thicken and !sbix) { rect.size.width += 2.0; rect.size.height += 2.0; @@ -314,29 +325,49 @@ pub const Face = struct { rect.origin.y -= 1.0; } - // We compute the minimum and maximum x and y values. - // We round our min points down and max points up. - const x0: i32, const x1: i32, const y0: i32, const y1: i32 = .{ - @intFromFloat(@floor(rect.origin.x)), - @intFromFloat(@ceil(rect.origin.x) + @ceil(rect.size.width)), - @intFromFloat(@floor(rect.origin.y)), - @intFromFloat(@ceil(rect.origin.y) + @ceil(rect.size.height)), - }; + // If our rect is smaller than a quarter pixel in either axis + // then it has no outlines or they're too small to render. + // + // In this case we just return 0-sized glyph struct. + if (rect.size.width < 0.25 or rect.size.height < 0.25) + return font.Glyph{ + .width = 0, + .height = 0, + .offset_x = 0, + .offset_y = 0, + .atlas_x = 0, + .atlas_y = 0, + }; - // This bitmap is blank. I've seen it happen in a font, I don't know why. - // If it is empty, we just return a valid glyph struct that does nothing. - if (x1 <= x0 or y1 <= y0) return font.Glyph{ - .width = 0, - .height = 0, - .offset_x = 0, - .offset_y = 0, - .atlas_x = 0, - .atlas_y = 0, - .advance_x = 0, - }; + const metrics = opts.grid_metrics; + const cell_width: f64 = @floatFromInt(metrics.cell_width); + const cell_height: f64 = @floatFromInt(metrics.cell_height); - const width: u32 = @intCast(x1 - x0); - const height: u32 = @intCast(y1 - y0); + const glyph_size = opts.constraint.constrain( + .{ + .width = rect.size.width, + .height = rect.size.height, + .x = rect.origin.x, + .y = rect.origin.y + @as(f64, @floatFromInt(metrics.cell_baseline)), + }, + cell_width, + cell_height, + opts.constraint_width, + ); + + const width = glyph_size.width; + const height = glyph_size.height; + const x = glyph_size.x; + const y = glyph_size.y; + + // We have to include the fractional pixels that we won't be offsetting + // in our width and height calculations, that is, we offset by the floor + // of the bearings when we render the glyph, meaning there's still a bit + // of extra width to the area that's drawn in beyond just the width of + // the glyph itself, so we include that extra fraction of a pixel when + // calculating the width and height here. + const px_width: u32 = @intFromFloat(@ceil(width + rect.origin.x - @floor(rect.origin.x))); + const px_height: u32 = @intFromFloat(@ceil(height + rect.origin.y - @floor(rect.origin.y))); // Settings that are specific to if we are rendering text or emoji. const color: struct { @@ -344,7 +375,7 @@ pub const Face = struct { depth: u32, space: *macos.graphics.ColorSpace, context_opts: c_uint, - } = if (!self.isColorGlyph(glyph_index)) .{ + } = if (!is_color) .{ .color = false, .depth = 1, .space = try macos.graphics.ColorSpace.createNamed(.linearGray), @@ -371,17 +402,17 @@ pub const Face = struct { // usually stabilizes pretty quickly and is very infrequent so I think // the allocation overhead is acceptable compared to the cost of // caching it forever or having to deal with a cache lifetime. - const buf = try alloc.alloc(u8, width * height * color.depth); + const buf = try alloc.alloc(u8, px_width * px_height * color.depth); defer alloc.free(buf); @memset(buf, 0); const context = macos.graphics.BitmapContext.context; const ctx = try macos.graphics.BitmapContext.create( buf, - width, - height, + px_width, + px_height, 8, - width * color.depth, + px_width * color.depth, color.space, color.context_opts, ); @@ -390,14 +421,14 @@ pub const Face = struct { // Perform an initial fill. This ensures that we don't have any // uninitialized pixels in the bitmap. if (color.color) - context.setRGBFillColor(ctx, 1, 1, 1, 0) + context.setRGBFillColor(ctx, 0, 0, 0, 0) else - context.setGrayFillColor(ctx, 1, 0); + context.setGrayFillColor(ctx, 0, 0); context.fillRect(ctx, .{ .origin = .{ .x = 0, .y = 0 }, .size = .{ - .width = @floatFromInt(width), - .height = @floatFromInt(height), + .width = @floatFromInt(px_width), + .height = @floatFromInt(px_height), }, }); @@ -427,76 +458,78 @@ pub const Face = struct { context.setLineWidth(ctx, line_width); } + context.scaleCTM( + ctx, + width / rect.size.width, + height / rect.size.height, + ); + // We want to render the glyphs at (0,0), but the glyphs themselves // are offset by bearings, so we have to undo those bearings in order // to get them to 0,0. - self.font.drawGlyphs(&glyphs, &.{ - .{ - .x = @floatFromInt(-x0), - .y = @floatFromInt(-y0), - }, - }, ctx); + self.font.drawGlyphs(&glyphs, &.{.{ + .x = -@floor(rect.origin.x), + .y = -@floor(rect.origin.y), + }}, ctx); - const region = region: { - // We reserve a region that's 1px wider and taller than we need - // in order to create a 1px separation between adjacent glyphs - // to prevent interpolation with adjacent glyphs while sampling - // from the atlas. - var region = try atlas.reserve( - alloc, - width + 1, - height + 1, - ); - - // We adjust the region width and height back down since we - // don't need the extra pixel, we just needed to reserve it - // so that it isn't used for other glyphs in the future. - region.width -= 1; - region.height -= 1; - break :region region; - }; + // Write our rasterized glyph to the atlas. + const region = try atlas.reserve(alloc, px_width, px_height); atlas.set(region, buf); - const metrics = opts.grid_metrics; - // This should be the distance from the bottom of // the cell to the top of the glyph's bounding box. - // - // The calculation is distance from bottom of cell to - // baseline plus distance from baseline to top of glyph. - const offset_y: i32 = @as(i32, @intCast(metrics.cell_baseline)) + y1; + const offset_y: i32 = + @as(i32, @intFromFloat(@floor(y))) + + @as(i32, @intCast(px_height)); // This should be the distance from the left of // the cell to the left of the glyph's bounding box. const offset_x: i32 = offset_x: { - var result: i32 = x0; - - // If our cell was resized then we adjust our glyph's - // position relative to the new center. This keeps glyphs - // centered in the cell whether it was made wider or narrower. - if (metrics.original_cell_width) |original_width| { - const before: i32 = @intCast(original_width); - const after: i32 = @intCast(metrics.cell_width); - // Increase the offset by half of the difference - // between the widths to keep things centered. - result += @divTrunc(after - before, 2); + // 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) { + var advances: [glyphs.len]macos.graphics.Size = undefined; + _ = self.font.getAdvancesForGlyphs(.horizontal, &glyphs, &advances); + const advance = advances[0].width; + 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)); } - - break :offset_x result; }; - // Get our advance - var advances: [glyphs.len]macos.graphics.Size = undefined; - _ = self.font.getAdvancesForGlyphs(.horizontal, &glyphs, &advances); - return .{ - .width = width, - .height = height, + .width = px_width, + .height = px_height, .offset_x = offset_x, .offset_y = offset_y, .atlas_x = region.x, .atlas_y = region.y, - .advance_x = @floatCast(advances[0].width), }; } @@ -728,6 +761,20 @@ pub const Face = struct { break :cell_width max; }; + // Measure "水" (CJK water ideograph, U+6C34) for our ic width. + const ic_width: ?f64 = ic_width: { + const glyph = self.glyphIndex('水') orelse break :ic_width null; + + var advances: [1]macos.graphics.Size = undefined; + _ = ct_font.getAdvancesForGlyphs( + .horizontal, + &.{@intCast(glyph)}, + &advances, + ); + + break :ic_width advances[0].width; + }; + return .{ .cell_width = cell_width, .ascent = ascent, @@ -739,6 +786,7 @@ pub const Face = struct { .strikethrough_thickness = strikethrough_thickness, .cap_height = cap_height, .ex_height = ex_height, + .ic_width = ic_width, }; } diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index accb891a4..ae3bd0968 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -15,12 +15,13 @@ const Allocator = std.mem.Allocator; const font = @import("../main.zig"); const Glyph = font.Glyph; const Library = font.Library; -const convert = @import("freetype_convert.zig"); const opentype = @import("../opentype.zig"); const fastmem = @import("../../fastmem.zig"); const quirks = @import("../../quirks.zig"); const config = @import("../../config.zig"); +const F26Dot6 = opentype.sfnt.F26Dot6; + const log = std.log.scoped(.font_face); pub const Face = struct { @@ -58,13 +59,8 @@ pub const Face = struct { bold: bool = false, } = .{}, - /// The matrix applied to a regular font to create a synthetic italic. - const italic_matrix: freetype.c.FT_Matrix = .{ - .xx = 0x10000, - .xy = 0x044ED, // approx. tan(15) - .yx = 0, - .yy = 0x10000, - }; + /// The current size this font is set to. + size: font.face.DesiredSize, /// Initialize a new font face with the given source in-memory. pub fn initFile( @@ -114,6 +110,7 @@ pub const Face = struct { .hb_font = hb_font, .ft_mutex = ft_mutex, .load_flags = opts.freetype_load_flags, + .size = opts.size, }; result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result); @@ -210,6 +207,7 @@ pub const Face = struct { /// for clearing any glyph caches, font atlas data, etc. pub fn setSize(self: *Face, opts: font.face.Options) !void { try setSize_(self.face, opts.size); + self.size = opts.size; } fn setSize_(face: freetype.Face, size: font.face.DesiredSize) !void { @@ -330,26 +328,32 @@ pub const Face = struct { self.ft_mutex.lock(); defer self.ft_mutex.unlock(); - const metrics = opts.grid_metrics; + // We enable hinting by default, and disable it if either of the + // constraint alignments are not center or none, since this means + // that the glyph needs to be aligned flush to the cell edge, and + // hinting can mess that up. + const do_hinting = self.load_flags.hinting and + switch (opts.constraint.align_horizontal) { + .start, .end => false, + .center, .none => true, + } and + switch (opts.constraint.align_vertical) { + .start, .end => false, + .center, .none => true, + }; - // If we have synthetic italic, then we apply a transformation matrix. - // We have to undo this because synthetic italic works by increasing - // the ref count of the base face. - if (self.synthetic.italic) self.face.setTransform(&italic_matrix, null); - defer if (self.synthetic.italic) self.face.setTransform(null, null); - - // If our glyph has color, we want to render the color + // Load the glyph. try self.face.loadGlyph(glyph_index, .{ + // If our glyph has color, we want to render the color .color = self.face.hasColor(), - // If we have synthetic bold, we have to set some additional - // glyph properties before render so we don't render here. - .render = !self.synthetic.bold, + // We don't render, because we'll invoke the render + // manually after applying constraints further down. + .render = false, // use options from config - .no_hinting = !self.load_flags.hinting, - .force_autohint = !self.load_flags.@"force-autohint", - .monochrome = !self.load_flags.monochrome, + .no_hinting = !do_hinting, + .force_autohint = self.load_flags.@"force-autohint", .no_autohint = !self.load_flags.autohint, // NO_SVG set to true because we don't currently support rendering @@ -359,260 +363,329 @@ pub const Face = struct { }); const glyph = self.face.handle.*.glyph; - // For synthetic bold, we embolden the glyph and render it. + const glyph_width: f64 = f26dot6ToF64(glyph.*.metrics.width); + const glyph_height: f64 = f26dot6ToF64(glyph.*.metrics.height); + + // If our glyph is smaller than a quarter pixel in either axis + // then it has no outlines or they're too small to render. + // + // In this case we just return 0-sized glyph struct. + if (glyph_width < 0.25 or glyph_height < 0.25) + return font.Glyph{ + .width = 0, + .height = 0, + .offset_x = 0, + .offset_y = 0, + .atlas_x = 0, + .atlas_y = 0, + }; + + // For synthetic bold, we embolden the glyph. if (self.synthetic.bold) { // We need to scale the embolden amount based on the font size. // This is a heuristic I found worked well across a variety of // founts: 1 pixel per 64 units of height. - const height: f64 = @floatFromInt(self.face.handle.*.size.*.metrics.height); + const font_height: f64 = @floatFromInt(self.face.handle.*.size.*.metrics.height); const ratio: f64 = 64.0 / 2048.0; - const amount = @ceil(height * ratio); + const amount = @ceil(font_height * ratio); _ = freetype.c.FT_Outline_Embolden(&glyph.*.outline, @intFromFloat(amount)); - try self.face.renderGlyph(.normal); } - // This bitmap is blank. I've seen it happen in a font, I don't know why. - // If it is empty, we just return a valid glyph struct that does nothing. - const bitmap_ft = glyph.*.bitmap; - if (bitmap_ft.rows == 0) return .{ - .width = 0, - .height = 0, - .offset_x = 0, - .offset_y = 0, - .atlas_x = 0, - .atlas_y = 0, - .advance_x = 0, - }; + // Next we need to apply any constraints. + const metrics = opts.grid_metrics; - // Ensure we know how to work with the font format. And assure that - // or color depth is as expected on the texture atlas. If format is null - // it means there is no native color format for our Atlas and we must try - // conversion. - const format: ?font.Atlas.Format = switch (bitmap_ft.pixel_mode) { - freetype.c.FT_PIXEL_MODE_MONO => null, - freetype.c.FT_PIXEL_MODE_GRAY => .grayscale, - freetype.c.FT_PIXEL_MODE_BGRA => .bgra, + const cell_width: f64 = @floatFromInt(metrics.cell_width); + const cell_height: f64 = @floatFromInt(metrics.cell_height); + + const glyph_x: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingX); + const glyph_y: f64 = f26dot6ToF64(glyph.*.metrics.horiBearingY) - glyph_height; + + const glyph_size = opts.constraint.constrain( + .{ + .width = glyph_width, + .height = glyph_height, + .x = glyph_x, + .y = glyph_y + @as(f64, @floatFromInt(metrics.cell_baseline)), + }, + cell_width, + cell_height, + opts.constraint_width, + ); + + const width = glyph_size.width; + const height = glyph_size.height; + // This may need to be adjusted later on. + var x = glyph_size.x; + const y = glyph_size.y; + + // Now we can render the glyph. + var bitmap: freetype.c.FT_Bitmap = undefined; + _ = freetype.c.FT_Bitmap_Init(&bitmap); + defer _ = freetype.c.FT_Bitmap_Done(self.lib.lib.handle, &bitmap); + switch (glyph.*.format) { + freetype.c.FT_GLYPH_FORMAT_OUTLINE => { + // Manually adjust the glyph outline with this transform. + // + // This offers better precision than using the freetype transform + // matrix, since that has 16.16 coefficients, and also I was having + // weird issues that I can only assume where due to freetype doing + // some bad caching or something when I did this using the matrix. + const scale_x = width / glyph_width; + const scale_y = height / glyph_height; + const skew: f64 = + if (self.synthetic.italic) + // We skew by 12 degrees to synthesize italics. + @tan(std.math.degreesToRadians(12)) + else + 0.0; + + var bbox_before: freetype.c.FT_BBox = undefined; + _ = freetype.c.FT_Outline_Get_BBox(&glyph.*.outline, &bbox_before); + + const outline = &glyph.*.outline; + for (outline.points[0..@intCast(outline.n_points)]) |*p| { + // Convert to f64 for processing + var px = f26dot6ToF64(p.x); + var py = f26dot6ToF64(p.y); + + // Scale + px *= scale_x; + py *= scale_y; + + // Skew + px += py * skew; + + // Convert back and store + p.x = @as(i32, @bitCast(F26Dot6.from(px))); + 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( + if (self.load_flags.monochrome) + .mono + else + .normal, + ); + + // Copy the glyph's bitmap, making sure + // that it's 8bpp and densely packed. + if (freetype.c.FT_Bitmap_Convert( + self.lib.lib.handle, + &glyph.*.bitmap, + &bitmap, + 1, + ) != 0) { + return error.BitmapHandlingError; + } + }, + + freetype.c.FT_GLYPH_FORMAT_BITMAP => { + // If our glyph has a non-color bitmap, we need + // to convert it to dense 8bpp so that the scale + // operation works correctly. + switch (glyph.*.bitmap.pixel_mode) { + freetype.c.FT_PIXEL_MODE_BGRA, + freetype.c.FT_PIXEL_MODE_GRAY, + => {}, + else => { + var converted: freetype.c.FT_Bitmap = undefined; + freetype.c.FT_Bitmap_Init(&converted); + if (freetype.c.FT_Bitmap_Convert( + self.lib.lib.handle, + &glyph.*.bitmap, + &converted, + 1, + ) != 0) { + return error.BitmapHandlingError; + } + // Free the existing glyph bitmap and + // replace it with the converted one. + _ = freetype.c.FT_Bitmap_Done( + self.lib.lib.handle, + &glyph.*.bitmap, + ); + glyph.*.bitmap = converted; + }, + } + + const glyph_bitmap = glyph.*.bitmap; + + // Round our target width and height + // as the size for our scaled bitmap. + const w: u32 = @intFromFloat(@round(width)); + const h: u32 = @intFromFloat(@round(height)); + const pitch = w * atlas.format.depth(); + + // Allocate a buffer for our scaled bitmap. + // + // We'll copy this to the original bitmap once we're + // done so we can free it at the end of this scope. + const buf = try alloc.alloc(u8, pitch * h); + defer alloc.free(buf); + + // Resize + if (stb.stbir_resize_uint8( + glyph_bitmap.buffer, + @intCast(glyph_bitmap.width), + @intCast(glyph_bitmap.rows), + glyph_bitmap.pitch, + buf.ptr, + @intCast(w), + @intCast(h), + @intCast(pitch), + atlas.format.depth(), + ) == 0) { + // This should never fail because this is a + // fairly straightforward in-memory operation... + return error.GlyphResizeFailed; + } + + const scaled_bitmap: freetype.c.FT_Bitmap = .{ + .buffer = buf.ptr, + .width = @intCast(w), + .rows = @intCast(h), + .pitch = @intCast(pitch), + .pixel_mode = glyph_bitmap.pixel_mode, + .num_grays = glyph_bitmap.num_grays, + }; + + // Replace the bitmap's buffer and size info. + if (freetype.c.FT_Bitmap_Copy( + self.lib.lib.handle, + &scaled_bitmap, + &bitmap, + ) != 0) { + return error.BitmapHandlingError; + } + }, + + else => |f| { + // Glyph formats are tags, so we can + // output a semi-readable error here. + log.err( + "Can't render glyph with unsupported glyph format \"{s}\"", + .{[4]u8{ + @truncate(f >> 24), + @truncate(f >> 16), + @truncate(f >> 8), + @truncate(f >> 0), + }}, + ); + return error.UnsupportedGlyphFormat; + }, + } + + // If this is a color glyph but we're trying to render it to the + // grayscale atlas, or vice versa, then we throw and error. Maybe + // in the future we could convert, but for now it should be fine. + switch (bitmap.pixel_mode) { + freetype.c.FT_PIXEL_MODE_GRAY => if (atlas.format != .grayscale) { + return error.WrongAtlas; + }, + freetype.c.FT_PIXEL_MODE_BGRA => if (atlas.format != .bgra) { + return error.WrongAtlas; + }, else => { - log.warn("glyph={} pixel mode={}", .{ glyph_index, bitmap_ft.pixel_mode }); + log.warn("glyph={} pixel mode={}", .{ glyph_index, bitmap.pixel_mode }); @panic("unsupported pixel mode"); }, - }; - - // If our atlas format doesn't match, look for conversions if possible. - const bitmap_converted = if (format == null or atlas.format != format.?) blk: { - const func = convert.map[bitmap_ft.pixel_mode].get(atlas.format) orelse { - log.warn("glyph={} pixel mode={}", .{ glyph_index, bitmap_ft.pixel_mode }); - return error.UnsupportedPixelMode; - }; - - log.debug("converting from pixel_mode={} to atlas_format={}", .{ - bitmap_ft.pixel_mode, - atlas.format, - }); - break :blk try func(alloc, bitmap_ft); - } else null; - defer if (bitmap_converted) |bm| { - const len = @as(usize, @intCast(bm.pitch)) * @as(usize, @intCast(bm.rows)); - alloc.free(bm.buffer[0..len]); - }; - - // Now we need to see if we need to resize this bitmap. This can happen - // in scenarios where we have fixed size glyphs. For example, emoji - // can be quite large (i.e. 128x128) when we have a cell width of 24! - // The issue with large bitmaps is they take a huge amount of space in - // the atlas and force resizes quite frequently. We pay some CPU cost - // up front to resize the glyph to avoid significant CPU cost to resize - // and copy the atlas. - const bitmap_original = bitmap_converted orelse bitmap_ft; - const bitmap_resized: ?freetype.c.struct_FT_Bitmap_ = resized: { - const original_width = bitmap_original.width; - const original_height = bitmap_original.rows; - var result = bitmap_original; - // TODO: We are limiting this to only color glyphs, so mainly emoji. - // We can rework this after a future improvement (promised by Qwerasd) - // which implements more flexible resizing rules. - if (atlas.format != .grayscale and opts.cell_width != null) { - const cell_width = opts.cell_width orelse unreachable; - // If we have a cell_width, we constrain - // the glyph to fit within the cell(s). - result.width = metrics.cell_width * @as(u32, cell_width); - result.rows = (result.width * original_height) / original_width; - } else { - // If we don't have a cell_width, we scale to fill vertically - result.rows = metrics.cell_height; - result.width = (metrics.cell_height * original_width) / original_height; - } - - // If we already fit, we don't need to resize - if (original_height <= result.rows and original_width <= result.width) { - break :resized null; - } - - result.pitch = @as(c_int, @intCast(result.width)) * atlas.format.depth(); - - const buf = try alloc.alloc( - u8, - @as(usize, @intCast(result.pitch)) * @as(usize, @intCast(result.rows)), - ); - result.buffer = buf.ptr; - errdefer alloc.free(buf); - - if (stb.stbir_resize_uint8( - bitmap_original.buffer, - @intCast(original_width), - @intCast(original_height), - bitmap_original.pitch, - result.buffer, - @intCast(result.width), - @intCast(result.rows), - result.pitch, - atlas.format.depth(), - ) == 0) { - // This should never fail because this is a fairly straightforward - // in-memory operation... - return error.GlyphResizeFailed; - } - - break :resized result; - }; - defer if (bitmap_resized) |bm| { - const len = @as(usize, @intCast(bm.pitch)) * @as(usize, @intCast(bm.rows)); - alloc.free(bm.buffer[0..len]); - }; - - const bitmap = bitmap_resized orelse (bitmap_converted orelse bitmap_ft); - const tgt_w = bitmap.width; - const tgt_h = bitmap.rows; - - // Must have non-empty bitmap because we return earlier - // if zero. We assume the rest of this that it is nont-zero so - // this is important. - assert(tgt_w > 0 and tgt_h > 0); - - // If we resized our bitmap, we need to recalculate some metrics that - // we use such as the top/left offsets. These need to be scaled by the - // same ratio as the resize. - const glyph_metrics = if (bitmap_resized) |bm| metrics: { - // Our ratio for the resize - const ratio = ratio: { - const new: f64 = @floatFromInt(bm.rows); - const old: f64 = @floatFromInt(bitmap_original.rows); - break :ratio new / old; - }; - - var copy = glyph.*; - copy.bitmap_top = @as(c_int, @intFromFloat(@round(@as(f64, @floatFromInt(copy.bitmap_top)) * ratio))); - copy.bitmap_left = @as(c_int, @intFromFloat(@round(@as(f64, @floatFromInt(copy.bitmap_left)) * ratio))); - break :metrics copy; - } else glyph.*; - - // Allocate our texture atlas region - const region = region: { - // We need to add a 1px padding to the font so that we don't - // get fuzzy issues when blending textures. - const padding = 1; - - // Get the full padded region - var region = try atlas.reserve( - alloc, - tgt_w + (padding * 2), // * 2 because left+right - tgt_h + (padding * 2), // * 2 because top+bottom - ); - - // Modify the region so that we remove the padding so that - // we write to the non-zero location. The data in an Altlas - // is always initialized to zero (Atlas.clear) so we don't - // need to worry about zero-ing that. - region.x += padding; - region.y += padding; - region.width -= padding * 2; - region.height -= padding * 2; - break :region region; - }; - - // Copy the image into the region. - assert(region.width > 0 and region.height > 0); - { - const depth = atlas.format.depth(); - - // We can avoid a buffer copy if our atlas width and bitmap - // width match and the bitmap pitch is just the width (meaning - // the data is tightly packed). - const needs_copy = !(tgt_w == bitmap.width and (bitmap.width * depth) == bitmap.pitch); - - // If we need to copy the data, we copy it into a temporary buffer. - const buffer = if (needs_copy) buffer: { - const temp = try alloc.alloc(u8, tgt_w * tgt_h * depth); - var dst_ptr = temp; - var src_ptr = bitmap.buffer; - var i: usize = 0; - while (i < bitmap.rows) : (i += 1) { - fastmem.copy(u8, dst_ptr, src_ptr[0 .. bitmap.width * depth]); - dst_ptr = dst_ptr[tgt_w * depth ..]; - src_ptr += @as(usize, @intCast(bitmap.pitch)); - } - break :buffer temp; - } else bitmap.buffer[0..(tgt_w * tgt_h * depth)]; - defer if (buffer.ptr != bitmap.buffer) alloc.free(buffer); - - // Write the glyph information into the atlas - assert(region.width == tgt_w); - assert(region.height == tgt_h); - atlas.set(region, buffer); } - const offset_y: c_int = offset_y: { - // For non-scalable colorized fonts, we assume they are pictographic - // and just center the glyph. So far this has only applied to emoji - // fonts. Emoji fonts don't always report a correct ascender/descender - // (mainly Apple Emoji) so we just center them. Also, since emoji font - // aren't scalable, cell_baseline is incorrect anyways. - // - // NOTE(mitchellh): I don't know if this is right, this doesn't - // _feel_ right, but it makes all my limited test cases work. - if (self.face.hasColor() and !self.face.isScalable()) { - break :offset_y @intCast(tgt_h + (metrics.cell_height -| tgt_h) / 2); + const px_width = bitmap.width; + const px_height = bitmap.rows; + const len: usize = @intCast( + @as(c_uint, @intCast(@abs(bitmap.pitch))) * bitmap.rows, + ); + + // If our bitmap is grayscale, make sure to multiply all pixel + // values by the right factor to bring `num_grays` up to 256. + // + // This is necessary because FT_Bitmap_Convert doesn't do this, + // it just sets num_grays to the correct number and uses the + // original smaller pixel values. + if (bitmap.pixel_mode == freetype.c.FT_PIXEL_MODE_GRAY and + bitmap.num_grays < 256) + { + const factor: u8 = @intCast(255 / (bitmap.num_grays - 1)); + for (bitmap.buffer[0..len]) |*p| { + p.* *= factor; } + bitmap.num_grays = 256; + } - // The Y offset is the offset of the top of our bitmap PLUS our - // baseline calculation. The baseline calculation is so that everything - // is properly centered when we render it out into a monospace grid. - // Note: we add here because our X/Y is actually reversed, adding goes UP. - break :offset_y glyph_metrics.bitmap_top + @as(c_int, @intCast(metrics.cell_baseline)); - }; + // Must have non-empty bitmap because we return earlier if zero. + // We assume the rest of this that it is non-zero so this is important. + assert(px_width > 0 and px_height > 0); + // If this doesn't match then something is wrong. + assert(px_width * atlas.format.depth() == bitmap.pitch); + + // Allocate our texture atlas region and copy our bitmap in to it. + const region = try atlas.reserve(alloc, px_width, px_height); + atlas.set(region, bitmap.buffer[0..len]); + + // This should be the distance from the bottom of + // the cell to the top of the glyph's bounding box. + const offset_y: i32 = + @as(i32, @intFromFloat(@floor(y))) + + @as(i32, @intCast(px_height)); + + // This should be the distance from the left of + // the cell to the left of the glyph's bounding box. const offset_x: i32 = offset_x: { - var result: i32 = glyph_metrics.bitmap_left; - - // If our cell was resized to be wider then we center our - // glyph in the cell. - if (metrics.original_cell_width) |original_width| { - if (original_width < metrics.cell_width) { - const diff = (metrics.cell_width - original_width) / 2; - result += @intCast(diff); + // 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)); } - - break :offset_x result; }; - // log.warn("renderGlyph width={} height={} offset_x={} offset_y={} glyph_metrics={}", .{ - // tgt_w, - // tgt_h, - // glyph_metrics.bitmap_left, - // offset_y, - // glyph_metrics, - // }); - - // Store glyph metadata return Glyph{ - .width = tgt_w, - .height = tgt_h, + .width = px_width, + .height = px_height, .offset_x = offset_x, .offset_y = offset_y, .atlas_x = region.x, .atlas_y = region.y, - .advance_x = f26dot6ToFloat(glyph_metrics.advance.x), }; } @@ -631,7 +704,7 @@ pub const Face = struct { } fn f26dot6ToF64(v: freetype.c.FT_F26Dot6) f64 { - return @as(opentype.sfnt.F26Dot6, @bitCast(@as(u32, @intCast(v)))).to(f64); + return @as(F26Dot6, @bitCast(@as(i32, @intCast(v)))).to(f64); } pub const GetMetricsError = error{ @@ -783,7 +856,7 @@ pub const Face = struct { while (c < 127) : (c += 1) { if (face.getCharIndex(c)) |glyph_index| { if (face.loadGlyph(glyph_index, .{ - .render = true, + .render = false, .no_svg = true, })) { max = @max( @@ -821,7 +894,7 @@ pub const Face = struct { defer self.ft_mutex.unlock(); if (face.getCharIndex('H')) |glyph_index| { if (face.loadGlyph(glyph_index, .{ - .render = true, + .render = false, .no_svg = true, })) { break :cap f26dot6ToF64(face.handle.*.glyph.*.metrics.height); @@ -834,7 +907,7 @@ pub const Face = struct { defer self.ft_mutex.unlock(); if (face.getCharIndex('x')) |glyph_index| { if (face.loadGlyph(glyph_index, .{ - .render = true, + .render = false, .no_svg = true, })) { break :ex f26dot6ToF64(face.handle.*.glyph.*.metrics.height); @@ -845,6 +918,21 @@ pub const Face = struct { }; }; + // Measure "水" (CJK water ideograph, U+6C34) for our ic width. + const ic_width: ?f64 = ic_width: { + self.ft_mutex.lock(); + defer self.ft_mutex.unlock(); + + const glyph = face.getCharIndex('水') orelse break :ic_width null; + + face.loadGlyph(glyph, .{ + .render = false, + .no_svg = true, + }) catch break :ic_width null; + + break :ic_width f26dot6ToF64(face.handle.*.glyph.*.advance.x); + }; + return .{ .cell_width = cell_width, @@ -860,6 +948,7 @@ pub const Face = struct { .cap_height = cap_height, .ex_height = ex_height, + .ic_width = ic_width, }; } @@ -950,13 +1039,15 @@ test "color emoji" { } // resize + // TODO: Comprehensive tests for constraints, + // this is just an adapted legacy test. { const glyph = try ft_font.renderGlyph( alloc, &atlas, ft_font.glyphIndex('🥸').?, .{ .grid_metrics = .{ - .cell_width = 10, + .cell_width = 13, .cell_height = 24, .cell_baseline = 0, .underline_position = 0, @@ -967,6 +1058,11 @@ test "color emoji" { .overline_thickness = 0, .box_thickness = 0, .cursor_height = 0, + }, .constraint_width = 2, .constraint = .{ + .size_horizontal = .cover, + .size_vertical = .cover, + .align_horizontal = .center, + .align_vertical = .center, } }, ); try testing.expectEqual(@as(u32, 24), glyph.height); diff --git a/src/font/face/freetype_convert.zig b/src/font/face/freetype_convert.zig deleted file mode 100644 index 3a7cf8c98..000000000 --- a/src/font/face/freetype_convert.zig +++ /dev/null @@ -1,88 +0,0 @@ -//! Various conversions from Freetype formats to Atlas formats. These are -//! currently implemented naively. There are definitely MUCH faster ways -//! to do this (likely using SIMD), but I started simple. -const std = @import("std"); -const freetype = @import("freetype"); -const font = @import("../main.zig"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; - -/// The mapping from freetype format to atlas format. -pub const map = genMap(); - -/// The map type. -pub const Map = [freetype.c.FT_PIXEL_MODE_MAX]AtlasArray; - -/// Conversion function type. The returning bitmap buffer is guaranteed -/// to be exactly `width * rows * depth` long for freeing it. The caller must -/// free the bitmap buffer. The depth is the depth of the atlas format in the -/// map. -pub const Func = *const fn (Allocator, Bitmap) Allocator.Error!Bitmap; - -/// Alias for the freetype FT_Bitmap type to make it easier to type. -pub const Bitmap = freetype.c.struct_FT_Bitmap_; - -const AtlasArray = std.EnumArray(font.Atlas.Format, ?Func); - -fn genMap() Map { - var result: Map = undefined; - - // Initialize to no converter - var i: usize = 0; - while (i < freetype.c.FT_PIXEL_MODE_MAX) : (i += 1) { - result[i] = .initFill(null); - } - - // Map our converters - result[freetype.c.FT_PIXEL_MODE_MONO].set(.grayscale, monoToGrayscale); - - return result; -} - -pub fn monoToGrayscale(alloc: Allocator, bm: Bitmap) Allocator.Error!Bitmap { - var buf = try alloc.alloc(u8, bm.width * bm.rows); - errdefer alloc.free(buf); - - for (0..bm.rows) |y| { - const row_offset = y * @as(usize, @intCast(bm.pitch)); - for (0..bm.width) |x| { - const byte_offset = row_offset + @divTrunc(x, 8); - const mask = @as(u8, 1) << @intCast(7 - (x % 8)); - const bit: u8 = @intFromBool((bm.buffer[byte_offset] & mask) != 0); - buf[y * bm.width + x] = bit * 255; - } - } - - var copy = bm; - copy.buffer = buf.ptr; - copy.pixel_mode = freetype.c.FT_PIXEL_MODE_GRAY; - copy.pitch = @as(c_int, @intCast(bm.width)); - return copy; -} - -test { - // Force comptime to run - _ = map; -} - -test "mono to grayscale" { - const testing = std.testing; - const alloc = testing.allocator; - - var mono_data = [_]u8{0b1010_0101}; - const source: Bitmap = .{ - .rows = 1, - .width = 8, - .pitch = 1, - .buffer = @ptrCast(&mono_data), - .num_grays = 0, - .pixel_mode = freetype.c.FT_PIXEL_MODE_MONO, - .palette_mode = 0, - .palette = null, - }; - - const result = try monoToGrayscale(alloc, source); - defer alloc.free(result.buffer[0..(result.width * result.rows)]); - try testing.expect(result.pixel_mode == freetype.c.FT_PIXEL_MODE_GRAY); - try testing.expectEqual(@as(u8, 255), result.buffer[0]); -} diff --git a/src/font/face/web_canvas.zig b/src/font/face/web_canvas.zig index 30540191d..7ea2f0426 100644 --- a/src/font/face/web_canvas.zig +++ b/src/font/face/web_canvas.zig @@ -235,7 +235,6 @@ pub const Face = struct { .offset_y = 0, .atlas_x = region.x, .atlas_y = region.y, - .advance_x = 0, }; } diff --git a/src/font/nerd_font_attributes.zig b/src/font/nerd_font_attributes.zig new file mode 100644 index 000000000..70920bb0a --- /dev/null +++ b/src/font/nerd_font_attributes.zig @@ -0,0 +1,370 @@ +//! This is a generated file, produced by nerd_font_codegen.py +//! DO NOT EDIT BY HAND! +//! +//! This file provides info extracted from the nerd fonts patcher script, +//! specifying the scaling/positioning attributes of various glyphs. + +const Constraint = @import("face.zig").RenderOptions.Constraint; + +/// Get the a constraints for the provided codepoint. +pub fn getConstraint(cp: u21) Constraint { + return switch (cp) { + 0x2500...0x259f, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .center, + .align_vertical = .center, + .pad_left = -0.02, + .pad_right = -0.02, + .pad_top = -0.01, + .pad_bottom = -0.01, + }, + 0x2630, + => .{ + .size_horizontal = .cover, + .size_vertical = .fit, + .max_constraint_width = 1, + .align_horizontal = .center, + .align_vertical = .center, + .pad_left = 0.1, + .pad_right = 0.1, + .pad_top = 0.1, + .pad_bottom = 0.1, + }, + 0x276c...0x2771, + => .{ + .size_horizontal = .cover, + .size_vertical = .fit, + .max_constraint_width = 1, + .align_horizontal = .center, + .align_vertical = .center, + .pad_top = 0.15, + .pad_bottom = 0.15, + }, + 0xe0b0, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.06, + .pad_right = -0.06, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0xe0b1, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .start, + .align_vertical = .center, + .max_xy_ratio = 0.7, + }, + 0xe0b2, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.06, + .pad_right = -0.06, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0xe0b3, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .end, + .align_vertical = .center, + .max_xy_ratio = 0.7, + }, + 0xe0b4, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.06, + .pad_right = -0.06, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.59, + }, + 0xe0b5, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .start, + .align_vertical = .center, + .max_xy_ratio = 0.5, + }, + 0xe0b6, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.06, + .pad_right = -0.06, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.59, + }, + 0xe0b7, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .end, + .align_vertical = .center, + .max_xy_ratio = 0.5, + }, + 0xe0b8, + 0xe0bc, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + }, + 0xe0b9, + 0xe0bd, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .start, + .align_vertical = .center, + }, + 0xe0ba, + 0xe0be, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + }, + 0xe0bb, + 0xe0bf, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .end, + .align_vertical = .center, + }, + 0xe0c0, + 0xe0c8, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + }, + 0xe0c1, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + }, + 0xe0c2, + 0xe0ca, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + }, + 0xe0c3, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + }, + 0xe0c4, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = 0.03, + .pad_right = 0.03, + .pad_top = 0.03, + .pad_bottom = 0.03, + .max_xy_ratio = 0.86, + }, + 0xe0c5, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = 0.03, + .pad_right = 0.03, + .pad_top = 0.03, + .pad_bottom = 0.03, + .max_xy_ratio = 0.86, + }, + 0xe0c6, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = 0.03, + .pad_right = 0.03, + .pad_top = 0.03, + .pad_bottom = 0.03, + .max_xy_ratio = 0.78, + }, + 0xe0c7, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = 0.03, + .pad_right = 0.03, + .pad_top = 0.03, + .pad_bottom = 0.03, + .max_xy_ratio = 0.78, + }, + 0xe0cc, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.02, + .pad_right = -0.02, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.85, + }, + 0xe0cd, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .align_horizontal = .start, + .align_vertical = .center, + .max_xy_ratio = 0.865, + }, + 0xe0ce, + 0xe0d0...0xe0d1, + => .{ + .size_horizontal = .cover, + .size_vertical = .cover, + .align_horizontal = .start, + .align_vertical = .center, + }, + 0xe0cf, + 0xe0d3, + 0xe0d5, + => .{ + .size_horizontal = .cover, + .size_vertical = .cover, + .align_horizontal = .center, + .align_vertical = .center, + }, + 0xe0d2, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.02, + .pad_right = -0.02, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0xe0d4, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.02, + .pad_right = -0.02, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0xe0d6, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .start, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0xe0d7, + => .{ + .size_horizontal = .stretch, + .size_vertical = .stretch, + .max_constraint_width = 1, + .align_horizontal = .end, + .align_vertical = .center, + .pad_left = -0.05, + .pad_right = -0.05, + .pad_top = -0.01, + .pad_bottom = -0.01, + .max_xy_ratio = 0.7, + }, + 0x23fb...0x23fe, + 0x2665, + 0x26a1, + 0x2b58, + 0xe000...0xe0a9, + 0xe4fa...0xe7ef, + 0xea60...0xec1e, + 0xed00...0xf847, + 0xf0001...0xf1af0, + => .{ + .size_horizontal = .fit, + .size_vertical = .fit, + .align_horizontal = .center, + .align_vertical = .center, + }, + else => .none, + }; +} diff --git a/src/font/nerd_font_codegen.py b/src/font/nerd_font_codegen.py new file mode 100644 index 000000000..e74b2ead1 --- /dev/null +++ b/src/font/nerd_font_codegen.py @@ -0,0 +1,276 @@ +""" +This file extracts the patch sets from the nerd fonts font patcher file in order to +extract scaling rules and attributes for different codepoint ranges which it then +codegens in to a Zig file with a function that switches over codepoints and returns the +attributes and scaling rules. + +This does include an `eval` call! This is spooky, but we trust the nerd fonts code to +be safe and not malicious or anything. + +This script requires Python 3.12 or greater. +""" + +import ast +import math +from collections import defaultdict +from contextlib import suppress +from pathlib import Path +from types import SimpleNamespace +from typing import Literal, TypedDict, cast + +type PatchSetAttributes = dict[Literal["default"] | int, PatchSetAttributeEntry] +type AttributeHash = tuple[str | None, str | None, str, float, float, float] +type ResolvedSymbol = PatchSetAttributes | PatchSetScaleRules | int | None + + +class PatchSetScaleRules(TypedDict): + ShiftMode: str + ScaleGroups: list[list[int] | range] + + +class PatchSetAttributeEntry(TypedDict): + align: str + valign: str + stretch: str + params: dict[str, float | bool] + + +class PatchSet(TypedDict): + SymStart: int + SymEnd: int + SrcStart: int | None + ScaleRules: PatchSetScaleRules | None + Attributes: PatchSetAttributes + + +class PatchSetExtractor(ast.NodeVisitor): + def __init__(self) -> None: + self.symbol_table: dict[str, ast.expr] = {} + self.patch_set_values: list[PatchSet] = [] + + def visit_ClassDef(self, node: ast.ClassDef) -> None: + if node.name != "font_patcher": + return + for item in node.body: + if isinstance(item, ast.FunctionDef) and item.name == "setup_patch_set": + self.visit_setup_patch_set(item) + + def visit_setup_patch_set(self, node: ast.FunctionDef) -> None: + # First pass: gather variable assignments + for stmt in node.body: + match stmt: + case ast.Assign(targets=[ast.Name(id=symbol)]): + # Store simple variable assignments in the symbol table + self.symbol_table[symbol] = stmt.value + + # Second pass: process self.patch_set + for stmt in node.body: + if not isinstance(stmt, ast.Assign): + continue + for target in stmt.targets: + if ( + isinstance(target, ast.Attribute) + and target.attr == "patch_set" + and isinstance(stmt.value, ast.List) + ): + for elt in stmt.value.elts: + if isinstance(elt, ast.Dict): + self.process_patch_entry(elt) + + def resolve_symbol(self, node: ast.expr) -> ResolvedSymbol: + """Resolve named variables to their actual values from the symbol table.""" + if isinstance(node, ast.Name) and node.id in self.symbol_table: + return self.safe_literal_eval(self.symbol_table[node.id]) + return self.safe_literal_eval(node) + + def safe_literal_eval(self, node: ast.expr) -> ResolvedSymbol: + """Try to evaluate or stringify an AST node.""" + try: + return ast.literal_eval(node) + except ValueError: + # Spooky eval! But we trust nerd fonts to be safe... + if hasattr(ast, "unparse"): + return eval( + ast.unparse(node), + {"box_keep": True}, + {"self": SimpleNamespace(args=SimpleNamespace(careful=True))}, + ) + msg = f"" + raise ValueError(msg) from None + + def process_patch_entry(self, dict_node: ast.Dict) -> None: + entry = {} + disallowed_key_nodes = frozenset({"Enabled", "Name", "Filename", "Exact"}) + for key_node, value_node in zip(dict_node.keys, dict_node.values): + if ( + isinstance(key_node, ast.Constant) + and key_node.value not in disallowed_key_nodes + ): + key = ast.literal_eval(cast("ast.Constant", key_node)) + entry[key] = self.resolve_symbol(value_node) + self.patch_set_values.append(cast("PatchSet", entry)) + + +def extract_patch_set_values(source_code: str) -> list[PatchSet]: + tree = ast.parse(source_code) + extractor = PatchSetExtractor() + extractor.visit(tree) + return extractor.patch_set_values + + +def parse_alignment(val: str) -> str | None: + return { + "l": ".start", + "r": ".end", + "c": ".center", + "": None, + }.get(val, ".none") + + +def attr_key(attr: PatchSetAttributeEntry) -> AttributeHash: + """Convert attributes to a hashable key for grouping.""" + params = attr.get("params", {}) + return ( + parse_alignment(attr.get("align", "")), + parse_alignment(attr.get("valign", "")), + attr.get("stretch", ""), + float(params.get("overlap", 0.0)), + float(params.get("xy-ratio", -1.0)), + float(params.get("ypadding", 0.0)), + ) + + +def coalesce_codepoints_to_ranges(codepoints: list[int]) -> list[tuple[int, int]]: + """Convert a sorted list of integers to a list of single values and ranges.""" + ranges: list[tuple[int, int]] = [] + cp_iter = iter(sorted(codepoints)) + with suppress(StopIteration): + start = prev = next(cp_iter) + for cp in cp_iter: + if cp == prev + 1: + prev = cp + else: + ranges.append((start, prev)) + start = prev = cp + ranges.append((start, prev)) + return ranges + + +def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry) -> str: + align = parse_alignment(attr.get("align", "")) + valign = parse_alignment(attr.get("valign", "")) + stretch = attr.get("stretch", "") + params = attr.get("params", {}) + + overlap = params.get("overlap", 0.0) + xy_ratio = params.get("xy-ratio", -1.0) + y_padding = params.get("ypadding", 0.0) + + ranges = coalesce_codepoints_to_ranges(codepoints) + keys = "\n".join( + f" {start:#x}...{end:#x}," if start != end else f" {start:#x}," + for start, end in ranges + ) + + s = f"{keys}\n => .{{\n" + + # These translations don't quite capture the way + # the actual patcher does scaling, but they're a + # good enough compromise. + if "xy" in stretch: + s += " .size_horizontal = .stretch,\n" + s += " .size_vertical = .stretch,\n" + elif "!" in stretch: + s += " .size_horizontal = .cover,\n" + s += " .size_vertical = .fit,\n" + elif "^" in stretch: + s += " .size_horizontal = .cover,\n" + s += " .size_vertical = .cover,\n" + else: + s += " .size_horizontal = .fit,\n" + s += " .size_vertical = .fit,\n" + + # There are two cases where we want to limit the constraint width to 1: + # - If there's a `1` in the stretch mode string. + # - If the stretch mode is `xy` and there's not an explicit `2`. + if "1" in stretch or ("xy" in stretch and "2" not in stretch): + s += " .max_constraint_width = 1,\n" + + if align is not None: + s += f" .align_horizontal = {align},\n" + if valign is not None: + s += f" .align_vertical = {valign},\n" + + # `overlap` and `ypadding` are mutually exclusive, + # this is asserted in the nerd fonts patcher itself. + if overlap: + pad = -overlap + s += f" .pad_left = {pad},\n" + s += f" .pad_right = {pad},\n" + # In the nerd fonts patcher, overlap values + # are capped at 0.01 in the vertical direction. + v_pad = -min(0.01, overlap) + s += f" .pad_top = {v_pad},\n" + s += f" .pad_bottom = {v_pad},\n" + elif y_padding: + s += f" .pad_top = {y_padding / 2},\n" + s += f" .pad_bottom = {y_padding / 2},\n" + + if xy_ratio > 0: + s += f" .max_xy_ratio = {xy_ratio},\n" + + s += " }," + return s + + +def generate_zig_switch_arms(patch_sets: list[PatchSet]) -> str: + entries: dict[int, PatchSetAttributeEntry] = {} + for entry in patch_sets: + attributes = entry["Attributes"] + + for cp in range(entry["SymStart"], entry["SymEnd"] + 1): + entries[cp] = attributes["default"] + + entries |= {k: v for k, v in attributes.items() if isinstance(k, int)} + + del entries[0] + + # Group codepoints by attribute key + grouped = defaultdict[AttributeHash, list[int]](list) + for cp, attr in entries.items(): + grouped[attr_key(attr)].append(cp) + + # Emit zig switch arms + result: list[str] = [] + for codepoints in sorted(grouped.values()): + # Use one of the attrs in the group to emit the value + attr = entries[codepoints[0]] + result.append(emit_zig_entry_multikey(codepoints, attr)) + + return "\n".join(result) + + +if __name__ == "__main__": + project_root = Path(__file__).resolve().parents[2] + + patcher_path = project_root / "vendor" / "nerd-fonts" / "font-patcher.py" + source = patcher_path.read_text(encoding="utf-8") + patch_set = extract_patch_set_values(source) + + out_path = project_root / "src" / "font" / "nerd_font_attributes.zig" + + with out_path.open("w", encoding="utf-8") as f: + f.write("""//! This is a generated file, produced by nerd_font_codegen.py +//! DO NOT EDIT BY HAND! +//! +//! This file provides info extracted from the nerd fonts patcher script, +//! specifying the scaling/positioning attributes of various glyphs. + +const Constraint = @import("face.zig").RenderOptions.Constraint; + +/// Get the a constraints for the provided codepoint. +pub fn getConstraint(cp: u21) Constraint { + return switch (cp) { +""") + f.write(generate_zig_switch_arms(patch_set)) + f.write("\n else => .none,\n };\n}\n") diff --git a/src/font/opentype/sfnt.zig b/src/font/opentype/sfnt.zig index 14a3b795a..82c118bce 100644 --- a/src/font/opentype/sfnt.zig +++ b/src/font/opentype/sfnt.zig @@ -76,24 +76,22 @@ fn FixedPoint(comptime T: type, int_bits: u64, frac_bits: u64) type { )); const half = @as(T, 1) << @intCast(frac_bits - 1); - frac: std.meta.Int(.unsigned, frac_bits), - int: std.meta.Int(type_info.signedness, int_bits), + const Frac = std.meta.Int(.unsigned, frac_bits); + const Int = std.meta.Int(type_info.signedness, int_bits); + + frac: Frac, + int: Int, pub fn to(self: Self, comptime FloatType: type) FloatType { - const i: FloatType = @floatFromInt(self.int); - const f: FloatType = @floatFromInt(self.frac); - - return i + f / frac_factor; + return @as(FloatType, @floatFromInt( + @as(T, @bitCast(self)), + )) / frac_factor; } pub fn from(float: anytype) Self { - const int = @floor(float); - const frac = @abs(float - int); - - return .{ - .int = @intFromFloat(int), - .frac = @intFromFloat(@round(frac * frac_factor)), - }; + return @bitCast( + @as(T, @intFromFloat(@round(float * frac_factor))), + ); } /// Round to the nearest integer, .5 rounds away from 0. diff --git a/src/font/shaper/coretext.zig b/src/font/shaper/coretext.zig index 1fd9719bb..f4f01d105 100644 --- a/src/font/shaper/coretext.zig +++ b/src/font/shaper/coretext.zig @@ -109,7 +109,8 @@ pub const Shaper = struct { /// settings the font features of a CoreText font. fn makeFeaturesDict(feats: []const Feature) !*macos.foundation.Dictionary { const list = try macos.foundation.MutableArray.create(); - errdefer list.release(); + // The list will be retained by the dict once we add it to it. + defer list.release(); for (feats) |feat| { const value_num: c_int = @intCast(feat.value); @@ -1768,7 +1769,7 @@ fn testShaperWithFont(alloc: Allocator, font_req: TestFont) !TestShaper { .geist_mono => font.embedded.geist_mono, .jetbrains_mono => font.embedded.jetbrains_mono, .monaspace_neon => font.embedded.monaspace_neon, - .nerd_font => font.embedded.nerd_font, + .nerd_font => font.embedded.test_nerd_font, }; var lib = try Library.init(alloc); diff --git a/src/font/sprite.zig b/src/font/sprite.zig index 6485d6008..cf86fa6dd 100644 --- a/src/font/sprite.zig +++ b/src/font/sprite.zig @@ -32,12 +32,7 @@ pub const Sprite = enum(u32) { cursor_rect, cursor_hollow_rect, cursor_bar, - - // Note: we don't currently put the box drawing glyphs in here because - // there are a LOT and I'm lazy. What I want to do is spend more time - // studying the patterns to see if we can programmatically build our - // enum perhaps and comptime generate the drawing code at the same time. - // I'm not sure if that's advisable yet though. + cursor_underline, test { const testing = std.testing; diff --git a/src/font/sprite/Box.zig b/src/font/sprite/Box.zig deleted file mode 100644 index f5140091d..000000000 --- a/src/font/sprite/Box.zig +++ /dev/null @@ -1,3397 +0,0 @@ -//! This file contains functions for drawing the box drawing characters -//! (https://en.wikipedia.org/wiki/Box-drawing_character) and related -//! characters that are provided by the terminal. -//! -//! The box drawing logic is based off similar logic in Kitty and Foot. -//! The primary drawing code was originally ported directly and slightly -//! modified from Foot (https://codeberg.org/dnkl/foot/). Foot is licensed -//! under the MIT license and is copyright 2019 Daniel Eklöf. -//! -//! The modifications made were primarily around spacing, DPI calculations, -//! and adapting the code to our atlas model. Further, more extensive changes -//! were made, refactoring the line characters to all share a single unified -//! function (draw_lines), as well as many of the fractional block characters -//! which now use draw_block instead of dedicated separate functions. -//! -//! Additional characters from Unicode 16.0 and beyond are original work. -const Box = @This(); - -const std = @import("std"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; - -const z2d = @import("z2d"); - -const font = @import("../main.zig"); -const Sprite = @import("../sprite.zig").Sprite; - -const log = std.log.scoped(.box_font); - -/// Grid metrics for the rendering. -metrics: font.Metrics, - -/// The thickness of a line. -const Thickness = enum { - super_light, - light, - heavy, - - /// Calculate the real height of a line based on its thickness - /// and a base thickness value. The base thickness value is expected - /// to be in pixels. - fn height(self: Thickness, base: u32) u32 { - return switch (self) { - .super_light => @max(base / 2, 1), - .light => base, - .heavy => base * 2, - }; - } -}; - -/// Specification of a traditional intersection-style line/box-drawing char, -/// which can have a different style of line from each edge to the center. -const Lines = packed struct(u8) { - up: Style = .none, - right: Style = .none, - down: Style = .none, - left: Style = .none, - - const Style = enum(u2) { - none, - light, - heavy, - double, - }; -}; - -/// Specification of a quadrants char, which has each of the -/// 4 quadrants of the character cell either filled or empty. -const Quads = packed struct(u4) { - tl: bool = false, - tr: bool = false, - bl: bool = false, - br: bool = false, -}; - -/// Specification of a branch drawing node, which consists of a -/// circle which is either empty or filled, and lines connecting -/// optionally between the circle and each of the 4 edges. -const BranchNode = packed struct(u5) { - up: bool = false, - right: bool = false, - down: bool = false, - left: bool = false, - filled: bool = false, -}; - -/// Alignment of a figure within a cell -const Alignment = struct { - horizontal: enum { - left, - right, - center, - } = .center, - - vertical: enum { - top, - bottom, - middle, - } = .middle, - - const upper: Alignment = .{ .vertical = .top }; - const lower: Alignment = .{ .vertical = .bottom }; - const left: Alignment = .{ .horizontal = .left }; - const right: Alignment = .{ .horizontal = .right }; - - const upper_left: Alignment = .{ .vertical = .top, .horizontal = .left }; - const upper_right: Alignment = .{ .vertical = .top, .horizontal = .right }; - const lower_left: Alignment = .{ .vertical = .bottom, .horizontal = .left }; - const lower_right: Alignment = .{ .vertical = .bottom, .horizontal = .right }; - - const center: Alignment = .{}; - - const upper_center = upper; - const lower_center = lower; - const middle_left = left; - const middle_right = right; - const middle_center: Alignment = center; - - const top = upper; - const bottom = lower; - const center_top = top; - const center_bottom = bottom; - - const top_left = upper_left; - const top_right = upper_right; - const bottom_left = lower_left; - const bottom_right = lower_right; -}; - -const Corner = enum(u2) { - tl, - tr, - bl, - br, -}; - -const Edge = enum(u2) { - top, - left, - bottom, - right, -}; - -const SmoothMosaic = packed struct(u10) { - tl: bool, - ul: bool, - ll: bool, - bl: bool, - bc: bool, - br: bool, - lr: bool, - ur: bool, - tr: bool, - tc: bool, - - fn from(comptime pattern: *const [15:0]u8) SmoothMosaic { - return .{ - .tl = pattern[0] == '#', - - .ul = pattern[4] == '#' and - (pattern[0] != '#' or pattern[8] != '#'), - - .ll = pattern[8] == '#' and - (pattern[4] != '#' or pattern[12] != '#'), - - .bl = pattern[12] == '#', - - .bc = pattern[13] == '#' and - (pattern[12] != '#' or pattern[14] != '#'), - - .br = pattern[14] == '#', - - .lr = pattern[10] == '#' and - (pattern[14] != '#' or pattern[6] != '#'), - - .ur = pattern[6] == '#' and - (pattern[10] != '#' or pattern[2] != '#'), - - .tr = pattern[2] == '#', - - .tc = pattern[1] == '#' and - (pattern[2] != '#' or pattern[0] != '#'), - }; - } -}; - -// Octant range, inclusive -const octant_min = 0x1cd00; -const octant_max = 0x1cde5; - -// Utility names for common fractions -const one_eighth: f64 = 0.125; -const one_quarter: f64 = 0.25; -const one_third: f64 = (1.0 / 3.0); -const three_eighths: f64 = 0.375; -const half: f64 = 0.5; -const five_eighths: f64 = 0.625; -const two_thirds: f64 = (2.0 / 3.0); -const three_quarters: f64 = 0.75; -const seven_eighths: f64 = 0.875; - -/// Shades -const Shade = enum(u8) { - off = 0x00, - light = 0x40, - medium = 0x80, - dark = 0xc0, - on = 0xff, - - _, -}; - -pub fn renderGlyph( - self: Box, - alloc: Allocator, - atlas: *font.Atlas, - cp: u32, -) !font.Glyph { - const metrics = self.metrics; - - // Create the canvas we'll use to draw - var canvas = try font.sprite.Canvas.init( - alloc, - metrics.cell_width, - metrics.cell_height, - ); - defer canvas.deinit(); - - // Perform the actual drawing - try self.draw(alloc, &canvas, cp); - - // Write the drawing to the atlas - const region = try canvas.writeAtlas(alloc, atlas); - - // Our coordinates start at the BOTTOM for our renderers so we have to - // specify an offset of the full height because we rendered a full size - // cell. - const offset_y = @as(i32, @intCast(metrics.cell_height)); - - return font.Glyph{ - .width = metrics.cell_width, - .height = metrics.cell_height, - .offset_x = 0, - .offset_y = offset_y, - .atlas_x = region.x, - .atlas_y = region.y, - .advance_x = @floatFromInt(metrics.cell_width), - }; -} - -fn draw(self: Box, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void { - _ = alloc; - switch (cp) { - // '─' - 0x2500 => self.draw_lines(canvas, .{ .left = .light, .right = .light }), - // '━' - 0x2501 => self.draw_lines(canvas, .{ .left = .heavy, .right = .heavy }), - // '│' - 0x2502 => self.draw_lines(canvas, .{ .up = .light, .down = .light }), - // '┃' - 0x2503 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy }), - // '┄' - 0x2504 => self.draw_light_triple_dash_horizontal(canvas), - // '┅' - 0x2505 => self.draw_heavy_triple_dash_horizontal(canvas), - // '┆' - 0x2506 => self.draw_light_triple_dash_vertical(canvas), - // '┇' - 0x2507 => self.draw_heavy_triple_dash_vertical(canvas), - // '┈' - 0x2508 => self.draw_light_quadruple_dash_horizontal(canvas), - // '┉' - 0x2509 => self.draw_heavy_quadruple_dash_horizontal(canvas), - // '┊' - 0x250a => self.draw_light_quadruple_dash_vertical(canvas), - // '┋' - 0x250b => self.draw_heavy_quadruple_dash_vertical(canvas), - // '┌' - 0x250c => self.draw_lines(canvas, .{ .down = .light, .right = .light }), - // '┍' - 0x250d => self.draw_lines(canvas, .{ .down = .light, .right = .heavy }), - // '┎' - 0x250e => self.draw_lines(canvas, .{ .down = .heavy, .right = .light }), - // '┏' - 0x250f => self.draw_lines(canvas, .{ .down = .heavy, .right = .heavy }), - - // '┐' - 0x2510 => self.draw_lines(canvas, .{ .down = .light, .left = .light }), - // '┑' - 0x2511 => self.draw_lines(canvas, .{ .down = .light, .left = .heavy }), - // '┒' - 0x2512 => self.draw_lines(canvas, .{ .down = .heavy, .left = .light }), - // '┓' - 0x2513 => self.draw_lines(canvas, .{ .down = .heavy, .left = .heavy }), - // '└' - 0x2514 => self.draw_lines(canvas, .{ .up = .light, .right = .light }), - // '┕' - 0x2515 => self.draw_lines(canvas, .{ .up = .light, .right = .heavy }), - // '┖' - 0x2516 => self.draw_lines(canvas, .{ .up = .heavy, .right = .light }), - // '┗' - 0x2517 => self.draw_lines(canvas, .{ .up = .heavy, .right = .heavy }), - // '┘' - 0x2518 => self.draw_lines(canvas, .{ .up = .light, .left = .light }), - // '┙' - 0x2519 => self.draw_lines(canvas, .{ .up = .light, .left = .heavy }), - // '┚' - 0x251a => self.draw_lines(canvas, .{ .up = .heavy, .left = .light }), - // '┛' - 0x251b => self.draw_lines(canvas, .{ .up = .heavy, .left = .heavy }), - // '├' - 0x251c => self.draw_lines(canvas, .{ .up = .light, .down = .light, .right = .light }), - // '┝' - 0x251d => self.draw_lines(canvas, .{ .up = .light, .down = .light, .right = .heavy }), - // '┞' - 0x251e => self.draw_lines(canvas, .{ .up = .heavy, .right = .light, .down = .light }), - // '┟' - 0x251f => self.draw_lines(canvas, .{ .down = .heavy, .right = .light, .up = .light }), - - // '┠' - 0x2520 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .right = .light }), - // '┡' - 0x2521 => self.draw_lines(canvas, .{ .down = .light, .right = .heavy, .up = .heavy }), - // '┢' - 0x2522 => self.draw_lines(canvas, .{ .up = .light, .right = .heavy, .down = .heavy }), - // '┣' - 0x2523 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .right = .heavy }), - // '┤' - 0x2524 => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .light }), - // '┥' - 0x2525 => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .heavy }), - // '┦' - 0x2526 => self.draw_lines(canvas, .{ .up = .heavy, .left = .light, .down = .light }), - // '┧' - 0x2527 => self.draw_lines(canvas, .{ .down = .heavy, .left = .light, .up = .light }), - // '┨' - 0x2528 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .light }), - // '┩' - 0x2529 => self.draw_lines(canvas, .{ .down = .light, .left = .heavy, .up = .heavy }), - // '┪' - 0x252a => self.draw_lines(canvas, .{ .up = .light, .left = .heavy, .down = .heavy }), - // '┫' - 0x252b => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy }), - // '┬' - 0x252c => self.draw_lines(canvas, .{ .down = .light, .left = .light, .right = .light }), - // '┭' - 0x252d => self.draw_lines(canvas, .{ .left = .heavy, .right = .light, .down = .light }), - // '┮' - 0x252e => self.draw_lines(canvas, .{ .right = .heavy, .left = .light, .down = .light }), - // '┯' - 0x252f => self.draw_lines(canvas, .{ .down = .light, .left = .heavy, .right = .heavy }), - - // '┰' - 0x2530 => self.draw_lines(canvas, .{ .down = .heavy, .left = .light, .right = .light }), - // '┱' - 0x2531 => self.draw_lines(canvas, .{ .right = .light, .left = .heavy, .down = .heavy }), - // '┲' - 0x2532 => self.draw_lines(canvas, .{ .left = .light, .right = .heavy, .down = .heavy }), - // '┳' - 0x2533 => self.draw_lines(canvas, .{ .down = .heavy, .left = .heavy, .right = .heavy }), - // '┴' - 0x2534 => self.draw_lines(canvas, .{ .up = .light, .left = .light, .right = .light }), - // '┵' - 0x2535 => self.draw_lines(canvas, .{ .left = .heavy, .right = .light, .up = .light }), - // '┶' - 0x2536 => self.draw_lines(canvas, .{ .right = .heavy, .left = .light, .up = .light }), - // '┷' - 0x2537 => self.draw_lines(canvas, .{ .up = .light, .left = .heavy, .right = .heavy }), - // '┸' - 0x2538 => self.draw_lines(canvas, .{ .up = .heavy, .left = .light, .right = .light }), - // '┹' - 0x2539 => self.draw_lines(canvas, .{ .right = .light, .left = .heavy, .up = .heavy }), - // '┺' - 0x253a => self.draw_lines(canvas, .{ .left = .light, .right = .heavy, .up = .heavy }), - // '┻' - 0x253b => self.draw_lines(canvas, .{ .up = .heavy, .left = .heavy, .right = .heavy }), - // '┼' - 0x253c => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .light, .right = .light }), - // '┽' - 0x253d => self.draw_lines(canvas, .{ .left = .heavy, .right = .light, .up = .light, .down = .light }), - // '┾' - 0x253e => self.draw_lines(canvas, .{ .right = .heavy, .left = .light, .up = .light, .down = .light }), - // '┿' - 0x253f => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .heavy, .right = .heavy }), - - // '╀' - 0x2540 => self.draw_lines(canvas, .{ .up = .heavy, .down = .light, .left = .light, .right = .light }), - // '╁' - 0x2541 => self.draw_lines(canvas, .{ .down = .heavy, .up = .light, .left = .light, .right = .light }), - // '╂' - 0x2542 => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .light, .right = .light }), - // '╃' - 0x2543 => self.draw_lines(canvas, .{ .left = .heavy, .up = .heavy, .right = .light, .down = .light }), - // '╄' - 0x2544 => self.draw_lines(canvas, .{ .right = .heavy, .up = .heavy, .left = .light, .down = .light }), - // '╅' - 0x2545 => self.draw_lines(canvas, .{ .left = .heavy, .down = .heavy, .right = .light, .up = .light }), - // '╆' - 0x2546 => self.draw_lines(canvas, .{ .right = .heavy, .down = .heavy, .left = .light, .up = .light }), - // '╇' - 0x2547 => self.draw_lines(canvas, .{ .down = .light, .up = .heavy, .left = .heavy, .right = .heavy }), - // '╈' - 0x2548 => self.draw_lines(canvas, .{ .up = .light, .down = .heavy, .left = .heavy, .right = .heavy }), - // '╉' - 0x2549 => self.draw_lines(canvas, .{ .right = .light, .left = .heavy, .up = .heavy, .down = .heavy }), - // '╊' - 0x254a => self.draw_lines(canvas, .{ .left = .light, .right = .heavy, .up = .heavy, .down = .heavy }), - // '╋' - 0x254b => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy, .right = .heavy }), - // '╌' - 0x254c => self.draw_light_double_dash_horizontal(canvas), - // '╍' - 0x254d => self.draw_heavy_double_dash_horizontal(canvas), - // '╎' - 0x254e => self.draw_light_double_dash_vertical(canvas), - // '╏' - 0x254f => self.draw_heavy_double_dash_vertical(canvas), - - // '═' - 0x2550 => self.draw_lines(canvas, .{ .left = .double, .right = .double }), - // '║' - 0x2551 => self.draw_lines(canvas, .{ .up = .double, .down = .double }), - // '╒' - 0x2552 => self.draw_lines(canvas, .{ .down = .light, .right = .double }), - // '╓' - 0x2553 => self.draw_lines(canvas, .{ .down = .double, .right = .light }), - // '╔' - 0x2554 => self.draw_lines(canvas, .{ .down = .double, .right = .double }), - // '╕' - 0x2555 => self.draw_lines(canvas, .{ .down = .light, .left = .double }), - // '╖' - 0x2556 => self.draw_lines(canvas, .{ .down = .double, .left = .light }), - // '╗' - 0x2557 => self.draw_lines(canvas, .{ .down = .double, .left = .double }), - // '╘' - 0x2558 => self.draw_lines(canvas, .{ .up = .light, .right = .double }), - // '╙' - 0x2559 => self.draw_lines(canvas, .{ .up = .double, .right = .light }), - // '╚' - 0x255a => self.draw_lines(canvas, .{ .up = .double, .right = .double }), - // '╛' - 0x255b => self.draw_lines(canvas, .{ .up = .light, .left = .double }), - // '╜' - 0x255c => self.draw_lines(canvas, .{ .up = .double, .left = .light }), - // '╝' - 0x255d => self.draw_lines(canvas, .{ .up = .double, .left = .double }), - // '╞' - 0x255e => self.draw_lines(canvas, .{ .up = .light, .down = .light, .right = .double }), - // '╟' - 0x255f => self.draw_lines(canvas, .{ .up = .double, .down = .double, .right = .light }), - - // '╠' - 0x2560 => self.draw_lines(canvas, .{ .up = .double, .down = .double, .right = .double }), - // '╡' - 0x2561 => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .double }), - // '╢' - 0x2562 => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .light }), - // '╣' - 0x2563 => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .double }), - // '╤' - 0x2564 => self.draw_lines(canvas, .{ .down = .light, .left = .double, .right = .double }), - // '╥' - 0x2565 => self.draw_lines(canvas, .{ .down = .double, .left = .light, .right = .light }), - // '╦' - 0x2566 => self.draw_lines(canvas, .{ .down = .double, .left = .double, .right = .double }), - // '╧' - 0x2567 => self.draw_lines(canvas, .{ .up = .light, .left = .double, .right = .double }), - // '╨' - 0x2568 => self.draw_lines(canvas, .{ .up = .double, .left = .light, .right = .light }), - // '╩' - 0x2569 => self.draw_lines(canvas, .{ .up = .double, .left = .double, .right = .double }), - // '╪' - 0x256a => self.draw_lines(canvas, .{ .up = .light, .down = .light, .left = .double, .right = .double }), - // '╫' - 0x256b => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .light, .right = .light }), - // '╬' - 0x256c => self.draw_lines(canvas, .{ .up = .double, .down = .double, .left = .double, .right = .double }), - // '╭' - 0x256d => try self.draw_arc(canvas, .br, .light), - // '╮' - 0x256e => try self.draw_arc(canvas, .bl, .light), - // '╯' - 0x256f => try self.draw_arc(canvas, .tl, .light), - - // '╰' - 0x2570 => try self.draw_arc(canvas, .tr, .light), - // '╱' - 0x2571 => self.draw_light_diagonal_upper_right_to_lower_left(canvas), - // '╲' - 0x2572 => self.draw_light_diagonal_upper_left_to_lower_right(canvas), - // '╳' - 0x2573 => self.draw_light_diagonal_cross(canvas), - // '╴' - 0x2574 => self.draw_lines(canvas, .{ .left = .light }), - // '╵' - 0x2575 => self.draw_lines(canvas, .{ .up = .light }), - // '╶' - 0x2576 => self.draw_lines(canvas, .{ .right = .light }), - // '╷' - 0x2577 => self.draw_lines(canvas, .{ .down = .light }), - // '╸' - 0x2578 => self.draw_lines(canvas, .{ .left = .heavy }), - // '╹' - 0x2579 => self.draw_lines(canvas, .{ .up = .heavy }), - // '╺' - 0x257a => self.draw_lines(canvas, .{ .right = .heavy }), - // '╻' - 0x257b => self.draw_lines(canvas, .{ .down = .heavy }), - // '╼' - 0x257c => self.draw_lines(canvas, .{ .left = .light, .right = .heavy }), - // '╽' - 0x257d => self.draw_lines(canvas, .{ .up = .light, .down = .heavy }), - // '╾' - 0x257e => self.draw_lines(canvas, .{ .left = .heavy, .right = .light }), - // '╿' - 0x257f => self.draw_lines(canvas, .{ .up = .heavy, .down = .light }), - - // '▀' UPPER HALF BLOCK - 0x2580 => self.draw_block(canvas, .upper, 1, half), - // '▁' LOWER ONE EIGHTH BLOCK - 0x2581 => self.draw_block(canvas, .lower, 1, one_eighth), - // '▂' LOWER ONE QUARTER BLOCK - 0x2582 => self.draw_block(canvas, .lower, 1, one_quarter), - // '▃' LOWER THREE EIGHTHS BLOCK - 0x2583 => self.draw_block(canvas, .lower, 1, three_eighths), - // '▄' LOWER HALF BLOCK - 0x2584 => self.draw_block(canvas, .lower, 1, half), - // '▅' LOWER FIVE EIGHTHS BLOCK - 0x2585 => self.draw_block(canvas, .lower, 1, five_eighths), - // '▆' LOWER THREE QUARTERS BLOCK - 0x2586 => self.draw_block(canvas, .lower, 1, three_quarters), - // '▇' LOWER SEVEN EIGHTHS BLOCK - 0x2587 => self.draw_block(canvas, .lower, 1, seven_eighths), - // '█' FULL BLOCK - 0x2588 => self.draw_full_block(canvas), - // '▉' LEFT SEVEN EIGHTHS BLOCK - 0x2589 => self.draw_block(canvas, .left, seven_eighths, 1), - // '▊' LEFT THREE QUARTERS BLOCK - 0x258a => self.draw_block(canvas, .left, three_quarters, 1), - // '▋' LEFT FIVE EIGHTHS BLOCK - 0x258b => self.draw_block(canvas, .left, five_eighths, 1), - // '▌' LEFT HALF BLOCK - 0x258c => self.draw_block(canvas, .left, half, 1), - // '▍' LEFT THREE EIGHTHS BLOCK - 0x258d => self.draw_block(canvas, .left, three_eighths, 1), - // '▎' LEFT ONE QUARTER BLOCK - 0x258e => self.draw_block(canvas, .left, one_quarter, 1), - // '▏' LEFT ONE EIGHTH BLOCK - 0x258f => self.draw_block(canvas, .left, one_eighth, 1), - - // '▐' RIGHT HALF BLOCK - 0x2590 => self.draw_block(canvas, .right, half, 1), - // '░' - 0x2591 => self.draw_light_shade(canvas), - // '▒' - 0x2592 => self.draw_medium_shade(canvas), - // '▓' - 0x2593 => self.draw_dark_shade(canvas), - // '▔' UPPER ONE EIGHTH BLOCK - 0x2594 => self.draw_block(canvas, .upper, 1, one_eighth), - // '▕' RIGHT ONE EIGHTH BLOCK - 0x2595 => self.draw_block(canvas, .right, one_eighth, 1), - // '▖' - 0x2596 => self.draw_quadrant(canvas, .{ .bl = true }), - // '▗' - 0x2597 => self.draw_quadrant(canvas, .{ .br = true }), - // '▘' - 0x2598 => self.draw_quadrant(canvas, .{ .tl = true }), - // '▙' - 0x2599 => self.draw_quadrant(canvas, .{ .tl = true, .bl = true, .br = true }), - // '▚' - 0x259a => self.draw_quadrant(canvas, .{ .tl = true, .br = true }), - // '▛' - 0x259b => self.draw_quadrant(canvas, .{ .tl = true, .tr = true, .bl = true }), - // '▜' - 0x259c => self.draw_quadrant(canvas, .{ .tl = true, .tr = true, .br = true }), - // '▝' - 0x259d => self.draw_quadrant(canvas, .{ .tr = true }), - // '▞' - 0x259e => self.draw_quadrant(canvas, .{ .tr = true, .bl = true }), - // '▟' - 0x259f => self.draw_quadrant(canvas, .{ .tr = true, .bl = true, .br = true }), - - // '◢' - 0x25e2 => self.draw_corner_triangle_shade(canvas, .br, .on), - // '◣' - 0x25e3 => self.draw_corner_triangle_shade(canvas, .bl, .on), - // '◤' - 0x25e4 => self.draw_corner_triangle_shade(canvas, .tl, .on), - // '◥' - 0x25e5 => self.draw_corner_triangle_shade(canvas, .tr, .on), - - // '◸' - 0x25f8 => { - const thickness_px = Thickness.light.height(self.metrics.box_thickness); - // top edge - self.rect( - canvas, - 0, - 0, - self.metrics.cell_width, - thickness_px, - ); - // left edge - self.rect( - canvas, - 0, - 0, - thickness_px, - self.metrics.cell_height -| 1, - ); - // diagonal - self.draw_cell_diagonal( - canvas, - .lower_left, - .upper_right, - ); - }, - // '◹' - 0x25f9 => { - const thickness_px = Thickness.light.height(self.metrics.box_thickness); - // top edge - self.rect( - canvas, - 0, - 0, - self.metrics.cell_width, - thickness_px, - ); - // right edge - self.rect( - canvas, - self.metrics.cell_width -| thickness_px, - 0, - self.metrics.cell_width, - self.metrics.cell_height -| 1, - ); - // diagonal - self.draw_cell_diagonal( - canvas, - .upper_left, - .lower_right, - ); - }, - // '◺' - 0x25fa => { - const thickness_px = Thickness.light.height(self.metrics.box_thickness); - // bottom edge - self.rect( - canvas, - 0, - self.metrics.cell_height -| thickness_px, - self.metrics.cell_width, - self.metrics.cell_height, - ); - // left edge - self.rect( - canvas, - 0, - 1, - thickness_px, - self.metrics.cell_height, - ); - // diagonal - self.draw_cell_diagonal( - canvas, - .upper_left, - .lower_right, - ); - }, - // '◿' - 0x25ff => { - const thickness_px = Thickness.light.height(self.metrics.box_thickness); - // bottom edge - self.rect( - canvas, - 0, - self.metrics.cell_height -| thickness_px, - self.metrics.cell_width, - self.metrics.cell_height, - ); - // right edge - self.rect( - canvas, - self.metrics.cell_width -| thickness_px, - 1, - self.metrics.cell_width, - self.metrics.cell_height, - ); - // diagonal - self.draw_cell_diagonal( - canvas, - .lower_left, - .upper_right, - ); - }, - - 0x2800...0x28ff => self.draw_braille(canvas, cp), - - 0x1fb00...0x1fb3b => self.draw_sextant(canvas, cp), - - octant_min...octant_max => self.draw_octant(canvas, cp), - - // '🬼' - 0x1fb3c => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\... - \\#.. - \\##. - )), - // '🬽' - 0x1fb3d => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\... - \\#\. - \\### - )), - // '🬾' - 0x1fb3e => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\#.. - \\#\. - \\##. - )), - // '🬿' - 0x1fb3f => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\#.. - \\##. - \\### - )), - // '🭀' - 0x1fb40 => try self.draw_smooth_mosaic(canvas, .from( - \\#.. - \\#.. - \\##. - \\##. - )), - - // '🭁' - 0x1fb41 => try self.draw_smooth_mosaic(canvas, .from( - \\/## - \\### - \\### - \\### - )), - // '🭂' - 0x1fb42 => try self.draw_smooth_mosaic(canvas, .from( - \\./# - \\### - \\### - \\### - )), - // '🭃' - 0x1fb43 => try self.draw_smooth_mosaic(canvas, .from( - \\.## - \\.## - \\### - \\### - )), - // '🭄' - 0x1fb44 => try self.draw_smooth_mosaic(canvas, .from( - \\..# - \\.## - \\### - \\### - )), - // '🭅' - 0x1fb45 => try self.draw_smooth_mosaic(canvas, .from( - \\.## - \\.## - \\.## - \\### - )), - // '🭆' - 0x1fb46 => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\./# - \\### - \\### - )), - - // '🭇' - 0x1fb47 => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\... - \\..# - \\.## - )), - // '🭈' - 0x1fb48 => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\... - \\./# - \\### - )), - // '🭉' - 0x1fb49 => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\..# - \\./# - \\.## - )), - // '🭊' - 0x1fb4a => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\..# - \\.## - \\### - )), - // '🭋' - 0x1fb4b => try self.draw_smooth_mosaic(canvas, .from( - \\..# - \\..# - \\.## - \\.## - )), - - // '🭌' - 0x1fb4c => try self.draw_smooth_mosaic(canvas, .from( - \\##\ - \\### - \\### - \\### - )), - // '🭍' - 0x1fb4d => try self.draw_smooth_mosaic(canvas, .from( - \\#\. - \\### - \\### - \\### - )), - // '🭎' - 0x1fb4e => try self.draw_smooth_mosaic(canvas, .from( - \\##. - \\##. - \\### - \\### - )), - // '🭏' - 0x1fb4f => try self.draw_smooth_mosaic(canvas, .from( - \\#.. - \\##. - \\### - \\### - )), - // '🭐' - 0x1fb50 => try self.draw_smooth_mosaic(canvas, .from( - \\##. - \\##. - \\##. - \\### - )), - // '🭑' - 0x1fb51 => try self.draw_smooth_mosaic(canvas, .from( - \\... - \\#\. - \\### - \\### - )), - - // '🭒' - 0x1fb52 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\### - \\\## - )), - // '🭓' - 0x1fb53 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\### - \\.\# - )), - // '🭔' - 0x1fb54 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\.## - \\.## - )), - // '🭕' - 0x1fb55 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\.## - \\..# - )), - // '🭖' - 0x1fb56 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\.## - \\.## - \\.## - )), - - // '🭗' - 0x1fb57 => try self.draw_smooth_mosaic(canvas, .from( - \\##. - \\#.. - \\... - \\... - )), - // '🭘' - 0x1fb58 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\#/. - \\... - \\... - )), - // '🭙' - 0x1fb59 => try self.draw_smooth_mosaic(canvas, .from( - \\##. - \\#/. - \\#.. - \\... - )), - // '🭚' - 0x1fb5a => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\##. - \\#.. - \\... - )), - // '🭛' - 0x1fb5b => try self.draw_smooth_mosaic(canvas, .from( - \\##. - \\##. - \\#.. - \\#.. - )), - - // '🭜' - 0x1fb5c => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\#/. - \\... - )), - // '🭝' - 0x1fb5d => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\### - \\##/ - )), - // '🭞' - 0x1fb5e => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\### - \\#/. - )), - // '🭟' - 0x1fb5f => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\##. - \\##. - )), - // '🭠' - 0x1fb60 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\##. - \\#.. - )), - // '🭡' - 0x1fb61 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\##. - \\##. - \\##. - )), - - // '🭢' - 0x1fb62 => try self.draw_smooth_mosaic(canvas, .from( - \\.## - \\..# - \\... - \\... - )), - // '🭣' - 0x1fb63 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\.\# - \\... - \\... - )), - // '🭤' - 0x1fb64 => try self.draw_smooth_mosaic(canvas, .from( - \\.## - \\.\# - \\..# - \\... - )), - // '🭥' - 0x1fb65 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\.## - \\..# - \\... - )), - // '🭦' - 0x1fb66 => try self.draw_smooth_mosaic(canvas, .from( - \\.## - \\.## - \\..# - \\..# - )), - // '🭧' - 0x1fb67 => try self.draw_smooth_mosaic(canvas, .from( - \\### - \\### - \\.\# - \\... - )), - - // '🭨' - 0x1fb68 => { - try self.draw_edge_triangle(canvas, .left); - canvas.invert(); - }, - // '🭩' - 0x1fb69 => { - try self.draw_edge_triangle(canvas, .top); - canvas.invert(); - }, - // '🭪' - 0x1fb6a => { - try self.draw_edge_triangle(canvas, .right); - canvas.invert(); - }, - // '🭫' - 0x1fb6b => { - try self.draw_edge_triangle(canvas, .bottom); - canvas.invert(); - }, - // '🭬' - 0x1fb6c => try self.draw_edge_triangle(canvas, .left), - // '🭭' - 0x1fb6d => try self.draw_edge_triangle(canvas, .top), - // '🭮' - 0x1fb6e => try self.draw_edge_triangle(canvas, .right), - // '🭯' - 0x1fb6f => try self.draw_edge_triangle(canvas, .bottom), - - // '🭰' - 0x1fb70 => self.draw_vertical_one_eighth_block_n(canvas, 1), - // '🭱' - 0x1fb71 => self.draw_vertical_one_eighth_block_n(canvas, 2), - // '🭲' - 0x1fb72 => self.draw_vertical_one_eighth_block_n(canvas, 3), - // '🭳' - 0x1fb73 => self.draw_vertical_one_eighth_block_n(canvas, 4), - // '🭴' - 0x1fb74 => self.draw_vertical_one_eighth_block_n(canvas, 5), - // '🭵' - 0x1fb75 => self.draw_vertical_one_eighth_block_n(canvas, 6), - - // '🭶' - 0x1fb76 => self.draw_horizontal_one_eighth_block_n(canvas, 1), - // '🭷' - 0x1fb77 => self.draw_horizontal_one_eighth_block_n(canvas, 2), - // '🭸' - 0x1fb78 => self.draw_horizontal_one_eighth_block_n(canvas, 3), - // '🭹' - 0x1fb79 => self.draw_horizontal_one_eighth_block_n(canvas, 4), - // '🭺' - 0x1fb7a => self.draw_horizontal_one_eighth_block_n(canvas, 5), - // '🭻' - 0x1fb7b => self.draw_horizontal_one_eighth_block_n(canvas, 6), - - // '🮂' UPPER ONE QUARTER BLOCK - 0x1fb82 => self.draw_block(canvas, .upper, 1, one_quarter), - // '🮃' UPPER THREE EIGHTHS BLOCK - 0x1fb83 => self.draw_block(canvas, .upper, 1, three_eighths), - // '🮄' UPPER FIVE EIGHTHS BLOCK - 0x1fb84 => self.draw_block(canvas, .upper, 1, five_eighths), - // '🮅' UPPER THREE QUARTERS BLOCK - 0x1fb85 => self.draw_block(canvas, .upper, 1, three_quarters), - // '🮆' UPPER SEVEN EIGHTHS BLOCK - 0x1fb86 => self.draw_block(canvas, .upper, 1, seven_eighths), - - // '🭼' LEFT AND LOWER ONE EIGHTH BLOCK - 0x1fb7c => { - self.draw_block(canvas, .left, one_eighth, 1); - self.draw_block(canvas, .lower, 1, one_eighth); - }, - // '🭽' LEFT AND UPPER ONE EIGHTH BLOCK - 0x1fb7d => { - self.draw_block(canvas, .left, one_eighth, 1); - self.draw_block(canvas, .upper, 1, one_eighth); - }, - // '🭾' RIGHT AND UPPER ONE EIGHTH BLOCK - 0x1fb7e => { - self.draw_block(canvas, .right, one_eighth, 1); - self.draw_block(canvas, .upper, 1, one_eighth); - }, - // '🭿' RIGHT AND LOWER ONE EIGHTH BLOCK - 0x1fb7f => { - self.draw_block(canvas, .right, one_eighth, 1); - self.draw_block(canvas, .lower, 1, one_eighth); - }, - // '🮀' UPPER AND LOWER ONE EIGHTH BLOCK - 0x1fb80 => { - self.draw_block(canvas, .upper, 1, one_eighth); - self.draw_block(canvas, .lower, 1, one_eighth); - }, - // '🮁' - 0x1fb81 => self.draw_horizontal_one_eighth_1358_block(canvas), - - // '🮇' RIGHT ONE QUARTER BLOCK - 0x1fb87 => self.draw_block(canvas, .right, one_quarter, 1), - // '🮈' RIGHT THREE EIGHTHS BLOCK - 0x1fb88 => self.draw_block(canvas, .right, three_eighths, 1), - // '🮉' RIGHT FIVE EIGHTHS BLOCK - 0x1fb89 => self.draw_block(canvas, .right, five_eighths, 1), - // '🮊' RIGHT THREE QUARTERS BLOCK - 0x1fb8a => self.draw_block(canvas, .right, three_quarters, 1), - // '🮋' RIGHT SEVEN EIGHTHS BLOCK - 0x1fb8b => self.draw_block(canvas, .right, seven_eighths, 1), - // '🮌' - 0x1fb8c => self.draw_block_shade(canvas, .left, half, 1, .medium), - // '🮍' - 0x1fb8d => self.draw_block_shade(canvas, .right, half, 1, .medium), - // '🮎' - 0x1fb8e => self.draw_block_shade(canvas, .upper, 1, half, .medium), - // '🮏' - 0x1fb8f => self.draw_block_shade(canvas, .lower, 1, half, .medium), - - // '🮐' - 0x1fb90 => self.draw_medium_shade(canvas), - // '🮑' - 0x1fb91 => { - self.draw_medium_shade(canvas); - self.draw_block(canvas, .upper, 1, half); - }, - // '🮒' - 0x1fb92 => { - self.draw_medium_shade(canvas); - self.draw_block(canvas, .lower, 1, half); - }, - // '🮔' - 0x1fb94 => { - self.draw_medium_shade(canvas); - self.draw_block(canvas, .right, half, 1); - }, - // '🮕' - 0x1fb95 => self.draw_checkerboard_fill(canvas, 0), - // '🮖' - 0x1fb96 => self.draw_checkerboard_fill(canvas, 1), - // '🮗' - 0x1fb97 => { - self.draw_horizontal_one_eighth_block_n(canvas, 2); - self.draw_horizontal_one_eighth_block_n(canvas, 3); - self.draw_horizontal_one_eighth_block_n(canvas, 6); - self.draw_horizontal_one_eighth_block_n(canvas, 7); - }, - // '🮘' - 0x1fb98 => self.draw_upper_left_to_lower_right_fill(canvas), - // '🮙' - 0x1fb99 => self.draw_upper_right_to_lower_left_fill(canvas), - // '🮚' - 0x1fb9a => { - try self.draw_edge_triangle(canvas, .top); - try self.draw_edge_triangle(canvas, .bottom); - }, - // '🮛' - 0x1fb9b => { - try self.draw_edge_triangle(canvas, .left); - try self.draw_edge_triangle(canvas, .right); - }, - // '🮜' - 0x1fb9c => self.draw_corner_triangle_shade(canvas, .tl, .medium), - // '🮝' - 0x1fb9d => self.draw_corner_triangle_shade(canvas, .tr, .medium), - // '🮞' - 0x1fb9e => self.draw_corner_triangle_shade(canvas, .br, .medium), - // '🮟' - 0x1fb9f => self.draw_corner_triangle_shade(canvas, .bl, .medium), - - // '🮠' - 0x1fba0 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true }), - // '🮡' - 0x1fba1 => self.draw_corner_diagonal_lines(canvas, .{ .tr = true }), - // '🮢' - 0x1fba2 => self.draw_corner_diagonal_lines(canvas, .{ .bl = true }), - // '🮣' - 0x1fba3 => self.draw_corner_diagonal_lines(canvas, .{ .br = true }), - // '🮤' - 0x1fba4 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .bl = true }), - // '🮥' - 0x1fba5 => self.draw_corner_diagonal_lines(canvas, .{ .tr = true, .br = true }), - // '🮦' - 0x1fba6 => self.draw_corner_diagonal_lines(canvas, .{ .bl = true, .br = true }), - // '🮧' - 0x1fba7 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true }), - // '🮨' - 0x1fba8 => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .br = true }), - // '🮩' - 0x1fba9 => self.draw_corner_diagonal_lines(canvas, .{ .tr = true, .bl = true }), - // '🮪' - 0x1fbaa => self.draw_corner_diagonal_lines(canvas, .{ .tr = true, .bl = true, .br = true }), - // '🮫' - 0x1fbab => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .bl = true, .br = true }), - // '🮬' - 0x1fbac => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .br = true }), - // '🮭' - 0x1fbad => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .bl = true }), - // '🮮' - 0x1fbae => self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .bl = true, .br = true }), - // '🮯' - 0x1fbaf => self.draw_lines(canvas, .{ .up = .heavy, .down = .heavy, .left = .light, .right = .light }), - - // '🮽' - 0x1fbbd => { - self.draw_light_diagonal_cross(canvas); - canvas.invert(); - }, - // '🮾' - 0x1fbbe => { - self.draw_corner_diagonal_lines(canvas, .{ .br = true }); - canvas.invert(); - }, - // '🮿' - 0x1fbbf => { - self.draw_corner_diagonal_lines(canvas, .{ .tl = true, .tr = true, .bl = true, .br = true }); - canvas.invert(); - }, - - // '🯎' - 0x1fbce => self.draw_block(canvas, .left, two_thirds, 1), - // '🯏' - 0x1fbcf => self.draw_block(canvas, .left, one_third, 1), - // '🯐' - 0x1fbd0 => self.draw_cell_diagonal( - canvas, - .middle_right, - .lower_left, - ), - // '🯑' - 0x1fbd1 => self.draw_cell_diagonal( - canvas, - .upper_right, - .middle_left, - ), - // '🯒' - 0x1fbd2 => self.draw_cell_diagonal( - canvas, - .upper_left, - .middle_right, - ), - // '🯓' - 0x1fbd3 => self.draw_cell_diagonal( - canvas, - .middle_left, - .lower_right, - ), - // '🯔' - 0x1fbd4 => self.draw_cell_diagonal( - canvas, - .upper_left, - .lower_center, - ), - // '🯕' - 0x1fbd5 => self.draw_cell_diagonal( - canvas, - .upper_center, - .lower_right, - ), - // '🯖' - 0x1fbd6 => self.draw_cell_diagonal( - canvas, - .upper_right, - .lower_center, - ), - // '🯗' - 0x1fbd7 => self.draw_cell_diagonal( - canvas, - .upper_center, - .lower_left, - ), - // '🯘' - 0x1fbd8 => { - self.draw_cell_diagonal( - canvas, - .upper_left, - .middle_center, - ); - self.draw_cell_diagonal( - canvas, - .middle_center, - .upper_right, - ); - }, - // '🯙' - 0x1fbd9 => { - self.draw_cell_diagonal( - canvas, - .upper_right, - .middle_center, - ); - self.draw_cell_diagonal( - canvas, - .middle_center, - .lower_right, - ); - }, - // '🯚' - 0x1fbda => { - self.draw_cell_diagonal( - canvas, - .lower_left, - .middle_center, - ); - self.draw_cell_diagonal( - canvas, - .middle_center, - .lower_right, - ); - }, - // '🯛' - 0x1fbdb => { - self.draw_cell_diagonal( - canvas, - .upper_left, - .middle_center, - ); - self.draw_cell_diagonal( - canvas, - .middle_center, - .lower_left, - ); - }, - // '🯜' - 0x1fbdc => { - self.draw_cell_diagonal( - canvas, - .upper_left, - .lower_center, - ); - self.draw_cell_diagonal( - canvas, - .lower_center, - .upper_right, - ); - }, - // '🯝' - 0x1fbdd => { - self.draw_cell_diagonal( - canvas, - .upper_right, - .middle_left, - ); - self.draw_cell_diagonal( - canvas, - .middle_left, - .lower_right, - ); - }, - // '🯞' - 0x1fbde => { - self.draw_cell_diagonal( - canvas, - .lower_left, - .upper_center, - ); - self.draw_cell_diagonal( - canvas, - .upper_center, - .lower_right, - ); - }, - // '🯟' - 0x1fbdf => { - self.draw_cell_diagonal( - canvas, - .upper_left, - .middle_right, - ); - self.draw_cell_diagonal( - canvas, - .middle_right, - .lower_left, - ); - }, - - // '🯠' - 0x1fbe0 => self.draw_circle(canvas, .top, false), - // '🯡' - 0x1fbe1 => self.draw_circle(canvas, .right, false), - // '🯢' - 0x1fbe2 => self.draw_circle(canvas, .bottom, false), - // '🯣' - 0x1fbe3 => self.draw_circle(canvas, .left, false), - // '🯤' - 0x1fbe4 => self.draw_block(canvas, .upper_center, 0.5, 0.5), - // '🯥' - 0x1fbe5 => self.draw_block(canvas, .lower_center, 0.5, 0.5), - // '🯦' - 0x1fbe6 => self.draw_block(canvas, .middle_left, 0.5, 0.5), - // '🯧' - 0x1fbe7 => self.draw_block(canvas, .middle_right, 0.5, 0.5), - // '🯨' - 0x1fbe8 => self.draw_circle(canvas, .top, true), - // '🯩' - 0x1fbe9 => self.draw_circle(canvas, .right, true), - // '🯪' - 0x1fbea => self.draw_circle(canvas, .bottom, true), - // '🯫' - 0x1fbeb => self.draw_circle(canvas, .left, true), - // '🯬' - 0x1fbec => self.draw_circle(canvas, .top_right, true), - // '🯭' - 0x1fbed => self.draw_circle(canvas, .bottom_left, true), - // '🯮' - 0x1fbee => self.draw_circle(canvas, .bottom_right, true), - // '🯯' - 0x1fbef => self.draw_circle(canvas, .top_left, true), - - // (Below:) - // Branch drawing character set, used for drawing git-like - // graphs in the terminal. Originally implemented in Kitty. - // Ref: - // - https://github.com/kovidgoyal/kitty/pull/7681 - // - https://github.com/kovidgoyal/kitty/pull/7805 - // NOTE: Kitty is GPL licensed, and its code was not referenced - // for these characters, only the loose specification of - // the character set in the pull request descriptions. - // - // TODO(qwerasd): This should be in another file, but really the - // general organization of the sprite font code - // needs to be reworked eventually. - // - //           - //                     - //                     - //             - - // '' - 0x0f5d0 => self.hline_middle(canvas, .light), - // '' - 0x0f5d1 => self.vline_middle(canvas, .light), - // '' - 0x0f5d2 => self.draw_fading_line(canvas, .right, .light), - // '' - 0x0f5d3 => self.draw_fading_line(canvas, .left, .light), - // '' - 0x0f5d4 => self.draw_fading_line(canvas, .bottom, .light), - // '' - 0x0f5d5 => self.draw_fading_line(canvas, .top, .light), - // '' - 0x0f5d6 => try self.draw_arc(canvas, .br, .light), - // '' - 0x0f5d7 => try self.draw_arc(canvas, .bl, .light), - // '' - 0x0f5d8 => try self.draw_arc(canvas, .tr, .light), - // '' - 0x0f5d9 => try self.draw_arc(canvas, .tl, .light), - // '' - 0x0f5da => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .tr, .light); - }, - // '' - 0x0f5db => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5dc => { - try self.draw_arc(canvas, .tr, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5dd => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .tl, .light); - }, - // '' - 0x0f5de => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .bl, .light); - }, - // '' - 0x0f5df => { - try self.draw_arc(canvas, .tl, .light); - try self.draw_arc(canvas, .bl, .light); - }, - - // '' - 0x0f5e0 => { - try self.draw_arc(canvas, .bl, .light); - self.hline_middle(canvas, .light); - }, - // '' - 0x0f5e1 => { - try self.draw_arc(canvas, .br, .light); - self.hline_middle(canvas, .light); - }, - // '' - 0x0f5e2 => { - try self.draw_arc(canvas, .br, .light); - try self.draw_arc(canvas, .bl, .light); - }, - // '' - 0x0f5e3 => { - try self.draw_arc(canvas, .tl, .light); - self.hline_middle(canvas, .light); - }, - // '' - 0x0f5e4 => { - try self.draw_arc(canvas, .tr, .light); - self.hline_middle(canvas, .light); - }, - // '' - 0x0f5e5 => { - try self.draw_arc(canvas, .tr, .light); - try self.draw_arc(canvas, .tl, .light); - }, - // '' - 0x0f5e6 => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .tl, .light); - try self.draw_arc(canvas, .tr, .light); - }, - // '' - 0x0f5e7 => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .bl, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5e8 => { - self.hline_middle(canvas, .light); - try self.draw_arc(canvas, .bl, .light); - try self.draw_arc(canvas, .tl, .light); - }, - // '' - 0x0f5e9 => { - self.hline_middle(canvas, .light); - try self.draw_arc(canvas, .tr, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5ea => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .tl, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5eb => { - self.vline_middle(canvas, .light); - try self.draw_arc(canvas, .tr, .light); - try self.draw_arc(canvas, .bl, .light); - }, - // '' - 0x0f5ec => { - self.hline_middle(canvas, .light); - try self.draw_arc(canvas, .tl, .light); - try self.draw_arc(canvas, .br, .light); - }, - // '' - 0x0f5ed => { - self.hline_middle(canvas, .light); - try self.draw_arc(canvas, .tr, .light); - try self.draw_arc(canvas, .bl, .light); - }, - // '' - 0x0f5ee => self.draw_branch_node(canvas, .{ .filled = true }, .light), - // '' - 0x0f5ef => self.draw_branch_node(canvas, .{}, .light), - - // '' - 0x0f5f0 => self.draw_branch_node(canvas, .{ - .right = true, - .filled = true, - }, .light), - // '' - 0x0f5f1 => self.draw_branch_node(canvas, .{ - .right = true, - }, .light), - // '' - 0x0f5f2 => self.draw_branch_node(canvas, .{ - .left = true, - .filled = true, - }, .light), - // '' - 0x0f5f3 => self.draw_branch_node(canvas, .{ - .left = true, - }, .light), - // '' - 0x0f5f4 => self.draw_branch_node(canvas, .{ - .left = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f5f5 => self.draw_branch_node(canvas, .{ - .left = true, - .right = true, - }, .light), - // '' - 0x0f5f6 => self.draw_branch_node(canvas, .{ - .down = true, - .filled = true, - }, .light), - // '' - 0x0f5f7 => self.draw_branch_node(canvas, .{ - .down = true, - }, .light), - // '' - 0x0f5f8 => self.draw_branch_node(canvas, .{ - .up = true, - .filled = true, - }, .light), - // '' - 0x0f5f9 => self.draw_branch_node(canvas, .{ - .up = true, - }, .light), - // '' - 0x0f5fa => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .filled = true, - }, .light), - // '' - 0x0f5fb => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - }, .light), - // '' - 0x0f5fc => self.draw_branch_node(canvas, .{ - .right = true, - .down = true, - .filled = true, - }, .light), - // '' - 0x0f5fd => self.draw_branch_node(canvas, .{ - .right = true, - .down = true, - }, .light), - // '' - 0x0f5fe => self.draw_branch_node(canvas, .{ - .left = true, - .down = true, - .filled = true, - }, .light), - // '' - 0x0f5ff => self.draw_branch_node(canvas, .{ - .left = true, - .down = true, - }, .light), - - // '' - 0x0f600 => self.draw_branch_node(canvas, .{ - .up = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f601 => self.draw_branch_node(canvas, .{ - .up = true, - .right = true, - }, .light), - // '' - 0x0f602 => self.draw_branch_node(canvas, .{ - .up = true, - .left = true, - .filled = true, - }, .light), - // '' - 0x0f603 => self.draw_branch_node(canvas, .{ - .up = true, - .left = true, - }, .light), - // '' - 0x0f604 => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f605 => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .right = true, - }, .light), - // '' - 0x0f606 => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .left = true, - .filled = true, - }, .light), - // '' - 0x0f607 => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .left = true, - }, .light), - // '' - 0x0f608 => self.draw_branch_node(canvas, .{ - .down = true, - .left = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f609 => self.draw_branch_node(canvas, .{ - .down = true, - .left = true, - .right = true, - }, .light), - // '' - 0x0f60a => self.draw_branch_node(canvas, .{ - .up = true, - .left = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f60b => self.draw_branch_node(canvas, .{ - .up = true, - .left = true, - .right = true, - }, .light), - // '' - 0x0f60c => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .left = true, - .right = true, - .filled = true, - }, .light), - // '' - 0x0f60d => self.draw_branch_node(canvas, .{ - .up = true, - .down = true, - .left = true, - .right = true, - }, .light), - - // '𜰡' - SEPARATED BLOCK QUADRANT-1 - 0x1cc21 => try self.draw_separated_block_quadrant(canvas, "1"), - // '𜰢' - SEPARATED BLOCK QUADRANT-2 - 0x1cc22 => try self.draw_separated_block_quadrant(canvas, "2"), - // '𜰣' - SEPARATED BLOCK QUADRANT-12 - 0x1cc23 => try self.draw_separated_block_quadrant(canvas, "12"), - // '𜰤' - SEPARATED BLOCK QUADRANT-3 - 0x1cc24 => try self.draw_separated_block_quadrant(canvas, "3"), - // '𜰥' - SEPARATED BLOCK QUADRANT-13 - 0x1cc25 => try self.draw_separated_block_quadrant(canvas, "13"), - // '𜰦' - SEPARATED BLOCK QUADRANT-23 - 0x1cc26 => try self.draw_separated_block_quadrant(canvas, "23"), - // '𜰧' - SEPARATED BLOCK QUADRANT-123 - 0x1cc27 => try self.draw_separated_block_quadrant(canvas, "123"), - // '𜰨' - SEPARATED BLOCK QUADRANT-4 - 0x1cc28 => try self.draw_separated_block_quadrant(canvas, "4"), - // '𜰩' - SEPARATED BLOCK QUADRANT-14 - 0x1cc29 => try self.draw_separated_block_quadrant(canvas, "14"), - // '𜰪' - SEPARATED BLOCK QUADRANT-24 - 0x1cc2a => try self.draw_separated_block_quadrant(canvas, "24"), - // '𜰫' - SEPARATED BLOCK QUADRANT-124 - 0x1cc2b => try self.draw_separated_block_quadrant(canvas, "124"), - // '𜰬' - SEPARATED BLOCK QUADRANT-34 - 0x1cc2c => try self.draw_separated_block_quadrant(canvas, "34"), - // '𜰭' - SEPARATED BLOCK QUADRANT-134 - 0x1cc2d => try self.draw_separated_block_quadrant(canvas, "134"), - // '𜰮' - SEPARATED BLOCK QUADRANT-234 - 0x1cc2e => try self.draw_separated_block_quadrant(canvas, "234"), - // '𜰯' - SEPARATED BLOCK QUADRANT-1234 - 0x1cc2f => try self.draw_separated_block_quadrant(canvas, "1234"), - - else => return error.InvalidCodepoint, - } -} - -fn draw_lines( - self: Box, - canvas: *font.sprite.Canvas, - lines: Lines, -) void { - const light_px = Thickness.light.height(self.metrics.box_thickness); - const heavy_px = Thickness.heavy.height(self.metrics.box_thickness); - - // Top of light horizontal strokes - const h_light_top = (self.metrics.cell_height -| light_px) / 2; - // Bottom of light horizontal strokes - const h_light_bottom = h_light_top +| light_px; - - // Top of heavy horizontal strokes - const h_heavy_top = (self.metrics.cell_height -| heavy_px) / 2; - // Bottom of heavy horizontal strokes - const h_heavy_bottom = h_heavy_top +| heavy_px; - - // Top of the top doubled horizontal stroke (bottom is `h_light_top`) - const h_double_top = h_light_top -| light_px; - // Bottom of the bottom doubled horizontal stroke (top is `h_light_bottom`) - const h_double_bottom = h_light_bottom +| light_px; - - // Left of light vertical strokes - const v_light_left = (self.metrics.cell_width -| light_px) / 2; - // Right of light vertical strokes - const v_light_right = v_light_left +| light_px; - - // Left of heavy vertical strokes - const v_heavy_left = (self.metrics.cell_width -| heavy_px) / 2; - // Right of heavy vertical strokes - const v_heavy_right = v_heavy_left +| heavy_px; - - // Left of the left doubled vertical stroke (right is `v_light_left`) - const v_double_left = v_light_left -| light_px; - // Right of the right doubled vertical stroke (left is `v_light_right`) - const v_double_right = v_light_right +| light_px; - - // The bottom of the up line - const up_bottom = if (lines.left == .heavy or lines.right == .heavy) - h_heavy_bottom - else if (lines.left != lines.right or lines.down == lines.up) - if (lines.left == .double or lines.right == .double) - h_double_bottom - else - h_light_bottom - else if (lines.left == .none and lines.right == .none) - h_light_bottom - else - h_light_top; - - // The top of the down line - const down_top = if (lines.left == .heavy or lines.right == .heavy) - h_heavy_top - else if (lines.left != lines.right or lines.up == lines.down) - if (lines.left == .double or lines.right == .double) - h_double_top - else - h_light_top - else if (lines.left == .none and lines.right == .none) - h_light_top - else - h_light_bottom; - - // The right of the left line - const left_right = if (lines.up == .heavy or lines.down == .heavy) - v_heavy_right - else if (lines.up != lines.down or lines.left == lines.right) - if (lines.up == .double or lines.down == .double) - v_double_right - else - v_light_right - else if (lines.up == .none and lines.down == .none) - v_light_right - else - v_light_left; - - // The left of the right line - const right_left = if (lines.up == .heavy or lines.down == .heavy) - v_heavy_left - else if (lines.up != lines.down or lines.right == lines.left) - if (lines.up == .double or lines.down == .double) - v_double_left - else - v_light_left - else if (lines.up == .none and lines.down == .none) - v_light_left - else - v_light_right; - - switch (lines.up) { - .none => {}, - .light => self.rect(canvas, v_light_left, 0, v_light_right, up_bottom), - .heavy => self.rect(canvas, v_heavy_left, 0, v_heavy_right, up_bottom), - .double => { - const left_bottom = if (lines.left == .double) h_light_top else up_bottom; - const right_bottom = if (lines.right == .double) h_light_top else up_bottom; - - self.rect(canvas, v_double_left, 0, v_light_left, left_bottom); - self.rect(canvas, v_light_right, 0, v_double_right, right_bottom); - }, - } - - switch (lines.right) { - .none => {}, - .light => self.rect(canvas, right_left, h_light_top, self.metrics.cell_width, h_light_bottom), - .heavy => self.rect(canvas, right_left, h_heavy_top, self.metrics.cell_width, h_heavy_bottom), - .double => { - const top_left = if (lines.up == .double) v_light_right else right_left; - const bottom_left = if (lines.down == .double) v_light_right else right_left; - - self.rect(canvas, top_left, h_double_top, self.metrics.cell_width, h_light_top); - self.rect(canvas, bottom_left, h_light_bottom, self.metrics.cell_width, h_double_bottom); - }, - } - - switch (lines.down) { - .none => {}, - .light => self.rect(canvas, v_light_left, down_top, v_light_right, self.metrics.cell_height), - .heavy => self.rect(canvas, v_heavy_left, down_top, v_heavy_right, self.metrics.cell_height), - .double => { - const left_top = if (lines.left == .double) h_light_bottom else down_top; - const right_top = if (lines.right == .double) h_light_bottom else down_top; - - self.rect(canvas, v_double_left, left_top, v_light_left, self.metrics.cell_height); - self.rect(canvas, v_light_right, right_top, v_double_right, self.metrics.cell_height); - }, - } - - switch (lines.left) { - .none => {}, - .light => self.rect(canvas, 0, h_light_top, left_right, h_light_bottom), - .heavy => self.rect(canvas, 0, h_heavy_top, left_right, h_heavy_bottom), - .double => { - const top_right = if (lines.up == .double) v_light_left else left_right; - const bottom_right = if (lines.down == .double) v_light_left else left_right; - - self.rect(canvas, 0, h_double_top, top_right, h_light_top); - self.rect(canvas, 0, h_light_bottom, bottom_right, h_double_bottom); - }, - } -} - -fn draw_light_triple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 3, - Thickness.light.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_heavy_triple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 3, - Thickness.heavy.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_light_triple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 3, - Thickness.light.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_heavy_triple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 3, - Thickness.heavy.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_light_quadruple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 4, - Thickness.light.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_heavy_quadruple_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 4, - Thickness.heavy.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_light_quadruple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 4, - Thickness.light.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_heavy_quadruple_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 4, - Thickness.heavy.height(self.metrics.box_thickness), - @max(4, Thickness.light.height(self.metrics.box_thickness)), - ); -} - -fn draw_light_double_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 2, - Thickness.light.height(self.metrics.box_thickness), - Thickness.light.height(self.metrics.box_thickness), - ); -} - -fn draw_heavy_double_dash_horizontal(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_horizontal( - canvas, - 2, - Thickness.heavy.height(self.metrics.box_thickness), - Thickness.heavy.height(self.metrics.box_thickness), - ); -} - -fn draw_light_double_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 2, - Thickness.light.height(self.metrics.box_thickness), - Thickness.heavy.height(self.metrics.box_thickness), - ); -} - -fn draw_heavy_double_dash_vertical(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_dash_vertical( - canvas, - 2, - Thickness.heavy.height(self.metrics.box_thickness), - Thickness.heavy.height(self.metrics.box_thickness), - ); -} - -fn draw_light_diagonal_upper_right_to_lower_left(self: Box, canvas: *font.sprite.Canvas) void { - canvas.line(.{ - .p0 = .{ .x = @floatFromInt(self.metrics.cell_width), .y = 0 }, - .p1 = .{ .x = 0, .y = @floatFromInt(self.metrics.cell_height) }, - }, @floatFromInt(Thickness.light.height(self.metrics.box_thickness)), .on) catch {}; -} - -fn draw_light_diagonal_upper_left_to_lower_right(self: Box, canvas: *font.sprite.Canvas) void { - canvas.line(.{ - .p0 = .{ .x = 0, .y = 0 }, - .p1 = .{ - .x = @floatFromInt(self.metrics.cell_width), - .y = @floatFromInt(self.metrics.cell_height), - }, - }, @floatFromInt(Thickness.light.height(self.metrics.box_thickness)), .on) catch {}; -} - -fn draw_light_diagonal_cross(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_light_diagonal_upper_right_to_lower_left(canvas); - self.draw_light_diagonal_upper_left_to_lower_right(canvas); -} - -fn draw_block( - self: Box, - canvas: *font.sprite.Canvas, - comptime alignment: Alignment, - comptime width: f64, - comptime height: f64, -) void { - self.draw_block_shade(canvas, alignment, width, height, .on); -} - -fn draw_block_shade( - self: Box, - canvas: *font.sprite.Canvas, - comptime alignment: Alignment, - comptime width: f64, - comptime height: f64, - comptime shade: Shade, -) void { - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - - const w: u32 = @intFromFloat(@round(float_width * width)); - const h: u32 = @intFromFloat(@round(float_height * height)); - - const x = switch (alignment.horizontal) { - .left => 0, - .right => self.metrics.cell_width - w, - .center => (self.metrics.cell_width - w) / 2, - }; - const y = switch (alignment.vertical) { - .top => 0, - .bottom => self.metrics.cell_height - h, - .middle => (self.metrics.cell_height - h) / 2, - }; - - canvas.rect(.{ - .x = x, - .y = y, - .width = w, - .height = h, - }, @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade)))); -} - -fn draw_corner_triangle_shade( - self: Box, - canvas: *font.sprite.Canvas, - comptime corner: Corner, - comptime shade: Shade, -) void { - const x0, const y0, const x1, const y1, const x2, const y2 = switch (corner) { - .tl => .{ 0, 0, 0, self.metrics.cell_height, self.metrics.cell_width, 0 }, - .tr => .{ 0, 0, self.metrics.cell_width, self.metrics.cell_height, self.metrics.cell_width, 0 }, - .bl => .{ 0, 0, 0, self.metrics.cell_height, self.metrics.cell_width, self.metrics.cell_height }, - .br => .{ 0, self.metrics.cell_height, self.metrics.cell_width, self.metrics.cell_height, self.metrics.cell_width, 0 }, - }; - - canvas.triangle(.{ - .p0 = .{ .x = @floatFromInt(x0), .y = @floatFromInt(y0) }, - .p1 = .{ .x = @floatFromInt(x1), .y = @floatFromInt(y1) }, - .p2 = .{ .x = @floatFromInt(x2), .y = @floatFromInt(y2) }, - }, @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade)))) catch {}; -} - -fn draw_full_block(self: Box, canvas: *font.sprite.Canvas) void { - self.rect(canvas, 0, 0, self.metrics.cell_width, self.metrics.cell_height); -} - -fn draw_vertical_one_eighth_block_n(self: Box, canvas: *font.sprite.Canvas, n: u32) void { - const x = @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(n)) * @as(f64, @floatFromInt(self.metrics.cell_width)) / 8))); - const w = @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 8))); - self.rect(canvas, x, 0, x + w, self.metrics.cell_height); -} - -fn draw_checkerboard_fill(self: Box, canvas: *font.sprite.Canvas, parity: u1) void { - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const x_size: usize = 4; - const y_size: usize = @intFromFloat(@round(4 * (float_height / float_width))); - for (0..x_size) |x| { - const x0 = (self.metrics.cell_width * x) / x_size; - const x1 = (self.metrics.cell_width * (x + 1)) / x_size; - for (0..y_size) |y| { - const y0 = (self.metrics.cell_height * y) / y_size; - const y1 = (self.metrics.cell_height * (y + 1)) / y_size; - if ((x + y) % 2 == parity) { - canvas.rect(.{ - .x = @intCast(x0), - .y = @intCast(y0), - .width = @intCast(x1 -| x0), - .height = @intCast(y1 -| y0), - }, .on); - } - } - } -} - -fn draw_upper_left_to_lower_right_fill(self: Box, canvas: *font.sprite.Canvas) void { - const thick_px = Thickness.light.height(self.metrics.box_thickness); - const line_count = self.metrics.cell_width / (2 * thick_px); - - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const float_thick: f64 = @floatFromInt(thick_px); - const stride = @round(float_width / @as(f64, @floatFromInt(line_count))); - - for (0..line_count * 2 + 1) |_i| { - const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count)); - const top_x = @as(f64, @floatFromInt(i)) * stride; - const bottom_x = float_width + top_x; - canvas.line(.{ - .p0 = .{ .x = top_x, .y = 0 }, - .p1 = .{ .x = bottom_x, .y = float_height }, - }, float_thick, .on) catch {}; - } -} - -fn draw_upper_right_to_lower_left_fill(self: Box, canvas: *font.sprite.Canvas) void { - const thick_px = Thickness.light.height(self.metrics.box_thickness); - const line_count = self.metrics.cell_width / (2 * thick_px); - - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const float_thick: f64 = @floatFromInt(thick_px); - const stride = @round(float_width / @as(f64, @floatFromInt(line_count))); - - for (0..line_count * 2 + 1) |_i| { - const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count)); - const bottom_x = @as(f64, @floatFromInt(i)) * stride; - const top_x = float_width + bottom_x; - canvas.line(.{ - .p0 = .{ .x = top_x, .y = 0 }, - .p1 = .{ .x = bottom_x, .y = float_height }, - }, float_thick, .on) catch {}; - } -} - -fn draw_corner_diagonal_lines( - self: Box, - canvas: *font.sprite.Canvas, - comptime corners: Quads, -) void { - const thick_px = Thickness.light.height(self.metrics.box_thickness); - - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const float_thick: f64 = @floatFromInt(thick_px); - const center_x: f64 = @floatFromInt(self.metrics.cell_width / 2 + self.metrics.cell_width % 2); - const center_y: f64 = @floatFromInt(self.metrics.cell_height / 2 + self.metrics.cell_height % 2); - - if (corners.tl) canvas.line(.{ - .p0 = .{ .x = center_x, .y = 0 }, - .p1 = .{ .x = 0, .y = center_y }, - }, float_thick, .on) catch {}; - - if (corners.tr) canvas.line(.{ - .p0 = .{ .x = center_x, .y = 0 }, - .p1 = .{ .x = float_width, .y = center_y }, - }, float_thick, .on) catch {}; - - if (corners.bl) canvas.line(.{ - .p0 = .{ .x = center_x, .y = float_height }, - .p1 = .{ .x = 0, .y = center_y }, - }, float_thick, .on) catch {}; - - if (corners.br) canvas.line(.{ - .p0 = .{ .x = center_x, .y = float_height }, - .p1 = .{ .x = float_width, .y = center_y }, - }, float_thick, .on) catch {}; -} - -fn draw_cell_diagonal( - self: Box, - canvas: *font.sprite.Canvas, - comptime from: Alignment, - comptime to: Alignment, -) void { - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - - const x0: f64 = switch (from.horizontal) { - .left => 0, - .right => float_width, - .center => float_width / 2, - }; - const y0: f64 = switch (from.vertical) { - .top => 0, - .bottom => float_height, - .middle => float_height / 2, - }; - const x1: f64 = switch (to.horizontal) { - .left => 0, - .right => float_width, - .center => float_width / 2, - }; - const y1: f64 = switch (to.vertical) { - .top => 0, - .bottom => float_height, - .middle => float_height / 2, - }; - - self.draw_line( - canvas, - .{ .x = x0, .y = y0 }, - .{ .x = x1, .y = y1 }, - .light, - ) catch {}; -} - -fn draw_fading_line( - self: Box, - canvas: *font.sprite.Canvas, - comptime to: Edge, - comptime thickness: Thickness, -) void { - const thick_px = thickness.height(self.metrics.box_thickness); - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - - // Top of horizontal strokes - const h_top = (self.metrics.cell_height -| thick_px) / 2; - // Bottom of horizontal strokes - const h_bottom = h_top +| thick_px; - // Left of vertical strokes - const v_left = (self.metrics.cell_width -| thick_px) / 2; - // Right of vertical strokes - const v_right = v_left +| thick_px; - - // If we're fading to the top or left, we start with 0.0 - // and increment up as we progress, otherwise we start - // at 255.0 and increment down (negative). - var color: f64 = switch (to) { - .top, .left => 0.0, - .bottom, .right => 255.0, - }; - const inc: f64 = 255.0 / switch (to) { - .top => float_height, - .bottom => -float_height, - .left => float_width, - .right => -float_width, - }; - - switch (to) { - .top, .bottom => { - for (0..self.metrics.cell_height) |y| { - for (v_left..v_right) |x| { - canvas.pixel( - @intCast(x), - @intCast(y), - @enumFromInt(@as(u8, @intFromFloat(@round(color)))), - ); - } - color += inc; - } - }, - .left, .right => { - for (0..self.metrics.cell_width) |x| { - for (h_top..h_bottom) |y| { - canvas.pixel( - @intCast(x), - @intCast(y), - @enumFromInt(@as(u8, @intFromFloat(@round(color)))), - ); - } - color += inc; - } - }, - } -} - -fn draw_branch_node( - self: Box, - canvas: *font.sprite.Canvas, - node: BranchNode, - comptime thickness: Thickness, -) void { - const thick_px = thickness.height(self.metrics.box_thickness); - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const float_thick: f64 = @floatFromInt(thick_px); - - // Top of horizontal strokes - const h_top = (self.metrics.cell_height -| thick_px) / 2; - // Bottom of horizontal strokes - const h_bottom = h_top +| thick_px; - // Left of vertical strokes - const v_left = (self.metrics.cell_width -| thick_px) / 2; - // Right of vertical strokes - const v_right = v_left +| thick_px; - - // We calculate the center of the circle this way - // to ensure it aligns with box drawing characters - // since the lines are sometimes off center to - // make sure they aren't split between pixels. - const cx: f64 = @as(f64, @floatFromInt(v_left)) + float_thick / 2; - const cy: f64 = @as(f64, @floatFromInt(h_top)) + float_thick / 2; - // The radius needs to be the smallest distance from the center to an edge. - const r: f64 = @min( - @min(cx, cy), - @min(float_width - cx, float_height - cy), - ); - - var ctx = canvas.getContext(); - defer ctx.deinit(); - ctx.setSource(.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }); - ctx.setLineWidth(float_thick); - - // These @intFromFloat casts shouldn't ever fail since r can never - // be greater than cx or cy, so when subtracting it from them the - // result can never be negative. - if (node.up) - self.rect(canvas, v_left, 0, v_right, @intFromFloat(@ceil(cy - r))); - if (node.right) - self.rect(canvas, @intFromFloat(@floor(cx + r)), h_top, self.metrics.cell_width, h_bottom); - if (node.down) - self.rect(canvas, v_left, @intFromFloat(@floor(cy + r)), v_right, self.metrics.cell_height); - if (node.left) - self.rect(canvas, 0, h_top, @intFromFloat(@ceil(cx - r)), h_bottom); - - if (node.filled) { - ctx.arc(cx, cy, r, 0, std.math.pi * 2) catch return; - ctx.closePath() catch return; - ctx.fill() catch return; - } else { - ctx.arc(cx, cy, r - float_thick / 2, 0, std.math.pi * 2) catch return; - ctx.closePath() catch return; - ctx.stroke() catch return; - } -} - -fn draw_circle( - self: Box, - canvas: *font.sprite.Canvas, - comptime position: Alignment, - comptime filled: bool, -) void { - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - - const x: f64 = switch (position.horizontal) { - .left => 0, - .right => float_width, - .center => float_width / 2, - }; - const y: f64 = switch (position.vertical) { - .top => 0, - .bottom => float_height, - .middle => float_height / 2, - }; - const r: f64 = 0.5 * @min(float_width, float_height); - - var ctx = canvas.getContext(); - defer ctx.deinit(); - ctx.setSource(.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }); - ctx.setLineWidth( - @floatFromInt(Thickness.light.height(self.metrics.box_thickness)), - ); - - if (filled) { - ctx.arc(x, y, r, 0, std.math.pi * 2) catch return; - ctx.closePath() catch return; - ctx.fill() catch return; - } else { - ctx.arc(x, y, r - ctx.line_width / 2, 0, std.math.pi * 2) catch return; - ctx.closePath() catch return; - ctx.stroke() catch return; - } -} - -fn draw_line( - self: Box, - canvas: *font.sprite.Canvas, - p0: font.sprite.Point(f64), - p1: font.sprite.Point(f64), - comptime thickness: Thickness, -) !void { - canvas.line( - .{ .p0 = p0, .p1 = p1 }, - @floatFromInt(thickness.height(self.metrics.box_thickness)), - .on, - ) catch {}; -} - -fn draw_shade(self: Box, canvas: *font.sprite.Canvas, v: u16) void { - canvas.rect((font.sprite.Box(u32){ - .p0 = .{ .x = 0, .y = 0 }, - .p1 = .{ - .x = self.metrics.cell_width, - .y = self.metrics.cell_height, - }, - }).rect(), @as(font.sprite.Color, @enumFromInt(v))); -} - -fn draw_light_shade(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_shade(canvas, 0x40); -} - -fn draw_medium_shade(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_shade(canvas, 0x80); -} - -fn draw_dark_shade(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_shade(canvas, 0xc0); -} - -fn draw_horizontal_one_eighth_block_n(self: Box, canvas: *font.sprite.Canvas, n: u32) void { - const h = @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(self.metrics.cell_height)) / 8))); - const y = @min( - self.metrics.cell_height -| h, - @as(u32, @intFromFloat(@round(@as(f64, @floatFromInt(n)) * @as(f64, @floatFromInt(self.metrics.cell_height)) / 8))), - ); - self.rect(canvas, 0, y, self.metrics.cell_width, y + h); -} - -fn draw_horizontal_one_eighth_1358_block(self: Box, canvas: *font.sprite.Canvas) void { - self.draw_horizontal_one_eighth_block_n(canvas, 0); - self.draw_horizontal_one_eighth_block_n(canvas, 2); - self.draw_horizontal_one_eighth_block_n(canvas, 4); - self.draw_horizontal_one_eighth_block_n(canvas, 7); -} - -fn draw_quadrant(self: Box, canvas: *font.sprite.Canvas, comptime quads: Quads) void { - const center_x = self.metrics.cell_width / 2 + self.metrics.cell_width % 2; - const center_y = self.metrics.cell_height / 2 + self.metrics.cell_height % 2; - - if (quads.tl) self.rect(canvas, 0, 0, center_x, center_y); - if (quads.tr) self.rect(canvas, center_x, 0, self.metrics.cell_width, center_y); - if (quads.bl) self.rect(canvas, 0, center_y, center_x, self.metrics.cell_height); - if (quads.br) self.rect(canvas, center_x, center_y, self.metrics.cell_width, self.metrics.cell_height); -} - -fn draw_braille(self: Box, canvas: *font.sprite.Canvas, cp: u32) void { - var w: u32 = @min(self.metrics.cell_width / 4, self.metrics.cell_height / 8); - var x_spacing: u32 = self.metrics.cell_width / 4; - var y_spacing: u32 = self.metrics.cell_height / 8; - var x_margin: u32 = x_spacing / 2; - var y_margin: u32 = y_spacing / 2; - - var x_px_left: u32 = self.metrics.cell_width - 2 * x_margin - x_spacing - 2 * w; - var y_px_left: u32 = self.metrics.cell_height - 2 * y_margin - 3 * y_spacing - 4 * w; - - // First, try hard to ensure the DOT width is non-zero - if (x_px_left >= 2 and y_px_left >= 4 and w == 0) { - w += 1; - x_px_left -= 2; - y_px_left -= 4; - } - - // Second, prefer a non-zero margin - if (x_px_left >= 2 and x_margin == 0) { - x_margin = 1; - x_px_left -= 2; - } - if (y_px_left >= 2 and y_margin == 0) { - y_margin = 1; - y_px_left -= 2; - } - - // Third, increase spacing - if (x_px_left >= 1) { - x_spacing += 1; - x_px_left -= 1; - } - if (y_px_left >= 3) { - y_spacing += 1; - y_px_left -= 3; - } - - // Fourth, margins (“spacing”, but on the sides) - if (x_px_left >= 2) { - x_margin += 1; - x_px_left -= 2; - } - if (y_px_left >= 2) { - y_margin += 1; - y_px_left -= 2; - } - - // Last - increase dot width - if (x_px_left >= 2 and y_px_left >= 4) { - w += 1; - x_px_left -= 2; - y_px_left -= 4; - } - - assert(x_px_left <= 1 or y_px_left <= 1); - assert(2 * x_margin + 2 * w + x_spacing <= self.metrics.cell_width); - assert(2 * y_margin + 4 * w + 3 * y_spacing <= self.metrics.cell_height); - - const x = [2]u32{ x_margin, x_margin + w + x_spacing }; - const y = y: { - var y: [4]u32 = undefined; - y[0] = y_margin; - y[1] = y[0] + w + y_spacing; - y[2] = y[1] + w + y_spacing; - y[3] = y[2] + w + y_spacing; - break :y y; - }; - - assert(cp >= 0x2800); - assert(cp <= 0x28ff); - const sym = cp - 0x2800; - - // Left side - if (sym & 1 > 0) - self.rect(canvas, x[0], y[0], x[0] + w, y[0] + w); - if (sym & 2 > 0) - self.rect(canvas, x[0], y[1], x[0] + w, y[1] + w); - if (sym & 4 > 0) - self.rect(canvas, x[0], y[2], x[0] + w, y[2] + w); - - // Right side - if (sym & 8 > 0) - self.rect(canvas, x[1], y[0], x[1] + w, y[0] + w); - if (sym & 16 > 0) - self.rect(canvas, x[1], y[1], x[1] + w, y[1] + w); - if (sym & 32 > 0) - self.rect(canvas, x[1], y[2], x[1] + w, y[2] + w); - - // 8-dot patterns - if (sym & 64 > 0) - self.rect(canvas, x[0], y[3], x[0] + w, y[3] + w); - if (sym & 128 > 0) - self.rect(canvas, x[1], y[3], x[1] + w, y[3] + w); -} - -fn draw_sextant(self: Box, canvas: *font.sprite.Canvas, cp: u32) void { - const Sextants = packed struct(u6) { - tl: bool, - tr: bool, - ml: bool, - mr: bool, - bl: bool, - br: bool, - }; - - assert(cp >= 0x1fb00 and cp <= 0x1fb3b); - const idx = cp - 0x1fb00; - const sex: Sextants = @bitCast(@as(u6, @intCast( - idx + (idx / 0x14) + 1, - ))); - - const x_halfs = self.xHalfs(); - const y_thirds = self.yThirds(); - - if (sex.tl) self.rect(canvas, 0, 0, x_halfs[0], y_thirds[0]); - if (sex.tr) self.rect(canvas, x_halfs[1], 0, self.metrics.cell_width, y_thirds[0]); - if (sex.ml) self.rect(canvas, 0, y_thirds[1], x_halfs[0], y_thirds[2]); - if (sex.mr) self.rect(canvas, x_halfs[1], y_thirds[1], self.metrics.cell_width, y_thirds[2]); - if (sex.bl) self.rect(canvas, 0, y_thirds[3], x_halfs[0], self.metrics.cell_height); - if (sex.br) self.rect(canvas, x_halfs[1], y_thirds[3], self.metrics.cell_width, self.metrics.cell_height); -} - -fn draw_octant(self: Box, canvas: *font.sprite.Canvas, cp: u32) void { - assert(cp >= octant_min and cp <= octant_max); - - // Octant representation. We use the funny numeric string keys - // so its easier to parse the actual name used in the Symbols for - // Legacy Computing spec. - const Octant = packed struct(u8) { - @"1": bool = false, - @"2": bool = false, - @"3": bool = false, - @"4": bool = false, - @"5": bool = false, - @"6": bool = false, - @"7": bool = false, - @"8": bool = false, - }; - - // Parse the octant data. This is all done at comptime so this is - // static data that is embedded in the binary. - const octants_len = octant_max - octant_min + 1; - const octants: [octants_len]Octant = comptime octants: { - @setEvalBranchQuota(10_000); - - var result: [octants_len]Octant = @splat(.{}); - var i: usize = 0; - - const data = @embedFile("octants.txt"); - var it = std.mem.splitScalar(u8, data, '\n'); - while (it.next()) |line| { - // Skip comments - if (line.len == 0 or line[0] == '#') continue; - - const current = &result[i]; - i += 1; - - // Octants are in the format "BLOCK OCTANT-1235". The numbers - // at the end are keys into our packed struct. Since we're - // at comptime we can metaprogram it all. - const idx = std.mem.indexOfScalar(u8, line, '-').?; - for (line[idx + 1 ..]) |c| @field(current, &.{c}) = true; - } - - assert(i == octants_len); - break :octants result; - }; - - const x_halfs = self.xHalfs(); - const y_quads = self.yQuads(); - const oct = octants[cp - octant_min]; - if (oct.@"1") self.rect(canvas, 0, 0, x_halfs[0], y_quads[0]); - if (oct.@"2") self.rect(canvas, x_halfs[1], 0, self.metrics.cell_width, y_quads[0]); - if (oct.@"3") self.rect(canvas, 0, y_quads[1], x_halfs[0], y_quads[2]); - if (oct.@"4") self.rect(canvas, x_halfs[1], y_quads[1], self.metrics.cell_width, y_quads[2]); - if (oct.@"5") self.rect(canvas, 0, y_quads[3], x_halfs[0], y_quads[4]); - if (oct.@"6") self.rect(canvas, x_halfs[1], y_quads[3], self.metrics.cell_width, y_quads[4]); - if (oct.@"7") self.rect(canvas, 0, y_quads[5], x_halfs[0], self.metrics.cell_height); - if (oct.@"8") self.rect(canvas, x_halfs[1], y_quads[5], self.metrics.cell_width, self.metrics.cell_height); -} - -/// xHalfs[0] should be used as the right edge of a left-aligned half. -/// xHalfs[1] should be used as the left edge of a right-aligned half. -fn xHalfs(self: Box) [2]u32 { - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const half_width: u32 = @intFromFloat(@round(0.5 * float_width)); - return .{ half_width, self.metrics.cell_width - half_width }; -} - -/// Use these values as such: -/// yThirds[0] bottom edge of the first third. -/// yThirds[1] top edge of the second third. -/// yThirds[2] bottom edge of the second third. -/// yThirds[3] top edge of the final third. -fn yThirds(self: Box) [4]u32 { - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const one_third_height: u32 = @intFromFloat(@round(one_third * float_height)); - const two_thirds_height: u32 = @intFromFloat(@round(two_thirds * float_height)); - return .{ - one_third_height, - self.metrics.cell_height - two_thirds_height, - two_thirds_height, - self.metrics.cell_height - one_third_height, - }; -} - -/// Use these values as such: -/// yQuads[0] bottom edge of first quarter. -/// yQuads[1] top edge of second quarter. -/// yQuads[2] bottom edge of second quarter. -/// yQuads[3] top edge of third quarter. -/// yQuads[4] bottom edge of third quarter -/// yQuads[5] top edge of fourth quarter. -fn yQuads(self: Box) [6]u32 { - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const quarter_height: u32 = @intFromFloat(@round(0.25 * float_height)); - const half_height: u32 = @intFromFloat(@round(0.50 * float_height)); - const three_quarters_height: u32 = @intFromFloat(@round(0.75 * float_height)); - return .{ - quarter_height, - self.metrics.cell_height - three_quarters_height, - half_height, - self.metrics.cell_height - half_height, - three_quarters_height, - self.metrics.cell_height - quarter_height, - }; -} - -fn draw_smooth_mosaic( - self: Box, - canvas: *font.sprite.Canvas, - mosaic: SmoothMosaic, -) !void { - const y_thirds = self.yThirds(); - const top: f64 = 0.0; - // We average the edge positions for the y_thirds boundaries here - // rather than having to deal with varying alignments depending on - // the surrounding pieces. The most this will be off by is half of - // a pixel, so hopefully it's not noticeable. - const upper: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[0])) + @as(f64, @floatFromInt(y_thirds[1]))); - const lower: f64 = 0.5 * (@as(f64, @floatFromInt(y_thirds[2])) + @as(f64, @floatFromInt(y_thirds[3]))); - const bottom: f64 = @floatFromInt(self.metrics.cell_height); - const left: f64 = 0.0; - const center: f64 = @round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2); - const right: f64 = @floatFromInt(self.metrics.cell_width); - - var path: z2d.StaticPath(12) = .{}; - path.init(); // nodes.len = 0 - - if (mosaic.tl) path.lineTo(left, top); // +1, nodes.len = 1 - if (mosaic.ul) path.lineTo(left, upper); // +1, nodes.len = 2 - if (mosaic.ll) path.lineTo(left, lower); // +1, nodes.len = 3 - if (mosaic.bl) path.lineTo(left, bottom); // +1, nodes.len = 4 - if (mosaic.bc) path.lineTo(center, bottom); // +1, nodes.len = 5 - if (mosaic.br) path.lineTo(right, bottom); // +1, nodes.len = 6 - if (mosaic.lr) path.lineTo(right, lower); // +1, nodes.len = 7 - if (mosaic.ur) path.lineTo(right, upper); // +1, nodes.len = 8 - if (mosaic.tr) path.lineTo(right, top); // +1, nodes.len = 9 - if (mosaic.tc) path.lineTo(center, top); // +1, nodes.len = 10 - path.close(); // +2, nodes.len = 12 - - try z2d.painter.fill( - canvas.alloc, - &canvas.sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }, - path.wrapped_path.nodes.items, - .{}, - ); -} - -fn draw_edge_triangle( - self: Box, - canvas: *font.sprite.Canvas, - comptime edge: Edge, -) !void { - const upper: f64 = 0.0; - const middle: f64 = @round(@as(f64, @floatFromInt(self.metrics.cell_height)) / 2); - const lower: f64 = @floatFromInt(self.metrics.cell_height); - const left: f64 = 0.0; - const center: f64 = @round(@as(f64, @floatFromInt(self.metrics.cell_width)) / 2); - const right: f64 = @floatFromInt(self.metrics.cell_width); - - const x0, const y0, const x1, const y1 = switch (edge) { - .top => .{ right, upper, left, upper }, - .left => .{ left, upper, left, lower }, - .bottom => .{ left, lower, right, lower }, - .right => .{ right, lower, right, upper }, - }; - - var path: z2d.StaticPath(5) = .{}; - path.init(); // nodes.len = 0 - - path.moveTo(center, middle); // +1, nodes.len = 1 - path.lineTo(x0, y0); // +1, nodes.len = 2 - path.lineTo(x1, y1); // +1, nodes.len = 3 - path.close(); // +2, nodes.len = 5 - - try z2d.painter.fill( - canvas.alloc, - &canvas.sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }, - path.wrapped_path.nodes.items, - .{}, - ); -} - -fn draw_arc( - self: Box, - canvas: *font.sprite.Canvas, - comptime corner: Corner, - comptime thickness: Thickness, -) !void { - const thick_px = thickness.height(self.metrics.box_thickness); - const float_width: f64 = @floatFromInt(self.metrics.cell_width); - const float_height: f64 = @floatFromInt(self.metrics.cell_height); - const float_thick: f64 = @floatFromInt(thick_px); - const center_x: f64 = @as(f64, @floatFromInt((self.metrics.cell_width -| thick_px) / 2)) + float_thick / 2; - const center_y: f64 = @as(f64, @floatFromInt((self.metrics.cell_height -| thick_px) / 2)) + float_thick / 2; - - const r = @min(float_width, float_height) / 2; - - // Fraction away from the center to place the middle control points, - const s: f64 = 0.25; - - var ctx = canvas.getContext(); - defer ctx.deinit(); - ctx.setSource(.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }); - ctx.setLineWidth(float_thick); - ctx.setLineCapMode(.round); - - switch (corner) { - .tl => { - try ctx.moveTo(center_x, 0); - try ctx.lineTo(center_x, center_y - r); - try ctx.curveTo( - center_x, - center_y - s * r, - center_x - s * r, - center_y, - center_x - r, - center_y, - ); - try ctx.lineTo(0, center_y); - }, - .tr => { - try ctx.moveTo(center_x, 0); - try ctx.lineTo(center_x, center_y - r); - try ctx.curveTo( - center_x, - center_y - s * r, - center_x + s * r, - center_y, - center_x + r, - center_y, - ); - try ctx.lineTo(float_width, center_y); - }, - .bl => { - try ctx.moveTo(center_x, float_height); - try ctx.lineTo(center_x, center_y + r); - try ctx.curveTo( - center_x, - center_y + s * r, - center_x - s * r, - center_y, - center_x - r, - center_y, - ); - try ctx.lineTo(0, center_y); - }, - .br => { - try ctx.moveTo(center_x, float_height); - try ctx.lineTo(center_x, center_y + r); - try ctx.curveTo( - center_x, - center_y + s * r, - center_x + s * r, - center_y, - center_x + r, - center_y, - ); - try ctx.lineTo(float_width, center_y); - }, - } - try ctx.stroke(); -} - -fn draw_dash_horizontal( - self: Box, - canvas: *font.sprite.Canvas, - count: u8, - thick_px: u32, - desired_gap: u32, -) void { - assert(count >= 2 and count <= 4); - - // +------------+ - // | | - // | | - // | | - // | | - // | -- -- -- | - // | | - // | | - // | | - // | | - // +------------+ - // Our dashed line should be made such that when tiled horizontally - // it creates one consistent line with no uneven gap or segment sizes. - // In order to make sure this is the case, we should have half-sized - // gaps on the left and right so that it is centered properly. - - // For N dashes, there are N - 1 gaps between them, but we also have - // half-sized gaps on either side, adding up to N total gaps. - const gap_count = count; - - // We need at least 1 pixel for each gap and each dash, if we don't - // have that then we can't draw our dashed line correctly so we just - // draw a solid line and return. - if (self.metrics.cell_width < count + gap_count) { - self.hline_middle(canvas, .light); - return; - } - - // We never want the gaps to take up more than 50% of the space, - // because if they do the dashes are too small and look wrong. - const gap_width = @min(desired_gap, self.metrics.cell_width / (2 * count)); - const total_gap_width = gap_count * gap_width; - const total_dash_width = self.metrics.cell_width - total_gap_width; - const dash_width = total_dash_width / count; - const remaining = total_dash_width % count; - - assert(dash_width * count + gap_width * gap_count + remaining == self.metrics.cell_width); - - // Our dashes should be centered vertically. - const y: u32 = (self.metrics.cell_height -| thick_px) / 2; - - // We start at half a gap from the left edge, in order to center - // our dashes properly. - var x: u32 = gap_width / 2; - - // We'll distribute the extra space in to dash widths, 1px at a - // time. We prefer this to making gaps larger since that is much - // more visually obvious. - var extra: u32 = remaining; - - for (0..count) |_| { - var x1 = x + dash_width; - // We distribute left-over size in to dash widths, - // since it's less obvious there than in the gaps. - if (extra > 0) { - extra -= 1; - x1 += 1; - } - self.hline(canvas, x, x1, y, thick_px); - // Advance by the width of the dash we drew and the width - // of a gap to get the the start of the next dash. - x = x1 + gap_width; - } -} - -fn draw_dash_vertical( - self: Box, - canvas: *font.sprite.Canvas, - comptime count: u8, - thick_px: u32, - desired_gap: u32, -) void { - assert(count >= 2 and count <= 4); - - // +-----------+ - // | | | - // | | | - // | | - // | | | - // | | | - // | | - // | | | - // | | | - // | | - // +-----------+ - // Our dashed line should be made such that when tiled vertically it - // it creates one consistent line with no uneven gap or segment sizes. - // In order to make sure this is the case, we should have an extra gap - // gap at the bottom. - // - // A single full-sized extra gap is preferred to two half-sized ones for - // vertical to allow better joining to solid characters without creating - // visible half-sized gaps. Unlike horizontal, centering is a lot less - // important, visually. - - // Because of the extra gap at the bottom, there are as many gaps as - // there are dashes. - const gap_count = count; - - // We need at least 1 pixel for each gap and each dash, if we don't - // have that then we can't draw our dashed line correctly so we just - // draw a solid line and return. - if (self.metrics.cell_height < count + gap_count) { - self.vline_middle(canvas, .light); - return; - } - - // We never want the gaps to take up more than 50% of the space, - // because if they do the dashes are too small and look wrong. - const gap_height = @min(desired_gap, self.metrics.cell_height / (2 * count)); - const total_gap_height = gap_count * gap_height; - const total_dash_height = self.metrics.cell_height - total_gap_height; - const dash_height = total_dash_height / count; - const remaining = total_dash_height % count; - - assert(dash_height * count + gap_height * gap_count + remaining == self.metrics.cell_height); - - // Our dashes should be centered horizontally. - const x: u32 = (self.metrics.cell_width -| thick_px) / 2; - - // We start at the top of the cell. - var y: u32 = 0; - - // We'll distribute the extra space in to dash heights, 1px at a - // time. We prefer this to making gaps larger since that is much - // more visually obvious. - var extra: u32 = remaining; - - inline for (0..count) |_| { - var y1 = y + dash_height; - // We distribute left-over size in to dash widths, - // since it's less obvious there than in the gaps. - if (extra > 0) { - extra -= 1; - y1 += 1; - } - self.vline(canvas, y, y1, x, thick_px); - // Advance by the height of the dash we drew and the height - // of a gap to get the the start of the next dash. - y = y1 + gap_height; - } -} - -fn vline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void { - const thick_px = thickness.height(self.metrics.box_thickness); - self.vline(canvas, 0, self.metrics.cell_height, (self.metrics.cell_width -| thick_px) / 2, thick_px); -} - -fn hline_middle(self: Box, canvas: *font.sprite.Canvas, thickness: Thickness) void { - const thick_px = thickness.height(self.metrics.box_thickness); - self.hline(canvas, 0, self.metrics.cell_width, (self.metrics.cell_height -| thick_px) / 2, thick_px); -} - -fn vline( - self: Box, - canvas: *font.sprite.Canvas, - y1: u32, - y2: u32, - x: u32, - thickness_px: u32, -) void { - canvas.rect((font.sprite.Box(u32){ .p0 = .{ - .x = @min(@max(x, 0), self.metrics.cell_width), - .y = @min(@max(y1, 0), self.metrics.cell_height), - }, .p1 = .{ - .x = @min(@max(x + thickness_px, 0), self.metrics.cell_width), - .y = @min(@max(y2, 0), self.metrics.cell_height), - } }).rect(), .on); -} - -fn hline( - self: Box, - canvas: *font.sprite.Canvas, - x1: u32, - x2: u32, - y: u32, - thickness_px: u32, -) void { - canvas.rect((font.sprite.Box(u32){ .p0 = .{ - .x = @min(@max(x1, 0), self.metrics.cell_width), - .y = @min(@max(y, 0), self.metrics.cell_height), - }, .p1 = .{ - .x = @min(@max(x2, 0), self.metrics.cell_width), - .y = @min(@max(y + thickness_px, 0), self.metrics.cell_height), - } }).rect(), .on); -} - -fn rect( - self: Box, - canvas: *font.sprite.Canvas, - x1: u32, - y1: u32, - x2: u32, - y2: u32, -) void { - canvas.rect((font.sprite.Box(u32){ .p0 = .{ - .x = @min(@max(x1, 0), self.metrics.cell_width), - .y = @min(@max(y1, 0), self.metrics.cell_height), - }, .p1 = .{ - .x = @min(@max(x2, 0), self.metrics.cell_width), - .y = @min(@max(y2, 0), self.metrics.cell_height), - } }).rect(), .on); -} - -// Separated Block Quadrants from Symbols for Legacy Computing Supplement -// 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯 -fn draw_separated_block_quadrant(self: Box, canvas: *font.sprite.Canvas, comptime fmt: []const u8) !void { - comptime { - if (fmt.len > 4) @compileError("cannot have more than four quadrants"); - var seen = [_]bool{false} ** (std.math.maxInt(u8) + 1); - for (fmt) |c| { - if (seen[c]) @compileError("repeated quadrants not allowed"); - seen[c] = true; - switch (c) { - '1'...'4' => {}, - else => @compileError("invalid quadrant"), - } - } - } - - var ctx = canvas.getContext(); - defer ctx.deinit(); - ctx.setSource(.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, - } }); - const gap: f64 = @max(1.0, @as(f64, @floatFromInt(self.metrics.cell_width)) * 0.10) / 2.0; - const left: f64 = gap; - const right = @as(f64, @floatFromInt(self.metrics.cell_width)) - gap; - const top: f64 = gap; - const bottom = @as(f64, @floatFromInt(self.metrics.cell_height)) - gap; - const center_x = @as(f64, @floatFromInt(self.metrics.cell_width)) / 2.0; - const center_left = center_x - gap; - const center_right = center_x + gap; - const center_y = @as(f64, @floatFromInt(self.metrics.cell_height)) / 2.0; - const center_top = center_y - gap; - const center_bottom = center_y + gap; - - inline for (fmt) |c| { - const x1, const y1, const x2, const y2 = switch (c) { - '1' => .{ - left, top, - center_left, center_top, - }, - '2' => .{ - center_right, top, - right, center_top, - }, - '3' => .{ - left, center_bottom, - center_left, bottom, - }, - '4' => .{ - center_right, center_bottom, - right, bottom, - }, - else => unreachable, - }; - try ctx.moveTo(x1, y1); - try ctx.lineTo(x2, y1); - try ctx.lineTo(x2, y2); - try ctx.lineTo(x1, y2); - try ctx.closePath(); - } - - try ctx.fill(); -} - -test "all" { - const testing = std.testing; - const alloc = testing.allocator; - - var cp: u32 = 0x2500; - const end = 0x259f; - while (cp <= end) : (cp += 1) { - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - const face: Box = .{ - .metrics = font.Metrics.calc(.{ - .cell_width = 18.0, - .ascent = 30.0, - .descent = -6.0, - .line_gap = 0.0, - }), - }; - const glyph = try face.renderGlyph( - alloc, - &atlas_grayscale, - cp, - ); - try testing.expectEqual(@as(u32, face.metrics.cell_width), glyph.width); - try testing.expectEqual(@as(u32, face.metrics.cell_height), glyph.height); - } -} - -fn testRenderAll(self: Box, alloc: Allocator, atlas: *font.Atlas) !void { - // Box Drawing and Block Elements. - var cp: u32 = 0x2500; - while (cp <= 0x259f) : (cp += 1) { - _ = try self.renderGlyph( - alloc, - atlas, - cp, - ); - } - - // Braille - cp = 0x2800; - while (cp <= 0x28ff) : (cp += 1) { - _ = try self.renderGlyph( - alloc, - atlas, - cp, - ); - } - - // Symbols for Legacy Computing. - cp = 0x1fb00; - while (cp <= 0x1fbef) : (cp += 1) { - switch (cp) { - // (Block Mosaics / "Sextants") - // 🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 🬟 🬠 - // 🬡 🬢 🬣 🬤 🬥 🬦 🬧 🬨 🬩 🬪 🬫 🬬 🬭 🬮 🬯 🬰 🬱 🬲 🬳 🬴 🬵 🬶 🬷 🬸 🬹 🬺 🬻 - // (Smooth Mosaics) - // 🬼 🬽 🬾 🬿 🭀 🭁 🭂 🭃 🭄 🭅 🭆 - // 🭇 🭈 🭉 🭊 🭋 🭌 🭍 🭎 🭏 🭐 🭑 - // 🭒 🭓 🭔 🭕 🭖 🭗 🭘 🭙 🭚 🭛 🭜 - // 🭝 🭞 🭟 🭠 🭡 🭢 🭣 🭤 🭥 🭦 🭧 - // 🭨 🭩 🭪 🭫 🭬 🭭 🭮 🭯 - // (Block Elements) - // 🭰 🭱 🭲 🭳 🭴 🭵 🭶 🭷 🭸 🭹 🭺 🭻 - // 🭼 🭽 🭾 🭿 🮀 🮁 - // 🮂 🮃 🮄 🮅 🮆 - // 🮇 🮈 🮉 🮊 🮋 - // (Rectangular Shade Characters) - // 🮌 🮍 🮎 🮏 🮐 🮑 🮒 - 0x1FB00...0x1FB92, - // (Rectangular Shade Characters) - // 🮔 - // (Fill Characters) - // 🮕 🮖 🮗 - // (Diagonal Fill Characters) - // 🮘 🮙 - // (Smooth Mosaics) - // 🮚 🮛 - // (Triangular Shade Characters) - // 🮜 🮝 🮞 🮟 - // (Character Cell Diagonals) - // 🮠 🮡 🮢 🮣 🮤 🮥 🮦 🮧 🮨 🮩 🮪 🮫 🮬 🮭 🮮 - // (Light Solid Line With Stroke) - // 🮯 - 0x1FB94...0x1FBAF, - // (Negative Terminal Characters) - // 🮽 🮾 🮿 - 0x1FBBD...0x1FBBF, - // (Block Elements) - // 🯎 🯏 - // (Character Cell Diagonals) - // 🯐 🯑 🯒 🯓 🯔 🯕 🯖 🯗 🯘 🯙 🯚 🯛 🯜 🯝 🯞 🯟 - // (Geometric Shapes) - // 🯠 🯡 🯢 🯣 🯤 🯥 🯦 🯧 🯨 🯩 🯪 🯫 🯬 🯭 🯮 🯯 - 0x1FBCE...0x1FBEF, - => _ = try self.renderGlyph( - alloc, - atlas, - cp, - ), - else => {}, - } - } - - // Branch drawing character set, used for drawing git-like - // graphs in the terminal. Originally implemented in Kitty. - // Ref: - // - https://github.com/kovidgoyal/kitty/pull/7681 - // - https://github.com/kovidgoyal/kitty/pull/7805 - // NOTE: Kitty is GPL licensed, and its code was not referenced - // for these characters, only the loose specification of - // the character set in the pull request descriptions. - // - // TODO(qwerasd): This should be in another file, but really the - // general organization of the sprite font code - // needs to be reworked eventually. - // - //           - //                     - //                     - //             - cp = 0xf5d0; - while (cp <= 0xf60d) : (cp += 1) { - _ = try self.renderGlyph( - alloc, - atlas, - cp, - ); - } - - // Symbols for Legacy Computing Supplement: Quadrants - // 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯 - cp = 0x1cc21; - while (cp <= 0x1cc2f) : (cp += 1) { - switch (cp) { - 0x1cc21...0x1cc2f => _ = try self.renderGlyph( - alloc, - atlas, - cp, - ), - else => {}, - } - } - - // Symbols for Legacy Computing Supplement: Octants - cp = 0x1CD00; - while (cp <= 0x1CDE5) : (cp += 1) { - switch (cp) { - 0x1CD00...0x1CDE5 => _ = try self.renderGlyph( - alloc, - atlas, - cp, - ), - else => {}, - } - } - - // Geometric Shapes: filled and outlined corners - for ([_]u21{ '◢', '◣', '◤', '◥', '◸', '◹', '◺', '◿' }) |char| { - _ = try self.renderGlyph( - alloc, - atlas, - char, - ); - } -} - -test "render all sprites" { - // Renders all sprites to an atlas and compares - // it to a ground truth for regression testing. - - const testing = std.testing; - const alloc = testing.allocator; - - var atlas_grayscale = try font.Atlas.init(alloc, 1024, .grayscale); - defer atlas_grayscale.deinit(alloc); - - // Even cell size and thickness (18 x 36) - try (Box{ - .metrics = font.Metrics.calc(.{ - .cell_width = 18.0, - .ascent = 30.0, - .descent = -6.0, - .line_gap = 0.0, - .underline_thickness = 2.0, - .strikethrough_thickness = 2.0, - }), - }).testRenderAll(alloc, &atlas_grayscale); - - // Odd cell size and thickness (9 x 15) - try (Box{ - .metrics = font.Metrics.calc(.{ - .cell_width = 9.0, - .ascent = 12.0, - .descent = -3.0, - .line_gap = 0.0, - .underline_thickness = 1.0, - .strikethrough_thickness = 1.0, - }), - }).testRenderAll(alloc, &atlas_grayscale); - - const ground_truth = @embedFile("./testdata/Box.ppm"); - - var stream = std.io.changeDetectionStream(ground_truth, std.io.null_writer); - try atlas_grayscale.dump(stream.writer()); - - if (stream.changeDetected()) { - log.err( - \\ - \\!! [Box.zig] Change detected from ground truth! - \\!! Dumping ./Box_test.ppm and ./Box_test_diff.ppm - \\!! Please check changes and update Box.ppm in testdata if intended. - , - .{}, - ); - - const ppm = try std.fs.cwd().createFile("Box_test.ppm", .{}); - defer ppm.close(); - try atlas_grayscale.dump(ppm.writer()); - - const diff = try std.fs.cwd().createFile("Box_test_diff.ppm", .{}); - defer diff.close(); - var writer = diff.writer(); - try writer.print( - \\P6 - \\{d} {d} - \\255 - \\ - , .{ atlas_grayscale.size, atlas_grayscale.size }); - for (ground_truth[try diff.getPos()..], atlas_grayscale.data) |a, b| { - if (a == b) { - try writer.writeByteNTimes(a / 3, 3); - } else { - try writer.writeByte(a); - try writer.writeByte(b); - try writer.writeByte(0); - } - } - } -} diff --git a/src/font/sprite/Face.zig b/src/font/sprite/Face.zig index af0c0af6a..dfff8fa75 100644 --- a/src/font/sprite/Face.zig +++ b/src/font/sprite/Face.zig @@ -16,25 +16,158 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const wuffs = @import("wuffs"); +const z2d = @import("z2d"); const font = @import("../main.zig"); const Sprite = font.sprite.Sprite; -const Box = @import("Box.zig"); -const Powerline = @import("Powerline.zig"); -const underline = @import("underline.zig"); -const cursor = @import("cursor.zig"); + +const special = @import("draw/special.zig"); const log = std.log.scoped(.font_sprite); /// Grid metrics for rendering sprites. metrics: font.Metrics, +pub const DrawFnError = + Allocator.Error || + z2d.painter.FillError || + z2d.painter.StrokeError || + error{ + /// Something went wrong while doing math. + MathError, + }; + +/// A function that draws a glyph on the provided canvas. +pub const DrawFn = fn ( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) DrawFnError!void; + +const Range = struct { + min: u32, + max: u32, + draw: DrawFn, +}; + +/// Automatically collect ranges for functions with names +/// in the format `draw` or `draw_`. +const ranges: []const Range = ranges: { + @setEvalBranchQuota(1_000_000); + + // Structs containing drawing functions for codepoint ranges. + const structs = [_]type{ + @import("draw/block.zig"), + @import("draw/box.zig"), + @import("draw/braille.zig"), + @import("draw/branch.zig"), + @import("draw/geometric_shapes.zig"), + @import("draw/powerline.zig"), + @import("draw/symbols_for_legacy_computing.zig"), + @import("draw/symbols_for_legacy_computing_supplement.zig"), + }; + + // Count how many draw fns we have + var range_count = 0; + for (structs) |s| { + for (@typeInfo(s).@"struct".decls) |decl| { + if (!@hasDecl(s, decl.name)) continue; + if (!std.mem.startsWith(u8, decl.name, "draw")) continue; + range_count += 1; + } + } + + // Make an array and collect ranges for each function. + var r: [range_count]Range = undefined; + var names: [range_count][:0]const u8 = undefined; + var i = 0; + for (structs) |s| { + for (@typeInfo(s).@"struct".decls) |decl| { + if (!@hasDecl(s, decl.name)) continue; + if (!std.mem.startsWith(u8, decl.name, "draw")) continue; + + const sep = std.mem.indexOfScalar(u8, decl.name, '_') orelse decl.name.len; + + const min = std.fmt.parseInt(u21, decl.name[4..sep], 16) catch unreachable; + + const max = if (sep == decl.name.len) + min + else + std.fmt.parseInt(u21, decl.name[sep + 1 ..], 16) catch unreachable; + + r[i] = .{ + .min = min, + .max = max, + .draw = @field(s, decl.name), + }; + names[i] = decl.name; + i += 1; + } + } + + // Sort ranges in ascending order + std.mem.sortUnstableContext(0, r.len, struct { + r: []Range, + names: [][:0]const u8, + pub fn lessThan(self: @This(), a: usize, b: usize) bool { + return self.r[a].min < self.r[b].min; + } + pub fn swap(self: @This(), a: usize, b: usize) void { + std.mem.swap(Range, &self.r[a], &self.r[b]); + std.mem.swap([:0]const u8, &self.names[a], &self.names[b]); + } + }{ + .r = &r, + .names = &names, + }); + + // Ensure there's no overlapping ranges + i = 0; + for (r, 0..) |n, k| { + if (n.min <= i) { + @compileError( + std.fmt.comptimePrint( + "Codepoint range for {s}(...) overlaps range for {s}(...), {X} <= {X} <= {X}", + .{ names[k], names[k - 1], r[k - 1].min, n.min, r[k - 1].max }, + ), + ); + } + i = n.max; + } + + // We need to copy in to a const rather than a var in order to take + // the reference at comptime so that we can break with a slice here. + const fixed = r; + + break :ranges &fixed; +}; + +fn getDrawFn(cp: u32) ?*const DrawFn { + // For special sprites (cursors, underlines, etc.) all sprites are drawn + // by functions from `Special` that share the name of the enum field. + if (cp >= Sprite.start) switch (@as(Sprite, @enumFromInt(cp))) { + inline else => |sprite| { + return @field(special, @tagName(sprite)); + }, + }; + + // Pray that the compiler is smart enough to + // turn this in to a jump table or something... + inline for (ranges) |range| { + if (cp >= range.min and cp <= range.max) return range.draw; + } + return null; +} + /// Returns true if the codepoint exists in our sprite font. pub fn hasCodepoint(self: Face, cp: u32, p: ?font.Presentation) bool { - // We ignore presentation. No matter what presentation is requested - // we always provide glyphs for our codepoints. + // We ignore presentation. No matter what presentation is + // requested we always provide glyphs for our codepoints. _ = p; _ = self; - return Kind.init(cp) != null; + return getDrawFn(cp) != null; } /// Render the glyph. @@ -52,6 +185,18 @@ pub fn renderGlyph( } } + // It should be impossible for this to be null and we assert that + // in runtime safety modes but in case it is its not worth memory + // corruption so we return a valid, blank glyph. + const draw = getDrawFn(cp) orelse return .{ + .width = 0, + .height = 0, + .offset_x = 0, + .offset_y = 0, + .atlas_x = 0, + .atlas_y = 0, + }; + const metrics = self.metrics; // We adjust our sprite width based on the cell width. @@ -60,230 +205,340 @@ pub fn renderGlyph( else => |width| metrics.cell_width * width, }; - // It should be impossible for this to be null and we assert that - // in runtime safety modes but in case it is its not worth memory - // corruption so we return a valid, blank glyph. - const kind = Kind.init(cp) orelse return .{ - .width = 0, - .height = 0, - .offset_x = 0, - .offset_y = 0, - .atlas_x = 0, - .atlas_y = 0, - .advance_x = 0, - }; + const height = metrics.cell_height; - // Safe to ".?" because of the above assertion. - return switch (kind) { - .box => (Box{ .metrics = metrics }).renderGlyph(alloc, atlas, cp), + const padding_x = width / 4; + const padding_y = height / 4; - .underline => try underline.renderGlyph( - alloc, - atlas, - @enumFromInt(cp), - width, - metrics.cell_height, - metrics.underline_position, - metrics.underline_thickness, - ), + // Make a canvas of the desired size + var canvas = try font.sprite.Canvas.init(alloc, width, height, padding_x, padding_y); + defer canvas.deinit(); - .strikethrough => try underline.renderGlyph( - alloc, - atlas, - @enumFromInt(cp), - width, - metrics.cell_height, - metrics.strikethrough_position, - metrics.strikethrough_thickness, - ), + try draw(cp, &canvas, width, height, metrics); - .overline => overline: { - var g = try underline.renderGlyph( - alloc, - atlas, - @enumFromInt(cp), - width, - metrics.cell_height, - 0, - metrics.overline_thickness, - ); + // Write the drawing to the atlas + const region = try canvas.writeAtlas(alloc, atlas); - // We have to manually subtract the overline position - // on the rendered glyph since it can be negative. - g.offset_y -= metrics.overline_position; - - break :overline g; - }, - - .powerline => powerline: { - const f: Powerline = .{ - .width = metrics.cell_width, - .height = metrics.cell_height, - .thickness = metrics.box_thickness, - }; - - break :powerline try f.renderGlyph(alloc, atlas, cp); - }, - - .cursor => cursor: { - var g = try cursor.renderGlyph( - alloc, - atlas, - @enumFromInt(cp), - width, - metrics.cursor_height, - metrics.cursor_thickness, - ); - - // Cursors are drawn at their specified height - // and are centered vertically within the cell. - const cursor_height: i32 = @intCast(metrics.cursor_height); - const cell_height: i32 = @intCast(metrics.cell_height); - g.offset_y += @divTrunc(cell_height - cursor_height, 2); - - break :cursor g; - }, + return .{ + .width = region.width, + .height = region.height, + .offset_x = @as(i32, @intCast(canvas.clip_left)) - @as(i32, @intCast(padding_x)), + .offset_y = @as(i32, @intCast(region.height +| canvas.clip_bottom)) - @as(i32, @intCast(padding_y)), + .atlas_x = region.x, + .atlas_y = region.y, }; } -/// Kind of sprites we have. Drawing is implemented separately for each kind. -const Kind = enum { - box, - underline, - overline, - strikethrough, - powerline, - cursor, +/// Used in `testDrawRanges`, checks for diff between the provided atlas +/// and the reference file for the range, returns true if there is a diff. +fn testDiffAtlas( + alloc: Allocator, + atlas: *z2d.Surface, + path: []const u8, + i: u32, + width: u32, + height: u32, + thickness: u32, +) !bool { + // Get the file contents, we compare the PNG data first in + // order to ensure that no one smuggles arbitrary binary + // data in to the reference PNGs. + const test_file = try std.fs.openFileAbsolute(path, .{ .mode = .read_only }); + defer test_file.close(); + const test_bytes = try test_file.readToEndAlloc( + alloc, + std.math.maxInt(usize), + ); + defer alloc.free(test_bytes); - pub fn init(cp: u32) ?Kind { - return switch (cp) { - Sprite.start...Sprite.end => switch (@as(Sprite, @enumFromInt(cp))) { - .underline, - .underline_double, - .underline_dotted, - .underline_dashed, - .underline_curly, - => .underline, + const cwd_absolute = try std.fs.cwd().realpathAlloc(alloc, "."); + defer alloc.free(cwd_absolute); - .overline, - => .overline, + // Get the reference file contents to compare. + const ref_path = try std.fmt.allocPrint( + alloc, + "./src/font/sprite/testdata/U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(ref_path); + const ref_file = + std.fs.cwd().openFile(ref_path, .{ .mode = .read_only }) catch |err| { + log.err("Can't open reference file {s}: {}\n", .{ + ref_path, + err, + }); - .strikethrough, - => .strikethrough, + // Copy the test PNG in to the CWD so it isn't + // cleaned up with the rest of the tmp dir files. + const test_path = try std.fmt.allocPrint( + alloc, + "{s}/sprite_face_test-U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ cwd_absolute, i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(test_path); + try std.fs.copyFileAbsolute(path, test_path, .{}); - .cursor_rect, - .cursor_hollow_rect, - .cursor_bar, - => .cursor, - }, - - // == Box fonts == - - // "Box Drawing" block - // ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏ ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟ ┠ - // ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯ ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿ ╀ ╁ - // ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏ ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ ╠ ╡ ╢ - // ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯ ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿ - 0x2500...0x257F, - - // "Block Elements" block - // ▀ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▉ ▊ ▋ ▌ ▍ ▎ ▏ ▐ ░ ▒ ▓ ▔ ▕ ▖ ▗ ▘ ▙ ▚ ▛ ▜ ▝ ▞ ▟ - 0x2580...0x259F, - - // "Geometric Shapes" block - 0x25e2...0x25e5, // ◢◣◤◥ - 0x25f8...0x25fa, // ◸◹◺ - 0x25ff, // ◿ - - // "Braille" block - 0x2800...0x28FF, - - // "Symbols for Legacy Computing" block - // (Block Mosaics / "Sextants") - // 🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 🬟 🬠 - // 🬡 🬢 🬣 🬤 🬥 🬦 🬧 🬨 🬩 🬪 🬫 🬬 🬭 🬮 🬯 🬰 🬱 🬲 🬳 🬴 🬵 🬶 🬷 🬸 🬹 🬺 🬻 - // (Smooth Mosaics) - // 🬼 🬽 🬾 🬿 🭀 🭁 🭂 🭃 🭄 🭅 🭆 - // 🭇 🭈 🭉 🭊 🭋 🭌 🭍 🭎 🭏 🭐 🭑 - // 🭒 🭓 🭔 🭕 🭖 🭗 🭘 🭙 🭚 🭛 🭜 - // 🭝 🭞 🭟 🭠 🭡 🭢 🭣 🭤 🭥 🭦 🭧 - // 🭨 🭩 🭪 🭫 🭬 🭭 🭮 🭯 - // (Block Elements) - // 🭰 🭱 🭲 🭳 🭴 🭵 🭶 🭷 🭸 🭹 🭺 🭻 - // 🭼 🭽 🭾 🭿 🮀 🮁 - // 🮂 🮃 🮄 🮅 🮆 - // 🮇 🮈 🮉 🮊 🮋 - // (Rectangular Shade Characters) - // 🮌 🮍 🮎 🮏 🮐 🮑 🮒 - 0x1FB00...0x1FB92, - // (Rectangular Shade Characters) - // 🮔 - // (Fill Characters) - // 🮕 🮖 🮗 - // (Diagonal Fill Characters) - // 🮘 🮙 - // (Smooth Mosaics) - // 🮚 🮛 - // (Triangular Shade Characters) - // 🮜 🮝 🮞 🮟 - // (Character Cell Diagonals) - // 🮠 🮡 🮢 🮣 🮤 🮥 🮦 🮧 🮨 🮩 🮪 🮫 🮬 🮭 🮮 - // (Light Solid Line With Stroke) - // 🮯 - 0x1FB94...0x1FBAF, - // (Negative Terminal Characters) - // 🮽 🮾 🮿 - 0x1FBBD...0x1FBBF, - // (Block Elements) - // 🯎 🯏 - // (Character Cell Diagonals) - // 🯐 🯑 🯒 🯓 🯔 🯕 🯖 🯗 🯘 🯙 🯚 🯛 🯜 🯝 🯞 🯟 - // (Geometric Shapes) - // 🯠 🯡 🯢 🯣 🯤 🯥 🯦 🯧 🯨 🯩 🯪 🯫 🯬 🯭 🯮 🯯 - 0x1FBCE...0x1FBEF, - // (Octants) - 0x1CD00...0x1CDE5, - => .box, - - // Branch drawing character set, used for drawing git-like - // graphs in the terminal. Originally implemented in Kitty. - // Ref: - // - https://github.com/kovidgoyal/kitty/pull/7681 - // - https://github.com/kovidgoyal/kitty/pull/7805 - // NOTE: Kitty is GPL licensed, and its code was not referenced - // for these characters, only the loose specification of - // the character set in the pull request descriptions. - // - //           - //                     - //                     - //             - 0xF5D0...0xF60D => .box, - - // Separated Block Quadrants from Symbols for Legacy Computing Supplement - // 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯 - 0x1CC21...0x1CC2F => .box, - - // Powerline fonts - 0xE0B0, - 0xE0B1, - 0xE0B3, - 0xE0B4, - 0xE0B6, - 0xE0B2, - 0xE0B8, - 0xE0BA, - 0xE0BC, - 0xE0BE, - 0xE0D2, - 0xE0D4, - => .powerline, - - else => null, + return true; }; + defer ref_file.close(); + const ref_bytes = try ref_file.readToEndAlloc( + alloc, + std.math.maxInt(usize), + ); + defer alloc.free(ref_bytes); + + // Do our PNG bytes comparison, if it's the same then we can + // move on, otherwise we'll decode the reference file and do + // a pixel-for-pixel diff. + if (std.mem.eql(u8, test_bytes, ref_bytes)) return false; + + // Copy the test PNG in to the CWD so it isn't + // cleaned up with the rest of the tmp dir files. + const test_path = try std.fmt.allocPrint( + alloc, + "{s}/sprite_face_test-U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ cwd_absolute, i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(test_path); + try std.fs.copyFileAbsolute(path, test_path, .{}); + + // Use wuffs to decode the reference PNG to raw pixels. + // These will be RGBA, so when diffing we can just compare + // every fourth byte. + const ref_rgba = try wuffs.png.decode(alloc, ref_bytes); + defer alloc.free(ref_rgba.data); + + assert(ref_rgba.width == atlas.getWidth()); + assert(ref_rgba.height == atlas.getHeight()); + + // We'll make a visual representation of the diff using + // red for removed pixels and green for added. We make + // a z2d surface for that here. + var diff = try z2d.Surface.init( + .image_surface_rgb, + alloc, + atlas.getWidth(), + atlas.getHeight(), + ); + defer diff.deinit(alloc); + const diff_pix = diff.image_surface_rgb.buf; + + const test_gray = std.mem.sliceAsBytes(atlas.image_surface_alpha8.buf); + + var differs: bool = false; + for (0..test_gray.len) |j| { + const t = test_gray[j]; + const r = ref_rgba.data[j * 4]; + if (t == r) { + // If the pixels match, write it as a faded gray. + diff_pix[j].r = t / 3; + diff_pix[j].g = t / 3; + diff_pix[j].b = t / 3; + } else { + differs = true; + // Otherwise put the reference value in the red + // channel and the new value in the green channel. + diff_pix[j].r = r; + diff_pix[j].g = t; + } } -}; + + // If the PNG data differs but not the raw pixels, that's + // a big red flag, since it could mean someone is trying to + // smuggle binary data in to the test files. + if (!differs) { + log.err( + "!!! Test PNG data does not match reference, but pixels do match! " ++ + "Either z2d's PNG exporter changed or someone is " ++ + "trying to smuggle binary data in the test files!\n" ++ + "test={s}, reference={s}", + .{ test_path, ref_path }, + ); + return true; + } + + // Drop the diff image as a PNG in the cwd. + const diff_path = try std.fmt.allocPrint( + alloc, + "./sprite_face_diff-U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(diff_path); + try z2d.png_exporter.writeToPNGFile(diff, diff_path, .{}); + log.err( + "One or more glyphs differ from reference file in range U+{X}...U+{X}! " ++ + "test={s}, reference={s}, diff={s}", + .{ i, i + 0xFF, test_path, ref_path, diff_path }, + ); + + return true; +} + +/// Draws all ranges in to a set of 16x16 glyph atlases, checks for regressions +/// against reference files, logs errors and exposes a diff for any difference +/// between the reference and test atlas. +/// +/// Returns true if there was a diff. +fn testDrawRanges( + width: u32, + ascent: u32, + descent: u32, + thickness: u32, +) !bool { + const testing = std.testing; + const alloc = testing.allocator; + + const metrics: font.Metrics = .calc(.{ + .cell_width = @floatFromInt(width), + .ascent = @floatFromInt(ascent), + .descent = -@as(f64, @floatFromInt(descent)), + .line_gap = 0.0, + .underline_thickness = @floatFromInt(thickness), + .strikethrough_thickness = @floatFromInt(thickness), + }); + + const height = ascent + descent; + + const padding_x = width / 4; + const padding_y = height / 4; + + // Canvas to draw glyphs on, we'll re-use this for all glyphs. + var canvas = try font.sprite.Canvas.init( + alloc, + width, + height, + padding_x, + padding_y, + ); + defer canvas.deinit(); + + // We render glyphs in batches of 256, which we copy (including padding) to + // a 16 by 16 surface to be compared with the reference file for that range. + const stride_x = width + 2 * padding_x; + const stride_y = height + 2 * padding_y; + var atlas = try z2d.Surface.init( + .image_surface_alpha8, + alloc, + @intCast(stride_x * 16), + @intCast(stride_y * 16), + ); + defer atlas.deinit(alloc); + + var i: u32 = std.mem.alignBackward(u32, ranges[0].min, 0x100); + + // Try to make the sprite_face_test folder if it doesn't already exist. + var dir = testing.tmpDir(.{}); + defer dir.cleanup(); + const tmp_dir = try dir.dir.realpathAlloc(alloc, "."); + defer alloc.free(tmp_dir); + + // We set this to true if we have any fails so we can + // return an error after we're done comparing all glyphs. + var fail: bool = false; + + inline for (ranges) |range| { + for (range.min..range.max + 1) |cp| { + // If we've moved to a new batch of 256, check the + // current one and clear the surface for the next one. + if (cp - i >= 0x100) { + // Export to our tmp dir. + const path = try std.fmt.allocPrint( + alloc, + "{s}/U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ tmp_dir, i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(path); + try z2d.png_exporter.writeToPNGFile(atlas, path, .{}); + + if (try testDiffAtlas( + alloc, + &atlas, + path, + i, + width, + height, + thickness, + )) fail = true; + + i = std.mem.alignBackward(u32, @intCast(cp), 0x100); + @memset(std.mem.sliceAsBytes(atlas.image_surface_alpha8.buf), 0); + } + + try getDrawFn(@intCast(cp)).?( + @intCast(cp), + &canvas, + width, + height, + metrics, + ); + canvas.clearClippingRegions(); + atlas.composite( + &canvas.sfc, + .src, + @intCast(stride_x * ((cp - i) % 16)), + @intCast(stride_y * ((cp - i) / 16)), + .{}, + ); + @memset(std.mem.sliceAsBytes(canvas.sfc.image_surface_alpha8.buf), 0); + canvas.clip_top = 0; + canvas.clip_left = 0; + canvas.clip_right = 0; + canvas.clip_bottom = 0; + } + } + + const path = try std.fmt.allocPrint( + alloc, + "{s}/U+{X}...U+{X}-{d}x{d}+{d}.png", + .{ tmp_dir, i, i + 0xFF, width, height, thickness }, + ); + defer alloc.free(path); + try z2d.png_exporter.writeToPNGFile(atlas, path, .{}); + if (try testDiffAtlas( + alloc, + &atlas, + path, + i, + width, + height, + thickness, + )) fail = true; + + return fail; +} + +test "sprite face render all sprites" { + // Renders all sprites to an atlas and compares + // it to a ground truth for regression testing. + + var diff: bool = false; + + // testDrawRanges(width, ascent, descent, thickness): + // + // We compare 4 different sets of metrics; + // - even cell size / even thickness + // - even cell size / odd thickness + // - odd cell size / even thickness + // - odd cell size / odd thickness + // (Also a decreasing range of sizes.) + if (try testDrawRanges(18, 30, 6, 4)) diff = true; + if (try testDrawRanges(12, 20, 4, 3)) diff = true; + if (try testDrawRanges(11, 19, 2, 2)) diff = true; + if (try testDrawRanges(9, 15, 2, 1)) diff = true; + + try std.testing.expect(!diff); // There should be no diffs from reference. +} + +// test "sprite face print all sprites" { +// std.debug.print("\n\n", .{}); +// inline for (ranges) |range| { +// for (range.min..range.max + 1) |cp| { +// std.debug.print("{u}", .{ @as(u21, @intCast(cp)) }); +// } +// } +// std.debug.print("\n\n", .{}); +// } test { - @import("std").testing.refAllDecls(@This()); + std.testing.refAllDecls(@This()); } diff --git a/src/font/sprite/Powerline.zig b/src/font/sprite/Powerline.zig deleted file mode 100644 index eaa7554b1..000000000 --- a/src/font/sprite/Powerline.zig +++ /dev/null @@ -1,564 +0,0 @@ -//! This file contains functions for drawing certain characters from Powerline -//! Extra (https://github.com/ryanoasis/powerline-extra-symbols). These -//! characters are similarly to box-drawing characters (see Box.zig), so the -//! logic will be mainly the same, just with a much reduced character set. -//! -//! Note that this is not the complete list of Powerline glyphs that may be -//! needed, so this may grow to add other glyphs from the set. -const Powerline = @This(); - -const std = @import("std"); -const Allocator = std.mem.Allocator; - -const font = @import("../main.zig"); -const Quad = @import("canvas.zig").Quad; - -const log = std.log.scoped(.powerline_font); - -/// The cell width and height because the boxes are fit perfectly -/// into a cell so that they all properly connect with zero spacing. -width: u32, -height: u32, - -/// Base thickness value for glyphs that are not completely solid (backslashes, -/// thin half-circles, etc). If you want to do any DPI scaling, it is expected -/// to be done earlier. -/// -/// TODO: this and Thickness are currently unused but will be when the -/// aforementioned glyphs are added. -thickness: u32, - -/// The thickness of a line. -const Thickness = enum { - super_light, - light, - heavy, - - /// Calculate the real height of a line based on its thickness - /// and a base thickness value. The base thickness value is expected - /// to be in pixels. - fn height(self: Thickness, base: u32) u32 { - return switch (self) { - .super_light => @max(base / 2, 1), - .light => base, - .heavy => base * 2, - }; - } -}; - -inline fn sq(x: anytype) @TypeOf(x) { - return x * x; -} - -pub fn renderGlyph( - self: Powerline, - alloc: Allocator, - atlas: *font.Atlas, - cp: u32, -) !font.Glyph { - // Create the canvas we'll use to draw - var canvas = try font.sprite.Canvas.init(alloc, self.width, self.height); - defer canvas.deinit(); - - // Perform the actual drawing - try self.draw(alloc, &canvas, cp); - - // Write the drawing to the atlas - const region = try canvas.writeAtlas(alloc, atlas); - - // Our coordinates start at the BOTTOM for our renderers so we have to - // specify an offset of the full height because we rendered a full size - // cell. - const offset_y = @as(i32, @intCast(self.height)); - - return font.Glyph{ - .width = self.width, - .height = self.height, - .offset_x = 0, - .offset_y = offset_y, - .atlas_x = region.x, - .atlas_y = region.y, - .advance_x = @floatFromInt(self.width), - }; -} - -fn draw(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void { - switch (cp) { - // Hard dividers and triangles - 0xE0B0, - 0xE0B2, - 0xE0B8, - 0xE0BA, - 0xE0BC, - 0xE0BE, - => try self.draw_wedge_triangle(canvas, cp), - - // Soft Dividers - 0xE0B1, - 0xE0B3, - => try self.draw_chevron(canvas, cp), - - // Half-circles - 0xE0B4, - 0xE0B6, - => try self.draw_half_circle(alloc, canvas, cp), - - // Mirrored top-down trapezoids - 0xE0D2, - 0xE0D4, - => try self.draw_trapezoid_top_bottom(canvas, cp), - - else => return error.InvalidCodepoint, - } -} - -fn draw_chevron(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void { - const width = self.width; - const height = self.height; - - var p1_x: u32 = 0; - var p1_y: u32 = 0; - var p2_x: u32 = 0; - var p2_y: u32 = 0; - var p3_x: u32 = 0; - var p3_y: u32 = 0; - - switch (cp) { - 0xE0B1 => { - p1_x = 0; - p1_y = 0; - p2_x = width; - p2_y = height / 2; - p3_x = 0; - p3_y = height; - }, - 0xE0B3 => { - p1_x = width; - p1_y = 0; - p2_x = 0; - p2_y = height / 2; - p3_x = width; - p3_y = height; - }, - - else => unreachable, - } - - try canvas.triangle_outline(.{ - .p0 = .{ .x = @floatFromInt(p1_x), .y = @floatFromInt(p1_y) }, - .p1 = .{ .x = @floatFromInt(p2_x), .y = @floatFromInt(p2_y) }, - .p2 = .{ .x = @floatFromInt(p3_x), .y = @floatFromInt(p3_y) }, - }, @floatFromInt(Thickness.light.height(self.thickness)), .on); -} - -fn draw_wedge_triangle(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void { - const width = self.width; - const height = self.height; - - var p1_x: u32 = 0; - var p2_x: u32 = 0; - var p3_x: u32 = 0; - var p1_y: u32 = 0; - var p2_y: u32 = 0; - var p3_y: u32 = 0; - - switch (cp) { - 0xE0B0 => { - p1_x = 0; - p1_y = 0; - p2_x = width; - p2_y = height / 2; - p3_x = 0; - p3_y = height; - }, - - 0xE0B2 => { - p1_x = width; - p1_y = 0; - p2_x = 0; - p2_y = height / 2; - p3_x = width; - p3_y = height; - }, - - 0xE0B8 => { - p1_x = 0; - p1_y = 0; - p2_x = width; - p2_y = height; - p3_x = 0; - p3_y = height; - }, - - 0xE0BA => { - p1_x = width; - p1_y = 0; - p2_x = width; - p2_y = height; - p3_x = 0; - p3_y = height; - }, - - 0xE0BC => { - p1_x = 0; - p1_y = 0; - p2_x = width; - p2_y = 0; - p3_x = 0; - p3_y = height; - }, - - 0xE0BE => { - p1_x = 0; - p1_y = 0; - p2_x = width; - p2_y = 0; - p3_x = width; - p3_y = height; - }, - - else => unreachable, - } - - try canvas.triangle(.{ - .p0 = .{ .x = @floatFromInt(p1_x), .y = @floatFromInt(p1_y) }, - .p1 = .{ .x = @floatFromInt(p2_x), .y = @floatFromInt(p2_y) }, - .p2 = .{ .x = @floatFromInt(p3_x), .y = @floatFromInt(p3_y) }, - }, .on); -} - -fn draw_half_circle(self: Powerline, alloc: Allocator, canvas: *font.sprite.Canvas, cp: u32) !void { - const supersample = 4; - - // We make a canvas big enough for the whole circle, with the supersample - // applied. - const width = self.width * 2 * supersample; - const height = self.height * supersample; - - // We set a minimum super-sampled canvas to assert on. The minimum cell - // size is 1x3px, and this looked safe in empirical testing. - std.debug.assert(width >= 8); // 1 * 2 * 4 - std.debug.assert(height >= 12); // 3 * 4 - - const center_x = width / 2 - 1; - const center_y = height / 2 - 1; - - // Our radii. We're technically drawing an ellipse here to ensure that this - // works for fonts with different aspect ratios than a typical 2:1 H*W, e.g. - // Iosevka (which is around 2.6:1). - const radius_x = width / 2 - 1; // This gives us a small margin for smoothing - const radius_y = height / 2; - - // Pre-allocate a matrix to plot the points on. - const cap = height * width; - var points = try alloc.alloc(u8, cap); - defer alloc.free(points); - @memset(points, 0); - - { - // This is a midpoint ellipse algorithm, similar to a midpoint circle - // algorithm in that we only draw the octants we need and then reflect - // the result across the other axes. Since an ellipse has two radii, we - // need to calculate two octants instead of one. There are variations - // on the algorithm and you can find many examples online. This one - // does use some floating point math in calculating the decision - // parameter, but I've found it clear in its implementation and it does - // not require adjustment for integer error. - // - // This algorithm has undergone some iterations, so the following - // references might be helpful for understanding: - // - // * "Drawing a circle, point by point, without floating point - // support" (Dennis Yurichev, - // https://yurichev.com/news/20220322_circle/), which describes the - // midpoint circle algorithm and implementation we initially adapted - // here. - // - // * "Ellipse-Generating Algorithms" (RTU Latvia, - // https://daugavpils.rtu.lv/wp-content/uploads/sites/34/2020/11/LEC_3.pdf), - // which was used to further adapt the algorithm for ellipses. - // - // * "An Effective Approach to Minimize Error in Midpoint Ellipse - // Drawing Algorithm" (Dr. M. Javed Idrisi, Aayesha Ashraf, - // https://arxiv.org/abs/2103.04033), which includes a synopsis of - // the history of ellipse drawing algorithms, and further references. - - // Declare some casted constants for use in various calculations below - const rx: i32 = @intCast(radius_x); - const ry: i32 = @intCast(radius_y); - const rxf: f64 = @floatFromInt(radius_x); - const ryf: f64 = @floatFromInt(radius_y); - const cx: i32 = @intCast(center_x); - const cy: i32 = @intCast(center_y); - - // Our plotting x and y - var x: i32 = 0; - var y: i32 = @intCast(radius_y); - - // Decision parameter, initialized for region 1 - var dparam: f64 = sq(ryf) - sq(rxf) * ryf + sq(rxf) * 0.25; - - // Region 1 - while (2 * sq(ry) * x < 2 * sq(rx) * y) { - // Right side - const x1 = @max(0, cx + x); - const y1 = @max(0, cy + y); - const x2 = @max(0, cx + x); - const y2 = @max(0, cy - y); - - // Left side - const x3 = @max(0, cx - x); - const y3 = @max(0, cy + y); - const x4 = @max(0, cx - x); - const y4 = @max(0, cy - y); - - // Points - const p1 = y1 * width + x1; - const p2 = y2 * width + x2; - const p3 = y3 * width + x3; - const p4 = y4 * width + x4; - - // Set the points in the matrix, ignore any out of bounds - if (p1 < cap) points[p1] = 0xFF; - if (p2 < cap) points[p2] = 0xFF; - if (p3 < cap) points[p3] = 0xFF; - if (p4 < cap) points[p4] = 0xFF; - - // Calculate next pixels based on midpoint bounds - x += 1; - if (dparam < 0) { - const xf: f64 = @floatFromInt(x); - dparam += 2 * sq(ryf) * xf + sq(ryf); - } else { - y -= 1; - const xf: f64 = @floatFromInt(x); - const yf: f64 = @floatFromInt(y); - dparam += 2 * sq(ryf) * xf - 2 * sq(rxf) * yf + sq(ryf); - } - } - - // Region 2 - { - // Reset our decision parameter for region 2 - const xf: f64 = @floatFromInt(x); - const yf: f64 = @floatFromInt(y); - dparam = sq(ryf) * sq(xf + 0.5) + sq(rxf) * sq(yf - 1) - sq(rxf) * sq(ryf); - } - while (y >= 0) { - // Right side - const x1 = @max(0, cx + x); - const y1 = @max(0, cy + y); - const x2 = @max(0, cx + x); - const y2 = @max(0, cy - y); - - // Left side - const x3 = @max(0, cx - x); - const y3 = @max(0, cy + y); - const x4 = @max(0, cx - x); - const y4 = @max(0, cy - y); - - // Points - const p1 = y1 * width + x1; - const p2 = y2 * width + x2; - const p3 = y3 * width + x3; - const p4 = y4 * width + x4; - - // Set the points in the matrix, ignore any out of bounds - if (p1 < cap) points[p1] = 0xFF; - if (p2 < cap) points[p2] = 0xFF; - if (p3 < cap) points[p3] = 0xFF; - if (p4 < cap) points[p4] = 0xFF; - - // Calculate next pixels based on midpoint bounds - y -= 1; - if (dparam > 0) { - const yf: f64 = @floatFromInt(y); - dparam -= 2 * sq(rxf) * yf + sq(rxf); - } else { - x += 1; - const xf: f64 = @floatFromInt(x); - const yf: f64 = @floatFromInt(y); - dparam += 2 * sq(ryf) * xf - 2 * sq(rxf) * yf + sq(rxf); - } - } - } - - // Fill - { - const u_height: u32 = @intCast(height); - const u_width: u32 = @intCast(width); - - for (0..u_height) |yf| { - for (0..u_width) |left| { - // Count forward from the left to the first filled pixel - if (points[yf * u_width + left] != 0) { - // Count back to our left point from the right to the first - // filled pixel on the other side. - var right: usize = u_width - 1; - while (right > left) : (right -= 1) { - if (points[yf * u_width + right] != 0) { - break; - } - } - - // Start filling 1 index after the left and go until we hit - // the right; this will be a no-op if the line length is < - // 3 as both left and right will have already been filled. - const start = yf * u_width + left; - const end = yf * u_width + right; - if (end - start >= 3) { - for (start + 1..end) |idx| { - points[idx] = 0xFF; - } - } - } - } - } - } - - // Now that we have our points, we need to "split" our matrix on the x - // axis for the downsample. - { - // The side of the circle we're drawing - const offset_j: u32 = if (cp == 0xE0B4) center_x + 1 else 0; - - for (0..self.height) |r| { - for (0..self.width) |c| { - var total: u32 = 0; - for (0..supersample) |i| { - for (0..supersample) |j| { - const idx = (r * supersample + i) * width + (c * supersample + j + offset_j); - total += points[idx]; - } - } - - const average = @as(u8, @intCast(@min(total / (supersample * supersample), 0xFF))); - canvas.rect( - .{ - .x = @intCast(c), - .y = @intCast(r), - .width = 1, - .height = 1, - }, - @as(font.sprite.Color, @enumFromInt(average)), - ); - } - } - } -} - -fn draw_trapezoid_top_bottom(self: Powerline, canvas: *font.sprite.Canvas, cp: u32) !void { - const t_top: Quad(f64) = if (cp == 0xE0D4) - .{ - .p0 = .{ - .x = 0, - .y = 0, - }, - .p1 = .{ - .x = @floatFromInt(self.width - self.width / 3), - .y = @floatFromInt(self.height / 2 - self.height / 20), - }, - .p2 = .{ - .x = @floatFromInt(self.width), - .y = @floatFromInt(self.height / 2 - self.height / 20), - }, - .p3 = .{ - .x = @floatFromInt(self.width), - .y = 0, - }, - } - else - .{ - .p0 = .{ - .x = 0, - .y = 0, - }, - .p1 = .{ - .x = 0, - .y = @floatFromInt(self.height / 2 - self.height / 20), - }, - .p2 = .{ - .x = @floatFromInt(self.width / 3), - .y = @floatFromInt(self.height / 2 - self.height / 20), - }, - .p3 = .{ - .x = @floatFromInt(self.width), - .y = 0, - }, - }; - - const t_bottom: Quad(f64) = if (cp == 0xE0D4) - .{ - .p0 = .{ - .x = @floatFromInt(self.width - self.width / 3), - .y = @floatFromInt(self.height / 2 + self.height / 20), - }, - .p1 = .{ - .x = 0, - .y = @floatFromInt(self.height), - }, - .p2 = .{ - .x = @floatFromInt(self.width), - .y = @floatFromInt(self.height), - }, - .p3 = .{ - .x = @floatFromInt(self.width), - .y = @floatFromInt(self.height / 2 + self.height / 20), - }, - } - else - .{ - .p0 = .{ - .x = 0, - .y = @floatFromInt(self.height / 2 + self.height / 20), - }, - .p1 = .{ - .x = 0, - .y = @floatFromInt(self.height), - }, - .p2 = .{ - .x = @floatFromInt(self.width), - .y = @floatFromInt(self.height), - }, - .p3 = .{ - .x = @floatFromInt(self.width / 3), - .y = @floatFromInt(self.height / 2 + self.height / 20), - }, - }; - - try canvas.quad(t_top, .on); - try canvas.quad(t_bottom, .on); -} - -test "all" { - const testing = std.testing; - const alloc = testing.allocator; - - const cps = [_]u32{ - 0xE0B0, - 0xE0B2, - 0xE0B8, - 0xE0BA, - 0xE0BC, - 0xE0BE, - 0xE0B4, - 0xE0B6, - 0xE0D2, - 0xE0D4, - 0xE0B1, - 0xE0B3, - }; - for (cps) |cp| { - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - const face: Powerline = .{ .width = 18, .height = 36, .thickness = 2 }; - const glyph = try face.renderGlyph( - alloc, - &atlas_grayscale, - cp, - ); - try testing.expectEqual(@as(u32, face.width), glyph.width); - try testing.expectEqual(@as(u32, face.height), glyph.height); - } -} diff --git a/src/font/sprite/canvas.zig b/src/font/sprite/canvas.zig index a5ca7b290..a77b90a56 100644 --- a/src/font/sprite/canvas.zig +++ b/src/font/sprite/canvas.zig @@ -81,19 +81,39 @@ pub const Canvas = struct { /// The underlying z2d surface. sfc: z2d.Surface, + padding_x: u32, + padding_y: u32, + + clip_top: u32 = 0, + clip_left: u32 = 0, + clip_right: u32 = 0, + clip_bottom: u32 = 0, + alloc: Allocator, - pub fn init(alloc: Allocator, width: u32, height: u32) !Canvas { + pub fn init( + alloc: Allocator, + width: u32, + height: u32, + padding_x: u32, + padding_y: u32, + ) !Canvas { // Create the surface we'll be using. + // We add padding to both sides (hence `2 *`) const sfc = try z2d.Surface.initPixel( .{ .alpha8 = .{ .a = 0 } }, alloc, - @intCast(width), - @intCast(height), + @intCast(width + 2 * padding_x), + @intCast(height + 2 * padding_y), ); errdefer sfc.deinit(alloc); - return .{ .sfc = sfc, .alloc = alloc }; + return .{ + .sfc = sfc, + .padding_x = padding_x, + .padding_y = padding_y, + .alloc = alloc, + }; } pub fn deinit(self: *Canvas) void { @@ -109,69 +129,155 @@ pub const Canvas = struct { ) (Allocator.Error || font.Atlas.Error)!font.Atlas.Region { assert(atlas.format == .grayscale); - const width = @as(u32, @intCast(self.sfc.getWidth())); - const height = @as(u32, @intCast(self.sfc.getHeight())); + self.trim(); + + const sfc_width: u32 = @intCast(self.sfc.getWidth()); + const sfc_height: u32 = @intCast(self.sfc.getHeight()); + + // Subtract our clip margins from the + // width and height to get region size. + const region_width = sfc_width -| self.clip_left -| self.clip_right; + const region_height = sfc_height -| self.clip_top -| self.clip_bottom; // Allocate our texture atlas region - const region = region: { - // We need to add a 1px padding to the font so that we don't - // get fuzzy issues when blending textures. - const padding = 1; - - // Get the full padded region - var region = try atlas.reserve( - alloc, - width + (padding * 2), // * 2 because left+right - height + (padding * 2), // * 2 because top+bottom - ); - - // Modify the region so that we remove the padding so that - // we write to the non-zero location. The data in an Altlas - // is always initialized to zero (Atlas.clear) so we don't - // need to worry about zero-ing that. - region.x += padding; - region.y += padding; - region.width -= padding * 2; - region.height -= padding * 2; - break :region region; - }; + const region = try atlas.reserve(alloc, region_width, region_height); if (region.width > 0 and region.height > 0) { const buffer: []u8 = @ptrCast(self.sfc.image_surface_alpha8.buf); // Write the glyph information into the atlas - assert(region.width == width); - assert(region.height == height); - atlas.set(region, buffer); + assert(region.width == region_width); + assert(region.height == region_height); + atlas.setFromLarger( + region, + buffer, + sfc_width, + self.clip_left, + self.clip_top, + ); } return region; } + // Adjust clip boundaries to trim off any fully transparent rows or columns. + // This circumvents abstractions from z2d so that it can be performant. + fn trim(self: *Canvas) void { + const width: u32 = @intCast(self.sfc.getWidth()); + const height: u32 = @intCast(self.sfc.getHeight()); + + const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf); + + top: while (self.clip_top < height - self.clip_bottom) { + const y = self.clip_top; + const x0 = self.clip_left; + const x1 = width - self.clip_right; + for (buf[y * width ..][x0..x1]) |v| { + if (v != 0) break :top; + } + self.clip_top += 1; + } + + bottom: while (self.clip_bottom < height - self.clip_top) { + const y = height - self.clip_bottom -| 1; + const x0 = self.clip_left; + const x1 = width - self.clip_right; + for (buf[y * width ..][x0..x1]) |v| { + if (v != 0) break :bottom; + } + self.clip_bottom += 1; + } + + left: while (self.clip_left < width - self.clip_right) { + const x = self.clip_left; + const y0 = self.clip_top; + const y1 = height - self.clip_bottom; + for (y0..y1) |y| { + if (buf[y * width + x] != 0) break :left; + } + self.clip_left += 1; + } + + right: while (self.clip_right < width - self.clip_left) { + const x = width - self.clip_right -| 1; + const y0 = self.clip_top; + const y1 = height - self.clip_bottom; + for (y0..y1) |y| { + if (buf[y * width + x] != 0) break :right; + } + self.clip_right += 1; + } + } + + /// Only really useful for test purposes, since the clipping region is + /// automatically excluded when writing to an atlas with `writeAtlas`. + pub fn clearClippingRegions(self: *Canvas) void { + const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf); + const width: usize = @intCast(self.sfc.getWidth()); + const height: usize = @intCast(self.sfc.getHeight()); + + for (0..height) |y| { + for (0..self.clip_left) |x| { + buf[y * width + x] = 0; + } + } + + for (0..height) |y| { + for (width - self.clip_right..width) |x| { + buf[y * width + x] = 0; + } + } + + for (0..self.clip_top) |y| { + for (0..width) |x| { + buf[y * width + x] = 0; + } + } + + for (height - self.clip_bottom..height) |y| { + for (0..width) |x| { + buf[y * width + x] = 0; + } + } + } + + /// Return a transformation representing the translation for our padding. + pub fn transformation(self: Canvas) z2d.Transformation { + return .{ + .ax = 1, + .by = 0, + .cx = 0, + .dy = 1, + .tx = @as(f64, @floatFromInt(self.padding_x)), + .ty = @as(f64, @floatFromInt(self.padding_y)), + }; + } + /// Acquires a z2d drawing context, caller MUST deinit context. pub fn getContext(self: *Canvas) z2d.Context { - return .init(self.alloc, &self.sfc); + var ctx = z2d.Context.init(self.alloc, &self.sfc); + // Offset by our padding to keep + // coordinates relative to the cell. + ctx.setTransformation(self.transformation()); + return ctx; } /// Draw and fill a single pixel - pub fn pixel(self: *Canvas, x: u32, y: u32, color: Color) void { + pub fn pixel(self: *Canvas, x: i32, y: i32, color: Color) void { self.sfc.putPixel( - @intCast(x), - @intCast(y), + x + @as(i32, @intCast(self.padding_x)), + y + @as(i32, @intCast(self.padding_y)), .{ .alpha8 = .{ .a = @intFromEnum(color) } }, ); } /// Draw and fill a rectangle. This is the main primitive for drawing /// lines as well (which are just generally skinny rectangles...) - pub fn rect(self: *Canvas, v: Rect(u32), color: Color) void { - const x0 = v.x; - const x1 = v.x + v.width; - const y0 = v.y; - const y1 = v.y + v.height; - - for (y0..y1) |y| { - for (x0..x1) |x| { + pub fn rect(self: *Canvas, v: Rect(i32), color: Color) void { + var y = v.y; + while (y < v.y + v.height) : (y += 1) { + var x = v.x; + while (x < v.x + v.width) : (x += 1) { self.pixel( @intCast(x), @intCast(y), @@ -181,96 +287,226 @@ pub const Canvas = struct { } } + /// Convenience wrapper for `Canvas.rect` + pub fn box( + self: *Canvas, + x0: i32, + y0: i32, + x1: i32, + y1: i32, + color: Color, + ) void { + self.rect((Box(i32){ + .p0 = .{ .x = x0, .y = y0 }, + .p1 = .{ .x = x1, .y = y1 }, + }).rect(), color); + } + /// Draw and fill a quad. pub fn quad(self: *Canvas, q: Quad(f64), color: Color) !void { - var path: z2d.StaticPath(6) = .{}; - path.init(); // nodes.len = 0 - + var path = self.staticPath(6); // nodes.len = 0 path.moveTo(q.p0.x, q.p0.y); // +1, nodes.len = 1 path.lineTo(q.p1.x, q.p1.y); // +1, nodes.len = 2 path.lineTo(q.p2.x, q.p2.y); // +1, nodes.len = 3 path.lineTo(q.p3.x, q.p3.y); // +1, nodes.len = 4 path.close(); // +2, nodes.len = 6 - - try z2d.painter.fill( - self.alloc, - &self.sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, - } }, - path.wrapped_path.nodes.items, - .{}, - ); + try self.fillPath(path.wrapped_path, .{}, color); } /// Draw and fill a triangle. pub fn triangle(self: *Canvas, t: Triangle(f64), color: Color) !void { - var path: z2d.StaticPath(5) = .{}; - path.init(); // nodes.len = 0 - + var path = self.staticPath(5); // nodes.len = 0 path.moveTo(t.p0.x, t.p0.y); // +1, nodes.len = 1 path.lineTo(t.p1.x, t.p1.y); // +1, nodes.len = 2 path.lineTo(t.p2.x, t.p2.y); // +1, nodes.len = 3 path.close(); // +2, nodes.len = 5 + try self.fillPath(path.wrapped_path, .{}, color); + } + /// Stroke a line. + pub fn line( + self: *Canvas, + l: Line(f64), + thickness: f64, + color: Color, + ) !void { + var path = self.staticPath(2); // nodes.len = 0 + path.moveTo(l.p0.x, l.p0.y); // +1, nodes.len = 1 + path.lineTo(l.p1.x, l.p1.y); // +1, nodes.len = 2 + try self.strokePath( + path.wrapped_path, + .{ + .line_cap_mode = .butt, + .line_width = thickness, + }, + color, + ); + } + + /// Create a static path of the provided len and initialize it. + /// Use this function instead of making the path manually since + /// it ensures that the transform is applied. + pub inline fn staticPath( + self: *Canvas, + comptime len: usize, + ) z2d.StaticPath(len) { + var path: z2d.StaticPath(len) = .{}; + path.init(); + path.wrapped_path.transformation = self.transformation(); + return path; + } + + /// Stroke a z2d path. + pub fn strokePath( + self: *Canvas, + path: z2d.Path, + opts: z2d.painter.StrokeOpts, + color: Color, + ) z2d.painter.StrokeError!void { + try z2d.painter.stroke( + self.alloc, + &self.sfc, + &.{ .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, + } }, + path.nodes.items, + opts, + ); + } + + /// Do an inner stroke on a z2d path, right now this involves a pretty + /// heavy workaround that uses two extra surfaces; in the future, z2d + /// should add inner and outer strokes natively. + pub fn innerStrokePath( + self: *Canvas, + path: z2d.Path, + opts: z2d.painter.StrokeOpts, + color: Color, + ) (z2d.painter.StrokeError || z2d.painter.FillError)!void { + // On one surface we fill the shape, this will be a mask we + // multiply with the double-width stroke so that only the + // part inside is used. + var fill_sfc: z2d.Surface = try .init( + .image_surface_alpha8, + self.alloc, + self.sfc.getWidth(), + self.sfc.getHeight(), + ); + defer fill_sfc.deinit(self.alloc); + + // On the other we'll do the double width stroke. + var stroke_sfc: z2d.Surface = try .init( + .image_surface_alpha8, + self.alloc, + self.sfc.getWidth(), + self.sfc.getHeight(), + ); + defer stroke_sfc.deinit(self.alloc); + + // Make a closed version of the path for our fill, so + // that we can support open paths for inner stroke. + var closed_path = path; + closed_path.nodes = try path.nodes.clone(self.alloc); + defer closed_path.deinit(self.alloc); + try closed_path.close(self.alloc); + + // Fill the shape in white to the fill surface, we use + // white because this is a mask that we'll multiply with + // the stroke, we want everything inside to be the stroke + // color. + try z2d.painter.fill( + self.alloc, + &fill_sfc, + &.{ .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = 255 } }, + } }, + closed_path.nodes.items, + .{}, + ); + + // Stroke the shape with double the desired width. + var mut_opts = opts; + mut_opts.line_width *= 2; + try z2d.painter.stroke( + self.alloc, + &stroke_sfc, + &.{ .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, + } }, + path.nodes.items, + mut_opts, + ); + + // We multiply the stroke sfc on to the fill surface. + // The z2d composite operation doesn't seem to work for + // this with alpha8 surfaces, so we have to do it manually. + for ( + std.mem.sliceAsBytes(fill_sfc.image_surface_alpha8.buf), + std.mem.sliceAsBytes(stroke_sfc.image_surface_alpha8.buf), + ) |*d, s| { + d.* = @intFromFloat(@round( + 255.0 * + (@as(f64, @floatFromInt(s)) / 255.0) * + (@as(f64, @floatFromInt(d.*)) / 255.0), + )); + } + + // Then we composite the result on to the main surface. + self.sfc.composite(&fill_sfc, .src_over, 0, 0, .{}); + } + + /// Fill a z2d path. + pub fn fillPath( + self: *Canvas, + path: z2d.Path, + opts: z2d.painter.FillOpts, + color: Color, + ) z2d.painter.FillError!void { try z2d.painter.fill( self.alloc, &self.sfc, &.{ .opaque_pattern = .{ .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, } }, - path.wrapped_path.nodes.items, - .{}, - ); - } - - pub fn triangle_outline(self: *Canvas, t: Triangle(f64), thickness: f64, color: Color) !void { - var path: z2d.StaticPath(3) = .{}; - path.init(); // nodes.len = 0 - - path.moveTo(t.p0.x, t.p0.y); // +1, nodes.len = 1 - path.lineTo(t.p1.x, t.p1.y); // +1, nodes.len = 2 - path.lineTo(t.p2.x, t.p2.y); // +1, nodes.len = 3 - - try z2d.painter.stroke( - self.alloc, - &self.sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, - } }, - path.wrapped_path.nodes.items, - .{ - .line_cap_mode = .round, - .line_width = thickness, - }, - ); - } - - /// Stroke a line. - pub fn line(self: *Canvas, l: Line(f64), thickness: f64, color: Color) !void { - var path: z2d.StaticPath(2) = .{}; - path.init(); // nodes.len = 0 - - path.moveTo(l.p0.x, l.p0.y); // +1, nodes.len = 1 - path.lineTo(l.p1.x, l.p1.y); // +1, nodes.len = 2 - - try z2d.painter.stroke( - self.alloc, - &self.sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, - } }, - path.wrapped_path.nodes.items, - .{ - .line_cap_mode = .round, - .line_width = thickness, - }, + path.nodes.items, + opts, ); } + /// Invert all pixels on the canvas. pub fn invert(self: *Canvas) void { for (std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf)) |*v| { v.* = 255 - v.*; } } + + /// Mirror the canvas horizontally. + pub fn flipHorizontal(self: *Canvas) Allocator.Error!void { + const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf); + const clone = try self.alloc.dupe(u8, buf); + defer self.alloc.free(clone); + const width: usize = @intCast(self.sfc.getWidth()); + const height: usize = @intCast(self.sfc.getHeight()); + for (0..height) |y| { + for (0..width) |x| { + buf[y * width + x] = clone[y * width + width - x - 1]; + } + } + std.mem.swap(u32, &self.clip_left, &self.clip_right); + } + + /// Mirror the canvas vertically. + pub fn flipVertical(self: *Canvas) Allocator.Error!void { + const buf = std.mem.sliceAsBytes(self.sfc.image_surface_alpha8.buf); + const clone = try self.alloc.dupe(u8, buf); + defer self.alloc.free(clone); + const width: usize = @intCast(self.sfc.getWidth()); + const height: usize = @intCast(self.sfc.getHeight()); + for (0..height) |y| { + for (0..width) |x| { + buf[y * width + x] = clone[(height - y - 1) * width + x]; + } + } + std.mem.swap(u32, &self.clip_top, &self.clip_bottom); + } }; diff --git a/src/font/sprite/cursor.zig b/src/font/sprite/cursor.zig deleted file mode 100644 index d63db624a..000000000 --- a/src/font/sprite/cursor.zig +++ /dev/null @@ -1,65 +0,0 @@ -//! This file renders cursor sprites. -const std = @import("std"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; -const font = @import("../main.zig"); -const Sprite = font.sprite.Sprite; - -/// Draw a cursor. -pub fn renderGlyph( - alloc: Allocator, - atlas: *font.Atlas, - sprite: Sprite, - width: u32, - height: u32, - thickness: u32, -) !font.Glyph { - // Make a canvas of the desired size - var canvas = try font.sprite.Canvas.init(alloc, width, height); - defer canvas.deinit(); - - // Draw the appropriate sprite - switch (sprite) { - Sprite.cursor_rect => canvas.rect(.{ - .x = 0, - .y = 0, - .width = width, - .height = height, - }, .on), - Sprite.cursor_hollow_rect => { - // left - canvas.rect(.{ .x = 0, .y = 0, .width = thickness, .height = height }, .on); - // right - canvas.rect(.{ .x = width -| thickness, .y = 0, .width = thickness, .height = height }, .on); - // top - canvas.rect(.{ .x = 0, .y = 0, .width = width, .height = thickness }, .on); - // bottom - canvas.rect(.{ .x = 0, .y = height -| thickness, .width = width, .height = thickness }, .on); - }, - Sprite.cursor_bar => canvas.rect(.{ - .x = 0, - .y = 0, - .width = thickness, - .height = height, - }, .on), - else => unreachable, - } - - // Write the drawing to the atlas - const region = try canvas.writeAtlas(alloc, atlas); - - return font.Glyph{ - // HACK: Set the width for the bar cursor to just the thickness, - // this is just for the benefit of the custom shader cursor - // uniform code. -- In the future code will be introduced to - // auto-crop the canvas so that this isn't needed. - .width = if (sprite == .cursor_bar) thickness else width, - .height = height, - .offset_x = 0, - .offset_y = @intCast(height), - .atlas_x = region.x, - .atlas_y = region.y, - .advance_x = @floatFromInt(width), - }; -} diff --git a/src/font/sprite/draw/README.md b/src/font/sprite/draw/README.md new file mode 100644 index 000000000..d16035996 --- /dev/null +++ b/src/font/sprite/draw/README.md @@ -0,0 +1,55 @@ +# This is a _special_ directory. + +The files in this directory are imported by `../Face.zig` and scanned for pub +functions with names matching a specific format, which are then used to handle +drawing specified codepoints. + +## IMPORTANT + +When you add a new file here, you need to add the corresponding import in +`../Face.zig` for its draw functions to be picked up. I tried dynamically +listing these files to do this automatically but it was more pain than it +was worth. + +## `draw*` functions + +Any function named `draw` or `draw_` will be used to +draw the codepoint or range of codepoints specified in the name. These are +hex-encoded values with upper case letters. + +`draw*` functions are provided with these arguments: + +```zig +/// The codepoint being drawn. For single-codepoint draw functions this can +/// just be discarded, but it's needed for range draw functions to determine +/// which value in the range needs to be drawn. +cp: u32, +/// The canvas on which to draw the codepoint. +//// +/// This canvas has been prepared with an extra quarter of the width/height on +/// each edge, and its transform has been set so that [0, 0] is still the upper +/// left of the cell and [width, height] is still the bottom right; in order to +/// draw above or to the left, use negative values, and to draw below or to the +/// right use values greater than the width or the height. +/// +/// Because the canvas has been prepared this way, it's possible to draw glyphs +/// that exit the cell bounds by some amount- an example of when this is useful +/// is in drawing box-drawing diagonals, with enough overlap so that they can +/// seamlessly connect across corners of cells. +canvas: *font.sprite.Canvas, +/// The width of the cell to draw for. +width: u32, +/// The height of the cell to draw for. +height: u32, +/// The font grid metrics. +metrics: font.Metrics, +``` + +`draw*` functions may only return `DrawFnError!void` (defined in `../Face.zig`). + +## `special.zig` + +The functions in `special.zig` are not for drawing unicode codepoints, +rather their names match the enum tag names in the `Sprite` enum from +`src/font/sprite.zig`. They are called with the same arguments as the +other `draw*` functions. diff --git a/src/font/sprite/draw/block.zig b/src/font/sprite/draw/block.zig new file mode 100644 index 000000000..571f25a79 --- /dev/null +++ b/src/font/sprite/draw/block.zig @@ -0,0 +1,181 @@ +//! Block Elements | U+2580...U+259F +//! https://en.wikipedia.org/wiki/Block_Elements +//! +//! ▀▁▂▃▄▅▆▇█▉▊▋▌▍▎▏ +//! ▐░▒▓▔▕▖▗▘▙▚▛▜▝▞▟ +//! + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Shade = common.Shade; +const Quads = common.Quads; +const Alignment = common.Alignment; +const fill = common.fill; + +const font = @import("../../main.zig"); +const Sprite = @import("../../sprite.zig").Sprite; + +// Utility names for common fractions +const one_eighth: f64 = 0.125; +const one_quarter: f64 = 0.25; +const one_third: f64 = (1.0 / 3.0); +const three_eighths: f64 = 0.375; +const half: f64 = 0.5; +const five_eighths: f64 = 0.625; +const two_thirds: f64 = (2.0 / 3.0); +const three_quarters: f64 = 0.75; +const seven_eighths: f64 = 0.875; + +pub fn draw2580_259F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '▀' UPPER HALF BLOCK + 0x2580 => block(metrics, canvas, .upper, 1, half), + // '▁' LOWER ONE EIGHTH BLOCK + 0x2581 => block(metrics, canvas, .lower, 1, one_eighth), + // '▂' LOWER ONE QUARTER BLOCK + 0x2582 => block(metrics, canvas, .lower, 1, one_quarter), + // '▃' LOWER THREE EIGHTHS BLOCK + 0x2583 => block(metrics, canvas, .lower, 1, three_eighths), + // '▄' LOWER HALF BLOCK + 0x2584 => block(metrics, canvas, .lower, 1, half), + // '▅' LOWER FIVE EIGHTHS BLOCK + 0x2585 => block(metrics, canvas, .lower, 1, five_eighths), + // '▆' LOWER THREE QUARTERS BLOCK + 0x2586 => block(metrics, canvas, .lower, 1, three_quarters), + // '▇' LOWER SEVEN EIGHTHS BLOCK + 0x2587 => block(metrics, canvas, .lower, 1, seven_eighths), + // '█' FULL BLOCK + 0x2588 => fullBlockShade(metrics, canvas, .on), + // '▉' LEFT SEVEN EIGHTHS BLOCK + 0x2589 => block(metrics, canvas, .left, seven_eighths, 1), + // '▊' LEFT THREE QUARTERS BLOCK + 0x258a => block(metrics, canvas, .left, three_quarters, 1), + // '▋' LEFT FIVE EIGHTHS BLOCK + 0x258b => block(metrics, canvas, .left, five_eighths, 1), + // '▌' LEFT HALF BLOCK + 0x258c => block(metrics, canvas, .left, half, 1), + // '▍' LEFT THREE EIGHTHS BLOCK + 0x258d => block(metrics, canvas, .left, three_eighths, 1), + // '▎' LEFT ONE QUARTER BLOCK + 0x258e => block(metrics, canvas, .left, one_quarter, 1), + // '▏' LEFT ONE EIGHTH BLOCK + 0x258f => block(metrics, canvas, .left, one_eighth, 1), + + // '▐' RIGHT HALF BLOCK + 0x2590 => block(metrics, canvas, .right, half, 1), + // '░' + 0x2591 => fullBlockShade(metrics, canvas, .light), + // '▒' + 0x2592 => fullBlockShade(metrics, canvas, .medium), + // '▓' + 0x2593 => fullBlockShade(metrics, canvas, .dark), + // '▔' UPPER ONE EIGHTH BLOCK + 0x2594 => block(metrics, canvas, .upper, 1, one_eighth), + // '▕' RIGHT ONE EIGHTH BLOCK + 0x2595 => block(metrics, canvas, .right, one_eighth, 1), + // '▖' + 0x2596 => quadrant(metrics, canvas, .{ .bl = true }), + // '▗' + 0x2597 => quadrant(metrics, canvas, .{ .br = true }), + // '▘' + 0x2598 => quadrant(metrics, canvas, .{ .tl = true }), + // '▙' + 0x2599 => quadrant(metrics, canvas, .{ .tl = true, .bl = true, .br = true }), + // '▚' + 0x259a => quadrant(metrics, canvas, .{ .tl = true, .br = true }), + // '▛' + 0x259b => quadrant(metrics, canvas, .{ .tl = true, .tr = true, .bl = true }), + // '▜' + 0x259c => quadrant(metrics, canvas, .{ .tl = true, .tr = true, .br = true }), + // '▝' + 0x259d => quadrant(metrics, canvas, .{ .tr = true }), + // '▞' + 0x259e => quadrant(metrics, canvas, .{ .tr = true, .bl = true }), + // '▟' + 0x259f => quadrant(metrics, canvas, .{ .tr = true, .bl = true, .br = true }), + + else => unreachable, + } +} + +pub fn block( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime alignment: Alignment, + comptime width: f64, + comptime height: f64, +) void { + blockShade(metrics, canvas, alignment, width, height, .on); +} + +pub fn blockShade( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime alignment: Alignment, + comptime width: f64, + comptime height: f64, + comptime shade: Shade, +) void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + const w: u32 = @intFromFloat(@round(float_width * width)); + const h: u32 = @intFromFloat(@round(float_height * height)); + + const x = switch (alignment.horizontal) { + .left => 0, + .right => metrics.cell_width - w, + .center => (metrics.cell_width - w) / 2, + }; + const y = switch (alignment.vertical) { + .top => 0, + .bottom => metrics.cell_height - h, + .middle => (metrics.cell_height - h) / 2, + }; + + canvas.rect(.{ + .x = @intCast(x), + .y = @intCast(y), + .width = @intCast(w), + .height = @intCast(h), + }, @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade)))); +} + +pub fn fullBlockShade( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + shade: Shade, +) void { + canvas.box( + 0, + 0, + @intCast(metrics.cell_width), + @intCast(metrics.cell_height), + @as(font.sprite.Color, @enumFromInt(@intFromEnum(shade))), + ); +} + +fn quadrant( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime quads: Quads, +) void { + if (quads.tl) fill(metrics, canvas, .zero, .half, .zero, .half); + if (quads.tr) fill(metrics, canvas, .half, .full, .zero, .half); + if (quads.bl) fill(metrics, canvas, .zero, .half, .half, .full); + if (quads.br) fill(metrics, canvas, .half, .full, .half, .full); +} diff --git a/src/font/sprite/draw/box.zig b/src/font/sprite/draw/box.zig new file mode 100644 index 000000000..f14e5a3f9 --- /dev/null +++ b/src/font/sprite/draw/box.zig @@ -0,0 +1,932 @@ +//! Box Drawing | U+2500...U+257F +//! https://en.wikipedia.org/wiki/Box_Drawing +//! +//! ─━│┃┄┅┆┇┈┉┊┋┌┍┎┏ +//! ┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟ +//! ┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯ +//! ┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿ +//! ╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏ +//! ═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟ +//! ╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯ +//! ╰╱╲╳╴╵╶╷╸╹╺╻╼╽╾╿ +//! + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Shade = common.Shade; +const Quads = common.Quads; +const Corner = common.Corner; +const Edge = common.Edge; +const Alignment = common.Alignment; +const hline = common.hline; +const vline = common.vline; +const hlineMiddle = common.hlineMiddle; +const vlineMiddle = common.vlineMiddle; + +const font = @import("../../main.zig"); +const Sprite = @import("../../sprite.zig").Sprite; + +/// Specification of a traditional intersection-style line/box-drawing char, +/// which can have a different style of line from each edge to the center. +pub const Lines = packed struct(u8) { + up: Style = .none, + right: Style = .none, + down: Style = .none, + left: Style = .none, + + const Style = enum(u2) { + none, + light, + heavy, + double, + }; +}; + +pub fn draw2500_257F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '─' + 0x2500 => linesChar(metrics, canvas, .{ .left = .light, .right = .light }), + // '━' + 0x2501 => linesChar(metrics, canvas, .{ .left = .heavy, .right = .heavy }), + // '│' + 0x2502 => linesChar(metrics, canvas, .{ .up = .light, .down = .light }), + // '┃' + 0x2503 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy }), + // '┄' + 0x2504 => dashHorizontal( + metrics, + canvas, + 3, + Thickness.light.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┅' + 0x2505 => dashHorizontal( + metrics, + canvas, + 3, + Thickness.heavy.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┆' + 0x2506 => dashVertical( + metrics, + canvas, + 3, + Thickness.light.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┇' + 0x2507 => dashVertical( + metrics, + canvas, + 3, + Thickness.heavy.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┈' + 0x2508 => dashHorizontal( + metrics, + canvas, + 4, + Thickness.light.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┉' + 0x2509 => dashHorizontal( + metrics, + canvas, + 4, + Thickness.heavy.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┊' + 0x250a => dashVertical( + metrics, + canvas, + 4, + Thickness.light.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┋' + 0x250b => dashVertical( + metrics, + canvas, + 4, + Thickness.heavy.height(metrics.box_thickness), + @max(4, Thickness.light.height(metrics.box_thickness)), + ), + // '┌' + 0x250c => linesChar(metrics, canvas, .{ .down = .light, .right = .light }), + // '┍' + 0x250d => linesChar(metrics, canvas, .{ .down = .light, .right = .heavy }), + // '┎' + 0x250e => linesChar(metrics, canvas, .{ .down = .heavy, .right = .light }), + // '┏' + 0x250f => linesChar(metrics, canvas, .{ .down = .heavy, .right = .heavy }), + + // '┐' + 0x2510 => linesChar(metrics, canvas, .{ .down = .light, .left = .light }), + // '┑' + 0x2511 => linesChar(metrics, canvas, .{ .down = .light, .left = .heavy }), + // '┒' + 0x2512 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .light }), + // '┓' + 0x2513 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .heavy }), + // '└' + 0x2514 => linesChar(metrics, canvas, .{ .up = .light, .right = .light }), + // '┕' + 0x2515 => linesChar(metrics, canvas, .{ .up = .light, .right = .heavy }), + // '┖' + 0x2516 => linesChar(metrics, canvas, .{ .up = .heavy, .right = .light }), + // '┗' + 0x2517 => linesChar(metrics, canvas, .{ .up = .heavy, .right = .heavy }), + // '┘' + 0x2518 => linesChar(metrics, canvas, .{ .up = .light, .left = .light }), + // '┙' + 0x2519 => linesChar(metrics, canvas, .{ .up = .light, .left = .heavy }), + // '┚' + 0x251a => linesChar(metrics, canvas, .{ .up = .heavy, .left = .light }), + // '┛' + 0x251b => linesChar(metrics, canvas, .{ .up = .heavy, .left = .heavy }), + // '├' + 0x251c => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .right = .light }), + // '┝' + 0x251d => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .right = .heavy }), + // '┞' + 0x251e => linesChar(metrics, canvas, .{ .up = .heavy, .right = .light, .down = .light }), + // '┟' + 0x251f => linesChar(metrics, canvas, .{ .down = .heavy, .right = .light, .up = .light }), + + // '┠' + 0x2520 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .right = .light }), + // '┡' + 0x2521 => linesChar(metrics, canvas, .{ .down = .light, .right = .heavy, .up = .heavy }), + // '┢' + 0x2522 => linesChar(metrics, canvas, .{ .up = .light, .right = .heavy, .down = .heavy }), + // '┣' + 0x2523 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .right = .heavy }), + // '┤' + 0x2524 => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .light }), + // '┥' + 0x2525 => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .heavy }), + // '┦' + 0x2526 => linesChar(metrics, canvas, .{ .up = .heavy, .left = .light, .down = .light }), + // '┧' + 0x2527 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .light, .up = .light }), + // '┨' + 0x2528 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .light }), + // '┩' + 0x2529 => linesChar(metrics, canvas, .{ .down = .light, .left = .heavy, .up = .heavy }), + // '┪' + 0x252a => linesChar(metrics, canvas, .{ .up = .light, .left = .heavy, .down = .heavy }), + // '┫' + 0x252b => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy }), + // '┬' + 0x252c => linesChar(metrics, canvas, .{ .down = .light, .left = .light, .right = .light }), + // '┭' + 0x252d => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light, .down = .light }), + // '┮' + 0x252e => linesChar(metrics, canvas, .{ .right = .heavy, .left = .light, .down = .light }), + // '┯' + 0x252f => linesChar(metrics, canvas, .{ .down = .light, .left = .heavy, .right = .heavy }), + + // '┰' + 0x2530 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .light, .right = .light }), + // '┱' + 0x2531 => linesChar(metrics, canvas, .{ .right = .light, .left = .heavy, .down = .heavy }), + // '┲' + 0x2532 => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy, .down = .heavy }), + // '┳' + 0x2533 => linesChar(metrics, canvas, .{ .down = .heavy, .left = .heavy, .right = .heavy }), + // '┴' + 0x2534 => linesChar(metrics, canvas, .{ .up = .light, .left = .light, .right = .light }), + // '┵' + 0x2535 => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light, .up = .light }), + // '┶' + 0x2536 => linesChar(metrics, canvas, .{ .right = .heavy, .left = .light, .up = .light }), + // '┷' + 0x2537 => linesChar(metrics, canvas, .{ .up = .light, .left = .heavy, .right = .heavy }), + // '┸' + 0x2538 => linesChar(metrics, canvas, .{ .up = .heavy, .left = .light, .right = .light }), + // '┹' + 0x2539 => linesChar(metrics, canvas, .{ .right = .light, .left = .heavy, .up = .heavy }), + // '┺' + 0x253a => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy, .up = .heavy }), + // '┻' + 0x253b => linesChar(metrics, canvas, .{ .up = .heavy, .left = .heavy, .right = .heavy }), + // '┼' + 0x253c => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .light, .right = .light }), + // '┽' + 0x253d => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light, .up = .light, .down = .light }), + // '┾' + 0x253e => linesChar(metrics, canvas, .{ .right = .heavy, .left = .light, .up = .light, .down = .light }), + // '┿' + 0x253f => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .heavy, .right = .heavy }), + + // '╀' + 0x2540 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .light, .left = .light, .right = .light }), + // '╁' + 0x2541 => linesChar(metrics, canvas, .{ .down = .heavy, .up = .light, .left = .light, .right = .light }), + // '╂' + 0x2542 => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .light, .right = .light }), + // '╃' + 0x2543 => linesChar(metrics, canvas, .{ .left = .heavy, .up = .heavy, .right = .light, .down = .light }), + // '╄' + 0x2544 => linesChar(metrics, canvas, .{ .right = .heavy, .up = .heavy, .left = .light, .down = .light }), + // '╅' + 0x2545 => linesChar(metrics, canvas, .{ .left = .heavy, .down = .heavy, .right = .light, .up = .light }), + // '╆' + 0x2546 => linesChar(metrics, canvas, .{ .right = .heavy, .down = .heavy, .left = .light, .up = .light }), + // '╇' + 0x2547 => linesChar(metrics, canvas, .{ .down = .light, .up = .heavy, .left = .heavy, .right = .heavy }), + // '╈' + 0x2548 => linesChar(metrics, canvas, .{ .up = .light, .down = .heavy, .left = .heavy, .right = .heavy }), + // '╉' + 0x2549 => linesChar(metrics, canvas, .{ .right = .light, .left = .heavy, .up = .heavy, .down = .heavy }), + // '╊' + 0x254a => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy, .up = .heavy, .down = .heavy }), + // '╋' + 0x254b => linesChar(metrics, canvas, .{ .up = .heavy, .down = .heavy, .left = .heavy, .right = .heavy }), + // '╌' + 0x254c => dashHorizontal( + metrics, + canvas, + 2, + Thickness.light.height(metrics.box_thickness), + Thickness.light.height(metrics.box_thickness), + ), + // '╍' + 0x254d => dashHorizontal( + metrics, + canvas, + 2, + Thickness.heavy.height(metrics.box_thickness), + Thickness.heavy.height(metrics.box_thickness), + ), + // '╎' + 0x254e => dashVertical( + metrics, + canvas, + 2, + Thickness.light.height(metrics.box_thickness), + Thickness.heavy.height(metrics.box_thickness), + ), + // '╏' + 0x254f => dashVertical( + metrics, + canvas, + 2, + Thickness.heavy.height(metrics.box_thickness), + Thickness.heavy.height(metrics.box_thickness), + ), + + // '═' + 0x2550 => linesChar(metrics, canvas, .{ .left = .double, .right = .double }), + // '║' + 0x2551 => linesChar(metrics, canvas, .{ .up = .double, .down = .double }), + // '╒' + 0x2552 => linesChar(metrics, canvas, .{ .down = .light, .right = .double }), + // '╓' + 0x2553 => linesChar(metrics, canvas, .{ .down = .double, .right = .light }), + // '╔' + 0x2554 => linesChar(metrics, canvas, .{ .down = .double, .right = .double }), + // '╕' + 0x2555 => linesChar(metrics, canvas, .{ .down = .light, .left = .double }), + // '╖' + 0x2556 => linesChar(metrics, canvas, .{ .down = .double, .left = .light }), + // '╗' + 0x2557 => linesChar(metrics, canvas, .{ .down = .double, .left = .double }), + // '╘' + 0x2558 => linesChar(metrics, canvas, .{ .up = .light, .right = .double }), + // '╙' + 0x2559 => linesChar(metrics, canvas, .{ .up = .double, .right = .light }), + // '╚' + 0x255a => linesChar(metrics, canvas, .{ .up = .double, .right = .double }), + // '╛' + 0x255b => linesChar(metrics, canvas, .{ .up = .light, .left = .double }), + // '╜' + 0x255c => linesChar(metrics, canvas, .{ .up = .double, .left = .light }), + // '╝' + 0x255d => linesChar(metrics, canvas, .{ .up = .double, .left = .double }), + // '╞' + 0x255e => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .right = .double }), + // '╟' + 0x255f => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .right = .light }), + + // '╠' + 0x2560 => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .right = .double }), + // '╡' + 0x2561 => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .double }), + // '╢' + 0x2562 => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .light }), + // '╣' + 0x2563 => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .double }), + // '╤' + 0x2564 => linesChar(metrics, canvas, .{ .down = .light, .left = .double, .right = .double }), + // '╥' + 0x2565 => linesChar(metrics, canvas, .{ .down = .double, .left = .light, .right = .light }), + // '╦' + 0x2566 => linesChar(metrics, canvas, .{ .down = .double, .left = .double, .right = .double }), + // '╧' + 0x2567 => linesChar(metrics, canvas, .{ .up = .light, .left = .double, .right = .double }), + // '╨' + 0x2568 => linesChar(metrics, canvas, .{ .up = .double, .left = .light, .right = .light }), + // '╩' + 0x2569 => linesChar(metrics, canvas, .{ .up = .double, .left = .double, .right = .double }), + // '╪' + 0x256a => linesChar(metrics, canvas, .{ .up = .light, .down = .light, .left = .double, .right = .double }), + // '╫' + 0x256b => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .light, .right = .light }), + // '╬' + 0x256c => linesChar(metrics, canvas, .{ .up = .double, .down = .double, .left = .double, .right = .double }), + // '╭' + 0x256d => try arc(metrics, canvas, .br, .light), + // '╮' + 0x256e => try arc(metrics, canvas, .bl, .light), + // '╯' + 0x256f => try arc(metrics, canvas, .tl, .light), + + // '╰' + 0x2570 => try arc(metrics, canvas, .tr, .light), + // '╱' + 0x2571 => lightDiagonalUpperRightToLowerLeft(metrics, canvas), + // '╲' + 0x2572 => lightDiagonalUpperLeftToLowerRight(metrics, canvas), + // '╳' + 0x2573 => lightDiagonalCross(metrics, canvas), + // '╴' + 0x2574 => linesChar(metrics, canvas, .{ .left = .light }), + // '╵' + 0x2575 => linesChar(metrics, canvas, .{ .up = .light }), + // '╶' + 0x2576 => linesChar(metrics, canvas, .{ .right = .light }), + // '╷' + 0x2577 => linesChar(metrics, canvas, .{ .down = .light }), + // '╸' + 0x2578 => linesChar(metrics, canvas, .{ .left = .heavy }), + // '╹' + 0x2579 => linesChar(metrics, canvas, .{ .up = .heavy }), + // '╺' + 0x257a => linesChar(metrics, canvas, .{ .right = .heavy }), + // '╻' + 0x257b => linesChar(metrics, canvas, .{ .down = .heavy }), + // '╼' + 0x257c => linesChar(metrics, canvas, .{ .left = .light, .right = .heavy }), + // '╽' + 0x257d => linesChar(metrics, canvas, .{ .up = .light, .down = .heavy }), + // '╾' + 0x257e => linesChar(metrics, canvas, .{ .left = .heavy, .right = .light }), + // '╿' + 0x257f => linesChar(metrics, canvas, .{ .up = .heavy, .down = .light }), + + else => unreachable, + } +} + +pub fn linesChar( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + lines: Lines, +) void { + const light_px = Thickness.light.height(metrics.box_thickness); + const heavy_px = Thickness.heavy.height(metrics.box_thickness); + + // Top of light horizontal strokes + const h_light_top = (metrics.cell_height -| light_px) / 2; + // Bottom of light horizontal strokes + const h_light_bottom = h_light_top +| light_px; + + // Top of heavy horizontal strokes + const h_heavy_top = (metrics.cell_height -| heavy_px) / 2; + // Bottom of heavy horizontal strokes + const h_heavy_bottom = h_heavy_top +| heavy_px; + + // Top of the top doubled horizontal stroke (bottom is `h_light_top`) + const h_double_top = h_light_top -| light_px; + // Bottom of the bottom doubled horizontal stroke (top is `h_light_bottom`) + const h_double_bottom = h_light_bottom +| light_px; + + // Left of light vertical strokes + const v_light_left = (metrics.cell_width -| light_px) / 2; + // Right of light vertical strokes + const v_light_right = v_light_left +| light_px; + + // Left of heavy vertical strokes + const v_heavy_left = (metrics.cell_width -| heavy_px) / 2; + // Right of heavy vertical strokes + const v_heavy_right = v_heavy_left +| heavy_px; + + // Left of the left doubled vertical stroke (right is `v_light_left`) + const v_double_left = v_light_left -| light_px; + // Right of the right doubled vertical stroke (left is `v_light_right`) + const v_double_right = v_light_right +| light_px; + + // The bottom of the up line + const up_bottom = if (lines.left == .heavy or lines.right == .heavy) + h_heavy_bottom + else if (lines.left != lines.right or lines.down == lines.up) + if (lines.left == .double or lines.right == .double) + h_double_bottom + else + h_light_bottom + else if (lines.left == .none and lines.right == .none) + h_light_bottom + else + h_light_top; + + // The top of the down line + const down_top = if (lines.left == .heavy or lines.right == .heavy) + h_heavy_top + else if (lines.left != lines.right or lines.up == lines.down) + if (lines.left == .double or lines.right == .double) + h_double_top + else + h_light_top + else if (lines.left == .none and lines.right == .none) + h_light_top + else + h_light_bottom; + + // The right of the left line + const left_right = if (lines.up == .heavy or lines.down == .heavy) + v_heavy_right + else if (lines.up != lines.down or lines.left == lines.right) + if (lines.up == .double or lines.down == .double) + v_double_right + else + v_light_right + else if (lines.up == .none and lines.down == .none) + v_light_right + else + v_light_left; + + // The left of the right line + const right_left = if (lines.up == .heavy or lines.down == .heavy) + v_heavy_left + else if (lines.up != lines.down or lines.right == lines.left) + if (lines.up == .double or lines.down == .double) + v_double_left + else + v_light_left + else if (lines.up == .none and lines.down == .none) + v_light_left + else + v_light_right; + + switch (lines.up) { + .none => {}, + .light => canvas.box( + @intCast(v_light_left), + 0, + @intCast(v_light_right), + @intCast(up_bottom), + .on, + ), + .heavy => canvas.box( + @intCast(v_heavy_left), + 0, + @intCast(v_heavy_right), + @intCast(up_bottom), + .on, + ), + .double => { + const left_bottom = if (lines.left == .double) h_light_top else up_bottom; + const right_bottom = if (lines.right == .double) h_light_top else up_bottom; + + canvas.box( + @intCast(v_double_left), + 0, + @intCast(v_light_left), + @intCast(left_bottom), + .on, + ); + canvas.box( + @intCast(v_light_right), + 0, + @intCast(v_double_right), + @intCast(right_bottom), + .on, + ); + }, + } + + switch (lines.right) { + .none => {}, + .light => canvas.box( + @intCast(right_left), + @intCast(h_light_top), + @intCast(metrics.cell_width), + @intCast(h_light_bottom), + .on, + ), + .heavy => canvas.box( + @intCast(right_left), + @intCast(h_heavy_top), + @intCast(metrics.cell_width), + @intCast(h_heavy_bottom), + .on, + ), + .double => { + const top_left = if (lines.up == .double) v_light_right else right_left; + const bottom_left = if (lines.down == .double) v_light_right else right_left; + + canvas.box( + @intCast(top_left), + @intCast(h_double_top), + @intCast(metrics.cell_width), + @intCast(h_light_top), + .on, + ); + canvas.box( + @intCast(bottom_left), + @intCast(h_light_bottom), + @intCast(metrics.cell_width), + @intCast(h_double_bottom), + .on, + ); + }, + } + + switch (lines.down) { + .none => {}, + .light => canvas.box( + @intCast(v_light_left), + @intCast(down_top), + @intCast(v_light_right), + @intCast(metrics.cell_height), + .on, + ), + .heavy => canvas.box( + @intCast(v_heavy_left), + @intCast(down_top), + @intCast(v_heavy_right), + @intCast(metrics.cell_height), + .on, + ), + .double => { + const left_top = if (lines.left == .double) h_light_bottom else down_top; + const right_top = if (lines.right == .double) h_light_bottom else down_top; + + canvas.box( + @intCast(v_double_left), + @intCast(left_top), + @intCast(v_light_left), + @intCast(metrics.cell_height), + .on, + ); + canvas.box( + @intCast(v_light_right), + @intCast(right_top), + @intCast(v_double_right), + @intCast(metrics.cell_height), + .on, + ); + }, + } + + switch (lines.left) { + .none => {}, + .light => canvas.box( + 0, + @intCast(h_light_top), + @intCast(left_right), + @intCast(h_light_bottom), + .on, + ), + .heavy => canvas.box( + 0, + @intCast(h_heavy_top), + @intCast(left_right), + @intCast(h_heavy_bottom), + .on, + ), + .double => { + const top_right = if (lines.up == .double) v_light_left else left_right; + const bottom_right = if (lines.down == .double) v_light_left else left_right; + + canvas.box( + 0, + @intCast(h_double_top), + @intCast(top_right), + @intCast(h_light_top), + .on, + ); + canvas.box( + 0, + @intCast(h_light_bottom), + @intCast(bottom_right), + @intCast(h_double_bottom), + .on, + ); + }, + } +} + +pub fn lightDiagonalUpperRightToLowerLeft( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, +) void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + // We overshoot the corners by a tiny bit, but we need to + // maintain the correct slope, so we calculate that here. + const slope_x: f64 = @min(1.0, float_width / float_height); + const slope_y: f64 = @min(1.0, float_height / float_width); + + canvas.line(.{ + .p0 = .{ + .x = float_width + 0.5 * slope_x, + .y = -0.5 * slope_y, + }, + .p1 = .{ + .x = -0.5 * slope_x, + .y = float_height + 0.5 * slope_y, + }, + }, @floatFromInt(Thickness.light.height(metrics.box_thickness)), .on) catch {}; +} + +pub fn lightDiagonalUpperLeftToLowerRight( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, +) void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + // We overshoot the corners by a tiny bit, but we need to + // maintain the correct slope, so we calculate that here. + const slope_x: f64 = @min(1.0, float_width / float_height); + const slope_y: f64 = @min(1.0, float_height / float_width); + + canvas.line(.{ + .p0 = .{ + .x = -0.5 * slope_x, + .y = -0.5 * slope_y, + }, + .p1 = .{ + .x = float_width + 0.5 * slope_x, + .y = float_height + 0.5 * slope_y, + }, + }, @floatFromInt(Thickness.light.height(metrics.box_thickness)), .on) catch {}; +} + +pub fn lightDiagonalCross( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, +) void { + lightDiagonalUpperRightToLowerLeft(metrics, canvas); + lightDiagonalUpperLeftToLowerRight(metrics, canvas); +} + +pub fn arc( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime corner: Corner, + comptime thickness: Thickness, +) !void { + const thick_px = thickness.height(metrics.box_thickness); + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const float_thick: f64 = @floatFromInt(thick_px); + const center_x: f64 = @as(f64, @floatFromInt((metrics.cell_width -| thick_px) / 2)) + float_thick / 2; + const center_y: f64 = @as(f64, @floatFromInt((metrics.cell_height -| thick_px) / 2)) + float_thick / 2; + + const r = @min(float_width, float_height) / 2; + + // Fraction away from the center to place the middle control points, + const s: f64 = 0.25; + + var path = canvas.staticPath(4); + + switch (corner) { + .tl => { + path.moveTo(center_x, 0); + path.lineTo(center_x, center_y - r); + path.curveTo( + center_x, + center_y - s * r, + center_x - s * r, + center_y, + center_x - r, + center_y, + ); + path.lineTo(0, center_y); + }, + .tr => { + path.moveTo(center_x, 0); + path.lineTo(center_x, center_y - r); + path.curveTo( + center_x, + center_y - s * r, + center_x + s * r, + center_y, + center_x + r, + center_y, + ); + path.lineTo(float_width, center_y); + }, + .bl => { + path.moveTo(center_x, float_height); + path.lineTo(center_x, center_y + r); + path.curveTo( + center_x, + center_y + s * r, + center_x - s * r, + center_y, + center_x - r, + center_y, + ); + path.lineTo(0, center_y); + }, + .br => { + path.moveTo(center_x, float_height); + path.lineTo(center_x, center_y + r); + path.curveTo( + center_x, + center_y + s * r, + center_x + s * r, + center_y, + center_x + r, + center_y, + ); + path.lineTo(float_width, center_y); + }, + } + + try canvas.strokePath( + path.wrapped_path, + .{ + .line_cap_mode = .butt, + .line_width = float_thick, + }, + .on, + ); +} + +fn dashHorizontal( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + count: u8, + thick_px: u32, + desired_gap: u32, +) void { + assert(count >= 2 and count <= 4); + + // +------------+ + // | | + // | | + // | | + // | | + // | -- -- -- | + // | | + // | | + // | | + // | | + // +------------+ + // Our dashed line should be made such that when tiled horizontally + // it creates one consistent line with no uneven gap or segment sizes. + // In order to make sure this is the case, we should have half-sized + // gaps on the left and right so that it is centered properly. + + // For N dashes, there are N - 1 gaps between them, but we also have + // half-sized gaps on either side, adding up to N total gaps. + const gap_count = count; + + // We need at least 1 pixel for each gap and each dash, if we don't + // have that then we can't draw our dashed line correctly so we just + // draw a solid line and return. + if (metrics.cell_width < count + gap_count) { + hlineMiddle(metrics, canvas, .light); + return; + } + + // We never want the gaps to take up more than 50% of the space, + // because if they do the dashes are too small and look wrong. + const gap_width: i32 = @intCast(@min(desired_gap, metrics.cell_width / (2 * count))); + const total_gap_width: i32 = gap_count * gap_width; + const total_dash_width: i32 = @as(i32, @intCast(metrics.cell_width)) - total_gap_width; + const dash_width: i32 = @divFloor(total_dash_width, count); + const remaining: i32 = @mod(total_dash_width, count); + + assert(dash_width * count + gap_width * gap_count + remaining == metrics.cell_width); + + // Our dashes should be centered vertically. + const y: i32 = @intCast((metrics.cell_height -| thick_px) / 2); + + // We start at half a gap from the left edge, in order to center + // our dashes properly. + var x: i32 = @divFloor(gap_width, 2); + + // We'll distribute the extra space in to dash widths, 1px at a + // time. We prefer this to making gaps larger since that is much + // more visually obvious. + var extra: i32 = remaining; + + for (0..count) |_| { + var x1 = x + dash_width; + // We distribute left-over size in to dash widths, + // since it's less obvious there than in the gaps. + if (extra > 0) { + extra -= 1; + x1 += 1; + } + hline(canvas, x, x1, y, thick_px); + // Advance by the width of the dash we drew and the width + // of a gap to get the the start of the next dash. + x = x1 + gap_width; + } +} + +fn dashVertical( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime count: u8, + thick_px: u32, + desired_gap: u32, +) void { + assert(count >= 2 and count <= 4); + + // +-----------+ + // | | | + // | | | + // | | + // | | | + // | | | + // | | + // | | | + // | | | + // | | + // +-----------+ + // Our dashed line should be made such that when tiled vertically it + // it creates one consistent line with no uneven gap or segment sizes. + // In order to make sure this is the case, we should have an extra gap + // gap at the bottom. + // + // A single full-sized extra gap is preferred to two half-sized ones for + // vertical to allow better joining to solid characters without creating + // visible half-sized gaps. Unlike horizontal, centering is a lot less + // important, visually. + + // Because of the extra gap at the bottom, there are as many gaps as + // there are dashes. + const gap_count = count; + + // We need at least 1 pixel for each gap and each dash, if we don't + // have that then we can't draw our dashed line correctly so we just + // draw a solid line and return. + if (metrics.cell_height < count + gap_count) { + vlineMiddle(metrics, canvas, .light); + return; + } + + // We never want the gaps to take up more than 50% of the space, + // because if they do the dashes are too small and look wrong. + const gap_height: i32 = @intCast(@min(desired_gap, metrics.cell_height / (2 * count))); + const total_gap_height: i32 = gap_count * gap_height; + const total_dash_height: i32 = @as(i32, @intCast(metrics.cell_height)) - total_gap_height; + const dash_height: i32 = @divFloor(total_dash_height, count); + const remaining: i32 = @mod(total_dash_height, count); + + assert(dash_height * count + gap_height * gap_count + remaining == metrics.cell_height); + + // Our dashes should be centered horizontally. + const x: i32 = @intCast((metrics.cell_width -| thick_px) / 2); + + // We start at the top of the cell. + var y: i32 = 0; + + // We'll distribute the extra space in to dash heights, 1px at a + // time. We prefer this to making gaps larger since that is much + // more visually obvious. + var extra: i32 = remaining; + + inline for (0..count) |_| { + var y1 = y + dash_height; + // We distribute left-over size in to dash widths, + // since it's less obvious there than in the gaps. + if (extra > 0) { + extra -= 1; + y1 += 1; + } + vline(canvas, y, y1, x, thick_px); + // Advance by the height of the dash we drew and the height + // of a gap to get the the start of the next dash. + y = y1 + gap_height; + } +} diff --git a/src/font/sprite/draw/braille.zig b/src/font/sprite/draw/braille.zig new file mode 100644 index 000000000..c756ff369 --- /dev/null +++ b/src/font/sprite/draw/braille.zig @@ -0,0 +1,148 @@ +//! Braille Patterns | U+2800...U+28FF +//! https://en.wikipedia.org/wiki/Braille_Patterns +//! +//! (6 dot patterns) +//! ⠀ ⠁ ⠂ ⠃ ⠄ ⠅ ⠆ ⠇ ⠈ ⠉ ⠊ ⠋ ⠌ ⠍ ⠎ ⠏ +//! ⠐ ⠑ ⠒ ⠓ ⠔ ⠕ ⠖ ⠗ ⠘ ⠙ ⠚ ⠛ ⠜ ⠝ ⠞ ⠟ +//! ⠠ ⠡ ⠢ ⠣ ⠤ ⠥ ⠦ ⠧ ⠨ ⠩ ⠪ ⠫ ⠬ ⠭ ⠮ ⠯ +//! ⠰ ⠱ ⠲ ⠳ ⠴ ⠵ ⠶ ⠷ ⠸ ⠹ ⠺ ⠻ ⠼ ⠽ ⠾ ⠿ +//! +//! (8 dot patterns) +//! ⡀ ⡁ ⡂ ⡃ ⡄ ⡅ ⡆ ⡇ ⡈ ⡉ ⡊ ⡋ ⡌ ⡍ ⡎ ⡏ +//! ⡐ ⡑ ⡒ ⡓ ⡔ ⡕ ⡖ ⡗ ⡘ ⡙ ⡚ ⡛ ⡜ ⡝ ⡞ ⡟ +//! ⡠ ⡡ ⡢ ⡣ ⡤ ⡥ ⡦ ⡧ ⡨ ⡩ ⡪ ⡫ ⡬ ⡭ ⡮ ⡯ +//! ⡰ ⡱ ⡲ ⡳ ⡴ ⡵ ⡶ ⡷ ⡸ ⡹ ⡺ ⡻ ⡼ ⡽ ⡾ ⡿ +//! ⢀ ⢁ ⢂ ⢃ ⢄ ⢅ ⢆ ⢇ ⢈ ⢉ ⢊ ⢋ ⢌ ⢍ ⢎ ⢏ +//! ⢐ ⢑ ⢒ ⢓ ⢔ ⢕ ⢖ ⢗ ⢘ ⢙ ⢚ ⢛ ⢜ ⢝ ⢞ ⢟ +//! ⢠ ⢡ ⢢ ⢣ ⢤ ⢥ ⢦ ⢧ ⢨ ⢩ ⢪ ⢫ ⢬ ⢭ ⢮ ⢯ +//! ⢰ ⢱ ⢲ ⢳ ⢴ ⢵ ⢶ ⢷ ⢸ ⢹ ⢺ ⢻ ⢼ ⢽ ⢾ ⢿ +//! ⣀ ⣁ ⣂ ⣃ ⣄ ⣅ ⣆ ⣇ ⣈ ⣉ ⣊ ⣋ ⣌ ⣍ ⣎ ⣏ +//! ⣐ ⣑ ⣒ ⣓ ⣔ ⣕ ⣖ ⣗ ⣘ ⣙ ⣚ ⣛ ⣜ ⣝ ⣞ ⣟ +//! ⣠ ⣡ ⣢ ⣣ ⣤ ⣥ ⣦ ⣧ ⣨ ⣩ ⣪ ⣫ ⣬ ⣭ ⣮ ⣯ +//! ⣰ ⣱ ⣲ ⣳ ⣴ ⣵ ⣶ ⣷ ⣸ ⣹ ⣺ ⣻ ⣼ ⣽ ⣾ ⣿ +//! + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +const font = @import("../../main.zig"); + +/// A braille pattern. +/// +/// Mnemonic: +/// [t]op - . . +/// [u]pper - . . +/// [l]ower - . . +/// [b]ottom - . . +/// | | +/// [l]eft, [r]ight +/// +/// Struct layout matches bit patterns of unicode codepoints. +const Pattern = packed struct(u8) { + tl: bool, + ul: bool, + ll: bool, + tr: bool, + ur: bool, + lr: bool, + bl: bool, + br: bool, + + fn from(cp: u32) Pattern { + return @bitCast(@as(u8, @truncate(cp))); + } +}; + +pub fn draw2800_28FF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = metrics; + + var w: i32 = @intCast(@min(width / 4, height / 8)); + var x_spacing: i32 = @intCast(width / 4); + var y_spacing: i32 = @intCast(height / 8); + var x_margin: i32 = @divFloor(x_spacing, 2); + var y_margin: i32 = @divFloor(y_spacing, 2); + + var x_px_left: i32 = + @as(i32, @intCast(width)) - 2 * x_margin - x_spacing - 2 * w; + + var y_px_left: i32 = + @as(i32, @intCast(height)) - 2 * y_margin - 3 * y_spacing - 4 * w; + + // First, try hard to ensure the DOT width is non-zero + if (x_px_left >= 2 and y_px_left >= 4 and w == 0) { + w += 1; + x_px_left -= 2; + y_px_left -= 4; + } + + // Second, prefer a non-zero margin + if (x_px_left >= 2 and x_margin == 0) { + x_margin = 1; + x_px_left -= 2; + } + if (y_px_left >= 2 and y_margin == 0) { + y_margin = 1; + y_px_left -= 2; + } + + // Third, increase spacing + if (x_px_left >= 1) { + x_spacing += 1; + x_px_left -= 1; + } + if (y_px_left >= 3) { + y_spacing += 1; + y_px_left -= 3; + } + + // Fourth, margins (“spacing”, but on the sides) + if (x_px_left >= 2) { + x_margin += 1; + x_px_left -= 2; + } + if (y_px_left >= 2) { + y_margin += 1; + y_px_left -= 2; + } + + // Last - increase dot width + if (x_px_left >= 2 and y_px_left >= 4) { + w += 1; + x_px_left -= 2; + y_px_left -= 4; + } + + assert(x_px_left <= 1 or y_px_left <= 1); + assert(2 * x_margin + 2 * w + x_spacing <= width); + assert(2 * y_margin + 4 * w + 3 * y_spacing <= height); + + const x = [2]i32{ x_margin, x_margin + w + x_spacing }; + const y = y: { + var y: [4]i32 = undefined; + y[0] = y_margin; + y[1] = y[0] + w + y_spacing; + y[2] = y[1] + w + y_spacing; + y[3] = y[2] + w + y_spacing; + break :y y; + }; + + assert(cp >= 0x2800); + assert(cp <= 0x28ff); + const p: Pattern = .from(cp); + + if (p.tl) canvas.box(x[0], y[0], x[0] + w, y[0] + w, .on); + if (p.ul) canvas.box(x[0], y[1], x[0] + w, y[1] + w, .on); + if (p.ll) canvas.box(x[0], y[2], x[0] + w, y[2] + w, .on); + if (p.bl) canvas.box(x[0], y[3], x[0] + w, y[3] + w, .on); + if (p.tr) canvas.box(x[1], y[0], x[1] + w, y[0] + w, .on); + if (p.ur) canvas.box(x[1], y[1], x[1] + w, y[1] + w, .on); + if (p.lr) canvas.box(x[1], y[2], x[1] + w, y[2] + w, .on); + if (p.br) canvas.box(x[1], y[3], x[1] + w, y[3] + w, .on); +} diff --git a/src/font/sprite/draw/branch.zig b/src/font/sprite/draw/branch.zig new file mode 100644 index 000000000..ac7220390 --- /dev/null +++ b/src/font/sprite/draw/branch.zig @@ -0,0 +1,505 @@ +//! Branch Drawing Characters | U+F5D0...U+F60D +//! +//! Branch drawing character set, used for drawing git-like +//! graphs in the terminal. Originally implemented in Kitty. +//! Ref: +//! - https://github.com/kovidgoyal/kitty/pull/7681 +//! - https://github.com/kovidgoyal/kitty/pull/7805 +//! NOTE: Kitty is GPL licensed, and its code was not referenced +//! for these characters, only the loose specification of +//! the character set in the pull request descriptions. +//! +//!                 +//!                 +//!                 +//!               +//! + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Shade = common.Shade; +const Edge = common.Edge; +const hlineMiddle = common.hlineMiddle; +const vlineMiddle = common.vlineMiddle; + +const arc = @import("box.zig").arc; + +const font = @import("../../main.zig"); + +/// Specification of a branch drawing node, which consists of a +/// circle which is either empty or filled, and lines connecting +/// optionally between the circle and each of the 4 edges. +const BranchNode = packed struct(u5) { + up: bool = false, + right: bool = false, + down: bool = false, + left: bool = false, + filled: bool = false, +}; + +pub fn drawF5D0_F60D( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '' + 0x0f5d0 => hlineMiddle(metrics, canvas, .light), + // '' + 0x0f5d1 => vlineMiddle(metrics, canvas, .light), + // '' + 0x0f5d2 => fadingLine(metrics, canvas, .right, .light), + // '' + 0x0f5d3 => fadingLine(metrics, canvas, .left, .light), + // '' + 0x0f5d4 => fadingLine(metrics, canvas, .bottom, .light), + // '' + 0x0f5d5 => fadingLine(metrics, canvas, .top, .light), + // '' + 0x0f5d6 => try arc(metrics, canvas, .br, .light), + // '' + 0x0f5d7 => try arc(metrics, canvas, .bl, .light), + // '' + 0x0f5d8 => try arc(metrics, canvas, .tr, .light), + // '' + 0x0f5d9 => try arc(metrics, canvas, .tl, .light), + // '' + 0x0f5da => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tr, .light); + }, + // '' + 0x0f5db => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5dc => { + try arc(metrics, canvas, .tr, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5dd => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tl, .light); + }, + // '' + 0x0f5de => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .bl, .light); + }, + // '' + 0x0f5df => { + try arc(metrics, canvas, .tl, .light); + try arc(metrics, canvas, .bl, .light); + }, + + // '' + 0x0f5e0 => { + try arc(metrics, canvas, .bl, .light); + hlineMiddle(metrics, canvas, .light); + }, + // '' + 0x0f5e1 => { + try arc(metrics, canvas, .br, .light); + hlineMiddle(metrics, canvas, .light); + }, + // '' + 0x0f5e2 => { + try arc(metrics, canvas, .br, .light); + try arc(metrics, canvas, .bl, .light); + }, + // '' + 0x0f5e3 => { + try arc(metrics, canvas, .tl, .light); + hlineMiddle(metrics, canvas, .light); + }, + // '' + 0x0f5e4 => { + try arc(metrics, canvas, .tr, .light); + hlineMiddle(metrics, canvas, .light); + }, + // '' + 0x0f5e5 => { + try arc(metrics, canvas, .tr, .light); + try arc(metrics, canvas, .tl, .light); + }, + // '' + 0x0f5e6 => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tl, .light); + try arc(metrics, canvas, .tr, .light); + }, + // '' + 0x0f5e7 => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .bl, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5e8 => { + hlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .bl, .light); + try arc(metrics, canvas, .tl, .light); + }, + // '' + 0x0f5e9 => { + hlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tr, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5ea => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tl, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5eb => { + vlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tr, .light); + try arc(metrics, canvas, .bl, .light); + }, + // '' + 0x0f5ec => { + hlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tl, .light); + try arc(metrics, canvas, .br, .light); + }, + // '' + 0x0f5ed => { + hlineMiddle(metrics, canvas, .light); + try arc(metrics, canvas, .tr, .light); + try arc(metrics, canvas, .bl, .light); + }, + // '' + 0x0f5ee => branchNode(metrics, canvas, .{ .filled = true }, .light), + // '' + 0x0f5ef => branchNode(metrics, canvas, .{}, .light), + + // '' + 0x0f5f0 => branchNode(metrics, canvas, .{ + .right = true, + .filled = true, + }, .light), + // '' + 0x0f5f1 => branchNode(metrics, canvas, .{ + .right = true, + }, .light), + // '' + 0x0f5f2 => branchNode(metrics, canvas, .{ + .left = true, + .filled = true, + }, .light), + // '' + 0x0f5f3 => branchNode(metrics, canvas, .{ + .left = true, + }, .light), + // '' + 0x0f5f4 => branchNode(metrics, canvas, .{ + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f5f5 => branchNode(metrics, canvas, .{ + .left = true, + .right = true, + }, .light), + // '' + 0x0f5f6 => branchNode(metrics, canvas, .{ + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5f7 => branchNode(metrics, canvas, .{ + .down = true, + }, .light), + // '' + 0x0f5f8 => branchNode(metrics, canvas, .{ + .up = true, + .filled = true, + }, .light), + // '' + 0x0f5f9 => branchNode(metrics, canvas, .{ + .up = true, + }, .light), + // '' + 0x0f5fa => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5fb => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + }, .light), + // '' + 0x0f5fc => branchNode(metrics, canvas, .{ + .right = true, + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5fd => branchNode(metrics, canvas, .{ + .right = true, + .down = true, + }, .light), + // '' + 0x0f5fe => branchNode(metrics, canvas, .{ + .left = true, + .down = true, + .filled = true, + }, .light), + // '' + 0x0f5ff => branchNode(metrics, canvas, .{ + .left = true, + .down = true, + }, .light), + + // '' + 0x0f600 => branchNode(metrics, canvas, .{ + .up = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f601 => branchNode(metrics, canvas, .{ + .up = true, + .right = true, + }, .light), + // '' + 0x0f602 => branchNode(metrics, canvas, .{ + .up = true, + .left = true, + .filled = true, + }, .light), + // '' + 0x0f603 => branchNode(metrics, canvas, .{ + .up = true, + .left = true, + }, .light), + // '' + 0x0f604 => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f605 => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .right = true, + }, .light), + // '' + 0x0f606 => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .left = true, + .filled = true, + }, .light), + // '' + 0x0f607 => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .left = true, + }, .light), + // '' + 0x0f608 => branchNode(metrics, canvas, .{ + .down = true, + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f609 => branchNode(metrics, canvas, .{ + .down = true, + .left = true, + .right = true, + }, .light), + // '' + 0x0f60a => branchNode(metrics, canvas, .{ + .up = true, + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f60b => branchNode(metrics, canvas, .{ + .up = true, + .left = true, + .right = true, + }, .light), + // '' + 0x0f60c => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .left = true, + .right = true, + .filled = true, + }, .light), + // '' + 0x0f60d => branchNode(metrics, canvas, .{ + .up = true, + .down = true, + .left = true, + .right = true, + }, .light), + + else => unreachable, + } +} + +fn branchNode( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + node: BranchNode, + comptime thickness: Thickness, +) void { + const thick_px = thickness.height(metrics.box_thickness); + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const float_thick: f64 = @floatFromInt(thick_px); + + // Top of horizontal strokes + const h_top = (metrics.cell_height -| thick_px) / 2; + // Bottom of horizontal strokes + const h_bottom = h_top +| thick_px; + // Left of vertical strokes + const v_left = (metrics.cell_width -| thick_px) / 2; + // Right of vertical strokes + const v_right = v_left +| thick_px; + + // We calculate the center of the circle this way + // to ensure it aligns with box drawing characters + // since the lines are sometimes off center to + // make sure they aren't split between pixels. + const cx: f64 = @as(f64, @floatFromInt(v_left)) + float_thick / 2; + const cy: f64 = @as(f64, @floatFromInt(h_top)) + float_thick / 2; + // The radius needs to be the smallest distance from the center to an edge. + const r: f64 = @min( + @min(cx, cy), + @min(float_width - cx, float_height - cy), + ); + + var ctx = canvas.getContext(); + defer ctx.deinit(); + ctx.setSource(.{ .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, + } }); + ctx.setLineWidth(float_thick); + + // These @intFromFloat casts shouldn't ever fail since r can never + // be greater than cx or cy, so when subtracting it from them the + // result can never be negative. + if (node.up) canvas.box( + @intCast(v_left), + 0, + @intCast(v_right), + @intFromFloat(@ceil(cy - r + float_thick / 2)), + .on, + ); + if (node.right) canvas.box( + @intFromFloat(@floor(cx + r - float_thick / 2)), + @intCast(h_top), + @intCast(metrics.cell_width), + @intCast(h_bottom), + .on, + ); + if (node.down) canvas.box( + @intCast(v_left), + @intFromFloat(@floor(cy + r - float_thick / 2)), + @intCast(v_right), + @intCast(metrics.cell_height), + .on, + ); + if (node.left) canvas.box( + 0, + @intCast(h_top), + @intFromFloat(@ceil(cx - r + float_thick / 2)), + @intCast(h_bottom), + .on, + ); + + if (node.filled) { + ctx.arc(cx, cy, r, 0, std.math.pi * 2) catch return; + ctx.closePath() catch return; + ctx.fill() catch return; + } else { + ctx.arc(cx, cy, r - float_thick / 2, 0, std.math.pi * 2) catch return; + ctx.closePath() catch return; + ctx.stroke() catch return; + } +} + +fn fadingLine( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime to: Edge, + comptime thickness: Thickness, +) void { + const thick_px = thickness.height(metrics.box_thickness); + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + // Top of horizontal strokes + const h_top = (metrics.cell_height -| thick_px) / 2; + // Bottom of horizontal strokes + const h_bottom = h_top +| thick_px; + // Left of vertical strokes + const v_left = (metrics.cell_width -| thick_px) / 2; + // Right of vertical strokes + const v_right = v_left +| thick_px; + + // If we're fading to the top or left, we start with 0.0 + // and increment up as we progress, otherwise we start + // at 255.0 and increment down (negative). + var color: f64 = switch (to) { + .top, .left => 0.0, + .bottom, .right => 255.0, + }; + const inc: f64 = 255.0 / switch (to) { + .top => float_height, + .bottom => -float_height, + .left => float_width, + .right => -float_width, + }; + + switch (to) { + .top, .bottom => { + for (0..metrics.cell_height) |y| { + for (v_left..v_right) |x| { + canvas.pixel( + @intCast(x), + @intCast(y), + @enumFromInt(@as(u8, @intFromFloat(@round(color)))), + ); + } + color += inc; + } + }, + .left, .right => { + for (0..metrics.cell_width) |x| { + for (h_top..h_bottom) |y| { + canvas.pixel( + @intCast(x), + @intCast(y), + @enumFromInt(@as(u8, @intFromFloat(@round(color)))), + ); + } + color += inc; + } + }, + } +} diff --git a/src/font/sprite/draw/common.zig b/src/font/sprite/draw/common.zig new file mode 100644 index 000000000..67b9dc778 --- /dev/null +++ b/src/font/sprite/draw/common.zig @@ -0,0 +1,378 @@ +//! This file contains a set of useful helper functions +//! and types for drawing our sprite font glyphs. These +//! are generally applicable to multiple sets of glyphs +//! rather than being single-use. + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; + +const z2d = @import("z2d"); + +const font = @import("../../main.zig"); +const Sprite = @import("../../sprite.zig").Sprite; + +const log = std.log.scoped(.sprite_font); + +// Utility names for common fractions +pub const one_eighth: f64 = 0.125; +pub const one_quarter: f64 = 0.25; +pub const one_third: f64 = (1.0 / 3.0); +pub const three_eighths: f64 = 0.375; +pub const half: f64 = 0.5; +pub const five_eighths: f64 = 0.625; +pub const two_thirds: f64 = (2.0 / 3.0); +pub const three_quarters: f64 = 0.75; +pub const seven_eighths: f64 = 0.875; + +/// The thickness of a line. +pub const Thickness = enum { + super_light, + light, + heavy, + + /// Calculate the real height of a line based on its + /// thickness and a base thickness value. The base + /// thickness value is expected to be in pixels. + pub fn height(self: Thickness, base: u32) u32 { + return switch (self) { + .super_light => @max(base / 2, 1), + .light => base, + .heavy => base * 2, + }; + } +}; + +/// Shades. +pub const Shade = enum(u8) { + off = 0x00, + light = 0x40, + medium = 0x80, + dark = 0xc0, + on = 0xff, + + _, +}; + +/// Applicable to any set of glyphs with features +/// that may be present or not in each quadrant. +pub const Quads = packed struct(u4) { + tl: bool = false, + tr: bool = false, + bl: bool = false, + br: bool = false, +}; + +/// A corner of a cell. +pub const Corner = enum(u2) { + tl, + tr, + bl, + br, +}; + +/// An edge of a cell. +pub const Edge = enum(u2) { + top, + left, + bottom, + right, +}; + +/// Alignment of a figure within a cell. +pub const Alignment = struct { + horizontal: enum { + left, + right, + center, + } = .center, + + vertical: enum { + top, + bottom, + middle, + } = .middle, + + pub const upper: Alignment = .{ .vertical = .top }; + pub const lower: Alignment = .{ .vertical = .bottom }; + pub const left: Alignment = .{ .horizontal = .left }; + pub const right: Alignment = .{ .horizontal = .right }; + + pub const upper_left: Alignment = .{ .vertical = .top, .horizontal = .left }; + pub const upper_right: Alignment = .{ .vertical = .top, .horizontal = .right }; + pub const lower_left: Alignment = .{ .vertical = .bottom, .horizontal = .left }; + pub const lower_right: Alignment = .{ .vertical = .bottom, .horizontal = .right }; + + pub const center: Alignment = .{}; + + pub const upper_center = upper; + pub const lower_center = lower; + pub const middle_left = left; + pub const middle_right = right; + pub const middle_center: Alignment = center; + + pub const top = upper; + pub const bottom = lower; + pub const center_top = top; + pub const center_bottom = bottom; + + pub const top_left = upper_left; + pub const top_right = upper_right; + pub const bottom_left = lower_left; + pub const bottom_right = lower_right; +}; + +/// A value that indicates some fraction across +/// the cell either horizontally or vertically. +/// +/// This has some redundant names in it so that you can +/// use whichever one feels most semantically appropriate. +pub const Fraction = enum { + // Names for the min edge + start, + left, + top, + zero, + + // Names based on eighths + eighth, + one_eighth, + two_eighths, + three_eighths, + four_eighths, + five_eighths, + six_eighths, + seven_eighths, + + // Names based on quarters + quarter, + one_quarter, + two_quarters, + three_quarters, + + // Names based on thirds + third, + one_third, + two_thirds, + + // Names based on halves + half, + one_half, + + // Alternative names for 1/2 + center, + middle, + + // Names for the max edge + end, + right, + bottom, + one, + full, + + /// This can be indexed to get the fraction for `i/8`. + pub const eighths: [9]Fraction = .{ + .zero, + .one_eighth, + .two_eighths, + .three_eighths, + .four_eighths, + .five_eighths, + .six_eighths, + .seven_eighths, + .one, + }; + + /// This can be indexed to get the fraction for `i/4`. + pub const quarters: [5]Fraction = .{ + .zero, + .one_quarter, + .two_quarters, + .three_quarters, + .one, + }; + + /// This can be indexed to get the fraction for `i/3`. + pub const thirds: [4]Fraction = .{ + .zero, + .one_third, + .two_thirds, + .one, + }; + + /// This can be indexed to get the fraction for `i/2`. + pub const halves: [3]Fraction = .{ + .zero, + .one_half, + .one, + }; + + /// Get the x position for this fraction across a particular + /// size (width or height), assuming it will be used as the + /// min (left/top) coordinate for a block. + /// + /// `size` can be any integer type, since it will be coerced + pub inline fn min(self: Fraction, size: anytype) i32 { + const s: f64 = @as(f64, @floatFromInt(size)); + // For min coordinates, we want to align with the complementary + // fraction taken from the end, this ensures that rounding evens + // out, so that for example, if `size` is `7`, and we're looking + // at the `half` line, `size - round((1 - 0.5) * size)` => `3`; + // whereas the max coordinate directly rounds, which means that + // both `start` -> `half` and `half` -> `end` will be 4px, from + // `0` -> `4` and `3` -> `7`. + return @intFromFloat(s - @round((1.0 - self.fraction()) * s)); + } + + /// Get the x position for this fraction across a particular + /// size (width or height), assuming it will be used as the + /// max (right/bottom) coordinate for a block. + /// + /// `size` can be any integer type, since it will be coerced + /// with `@floatFromInt`. + pub inline fn max(self: Fraction, size: anytype) i32 { + const s: f64 = @as(f64, @floatFromInt(size)); + // See explanation of why these are different in `min`. + return @intFromFloat(@round(self.fraction() * s)); + } + + /// Get this fraction across a particular size (width/height). + /// If you need an integer, use `min` or `max` instead, since + /// they contain special logic for consistent alignment. This + /// is for when you're drawing with paths and don't care about + /// pixel alignment. + /// + /// `size` can be any integer type, since it will be coerced + /// with `@floatFromInt`. + pub inline fn float(self: Fraction, size: anytype) f64 { + return self.fraction() * @as(f64, @floatFromInt(size)); + } + + /// Get a float for the fraction this represents. + pub inline fn fraction(self: Fraction) f64 { + return switch (self) { + .start, + .left, + .top, + .zero, + => 0.0, + + .eighth, + .one_eighth, + => 0.125, + + .quarter, + .one_quarter, + .two_eighths, + => 0.25, + + .third, + .one_third, + => 1.0 / 3.0, + + .three_eighths, + => 0.375, + + .half, + .one_half, + .two_quarters, + .four_eighths, + .center, + .middle, + => 0.5, + + .five_eighths, + => 0.625, + + .two_thirds, + => 2.0 / 3.0, + + .three_quarters, + .six_eighths, + => 0.75, + + .seven_eighths, + => 0.875, + + .end, + .right, + .bottom, + .one, + .full, + => 1.0, + }; + } +}; + +/// Fill a section of the cell, specified by a +/// horizontal and vertical pair of fraction lines. +pub fn fill( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + x0: Fraction, + x1: Fraction, + y0: Fraction, + y1: Fraction, +) void { + canvas.box( + x0.min(metrics.cell_width), + y0.min(metrics.cell_height), + x1.max(metrics.cell_width), + y1.max(metrics.cell_height), + .on, + ); +} + +/// Centered vertical line of the provided thickness. +pub fn vlineMiddle( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + thickness: Thickness, +) void { + const thick_px = thickness.height(metrics.box_thickness); + vline( + canvas, + 0, + @intCast(metrics.cell_height), + @intCast((metrics.cell_width -| thick_px) / 2), + thick_px, + ); +} + +/// Centered horizontal line of the provided thickness. +pub fn hlineMiddle( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + thickness: Thickness, +) void { + const thick_px = thickness.height(metrics.box_thickness); + hline( + canvas, + 0, + @intCast(metrics.cell_width), + @intCast((metrics.cell_height -| thick_px) / 2), + thick_px, + ); +} + +/// Vertical line with the left edge at `x`, between `y1` and `y2`. +pub fn vline( + canvas: *font.sprite.Canvas, + y1: i32, + y2: i32, + x: i32, + thickness_px: u32, +) void { + canvas.box(x, y1, x + @as(i32, @intCast(thickness_px)), y2, .on); +} + +/// Horizontal line with the top edge at `y`, between `x1` and `x2`. +pub fn hline( + canvas: *font.sprite.Canvas, + x1: i32, + x2: i32, + y: i32, + thickness_px: u32, +) void { + canvas.box(x1, y, x2, y + @as(i32, @intCast(thickness_px)), .on); +} diff --git a/src/font/sprite/draw/geometric_shapes.zig b/src/font/sprite/draw/geometric_shapes.zig new file mode 100644 index 000000000..d95a4fd2f --- /dev/null +++ b/src/font/sprite/draw/geometric_shapes.zig @@ -0,0 +1,200 @@ +//! Geometric Shapes | U+25A0...U+25FF +//! https://en.wikipedia.org/wiki/Geometric_Shapes_(Unicode_block) +//! +//! ■ □ ▢ ▣ ▤ ▥ ▦ ▧ ▨ ▩ ▪ ▫ ▬ ▭ ▮ ▯ +//! ▰ ▱ ▲ △ ▴ ▵ ▶ ▷ ▸ ▹ ► ▻ ▼ ▽ ▾ ▿ +//! ◀ ◁ ◂ ◃ ◄ ◅ ◆ ◇ ◈ ◉ ◊ ○ ◌ ◍ ◎ ● +//! ◐ ◑ ◒ ◓ ◔ ◕ ◖ ◗ ◘ ◙ ◚ ◛ ◜ ◝ ◞ ◟ +//! ◠ ◡ ◢ ◣ ◤ ◥ ◦ ◧ ◨ ◩ ◪ ◫ ◬ ◭ ◮ ◯ +//! ◰ ◱ ◲ ◳ ◴ ◵ ◶ ◷ ◸ ◹ ◺ ◻ ◼ ◽︎◾︎◿ +//! +//! Only a subset of this block is viable for sprite drawing; filling +//! out this file to have full coverage of this block is not the goal. +//! + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Corner = common.Corner; +const Shade = common.Shade; + +const font = @import("../../main.zig"); + +/// ◢ ◣ ◤ ◥ +pub fn draw25E2_25E5( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + switch (cp) { + // ◢ + 0x25e2 => try cornerTriangleShade(metrics, canvas, .br, .on), + // ◣ + 0x25e3 => try cornerTriangleShade(metrics, canvas, .bl, .on), + // ◤ + 0x25e4 => try cornerTriangleShade(metrics, canvas, .tl, .on), + // ◥ + 0x25e5 => try cornerTriangleShade(metrics, canvas, .tr, .on), + + else => unreachable, + } +} + +/// ◸ ◹ ◺ +pub fn draw25F8_25FA( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + switch (cp) { + // ◸ + 0x25f8 => try cornerTriangleOutline(metrics, canvas, .tl), + // ◹ + 0x25f9 => try cornerTriangleOutline(metrics, canvas, .tr), + // ◺ + 0x25fa => try cornerTriangleOutline(metrics, canvas, .bl), + + else => unreachable, + } +} + +/// ◿ +pub fn draw25FF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + try cornerTriangleOutline(metrics, canvas, .br); +} + +pub fn cornerTriangleShade( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime corner: Corner, + comptime shade: Shade, +) !void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + const x0, const y0, const x1, const y1, const x2, const y2 = + switch (corner) { + .tl => .{ + 0, + 0, + 0, + float_height, + float_width, + 0, + }, + .tr => .{ + 0, + 0, + float_width, + float_height, + float_width, + 0, + }, + .bl => .{ + 0, + 0, + 0, + float_height, + float_width, + float_height, + }, + .br => .{ + 0, + float_height, + float_width, + float_height, + float_width, + 0, + }, + }; + + var path = canvas.staticPath(5); // nodes.len = 0 + path.moveTo(x0, y0); // +1, nodes.len = 1 + path.lineTo(x1, y1); // +1, nodes.len = 2 + path.lineTo(x2, y2); // +1, nodes.len = 3 + path.close(); // +2, nodes.len = 5 + + try canvas.fillPath( + path.wrapped_path, + .{}, + @enumFromInt(@intFromEnum(shade)), + ); +} + +pub fn cornerTriangleOutline( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime corner: Corner, +) !void { + const float_thick: f64 = @floatFromInt(Thickness.light.height(metrics.box_thickness)); + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + const x0, const y0, const x1, const y1, const x2, const y2 = + switch (corner) { + .tl => .{ + 0, + 0, + 0, + float_height, + float_width, + 0, + }, + .tr => .{ + 0, + 0, + float_width, + float_height, + float_width, + 0, + }, + .bl => .{ + 0, + 0, + 0, + float_height, + float_width, + float_height, + }, + .br => .{ + 0, + float_height, + float_width, + float_height, + float_width, + 0, + }, + }; + + var path = canvas.staticPath(5); // nodes.len = 0 + path.moveTo(x0, y0); // +1, nodes.len = 1 + path.lineTo(x1, y1); // +1, nodes.len = 2 + path.lineTo(x2, y2); // +1, nodes.len = 3 + path.close(); // +2, nodes.len = 5 + + try canvas.innerStrokePath(path.wrapped_path, .{ + .line_cap_mode = .butt, + .line_width = float_thick, + }, .on); +} diff --git a/src/font/sprite/octants.txt b/src/font/sprite/draw/octants.txt similarity index 100% rename from src/font/sprite/octants.txt rename to src/font/sprite/draw/octants.txt diff --git a/src/font/sprite/draw/powerline.zig b/src/font/sprite/draw/powerline.zig new file mode 100644 index 000000000..24fce454b --- /dev/null +++ b/src/font/sprite/draw/powerline.zig @@ -0,0 +1,396 @@ +//! Powerline + Powerline Extra Symbols | U+E0B0...U+E0D4 +//! https://github.com/ryanoasis/powerline-extra-symbols +//! +//!                 +//!               +//!     +//! +//! We implement the more geometric glyphs here, but not the stylized ones. +//! + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Shade = common.Shade; + +const box = @import("box.zig"); + +const font = @import("../../main.zig"); +const Quad = font.sprite.Canvas.Quad; + +///  +pub fn drawE0B0( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = 0, .y = 0 }, + .p1 = .{ .x = float_width, .y = float_height / 2 }, + .p2 = .{ .x = 0, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0B2( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = float_width, .y = 0 }, + .p1 = .{ .x = 0, .y = float_height / 2 }, + .p2 = .{ .x = float_width, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0B8( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = 0, .y = 0 }, + .p1 = .{ .x = float_width, .y = float_height }, + .p2 = .{ .x = 0, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0B9( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + box.lightDiagonalUpperLeftToLowerRight(metrics, canvas); +} + +///  +pub fn drawE0BA( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = float_width, .y = 0 }, + .p1 = .{ .x = float_width, .y = float_height }, + .p2 = .{ .x = 0, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0BB( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + box.lightDiagonalUpperRightToLowerLeft(metrics, canvas); +} + +///  +pub fn drawE0BC( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = 0, .y = 0 }, + .p1 = .{ .x = float_width, .y = 0 }, + .p2 = .{ .x = 0, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0BD( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + box.lightDiagonalUpperRightToLowerLeft(metrics, canvas); +} + +///  +pub fn drawE0BE( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + try canvas.triangle(.{ + .p0 = .{ .x = 0, .y = 0 }, + .p1 = .{ .x = float_width, .y = 0 }, + .p2 = .{ .x = float_width, .y = float_height }, + }, .on); +} + +///  +pub fn drawE0BF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + box.lightDiagonalUpperLeftToLowerRight(metrics, canvas); +} + +///  +pub fn drawE0B1( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + + var path = canvas.staticPath(3); + path.moveTo(0, 0); + path.lineTo(float_width, float_height / 2); + path.lineTo(0, float_height); + + try canvas.strokePath( + path.wrapped_path, + .{ + .line_cap_mode = .butt, + .line_width = @floatFromInt( + Thickness.light.height(metrics.box_thickness), + ), + }, + .on, + ); +} + +///  +pub fn drawE0B3( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + try drawE0B1(cp, canvas, width, height, metrics); + try canvas.flipHorizontal(); +} + +///  +pub fn drawE0B4( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + + // Coefficient for approximating a circular arc. + const c: f64 = (std.math.sqrt2 - 1.0) * 4.0 / 3.0; + + const radius: f64 = @min(float_width, float_height / 2); + + var path = canvas.staticPath(6); + path.moveTo(0, 0); + path.curveTo( + radius * c, + 0, + radius, + radius - radius * c, + radius, + radius, + ); + path.lineTo(radius, float_height - radius); + path.curveTo( + radius, + float_height - radius + radius * c, + radius * c, + float_height, + 0, + float_height, + ); + path.close(); + + try canvas.fillPath(path.wrapped_path, .{}, .on); +} + +///  +pub fn drawE0B5( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + + // Coefficient for approximating a circular arc. + const c: f64 = (std.math.sqrt2 - 1.0) * 4.0 / 3.0; + + const radius: f64 = @min(float_width, float_height / 2); + + var path = canvas.staticPath(4); + path.moveTo(0, 0); + path.curveTo( + radius * c, + 0, + radius, + radius - radius * c, + radius, + radius, + ); + path.lineTo(radius, float_height - radius); + path.curveTo( + radius, + float_height - radius + radius * c, + radius * c, + float_height, + 0, + float_height, + ); + + try canvas.innerStrokePath(path.wrapped_path, .{ + .line_width = @floatFromInt(metrics.box_thickness), + .line_cap_mode = .butt, + }, .on); +} + +///  +pub fn drawE0B6( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + try drawE0B4(cp, canvas, width, height, metrics); + try canvas.flipHorizontal(); +} + +///  +pub fn drawE0B7( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + try drawE0B5(cp, canvas, width, height, metrics); + try canvas.flipHorizontal(); +} + +///  +pub fn drawE0D2( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + + const float_width: f64 = @floatFromInt(width); + const float_height: f64 = @floatFromInt(height); + const float_thick: f64 = @floatFromInt(metrics.box_thickness); + + // Top piece + { + var path = canvas.staticPath(6); + path.moveTo(0, 0); + path.lineTo(float_width, 0); + path.lineTo(float_width / 2, float_height / 2 - float_thick / 2); + path.lineTo(0, float_height / 2 - float_thick / 2); + path.close(); + + try canvas.fillPath(path.wrapped_path, .{}, .on); + } + + // Bottom piece + { + var path = canvas.staticPath(6); + path.moveTo(0, float_height); + path.lineTo(float_width, float_height); + path.lineTo(float_width / 2, float_height / 2 + float_thick / 2); + path.lineTo(0, float_height / 2 + float_thick / 2); + path.close(); + + try canvas.fillPath(path.wrapped_path, .{}, .on); + } +} + +///  +pub fn drawE0D4( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + try drawE0D2(cp, canvas, width, height, metrics); + try canvas.flipHorizontal(); +} diff --git a/src/font/sprite/draw/special.zig b/src/font/sprite/draw/special.zig new file mode 100644 index 000000000..e41cac487 --- /dev/null +++ b/src/font/sprite/draw/special.zig @@ -0,0 +1,346 @@ +//! This file contains glyph drawing functions for all of the +//! non-Unicode sprite glyphs, such as cursors and underlines. +//! +//! The naming convention in this file differs from the usual +//! because the draw functions for special sprites are found by +//! having names that exactly match the enum fields in Sprite. + +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const font = @import("../../main.zig"); +const Sprite = font.sprite.Sprite; + +pub fn underline( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.underline_position), + .width = @intCast(width), + .height = @intCast(metrics.underline_thickness), + }, .on); +} + +pub fn underline_double( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + // We place one underline above the underline position, and one below + // by one thickness, creating a "negative" underline where the single + // underline would be placed. + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.underline_position -| metrics.underline_thickness), + .width = @intCast(width), + .height = @intCast(metrics.underline_thickness), + }, .on); + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.underline_position +| metrics.underline_thickness), + .width = @intCast(width), + .height = @intCast(metrics.underline_thickness), + }, .on); +} + +pub fn underline_dotted( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + // TODO: Rework this now that we can go out of bounds, just + // make sure that adjacent versions of this glyph align. + const dot_width = @max(metrics.underline_thickness, 3); + const dot_count = @max((width / dot_width) / 2, 1); + const gap_width = std.math.divCeil( + u32, + width -| (dot_count * dot_width), + dot_count, + ) catch return error.MathError; + var i: u32 = 0; + while (i < dot_count) : (i += 1) { + // Ensure we never go out of bounds for the rect + const x = @min(i * (dot_width + gap_width), width - 1); + const rect_width = @min(width - x, dot_width); + canvas.rect(.{ + .x = @intCast(x), + .y = @intCast(metrics.underline_position), + .width = @intCast(rect_width), + .height = @intCast(metrics.underline_thickness), + }, .on); + } +} + +pub fn underline_dashed( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + const dash_width = width / 3 + 1; + const dash_count = (width / dash_width) + 1; + var i: u32 = 0; + while (i < dash_count) : (i += 2) { + // Ensure we never go out of bounds for the rect + const x = @min(i * dash_width, width - 1); + const rect_width = @min(width - x, dash_width); + canvas.rect(.{ + .x = @intCast(x), + .y = @intCast(metrics.underline_position), + .width = @intCast(rect_width), + .height = @intCast(metrics.underline_thickness), + }, .on); + } +} + +pub fn underline_curly( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + // TODO: Rework this using z2d, this is pretty cool code and all but + // it doesn't need to be highly optimized and z2d path drawing + // code would be clearer and nicer to have. + + const float_width: f64 = @floatFromInt(width); + // Because of we way we draw the undercurl, we end up making it around 1px + // thicker than it should be, to fix this we just reduce the thickness by 1. + // + // We use a minimum thickness of 0.414 because this empirically produces + // the nicest undercurls at 1px underline thickness; thinner tends to look + // too thin compared to straight underlines and has artefacting. + const float_thick: f64 = @max( + 0.414, + @as(f64, @floatFromInt(metrics.underline_thickness -| 1)), + ); + + // Calculate the wave period for a single character + // `2 * pi...` = 1 peak per character + // `4 * pi...` = 2 peaks per character + const wave_period = 2 * std.math.pi / float_width; + + // The full amplitude of the wave can be from the bottom to the + // underline position. We also calculate our mid y point of the wave + const half_amplitude = 1.0 / wave_period; + const y_mid: f64 = half_amplitude + float_thick * 0.5 + 1; + + // Offset to move the undercurl up slightly. + const y_off: u32 = @intFromFloat(half_amplitude * 0.5); + + // This is used in calculating the offset curve estimate below. + const offset_factor = @min(1.0, float_thick * 0.5 * wave_period) * @min( + 1.0, + half_amplitude * wave_period, + ); + + // follow Xiaolin Wu's antialias algorithm to draw the curve + var x: u32 = 0; + while (x < width) : (x += 1) { + // We sample the wave function at the *middle* of each + // pixel column, to ensure that it renders symmetrically. + const t: f64 = (@as(f64, @floatFromInt(x)) + 0.5) * wave_period; + // Use the slope at this location to add thickness to + // the line on this column, counteracting the thinning + // caused by the slope. + // + // This is not the exact offset curve for a sine wave, + // but it's a decent enough approximation. + // + // How did I derive this? I stared at Desmos and fiddled + // with numbers for an hour until it was good enough. + const t_u: f64 = t + std.math.pi; + const slope_factor_u: f64 = + (@sin(t_u) * @sin(t_u) * offset_factor) / + ((1.0 + @cos(t_u / 2) * @cos(t_u / 2) * 2) * wave_period); + const slope_factor_l: f64 = + (@sin(t) * @sin(t) * offset_factor) / + ((1.0 + @cos(t / 2) * @cos(t / 2) * 2) * wave_period); + + const cosx: f64 = @cos(t); + // This will be the center of our stroke. + const y: f64 = y_mid + half_amplitude * cosx; + + // The upper pixel and lower pixel are + // calculated relative to the center. + const y_u: f64 = y - float_thick * 0.5 - slope_factor_u; + const y_l: f64 = y + float_thick * 0.5 + slope_factor_l; + const y_upper: u32 = @intFromFloat(@floor(y_u)); + const y_lower: u32 = @intFromFloat(@ceil(y_l)); + const alpha_u: u8 = @intFromFloat( + @round(255 * (1.0 - @abs(y_u - @floor(y_u)))), + ); + const alpha_l: u8 = @intFromFloat( + @round(255 * (1.0 - @abs(y_l - @ceil(y_l)))), + ); + + // upper and lower bounds + canvas.pixel( + @intCast(x), + @intCast(metrics.underline_position +| y_upper -| y_off), + @enumFromInt(alpha_u), + ); + canvas.pixel( + @intCast(x), + @intCast(metrics.underline_position +| y_lower -| y_off), + @enumFromInt(alpha_l), + ); + + // fill between upper and lower bound + var y_fill: u32 = y_upper + 1; + while (y_fill < y_lower) : (y_fill += 1) { + canvas.pixel( + @intCast(x), + @intCast(metrics.underline_position +| y_fill -| y_off), + .on, + ); + } + } +} + +pub fn strikethrough( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.strikethrough_position), + .width = @intCast(width), + .height = @intCast(metrics.strikethrough_thickness), + }, .on); +} + +pub fn overline( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.overline_position), + .width = @intCast(width), + .height = @intCast(metrics.overline_thickness), + }, .on); +} + +pub fn cursor_rect( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = metrics; + + canvas.rect(.{ + .x = 0, + .y = 0, + .width = @intCast(width), + .height = @intCast(height), + }, .on); +} + +pub fn cursor_hollow_rect( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + + // We fill the entire rect and then hollow out the inside, this isn't very + // efficient but it doesn't need to be and it's the easiest way to write it. + canvas.rect(.{ + .x = 0, + .y = 0, + .width = @intCast(width), + .height = @intCast(height), + }, .on); + canvas.rect(.{ + .x = @intCast(metrics.cursor_thickness), + .y = @intCast(metrics.cursor_thickness), + .width = @intCast(width -| metrics.cursor_thickness * 2), + .height = @intCast(height -| metrics.cursor_thickness * 2), + }, .off); +} + +pub fn cursor_bar( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + + // We place the bar cursor half of its thickness over the left edge of the + // cell, so that it sits centered between characters, not biased to a side. + // + // We round up (add 1 before dividing by 2) because, empirically, having a + // 1px cursor shifted left a pixel looks better than having it not shifted. + canvas.rect(.{ + .x = -@as(i32, @intCast((metrics.cursor_thickness + 1) / 2)), + .y = 0, + .width = @intCast(metrics.cursor_thickness), + .height = @intCast(height), + }, .on); +} + +pub fn cursor_underline( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = height; + + canvas.rect(.{ + .x = 0, + .y = @intCast(metrics.underline_position), + .width = @intCast(width), + .height = @intCast(metrics.cursor_thickness), + }, .on); +} diff --git a/src/font/sprite/draw/symbols_for_legacy_computing.zig b/src/font/sprite/draw/symbols_for_legacy_computing.zig new file mode 100644 index 000000000..164aa1ac3 --- /dev/null +++ b/src/font/sprite/draw/symbols_for_legacy_computing.zig @@ -0,0 +1,1415 @@ +//! Symbols for Legacy Computing | U+1FB00...U+1FBFF +//! https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing +//! +//! 🬀 🬁 🬂 🬃 🬄 🬅 🬆 🬇 🬈 🬉 🬊 🬋 🬌 🬍 🬎 🬏 +//! 🬐 🬑 🬒 🬓 🬔 🬕 🬖 🬗 🬘 🬙 🬚 🬛 🬜 🬝 🬞 🬟 +//! 🬠 🬡 🬢 🬣 🬤 🬥 🬦 🬧 🬨 🬩 🬪 🬫 🬬 🬭 🬮 🬯 +//! 🬰 🬱 🬲 🬳 🬴 🬵 🬶 🬷 🬸 🬹 🬺 🬻 🬼 🬽 🬾 🬿 +//! 🭀 🭁 🭂 🭃 🭄 🭅 🭆 🭇 🭈 🭉 🭊 🭋 🭌 🭍 🭎 🭏 +//! 🭐 🭑 🭒 🭓 🭔 🭕 🭖 🭗 🭘 🭙 🭚 🭛 🭜 🭝 🭞 🭟 +//! 🭠 🭡 🭢 🭣 🭤 🭥 🭦 🭧 🭨 🭩 🭪 🭫 🭬 🭭 🭮 🭯 +//! 🭰 🭱 🭲 🭳 🭴 🭵 🭶 🭷 🭸 🭹 🭺 🭻 🭼 🭽 🭾 🭿 +//! 🮀 🮁 🮂 🮃 🮄 🮅 🮆 🮇 🮈 🮉 🮊 🮋 🮌 🮍 🮎 🮏 +//! 🮐 🮑 🮒 🮔 🮕 🮖 🮗 🮘 🮙 🮚 🮛 🮜 🮝 🮞 🮟 +//! 🮠 🮡 🮢 🮣 🮤 🮥 🮦 🮧 🮨 🮩 🮪 🮫 🮬 🮭 🮮 🮯 +//! 🮰 🮱 🮲 🮳 🮴 🮵 🮶 🮷 🮸 🮹 🮺 🮻 🮼 🮽 🮾 🮿 +//! 🯀 🯁 🯂 🯃 🯄 🯅 🯆 🯇 🯈 🯉 🯊 🯋 🯌 🯍 🯎 🯏 +//! 🯐 🯑 🯒 🯓 🯔 🯕 🯖 🯗 🯘 🯙 🯚 🯛 🯜 🯝 🯞 🯟 +//! 🯠 🯡 🯢 🯣 🯤 🯥 🯦 🯧 🯨 🯩 🯪 🯫 🯬 🯭 🯮 🯯 +//! 🯰 🯱 🯲 🯳 🯴 🯵 🯶 🯷 🯸 🯹 +//! + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Alignment = common.Alignment; +const Fraction = common.Fraction; +const Corner = common.Corner; +const Quads = common.Quads; +const Edge = common.Edge; +const Shade = common.Shade; +const fill = common.fill; + +const box = @import("box.zig"); +const block = @import("block.zig"); +const geo = @import("geometric_shapes.zig"); + +const font = @import("../../main.zig"); + +// Utility names for common fractions +const one_eighth: f64 = 0.125; +const one_quarter: f64 = 0.25; +const one_third: f64 = (1.0 / 3.0); +const three_eighths: f64 = 0.375; +const half: f64 = 0.5; +const five_eighths: f64 = 0.625; +const two_thirds: f64 = (2.0 / 3.0); +const three_quarters: f64 = 0.75; +const seven_eighths: f64 = 0.875; + +const SmoothMosaic = packed struct(u10) { + tl: bool, + ul: bool, + ll: bool, + bl: bool, + bc: bool, + br: bool, + lr: bool, + ur: bool, + tr: bool, + tc: bool, + + fn from(comptime pattern: *const [15:0]u8) SmoothMosaic { + return .{ + .tl = pattern[0] == '#', + + .ul = pattern[4] == '#' and + (pattern[0] != '#' or pattern[8] != '#'), + + .ll = pattern[8] == '#' and + (pattern[4] != '#' or pattern[12] != '#'), + + .bl = pattern[12] == '#', + + .bc = pattern[13] == '#' and + (pattern[12] != '#' or pattern[14] != '#'), + + .br = pattern[14] == '#', + + .lr = pattern[10] == '#' and + (pattern[14] != '#' or pattern[6] != '#'), + + .ur = pattern[6] == '#' and + (pattern[10] != '#' or pattern[2] != '#'), + + .tr = pattern[2] == '#', + + .tc = pattern[1] == '#' and + (pattern[2] != '#' or pattern[0] != '#'), + }; + } +}; + +/// Sextants +pub fn draw1FB00_1FB3B( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + const Sextants = packed struct(u6) { + tl: bool, + tr: bool, + ml: bool, + mr: bool, + bl: bool, + br: bool, + }; + + assert(cp >= 0x1fb00 and cp <= 0x1fb3b); + const idx = cp - 0x1fb00; + const sex: Sextants = @bitCast(@as(u6, @intCast( + idx + (idx / 0x14) + 1, + ))); + if (sex.tl) fill(metrics, canvas, .zero, .half, .zero, .one_third); + if (sex.tr) fill(metrics, canvas, .half, .full, .zero, .one_third); + if (sex.ml) fill(metrics, canvas, .zero, .half, .one_third, .two_thirds); + if (sex.mr) fill(metrics, canvas, .half, .full, .one_third, .two_thirds); + if (sex.bl) fill(metrics, canvas, .zero, .half, .two_thirds, .end); + if (sex.br) fill(metrics, canvas, .half, .full, .two_thirds, .end); +} + +/// Smooth Mosaics +pub fn draw1FB3C_1FB67( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + // Hand written lookup table for these shapes since I couldn't + // determine any sort of mathematical pattern in the codepoints. + const mosaic: SmoothMosaic = switch (cp) { + // '🬼' + 0x1fb3c => SmoothMosaic.from( + \\... + \\... + \\#.. + \\##. + ), + // '🬽' + 0x1fb3d => SmoothMosaic.from( + \\... + \\... + \\#\. + \\### + ), + // '🬾' + 0x1fb3e => SmoothMosaic.from( + \\... + \\#.. + \\#\. + \\##. + ), + // '🬿' + 0x1fb3f => SmoothMosaic.from( + \\... + \\#.. + \\##. + \\### + ), + // '🭀' + 0x1fb40 => SmoothMosaic.from( + \\#.. + \\#.. + \\##. + \\##. + ), + + // '🭁' + 0x1fb41 => SmoothMosaic.from( + \\/## + \\### + \\### + \\### + ), + // '🭂' + 0x1fb42 => SmoothMosaic.from( + \\./# + \\### + \\### + \\### + ), + // '🭃' + 0x1fb43 => SmoothMosaic.from( + \\.## + \\.## + \\### + \\### + ), + // '🭄' + 0x1fb44 => SmoothMosaic.from( + \\..# + \\.## + \\### + \\### + ), + // '🭅' + 0x1fb45 => SmoothMosaic.from( + \\.## + \\.## + \\.## + \\### + ), + // '🭆' + 0x1fb46 => SmoothMosaic.from( + \\... + \\./# + \\### + \\### + ), + + // '🭇' + 0x1fb47 => SmoothMosaic.from( + \\... + \\... + \\..# + \\.## + ), + // '🭈' + 0x1fb48 => SmoothMosaic.from( + \\... + \\... + \\./# + \\### + ), + // '🭉' + 0x1fb49 => SmoothMosaic.from( + \\... + \\..# + \\./# + \\.## + ), + // '🭊' + 0x1fb4a => SmoothMosaic.from( + \\... + \\..# + \\.## + \\### + ), + // '🭋' + 0x1fb4b => SmoothMosaic.from( + \\..# + \\..# + \\.## + \\.## + ), + + // '🭌' + 0x1fb4c => SmoothMosaic.from( + \\##\ + \\### + \\### + \\### + ), + // '🭍' + 0x1fb4d => SmoothMosaic.from( + \\#\. + \\### + \\### + \\### + ), + // '🭎' + 0x1fb4e => SmoothMosaic.from( + \\##. + \\##. + \\### + \\### + ), + // '🭏' + 0x1fb4f => SmoothMosaic.from( + \\#.. + \\##. + \\### + \\### + ), + // '🭐' + 0x1fb50 => SmoothMosaic.from( + \\##. + \\##. + \\##. + \\### + ), + // '🭑' + 0x1fb51 => SmoothMosaic.from( + \\... + \\#\. + \\### + \\### + ), + + // '🭒' + 0x1fb52 => SmoothMosaic.from( + \\### + \\### + \\### + \\\## + ), + // '🭓' + 0x1fb53 => SmoothMosaic.from( + \\### + \\### + \\### + \\.\# + ), + // '🭔' + 0x1fb54 => SmoothMosaic.from( + \\### + \\### + \\.## + \\.## + ), + // '🭕' + 0x1fb55 => SmoothMosaic.from( + \\### + \\### + \\.## + \\..# + ), + // '🭖' + 0x1fb56 => SmoothMosaic.from( + \\### + \\.## + \\.## + \\.## + ), + + // '🭗' + 0x1fb57 => SmoothMosaic.from( + \\##. + \\#.. + \\... + \\... + ), + // '🭘' + 0x1fb58 => SmoothMosaic.from( + \\### + \\#/. + \\... + \\... + ), + // '🭙' + 0x1fb59 => SmoothMosaic.from( + \\##. + \\#/. + \\#.. + \\... + ), + // '🭚' + 0x1fb5a => SmoothMosaic.from( + \\### + \\##. + \\#.. + \\... + ), + // '🭛' + 0x1fb5b => SmoothMosaic.from( + \\##. + \\##. + \\#.. + \\#.. + ), + + // '🭜' + 0x1fb5c => SmoothMosaic.from( + \\### + \\### + \\#/. + \\... + ), + // '🭝' + 0x1fb5d => SmoothMosaic.from( + \\### + \\### + \\### + \\##/ + ), + // '🭞' + 0x1fb5e => SmoothMosaic.from( + \\### + \\### + \\### + \\#/. + ), + // '🭟' + 0x1fb5f => SmoothMosaic.from( + \\### + \\### + \\##. + \\##. + ), + // '🭠' + 0x1fb60 => SmoothMosaic.from( + \\### + \\### + \\##. + \\#.. + ), + // '🭡' + 0x1fb61 => SmoothMosaic.from( + \\### + \\##. + \\##. + \\##. + ), + + // '🭢' + 0x1fb62 => SmoothMosaic.from( + \\.## + \\..# + \\... + \\... + ), + // '🭣' + 0x1fb63 => SmoothMosaic.from( + \\### + \\.\# + \\... + \\... + ), + // '🭤' + 0x1fb64 => SmoothMosaic.from( + \\.## + \\.\# + \\..# + \\... + ), + // '🭥' + 0x1fb65 => SmoothMosaic.from( + \\### + \\.## + \\..# + \\... + ), + // '🭦' + 0x1fb66 => SmoothMosaic.from( + \\.## + \\.## + \\..# + \\..# + ), + // '🭧' + 0x1fb67 => SmoothMosaic.from( + \\### + \\### + \\.\# + \\... + ), + else => unreachable, + }; + + const top: f64 = 0.0; + const upper: f64 = Fraction.one_third.float(metrics.cell_height); + const lower: f64 = Fraction.two_thirds.float(metrics.cell_height); + const bottom: f64 = @floatFromInt(metrics.cell_height); + const left: f64 = 0.0; + const center: f64 = Fraction.half.float(metrics.cell_width); + const right: f64 = @floatFromInt(metrics.cell_width); + + var path = canvas.staticPath(12); // nodes.len = 0 + if (mosaic.tl) path.lineTo(left, top); // +1, nodes.len = 1 + if (mosaic.ul) path.lineTo(left, upper); // +1, nodes.len = 2 + if (mosaic.ll) path.lineTo(left, lower); // +1, nodes.len = 3 + if (mosaic.bl) path.lineTo(left, bottom); // +1, nodes.len = 4 + if (mosaic.bc) path.lineTo(center, bottom); // +1, nodes.len = 5 + if (mosaic.br) path.lineTo(right, bottom); // +1, nodes.len = 6 + if (mosaic.lr) path.lineTo(right, lower); // +1, nodes.len = 7 + if (mosaic.ur) path.lineTo(right, upper); // +1, nodes.len = 8 + if (mosaic.tr) path.lineTo(right, top); // +1, nodes.len = 9 + if (mosaic.tc) path.lineTo(center, top); // +1, nodes.len = 10 + path.close(); // +2, nodes.len = 12 + + try canvas.fillPath(path.wrapped_path, .{}, .on); +} + +pub fn draw1FB68_1FB6F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '🭨' + 0x1fb68 => { + try edgeTriangle(metrics, canvas, .left); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + }, + // '🭩' + 0x1fb69 => { + try edgeTriangle(metrics, canvas, .top); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + }, + // '🭪' + 0x1fb6a => { + try edgeTriangle(metrics, canvas, .right); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + }, + // '🭫' + 0x1fb6b => { + try edgeTriangle(metrics, canvas, .bottom); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + }, + // '🭬' + 0x1fb6c => try edgeTriangle(metrics, canvas, .left), + // '🭭' + 0x1fb6d => try edgeTriangle(metrics, canvas, .top), + // '🭮' + 0x1fb6e => try edgeTriangle(metrics, canvas, .right), + // '🭯' + 0x1fb6f => try edgeTriangle(metrics, canvas, .bottom), + + else => unreachable, + } +} + +/// Vertical one eighth blocks +pub fn draw1FB70_1FB75( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + const n = cp + 1 - 0x1fb70; + + fill( + metrics, + canvas, + Fraction.eighths[n], + Fraction.eighths[n + 1], + .top, + .bottom, + ); +} + +/// Horizontal one eighth blocks +pub fn draw1FB76_1FB7B( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + const n = cp + 1 - 0x1fb76; + + fill( + metrics, + canvas, + .left, + .right, + Fraction.eighths[n], + Fraction.eighths[n + 1], + ); +} + +pub fn draw1FB7C_1FB97( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + switch (cp) { + + // '🭼' LEFT AND LOWER ONE EIGHTH BLOCK + 0x1fb7c => { + block.block(metrics, canvas, .left, one_eighth, 1); + block.block(metrics, canvas, .lower, 1, one_eighth); + }, + // '🭽' LEFT AND UPPER ONE EIGHTH BLOCK + 0x1fb7d => { + block.block(metrics, canvas, .left, one_eighth, 1); + block.block(metrics, canvas, .upper, 1, one_eighth); + }, + // '🭾' RIGHT AND UPPER ONE EIGHTH BLOCK + 0x1fb7e => { + block.block(metrics, canvas, .right, one_eighth, 1); + block.block(metrics, canvas, .upper, 1, one_eighth); + }, + // '🭿' RIGHT AND LOWER ONE EIGHTH BLOCK + 0x1fb7f => { + block.block(metrics, canvas, .right, one_eighth, 1); + block.block(metrics, canvas, .lower, 1, one_eighth); + }, + // '🮀' UPPER AND LOWER ONE EIGHTH BLOCK + 0x1fb80 => { + block.block(metrics, canvas, .upper, 1, one_eighth); + block.block(metrics, canvas, .lower, 1, one_eighth); + }, + // '🮁' Horizontal One Eighth Block 1358 + 0x1fb81 => { + // We just call the draw function for each of the relevant codepoints. + // The first codepoint is actually a lie, it's before the range, but + // we need it to get the first (0th) block position. This might be a + // bit brittle, oh well, if it breaks we can fix it. + try draw1FB76_1FB7B(0x1fb74 + 1, canvas, width, height, metrics); + try draw1FB76_1FB7B(0x1fb74 + 3, canvas, width, height, metrics); + try draw1FB76_1FB7B(0x1fb74 + 5, canvas, width, height, metrics); + try draw1FB76_1FB7B(0x1fb74 + 8, canvas, width, height, metrics); + }, + + // '🮂' UPPER ONE QUARTER BLOCK + 0x1fb82 => block.block(metrics, canvas, .upper, 1, one_quarter), + // '🮃' UPPER THREE EIGHTHS BLOCK + 0x1fb83 => block.block(metrics, canvas, .upper, 1, three_eighths), + // '🮄' UPPER FIVE EIGHTHS BLOCK + 0x1fb84 => block.block(metrics, canvas, .upper, 1, five_eighths), + // '🮅' UPPER THREE QUARTERS BLOCK + 0x1fb85 => block.block(metrics, canvas, .upper, 1, three_quarters), + // '🮆' UPPER SEVEN EIGHTHS BLOCK + 0x1fb86 => block.block(metrics, canvas, .upper, 1, seven_eighths), + + // '🮇' RIGHT ONE QUARTER BLOCK + 0x1fb87 => block.block(metrics, canvas, .right, one_quarter, 1), + // '🮈' RIGHT THREE EIGHTHS BLOCK + 0x1fb88 => block.block(metrics, canvas, .right, three_eighths, 1), + // '🮉' RIGHT FIVE EIGHTHS BLOCK + 0x1fb89 => block.block(metrics, canvas, .right, five_eighths, 1), + // '🮊' RIGHT THREE QUARTERS BLOCK + 0x1fb8a => block.block(metrics, canvas, .right, three_quarters, 1), + // '🮋' RIGHT SEVEN EIGHTHS BLOCK/ + 0x1fb8b => block.block(metrics, canvas, .right, seven_eighths, 1), + + // '🮌' + 0x1fb8c => block.blockShade(metrics, canvas, .left, half, 1, .medium), + // '🮍' + 0x1fb8d => block.blockShade(metrics, canvas, .right, half, 1, .medium), + // '🮎' + 0x1fb8e => block.blockShade(metrics, canvas, .upper, 1, half, .medium), + // '🮏' + 0x1fb8f => block.blockShade(metrics, canvas, .lower, 1, half, .medium), + + // '🮐' + 0x1fb90 => block.fullBlockShade(metrics, canvas, .medium), + // '🮑' + 0x1fb91 => { + block.fullBlockShade(metrics, canvas, .medium); + block.block(metrics, canvas, .upper, 1, half); + }, + // '🮒' + 0x1fb92 => { + block.fullBlockShade(metrics, canvas, .medium); + block.block(metrics, canvas, .lower, 1, half); + }, + 0x1fb93 => { + // NOTE: This codepoint is currently un-allocated, it's a hole + // in the unicode block, so it's safe to just render it + // as an empty glyph, probably. + }, + // '🮔' + 0x1fb94 => { + block.fullBlockShade(metrics, canvas, .medium); + block.block(metrics, canvas, .right, half, 1); + }, + // '🮕' + 0x1fb95 => checkerboardFill(metrics, canvas, 0), + // '🮖' + 0x1fb96 => checkerboardFill(metrics, canvas, 1), + // '🮗' + 0x1fb97 => { + canvas.box( + 0, + @intCast(height / 4), + @intCast(width), + @intCast(2 * height / 4), + .on, + ); + canvas.box( + 0, + @intCast(3 * height / 4), + @intCast(width), + @intCast(height), + .on, + ); + }, + + else => unreachable, + } +} + +/// Upper Left to Lower Right Fill +/// 🮘 +pub fn draw1FB98( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + + // TODO: This doesn't align properly for most cell sizes, fix that. + + const thick_px = Thickness.light.height(metrics.box_thickness); + const line_count = metrics.cell_width / (2 * thick_px); + + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const float_thick: f64 = @floatFromInt(thick_px); + const stride = @round(float_width / @as(f64, @floatFromInt(line_count))); + + for (0..line_count * 2 + 1) |_i| { + const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count)); + const top_x = @as(f64, @floatFromInt(i)) * stride; + const bottom_x = float_width + top_x; + canvas.line(.{ + .p0 = .{ .x = top_x, .y = 0 }, + .p1 = .{ .x = bottom_x, .y = float_height }, + }, float_thick, .on) catch {}; + } +} + +/// Upper Right to Lower Left Fill +/// 🮙 +pub fn draw1FB99( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + + // TODO: This doesn't align properly for most cell sizes, fix that. + + const thick_px = Thickness.light.height(metrics.box_thickness); + const line_count = metrics.cell_width / (2 * thick_px); + + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const float_thick: f64 = @floatFromInt(thick_px); + const stride = @round(float_width / @as(f64, @floatFromInt(line_count))); + + for (0..line_count * 2 + 1) |_i| { + const i = @as(i32, @intCast(_i)) - @as(i32, @intCast(line_count)); + const bottom_x = @as(f64, @floatFromInt(i)) * stride; + const top_x = float_width + bottom_x; + canvas.line(.{ + .p0 = .{ .x = top_x, .y = 0 }, + .p1 = .{ .x = bottom_x, .y = float_height }, + }, float_thick, .on) catch {}; + } +} + +pub fn draw1FB9A_1FB9F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '🮚' + 0x1fb9a => { + try edgeTriangle(metrics, canvas, .top); + try edgeTriangle(metrics, canvas, .bottom); + }, + // '🮛' + 0x1fb9b => { + try edgeTriangle(metrics, canvas, .left); + try edgeTriangle(metrics, canvas, .right); + }, + // '🮜' + 0x1fb9c => try geo.cornerTriangleShade(metrics, canvas, .tl, .medium), + // '🮝' + 0x1fb9d => try geo.cornerTriangleShade(metrics, canvas, .tr, .medium), + // '🮞' + 0x1fb9e => try geo.cornerTriangleShade(metrics, canvas, .br, .medium), + // '🮟' + 0x1fb9f => try geo.cornerTriangleShade(metrics, canvas, .bl, .medium), + + else => unreachable, + } +} + +pub fn draw1FBA0_1FBAE( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '🮠' + 0x1fba0 => cornerDiagonalLines(metrics, canvas, .{ .tl = true }), + // '🮡' + 0x1fba1 => cornerDiagonalLines(metrics, canvas, .{ .tr = true }), + // '🮢' + 0x1fba2 => cornerDiagonalLines(metrics, canvas, .{ .bl = true }), + // '🮣' + 0x1fba3 => cornerDiagonalLines(metrics, canvas, .{ .br = true }), + // '🮤' + 0x1fba4 => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .bl = true }), + // '🮥' + 0x1fba5 => cornerDiagonalLines(metrics, canvas, .{ .tr = true, .br = true }), + // '🮦' + 0x1fba6 => cornerDiagonalLines(metrics, canvas, .{ .bl = true, .br = true }), + // '🮧' + 0x1fba7 => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true }), + // '🮨' + 0x1fba8 => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .br = true }), + // '🮩' + 0x1fba9 => cornerDiagonalLines(metrics, canvas, .{ .tr = true, .bl = true }), + // '🮪' + 0x1fbaa => cornerDiagonalLines(metrics, canvas, .{ .tr = true, .bl = true, .br = true }), + // '🮫' + 0x1fbab => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .bl = true, .br = true }), + // '🮬' + 0x1fbac => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true, .br = true }), + // '🮭' + 0x1fbad => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true, .bl = true }), + // '🮮' + 0x1fbae => cornerDiagonalLines(metrics, canvas, .{ .tl = true, .tr = true, .bl = true, .br = true }), + + else => unreachable, + } +} + +/// 🮯 +pub fn draw1FBAF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + box.linesChar(metrics, canvas, .{ + .up = .heavy, + .down = .heavy, + .left = .light, + .right = .light, + }); +} + +/// 🮽 +pub fn draw1FBBD( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + box.lightDiagonalCross(metrics, canvas); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; +} + +/// 🮾 +pub fn draw1FBBE( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + cornerDiagonalLines(metrics, canvas, .{ .br = true }); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; +} + +/// 🮿 +pub fn draw1FBBF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + cornerDiagonalLines(metrics, canvas, .{ + .tl = true, + .tr = true, + .bl = true, + .br = true, + }); + canvas.invert(); + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; +} + +/// 🯎 +pub fn draw1FBCE( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + block.block(metrics, canvas, .left, two_thirds, 1); +} + +// 🯏 +pub fn draw1FBCF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + + block.block(metrics, canvas, .left, one_third, 1); +} + +/// Cell diagonals. +pub fn draw1FBD0_1FBDF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '🯐' + 0x1fbd0 => cellDiagonal( + metrics, + canvas, + .middle_right, + .lower_left, + ), + // '🯑' + 0x1fbd1 => cellDiagonal( + metrics, + canvas, + .upper_right, + .middle_left, + ), + // '🯒' + 0x1fbd2 => cellDiagonal( + metrics, + canvas, + .upper_left, + .middle_right, + ), + // '🯓' + 0x1fbd3 => cellDiagonal( + metrics, + canvas, + .middle_left, + .lower_right, + ), + // '🯔' + 0x1fbd4 => cellDiagonal( + metrics, + canvas, + .upper_left, + .lower_center, + ), + // '🯕' + 0x1fbd5 => cellDiagonal( + metrics, + canvas, + .upper_center, + .lower_right, + ), + // '🯖' + 0x1fbd6 => cellDiagonal( + metrics, + canvas, + .upper_right, + .lower_center, + ), + // '🯗' + 0x1fbd7 => cellDiagonal( + metrics, + canvas, + .upper_center, + .lower_left, + ), + // '🯘' + 0x1fbd8 => { + cellDiagonal( + metrics, + canvas, + .upper_left, + .middle_center, + ); + cellDiagonal( + metrics, + canvas, + .middle_center, + .upper_right, + ); + }, + // '🯙' + 0x1fbd9 => { + cellDiagonal( + metrics, + canvas, + .upper_right, + .middle_center, + ); + cellDiagonal( + metrics, + canvas, + .middle_center, + .lower_right, + ); + }, + // '🯚' + 0x1fbda => { + cellDiagonal( + metrics, + canvas, + .lower_left, + .middle_center, + ); + cellDiagonal( + metrics, + canvas, + .middle_center, + .lower_right, + ); + }, + // '🯛' + 0x1fbdb => { + cellDiagonal( + metrics, + canvas, + .upper_left, + .middle_center, + ); + cellDiagonal( + metrics, + canvas, + .middle_center, + .lower_left, + ); + }, + // '🯜' + 0x1fbdc => { + cellDiagonal( + metrics, + canvas, + .upper_left, + .lower_center, + ); + cellDiagonal( + metrics, + canvas, + .lower_center, + .upper_right, + ); + }, + // '🯝' + 0x1fbdd => { + cellDiagonal( + metrics, + canvas, + .upper_right, + .middle_left, + ); + cellDiagonal( + metrics, + canvas, + .middle_left, + .lower_right, + ); + }, + // '🯞' + 0x1fbde => { + cellDiagonal( + metrics, + canvas, + .lower_left, + .upper_center, + ); + cellDiagonal( + metrics, + canvas, + .upper_center, + .lower_right, + ); + }, + // '🯟' + 0x1fbdf => { + cellDiagonal( + metrics, + canvas, + .upper_left, + .middle_right, + ); + cellDiagonal( + metrics, + canvas, + .middle_right, + .lower_left, + ); + }, + + else => unreachable, + } +} + +pub fn draw1FBE0_1FBEF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + switch (cp) { + // '🯠' + 0x1fbe0 => circle(metrics, canvas, .top, false), + // '🯡' + 0x1fbe1 => circle(metrics, canvas, .right, false), + // '🯢' + 0x1fbe2 => circle(metrics, canvas, .bottom, false), + // '🯣' + 0x1fbe3 => circle(metrics, canvas, .left, false), + // '🯤' + 0x1fbe4 => block.block(metrics, canvas, .upper_center, 0.5, 0.5), + // '🯥' + 0x1fbe5 => block.block(metrics, canvas, .lower_center, 0.5, 0.5), + // '🯦' + 0x1fbe6 => block.block(metrics, canvas, .middle_left, 0.5, 0.5), + // '🯧' + 0x1fbe7 => block.block(metrics, canvas, .middle_right, 0.5, 0.5), + // '🯨' + 0x1fbe8 => circle(metrics, canvas, .top, true), + // '🯩' + 0x1fbe9 => circle(metrics, canvas, .right, true), + // '🯪' + 0x1fbea => circle(metrics, canvas, .bottom, true), + // '🯫' + 0x1fbeb => circle(metrics, canvas, .left, true), + // '🯬' + 0x1fbec => circle(metrics, canvas, .top_right, true), + // '🯭' + 0x1fbed => circle(metrics, canvas, .bottom_left, true), + // '🯮' + 0x1fbee => circle(metrics, canvas, .bottom_right, true), + // '🯯' + 0x1fbef => circle(metrics, canvas, .top_left, true), + + else => unreachable, + } +} + +fn edgeTriangle( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime edge: Edge, +) !void { + const upper: f64 = 0.0; + const middle: f64 = @round(@as(f64, @floatFromInt(metrics.cell_height)) / 2); + const lower: f64 = @floatFromInt(metrics.cell_height); + const left: f64 = 0.0; + const center: f64 = @round(@as(f64, @floatFromInt(metrics.cell_width)) / 2); + const right: f64 = @floatFromInt(metrics.cell_width); + + const x0, const y0, const x1, const y1 = switch (edge) { + .top => .{ right, upper, left, upper }, + .left => .{ left, upper, left, lower }, + .bottom => .{ left, lower, right, lower }, + .right => .{ right, lower, right, upper }, + }; + + var path = canvas.staticPath(5); // nodes.len = 0 + path.moveTo(center, middle); // +1, nodes.len = 1 + path.lineTo(x0, y0); // +1, nodes.len = 2 + path.lineTo(x1, y1); // +1, nodes.len = 3 + path.close(); // +2, nodes.len = 5 + + try canvas.fillPath(path.wrapped_path, .{}, .on); +} + +fn cornerDiagonalLines( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime corners: Quads, +) void { + const thick_px = Thickness.light.height(metrics.box_thickness); + + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const float_thick: f64 = @floatFromInt(thick_px); + const center_x: f64 = @floatFromInt(metrics.cell_width / 2 + metrics.cell_width % 2); + const center_y: f64 = @floatFromInt(metrics.cell_height / 2 + metrics.cell_height % 2); + + if (corners.tl) canvas.line(.{ + .p0 = .{ .x = center_x, .y = 0 }, + .p1 = .{ .x = 0, .y = center_y }, + }, float_thick, .on) catch {}; + + if (corners.tr) canvas.line(.{ + .p0 = .{ .x = center_x, .y = 0 }, + .p1 = .{ .x = float_width, .y = center_y }, + }, float_thick, .on) catch {}; + + if (corners.bl) canvas.line(.{ + .p0 = .{ .x = center_x, .y = float_height }, + .p1 = .{ .x = 0, .y = center_y }, + }, float_thick, .on) catch {}; + + if (corners.br) canvas.line(.{ + .p0 = .{ .x = center_x, .y = float_height }, + .p1 = .{ .x = float_width, .y = center_y }, + }, float_thick, .on) catch {}; +} + +fn cellDiagonal( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime from: Alignment, + comptime to: Alignment, +) void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + const x0: f64 = switch (from.horizontal) { + .left => 0, + .right => float_width, + .center => float_width / 2, + }; + const y0: f64 = switch (from.vertical) { + .top => 0, + .bottom => float_height, + .middle => float_height / 2, + }; + const x1: f64 = switch (to.horizontal) { + .left => 0, + .right => float_width, + .center => float_width / 2, + }; + const y1: f64 = switch (to.vertical) { + .top => 0, + .bottom => float_height, + .middle => float_height / 2, + }; + + canvas.line( + .{ + .p0 = .{ .x = x0, .y = y0 }, + .p1 = .{ .x = x1, .y = y1 }, + }, + @floatFromInt(Thickness.light.height(metrics.box_thickness)), + .on, + ) catch {}; +} + +fn checkerboardFill( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + parity: u1, +) void { + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + const x_size: usize = 4; + const y_size: usize = @intFromFloat(@round(4 * (float_height / float_width))); + for (0..x_size) |x| { + const x0 = (metrics.cell_width * x) / x_size; + const x1 = (metrics.cell_width * (x + 1)) / x_size; + for (0..y_size) |y| { + const y0 = (metrics.cell_height * y) / y_size; + const y1 = (metrics.cell_height * (y + 1)) / y_size; + if ((x + y) % 2 == parity) { + canvas.rect(.{ + .x = @intCast(x0), + .y = @intCast(y0), + .width = @intCast(x1 -| x0), + .height = @intCast(y1 -| y0), + }, .on); + } + } + } +} + +pub fn circle( + metrics: font.Metrics, + canvas: *font.sprite.Canvas, + comptime position: Alignment, + comptime filled: bool, +) void { + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + + const float_width: f64 = @floatFromInt(metrics.cell_width); + const float_height: f64 = @floatFromInt(metrics.cell_height); + + const x: f64 = switch (position.horizontal) { + .left => 0, + .right => float_width, + .center => float_width / 2, + }; + const y: f64 = switch (position.vertical) { + .top => 0, + .bottom => float_height, + .middle => float_height / 2, + }; + const r: f64 = 0.5 * @min(float_width, float_height); + + var ctx = canvas.getContext(); + defer ctx.deinit(); + ctx.setSource(.{ .opaque_pattern = .{ + .pixel = .{ .alpha8 = .{ .a = @intFromEnum(Shade.on) } }, + } }); + ctx.setLineWidth( + @floatFromInt(Thickness.light.height(metrics.box_thickness)), + ); + + if (filled) { + ctx.arc(x, y, r, 0, std.math.pi * 2) catch return; + ctx.closePath() catch return; + ctx.fill() catch return; + } else { + ctx.arc(x, y, r - ctx.line_width / 2, 0, std.math.pi * 2) catch return; + ctx.closePath() catch return; + ctx.stroke() catch return; + } +} diff --git a/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig new file mode 100644 index 000000000..f43949eb9 --- /dev/null +++ b/src/font/sprite/draw/symbols_for_legacy_computing_supplement.zig @@ -0,0 +1,628 @@ +//! Symbols for Legacy Computing Supplement | U+1CC00...U+1CEBF +//! https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing_Supplement +//! +//! 𜰀 𜰁 𜰂 𜰃 𜰄 𜰅 𜰆 𜰇 𜰈 𜰉 𜰊 𜰋 𜰌 𜰍 𜰎 𜰏 +//! 𜰐 𜰑 𜰒 𜰓 𜰔 𜰕 𜰖 𜰗 𜰘 𜰙 𜰚 𜰛 𜰜 𜰝 𜰞 𜰟 +//! 𜰠 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯 +//! 𜰰 𜰱 𜰲 𜰳 𜰴 𜰵 𜰶 𜰷 𜰸 𜰹 𜰺 𜰻 𜰼 𜰽 𜰾 𜰿 +//! 𜱀 𜱁 𜱂 𜱃 𜱄 𜱅 𜱆 𜱇 𜱈 𜱉 𜱊 𜱋 𜱌 𜱍 𜱎 𜱏 +//! 𜱐 𜱑 𜱒 𜱓 𜱔 𜱕 𜱖 𜱗 𜱘 𜱙 𜱚 𜱛 𜱜 𜱝 𜱞 𜱟 +//! 𜱠 𜱡 𜱢 𜱣 𜱤 𜱥 𜱦 𜱧 𜱨 𜱩 𜱪 𜱫 𜱬 𜱭 𜱮 𜱯 +//! 𜱰 𜱱 𜱲 𜱳 𜱴 𜱵 𜱶 𜱷 𜱸 𜱹 𜱺 𜱻 𜱼 𜱽 𜱾 𜱿 +//! 𜲀 𜲁 𜲂 𜲃 𜲄 𜲅 𜲆 𜲇 𜲈 𜲉 𜲊 𜲋 𜲌 𜲍 𜲎 𜲏 +//! 𜲐 𜲑 𜲒 𜲓 𜲔 𜲕 𜲖 𜲗 𜲘 𜲙 𜲚 𜲛 𜲜 𜲝 𜲞 𜲟 +//! 𜲠 𜲡 𜲢 𜲣 𜲤 𜲥 𜲦 𜲧 𜲨 𜲩 𜲪 𜲫 𜲬 𜲭 𜲮 𜲯 +//! 𜲰 𜲱 𜲲 𜲳 𜲴 𜲵 𜲶 𜲷 𜲸 𜲹 𜲺 𜲻 𜲼 𜲽 𜲾 𜲿 +//! 𜳀 𜳁 𜳂 𜳃 𜳄 𜳅 𜳆 𜳇 𜳈 𜳉 𜳊 𜳋 𜳌 𜳍 𜳎 𜳏 +//! 𜳐 𜳑 𜳒 𜳓 𜳔 𜳕 𜳖 𜳗 𜳘 𜳙 𜳚 𜳛 𜳜 𜳝 𜳞 𜳟 +//! 𜳠 𜳡 𜳢 𜳣 𜳤 𜳥 𜳦 𜳧 𜳨 𜳩 𜳪 𜳫 𜳬 𜳭 𜳮 𜳯 +//! 𜳰 𜳱 𜳲 𜳳 𜳴 𜳵 𜳶 𜳷 𜳸 𜳹 +//! 𜴀 𜴁 𜴂 𜴃 𜴄 𜴅 𜴆 𜴇 𜴈 𜴉 𜴊 𜴋 𜴌 𜴍 𜴎 𜴏 +//! 𜴐 𜴑 𜴒 𜴓 𜴔 𜴕 𜴖 𜴗 𜴘 𜴙 𜴚 𜴛 𜴜 𜴝 𜴞 𜴟 +//! 𜴠 𜴡 𜴢 𜴣 𜴤 𜴥 𜴦 𜴧 𜴨 𜴩 𜴪 𜴫 𜴬 𜴭 𜴮 𜴯 +//! 𜴰 𜴱 𜴲 𜴳 𜴴 𜴵 𜴶 𜴷 𜴸 𜴹 𜴺 𜴻 𜴼 𜴽 𜴾 𜴿 +//! 𜵀 𜵁 𜵂 𜵃 𜵄 𜵅 𜵆 𜵇 𜵈 𜵉 𜵊 𜵋 𜵌 𜵍 𜵎 𜵏 +//! 𜵐 𜵑 𜵒 𜵓 𜵔 𜵕 𜵖 𜵗 𜵘 𜵙 𜵚 𜵛 𜵜 𜵝 𜵞 𜵟 +//! 𜵠 𜵡 𜵢 𜵣 𜵤 𜵥 𜵦 𜵧 𜵨 𜵩 𜵪 𜵫 𜵬 𜵭 𜵮 𜵯 +//! 𜵰 𜵱 𜵲 𜵳 𜵴 𜵵 𜵶 𜵷 𜵸 𜵹 𜵺 𜵻 𜵼 𜵽 𜵾 𜵿 +//! 𜶀 𜶁 𜶂 𜶃 𜶄 𜶅 𜶆 𜶇 𜶈 𜶉 𜶊 𜶋 𜶌 𜶍 𜶎 𜶏 +//! 𜶐 𜶑 𜶒 𜶓 𜶔 𜶕 𜶖 𜶗 𜶘 𜶙 𜶚 𜶛 𜶜 𜶝 𜶞 𜶟 +//! 𜶠 𜶡 𜶢 𜶣 𜶤 𜶥 𜶦 𜶧 𜶨 𜶩 𜶪 𜶫 𜶬 𜶭 𜶮 𜶯 +//! 𜶰 𜶱 𜶲 𜶳 𜶴 𜶵 𜶶 𜶷 𜶸 𜶹 𜶺 𜶻 𜶼 𜶽 𜶾 𜶿 +//! 𜷀 𜷁 𜷂 𜷃 𜷄 𜷅 𜷆 𜷇 𜷈 𜷉 𜷊 𜷋 𜷌 𜷍 𜷎 𜷏 +//! 𜷐 𜷑 𜷒 𜷓 𜷔 𜷕 𜷖 𜷗 𜷘 𜷙 𜷚 𜷛 𜷜 𜷝 𜷞 𜷟 +//! 𜷠 𜷡 𜷢 𜷣 𜷤 𜷥 𜷦 𜷧 𜷨 𜷩 𜷪 𜷫 𜷬 𜷭 𜷮 𜷯 +//! 𜷰 𜷱 𜷲 𜷳 𜷴 𜷵 𜷶 𜷷 𜷸 𜷹 𜷺 𜷻 𜷼 𜷽 𜷾 𜷿 +//! 𜸀 𜸁 𜸂 𜸃 𜸄 𜸅 𜸆 𜸇 𜸈 𜸉 𜸊 𜸋 𜸌 𜸍 𜸎 𜸏 +//! 𜸐 𜸑 𜸒 𜸓 𜸔 𜸕 𜸖 𜸗 𜸘 𜸙 𜸚 𜸛 𜸜 𜸝 𜸞 𜸟 +//! 𜸠 𜸡 𜸢 𜸣 𜸤 𜸥 𜸦 𜸧 𜸨 𜸩 𜸪 𜸫 𜸬 𜸭 𜸮 𜸯 +//! 𜸰 𜸱 𜸲 𜸳 𜸴 𜸵 𜸶 𜸷 𜸸 𜸹 𜸺 𜸻 𜸼 𜸽 𜸾 𜸿 +//! 𜹀 𜹁 𜹂 𜹃 𜹄 𜹅 𜹆 𜹇 𜹈 𜹉 𜹊 𜹋 𜹌 𜹍 𜹎 𜹏 +//! 𜹐 𜹑 𜹒 𜹓 𜹔 𜹕 𜹖 𜹗 𜹘 𜹙 𜹚 𜹛 𜹜 𜹝 𜹞 𜹟 +//! 𜹠 𜹡 𜹢 𜹣 𜹤 𜹥 𜹦 𜹧 𜹨 𜹩 𜹪 𜹫 𜹬 𜹭 𜹮 𜹯 +//! 𜹰 𜹱 𜹲 𜹳 𜹴 𜹵 𜹶 𜹷 𜹸 𜹹 𜹺 𜹻 𜹼 𜹽 𜹾 𜹿 +//! 𜺀 𜺁 𜺂 𜺃 𜺄 𜺅 𜺆 𜺇 𜺈 𜺉 𜺊 𜺋 𜺌 𜺍 𜺎 𜺏 +//! 𜺐 𜺑 𜺒 𜺓 𜺔 𜺕 𜺖 𜺗 𜺘 𜺙 𜺚 𜺛 𜺜 𜺝 𜺞 𜺟 +//! 𜺠 𜺡 𜺢 𜺣 𜺤 𜺥 𜺦 𜺧 𜺨 𜺩 𜺪 𜺫 𜺬 𜺭 𜺮 𜺯 +//! 𜺰 𜺱 𜺲 𜺳 +//! + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +const z2d = @import("z2d"); + +const common = @import("common.zig"); +const Thickness = common.Thickness; +const Fraction = common.Fraction; +const Corner = common.Corner; +const Shade = common.Shade; +const fill = common.fill; + +const box = @import("box.zig"); +const sflc = @import("symbols_for_legacy_computing.zig"); + +const font = @import("../../main.zig"); + +const octant_min = 0x1cd00; +const octant_max = 0x1cde5; + +/// Octants +pub fn draw1CD00_1CDE5( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + + // Octant representation. We use the funny numeric string keys + // so its easier to parse the actual name used in the Symbols for + // Legacy Computing spec. + const Octant = packed struct(u8) { + @"1": bool = false, + @"2": bool = false, + @"3": bool = false, + @"4": bool = false, + @"5": bool = false, + @"6": bool = false, + @"7": bool = false, + @"8": bool = false, + }; + + // Parse the octant data. This is all done at comptime so + // that this is static data that is embedded in the binary. + const octants_len = octant_max - octant_min + 1; + const octants: [octants_len]Octant = comptime octants: { + @setEvalBranchQuota(10_000); + + var result: [octants_len]Octant = @splat(.{}); + var i: usize = 0; + + const data = @embedFile("octants.txt"); + var it = std.mem.splitScalar(u8, data, '\n'); + while (it.next()) |line| { + // Skip comments + if (line.len == 0 or line[0] == '#') continue; + + const current = &result[i]; + i += 1; + + // Octants are in the format "BLOCK OCTANT-1235". The numbers + // at the end are keys into our packed struct. Since we're + // at comptime we can metaprogram it all. + const idx = std.mem.indexOfScalar(u8, line, '-').?; + for (line[idx + 1 ..]) |c| @field(current, &.{c}) = true; + } + + assert(i == octants_len); + break :octants result; + }; + + const oct = octants[cp - octant_min]; + if (oct.@"1") fill(metrics, canvas, .zero, .half, .zero, .one_quarter); + if (oct.@"2") fill(metrics, canvas, .half, .full, .zero, .one_quarter); + if (oct.@"3") fill(metrics, canvas, .zero, .half, .one_quarter, .two_quarters); + if (oct.@"4") fill(metrics, canvas, .half, .full, .one_quarter, .two_quarters); + if (oct.@"5") fill(metrics, canvas, .zero, .half, .two_quarters, .three_quarters); + if (oct.@"6") fill(metrics, canvas, .half, .full, .two_quarters, .three_quarters); + if (oct.@"7") fill(metrics, canvas, .zero, .half, .three_quarters, .end); + if (oct.@"8") fill(metrics, canvas, .half, .full, .three_quarters, .end); +} + +// Separated Block Quadrants +// 𜰡 𜰢 𜰣 𜰤 𜰥 𜰦 𜰧 𜰨 𜰩 𜰪 𜰫 𜰬 𜰭 𜰮 𜰯 +pub fn draw1CC21_1CC2F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = metrics; + + // Struct laid out to match the codepoint order so we can cast from it. + const Quads = packed struct(u4) { + tl: bool, + tr: bool, + bl: bool, + br: bool, + }; + + const quad: Quads = @bitCast(@as(u4, @truncate(cp - 0x1CC20))); + + const gap: i32 = @intCast(@max(1, width / 12)); + + const mid_gap_x: i32 = gap * 2 + @as(i32, @intCast(width % 2)); + const mid_gap_y: i32 = gap * 2 + @as(i32, @intCast(height % 2)); + + const w: i32 = @divExact(@as(i32, @intCast(width)) - gap * 2 - mid_gap_x, 2); + const h: i32 = @divExact(@as(i32, @intCast(height)) - gap * 2 - mid_gap_y, 2); + + if (quad.tl) canvas.box( + gap, + gap, + gap + w, + gap + h, + .on, + ); + if (quad.tr) canvas.box( + gap + w + mid_gap_x, + gap, + gap + w + mid_gap_x + w, + gap + h, + .on, + ); + if (quad.bl) canvas.box( + gap, + gap + h + mid_gap_y, + gap + w, + gap + h + mid_gap_y + h, + .on, + ); + if (quad.br) canvas.box( + gap + w + mid_gap_x, + gap + h + mid_gap_y, + gap + w + mid_gap_x + w, + gap + h + mid_gap_y + h, + .on, + ); +} + +/// Twelfth and Quarter circle pieces. +/// 𜰰 𜰱 𜰲 𜰳 𜰴 𜰵 𜰶 𜰷 𜰸 𜰹 𜰺 𜰻 𜰼 𜰽 𜰾 𜰿 +/// +/// 𜰰𜰱𜰲𜰳 +/// 𜰴𜰵𜰶𜰷 +/// 𜰸𜰹𜰺𜰻 +/// 𜰼𜰽𜰾𜰿 +/// +/// These are actually ellipses, sized to touch +/// the edge of their enclosing set of cells. +pub fn draw1CC30_1CC3F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + switch (cp) { + // 𜰰 UPPER LEFT TWELFTH CIRCLE + 0x1CC30 => try circlePiece(canvas, width, height, metrics, 0, 0, 2, 2, .tl), + // 𜰱 UPPER CENTRE LEFT TWELFTH CIRCLE + 0x1CC31 => try circlePiece(canvas, width, height, metrics, 1, 0, 2, 2, .tl), + // 𜰲 UPPER CENTRE RIGHT TWELFTH CIRCLE + 0x1CC32 => try circlePiece(canvas, width, height, metrics, 2, 0, 2, 2, .tr), + // 𜰳 UPPER RIGHT TWELFTH CIRCLE + 0x1CC33 => try circlePiece(canvas, width, height, metrics, 3, 0, 2, 2, .tr), + // 𜰴 UPPER MIDDLE LEFT TWELFTH CIRCLE + 0x1CC34 => try circlePiece(canvas, width, height, metrics, 0, 1, 2, 2, .tl), + // 𜰵 UPPER LEFT QUARTER CIRCLE + 0x1CC35 => try circlePiece(canvas, width, height, metrics, 0, 0, 1, 1, .tl), + // 𜰶 UPPER RIGHT QUARTER CIRCLE + 0x1CC36 => try circlePiece(canvas, width, height, metrics, 1, 0, 1, 1, .tr), + // 𜰷 UPPER MIDDLE RIGHT TWELFTH CIRCLE + 0x1CC37 => try circlePiece(canvas, width, height, metrics, 3, 1, 2, 2, .tr), + // 𜰸 LOWER MIDDLE LEFT TWELFTH CIRCLE + 0x1CC38 => try circlePiece(canvas, width, height, metrics, 0, 2, 2, 2, .bl), + // 𜰹 LOWER LEFT QUARTER CIRCLE + 0x1CC39 => try circlePiece(canvas, width, height, metrics, 0, 1, 1, 1, .bl), + // 𜰺 LOWER RIGHT QUARTER CIRCLE + 0x1CC3A => try circlePiece(canvas, width, height, metrics, 1, 1, 1, 1, .br), + // 𜰻 LOWER MIDDLE RIGHT TWELFTH CIRCLE + 0x1CC3B => try circlePiece(canvas, width, height, metrics, 3, 2, 2, 2, .br), + // 𜰼 LOWER LEFT TWELFTH CIRCLE + 0x1CC3C => try circlePiece(canvas, width, height, metrics, 0, 3, 2, 2, .bl), + // 𜰽 LOWER CENTRE LEFT TWELFTH CIRCLE + 0x1CC3D => try circlePiece(canvas, width, height, metrics, 1, 3, 2, 2, .bl), + // 𜰾 LOWER CENTRE RIGHT TWELFTH CIRCLE + 0x1CC3E => try circlePiece(canvas, width, height, metrics, 2, 3, 2, 2, .br), + // 𜰿 LOWER RIGHT TWELFTH CIRCLE + 0x1CC3F => try circlePiece(canvas, width, height, metrics, 3, 3, 2, 2, .br), + else => unreachable, + } +} + +/// TODO: These two characters should be easy, but it's not clear how they're +/// meant to align with adjacent cells, what characters they're meant to +/// be used with: +/// - 1CC1F 𜰟 BOX DRAWINGS DOUBLE DIAGONAL UPPER RIGHT TO LOWER LEFT +/// - 1CC20 𜰠 BOX DRAWINGS DOUBLE DIAGONAL UPPER LEFT TO LOWER RIGHT +pub fn draw1CC1B_1CC1E( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + const w: i32 = @intCast(width); + const h: i32 = @intCast(height); + const t: i32 = @intCast(metrics.box_thickness); + switch (cp) { + // 𜰛 BOX DRAWINGS LIGHT HORIZONTAL AND UPPER RIGHT + 0x1CC1B => { + box.linesChar(metrics, canvas, .{ .left = .light, .right = .light }); + canvas.box(w - t, 0, w, @divFloor(h, 2), .on); + }, + // 𜰜 BOX DRAWINGS LIGHT HORIZONTAL AND LOWER RIGHT + 0x1CC1C => { + box.linesChar(metrics, canvas, .{ .left = .light, .right = .light }); + canvas.box(w - t, @divFloor(h, 2), w, h, .on); + }, + // 𜰝 BOX DRAWINGS LIGHT TOP AND UPPER LEFT + 0x1CC1D => { + canvas.box(0, 0, w, t, .on); + canvas.box(0, 0, t, @divFloor(h, 2), .on); + }, + // 𜰞 BOX DRAWINGS LIGHT BOTTOM AND LOWER LEFT + 0x1CC1E => { + canvas.box(0, h - t, w, h, .on); + canvas.box(0, @divFloor(h, 2), t, h, .on); + }, + else => unreachable, + } +} + +/// 𜸀 RIGHT HALF AND LEFT HALF WHITE CIRCLE +pub fn draw1CE00( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + sflc.circle(metrics, canvas, .left, false); + sflc.circle(metrics, canvas, .right, false); +} + +/// 𜸁 LOWER HALF AND UPPER HALF WHITE CIRCLE +pub fn draw1CE01( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + _ = width; + _ = height; + sflc.circle(metrics, canvas, .top, false); + sflc.circle(metrics, canvas, .bottom, false); +} + +/// 𜸋 LEFT HALF WHITE ELLIPSE +pub fn draw1CE0B( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + try circlePiece(canvas, width, height, metrics, 0, 0, 1, 0.5, .tl); + try circlePiece(canvas, width, height, metrics, 0, 0, 1, 0.5, .bl); +} + +/// 𜸌 RIGHT HALF WHITE ELLIPSE +pub fn draw1CE0C( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = cp; + try circlePiece(canvas, width, height, metrics, 1, 0, 1, 0.5, .tr); + try circlePiece(canvas, width, height, metrics, 1, 0, 1, 0.5, .br); +} + +pub fn draw1CE16_1CE19( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + const w: i32 = @intCast(width); + const h: i32 = @intCast(height); + const t: i32 = @intCast(metrics.box_thickness); + switch (cp) { + // 𜸖 BOX DRAWINGS LIGHT VERTICAL AND TOP RIGHT + 0x1CE16 => { + box.linesChar(metrics, canvas, .{ .up = .light, .down = .light }); + canvas.box(@divFloor(w, 2), 0, w, t, .on); + }, + // 𜸗 BOX DRAWINGS LIGHT VERTICAL AND BOTTOM RIGHT + 0x1CE17 => { + box.linesChar(metrics, canvas, .{ .up = .light, .down = .light }); + canvas.box(@divFloor(w, 2), h - t, w, h, .on); + }, + // 𜸘 BOX DRAWINGS LIGHT VERTICAL AND TOP LEFT + 0x1CE18 => { + box.linesChar(metrics, canvas, .{ .up = .light, .down = .light }); + canvas.box(0, 0, @divFloor(w, 2), t, .on); + }, + // 𜸙 BOX DRAWINGS LIGHT VERTICAL AND BOTTOM LEFT + 0x1CE19 => { + box.linesChar(metrics, canvas, .{ .up = .light, .down = .light }); + canvas.box(0, h - t, @divFloor(w, 2), h, .on); + }, + else => unreachable, + } +} + +/// Separated Block Sextants +pub fn draw1CE51_1CE8F( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = metrics; + + // Struct laid out to match the codepoint order so we can cast from it. + const Sextants = packed struct(u6) { + tl: bool, + tr: bool, + ml: bool, + mr: bool, + bl: bool, + br: bool, + }; + + const sex: Sextants = @bitCast(@as(u6, @truncate(cp - 0x1CE50))); + + const gap: i32 = @intCast(@max(1, width / 12)); + + const mid_gap_x: i32 = gap * 2 + @as(i32, @intCast(width % 2)); + const y_extra: i32 = @as(i32, @intCast(height % 3)); + const mid_gap_y: i32 = gap * 2 + @divFloor(y_extra, 2); + + const w: i32 = @divExact(@as(i32, @intCast(width)) - gap * 2 - mid_gap_x, 2); + const h: i32 = @divFloor( + @as(i32, @intCast(height)) - gap * 2 - mid_gap_y * 2, + 3, + ); + // Distribute any leftover height in to the middle row of blocks. + const h_m: i32 = @as(i32, @intCast(height)) - gap * 2 - mid_gap_y * 2 - h * 2; + + if (sex.tl) canvas.box( + gap, + gap, + gap + w, + gap + h, + .on, + ); + if (sex.tr) canvas.box( + gap + w + mid_gap_x, + gap, + gap + w + mid_gap_x + w, + gap + h, + .on, + ); + if (sex.ml) canvas.box( + gap, + gap + h + mid_gap_y, + gap + w, + gap + h + mid_gap_y + h_m, + .on, + ); + if (sex.mr) canvas.box( + gap + w + mid_gap_x, + gap + h + mid_gap_y, + gap + w + mid_gap_x + w, + gap + h + mid_gap_y + h_m, + .on, + ); + if (sex.bl) canvas.box( + gap, + gap + h + mid_gap_y + h_m + mid_gap_y, + gap + w, + gap + h + mid_gap_y + h_m + mid_gap_y + h, + .on, + ); + if (sex.br) canvas.box( + gap + w + mid_gap_x, + gap + h + mid_gap_y + h_m + mid_gap_y, + gap + w + mid_gap_x + w, + gap + h + mid_gap_y + h_m + mid_gap_y + h, + .on, + ); +} + +/// Sixteenth Blocks +pub fn draw1CE90_1CEAF( + cp: u32, + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, +) !void { + _ = width; + _ = height; + const q = Fraction.quarters; + switch (cp) { + // 𜺐 UPPER LEFT ONE SIXTEENTH BLOCK + 0x1CE90 => fill(metrics, canvas, q[0], q[1], q[0], q[1]), + // 𜺑 UPPER CENTRE LEFT ONE SIXTEENTH BLOCK + 0x1CE91 => fill(metrics, canvas, q[1], q[2], q[0], q[1]), + // 𜺒 UPPER CENTRE RIGHT ONE SIXTEENTH BLOCK + 0x1CE92 => fill(metrics, canvas, q[2], q[3], q[0], q[1]), + // 𜺓 UPPER RIGHT ONE SIXTEENTH BLOCK + 0x1CE93 => fill(metrics, canvas, q[3], q[4], q[0], q[1]), + // 𜺔 UPPER MIDDLE LEFT ONE SIXTEENTH BLOCK + 0x1CE94 => fill(metrics, canvas, q[0], q[1], q[1], q[2]), + // 𜺕 UPPER MIDDLE CENTRE LEFT ONE SIXTEENTH BLOCK + 0x1CE95 => fill(metrics, canvas, q[1], q[2], q[1], q[2]), + // 𜺖 UPPER MIDDLE CENTRE RIGHT ONE SIXTEENTH BLOCK + 0x1CE96 => fill(metrics, canvas, q[2], q[3], q[1], q[2]), + // 𜺗 UPPER MIDDLE RIGHT ONE SIXTEENTH BLOCK + 0x1CE97 => fill(metrics, canvas, q[3], q[4], q[1], q[2]), + // 𜺘 LOWER MIDDLE LEFT ONE SIXTEENTH BLOCK + 0x1CE98 => fill(metrics, canvas, q[0], q[1], q[2], q[3]), + // 𜺙 LOWER MIDDLE CENTRE LEFT ONE SIXTEENTH BLOCK + 0x1CE99 => fill(metrics, canvas, q[1], q[2], q[2], q[3]), + // 𜺚 LOWER MIDDLE CENTRE RIGHT ONE SIXTEENTH BLOCK + 0x1CE9A => fill(metrics, canvas, q[2], q[3], q[2], q[3]), + // 𜺛 LOWER MIDDLE RIGHT ONE SIXTEENTH BLOCK + 0x1CE9B => fill(metrics, canvas, q[3], q[4], q[2], q[3]), + // 𜺜 LOWER LEFT ONE SIXTEENTH BLOCK + 0x1CE9C => fill(metrics, canvas, q[0], q[1], q[3], q[4]), + // 𜺝 LOWER CENTRE LEFT ONE SIXTEENTH BLOCK + 0x1CE9D => fill(metrics, canvas, q[1], q[2], q[3], q[4]), + // 𜺞 LOWER CENTRE RIGHT ONE SIXTEENTH BLOCK + 0x1CE9E => fill(metrics, canvas, q[2], q[3], q[3], q[4]), + // 𜺟 LOWER RIGHT ONE SIXTEENTH BLOCK + 0x1CE9F => fill(metrics, canvas, q[3], q[4], q[3], q[4]), + + // 𜺠 RIGHT HALF LOWER ONE QUARTER BLOCK + 0x1CEA0 => fill(metrics, canvas, q[2], q[4], q[3], q[4]), + // 𜺡 RIGHT THREE QUARTERS LOWER ONE QUARTER BLOCK + 0x1CEA1 => fill(metrics, canvas, q[1], q[4], q[3], q[4]), + // 𜺢 LEFT THREE QUARTERS LOWER ONE QUARTER BLOCK + 0x1CEA2 => fill(metrics, canvas, q[0], q[3], q[3], q[4]), + // 𜺣 LEFT HALF LOWER ONE QUARTER BLOCK + 0x1CEA3 => fill(metrics, canvas, q[0], q[2], q[3], q[4]), + // 𜺤 LOWER HALF LEFT ONE QUARTER BLOCK + 0x1CEA4 => fill(metrics, canvas, q[0], q[1], q[2], q[4]), + // 𜺥 LOWER THREE QUARTERS LEFT ONE QUARTER BLOCK + 0x1CEA5 => fill(metrics, canvas, q[0], q[1], q[1], q[4]), + // 𜺦 UPPER THREE QUARTERS LEFT ONE QUARTER BLOCK + 0x1CEA6 => fill(metrics, canvas, q[0], q[1], q[0], q[3]), + // 𜺧 UPPER HALF LEFT ONE QUARTER BLOCK + 0x1CEA7 => fill(metrics, canvas, q[0], q[1], q[0], q[2]), + // 𜺨 LEFT HALF UPPER ONE QUARTER BLOCK + 0x1CEA8 => fill(metrics, canvas, q[0], q[2], q[0], q[1]), + // 𜺩 LEFT THREE QUARTERS UPPER ONE QUARTER BLOCK + 0x1CEA9 => fill(metrics, canvas, q[0], q[3], q[0], q[1]), + // 𜺪 RIGHT THREE QUARTERS UPPER ONE QUARTER BLOCK + 0x1CEAA => fill(metrics, canvas, q[1], q[4], q[0], q[1]), + // 𜺫 RIGHT HALF UPPER ONE QUARTER BLOCK + 0x1CEAB => fill(metrics, canvas, q[2], q[4], q[0], q[1]), + // 𜺬 UPPER HALF RIGHT ONE QUARTER BLOCK + 0x1CEAC => fill(metrics, canvas, q[3], q[4], q[0], q[2]), + // 𜺭 UPPER THREE QUARTERS RIGHT ONE QUARTER BLOCK + 0x1CEAD => fill(metrics, canvas, q[3], q[4], q[0], q[3]), + // 𜺮 LOWER THREE QUARTERS RIGHT ONE QUARTER BLOCK + 0x1CEAE => fill(metrics, canvas, q[3], q[4], q[1], q[4]), + // 𜺯 LOWER HALF RIGHT ONE QUARTER BLOCK + 0x1CEAF => fill(metrics, canvas, q[3], q[4], q[2], q[4]), + + else => unreachable, + } +} + +fn circlePiece( + canvas: *font.sprite.Canvas, + width: u32, + height: u32, + metrics: font.Metrics, + x: f64, + y: f64, + w: f64, + h: f64, + corner: Corner, +) !void { + // Radius in pixels of the arc we need to draw. + const wdth: f64 = @as(f64, @floatFromInt(width)) * w; + const hght: f64 = @as(f64, @floatFromInt(height)) * h; + + // Position in pixels (rather than cells) for x/y + const xp: f64 = @as(f64, @floatFromInt(width)) * x; + const yp: f64 = @as(f64, @floatFromInt(height)) * y; + + // Set the clip so we don't include anything outside of the cell. + canvas.clip_left = canvas.padding_x; + canvas.clip_right = canvas.padding_x; + canvas.clip_top = canvas.padding_y; + canvas.clip_bottom = canvas.padding_y; + + // Coefficient for approximating a circular arc. + const c: f64 = (std.math.sqrt2 - 1.0) * 4.0 / 3.0; + const cw = c * wdth; + const ch = c * hght; + + const thick: f64 = @floatFromInt(metrics.box_thickness); + const ht = thick * 0.5; + + var path = canvas.staticPath(2); + + switch (corner) { + .tl => { + path.moveTo(wdth - xp, ht - yp); + path.curveTo( + wdth - cw - xp, + ht - yp, + ht - xp, + hght - ch - yp, + ht - xp, + hght - yp, + ); + }, + .tr => { + path.moveTo(wdth - xp, ht - yp); + path.curveTo( + wdth + cw - xp, + ht - yp, + wdth * 2 - ht - xp, + hght - ch - yp, + wdth * 2 - ht - xp, + hght - yp, + ); + }, + .bl => { + path.moveTo(ht - xp, hght - yp); + path.curveTo( + ht - xp, + hght + ch - yp, + wdth - cw - xp, + hght * 2 - ht - yp, + wdth - xp, + hght * 2 - ht - yp, + ); + }, + .br => { + path.moveTo(wdth * 2 - ht - xp, hght - yp); + path.curveTo( + wdth * 2 - ht - xp, + hght + ch - yp, + wdth + cw - xp, + hght * 2 - ht - yp, + wdth - xp, + hght * 2 - ht - yp, + ); + }, + } + + try canvas.strokePath(path.wrapped_path, .{ + .line_cap_mode = .butt, + .line_width = @floatFromInt(metrics.box_thickness), + }, .on); +} diff --git a/src/font/sprite/testdata/Box.ppm b/src/font/sprite/testdata/Box.ppm deleted file mode 100644 index 6082475af..000000000 Binary files a/src/font/sprite/testdata/Box.ppm and /dev/null differ diff --git a/src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.png b/src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.png new file mode 100644 index 000000000..e04e7726b Binary files /dev/null and b/src/font/sprite/testdata/U+1CC00...U+1CCFF-11x21+2.png differ diff --git a/src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.png b/src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.png new file mode 100644 index 000000000..ce1b1c422 Binary files /dev/null and b/src/font/sprite/testdata/U+1CC00...U+1CCFF-12x24+3.png differ diff --git a/src/font/sprite/testdata/U+1CC00...U+1CCFF-18x36+4.png b/src/font/sprite/testdata/U+1CC00...U+1CCFF-18x36+4.png new file mode 100644 index 000000000..1c21f688a Binary files /dev/null and b/src/font/sprite/testdata/U+1CC00...U+1CCFF-18x36+4.png differ diff --git a/src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.png b/src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.png new file mode 100644 index 000000000..459822e63 Binary files /dev/null and b/src/font/sprite/testdata/U+1CC00...U+1CCFF-9x17+1.png differ diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.png new file mode 100644 index 000000000..8d7de36ac Binary files /dev/null and b/src/font/sprite/testdata/U+1CD00...U+1CDFF-11x21+2.png differ diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.png new file mode 100644 index 000000000..ab6bec96d Binary files /dev/null and b/src/font/sprite/testdata/U+1CD00...U+1CDFF-12x24+3.png differ diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.png new file mode 100644 index 000000000..43035aefb Binary files /dev/null and b/src/font/sprite/testdata/U+1CD00...U+1CDFF-18x36+4.png differ diff --git a/src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.png b/src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.png new file mode 100644 index 000000000..fc111e2d7 Binary files /dev/null and b/src/font/sprite/testdata/U+1CD00...U+1CDFF-9x17+1.png differ diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png new file mode 100644 index 000000000..b3cda82d1 Binary files /dev/null and b/src/font/sprite/testdata/U+1CE00...U+1CEFF-11x21+2.png differ diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png new file mode 100644 index 000000000..e076da7c5 Binary files /dev/null and b/src/font/sprite/testdata/U+1CE00...U+1CEFF-12x24+3.png differ diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png new file mode 100644 index 000000000..366be3867 Binary files /dev/null and b/src/font/sprite/testdata/U+1CE00...U+1CEFF-18x36+4.png differ diff --git a/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png b/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png new file mode 100644 index 000000000..5cd7a4efe Binary files /dev/null and b/src/font/sprite/testdata/U+1CE00...U+1CEFF-9x17+1.png differ diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.png new file mode 100644 index 000000000..023396feb Binary files /dev/null and b/src/font/sprite/testdata/U+1FB00...U+1FBFF-11x21+2.png differ diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.png new file mode 100644 index 000000000..2eff59c76 Binary files /dev/null and b/src/font/sprite/testdata/U+1FB00...U+1FBFF-12x24+3.png differ diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.png new file mode 100644 index 000000000..b77f7dfae Binary files /dev/null and b/src/font/sprite/testdata/U+1FB00...U+1FBFF-18x36+4.png differ diff --git a/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png b/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png new file mode 100644 index 000000000..062a7da81 Binary files /dev/null and b/src/font/sprite/testdata/U+1FB00...U+1FBFF-9x17+1.png differ diff --git a/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png b/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png new file mode 100644 index 000000000..9ae5b45ff Binary files /dev/null and b/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png differ diff --git a/src/font/sprite/testdata/U+2500...U+25FF-12x24+3.png b/src/font/sprite/testdata/U+2500...U+25FF-12x24+3.png new file mode 100644 index 000000000..89011df49 Binary files /dev/null and b/src/font/sprite/testdata/U+2500...U+25FF-12x24+3.png differ diff --git a/src/font/sprite/testdata/U+2500...U+25FF-18x36+4.png b/src/font/sprite/testdata/U+2500...U+25FF-18x36+4.png new file mode 100644 index 000000000..05ff2a5f2 Binary files /dev/null and b/src/font/sprite/testdata/U+2500...U+25FF-18x36+4.png differ diff --git a/src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png b/src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png new file mode 100644 index 000000000..cf96b7d15 Binary files /dev/null and b/src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png differ diff --git a/src/font/sprite/testdata/U+2800...U+28FF-11x21+2.png b/src/font/sprite/testdata/U+2800...U+28FF-11x21+2.png new file mode 100644 index 000000000..804e3017c Binary files /dev/null and b/src/font/sprite/testdata/U+2800...U+28FF-11x21+2.png differ diff --git a/src/font/sprite/testdata/U+2800...U+28FF-12x24+3.png b/src/font/sprite/testdata/U+2800...U+28FF-12x24+3.png new file mode 100644 index 000000000..aa3e2f99b Binary files /dev/null and b/src/font/sprite/testdata/U+2800...U+28FF-12x24+3.png differ diff --git a/src/font/sprite/testdata/U+2800...U+28FF-18x36+4.png b/src/font/sprite/testdata/U+2800...U+28FF-18x36+4.png new file mode 100644 index 000000000..49f431a43 Binary files /dev/null and b/src/font/sprite/testdata/U+2800...U+28FF-18x36+4.png differ diff --git a/src/font/sprite/testdata/U+2800...U+28FF-9x17+1.png b/src/font/sprite/testdata/U+2800...U+28FF-9x17+1.png new file mode 100644 index 000000000..4308c5361 Binary files /dev/null and b/src/font/sprite/testdata/U+2800...U+28FF-9x17+1.png differ diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png b/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png new file mode 100644 index 000000000..addd1f496 Binary files /dev/null and b/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png differ diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png b/src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png new file mode 100644 index 000000000..ddfa79e85 Binary files /dev/null and b/src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png differ diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png b/src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png new file mode 100644 index 000000000..787cbcb6c Binary files /dev/null and b/src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png differ diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-9x17+1.png b/src/font/sprite/testdata/U+E000...U+E0FF-9x17+1.png new file mode 100644 index 000000000..461516971 Binary files /dev/null and b/src/font/sprite/testdata/U+E000...U+E0FF-9x17+1.png differ diff --git a/src/font/sprite/testdata/U+F500...U+F5FF-11x21+2.png b/src/font/sprite/testdata/U+F500...U+F5FF-11x21+2.png new file mode 100644 index 000000000..103853790 Binary files /dev/null and b/src/font/sprite/testdata/U+F500...U+F5FF-11x21+2.png differ diff --git a/src/font/sprite/testdata/U+F500...U+F5FF-12x24+3.png b/src/font/sprite/testdata/U+F500...U+F5FF-12x24+3.png new file mode 100644 index 000000000..b1bb803ab Binary files /dev/null and b/src/font/sprite/testdata/U+F500...U+F5FF-12x24+3.png differ diff --git a/src/font/sprite/testdata/U+F500...U+F5FF-18x36+4.png b/src/font/sprite/testdata/U+F500...U+F5FF-18x36+4.png new file mode 100644 index 000000000..e7415b7ae Binary files /dev/null and b/src/font/sprite/testdata/U+F500...U+F5FF-18x36+4.png differ diff --git a/src/font/sprite/testdata/U+F500...U+F5FF-9x17+1.png b/src/font/sprite/testdata/U+F500...U+F5FF-9x17+1.png new file mode 100644 index 000000000..730a4c42c Binary files /dev/null and b/src/font/sprite/testdata/U+F500...U+F5FF-9x17+1.png differ diff --git a/src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.png b/src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.png new file mode 100644 index 000000000..b216174e4 Binary files /dev/null and b/src/font/sprite/testdata/U+F600...U+F6FF-11x21+2.png differ diff --git a/src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.png b/src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.png new file mode 100644 index 000000000..2243248b0 Binary files /dev/null and b/src/font/sprite/testdata/U+F600...U+F6FF-12x24+3.png differ diff --git a/src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.png b/src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.png new file mode 100644 index 000000000..f3a887d20 Binary files /dev/null and b/src/font/sprite/testdata/U+F600...U+F6FF-18x36+4.png differ diff --git a/src/font/sprite/testdata/U+F600...U+F6FF-9x17+1.png b/src/font/sprite/testdata/U+F600...U+F6FF-9x17+1.png new file mode 100644 index 000000000..82ce82468 Binary files /dev/null and b/src/font/sprite/testdata/U+F600...U+F6FF-9x17+1.png differ diff --git a/src/font/sprite/underline.zig b/src/font/sprite/underline.zig deleted file mode 100644 index d2e439e6a..000000000 --- a/src/font/sprite/underline.zig +++ /dev/null @@ -1,312 +0,0 @@ -//! This file renders underline sprites. To draw underlines, we render the -//! full cell-width as a sprite and then draw it as a separate pass to the -//! text. -//! -//! We used to render the underlines directly in the GPU shaders but its -//! annoying to support multiple types of underlines and its also annoying -//! to maintain and debug another set of shaders for each renderer instead of -//! just relying on the glyph system we already need to support for text -//! anyways. -//! -//! This also renders strikethrough, so its really more generally a -//! "horizontal line" renderer. -const std = @import("std"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const Allocator = std.mem.Allocator; -const font = @import("../main.zig"); -const Sprite = font.sprite.Sprite; - -/// Draw an underline. -pub fn renderGlyph( - alloc: Allocator, - atlas: *font.Atlas, - sprite: Sprite, - width: u32, - height: u32, - line_pos: u32, - line_thickness: u32, -) !font.Glyph { - // Draw the appropriate sprite - var canvas: font.sprite.Canvas, const offset_y: i32 = switch (sprite) { - .underline => try drawSingle(alloc, width, line_thickness), - .underline_double => try drawDouble(alloc, width, line_thickness), - .underline_dotted => try drawDotted(alloc, width, line_thickness), - .underline_dashed => try drawDashed(alloc, width, line_thickness), - .underline_curly => try drawCurly(alloc, width, line_thickness), - .overline => try drawSingle(alloc, width, line_thickness), - .strikethrough => try drawSingle(alloc, width, line_thickness), - else => unreachable, - }; - defer canvas.deinit(); - - // Write the drawing to the atlas - const region = try canvas.writeAtlas(alloc, atlas); - - return font.Glyph{ - .width = width, - .height = @intCast(region.height), - .offset_x = 0, - // Glyph.offset_y is the distance between the top of the glyph and the - // bottom of the cell. We want the top of the glyph to be at line_pos - // from the TOP of the cell, and then offset by the offset_y from the - // draw function. - .offset_y = @as(i32, @intCast(height -| line_pos)) - offset_y, - .atlas_x = region.x, - .atlas_y = region.y, - .advance_x = @floatFromInt(width), - }; -} - -/// A tuple with the canvas that the desired sprite was drawn on and -/// a recommended offset (+Y = down) to shift its Y position by, to -/// correct for underline styles with additional thickness. -const CanvasAndOffset = struct { font.sprite.Canvas, i32 }; - -/// Draw a single underline. -fn drawSingle(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { - const height: u32 = thickness; - var canvas = try font.sprite.Canvas.init(alloc, width, height); - - canvas.rect(.{ - .x = 0, - .y = 0, - .width = width, - .height = thickness, - }, .on); - - const offset_y: i32 = 0; - - return .{ canvas, offset_y }; -} - -/// Draw a double underline. -fn drawDouble(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { - // Our gap between lines will be at least 2px. - // (i.e. if our thickness is 1, we still have a gap of 2) - const gap = @max(2, thickness); - - const height: u32 = thickness * 2 * gap; - var canvas = try font.sprite.Canvas.init(alloc, width, height); - - canvas.rect(.{ - .x = 0, - .y = 0, - .width = width, - .height = thickness, - }, .on); - - canvas.rect(.{ - .x = 0, - .y = thickness * 2, - .width = width, - .height = thickness, - }, .on); - - const offset_y: i32 = -@as(i32, @intCast(thickness)); - - return .{ canvas, offset_y }; -} - -/// Draw a dotted underline. -fn drawDotted(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { - const height: u32 = thickness; - var canvas = try font.sprite.Canvas.init(alloc, width, height); - - const dot_width = @max(thickness, 3); - const dot_count = @max((width / dot_width) / 2, 1); - const gap_width = try std.math.divCeil(u32, width -| (dot_count * dot_width), dot_count); - var i: u32 = 0; - while (i < dot_count) : (i += 1) { - // Ensure we never go out of bounds for the rect - const x = @min(i * (dot_width + gap_width), width - 1); - const rect_width = @min(width - x, dot_width); - canvas.rect(.{ - .x = @intCast(x), - .y = 0, - .width = rect_width, - .height = thickness, - }, .on); - } - - const offset_y: i32 = 0; - - return .{ canvas, offset_y }; -} - -/// Draw a dashed underline. -fn drawDashed(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { - const height: u32 = thickness; - var canvas = try font.sprite.Canvas.init(alloc, width, height); - - const dash_width = width / 3 + 1; - const dash_count = (width / dash_width) + 1; - var i: u32 = 0; - while (i < dash_count) : (i += 2) { - // Ensure we never go out of bounds for the rect - const x = @min(i * dash_width, width - 1); - const rect_width = @min(width - x, dash_width); - canvas.rect(.{ - .x = @intCast(x), - .y = 0, - .width = rect_width, - .height = thickness, - }, .on); - } - - const offset_y: i32 = 0; - - return .{ canvas, offset_y }; -} - -/// Draw a curly underline. Thanks to Wez Furlong for providing -/// the basic math structure for this since I was lazy with the -/// geometry. -fn drawCurly(alloc: Allocator, width: u32, thickness: u32) !CanvasAndOffset { - const float_width: f64 = @floatFromInt(width); - // Because of we way we draw the undercurl, we end up making it around 1px - // thicker than it should be, to fix this we just reduce the thickness by 1. - // - // We use a minimum thickness of 0.414 because this empirically produces - // the nicest undercurls at 1px underline thickness; thinner tends to look - // too thin compared to straight underlines and has artefacting. - const float_thick: f64 = @max(0.414, @as(f64, @floatFromInt(thickness -| 1))); - - // Calculate the wave period for a single character - // `2 * pi...` = 1 peak per character - // `4 * pi...` = 2 peaks per character - const wave_period = 2 * std.math.pi / float_width; - - // The full amplitude of the wave can be from the bottom to the - // underline position. We also calculate our mid y point of the wave - const half_amplitude = 1.0 / wave_period; - const y_mid: f64 = half_amplitude + float_thick * 0.5 + 1; - - // This is used in calculating the offset curve estimate below. - const offset_factor = @min(1.0, float_thick * 0.5 * wave_period) * @min(1.0, half_amplitude * wave_period); - - const height: u32 = @intFromFloat(@ceil(half_amplitude + float_thick + 1) * 2); - - var canvas = try font.sprite.Canvas.init(alloc, width, height); - - // follow Xiaolin Wu's antialias algorithm to draw the curve - var x: u32 = 0; - while (x < width) : (x += 1) { - // We sample the wave function at the *middle* of each - // pixel column, to ensure that it renders symmetrically. - const t: f64 = (@as(f64, @floatFromInt(x)) + 0.5) * wave_period; - // Use the slope at this location to add thickness to - // the line on this column, counteracting the thinning - // caused by the slope. - // - // This is not the exact offset curve for a sine wave, - // but it's a decent enough approximation. - // - // How did I derive this? I stared at Desmos and fiddled - // with numbers for an hour until it was good enough. - const t_u: f64 = t + std.math.pi; - const slope_factor_u: f64 = (@sin(t_u) * @sin(t_u) * offset_factor) / ((1.0 + @cos(t_u / 2) * @cos(t_u / 2) * 2) * wave_period); - const slope_factor_l: f64 = (@sin(t) * @sin(t) * offset_factor) / ((1.0 + @cos(t / 2) * @cos(t / 2) * 2) * wave_period); - - const cosx: f64 = @cos(t); - // This will be the center of our stroke. - const y: f64 = y_mid + half_amplitude * cosx; - - // The upper pixel and lower pixel are - // calculated relative to the center. - const y_u: f64 = y - float_thick * 0.5 - slope_factor_u; - const y_l: f64 = y + float_thick * 0.5 + slope_factor_l; - const y_upper: u32 = @intFromFloat(@floor(y_u)); - const y_lower: u32 = @intFromFloat(@ceil(y_l)); - const alpha_u: u8 = @intFromFloat(@round(255 * (1.0 - @abs(y_u - @floor(y_u))))); - const alpha_l: u8 = @intFromFloat(@round(255 * (1.0 - @abs(y_l - @ceil(y_l))))); - - // upper and lower bounds - canvas.pixel(x, @min(y_upper, height - 1), @enumFromInt(alpha_u)); - canvas.pixel(x, @min(y_lower, height - 1), @enumFromInt(alpha_l)); - - // fill between upper and lower bound - var y_fill: u32 = y_upper + 1; - while (y_fill < y_lower) : (y_fill += 1) { - canvas.pixel(x, @min(y_fill, height - 1), .on); - } - } - - const offset_y: i32 = @intFromFloat(-@round(half_amplitude)); - - return .{ canvas, offset_y }; -} - -test "single" { - const testing = std.testing; - const alloc = testing.allocator; - - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - _ = try renderGlyph( - alloc, - &atlas_grayscale, - .underline, - 36, - 18, - 9, - 2, - ); -} - -test "strikethrough" { - const testing = std.testing; - const alloc = testing.allocator; - - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - _ = try renderGlyph( - alloc, - &atlas_grayscale, - .strikethrough, - 36, - 18, - 9, - 2, - ); -} - -test "single large thickness" { - const testing = std.testing; - const alloc = testing.allocator; - - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - // unrealistic thickness but used to cause a crash - // https://github.com/mitchellh/ghostty/pull/1548 - _ = try renderGlyph( - alloc, - &atlas_grayscale, - .underline, - 36, - 18, - 9, - 200, - ); -} - -test "curly" { - const testing = std.testing; - const alloc = testing.allocator; - - var atlas_grayscale = try font.Atlas.init(alloc, 512, .grayscale); - defer atlas_grayscale.deinit(alloc); - - _ = try renderGlyph( - alloc, - &atlas_grayscale, - .underline_curly, - 36, - 18, - 9, - 2, - ); -} diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 7cdb8047c..f76da360a 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -281,6 +281,10 @@ pub const Action = union(enum) { /// If there is a URL under the cursor, copy it to the default clipboard. copy_url_to_clipboard, + /// Copy the terminal title to the clipboard. If the terminal title is not + /// set or is empty this has no effect. + copy_title_to_clipboard, + /// Increase the font size by the specified amount in points (pt). /// /// For example, `increase_font_size:1.5` will increase the font size @@ -296,6 +300,12 @@ pub const Action = union(enum) { /// Reset the font size to the original configured size. reset_font_size, + /// Set the font size to the specified size in points (pt). + /// + /// For example, `set_font_size:14.5` will set the font size + /// to 14.5 points. + set_font_size: f32, + /// Clear the screen and all scrollback. clear_screen, @@ -999,11 +1009,13 @@ pub const Action = union(enum) { .reset, .copy_to_clipboard, .copy_url_to_clipboard, + .copy_title_to_clipboard, .paste_from_clipboard, .paste_from_selection, .increase_font_size, .decrease_font_size, .reset_font_size, + .set_font_size, .prompt_surface_title, .clear_screen, .select_all, @@ -3065,6 +3077,7 @@ test "set: getEvent codepoint case folding" { try testing.expect(action == null); } } + test "Action: clone" { const testing = std.testing; var arena = std.heap.ArenaAllocator.init(testing.allocator); @@ -3083,3 +3096,42 @@ test "Action: clone" { try testing.expect(b == .text); } } + +test "parse: increase_font_size" { + const testing = std.testing; + + { + const binding = try parseSingle("a=increase_font_size:1.5"); + try testing.expect(binding.action == .increase_font_size); + try testing.expectEqual(1.5, binding.action.increase_font_size); + } +} + +test "parse: decrease_font_size" { + const testing = std.testing; + + { + const binding = try parseSingle("a=decrease_font_size:2.5"); + try testing.expect(binding.action == .decrease_font_size); + try testing.expectEqual(2.5, binding.action.decrease_font_size); + } +} + +test "parse: reset_font_size" { + const testing = std.testing; + + { + const binding = try parseSingle("a=reset_font_size"); + try testing.expect(binding.action == .reset_font_size); + } +} + +test "parse: set_font_size" { + const testing = std.testing; + + { + const binding = try parseSingle("a=set_font_size:13.5"); + try testing.expect(binding.action == .set_font_size); + try testing.expectEqual(13.5, binding.action.set_font_size); + } +} diff --git a/src/input/command.zig b/src/input/command.zig index 693d5c8d4..84e9afc79 100644 --- a/src/input/command.zig +++ b/src/input/command.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Action = @import("Binding.zig").Action; @@ -131,6 +132,12 @@ fn actionCommands(action: Action.Key) []const Command { .description = "Copy the URL under the cursor to the clipboard.", }}, + .copy_title_to_clipboard => comptime &.{.{ + .action = .copy_title_to_clipboard, + .title = "Copy Terminal Title to Clipboard", + .description = "Copy the terminal title to the clipboard. If the terminal title is not set this has no effect.", + }}, + .paste_from_clipboard => comptime &.{.{ .action = .paste_from_clipboard, .title = "Paste from Clipboard", @@ -460,6 +467,7 @@ fn actionCommands(action: Action.Key) []const Command { .esc, .text, .cursor_key, + .set_font_size, .scroll_page_fractional, .scroll_page_lines, .adjust_selection, diff --git a/src/main_c.zig b/src/main_c.zig index 1b73d7327..2c266cfb5 100644 --- a/src/main_c.zig +++ b/src/main_c.zig @@ -19,7 +19,12 @@ const internal_os = @import("os/main.zig"); // Some comptime assertions that our C API depends on. comptime { - assert(apprt.runtime == apprt.embedded); + // We allow tests to reference this file because we unit test + // some of the C API. At runtime though we should never get these + // functions unless we are building libghostty. + if (!builtin.is_test) { + assert(apprt.runtime == apprt.embedded); + } } /// Global options so we can log. This is identical to main. @@ -29,7 +34,9 @@ comptime { // These structs need to be referenced so the `export` functions // are truly exported by the C API lib. _ = @import("config.zig").CAPI; - _ = apprt.runtime.CAPI; + if (@hasDecl(apprt.runtime, "CAPI")) { + _ = apprt.runtime.CAPI; + } } /// ghostty_info_s @@ -46,17 +53,29 @@ const Info = extern struct { }; }; -/// Initialize ghostty global state. It is possible to have more than -/// one global state but it has zero practical benefit. -export fn ghostty_init() c_int { +/// ghostty_string_s +pub const String = extern struct { + ptr: ?[*]const u8, + len: usize, + + pub const empty: String = .{ + .ptr = null, + .len = 0, + }; + + pub fn fromSlice(slice: []const u8) String { + return .{ + .ptr = slice.ptr, + .len = slice.len, + }; + } +}; + +/// Initialize ghostty global state. +export fn ghostty_init(argc: usize, argv: [*][*:0]u8) c_int { assert(builtin.link_libc); - // Since in the lib we don't go through start.zig, we need - // to populate argv so that inspecting std.os.argv doesn't - // touch uninitialized memory. - var argv: [0][*:0]u8 = .{}; - std.os.argv = &argv; - + std.os.argv = argv[0..argc]; state.init() catch |err| { std.log.err("failed to initialize ghostty error={}", .{err}); return 1; @@ -65,15 +84,17 @@ export fn ghostty_init() c_int { return 0; } -/// This is the entrypoint for the CLI version of Ghostty. This -/// is mutually exclusive to ghostty_init. Do NOT run ghostty_init -/// if you are going to run this. This will not return. -export fn ghostty_cli_main(argc: usize, argv: [*][*:0]u8) noreturn { - std.os.argv = argv[0..argc]; - main.main() catch |err| { - std.log.err("failed to run ghostty error={}", .{err}); +/// Runs an action if it is specified. If there is no action this returns +/// false. If there is an action then this doesn't return. +export fn ghostty_cli_try_action() void { + const action = state.action orelse return; + std.log.info("executing CLI action={}", .{action}); + posix.exit(action.run(state.alloc) catch |err| { + std.log.err("CLI action failed error={}", .{err}); posix.exit(1); - }; + }); + + posix.exit(0); } /// Return metadata about Ghostty, such as version, build mode, etc. @@ -99,3 +120,8 @@ export fn ghostty_info() Info { export fn ghostty_translate(msgid: [*:0]const u8) [*:0]const u8 { return internal_os.i18n._(msgid); } + +/// Free a string allocated by Ghostty. +export fn ghostty_string_free(str: String) void { + state.alloc.free(str.ptr.?[0..str.len]); +} diff --git a/src/main_ghostty.zig b/src/main_ghostty.zig index 567eec5f9..b747fe6f0 100644 --- a/src/main_ghostty.zig +++ b/src/main_ghostty.zig @@ -7,7 +7,6 @@ const Allocator = std.mem.Allocator; const posix = std.posix; const build_config = @import("build_config.zig"); const options = @import("build_options"); -const glfw = @import("glfw"); const glslang = @import("glslang"); const macos = @import("macos"); const oni = @import("oniguruma"); diff --git a/src/os/desktop.zig b/src/os/desktop.zig index 3bc843e5c..93bfb74bc 100644 --- a/src/os/desktop.zig +++ b/src/os/desktop.zig @@ -24,8 +24,15 @@ pub fn launchedFromDesktop() bool { // This special case is so that if we launch the app via the // app bundle (i.e. via open) then we still treat it as if it // was launched from the desktop. - if (build_config.artifact == .lib and - posix.getenv("GHOSTTY_MAC_APP") != null) break :macos true; + if (build_config.artifact == .lib) lib: { + const env = "GHOSTTY_MAC_LAUNCH_SOURCE"; + const source = posix.getenv(env) orelse break :lib; + + // Source can be "app", "cli", or "zig_run". We assume + // its the desktop only if its "app". We may want to do + // "zig_run" but at the moment there's no reason. + if (std.mem.eql(u8, source, "app")) break :macos true; + } break :macos c.getppid() == 1; }, diff --git a/src/os/i18n.zig b/src/os/i18n.zig index 6981d55a0..2ecae27ac 100644 --- a/src/os/i18n.zig +++ b/src/os/i18n.zig @@ -39,6 +39,7 @@ pub const locales = [_][:0]const u8{ "ru_RU.UTF-8", "uk_UA.UTF-8", "pl_PL.UTF-8", + "ko_KR.UTF-8", "mk_MK.UTF-8", "tr_TR.UTF-8", "id_ID.UTF-8", @@ -46,7 +47,9 @@ pub const locales = [_][:0]const u8{ "es_AR.UTF-8", "pt_BR.UTF-8", "ca_ES.UTF-8", + "bg_BG.UTF-8", "ga_IE.UTF-8", + "he_IL.UTF-8", }; /// Set for faster membership lookup of locales. diff --git a/src/os/kernel_info.zig b/src/os/kernel_info.zig new file mode 100644 index 000000000..9e3933dde --- /dev/null +++ b/src/os/kernel_info.zig @@ -0,0 +1,27 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +pub fn getKernelInfo(alloc: std.mem.Allocator) ?[]const u8 { + if (comptime builtin.os.tag != .linux) return null; + const path = "/proc/sys/kernel/osrelease"; + var file = std.fs.openFileAbsolute(path, .{}) catch return null; + defer file.close(); + + // 128 bytes should be enough to hold the kernel information + const kernel_info = file.readToEndAlloc(alloc, 128) catch return null; + defer alloc.free(kernel_info); + return alloc.dupe(u8, std.mem.trim(u8, kernel_info, &std.ascii.whitespace)) catch return null; +} + +test "read /proc/sys/kernel/osrelease" { + if (comptime builtin.os.tag != .linux) return null; + const allocator = std.testing.allocator; + + const kernel_info = try getKernelInfo(allocator); + defer allocator.free(kernel_info); + + // Since we can't hardcode the info in tests, just check + // if something was read from the file + try std.testing.expect(kernel_info.len > 0); + try std.testing.expect(!std.mem.eql(u8, kernel_info, "")); +} diff --git a/src/os/main.zig b/src/os/main.zig index 906e3d150..7398fc779 100644 --- a/src/os/main.zig +++ b/src/os/main.zig @@ -14,6 +14,7 @@ const openpkg = @import("open.zig"); const pipepkg = @import("pipe.zig"); const resourcesdir = @import("resourcesdir.zig"); const systemd = @import("systemd.zig"); +const kernelInfo = @import("kernel_info.zig"); // Namespaces pub const args = @import("args.zig"); @@ -58,6 +59,7 @@ pub const pipe = pipepkg.pipe; pub const resourcesDir = resourcesdir.resourcesDir; pub const ResourcesDir = resourcesdir.ResourcesDir; pub const ShellEscapeWriter = shell.ShellEscapeWriter; +pub const getKernelInfo = kernelInfo.getKernelInfo; test { _ = i18n; diff --git a/src/os/open.zig b/src/os/open.zig index ce62a7e0b..9b069c80f 100644 --- a/src/os/open.zig +++ b/src/os/open.zig @@ -1,24 +1,23 @@ const std = @import("std"); const builtin = @import("builtin"); const Allocator = std.mem.Allocator; +const apprt = @import("../apprt.zig"); const log = std.log.scoped(.@"os-open"); -/// The type of the data at the URL to open. This is used as a hint -/// to potentially open the URL in a different way. -pub const Type = enum { - text, - unknown, -}; - /// Open a URL in the default handling application. /// /// Any output on stderr is logged as a warning in the application logs. /// Output on stdout is ignored. The allocator is used to buffer the /// log output and may allocate from another thread. +/// +/// This function is purposely simple for the sake of providing +/// some portable way to open URLs. If you are implementing an +/// apprt for Ghostty, you should consider doing something special-cased +/// for your platform. pub fn open( alloc: Allocator, - typ: Type, + kind: apprt.action.OpenUrl.Kind, url: []const u8, ) !void { var exe: std.process.Child = switch (builtin.os.tag) { @@ -33,7 +32,7 @@ pub fn open( ), .macos => .init( - switch (typ) { + switch (kind) { .text => &.{ "open", "-t", url }, .unknown => &.{ "open", url }, }, diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 3899bb8c5..70be1a96b 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -5,7 +5,6 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const builtin = @import("builtin"); -const glfw = @import("glfw"); const objc = @import("objc"); const macos = @import("macos"); const graphics = macos.graphics; @@ -38,11 +37,6 @@ pub const swap_chain_count = 3; const log = std.log.scoped(.metal); -// Get native API access on certain platforms so we can do more customization. -const glfwNative = glfw.Native(.{ - .cocoa = builtin.os.tag == .macos, -}); - layer: IOSurfaceLayer, /// MTLDevice @@ -87,27 +81,6 @@ pub fn init(alloc: Allocator, opts: rendererpkg.Options) !Metal { // Get the metadata about our underlying view that we'll be rendering to. const info: ViewInfo = switch (apprt.runtime) { - apprt.glfw => info: { - // Everything in glfw is window-oriented so we grab the backing - // window, then derive everything from that. - const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow( - opts.rt_surface.window, - ).?); - - const contentView = objc.Object.fromId( - nswindow.getProperty(?*anyopaque, "contentView").?, - ); - const scaleFactor = nswindow.getProperty( - graphics.c.CGFloat, - "backingScaleFactor", - ); - - break :info .{ - .view = contentView, - .scaleFactor = scaleFactor, - }; - }, - apprt.embedded => .{ .scaleFactor = @floatCast(opts.rt_surface.content_scale.x), .view = switch (opts.rt_surface.platform) { diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index cf195361e..882d6fc03 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -5,7 +5,6 @@ const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const builtin = @import("builtin"); -const glfw = @import("glfw"); const gl = @import("opengl"); const shadertoy = @import("shadertoy.zig"); const apprt = @import("../apprt.zig"); @@ -60,18 +59,6 @@ pub fn deinit(self: *OpenGL) void { self.* = undefined; } -/// Returns the hints that we want for this -pub fn glfwWindowHints(config: *const configpkg.Config) glfw.Window.Hints { - _ = config; - return .{ - .context_version_major = MIN_VERSION_MAJOR, - .context_version_minor = MIN_VERSION_MINOR, - .opengl_profile = .opengl_core_profile, - .opengl_forward_compat = true, - .transparent_framebuffer = true, - }; -} - /// 32-bit windows cross-compilation breaks with `.c` for some reason, so... const gl_debug_proc_callconv = @typeInfo( @@ -172,8 +159,7 @@ fn prepareContext(getProcAddress: anytype) !void { /// This is called early right after surface creation. pub fn surfaceInit(surface: *apprt.Surface) !void { - // Treat this like a thread entry - const self: OpenGL = undefined; + _ = surface; switch (apprt.runtime) { else => @compileError("unsupported app runtime for OpenGL"), @@ -181,8 +167,6 @@ pub fn surfaceInit(surface: *apprt.Surface) !void { // GTK uses global OpenGL context so we load from null. apprt.gtk => try prepareContext(null), - apprt.glfw => try self.threadEnter(surface), - apprt.embedded => { // TODO(mitchellh): this does nothing today to allow libghostty // to compile for OpenGL targets but libghostty is strictly @@ -205,17 +189,12 @@ pub fn surfaceInit(surface: *apprt.Surface) !void { pub fn finalizeSurfaceInit(self: *const OpenGL, surface: *apprt.Surface) !void { _ = self; _ = surface; - - // For GLFW, we grabbed the OpenGL context in surfaceInit and - // we need to release it before we start the renderer thread. - if (apprt.runtime == apprt.glfw) { - glfw.makeContextCurrent(null); - } } /// Callback called by renderer.Thread when it begins. pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void { _ = self; + _ = surface; switch (apprt.runtime) { else => @compileError("unsupported app runtime for OpenGL"), @@ -227,21 +206,6 @@ pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void { // on the main thread. As such, we don't do anything here. }, - apprt.glfw => { - // We need to make the OpenGL context current. OpenGL requires - // that a single thread own the a single OpenGL context (if any). - // This ensures that the context switches over to our thread. - // Important: the prior thread MUST have detached the context - // prior to calling this entrypoint. - glfw.makeContextCurrent(surface.window); - errdefer glfw.makeContextCurrent(null); - glfw.swapInterval(1); - - // Load OpenGL bindings. This API is context-aware so this sets - // a threadlocal context for these pointers. - try prepareContext(&glfw.getProcAddress); - }, - apprt.embedded => { // TODO(mitchellh): this does nothing today to allow libghostty // to compile for OpenGL targets but libghostty is strictly @@ -262,11 +226,6 @@ pub fn threadExit(self: *const OpenGL) void { // be sharing the global bindings with other windows. }, - apprt.glfw => { - gl.glad.unload(); - glfw.makeContextCurrent(null); - }, - apprt.embedded => { // TODO: see threadEnter }, @@ -397,6 +356,10 @@ pub inline fn textureOptions(self: OpenGL) Texture.Options { .format = .rgba, .internal_format = .srgba, .target = .@"2D", + .min_filter = .linear, + .mag_filter = .linear, + .wrap_s = .clamp_to_edge, + .wrap_t = .clamp_to_edge, }; } @@ -429,6 +392,16 @@ pub inline fn imageTextureOptions( .format = format.toPixelFormat(), .internal_format = if (srgb) .srgba else .rgba, .target = .@"2D", + // TODO: Generate mipmaps for image textures and use + // linear_mipmap_linear filtering so that they + // look good even when scaled way down. + .min_filter = .linear, + .mag_filter = .linear, + // TODO: Separate out background image options, use + // repeating coordinate modes so we don't have + // to do the modulus in the shader. + .wrap_s = .clamp_to_edge, + .wrap_t = .clamp_to_edge, }; } @@ -450,6 +423,10 @@ pub fn initAtlasTexture( .format = format, .internal_format = internal_format, .target = .Rectangle, + .min_filter = .nearest, + .mag_filter = .nearest, + .wrap_s = .clamp_to_edge, + .wrap_t = .clamp_to_edge, }, atlas.size, atlas.size, diff --git a/src/renderer/cell.zig b/src/renderer/cell.zig index ef7122699..43d744176 100644 --- a/src/renderer/cell.zig +++ b/src/renderer/cell.zig @@ -103,11 +103,12 @@ pub const Contents = struct { // form a single grapheme, and multi-substitutions in fonts, the number // of glyphs in a row is theoretically unlimited. // - // We have size.rows + 1 lists because index 0 is used for a special - // list containing the cursor cell which needs to be first in the buffer. + // We have size.rows + 2 lists because indexes 0 and size.rows - 1 are + // used for special lists containing the cursor cell which need to + // be first and last in the buffer, respectively. var fg_rows = try ArrayListCollection(shaderpkg.CellText).init( alloc, - size.rows + 1, + size.rows + 2, size.columns * 3, ); errdefer fg_rows.deinit(alloc); @@ -118,14 +119,19 @@ pub const Contents = struct { self.bg_cells = bg_cells; self.fg_rows = fg_rows; - // We don't need 3*cols worth of cells for the cursor list, so we can - // replace it with a smaller list. This is technically a tiny bit of + // We don't need 3*cols worth of cells for the cursor lists, so we can + // replace them with smaller lists. This is technically a tiny bit of // extra work but resize is not a hot function so it's worth it to not // waste the memory. self.fg_rows.lists[0].deinit(alloc); self.fg_rows.lists[0] = try std.ArrayListUnmanaged( shaderpkg.CellText, ).initCapacity(alloc, 1); + + self.fg_rows.lists[size.rows + 1].deinit(alloc); + self.fg_rows.lists[size.rows + 1] = try std.ArrayListUnmanaged( + shaderpkg.CellText, + ).initCapacity(alloc, 1); } /// Reset the cell contents to an empty state without resizing. @@ -135,11 +141,18 @@ pub const Contents = struct { } /// Set the cursor value. If the value is null then the cursor is hidden. - pub fn setCursor(self: *Contents, v: ?shaderpkg.CellText) void { + pub fn setCursor(self: *Contents, v: ?shaderpkg.CellText, cursor_style: ?renderer.CursorStyle) void { self.fg_rows.lists[0].clearRetainingCapacity(); + self.fg_rows.lists[self.size.rows + 1].clearRetainingCapacity(); - if (v) |cell| { - self.fg_rows.lists[0].appendAssumeCapacity(cell); + const cell = v orelse return; + const style = cursor_style orelse return; + + switch (style) { + // Block cursors should be drawn first + .block => self.fg_rows.lists[0].appendAssumeCapacity(cell), + // Other cursor styles should be drawn last + .block_hollow, .bar, .underline, .lock => self.fg_rows.lists[self.size.rows + 1].appendAssumeCapacity(cell), } } @@ -205,103 +218,64 @@ pub fn isCovering(cp: u21) bool { }; } -pub const FgMode = enum { - /// Normal non-colored text rendering. The text can leave the cell - /// size if it is larger than the cell to allow for ligatures. - normal, +/// Returns the appropriate `constraint_width` for +/// the provided cell when rendering its glyph(s). +pub fn constraintWidth(cell_pin: terminal.Pin) u2 { + const cell = cell_pin.rowAndCell().cell; + const cp = cell.codepoint(); - /// Colored text rendering, specifically Emoji. - color, + if (!ziglyph.general_category.isPrivateUse(cp) and + !ziglyph.blocks.isDingbats(cp)) + { + return cell.gridWidth(); + } - /// Similar to normal but the text must be constrained to the cell - /// size. If a glyph is larger than the cell then it must be resized - /// to fit. - constrained, + // 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; - /// Similar to normal, but the text consists of Powerline glyphs and is - /// optionally exempt from padding color extension and minimum contrast requirements. - powerline, -}; + // If we have a previous cell and it was PUA then we need to + // also constrain. This is so that multiple PUA glyphs align. + // As an exception, we ignore powerline glyphs since they are + // used for box drawing and we consider them whitespace. + if (cell_pin.x > 0) prev: { + const prev_cp = prev_cp: { + var copy = cell_pin; + copy.x -= 1; + const prev_cell = copy.rowAndCell().cell; + break :prev_cp prev_cell.codepoint(); + }; -/// Returns the appropriate foreground mode for the given cell. This is -/// meant to be called from the typical updateCell function within a -/// renderer. -pub fn fgMode( - presentation: font.Presentation, - cell_pin: terminal.Pin, -) FgMode { - return switch (presentation) { - // Emoji is always full size and color. - .emoji => .color, + // We consider powerline glyphs whitespace. + if (isPowerline(prev_cp)) break :prev; - // If it is text it is slightly more complex. If we are a codepoint - // in the private use area and we are at the end or the next cell - // is not empty, we need to constrain rendering. - // - // We do this specifically so that Nerd Fonts can render their - // icons without overlapping with subsequent characters. But if - // the subsequent character is empty, then we allow it to use - // the full glyph size. See #1071. - .text => text: { - const cell = cell_pin.rowAndCell().cell; - const cp = cell.codepoint(); + if (ziglyph.general_category.isPrivateUse(prev_cp)) { + return 1; + } + } - if (!ziglyph.general_category.isPrivateUse(cp) and - !ziglyph.blocks.isDingbats(cp)) - { - break :text .normal; - } - - // Special-case Powerline glyphs. They exhibit box drawing behavior - // and should not be constrained. They have their own special category - // though because they're used for other logic (i.e. disabling - // min contrast). - if (isPowerline(cp)) { - break :text .powerline; - } - - // If we are at the end of the screen its definitely constrained - if (cell_pin.x == cell_pin.node.data.size.cols - 1) break :text .constrained; - - // If we have a previous cell and it was PUA then we need to - // also constrain. This is so that multiple PUA glyphs align. - // As an exception, we ignore powerline glyphs since they are - // used for box drawing and we consider them whitespace. - if (cell_pin.x > 0) prev: { - const prev_cp = prev_cp: { - var copy = cell_pin; - copy.x -= 1; - const prev_cell = copy.rowAndCell().cell; - break :prev_cp prev_cell.codepoint(); - }; - - // Powerline is whitespace - if (isPowerline(prev_cp)) break :prev; - - if (ziglyph.general_category.isPrivateUse(prev_cp)) { - break :text .constrained; - } - } - - // If the next cell is empty, then we allow it to use the - // full glyph size. - const next_cp = next_cp: { - var copy = cell_pin; - copy.x += 1; - const next_cell = copy.rowAndCell().cell; - break :next_cp next_cell.codepoint(); - }; - if (next_cp == 0 or - isSpace(next_cp) or - isPowerline(next_cp)) - { - break :text .normal; - } - - // Must be constrained - break :text .constrained; - }, + // If the next cell is whitespace, then + // we allow it to be up to two cells wide. + const next_cp = next_cp: { + var copy = cell_pin; + copy.x += 1; + const next_cell = copy.rowAndCell().cell; + break :next_cp next_cell.codepoint(); }; + if (next_cp == 0 or + isSpace(next_cp) or + isPowerline(next_cp)) + { + return 2; + } + + // Must be constrained + return 1; +} + +/// Whether min contrast should be disabled for a given glyph. +pub fn noMinContrast(cp: u21) bool { + // TODO: We should disable for all box drawing type characters. + return isPowerline(cp); } // Some general spaces, others intentionally kept @@ -348,7 +322,7 @@ test Contents { // Add some contents. const bg_cell: shaderpkg.CellBg = .{ 0, 0, 0, 1 }; const fg_cell: shaderpkg.CellText = .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ 4, 1 }, .color = .{ 0, 0, 0, 1 }, }; @@ -367,18 +341,23 @@ test Contents { } } - // Add a cursor. + // Add a block cursor. const cursor_cell: shaderpkg.CellText = .{ - .mode = .cursor, + .atlas = .grayscale, + .bools = .{ .is_cursor_glyph = true }, .grid_pos = .{ 2, 3 }, .color = .{ 0, 0, 0, 1 }, }; - c.setCursor(cursor_cell); + c.setCursor(cursor_cell, .block); try testing.expectEqual(cursor_cell, c.fg_rows.lists[0].items[0]); // And remove it. - c.setCursor(null); + c.setCursor(null, null); try testing.expectEqual(0, c.fg_rows.lists[0].items.len); + + // Add a hollow cursor. + c.setCursor(cursor_cell, .block_hollow); + try testing.expectEqual(cursor_cell, c.fg_rows.lists[rows + 1].items[0]); } test "Contents clear retains other content" { @@ -396,7 +375,7 @@ test "Contents clear retains other content" { // bg and fg cells in row 1 const bg_cell_1: shaderpkg.CellBg = .{ 0, 0, 0, 1 }; const fg_cell_1: shaderpkg.CellText = .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ 4, 1 }, .color = .{ 0, 0, 0, 1 }, }; @@ -405,7 +384,7 @@ test "Contents clear retains other content" { // bg and fg cells in row 2 const bg_cell_2: shaderpkg.CellBg = .{ 0, 0, 0, 1 }; const fg_cell_2: shaderpkg.CellText = .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ 4, 2 }, .color = .{ 0, 0, 0, 1 }, }; @@ -436,7 +415,7 @@ test "Contents clear last added content" { // bg and fg cells in row 1 const bg_cell_1: shaderpkg.CellBg = .{ 0, 0, 0, 1 }; const fg_cell_1: shaderpkg.CellText = .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ 4, 1 }, .color = .{ 0, 0, 0, 1 }, }; @@ -445,7 +424,7 @@ test "Contents clear last added content" { // bg and fg cells in row 2 const bg_cell_2: shaderpkg.CellBg = .{ 0, 0, 0, 1 }; const fg_cell_2: shaderpkg.CellText = .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ 4, 2 }, .color = .{ 0, 0, 0, 1 }, }; diff --git a/src/renderer/generic.zig b/src/renderer/generic.zig index fba577231..3965d302a 100644 --- a/src/renderer/generic.zig +++ b/src/renderer/generic.zig @@ -1,6 +1,5 @@ const std = @import("std"); const builtin = @import("builtin"); -const glfw = @import("glfw"); const xev = @import("xev"); const wuffs = @import("wuffs"); const apprt = @import("../apprt.zig"); @@ -13,7 +12,8 @@ const math = @import("../math.zig"); const Surface = @import("../Surface.zig"); const link = @import("link.zig"); const cellpkg = @import("cell.zig"); -const fgMode = cellpkg.fgMode; +const noMinContrast = cellpkg.noMinContrast; +const constraintWidth = cellpkg.constraintWidth; const isCovering = cellpkg.isCovering; const imagepkg = @import("image.zig"); const Image = imagepkg.Image; @@ -26,6 +26,8 @@ const ArenaAllocator = std.heap.ArenaAllocator; const Terminal = terminal.Terminal; const Health = renderer.Health; +const getConstraint = @import("../font/nerd_font_attributes.zig").getConstraint; + const FileType = @import("../file_type.zig").FileType; const macos = switch (builtin.os.tag) { @@ -133,12 +135,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { /// This is cursor color as set in the user's config, if any. If no cursor color /// is set in the user's config, then the cursor color is determined by the /// current foreground color. - default_cursor_color: ?terminal.color.RGB, - - /// When `cursor_color` is null, swap the foreground and background colors of - /// the cell under the cursor for the cursor color. Otherwise, use the default - /// foreground color as the cursor color. - cursor_invert: bool, + default_cursor_color: ?configpkg.Config.TerminalColor, /// The current set of cells to render. This is rebuilt on every frame /// but we keep this around so that we don't reallocate. Each set of @@ -514,17 +511,15 @@ pub fn Renderer(comptime GraphicsAPI: type) type { font_features: std.ArrayListUnmanaged([:0]const u8), font_styles: font.CodepointResolver.StyleStatus, font_shaping_break: configpkg.FontShapingBreak, - cursor_color: ?terminal.color.RGB, - cursor_invert: bool, + cursor_color: ?configpkg.Config.TerminalColor, cursor_opacity: f64, - cursor_text: ?terminal.color.RGB, + cursor_text: ?configpkg.Config.TerminalColor, background: terminal.color.RGB, background_opacity: f64, foreground: terminal.color.RGB, - selection_background: ?terminal.color.RGB, - selection_foreground: ?terminal.color.RGB, - invert_selection_fg_bg: bool, - bold_is_bright: bool, + selection_background: ?configpkg.Config.TerminalColor, + selection_foreground: ?configpkg.Config.TerminalColor, + bold_color: ?configpkg.BoldColor, min_contrast: f32, padding_color: configpkg.WindowPaddingColor, custom_shaders: configpkg.RepeatablePath, @@ -571,8 +566,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type { config.link.links.items, ); - const cursor_invert = config.@"cursor-invert-fg-bg"; - return .{ .background_opacity = @max(0, @min(1, config.@"background-opacity")), .font_thicken = config.@"font-thicken", @@ -581,36 +574,19 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .font_styles = font_styles, .font_shaping_break = config.@"font-shaping-break", - .cursor_color = if (!cursor_invert and config.@"cursor-color" != null) - config.@"cursor-color".?.toTerminalRGB() - else - null, - - .cursor_invert = cursor_invert, - - .cursor_text = if (config.@"cursor-text") |txt| - txt.toTerminalRGB() - else - null, - + .cursor_color = config.@"cursor-color", + .cursor_text = config.@"cursor-text", .cursor_opacity = @max(0, @min(1, config.@"cursor-opacity")), .background = config.background.toTerminalRGB(), .foreground = config.foreground.toTerminalRGB(), - .invert_selection_fg_bg = config.@"selection-invert-fg-bg", - .bold_is_bright = config.@"bold-is-bright", + .bold_color = config.@"bold-color", + .min_contrast = @floatCast(config.@"minimum-contrast"), .padding_color = config.@"window-padding-color", - .selection_background = if (config.@"selection-background") |bg| - bg.toTerminalRGB() - else - null, - - .selection_foreground = if (config.@"selection-foreground") |bg| - bg.toTerminalRGB() - else - null, + .selection_background = config.@"selection-background", + .selection_foreground = config.@"selection-foreground", .custom_shaders = custom_shaders, .bg_image = bg_image, @@ -633,20 +609,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type { } }; - /// Returns the hints that we want for this window. - pub fn glfwWindowHints(config: *const configpkg.Config) glfw.Window.Hints { - // If our graphics API provides hints, use them, - // otherwise fall back to generic hints. - if (@hasDecl(GraphicsAPI, "glfwWindowHints")) { - return GraphicsAPI.glfwWindowHints(config); - } - - return .{ - .client_api = .no_api, - .transparent_framebuffer = config.@"background-opacity" < 1, - }; - } - pub fn init(alloc: Allocator, options: renderer.Options) !Self { // Initialize our graphics API wrapper, this will prepare the // surface provided by the apprt and set up any API-specific @@ -703,7 +665,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .default_background_color = options.config.background, .cursor_color = null, .default_cursor_color = options.config.cursor_color, - .cursor_invert = options.config.cursor_invert, // Render state .cells = .{}, @@ -2079,8 +2040,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Set our new colors self.default_background_color = config.background; self.default_foreground_color = config.foreground; - self.default_cursor_color = if (!config.cursor_invert) config.cursor_color else null; - self.cursor_invert = config.cursor_invert; + self.default_cursor_color = config.cursor_color; const bg_image_config_changed = self.config.bg_image_fit != config.bg_image_fit or @@ -2577,28 +2537,32 @@ pub fn Renderer(comptime GraphicsAPI: type) type { else false; + // The `_style` suffixed values are the colors based on + // the cell style (SGR), before applying any additional + // configuration, inversions, selections, etc. const bg_style = style.bg(cell, color_palette); - const fg_style = style.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color; + const fg_style = style.fg(.{ + .default = self.foreground_color orelse self.default_foreground_color, + .palette = color_palette, + .bold = self.config.bold_color, + }); // The final background color for the cell. const bg = bg: { if (selected) { - break :bg if (self.config.invert_selection_fg_bg) - if (style.flags.inverse) - // Cell is selected with invert selection fg/bg - // enabled, and the cell has the inverse style - // flag, so they cancel out and we get the normal - // bg color. - bg_style - else - // If it doesn't have the inverse style - // flag then we use the fg color instead. - fg_style - else - // If we don't have invert selection fg/bg set then we - // just use the selection background if set, otherwise - // the default fg color. - break :bg self.config.selection_background orelse self.foreground_color orelse self.default_foreground_color; + // If we have an explicit selection background color + // specified int he config, use that + if (self.config.selection_background) |v| { + break :bg switch (v) { + .color => |color| color.toTerminalRGB(), + .@"cell-foreground" => if (style.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (style.flags.inverse) fg_style else bg_style, + }; + } + + // If no configuration, then our selection background + // is our foreground color. + break :bg self.foreground_color orelse self.default_foreground_color; } // Not selected @@ -2618,20 +2582,31 @@ pub fn Renderer(comptime GraphicsAPI: type) type { }; const fg = fg: { - if (selected and !self.config.invert_selection_fg_bg) { - // If we don't have invert selection fg/bg set - // then we just use the selection foreground if - // set, otherwise the default bg color. - break :fg self.config.selection_foreground orelse self.background_color orelse self.default_background_color; - } + // Our happy-path non-selection background color + // is our style or our configured defaults. + const final_bg = bg_style orelse + self.background_color orelse + self.default_background_color; // Whether we need to use the bg color as our fg color: + // - Cell is selected, inverted, and set to cell-foreground + // - Cell is selected, not inverted, and set to cell-background // - Cell is inverted and not selected - // - Cell is selected and not inverted - // Note: if selected then invert sel fg / bg must be - // false since we separately handle it if true above. - break :fg if (style.flags.inverse != selected) - bg_style orelse self.background_color orelse self.default_background_color + if (selected) { + // Use the selection foreground if set + if (self.config.selection_foreground) |v| { + break :fg switch (v) { + .color => |color| color.toTerminalRGB(), + .@"cell-foreground" => if (style.flags.inverse) final_bg else fg_style, + .@"cell-background" => if (style.flags.inverse) fg_style else final_bg, + }; + } + + break :fg self.background_color orelse self.default_background_color; + } + + break :fg if (style.flags.inverse) + final_bg else fg_style; }; @@ -2806,7 +2781,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Setup our cursor rendering information. cursor: { // By default, we don't handle cursor inversion on the shader. - self.cells.setCursor(null); + self.cells.setCursor(null, null); self.uniforms.cursor_pos = .{ std.math.maxInt(u16), std.math.maxInt(u16), @@ -2817,18 +2792,36 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Prepare the cursor cell contents. const style = cursor_style_ orelse break :cursor; - const cursor_color = self.cursor_color orelse self.default_cursor_color orelse color: { - if (self.cursor_invert) { - // Use the foreground color from the cell under the cursor, if any. - const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); - break :color if (sty.flags.inverse) - // If the cell is reversed, use background color instead. - (sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color) - else - (sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color); - } else { - break :color self.foreground_color orelse self.default_foreground_color; - } + const cursor_color = cursor_color: { + // If an explicit cursor color was set by OSC 12, use that. + if (self.cursor_color) |v| break :cursor_color v; + + // Use our configured color if specified + if (self.default_cursor_color) |v| switch (v) { + .color => |color| break :cursor_color color.toTerminalRGB(), + inline .@"cell-foreground", + .@"cell-background", + => |_, tag| { + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); + const fg_style = sty.fg(.{ + .default = self.foreground_color orelse self.default_foreground_color, + .palette = color_palette, + .bold = self.config.bold_color, + }); + const bg_style = sty.bg( + screen.cursor.page_cell, + color_palette, + ) orelse self.background_color orelse self.default_background_color; + + break :cursor_color switch (tag) { + .color => unreachable, + .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, + }; + }, + }; + + break :cursor_color self.foreground_color orelse self.default_foreground_color; }; self.addCursor(screen, style, cursor_color); @@ -2853,18 +2846,29 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .wide, .spacer_tail => true, }; - const uniform_color = if (self.cursor_invert) blk: { - // Use the background color from the cell under the cursor, if any. + const uniform_color = if (self.config.cursor_text) |txt| blk: { + // If cursor-text is set, then compute the correct color. + // Otherwise, use the background color. + if (txt == .color) { + // Use the color set by cursor-text, if any. + break :blk txt.color.toTerminalRGB(); + } + const sty = screen.cursor.page_pin.style(screen.cursor.page_cell); - break :blk if (sty.flags.inverse) - // If the cell is reversed, use foreground color instead. - (sty.fg(color_palette, self.config.bold_is_bright) orelse self.foreground_color orelse self.default_foreground_color) - else - (sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color); - } else if (self.config.cursor_text) |txt| - txt - else - self.background_color orelse self.default_background_color; + const fg_style = sty.fg(.{ + .default = self.foreground_color orelse self.default_foreground_color, + .palette = color_palette, + .bold = self.config.bold_color, + }); + const bg_style = sty.bg(screen.cursor.page_cell, color_palette) orelse self.background_color orelse self.default_background_color; + + break :blk switch (txt) { + // If the cell is reversed, use the opposite cell color instead. + .@"cell-foreground" => if (sty.flags.inverse) bg_style else fg_style, + .@"cell-background" => if (sty.flags.inverse) fg_style else bg_style, + else => unreachable, + }; + } else self.background_color orelse self.default_background_color; self.uniforms.cursor_color = .{ uniform_color.r, @@ -2930,9 +2934,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { ); try self.cells.add(self.alloc, .underline, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = 1, .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -2962,9 +2965,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { ); try self.cells.add(self.alloc, .overline, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = 1, .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -2994,9 +2996,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { ); try self.cells.add(self.alloc, .strikethrough, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = 1, .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -3021,6 +3022,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { const rac = cell_pin.rowAndCell(); const cell = rac.cell; + const cp = cell.codepoint(); + // Render const render = try self.font_grid.renderGlyph( self.alloc, @@ -3030,6 +3033,9 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .grid_metrics = self.grid_metrics, .thicken = self.config.font_thicken, .thicken_strength = self.config.font_thicken_strength, + .cell_width = cell.gridWidth(), + .constraint = getConstraint(cp), + .constraint_width = constraintWidth(cell_pin), }, ); @@ -3039,20 +3045,13 @@ pub fn Renderer(comptime GraphicsAPI: type) type { return; } - const mode: shaderpkg.CellText.Mode = switch (fgMode( - render.presentation, - cell_pin, - )) { - .normal => .fg, - .color => .fg_color, - .constrained => .fg_constrained, - .powerline => .fg_powerline, - }; - try self.cells.add(self.alloc, .text, .{ - .mode = mode, + .atlas = switch (render.presentation) { + .emoji => .color, + .text => .grayscale, + }, + .bools = .{ .no_min_contrast = noMinContrast(cp) }, .grid_pos = .{ @intCast(x), @intCast(y) }, - .constraint_width = cell.gridWidth(), .color = .{ color.r, color.g, color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, .glyph_size = .{ render.glyph.width, render.glyph.height }, @@ -3098,7 +3097,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { .block => .cursor_rect, .block_hollow => .cursor_hollow_rect, .bar => .cursor_bar, - .underline => .underline, + .underline => .cursor_underline, .lock => unreachable, }; @@ -3137,7 +3136,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type { }; self.cells.setCursor(.{ - .mode = .cursor, + .atlas = .grayscale, + .bools = .{ .is_cursor_glyph = true }, .grid_pos = .{ x, screen.cursor.y }, .color = .{ cursor_color.r, cursor_color.g, cursor_color.b, alpha }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, @@ -3146,7 +3146,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { @intCast(render.glyph.offset_x), @intCast(render.glyph.offset_y), }, - }); + }, cursor_style); } fn addPreeditCell( @@ -3186,7 +3186,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type { // Add our text try self.cells.add(self.alloc, .text, .{ - .mode = .fg, + .atlas = .grayscale, .grid_pos = .{ @intCast(coord.x), @intCast(coord.y) }, .color = .{ fg.r, fg.g, fg.b, 255 }, .glyph_pos = .{ render.glyph.atlas_x, render.glyph.atlas_y }, diff --git a/src/renderer/metal/Frame.zig b/src/renderer/metal/Frame.zig index 81b38e7b6..c766fb8ed 100644 --- a/src/renderer/metal/Frame.zig +++ b/src/renderer/metal/Frame.zig @@ -28,7 +28,7 @@ pub const Options = struct { /// MTLCommandBuffer buffer: objc.Object, -block: CompletionBlock, +block: CompletionBlock.Context, /// Begin encoding a frame. pub fn begin( @@ -47,7 +47,7 @@ pub fn begin( // Create our block to register for completion updates. // The block is deallocated by the objC runtime on success. - const block = try CompletionBlock.init( + const block = CompletionBlock.init( .{ .renderer = renderer, .target = target, @@ -55,7 +55,6 @@ pub fn begin( }, &bufferCompleted, ); - errdefer block.deinit(); return .{ .buffer = buffer, .block = block }; } @@ -114,24 +113,23 @@ pub inline fn complete(self: *Self, sync: bool) void { // If we don't need to complete synchronously, // we add our block as a completion handler. // - // It will be deallocated by the objc runtime on success. + // It will be copied when we add the handler, and then the + // copy will be deallocated by the objc runtime on success. if (!sync) { self.buffer.msgSend( void, objc.sel("addCompletedHandler:"), - .{self.block.context}, + .{&self.block}, ); } self.buffer.msgSend(void, objc.sel("commit"), .{}); // If we need to complete synchronously, we wait until - // the buffer is completed and call the callback directly, - // deiniting the block after we're done. + // the buffer is completed and invoke the block directly. if (sync) { self.buffer.msgSend(void, "waitUntilCompleted", .{}); - self.block.context.sync = true; - bufferCompleted(self.block.context, self.buffer.value); - self.block.deinit(); + self.block.sync = true; + CompletionBlock.invoke(&self.block, .{self.buffer.value}); } } diff --git a/src/renderer/metal/IOSurfaceLayer.zig b/src/renderer/metal/IOSurfaceLayer.zig index 9212bd5e1..5a6bf7307 100644 --- a/src/renderer/metal/IOSurfaceLayer.zig +++ b/src/renderer/metal/IOSurfaceLayer.zig @@ -54,13 +54,11 @@ pub inline fn setSurface(self: *IOSurfaceLayer, surface: *IOSurface) !void { // // We release in the callback after setting the contents. surface.retain(); - // We also need to retain the layer itself to make sure it - // isn't destroyed before the callback completes, since if - // that happens it will try to interact with a deallocated - // object. - _ = self.layer.retain(); + // NOTE: Since `self.layer` is passed as an `objc.c.id`, it's + // automatically retained when the block is copied, so we + // don't need to retain it ourselves like with the surface. - var block = try SetSurfaceBlock.init(.{ + var block = SetSurfaceBlock.init(.{ .layer = self.layer.value, .surface = surface, }, &setSurfaceCallback); @@ -68,15 +66,15 @@ pub inline fn setSurface(self: *IOSurfaceLayer, surface: *IOSurface) !void { // We check if we're on the main thread and run the block directly if so. const NSThread = objc.getClass("NSThread").?; if (NSThread.msgSend(bool, "isMainThread", .{})) { - setSurfaceCallback(block.context); - block.deinit(); + setSurfaceCallback(&block); } else { - // NOTE: The block will automatically be deallocated by the objc - // runtime once it's executed, so there's no need to deinit it. + // NOTE: The block will be copied when we pass it to dispatch_async, + // and then automatically be deallocated by the objc runtime + // once it's executed. macos.dispatch.dispatch_async( @ptrCast(macos.dispatch.queue.getMain()), - @ptrCast(block.context), + @ptrCast(&block), ); } } @@ -100,10 +98,7 @@ fn setSurfaceCallback( const surface: *IOSurface = block.surface; // See explanation of why we retain and release in `setSurface`. - defer { - surface.release(); - layer.release(); - } + defer surface.release(); // We check to see if the surface is the appropriate size for // the layer, if it's not then we discard it. This is because diff --git a/src/renderer/metal/Target.zig b/src/renderer/metal/Target.zig index fa62d3014..15780189b 100644 --- a/src/renderer/metal/Target.zig +++ b/src/renderer/metal/Target.zig @@ -68,7 +68,7 @@ pub fn init(opts: Options) !Self { const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{}); break :init id_init; }; - errdefer desc.msgSend(void, objc.sel("release"), .{}); + defer desc.release(); // Set our properties desc.setProperty("width", @as(c_ulong, @intCast(opts.width))); diff --git a/src/renderer/metal/Texture.zig b/src/renderer/metal/Texture.zig index 32820f8fc..5e6ef78d0 100644 --- a/src/renderer/metal/Texture.zig +++ b/src/renderer/metal/Texture.zig @@ -50,7 +50,7 @@ pub fn init( const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{}); break :init id_init; }; - errdefer desc.msgSend(void, objc.sel("release"), .{}); + defer desc.release(); // Set our properties desc.setProperty("pixelFormat", @intFromEnum(opts.pixel_format)); diff --git a/src/renderer/metal/shaders.zig b/src/renderer/metal/shaders.zig index 9fe0862ed..bf3bcc6e4 100644 --- a/src/renderer/metal/shaders.zig +++ b/src/renderer/metal/shaders.zig @@ -269,15 +269,16 @@ pub const CellText = extern struct { bearings: [2]i16 align(4) = .{ 0, 0 }, grid_pos: [2]u16 align(4), color: [4]u8 align(4), - mode: Mode align(1), - constraint_width: u8 align(1) = 0, + atlas: Atlas align(1), + bools: packed struct(u8) { + no_min_contrast: bool = false, + is_cursor_glyph: bool = false, + _padding: u6 = 0, + } align(1) = .{}, - pub const Mode = enum(u8) { - fg = 1, - fg_constrained = 2, - fg_color = 3, - cursor = 4, - fg_powerline = 5, + pub const Atlas = enum(u8) { + grayscale = 0, + color = 1, }; test { diff --git a/src/renderer/opengl/Texture.zig b/src/renderer/opengl/Texture.zig index 9be2b7078..2f3e7f46a 100644 --- a/src/renderer/opengl/Texture.zig +++ b/src/renderer/opengl/Texture.zig @@ -16,6 +16,10 @@ pub const Options = struct { format: gl.Texture.Format, internal_format: gl.Texture.InternalFormat, target: gl.Texture.Target, + min_filter: gl.Texture.MinFilter, + mag_filter: gl.Texture.MagFilter, + wrap_s: gl.Texture.Wrap, + wrap_t: gl.Texture.Wrap, }; texture: gl.Texture, @@ -48,10 +52,10 @@ pub fn init( { const texbind = tex.bind(opts.target) catch return error.OpenGLFailed; defer texbind.unbind(); - texbind.parameter(.WrapS, gl.c.GL_CLAMP_TO_EDGE) catch return error.OpenGLFailed; - texbind.parameter(.WrapT, gl.c.GL_CLAMP_TO_EDGE) catch return error.OpenGLFailed; - texbind.parameter(.MinFilter, gl.c.GL_LINEAR) catch return error.OpenGLFailed; - texbind.parameter(.MagFilter, gl.c.GL_LINEAR) catch return error.OpenGLFailed; + texbind.parameter(.WrapS, @intFromEnum(opts.wrap_s)) catch return error.OpenGLFailed; + texbind.parameter(.WrapT, @intFromEnum(opts.wrap_t)) catch return error.OpenGLFailed; + texbind.parameter(.MinFilter, @intFromEnum(opts.min_filter)) catch return error.OpenGLFailed; + texbind.parameter(.MagFilter, @intFromEnum(opts.mag_filter)) catch return error.OpenGLFailed; texbind.image2D( 0, opts.internal_format, diff --git a/src/renderer/opengl/shaders.zig b/src/renderer/opengl/shaders.zig index 0b67eaff0..80980bac7 100644 --- a/src/renderer/opengl/shaders.zig +++ b/src/renderer/opengl/shaders.zig @@ -237,15 +237,16 @@ pub const CellText = extern struct { bearings: [2]i16 align(4) = .{ 0, 0 }, grid_pos: [2]u16 align(4), color: [4]u8 align(4), - mode: Mode align(4), - constraint_width: u32 align(4) = 0, + atlas: Atlas align(1), + bools: packed struct(u8) { + no_min_contrast: bool = false, + is_cursor_glyph: bool = false, + _padding: u6 = 0, + } align(1) = .{}, - pub const Mode = enum(u32) { - fg = 1, - fg_constrained = 2, - fg_color = 3, - cursor = 4, - fg_powerline = 5, + pub const Atlas = enum(u8) { + grayscale = 0, + color = 1, }; // test { diff --git a/src/renderer/shaders/glsl/cell_bg.f.glsl b/src/renderer/shaders/glsl/cell_bg.f.glsl index 7ba6caaa6..fa48c6736 100644 --- a/src/renderer/shaders/glsl/cell_bg.f.glsl +++ b/src/renderer/shaders/glsl/cell_bg.f.glsl @@ -1,7 +1,7 @@ #include "common.glsl" // Position the origin to the upper left -layout(origin_upper_left, pixel_center_integer) in vec4 gl_FragCoord; +layout(origin_upper_left) in vec4 gl_FragCoord; // Must declare this output for some versions of OpenGL. layout(location = 0) out vec4 out_FragColor; diff --git a/src/renderer/shaders/glsl/cell_text.f.glsl b/src/renderer/shaders/glsl/cell_text.f.glsl index fda6d8134..176efcbde 100644 --- a/src/renderer/shaders/glsl/cell_text.f.glsl +++ b/src/renderer/shaders/glsl/cell_text.f.glsl @@ -4,21 +4,15 @@ layout(binding = 0) uniform sampler2DRect atlas_grayscale; layout(binding = 1) uniform sampler2DRect atlas_color; in CellTextVertexOut { - flat uint mode; + flat uint atlas; flat vec4 color; flat vec4 bg_color; vec2 tex_coord; } in_data; -// These are the possible modes that "mode" can be set to. This is -// used to multiplex multiple render modes into a single shader. -// -// NOTE: this must be kept in sync with the fragment shader -const uint MODE_TEXT = 1u; -const uint MODE_TEXT_CONSTRAINED = 2u; -const uint MODE_TEXT_COLOR = 3u; -const uint MODE_TEXT_CURSOR = 4u; -const uint MODE_TEXT_POWERLINE = 5u; +// Values `atlas` can take. +const uint ATLAS_GRAYSCALE = 0u; +const uint ATLAS_COLOR = 1u; // Must declare this output for some versions of OpenGL. layout(location = 0) out vec4 out_FragColor; @@ -27,12 +21,9 @@ void main() { bool use_linear_blending = (bools & USE_LINEAR_BLENDING) != 0; bool use_linear_correction = (bools & USE_LINEAR_CORRECTION) != 0; - switch (in_data.mode) { + switch (in_data.atlas) { default: - case MODE_TEXT_CURSOR: - case MODE_TEXT_CONSTRAINED: - case MODE_TEXT_POWERLINE: - case MODE_TEXT: + case ATLAS_GRAYSCALE: { // Our input color is always linear. vec4 color = in_data.color; @@ -84,7 +75,7 @@ void main() { return; } - case MODE_TEXT_COLOR: + case ATLAS_COLOR: { // For now, we assume that color glyphs // are already premultiplied linear colors. diff --git a/src/renderer/shaders/glsl/cell_text.v.glsl b/src/renderer/shaders/glsl/cell_text.v.glsl index 10965ddd2..7e38e2f0c 100644 --- a/src/renderer/shaders/glsl/cell_text.v.glsl +++ b/src/renderer/shaders/glsl/cell_text.v.glsl @@ -15,22 +15,22 @@ layout(location = 3) in uvec2 grid_pos; // The color of the rendered text glyph. layout(location = 4) in uvec4 color; -// The mode for this cell. -layout(location = 5) in uint mode; +// Which atlas this glyph is in. +layout(location = 5) in uint atlas; -// The width to constrain the glyph to, in cells, or 0 for no constraint. -layout(location = 6) in uint constraint_width; +// Misc glyph properties. +layout(location = 6) in uint glyph_bools; -// These are the possible modes that "mode" can be set to. This is -// used to multiplex multiple render modes into a single shader. -const uint MODE_TEXT = 1u; -const uint MODE_TEXT_CONSTRAINED = 2u; -const uint MODE_TEXT_COLOR = 3u; -const uint MODE_TEXT_CURSOR = 4u; -const uint MODE_TEXT_POWERLINE = 5u; +// Values `atlas` can take. +const uint ATLAS_GRAYSCALE = 0u; +const uint ATLAS_COLOR = 1u; + +// Masks for the `glyph_bools` attribute +const uint NO_MIN_CONTRAST = 1u; +const uint IS_CURSOR_GLYPH = 2u; out CellTextVertexOut { - flat uint mode; + flat uint atlas; flat vec4 color; flat vec4 bg_color; vec2 tex_coord; @@ -69,7 +69,7 @@ void main() { corner.x = float(vid == 1 || vid == 3); corner.y = float(vid == 2 || vid == 3); - out_data.mode = mode; + out_data.atlas = atlas; // === Grid Cell === // +X @@ -102,25 +102,6 @@ void main() { offset.y = cell_size.y - offset.y; - // If we're constrained then we need to scale the glyph. - if (mode == MODE_TEXT_CONSTRAINED) { - float max_width = cell_size.x * constraint_width; - // If this glyph is wider than the constraint width, - // fit it to the width and remove its horizontal offset. - if (size.x > max_width) { - float new_y = size.y * (max_width / size.x); - offset.y += (size.y - new_y) / 2.0; - offset.x = 0.0; - size.y = new_y; - size.x = max_width; - } else if (max_width - size.x > offset.x) { - // However, if it does fit in the constraint width, make - // sure the offset is small enough to not push it over the - // right edge of the constraint width. - offset.x = max_width - size.x; - } - } - // Calculate the final position of the cell which uses our glyph size // and glyph offset to create the correct bounding box for the glyph. cell_pos = cell_pos + size * corner + offset; @@ -149,11 +130,7 @@ void main() { // If we have a minimum contrast, we need to check if we need to // change the color of the text to ensure it has enough contrast // with the background. - // We only apply this adjustment to "normal" text with MODE_TEXT, - // since we want color glyphs to appear in their original color - // and Powerline glyphs to be unaffected (else parts of the line would - // have different colors as some parts are displayed via background colors). - if (min_contrast > 1.0f && mode == MODE_TEXT) { + if (min_contrast > 1.0f && (glyph_bools & NO_MIN_CONTRAST) == 0) { // Ensure our minimum contrast out_data.color = contrasted_color(min_contrast, out_data.color, out_data.bg_color); } @@ -161,8 +138,9 @@ void main() { // Check if current position is under cursor (including wide cursor) bool is_cursor_pos = ((grid_pos.x == cursor_pos.x) || (cursor_wide && (grid_pos.x == (cursor_pos.x + 1)))) && (grid_pos.y == cursor_pos.y); - // If this cell is the cursor cell, then we need to change the color. - if (mode != MODE_TEXT_CURSOR && is_cursor_pos) { + // If this cell is the cursor cell, but we're not processing + // the cursor glyph itself, then we need to change the color. + if ((glyph_bools & IS_CURSOR_GLYPH) == 0 && is_cursor_pos) { out_data.color = load_color(unpack4u8(cursor_color_packed_4u8), use_linear_blending); } } diff --git a/src/renderer/shaders/shaders.metal b/src/renderer/shaders/shaders.metal index b62e0c3cf..4797f89e4 100644 --- a/src/renderer/shaders/shaders.metal +++ b/src/renderer/shaders/shaders.metal @@ -509,13 +509,17 @@ fragment float4 cell_bg_fragment( //------------------------------------------------------------------- #pragma mark - Cell Text Shader -// The possible modes that a cell fg entry can take. -enum CellTextMode : uint8_t { - MODE_TEXT = 1u, - MODE_TEXT_CONSTRAINED = 2u, - MODE_TEXT_COLOR = 3u, - MODE_TEXT_CURSOR = 4u, - MODE_TEXT_POWERLINE = 5u, +enum CellTextAtlas : uint8_t { + ATLAS_GRAYSCALE = 0u, + ATLAS_COLOR = 1u, +}; + +// We use a packed struct of bools for misc properties of the glyph. +enum CellTextBools : uint8_t { + // Don't apply min contrast to this glyph. + NO_MIN_CONTRAST = 1u, + // This is the cursor glyph. + IS_CURSOR_GLYPH = 2u, }; struct CellTextVertexIn { @@ -534,16 +538,16 @@ struct CellTextVertexIn { // The color of the rendered text glyph. uchar4 color [[attribute(4)]]; - // The mode for this cell. - uint8_t mode [[attribute(5)]]; + // Which atlas to sample for our glyph. + uint8_t atlas [[attribute(5)]]; - // The width to constrain the glyph to, in cells, or 0 for no constraint. - uint8_t constraint_width [[attribute(6)]]; + // Misc properties of the glyph. + uint8_t bools [[attribute(6)]]; }; struct CellTextVertexOut { float4 position [[position]]; - uint8_t mode [[flat]]; + uint8_t atlas [[flat]]; float4 color [[flat]]; float4 bg_color [[flat]]; float2 tex_coord; @@ -577,7 +581,7 @@ vertex CellTextVertexOut cell_text_vertex( corner.y = float(vid == 2 || vid == 3); CellTextVertexOut out; - out.mode = in.mode; + out.atlas = in.atlas; // === Grid Cell === // +X @@ -610,25 +614,6 @@ vertex CellTextVertexOut cell_text_vertex( offset.y = uniforms.cell_size.y - offset.y; - // If we're constrained then we need to scale the glyph. - if (in.mode == MODE_TEXT_CONSTRAINED) { - float max_width = uniforms.cell_size.x * in.constraint_width; - // If this glyph is wider than the constraint width, - // fit it to the width and remove its horizontal offset. - if (size.x > max_width) { - float new_y = size.y * (max_width / size.x); - offset.y += (size.y - new_y) / 2; - offset.x = 0; - size.y = new_y; - size.x = max_width; - } else if (max_width - size.x > offset.x) { - // However, if it does fit in the constraint width, make - // sure the offset is small enough to not push it over the - // right edge of the constraint width. - offset.x = max_width - size.x; - } - } - // Calculate the final position of the cell which uses our glyph size // and glyph offset to create the correct bounding box for the glyph. cell_pos = cell_pos + size * corner + offset; @@ -665,11 +650,7 @@ vertex CellTextVertexOut cell_text_vertex( // If we have a minimum contrast, we need to check if we need to // change the color of the text to ensure it has enough contrast // with the background. - // We only apply this adjustment to "normal" text with MODE_TEXT, - // since we want color glyphs to appear in their original color - // and Powerline glyphs to be unaffected (else parts of the line would - // have different colors as some parts are displayed via background colors). - if (uniforms.min_contrast > 1.0f && in.mode == MODE_TEXT) { + if (uniforms.min_contrast > 1.0f && (in.bools & NO_MIN_CONTRAST) == 0) { // Ensure our minimum contrast out.color = contrasted_color(uniforms.min_contrast, out.color, out.bg_color); } @@ -681,8 +662,9 @@ vertex CellTextVertexOut cell_text_vertex( in.grid_pos.x == uniforms.cursor_pos.x + 1 ) && in.grid_pos.y == uniforms.cursor_pos.y; - // If this cell is the cursor cell, then we need to change the color. - if (in.mode != MODE_TEXT_CURSOR && is_cursor_pos) { + // If this cell is the cursor cell, but we're not processing + // the cursor glyph itself, then we need to change the color. + if ((in.bools & IS_CURSOR_GLYPH) == 0 && is_cursor_pos) { out.color = load_color( uniforms.cursor_color, uniforms.use_display_p3, @@ -702,19 +684,12 @@ fragment float4 cell_text_fragment( constexpr sampler textureSampler( coord::pixel, address::clamp_to_edge, - // TODO(qwerasd): This can be changed back to filter::nearest when - // we move the constraint logic out of the GPU code - // which should once again guarantee pixel perfect - // sizing. - filter::linear + filter::nearest ); - switch (in.mode) { + switch (in.atlas) { default: - case MODE_TEXT_CURSOR: - case MODE_TEXT_CONSTRAINED: - case MODE_TEXT_POWERLINE: - case MODE_TEXT: { + case ATLAS_GRAYSCALE: { // Our input color is always linear. float4 color = in.color; @@ -764,7 +739,7 @@ fragment float4 cell_text_fragment( return color; } - case MODE_TEXT_COLOR: { + case ATLAS_COLOR: { // For now, we assume that color glyphs // are already premultiplied linear colors. float4 color = textureColor.sample(textureSampler, in.tex_coord); diff --git a/src/shell-integration/bash/ghostty.bash b/src/shell-integration/bash/ghostty.bash index 0766198f9..df4c7f9a7 100644 --- a/src/shell-integration/bash/ghostty.bash +++ b/src/shell-integration/bash/ghostty.bash @@ -106,7 +106,6 @@ _ghostty_last_reported_cwd="" function __ghostty_precmd() { local ret="$?" if test "$_ghostty_executing" != "0"; then - _GHOSTTY_SAVE_PS0="$PS0" _GHOSTTY_SAVE_PS1="$PS1" _GHOSTTY_SAVE_PS2="$PS2" @@ -123,8 +122,8 @@ function __ghostty_precmd() { # Cursor if [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor"* ]]; then - PS1=$PS1'\[\e[5 q\]' - PS0=$PS0'\[\e[0 q\]' + [[ "$PS1" != *'\[\e[5 q\]'* ]] && PS1=$PS1'\[\e[5 q\]' # input + [[ "$PS0" != *'\[\e[0 q\]'* ]] && PS0=$PS0'\[\e[0 q\]' # reset fi # Title (working directory) @@ -154,7 +153,6 @@ function __ghostty_precmd() { function __ghostty_preexec() { builtin local cmd="$1" - PS0="$_GHOSTTY_SAVE_PS0" PS1="$_GHOSTTY_SAVE_PS1" PS2="$_GHOSTTY_SAVE_PS2" diff --git a/src/terminal/style.zig b/src/terminal/style.zig index 865e15f64..78afcdf39 100644 --- a/src/terminal/style.zig +++ b/src/terminal/style.zig @@ -1,5 +1,6 @@ const std = @import("std"); const assert = std.debug.assert; +const configpkg = @import("../config.zig"); const color = @import("color.zig"); const sgr = @import("sgr.zig"); const page = @import("page.zig"); @@ -115,24 +116,68 @@ pub const Style = struct { }; } - /// Returns the fg color for a cell with this style given the palette. + pub const Fg = struct { + /// The default color to use if the style doesn't specify a + /// foreground color and no configuration options override + /// it. + default: color.RGB, + + /// The current color palette. Required to map palette indices to + /// real color values. + palette: *const color.Palette, + + /// If specified, the color to use for bold text. + bold: ?configpkg.BoldColor = null, + }; + + /// Returns the fg color for a cell with this style given the palette + /// and various configuration options. pub fn fg( self: Style, - palette: *const color.Palette, - bold_is_bright: bool, - ) ?color.RGB { + opts: Fg, + ) color.RGB { + // Note we don't pull the bold check to the top-level here because + // we don't want to duplicate the conditional multiple times since + // certain colors require more checks (e.g. `bold_is_bright`). + return switch (self.fg_color) { - .none => null, - .palette => |idx| palette: { - if (bold_is_bright and self.flags.bold) { - const bright_offset = @intFromEnum(color.Name.bright_black); - if (idx < bright_offset) - break :palette palette[idx + bright_offset]; + .none => default: { + if (self.flags.bold) { + if (opts.bold) |bold| switch (bold) { + .bright => {}, + .color => |v| break :default v.toTerminalRGB(), + }; } - break :palette palette[idx]; + break :default opts.default; + }, + + .palette => |idx| palette: { + if (self.flags.bold) { + if (opts.bold) |bold| switch (bold) { + .color => |v| break :palette v.toTerminalRGB(), + .bright => { + const bright_offset = @intFromEnum(color.Name.bright_black); + if (idx < bright_offset) { + break :palette opts.palette[idx + bright_offset]; + } + }, + }; + } + + break :palette opts.palette[idx]; + }, + + .rgb => |rgb| rgb: { + if (self.flags.bold and rgb.eql(opts.default)) { + if (opts.bold) |bold| switch (bold) { + .color => |v| break :rgb v.toTerminalRGB(), + .bright => {}, + }; + } + + break :rgb rgb; }, - .rgb => |rgb| rgb, }; } diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index b8f838cf9..15b6b8cd4 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -90,15 +90,13 @@ pub fn threadEnter( // Start our subprocess const pty_fds = self.subprocess.start(alloc) catch |err| { // If we specifically got this error then we are in the forked - // process and our child failed to execute. In that case - if (err != error.Termio) return err; + // process and our child failed to execute. If we DIDN'T + // get this specific error then we're in the parent and + // we need to bubble it up. + if (err != error.ExecFailedInChild) return err; - // Output an error message about the exec faililng and exit. - // This generally should NOT happen because we always wrap - // our command execution either in login (macOS) or /bin/sh - // (Linux) which are usually guaranteed to exist. Still, we - // want to handle this scenario. - execFailedInChild() catch {}; + // We're in the child. Nothing more we can do but abnormal exit. + // The Command will output some additional information. posix.exit(1); }; errdefer self.subprocess.stop(); @@ -272,25 +270,6 @@ pub fn resize( return try self.subprocess.resize(grid_size, screen_size); } -/// This outputs an error message when exec failed and we are the -/// child process. This returns so the caller should probably exit -/// after calling this. -/// -/// Note that this usually is only called under very very rare -/// circumstances because we wrap our command execution in login -/// (macOS) or /bin/sh (Linux). So this output can be pretty crude -/// because it should never happen. Notably, this is not the error -/// users see when `command` is invalid. -fn execFailedInChild() !void { - const stderr = std.io.getStdErr().writer(); - try stderr.writeAll("exec failed\n"); - try stderr.writeAll("press any key to exit\n"); - - var buf: [1]u8 = undefined; - var reader = std.io.getStdIn().reader(); - _ = try reader.read(&buf); -} - fn processExitCommon(td: *termio.Termio.ThreadData, exit_code: u32) void { assert(td.backend == .exec); const execdata = &td.backend.exec; @@ -847,6 +826,15 @@ const Subprocess = struct { else null; + // Propagate the current working directory (CWD) to the shell, enabling + // the shell to display the current directory name rather than the + // resolved path for symbolic links. This is important and based + // on the same behavior in Konsole and Kitty (see the linked issues): + // https://bugs.kde.org/show_bug.cgi?id=242114 + // https://github.com/kovidgoyal/kitty/issues/1595 + // https://github.com/ghostty-org/ghostty/discussions/7769 + if (cwd) |pwd| try env.put("PWD", pwd); + // If we have a cgroup, then we copy that into our arena so the // memory remains valid when we start. const linux_cgroup: Command.LinuxCgroup = cgroup: { @@ -886,6 +874,12 @@ const Subprocess = struct { } { assert(self.pty == null and self.command == null); + // This function is funny because on POSIX systems it can + // fail in the forked process. This is flipped to true if + // we're in an error state in the forked process (child + // process). + var in_child: bool = false; + // Create our pty var pty = try Pty.open(.{ .ws_row = @intCast(self.grid_size.rows), @@ -894,14 +888,14 @@ const Subprocess = struct { .ws_ypixel = @intCast(self.screen_size.height), }); self.pty = pty; - errdefer { + errdefer if (!in_child) { if (comptime builtin.os.tag != .windows) { _ = posix.close(pty.slave); } pty.deinit(); self.pty = null; - } + }; log.debug("starting command command={s}", .{self.args}); @@ -1004,7 +998,22 @@ const Subprocess = struct { .data = self, .linux_cgroup = self.linux_cgroup, }; - try cmd.start(alloc); + + cmd.start(alloc) catch |err| { + // We have to do this because start on Windows can't + // ever return ExecFailedInChild + const StartError = error{ExecFailedInChild} || @TypeOf(err); + switch (@as(StartError, err)) { + // If we fail in our child we need to flag it so our + // errdefers don't run. + error.ExecFailedInChild => { + in_child = true; + return err; + }, + + else => return err, + } + }; errdefer killCommand(&cmd) catch |err| { log.warn("error killing command during cleanup err={}", .{err}); }; diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index 8aaa87011..4b5b93641 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -163,8 +163,7 @@ pub const DerivedConfig = struct { image_storage_limit: usize, cursor_style: terminalpkg.CursorStyle, cursor_blink: ?bool, - cursor_color: ?configpkg.Config.Color, - cursor_invert: bool, + cursor_color: ?configpkg.Config.TerminalColor, foreground: configpkg.Config.Color, background: configpkg.Config.Color, osc_color_report_format: configpkg.Config.OSCColorReportFormat, @@ -185,7 +184,6 @@ pub const DerivedConfig = struct { .cursor_style = config.@"cursor-style", .cursor_blink = config.@"cursor-style-blink", .cursor_color = config.@"cursor-color", - .cursor_invert = config.@"cursor-invert-fg-bg", .foreground = config.foreground, .background = config.background, .osc_color_report_format = config.@"osc-color-report-format", @@ -265,10 +263,16 @@ pub fn init(self: *Termio, alloc: Allocator, opts: termio.Options) !void { // Create our stream handler. This points to memory in self so it // isn't safe to use until self.* is set. const handler: StreamHandler = handler: { - const default_cursor_color = if (!opts.config.cursor_invert and opts.config.cursor_color != null) - opts.config.cursor_color.?.toTerminalRGB() - else - null; + const default_cursor_color: ?terminalpkg.color.RGB = color: { + if (opts.config.cursor_color) |color| switch (color) { + .color => break :color color.color.toTerminalRGB(), + .@"cell-foreground", + .@"cell-background", + => {}, + }; + + break :color null; + }; break :handler .{ .alloc = alloc, diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 1b4fdd3aa..039b11c03 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -15,11 +15,6 @@ const posix = std.posix; const log = std.log.scoped(.io_handler); -/// True if we should disable the kitty keyboard protocol. We have to -/// disable this on GLFW because GLFW input events don't support the -/// correct granularity of events. -const disable_kitty_keyboard_protocol = apprt.runtime == apprt.glfw; - /// This is used as the handler for the terminal.Stream type. This is /// stateful and is expected to live for the entire lifetime of the terminal. /// It is NOT VALID to stop a stream handler, create a new one, and use that @@ -121,10 +116,16 @@ pub const StreamHandler = struct { self.default_background_color = config.background.toTerminalRGB(); self.default_cursor_style = config.cursor_style; self.default_cursor_blink = config.cursor_blink; - self.default_cursor_color = if (!config.cursor_invert and config.cursor_color != null) - config.cursor_color.?.toTerminalRGB() - else - null; + self.default_cursor_color = color: { + if (config.cursor_color) |color| switch (color) { + .color => break :color color.color.toTerminalRGB(), + .@"cell-foreground", + .@"cell-background", + => {}, + }; + + break :color null; + }; // If our cursor is the default, then we update it immediately. if (self.default_cursor) self.setCursorStyle(.default) catch |err| { @@ -907,8 +908,6 @@ pub const StreamHandler = struct { } pub fn queryKittyKeyboard(self: *StreamHandler) !void { - if (comptime disable_kitty_keyboard_protocol) return; - log.debug("querying kitty keyboard mode", .{}); var data: termio.Message.WriteReq.Small.Array = undefined; const resp = try std.fmt.bufPrint(&data, "\x1b[?{}u", .{ @@ -927,15 +926,11 @@ pub const StreamHandler = struct { self: *StreamHandler, flags: terminal.kitty.KeyFlags, ) !void { - if (comptime disable_kitty_keyboard_protocol) return; - log.debug("pushing kitty keyboard mode: {}", .{flags}); self.terminal.screen.kitty_keyboard.push(flags); } pub fn popKittyKeyboard(self: *StreamHandler, n: u16) !void { - if (comptime disable_kitty_keyboard_protocol) return; - log.debug("popping kitty keyboard mode n={}", .{n}); self.terminal.screen.kitty_keyboard.pop(@intCast(n)); } @@ -945,8 +940,6 @@ pub const StreamHandler = struct { mode: terminal.kitty.KeySetMode, flags: terminal.kitty.KeyFlags, ) !void { - if (comptime disable_kitty_keyboard_protocol) return; - log.debug("setting kitty keyboard mode: {} {}", .{ mode, flags }); self.terminal.screen.kitty_keyboard.set(mode, flags); } diff --git a/typos.toml b/typos.toml index fafc38858..1fb54ecc6 100644 --- a/typos.toml +++ b/typos.toml @@ -32,13 +32,13 @@ extend-ignore-re = [ # Ignore typos in test expectations "testing\\.expect[^;]*;", "kHOM\\d*", + # Ignore "typos" in sprite font draw fn names + "draw[0-9A-F]+(_[0-9A-F]+)?\\(", ] [default.extend-words] Pn = "Pn" thr = "thr" -# Should be "halves", but for now skip it as it would make diff huge -halfs = "halfs" # Swift oddities Requestor = "Requestor" iterm = "iterm" diff --git a/vendor/nerd-fonts/LICENSE b/vendor/nerd-fonts/LICENSE new file mode 100644 index 000000000..d163912b3 --- /dev/null +++ b/vendor/nerd-fonts/LICENSE @@ -0,0 +1,126 @@ +# Nerd Fonts Licensing + +There are various sources used under various licenses: + +* Nerd Fonts source fonts, patched fonts, and folders with explict OFL SIL files are licensed under SIL OPEN FONT LICENSE Version 1.1 (see below). +* Nerd Fonts original source code files (such as `.sh`, `.py`, `font-patcher` and others) are licensed under the MIT License (MIT) (see below). +* Many other licenses are present in this project for even more detailed breakdown see: [License Audit](https://github.com/ryanoasis/nerd-fonts/blob/-/license-audit.md). + +## Source files not in folders containing an explicit license are using the MIT License (MIT) + +The MIT License (MIT) + +Copyright (c) 2014 Ryan L McIntyre + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +## Various Fonts, Patched Fonts, SVGs, Glyph Fonts, and any files in a folder with explicit SIL OFL 1.1 License + +Copyright (c) 2014, Ryan L McIntyre (https://ryanlmcintyre.com). + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/vendor/nerd-fonts/README.md b/vendor/nerd-fonts/README.md new file mode 100644 index 000000000..66dec54cb --- /dev/null +++ b/vendor/nerd-fonts/README.md @@ -0,0 +1,10 @@ +We have a copy of the `font-patcher` file from `nerd-fonts` here, fetched from +https://github.com/ryanoasis/nerd-fonts/blob/master/font-patcher. + +This is MIT licensed, see `LICENSE` in this directory. + +We use parse a section of this file to codegen a lookup table of the nerd font +scaling rules. See `src/font/nerd_font_codegen.py` in the main Ghostty source +tree for more info. + +Last fetched commit: ebc376cbd43f609d8084f47dd348646595ce066e diff --git a/vendor/nerd-fonts/font-patcher.py b/vendor/nerd-fonts/font-patcher.py new file mode 100644 index 000000000..6c7ebfe37 --- /dev/null +++ b/vendor/nerd-fonts/font-patcher.py @@ -0,0 +1,2296 @@ +#!/usr/bin/env python +# coding=utf8 +# Nerd Fonts Version: 3.4.0 +# Script version is further down + +from __future__ import absolute_import, print_function, unicode_literals + +# Change the script version when you edit this script: +script_version = "4.20.5" + +version = "3.4.0" +projectName = "Nerd Fonts" +projectNameAbbreviation = "NF" +projectNameSingular = projectName[:-1] + +import sys +import re +import os +import argparse +from argparse import RawTextHelpFormatter +import errno +import subprocess +import json +from enum import Enum +import logging +try: + import configparser +except ImportError: + sys.exit(projectName + ": configparser module is probably not installed. Try `pip install configparser` or equivalent") +try: + import psMat + import fontforge +except ImportError: + sys.exit( + projectName + ( + ": FontForge module could not be loaded. Try installing fontforge python bindings " + "[e.g. on Linux Debian or Ubuntu: `sudo apt install fontforge python3-fontforge`]" + ) + ) + +sys.path.insert(0, os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'bin', 'scripts', 'name_parser')) +try: + from FontnameParser import FontnameParser + from FontnameTools import FontnameTools + FontnameParserOK = True +except ImportError: + FontnameParserOK = False + +class TableHEADWriter: + """ Access to the HEAD table without external dependencies """ + def getlong(self, pos = None): + """ Get four bytes from the font file as integer number """ + if pos: + self.goto(pos) + return (ord(self.f.read(1)) << 24) + (ord(self.f.read(1)) << 16) + (ord(self.f.read(1)) << 8) + ord(self.f.read(1)) + + def getshort(self, pos = None): + """ Get two bytes from the font file as integer number """ + if pos: + self.goto(pos) + return (ord(self.f.read(1)) << 8) + ord(self.f.read(1)) + + def putlong(self, num, pos = None): + """ Put number as four bytes into font file """ + if pos: + self.goto(pos) + self.f.write(bytearray([(num >> 24) & 0xFF, (num >> 16) & 0xFF ,(num >> 8) & 0xFF, num & 0xFF])) + self.modified = True + + def putshort(self, num, pos = None): + """ Put number as two bytes into font file """ + if pos: + self.goto(pos) + self.f.write(bytearray([(num >> 8) & 0xFF, num & 0xFF])) + self.modified = True + + def calc_checksum(self, start, end, checksum = 0): + """ Calculate a font table checksum, optionally ignoring another embedded checksum value (for table 'head') """ + self.f.seek(start) + for i in range(start, end - 4, 4): + checksum += self.getlong() + checksum &= 0xFFFFFFFF + i += 4 + extra = 0 + for j in range(4): + extra = extra << 8 + if i + j <= end: + extra += ord(self.f.read(1)) + checksum = (checksum + extra) & 0xFFFFFFFF + return checksum + + def find_table(self, tablenames, idx): + """ Search all tables for one of the tables in tablenames and store its metadata """ + # Use font with index idx if this is a font collection file + self.f.seek(0, 0) + tag = self.f.read(4) + if tag == b'ttcf': + self.f.seek(2*2, 1) + self.num_fonts = self.getlong() + if (idx >= self.num_fonts): + raise Exception('Trying to access subfont index {} but have only {} fonts'.format(idx, num_fonts)) + for _ in range(idx + 1): + offset = self.getlong() + self.f.seek(offset, 0) + elif idx != 0: + raise Exception('Trying to access subfont but file is no collection') + else: + self.f.seek(0, 0) + self.num_fonts = 1 + + self.f.seek(4, 1) + numtables = self.getshort() + self.f.seek(3*2, 1) + + for i in range(numtables): + tab_name = self.f.read(4) + self.tab_check_offset = self.f.tell() + self.tab_check = self.getlong() + self.tab_offset = self.getlong() + self.tab_length = self.getlong() + if tab_name in tablenames: + return True + return False + + def find_head_table(self, idx): + """ Search all tables for the HEAD table and store its metadata """ + # Use font with index idx if this is a font collection file + found = self.find_table([ b'head' ], idx) + if not found: + raise Exception('No HEAD table found in font idx {}'.format(idx)) + + + def goto(self, where): + """ Go to a named location in the file or to the specified index """ + if isinstance(where, str): + positions = {'checksumAdjustment': 2+2+4, + 'flags': 2+2+4+4+4, + 'lowestRecPPEM': 2+2+4+4+4+2+2+8+8+2+2+2+2+2, + 'avgWidth': 2, + } + where = self.tab_offset + positions[where] + self.f.seek(where) + + + def calc_full_checksum(self, check = False): + """ Calculate the whole file's checksum """ + self.f.seek(0, 2) + self.end = self.f.tell() + full_check = self.calc_checksum(0, self.end, (-self.checksum_adj) & 0xFFFFFFFF) + if check and (0xB1B0AFBA - full_check) & 0xFFFFFFFF != self.checksum_adj: + sys.exit("Checksum of whole font is bad") + return full_check + + def calc_table_checksum(self, check = False): + tab_check_new = self.calc_checksum(self.tab_offset, self.tab_offset + self.tab_length - 1, (-self.checksum_adj) & 0xFFFFFFFF) + if check and tab_check_new != self.tab_check: + sys.exit("Checksum of 'head' in font is bad") + return tab_check_new + + def reset_table_checksum(self): + new_check = self.calc_table_checksum() + self.putlong(new_check, self.tab_check_offset) + + def reset_full_checksum(self): + new_adj = (0xB1B0AFBA - self.calc_full_checksum()) & 0xFFFFFFFF + self.putlong(new_adj, 'checksumAdjustment') + + def close(self): + self.f.close() + + + def __init__(self, filename): + self.modified = False + self.f = open(filename, 'r+b') + + self.find_head_table(0) + + self.flags = self.getshort('flags') + self.lowppem = self.getshort('lowestRecPPEM') + self.checksum_adj = self.getlong('checksumAdjustment') + +def check_panose_monospaced(font): + """ Check if the font's Panose flags say it is monospaced """ + # https://forum.high-logic.com/postedfiles/Panose.pdf + panose = list(font.os2_panose) + if panose[0] < 2 or panose[0] > 5: + return -1 # invalid Panose info + panose_mono = ((panose[0] == 2 and panose[3] == 9) or + (panose[0] == 3 and panose[3] == 3)) + return 1 if panose_mono else 0 + +def panose_check_to_text(value, panose = False): + """ Convert value from check_panose_monospaced() to human readable string """ + if value == 0: + return "Panose says \"not monospaced\"" + if value == 1: + return "Panose says \"monospaced\"" + return "Panose is invalid" + (" ({})".format(list(panose)) if panose else "") + +def panose_proportion_to_text(value): + """ Interpret a Panose proportion value (4th value) for family 2 (latin text) """ + proportion = { + 0: "Any", 1: "No Fit", 2: "Old Style", 3: "Modern", 4: "Even Width", + 5: "Extended", 6: "Condensed", 7: "Very Extended", 8: "Very Condensed", + 9: "Monospaced" } + return proportion.get(value, "??? {}".format(value)) + +def is_monospaced(font): + """ Check if a font is probably monospaced """ + # Some fonts lie (or have not any Panose flag set), spot check monospaced: + width = -1 + width_mono = True + for glyph in [ 0x49, 0x4D, 0x57, 0x61, 0x69, 0x6d, 0x2E ]: # wide and slim glyphs 'I', 'M', 'W', 'a', 'i', 'm', '.' + if not glyph in font: + # A 'strange' font, believe Panose + return (check_panose_monospaced(font) == 1, None) + # print(" -> {} {}".format(glyph, font[glyph].width)) + if width < 0: + width = font[glyph].width + continue + if font[glyph].width != width: + # Exception for fonts like Code New Roman Regular or Hermit Light/Bold: + # Allow small 'i' and dot to be smaller than normal + # I believe the source fonts are buggy + if glyph in [ 0x69, 0x2E ]: + if width > font[glyph].width: + continue + (xmin, _, xmax, _) = font[glyph].boundingBox() + if width > xmax - xmin: + continue + width_mono = False + break + # We believe our own check more then Panose ;-D + return (width_mono, None if width_mono else glyph) + +def force_panose_monospaced(font): + """ Forces the Panose flag to monospaced if they are unset or halfway ok already """ + # For some Windows applications (e.g. 'cmd'), they seem to honour the Panose table + # https://forum.high-logic.com/postedfiles/Panose.pdf + panose = list(font.os2_panose) + if panose[0] == 0: # 0 (1st value) = family kind; 0 = any (default) + panose[0] = 2 # make kind latin text and display + logger.info("Setting Panose 'Family Kind' to 'Latin Text and Display' (was 'Any')") + font.os2_panose = tuple(panose) + if panose[0] == 2 and panose[3] != 9: + logger.info("Setting Panose 'Proportion' to 'Monospaced' (was '%s')", panose_proportion_to_text(panose[3])) + panose[3] = 9 # 3 (4th value) = proportion; 9 = monospaced + font.os2_panose = tuple(panose) + +def get_advance_width(font, extended, minimum): + """ Get the maximum/minimum advance width in the extended(?) range """ + width = 0 + if not extended: + r = range(0x021, 0x07e) + else: + r = range(0x07f, 0x17f) + for glyph in r: + if not glyph in font: + continue + if glyph in range(0x7F, 0xBF): + continue # ignore special characters like '1/4' etc + if width == 0: + width = font[glyph].width + continue + if not minimum and width < font[glyph].width: + width = font[glyph].width + elif minimum and width > font[glyph].width: + width = font[glyph].width + return width + +def report_advance_widths(font): + return "Advance widths (base/extended): {} - {} / {} - {}".format( + get_advance_width(font, False, True), get_advance_width(font, False, False), + get_advance_width(font, True, True), get_advance_width(font, True, False)) + +def get_btb_metrics(font): + """ Get the baseline to baseline distance for all three metrics """ + hhea_height = font.hhea_ascent - font.hhea_descent + typo_height = font.os2_typoascent - font.os2_typodescent + win_height = font.os2_winascent + font.os2_windescent + win_gap = max(0, font.hhea_linegap - win_height + hhea_height) + hhea_btb = hhea_height + font.hhea_linegap + typo_btb = typo_height + font.os2_typolinegap + win_btb = win_height + win_gap + return (hhea_btb, typo_btb, win_btb, win_gap) + +def get_metrics_names(): + """ Helper to get the line metrics names consistent """ + return ['HHEA','TYPO','WIN'] + +def get_old_average_x_width(font): + """ Determine xAvgCharWidth of the OS/2 table """ + # Fontforge can not create fonts with old (i.e. prior to OS/2 version 3) + # table values, but some very old applications do need them sometimes + # https://learn.microsoft.com/en-us/typography/opentype/spec/os2#xavgcharwidth + s = 0 + weights = { + 'a': 64, 'b': 14, 'c': 27, 'd': 35, 'e': 100, 'f': 20, 'g': 14, 'h': 42, 'i': 63, + 'j': 3, 'k': 6, 'l': 35, 'm': 20, 'n': 56, 'o': 56, 'p': 17, 'q': 4, 'r': 49, + 's': 56, 't': 71, 'u': 31, 'v': 10, 'w': 18, 'x': 3, 'y': 18, 'z': 2, 32: 166, + } + for g in weights: + if g not in font: + logger.critical("Can not determine ancient style xAvgCharWidth") + sys.exit(1) + s += font[g].width * weights[g] + return int(s / 1000) + +def create_filename(fonts): + """ Determine filename from font object(s) """ + # Only consider the standard (i.e. English-US) names + sfnt = { k: v for l, k, v in fonts[0].sfnt_names if l == 'English (US)' } + sfnt_pfam = sfnt.get('Preferred Family', sfnt['Family']) + sfnt_psubfam = sfnt.get('Preferred Styles', sfnt['SubFamily']) + if len(fonts) > 1: + return sfnt_pfam + if len(sfnt_psubfam) > 0: + sfnt_psubfam = '-' + sfnt_psubfam + return (sfnt_pfam + sfnt_psubfam).replace(' ', '') + +def fetch_glyphnames(): + """ Read the glyphname database and put it into a dictionary """ + try: + glyphnamefile = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), 'glyphnames.json')) + with open(glyphnamefile, 'rb') as f: + namelist = json.load(f) + return { int(v['code'], 16): k for k, v in namelist.items() if 'code' in v } + except Exception as error: + logger.warning("Can not read glyphnames file (%s)", repr(error)) + return {} + +class font_patcher: + def __init__(self, args, conf): + self.args = args # class 'argparse.Namespace' + self.sym_font_args = [] + self.config = conf # class 'configparser.ConfigParser' + self.sourceFont = None # class 'fontforge.font' + self.patch_set = None # class 'list' + self.font_dim = None # class 'dict' + self.font_extrawide = False + self.source_monospaced = None # Later True or False + self.symbolsonly = False # Are we generating the SymbolsOnly font? + self.onlybitmaps = 0 + self.essential = set() + self.xavgwidth = [] # list of ints + self.glyphnames = fetch_glyphnames() + + def patch(self, font): + self.sourceFont = font + self.setup_version() + self.assert_monospace() + self.remove_ligatures() + self.manipulate_hints() + self.get_essential_references() + self.get_sourcefont_dimensions() + self.setup_patch_set() + self.improve_line_dimensions() + self.sourceFont.encoding = 'UnicodeFull' # Update the font encoding to ensure that the Unicode glyphs are available + self.onlybitmaps = self.sourceFont.onlybitmaps # Fetch this property before adding outlines. NOTE self.onlybitmaps initialized and never used + + if self.args.forcemono: + # Force width to be equal on all glyphs to ensure the font is considered monospaced on Windows. + # This needs to be done on all characters, as some information seems to be lost from the original font file. + self.set_sourcefont_glyph_widths() + + # For very wide (almost square or wider) fonts we do not want to generate 2 cell wide Powerline glyphs + if self.font_dim['height'] * 1.8 < self.font_dim['width'] * 2: + logger.warning("Very wide and short font, disabling 2 cell Powerline glyphs") + self.font_extrawide = True + + # Prevent opening and closing the fontforge font. Makes things faster when patching + # multiple ranges using the same symbol font. + PreviousSymbolFilename = "" + symfont = None + + if not os.path.isdir(self.args.glyphdir): + logger.critical("Can not find symbol glyph directory %s " + "(probably you need to download the src/glyphs/ directory?)", self.args.glyphdir) + sys.exit(1) + + if self.args.dry_run: + return + + for patch in self.patch_set: + if patch['Enabled']: + if PreviousSymbolFilename != patch['Filename']: + # We have a new symbol font, so close the previous one if it exists + if symfont: + symfont.close() + symfont = None + symfont_file = os.path.join(self.args.glyphdir, patch['Filename']) + if not os.path.isfile(symfont_file): + logger.critical("Can not find symbol source for '%s' (i.e. %s)", + patch['Name'], symfont_file) + sys.exit(1) + if not os.access(symfont_file, os.R_OK): + logger.critical("Can not open symbol source for '%s' (i.e. %s)", + patch['Name'], symfont_file) + sys.exit(1) + symfont = fontforge.open(symfont_file) + symfont.encoding = 'UnicodeFull' + + # Match the symbol font size to the source font size + symfont.em = self.sourceFont.em + PreviousSymbolFilename = patch['Filename'] + + # If patch table doesn't include a source start, re-use the symbol font values + SrcStart = patch['SrcStart'] + if not SrcStart: + SrcStart = patch['SymStart'] + self.copy_glyphs(SrcStart, symfont, patch['SymStart'], patch['SymEnd'], patch['Exact'], patch['ScaleRules'], patch['Name'], patch['Attributes']) + + if symfont: + symfont.close() + + # The grave accent and fontforge: + # If the type is 'auto' fontforge changes it to 'mark' on export. + # We can not prevent this. So set it to 'baseglyph' instead, as + # that resembles the most common expectations. + # This is not needed with fontforge March 2022 Release anymore. + if "grave" in self.sourceFont: + self.sourceFont["grave"].glyphclass="baseglyph" + + + def generate(self, sourceFonts): + sourceFont = sourceFonts[0] + # the `PfEd-comments` flag is required for Fontforge to save '.comment' and '.fontlog'. + if int(fontforge.version()) >= 20201107: + gen_flags = (str('opentype'), str('PfEd-comments'), str('no-FFTM-table')) + else: + gen_flags = (str('opentype'), str('PfEd-comments')) + if len(sourceFonts) > 1: + layer = None + # use first non-background layer + for l in sourceFont.layers: + if not sourceFont.layers[l].is_background: + layer = l + break + outfile = os.path.normpath(os.path.join( + sanitize_filename(self.args.outputdir, True), + sanitize_filename(create_filename(sourceFonts)) + ".ttc")) + sourceFonts[0].generateTtc(outfile, sourceFonts[1:], flags=gen_flags, layer=layer) + message = " Generated {} fonts\n \\===> '{}'".format(len(sourceFonts), outfile) + else: + fontname = create_filename(sourceFonts) + if not fontname: + fontname = sourceFont.cidfontname + outfile = os.path.normpath(os.path.join( + sanitize_filename(self.args.outputdir, True), + sanitize_filename(fontname) + self.args.extension)) + bitmaps = str() + if len(sourceFont.bitmapSizes): + logger.debug("Preserving bitmaps %s", repr(sourceFont.bitmapSizes)) + bitmaps = str('otf') # otf/ttf, both is bf_ttf + if self.args.dry_run: + logger.debug("=====> Filename '%s'", outfile) + return + sourceFont.generate(outfile, bitmap_type=bitmaps, flags=gen_flags) + message = " {}\n \\===> '{}'".format(sourceFont.fullname, outfile) + + # Adjust flags that can not be changed via fontforge + if re.search(r'\.[ot]tf$', self.args.font, re.IGNORECASE) and re.search(r'\.[ot]tf$', outfile, re.IGNORECASE): + if not os.path.isfile(outfile) or os.path.getsize(outfile) < 1: + logger.critical("Something went wrong and Fontforge did not generate the new font - look for messages above") + sys.exit(1) + try: + source_font = TableHEADWriter(self.args.font) + dest_font = TableHEADWriter(outfile) + for idx in range(source_font.num_fonts): + logger.debug("Tweaking %d/%d", idx + 1, source_font.num_fonts) + xwidth_s = '' + xwidth = self.xavgwidth[idx] if len(self.xavgwidth) > idx else None + if isinstance(xwidth, int): + if isinstance(xwidth, bool) and xwidth: + source_font.find_table([b'OS/2'], idx) + xwidth = source_font.getshort('avgWidth') + xwidth_s = ' (copied from source)' + dest_font.find_table([b'OS/2'], idx) + d_xwidth = dest_font.getshort('avgWidth') + if d_xwidth != xwidth: + logger.debug("Changing xAvgCharWidth from %d to %d%s", d_xwidth, xwidth, xwidth_s) + dest_font.putshort(xwidth, 'avgWidth') + dest_font.reset_table_checksum() + source_font.find_head_table(idx) + dest_font.find_head_table(idx) + if source_font.flags & 0x08 == 0 and dest_font.flags & 0x08 != 0: + logger.debug("Changing flags from 0x%X to 0x%X", dest_font.flags, dest_font.flags & ~0x08) + dest_font.putshort(dest_font.flags & ~0x08, 'flags') # clear 'ppem_to_int' + if source_font.lowppem != dest_font.lowppem: + logger.debug("Changing lowestRecPPEM from %d to %d", dest_font.lowppem, source_font.lowppem) + dest_font.putshort(source_font.lowppem, 'lowestRecPPEM') + if dest_font.modified: + dest_font.reset_table_checksum() + if dest_font.modified: + dest_font.reset_full_checksum() + except Exception as error: + logger.error("Can not handle font flags (%s)", repr(error)) + finally: + try: + source_font.close() + dest_font.close() + except: + pass + if self.args.is_variable: + logger.critical("Source font is a variable open type font (VF) and the patch results will most likely not be what you want") + print(message) + + if self.args.postprocess: + subprocess.call([self.args.postprocess, outfile]) + print("\n") + logger.info("Post Processed: %s", outfile) + + + def setup_name_backup(self, font): + """ Store the original font names to be able to rename the font multiple times """ + font.persistent = { + "fontname": font.fontname, + "fullname": font.fullname, + "familyname": font.familyname, + } + + + def setup_font_names(self, font): + font.fontname = font.persistent["fontname"] + if isinstance(font.persistent["fullname"], str): + font.fullname = font.persistent["fullname"] + if isinstance(font.persistent["familyname"], str): + font.familyname = font.persistent["familyname"] + verboseAdditionalFontNameSuffix = "" + additionalFontNameSuffix = "" + if not self.args.complete: + # NOTE not all symbol fonts have appended their suffix here + if self.args.fontawesome: + additionalFontNameSuffix += " A" + verboseAdditionalFontNameSuffix += " Plus Font Awesome" + if self.args.fontawesomeextension: + additionalFontNameSuffix += " AE" + verboseAdditionalFontNameSuffix += " Plus Font Awesome Extension" + if self.args.octicons: + additionalFontNameSuffix += " O" + verboseAdditionalFontNameSuffix += " Plus Octicons" + if self.args.powersymbols: + additionalFontNameSuffix += " PS" + verboseAdditionalFontNameSuffix += " Plus Power Symbols" + if self.args.codicons: + additionalFontNameSuffix += " C" + verboseAdditionalFontNameSuffix += " Plus Codicons" + if self.args.pomicons: + additionalFontNameSuffix += " P" + verboseAdditionalFontNameSuffix += " Plus Pomicons" + if self.args.fontlogos: + additionalFontNameSuffix += " L" + verboseAdditionalFontNameSuffix += " Plus Font Logos" + if self.args.material: + additionalFontNameSuffix += " MDI" + verboseAdditionalFontNameSuffix += " Plus Material Design Icons" + if self.args.weather: + additionalFontNameSuffix += " WEA" + verboseAdditionalFontNameSuffix += " Plus Weather Icons" + + # add mono signifier to beginning of name suffix + if self.args.single: + variant_abbrev = "M" + variant_full = " Mono" + elif self.args.nonmono and not self.symbolsonly: + variant_abbrev = "P" + variant_full = " Propo" + else: + variant_abbrev = "" + variant_full = "" + + ps_suffix = projectNameAbbreviation + variant_abbrev + additionalFontNameSuffix + + # add 'Nerd Font' to beginning of name suffix + verboseAdditionalFontNameSuffix = " " + projectNameSingular + variant_full + verboseAdditionalFontNameSuffix + additionalFontNameSuffix = " " + projectNameSingular + variant_full + additionalFontNameSuffix + + if FontnameParserOK and self.args.makegroups > 0: + user_supplied_name = False # User supplied names are kept unchanged + if not isinstance(self.args.force_name, str): + use_fullname = isinstance(font.fullname, str) # Usually the fullname is better to parse + # Use fullname if it is 'equal' to the fontname + if font.fullname: + use_fullname |= font.fontname.lower() == FontnameTools.postscript_char_filter(font.fullname).lower() + # Use fullname for any of these source fonts (that are impossible to disentangle from the fontname, we need the blanks) + for hit in [ 'Meslo' ]: + use_fullname |= font.fontname.lower().startswith(hit.lower()) + parser_name = font.fullname if use_fullname else font.fontname + # Gohu fontnames hide the weight, but the file names are ok... + if parser_name.startswith('Gohu'): + parser_name = os.path.splitext(os.path.basename(self.args.font))[0] + else: + if self.args.force_name == 'full': + parser_name = font.fullname + elif self.args.force_name == 'postscript': + parser_name = font.fontname + elif self.args.force_name == 'filename': + parser_name = os.path.basename(font.path).split('.')[0] + else: + parser_name = self.args.force_name + user_supplied_name = True + if not isinstance(parser_name, str) or len(parser_name) < 1: + logger.critical("Specified --name not usable because the name will be empty") + sys.exit(2) + n = FontnameParser(parser_name, logger) + if not n.parse_ok: + logger.warning("Have only minimal naming information, check resulting name. Maybe specify --makegroups 0") + n.drop_for_powerline() + n.enable_short_families(not user_supplied_name, self.args.makegroups in [ 2, 3, 5, 6, ], self.args.makegroups in [ 3, 6, ]) + if not n.set_expect_no_italic(self.args.noitalic): + logger.critical("Detected 'Italic' slant but --has-no-italic specified") + sys.exit(1) + + # All the following stuff is ignored in makegroups-mode + + # basically split the font name around the dash "-" to get the fontname and the style (e.g. Bold) + # this does not seem very reliable so only use the style here as a fallback if the font does not + # have an internal style defined (in sfnt_names) + # using '([^-]*?)' to get the item before the first dash "-" + # using '([^-]*(?!.*-))' to get the item after the last dash "-" + fontname, fallbackStyle = re.match("^([^-]*).*?([^-]*(?!.*-))$", font.fontname).groups() + + # dont trust 'font.familyname' + familyname = fontname + + # fullname (filename) can always use long/verbose font name, even in windows + if font.fullname != None: + fullname = font.fullname + verboseAdditionalFontNameSuffix + else: + fullname = font.cidfontname + verboseAdditionalFontNameSuffix + + fontname = fontname + additionalFontNameSuffix.replace(" ", "") + + # let us try to get the 'style' from the font info in sfnt_names and fallback to the + # parse fontname if it fails: + try: + # search tuple: + subFamilyTupleIndex = [x[1] for x in font.sfnt_names].index("SubFamily") + + # String ID is at the second index in the Tuple lists + sfntNamesStringIDIndex = 2 + + # now we have the correct item: + subFamily = font.sfnt_names[subFamilyTupleIndex][sfntNamesStringIDIndex] + except IndexError: + sys.stderr.write("{}: Could not find 'SubFamily' for given font, falling back to parsed fontname\n".format(projectName)) + subFamily = fallbackStyle + + # some fonts have inaccurate 'SubFamily', if it is Regular let us trust the filename more: + if subFamily == "Regular" and len(fallbackStyle): + subFamily = fallbackStyle + + # This is meant to cover the case where the SubFamily is "Italic" and the filename is *-BoldItalic. + if len(subFamily) < len(fallbackStyle): + subFamily = fallbackStyle + + if len(subFamily) == 0: + subFamily = "Regular" + + familyname += " " + projectNameSingular + variant_full + + # Don't truncate the subfamily to keep fontname unique. MacOS treats fonts with + # the same name as the same font, even if subFamily is different. Make sure to + # keep the resulting fontname (PostScript name) valid by removing spaces. + fontname += '-' + subFamily.replace(' ', '') + + # rename font + # + # comply with SIL Open Font License (OFL) + reservedFontNameReplacements = { + 'source' : 'sauce', + 'Source' : 'Sauce', + 'Bitstream Vera Sans Mono' : 'Bitstrom Wera', + 'BitstreamVeraSansMono' : 'BitstromWera', + 'bitstream vera sans mono' : 'bitstrom wera', + 'bitstreamverasansmono' : 'bitstromwera', + 'hermit' : 'hurmit', + 'Hermit' : 'Hurmit', + 'hasklig' : 'hasklug', + 'Hasklig' : 'Hasklug', + 'Share' : 'Shure', + 'share' : 'shure', + 'IBMPlex' : 'Blex', + 'ibmplex' : 'blex', + 'IBM-Plex' : 'Blex', + 'IBM Plex' : 'Blex', + 'terminus' : 'terminess', + 'Terminus' : 'Terminess', + 'liberation' : 'literation', + 'Liberation' : 'Literation', + 'iAWriter' : 'iMWriting', + 'iA Writer' : 'iM Writing', + 'iA-Writer' : 'iM-Writing', + 'Anka/Coder' : 'AnaConder', + 'anka/coder' : 'anaconder', + 'Cascadia Code' : 'Caskaydia Cove', + 'cascadia code' : 'caskaydia cove', + 'CascadiaCode' : 'CaskaydiaCove', + 'cascadiacode' : 'caskaydiacove', + 'Cascadia Mono' : 'Caskaydia Mono', + 'cascadia mono' : 'caskaydia mono', + 'CascadiaMono' : 'CaskaydiaMono', + 'cascadiamono' : 'caskaydiamono', + 'Fira Mono' : 'Fura Mono', + 'Fira Sans' : 'Fura Sans', + 'FiraMono' : 'FuraMono', + 'FiraSans' : 'FuraSans', + 'fira mono' : 'fura mono', + 'fira sans' : 'fura sans', + 'firamono' : 'furamono', + 'firasans' : 'furasans', + 'IntelOneMono' : 'IntoneMono', + 'IntelOne Mono' : 'Intone Mono', + 'Intel One Mono' : 'Intone Mono', + } + + # remove overly verbose font names + # particularly regarding Powerline sourced Fonts (https://github.com/powerline/fonts) + additionalFontNameReplacements = { + 'for Powerline': '', + 'ForPowerline': '' + } + + additionalFontNameReplacements2 = { + 'Powerline': '' + } + + projectInfo = ( + "Patched with '" + projectName + " Patcher' (https://github.com/ryanoasis/nerd-fonts)\n\n" + "* Website: https://www.nerdfonts.com\n" + "* Version: " + version + "\n" + "* Development Website: https://github.com/ryanoasis/nerd-fonts\n" + "* Changelog: https://github.com/ryanoasis/nerd-fonts/blob/-/changelog.md" + ) + + familyname = replace_font_name(familyname, reservedFontNameReplacements) + fullname = replace_font_name(fullname, reservedFontNameReplacements) + fontname = replace_font_name(fontname, reservedFontNameReplacements) + familyname = replace_font_name(familyname, additionalFontNameReplacements) + fullname = replace_font_name(fullname, additionalFontNameReplacements) + fontname = replace_font_name(fontname, additionalFontNameReplacements) + familyname = replace_font_name(familyname, additionalFontNameReplacements2) + fullname = replace_font_name(fullname, additionalFontNameReplacements2) + fontname = replace_font_name(fontname, additionalFontNameReplacements2) + + if self.args.makegroups < 0: + logger.warning("Renaming disabled! Make sure to comply with font license, esp RFN clause!") + elif not (FontnameParserOK and self.args.makegroups > 0): + # replace any extra whitespace characters: + font.familyname = " ".join(familyname.split()) + font.fullname = " ".join(fullname.split()) + font.fontname = " ".join(fontname.split()) + + font.appendSFNTName(str('English (US)'), str('Preferred Family'), font.familyname) + font.appendSFNTName(str('English (US)'), str('Family'), font.familyname) + font.appendSFNTName(str('English (US)'), str('Compatible Full'), font.fullname) + font.appendSFNTName(str('English (US)'), str('SubFamily'), subFamily) + else: + # Add Nerd Font suffix unless user specifically asked for some excplicit name via --name + if not user_supplied_name: + short_family = projectNameAbbreviation + variant_abbrev if self.args.makegroups >= 4 else projectNameSingular + variant_full + # inject_suffix(family, ps_fontname, short_family) + n.inject_suffix(verboseAdditionalFontNameSuffix, ps_suffix, short_family) + n.rename_font(font) + + font.comment = projectInfo + font.fontlog = projectInfo + + + def setup_version(self): + """ Add the Nerd Font version to the original version """ + # print("Version was {}".format(sourceFont.version)) + if self.sourceFont.version != None: + self.sourceFont.version += ";" + projectName + " " + version + else: + self.sourceFont.version = str(self.sourceFont.cidversion) + ";" + projectName + " " + version + self.sourceFont.sfntRevision = None # Auto-set (refreshed) by fontforge + self.sourceFont.appendSFNTName(str('English (US)'), str('Version'), "Version " + self.sourceFont.version) + # The Version SFNT name is later reused by the NameParser for UniqueID + # print("Version now is {}".format(sourceFont.version)) + + + def remove_ligatures(self): + # let's deal with ligatures (mostly for monospaced fonts) + # Usually removes 'fi' ligs that end up being only one cell wide, and 'ldot' + if self.args.removeligatures: + logger.info("Removing ligatures from configfile `Subtables` section") + if 'Subtables' not in self.config: + logger.warning("No ligature data (config file missing?)") + return + ligature_subtables = json.loads(self.config.get('Subtables', 'ligatures', fallback='[]')) + for subtable in ligature_subtables: + logger.debug("Removing subtable: %s", subtable) + try: + self.sourceFont.removeLookupSubtable(subtable) + logger.debug("Successfully removed subtable: %s", subtable) + except Exception: + logger.error("Failed to remove subtable: %s", subtable) + + + def manipulate_hints(self): + """ Redo the hinting on some problematic glyphs """ + if 'Hinting' not in self.config: + return + redo = json.loads(self.config.get('Hinting', 're_hint', fallback='[]')) + if not len(redo): + return + logger.debug("Working on {} rehinting rules (this may create a lot of fontforge warnings)".format(len(redo))) + count = 0 + for gname in self.sourceFont: + for regex in redo: + if re.fullmatch(regex, gname): + glyph = self.sourceFont[gname] + glyph.autoHint() + glyph.autoInstr() + count += 1 + break + logger.info("Rehinted {} glyphs".format(count)) + + def assert_monospace(self): + # Check if the sourcefont is monospaced + width_mono, offending_char = is_monospaced(self.sourceFont) + self.source_monospaced = width_mono + if self.args.nonmono: + return + panose_mono = check_panose_monospaced(self.sourceFont) + logger.debug("Monospace check: %s; glyph-width-mono %s", + panose_check_to_text(panose_mono, self.sourceFont.os2_panose), repr(width_mono)) + # The following is in fact "width_mono != panose_mono", but only if panose_mono is not 'unknown' + if (width_mono and panose_mono == 0) or (not width_mono and panose_mono == 1): + logger.warning("Monospaced check: Panose assumed to be wrong") + logger.warning("Monospaced check: %s and %s", + report_advance_widths(self.sourceFont), + panose_check_to_text(panose_mono, self.sourceFont.os2_panose)) + if self.args.forcemono and not width_mono: + logger.warning("Sourcefont is not monospaced - forcing to monospace not advisable, " + "results might be useless%s", + " - offending char: {:X}".format(offending_char) if offending_char is not None else "") + if self.args.forcemono <= 1: + logger.critical("Font will not be patched! Give --mono (or -s) twice to force patching") + sys.exit(1) + if width_mono: + force_panose_monospaced(self.sourceFont) + + + def setup_patch_set(self): + """ Creates list of dicts to with instructions on copying glyphs from each symbol font into self.sourceFont """ + + box_enabled = self.source_monospaced and not self.symbolsonly # Box glyph only for monospaced and not for Symbols Only + box_keep = False + if box_enabled or self.args.forcebox: + self.sourceFont.selection.select(("ranges",), 0x2500, 0x259f) + box_glyphs_target = len(list(self.sourceFont.selection)) + box_glyphs_current = len(list(self.sourceFont.selection.byGlyphs)) + if box_glyphs_target > box_glyphs_current or self.args.forcebox: + # Sourcefont does not have all of these glyphs, do not mix sets (overwrite existing) + if box_glyphs_current > 0: + logger.debug("%d/%d box drawing glyphs will be replaced", + box_glyphs_current, box_glyphs_target) + box_enabled = True + else: + # Sourcefont does have all of these glyphs + # box_keep = True # just scale do not copy (need to scale to fit new cell size) + box_enabled = False # Cowardly not scaling existing glyphs, although the code would allow this + + # Stretch 'xz' or 'pa' (preserve aspect ratio) + # Supported params: overlap | careful | xy-ratio | dont_copy | ypadding + # Overlap value is used horizontally but vertically limited to 0.01 + # Careful does not overwrite/modify existing glyphs + # The xy-ratio limits the x-scale for a given y-scale to make the ratio <= this value (to prevent over-wide glyphs) + # '1' means occupu 1 cell (default for 'xy') + # '2' means occupy 2 cells (default for 'pa') + # '!' means do the 'pa' scaling even with non mono fonts (else it just scales down, never up) + # '^' means that scaling shall fill the whole cell and not only the icon-cap-height (for mono fonts, other always use the whole cell) + # Dont_copy does not overwrite existing glyphs but rescales the preexisting ones + # + # Be careful, stretch may not change within a ScaleRule! + + SYM_ATTR_DEFAULT = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}} + } + SYM_ATTR_POWERLINE = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': '^pa', 'params': {}}, + + # Arrow tips + 0xe0b0: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.06, 'xy-ratio': 0.7}}, + 0xe0b1: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'xy-ratio': 0.7}}, + 0xe0b2: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.06, 'xy-ratio': 0.7}}, + 0xe0b3: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'xy-ratio': 0.7}}, + + # Inverse arrow tips + 0xe0d6: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'xy-ratio': 0.7}}, + 0xe0d7: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'xy-ratio': 0.7}}, + + # Rounded arcs + 0xe0b4: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.06, 'xy-ratio': 0.59}}, + 0xe0b5: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'xy-ratio': 0.5}}, + 0xe0b6: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.06, 'xy-ratio': 0.59}}, + 0xe0b7: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'xy-ratio': 0.5}}, + + # Bottom Triangles + 0xe0b8: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05}}, + 0xe0b9: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {}}, + 0xe0ba: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05}}, + 0xe0bb: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {}}, + + # Top Triangles + 0xe0bc: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05}}, + 0xe0bd: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {}}, + 0xe0be: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05}}, + 0xe0bf: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {}}, + + # Flames + 0xe0c0: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': 0.05}}, + 0xe0c1: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {}}, + 0xe0c2: {'align': 'r', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': 0.05}}, + 0xe0c3: {'align': 'r', 'valign': 'c', 'stretch': '^xy2', 'params': {}}, + + # Small squares + 0xe0c4: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.86}}, + 0xe0c5: {'align': 'r', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.86}}, + + # Bigger squares + 0xe0c6: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.78}}, + 0xe0c7: {'align': 'r', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': -0.03, 'xy-ratio': 0.78}}, + + # Waveform + 0xe0c8: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': 0.05}}, + 0xe0ca: {'align': 'r', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': 0.05}}, + + # Hexagons + 0xe0cc: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'overlap': 0.02, 'xy-ratio': 0.85}}, + 0xe0cd: {'align': 'l', 'valign': 'c', 'stretch': '^xy2', 'params': {'xy-ratio': 0.865}}, + + # Legos + 0xe0ce: {'align': 'l', 'valign': 'c', 'stretch': '^pa', 'params': {}}, + 0xe0cf: {'align': 'c', 'valign': 'c', 'stretch': '^pa', 'params': {}}, + 0xe0d0: {'align': 'l', 'valign': 'c', 'stretch': '^pa', 'params': {}}, + 0xe0d1: {'align': 'l', 'valign': 'c', 'stretch': '^pa', 'params': {}}, + + # Top and bottom trapezoid + 0xe0d2: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}}, + 0xe0d4: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.02, 'xy-ratio': 0.7}} + } + SYM_ATTR_TRIGRAPH = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa1!', 'params': {'overlap': -0.10, 'careful': True}} + } + SYM_ATTR_FONTA = { + # 'pa' == preserve aspect ratio + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {}}, + + # Don't center these arrows vertically + 0xf0dc: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}}, + 0xf0dd: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}}, + 0xf0de: {'align': 'c', 'valign': '', 'stretch': 'pa', 'params': {}} + } + SYM_ATTR_HEAVYBRACKETS = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': '^pa1!', 'params': {'ypadding': 0.3, 'careful': True}} + } + SYM_ATTR_BOX = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.02, 'dont_copy': box_keep}}, + # No overlap with checkered greys (commented out because that raises problems on rescaling clients) + # 0x2591: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}}, + # 0x2592: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}}, + # 0x2593: {'align': 'c', 'valign': 'c', 'stretch': 'xy', 'params': {'dont_copy': box_keep}}, + } + SYM_ATTR_PROGRESS = { + 'default': {'align': 'c', 'valign': 'c', 'stretch': '^pa1!', 'params': {'overlap': -0.03, 'careful': True}}, # Cirles + # All the squares: + 0xee00: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'careful': True}}, + 0xee01: {'align': 'c', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.10, 'careful': True}}, + 0xee02: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'careful': True}}, + 0xee03: {'align': 'r', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'careful': True}}, + 0xee04: {'align': 'c', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.10, 'careful': True}}, + 0xee05: {'align': 'l', 'valign': 'c', 'stretch': '^xy', 'params': {'overlap': 0.05, 'careful': True}}, + } + CUSTOM_ATTR = { + # previous custom scaling => do not touch the icons + # 'default': {'align': 'c', 'valign': '', 'stretch': '', 'params': {}} + 'default': {'align': 'c', 'valign': 'c', 'stretch': 'pa', 'params': {'careful': self.args.careful}} + } + + # Most glyphs we want to maximize (individually) during the scale + # However, there are some that need to be small or stay relative in + # size to each other. + # The glyph-specific behavior can be given as ScaleRules in the patch-set. + # + # ScaleRules can contain two different kind of rules (possibly in parallel): + # - ScaleGlyph: + # Here one specific glyph is used as 'scale blueprint'. Other glyphs are + # scaled by the same factor as this glyph. This is useful if you have one + # 'biggest' glyph and all others should stay relatively in size. + # Shifting in addition to scaling can be selected too (see below). + # - ScaleGroups: + # Here you specify a group of glyphs that should be handled together + # with the same scaling and shifting (see bottom). The basis for it is + # a 'combined bounding box' of all glyphs in that group. All glyphs are + # handled as if they fill that combined bounding box. + # (- ScaleGroupsVert: Removed with this commit) + # + # The ScaleGlyph method: You set 'ScaleGlyph' to the unicode of the reference glyph. + # Note that there can be only one per patch-set. + # Additionally you set 'GlyphsToScale' that contains all the glyphs that shall be + # handled (scaled) like the reference glyph. + # It is a List of: ((glyph code) or (tuple of two glyph codes that form a closed range)) + # 'GlyphsToScale': [ + # 0x0100, 0x0300, 0x0400, # The single glyphs 0x0100, 0x0300, and 0x0400 + # (0x0200, 0x0210), # All glyphs 0x0200 to 0x0210 including both 0x0200 and 0x0210 + # ]} + # If you want to not only scale but also shift as the reference glyph you give the + # data as 'GlyphsToScale+'. Note that only one set is used and the plus version is preferred. + # + # For the ScaleGroup method you define any number groups of glyphs and each group is + # handled separately. The combined bounding box of all glyphs in the group is determined + # and based on that the scale and shift (see bottom) for all the glyphs in the group. + # You define the groups as value of 'ScaleGroups'. + # It is a List of: ((lists of glyph codes) or (ranges of glyph codes)) + # 'ScaleGroups': [ + # [0x0100, 0x0300, 0x0400], # One group consists of glyphs 0x0100, 0x0300, and 0x0400 + # range(0x0200, 0x0210 + 1), # Another group contains glyphs 0x0200 to 0x0210 incl. + # + # Note the subtle differences: tuple vs. range; closed vs open range; etc + # See prepareScaleRules() for some more details. + # For historic reasons ScaleGroups is sometimes called 'new method' and ScaleGlyph 'old'. + # The codepoints mentioned here are symbol-font-codepoints. + # + # Shifting: + # If we have a combined bounding box stored in a range, that + # box is used to align all symbols in the range identically. + # - If the symbol font is proportinal only the v alignment is synced. + # - If the symbol font is monospaced v and h alignemnts are synced. + # To make sure the behavior is as expected you are required to set a ShiftMode property + # accordingly. It just checks, you can not (!) select what is done with that property. + + BOX_SCALE_LIST = {'ShiftMode': 'xy', 'ScaleGroups': [ + [*range(0x2500, 0x2570 + 1), *range(0x2574, 0x257f + 1)], # box drawing + range(0x2571, 0x2573 + 1), # diagonals + range(0x2580, 0x259f + 1), # blocks and greys (greys are less tall originally, so overlap will be less) + ]} + CODI_SCALE_LIST = {'ShiftMode': 'xy', 'ScaleGroups': [ + [0xea61, 0xeb13], # lightbulb + range(0xeab4, 0xeab7 + 1), # chevrons + [0xea7d, *range(0xea99, 0xeaa1 + 1), 0xebcb], # arrows + [0xeaa2, 0xeb9a, 0xec08, 0xec09], # bells + range(0xead4, 0xead6 + 1), # dot and arrow + [0xeb43, 0xec0b, 0xec0c], # (pull) request changes + range(0xeb6e, 0xeb71 + 1), # triangles + [*range(0xeb89, 0xeb8b + 1), 0xec07], # smallish dots + range(0xebd5, 0xebd7 + 1), # compasses + ]} + DEVI_SCALE_LIST = None + FONTA_SCALE_LIST = {'ShiftMode': '', 'ScaleGroups': [ + [0xf005, 0xf006, 0xf089], # star, star empty, half star + range(0xf026, 0xf028 + 1), # volume off, down, up + range(0xf02b, 0xf02c + 1), # tag, tags + range(0xf031, 0xf035 + 1), # font et al + range(0xf044, 0xf046 + 1), # edit, share, check (boxes) + range(0xf048, 0xf052 + 1), # multimedia buttons + range(0xf060, 0xf063 + 1), # arrows + [0xf053, 0xf054, 0xf077, 0xf078], # chevron all directions + range(0xf07d, 0xf07e + 1), # resize + range(0xf0a4, 0xf0a7 + 1), # pointing hands + [0xf0d7, 0xf0d8, 0xf0d9, 0xf0da, 0xf0dc, 0xf0dd, 0xf0de], # caret all directions and same looking sort + range(0xf100, 0xf107 + 1), # angle + range(0xf130, 0xf131 + 1), # mic + range(0xf141, 0xf142 + 1), # ellipsis + range(0xf153, 0xf15a + 1), # currencies + range(0xf175, 0xf178 + 1), # long arrows + range(0xf182, 0xf183 + 1), # male and female + range(0xf221, 0xf22d + 1), # gender or so + range(0xf255, 0xf25b + 1), # hand symbols + ]} + HEAVY_SCALE_LIST = {'ShiftMode': 'xy', 'ScaleGroups': [ + range(0x276c, 0x2771+1) + ]} + OCTI_SCALE_LIST = {'ShiftMode': '', 'ScaleGroups': [ + [*range(0xf03d, 0xf040 + 1), 0xf019, 0xf030, 0xf04a, 0xf051, 0xf071, 0xf08c ], # arrows + [0xF0E7, # Smily and ... + 0xf044, 0xf05a, 0xf05b, 0xf0aa, # triangles + 0xf052, 0xf053, 0xf296, 0xf2f0, # small stuff + 0xf078, 0xf0a2, 0xf0a3, 0xf0a4, # chevrons + 0xf0ca, 0xf081, 0xf092, # dash, X, github-text + ], + [0xf09c, 0xf09f, 0xf0de], # bells + range(0xf2c2, 0xf2c5 + 1), # move to + [0xf07b, 0xf0a1, 0xf0d6, 0xf306], # bookmarks + ]} + PROGR_SCALE_LIST = {'ShiftMode': 'xy', 'ScaleGroups': [ + range(0xedff, 0xee05 + 1), # boxes... with helper glyph EDFF for Y padding + range(0xee06, 0xee0b + 1), # circles + ]} + WEATH_SCALE_LIST = {'ShiftMode': '', 'ScaleGroups': [ + [0xf03c, 0xf042, 0xf045 ], # degree signs + [0xf043, 0xf044, 0xf048, 0xf04b, 0xf04c, 0xf04d, 0xf057, 0xf058, 0xf087, 0xf088], # arrows + range(0xf053, 0xf055 + 1), # thermometers + [*range(0xf059, 0xf061 + 1), 0xf0b1], # wind directions + range(0xf089, 0xf094 + 1), # clocks + range(0xf095, 0xf0b0 + 1), # moon phases + range(0xf0b7, 0xf0c3 + 1), # wind strengths + [0xf06e, 0xf070 ], # solar/lunar eclipse + [0xf051, 0xf052, 0xf0c9, 0xf0ca, 0xf072 ], # sun/moon up/down + [0xf049, 0xf056, 0xf071, *range(0xf073, 0xf07c + 1), 0xf08a], # other things + # Note: Codepoints listed before that are also in the following range + # will take the scaling of the previous group (the ScaleGroups are + # searched through in definition order). + # But be careful, the combined bounding box for the following group + # _will_ include all glyphs in its definition: Make sure the exempt + # glyphs from above are smaller (do not extend) the combined bounding + # box of this range: + [ *range(0xf000, 0xf041 + 1), + *range(0xf064, 0xf06d + 1), + *range(0xf07d, 0xf083 + 1), + *range(0xf085, 0xf086 + 1), + *range(0xf0b2, 0xf0b6 + 1) + ], # lots of clouds (weather states) (Please read note above!) + ]} + MDI_SCALE_LIST = None # Maybe later add some selected ScaleGroups + + + # Define the character ranges + # Symbol font ranges + self.patch_set = [ + {'Enabled': True, 'Name': "Seti-UI + Custom", 'Filename': "original-source.otf", 'Exact': False, 'SymStart': 0xE4FA, 'SymEnd': 0xE5FF, 'SrcStart': 0xE5FA, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': True, 'Name': "Heavy Angle Brackets", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x276C, 'SymEnd': 0x2771, 'SrcStart': None, 'ScaleRules': HEAVY_SCALE_LIST, 'Attributes': SYM_ATTR_HEAVYBRACKETS}, + {'Enabled': box_enabled, 'Name': "Box Drawing", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0x2500, 'SymEnd': 0x259F, 'SrcStart': None, 'ScaleRules': BOX_SCALE_LIST, 'Attributes': SYM_ATTR_BOX}, + {'Enabled': True, 'Name': "Progress Indicators", 'Filename': "extraglyphs.sfd", 'Exact': True, 'SymStart': 0xEE00, 'SymEnd': 0xEE0B, 'SrcStart': None, 'ScaleRules': PROGR_SCALE_LIST, 'Attributes': SYM_ATTR_PROGRESS}, + {'Enabled': True, 'Name': "Devicons", 'Filename': "devicons/devicons.otf", 'Exact': False, 'SymStart': 0xE600, 'SymEnd': 0xE7EF, 'SrcStart': 0xE700, 'ScaleRules': DEVI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0A0, 'SymEnd': 0xE0A2, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerline, 'Name': "Powerline Symbols", 'Filename': "powerline-symbols/PowerlineSymbols.otf", 'Exact': True, 'SymStart': 0xE0B0, 'SymEnd': 0xE0B3, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "powerline-extra/PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0A3, 'SymEnd': 0xE0A3, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "powerline-extra/PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0B4, 'SymEnd': 0xE0C8, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "powerline-extra/PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CA, 'SymEnd': 0xE0CA, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "powerline-extra/PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0xE0CC, 'SymEnd': 0xE0D7, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_POWERLINE}, + {'Enabled': self.args.powerlineextra, 'Name': "Powerline Extra Symbols", 'Filename': "powerline-extra/PowerlineExtraSymbols.otf", 'Exact': True, 'SymStart': 0x2630, 'SymEnd': 0x2630, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_TRIGRAPH}, + {'Enabled': self.args.pomicons, 'Name': "Pomicons", 'Filename': "pomicons/Pomicons.otf", 'Exact': True, 'SymStart': 0xE000, 'SymEnd': 0xE00A, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.fontawesome, 'Name': "Font Awesome", 'Filename': "font-awesome/FontAwesome.otf", 'Exact': True, 'SymStart': 0xED00, 'SymEnd': 0xF2FF, 'SrcStart': None, 'ScaleRules': FONTA_SCALE_LIST, 'Attributes': SYM_ATTR_FONTA}, + {'Enabled': self.args.fontawesomeextension, 'Name': "Font Awesome Extension", 'Filename': "font-awesome-extension.ttf", 'Exact': False, 'SymStart': 0xE000, 'SymEnd': 0xE0A9, 'SrcStart': 0xE200, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Maximize + {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x23FB, 'SymEnd': 0x23FE, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Power, Power On/Off, Power On, Sleep + {'Enabled': self.args.powersymbols, 'Name': "Power Symbols", 'Filename': "Unicode_IEC_symbol_font.otf", 'Exact': True, 'SymStart': 0x2B58, 'SymEnd': 0x2B58, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, # Heavy Circle (aka Power Off) + {'Enabled': False , 'Name': "Material legacy", 'Filename': "materialdesign/materialdesignicons-webfont.ttf", 'Exact': False, 'SymStart': 0xF001, 'SymEnd': 0xF847, 'SrcStart': 0xF500, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.material, 'Name': "Material", 'Filename': "materialdesign/MaterialDesignIconsDesktop.ttf", 'Exact': True, 'SymStart': 0xF0001,'SymEnd': 0xF1AF0,'SrcStart': None, 'ScaleRules': MDI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.weather, 'Name': "Weather Icons", 'Filename': "weather-icons/weathericons-regular-webfont.ttf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF0EB, 'SrcStart': 0xE300, 'ScaleRules': WEATH_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.fontlogos, 'Name': "Font Logos", 'Filename': "font-logos.ttf", 'Exact': True, 'SymStart': 0xF300, 'SymEnd': 0xF381, 'SrcStart': None, 'ScaleRules': None, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.otf", 'Exact': False, 'SymStart': 0xF000, 'SymEnd': 0xF105, 'SrcStart': 0xF400, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Magnifying glass + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.otf", 'Exact': True, 'SymStart': 0x2665, 'SymEnd': 0x2665, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Heart + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.otf", 'Exact': True, 'SymStart': 0X26A1, 'SymEnd': 0X26A1, 'SrcStart': None, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, # Zap + {'Enabled': self.args.octicons, 'Name': "Octicons", 'Filename': "octicons/octicons.otf", 'Exact': False, 'SymStart': 0xF27C, 'SymEnd': 0xF306, 'SrcStart': 0xF4A9, 'ScaleRules': OCTI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.codicons, 'Name': "Codicons", 'Filename': "codicons/codicon.ttf", 'Exact': True, 'SymStart': 0xEA60, 'SymEnd': 0xEC1E, 'SrcStart': None, 'ScaleRules': CODI_SCALE_LIST, 'Attributes': SYM_ATTR_DEFAULT}, + {'Enabled': self.args.custom, 'Name': "Custom", 'Filename': self.args.custom, 'Exact': True, 'SymStart': 0x0000, 'SymEnd': 0x0000, 'SrcStart': None, 'ScaleRules': None, 'Attributes': CUSTOM_ATTR} + ] + + def improve_line_dimensions(self): + # Make the total line size even. This seems to make the powerline separators + # center more evenly. + if self.args.adjustLineHeight: + if (self.sourceFont.os2_winascent + self.sourceFont.os2_windescent) % 2 != 0: + # All three are equal before due to get_sourcefont_dimensions() + self.sourceFont.hhea_ascent += 1 + self.sourceFont.os2_typoascent += 1 + self.sourceFont.os2_winascent += 1 + + def add_glyphrefs_to_essential(self, unicode): + self.essential.add(unicode) + # According to fontforge spec, altuni is either None or a tuple of tuples + # Those tuples contained in altuni are of the following "format": + # (unicode-value, variation-selector, reserved-field) + altuni = self.sourceFont[unicode].altuni + if altuni is not None: + for altcode in [ v for v, s, r in altuni if v >= 0 ]: + # If alternate unicode already exists in self.essential, + # that means it has gone through this function before. + # Therefore we skip it to avoid infinite loop. + # A unicode value of -1 basically means unused and is also worth skipping. + if altcode not in self.essential: + self.add_glyphrefs_to_essential(altcode) + # From fontforge documentation: + # glyph.references return a tuple of tuples containing, for each reference in foreground, + # a glyph name, a transformation matrix, and (depending on ff version) whether the + # reference is currently selected. + references = self.sourceFont[unicode].references + for refcode in [ self.sourceFont[n].unicode for n, *_ in references ]: # tuple of 2 or 3 depending on ff version + if refcode not in self.essential and refcode >= 0: + self.add_glyphrefs_to_essential(refcode) + + def get_essential_references(self): + """Find glyphs that are needed for the basic glyphs""" + # Sometimes basic glyphs are constructed from multiple other glyphs. + # Find out which other glyphs are also needed to keep the basic + # glyphs intact. + # 0x0000-0x017f is the Latin Extended-A range + # 0xfb00-0xfb06 are 'fi' and other ligatures + basic_glyphs = { c for c in range(0x21, 0x17f + 1) if c in self.sourceFont } + # Collect substitution destinations + for glyph in list(basic_glyphs) + [*range(0xfb00, 0xfb06 + 1)]: + if not glyph in self.sourceFont: + continue + for possub in self.sourceFont[glyph].getPosSub('*'): + if possub[1] == 'Substitution' or possub[1] == 'Ligature': + basic_glyphs.add(glyph) + basic_glyphs.add(self.sourceFont[possub[2]].unicode) + basic_glyphs.discard(-1) # the .notdef glyph + for glyph in basic_glyphs: + self.add_glyphrefs_to_essential(glyph) + + def get_sourcefont_dimensions(self): + """ This gets the font dimensions (cell width and height), and makes them equal on all platforms """ + # Step 1 + # There are three ways to describe the baseline to baseline distance + # (a.k.a. line spacing) of a font. That is all a kuddelmuddel + # and we try to sort this out here + # See also https://glyphsapp.com/learn/vertical-metrics + # See also https://github.com/source-foundry/font-line + (hhea_btb, typo_btb, win_btb, win_gap) = get_btb_metrics(self.sourceFont) + use_typo = self.sourceFont.os2_use_typo_metrics != 0 + + Metric = Enum('Metric', get_metrics_names()) + + if not self.args.metrics: + # We use either TYPO (1) or WIN (2) and compare with HHEA + # and use HHEA (0) if the fonts seems broken - no WIN, see #1056 + our_btb = typo_btb if use_typo else win_btb + if our_btb == hhea_btb: + metrics = Metric.TYPO if use_typo else Metric.WIN # conforming font + elif abs(our_btb - hhea_btb) / our_btb < 0.03: + logger.info("Font vertical metrics slightly off (%.1f%%)", (our_btb - hhea_btb) / our_btb * 100.0) + metrics = Metric.TYPO if use_typo else Metric.WIN + else: + # Try the other metric + our_btb = typo_btb if not use_typo else win_btb + if our_btb == hhea_btb: + use_typo = not use_typo + logger.warning("Font vertical metrics probably wrong USE TYPO METRICS, assume opposite (i.e. %s)", repr(use_typo)) + self.sourceFont.os2_use_typo_metrics = 1 if use_typo else 0 + metrics = Metric.TYPO if use_typo else Metric.WIN + else: + # We trust the WIN metric more, see experiments in #1056 + logger.warning("Font vertical metrics inconsistent (HHEA %d / TYPO %d / WIN %d), using WIN", hhea_btb, typo_btb, win_btb) + our_btb = win_btb + metrics = Metric.WIN + else: + metrics = Metric[self.args.metrics] + logger.debug("Metrics in the font: HHEA %d / TYPO %d / WIN %d", hhea_btb, typo_btb, win_btb) + if metrics == Metric.HHEA: + our_btb = hhea_btb + elif metrics == Metric.TYPO: + our_btb = typo_btb + else: + our_btb = win_btb + logger.info("Manually selected metrics: %s (%d)", self.args.metrics, our_btb) + + # print("FINI hhea {} typo {} win {} use {} {} {}".format(hhea_btb, typo_btb, win_btb, use_typo, our_btb != hhea_btb, self.sourceFont.fontname)) + + self.font_dim = {'xmin': 0, 'ymin': 0, 'xmax': 0, 'ymax': 0, 'width' : 0, 'height': 0, 'iconheight': 0, 'ypadding': 0} + + if metrics == Metric.HHEA: + self.font_dim['ymin'] = self.sourceFont.hhea_descent - half_gap(self.sourceFont.hhea_linegap, False) + self.font_dim['ymax'] = self.sourceFont.hhea_ascent + half_gap(self.sourceFont.hhea_linegap, True) + elif metrics == Metric.TYPO: + self.font_dim['ymin'] = self.sourceFont.os2_typodescent - half_gap(self.sourceFont.os2_typolinegap, False) + self.font_dim['ymax'] = self.sourceFont.os2_typoascent + half_gap(self.sourceFont.os2_typolinegap, True) + elif metrics == Metric.WIN: + self.font_dim['ymin'] = -self.sourceFont.os2_windescent - half_gap(win_gap, False) + self.font_dim['ymax'] = self.sourceFont.os2_winascent + half_gap(win_gap, True) + else: + logger.debug("Metrics is strange") + pass # Will fail the metrics check some line later + + if isinstance(self.args.cellopt, list): + logger.debug("Overriding cell Y{%d:%d} with Y{%d:%d}", + self.font_dim['ymin'], self.font_dim['ymax'], + self.args.cellopt[2], self.args.cellopt[3]) + self.font_dim['ymin'] = self.args.cellopt[2] + self.font_dim['ymax'] = self.args.cellopt[3] + our_btb = self.args.cellopt[3] - self.args.cellopt[2] + + # Calculate font height + self.font_dim['height'] = -self.font_dim['ymin'] + self.font_dim['ymax'] + if self.font_dim['height'] == 0: + # This can only happen if the input font is empty + # Assume we are using our prepared templates + self.symbolsonly = True + self.font_dim = { + 'xmin' : 0, + 'ymin' : -self.sourceFont.descent, + 'xmax' : self.sourceFont.em, + 'ymax' : self.sourceFont.ascent, + 'width' : self.sourceFont.em, + 'height' : self.sourceFont.descent + self.sourceFont.ascent, + 'iconheight': self.sourceFont.descent + self.sourceFont.ascent, + 'ypadding' : 0, + } + our_btb = self.sourceFont.descent + self.sourceFont.ascent + if self.font_dim['height'] <= 0: + logger.critical("Can not detect sane font height") + sys.exit(1) + + self.font_dim['iconheight'] = self.font_dim['height'] + if self.args.single and self.sourceFont.capHeight > 0 and not isinstance(self.args.cellopt, list): + # Limit the icon height on monospaced fonts because very slender and tall icons render + # excessively tall otherwise. We ignore that effect for the other variants because it + # does not look so much out of place there. + # Icons can be bigger than the letter capitals, but not the whole cell: + self.font_dim['iconheight'] = (self.sourceFont.capHeight * 2 + self.font_dim['height']) / 3 + + # Make all metrics equal + self.sourceFont.os2_typolinegap = 0 + self.sourceFont.os2_typoascent = self.font_dim['ymax'] + self.sourceFont.os2_typodescent = self.font_dim['ymin'] + self.sourceFont.os2_winascent = self.sourceFont.os2_typoascent + self.sourceFont.os2_windescent = -self.sourceFont.os2_typodescent + self.sourceFont.hhea_ascent = self.sourceFont.os2_typoascent + self.sourceFont.hhea_descent = self.sourceFont.os2_typodescent + self.sourceFont.hhea_linegap = self.sourceFont.os2_typolinegap + self.sourceFont.os2_use_typo_metrics = 1 + (check_hhea_btb, check_typo_btb, check_win_btb, _) = get_btb_metrics(self.sourceFont) + if check_hhea_btb != check_typo_btb or check_typo_btb != check_win_btb or check_win_btb != our_btb: + logger.critical("Error in baseline to baseline code detected") + sys.exit(1) + + # Step 2 + # Find the biggest char width and advance width + # 0x00-0x17f is the Latin Extended-A range + warned1 = self.args.nonmono # Do not warn if proportional target + warned2 = warned1 + for glyph in range(0x21, 0x17f): + if glyph in range(0x7F, 0xBF) or glyph in [ + 0x132, 0x133, # IJ, ij (in Overpass Mono) + 0x022, 0x027, 0x060, # Single and double quotes in Inconsolata LGC + 0x0D0, 0x10F, 0x110, 0x111, 0x127, 0x13E, 0x140, 0x165, # Eth and others with stroke or caron in RobotoMono + 0x149, # napostrophe in DaddyTimeMono + 0x02D, # hyphen for Monofur + ]: + continue # ignore special characters like '1/4' etc and some specifics + try: + (_, _, xmax, _) = self.sourceFont[glyph].boundingBox() + except TypeError: + continue + # print("WIDTH {:X} {} ({} {})".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) + if self.font_dim['width'] < self.sourceFont[glyph].width: + self.font_dim['width'] = self.sourceFont[glyph].width + if not warned1 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z + logger.debug("Extended glyphs wider than basic glyphs, results might be useless") + logger.debug("%s", report_advance_widths(self.sourceFont)) + warned1 = True + # print("New MAXWIDTH-A {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) + if xmax > self.font_dim['xmax']: + self.font_dim['xmax'] = xmax + if not warned2 and glyph > 0x7a: # NOT 'basic' glyph, which includes a-zA-Z + logger.debug("Extended glyphs wider bounding box than basic glyphs") + warned2 = True + # print("New MAXWIDTH-B {:X} {} -> {} {}".format(glyph, self.sourceFont[glyph].width, self.font_dim['width'], xmax)) + if self.font_dim['width'] < self.font_dim['xmax']: + logger.debug("Font has negative right side bearing in extended glyphs") + self.font_dim['xmax'] = self.font_dim['width'] # In fact 'xmax' is never used + if self.font_dim['width'] <= 0: + logger.critical("Can not detect sane font width") + sys.exit(1) + if isinstance(self.args.cellopt, list): + logger.debug("Overriding cell X{%d:%d} with X{%d:%d}", + self.font_dim['xmin'], self.font_dim['xmin'] + self.font_dim['width'], + self.args.cellopt[0], self.args.cellopt[1]) + self.font_dim['xmin'] = self.args.cellopt[0] + self.font_dim['xmax'] = self.args.cellopt[1] + self.font_dim['width'] = self.args.cellopt[1] + if self.args.cellopt: + logger.info("Cell coordinates (Xmin:Xmax:Ymin:Ymax) %s%d:%d:%d:%d", + '' if not isinstance(self.args.cellopt, list) else 'overridden with ', + self.font_dim['xmin'], self.font_dim['width'], + self.font_dim['ymax'] - self.font_dim['height'], self.font_dim['ymax']) + logger.debug("Final font cell dimensions %d w x %d h%s", + self.font_dim['width'], self.font_dim['height'], + ' (with icon cell {} h)'.format(int(self.font_dim['iconheight'])) if self.font_dim['iconheight'] != self.font_dim['height'] else '') + try: + middle = lambda x, y: abs(x - y) / 2 + min(x, y) + x_bb = self.sourceFont['x'].boundingBox(); + X_bb = self.sourceFont['X'].boundingBox(); + logger.debug("Center x-height/cell/capitals %d/%d/%d", + middle(x_bb[1], x_bb[3]), + middle(self.font_dim['ymin'], self.font_dim['ymax']), + middle(X_bb[1], X_bb[3])) + except: + pass + + self.xavgwidth.append(self.args.xavgwidth) + if isinstance(self.xavgwidth[-1], int) and self.xavgwidth[-1] == 0: + self.xavgwidth[-1] = get_old_average_x_width(self.sourceFont) + + + def get_target_width(self, stretch): + """ Get the target width (1 or 2 'cell') for a given stretch parameter """ + # For monospaced fonts all chars need to be maximum 'one' space wide + # other fonts allows double width glyphs for 'pa' or if requested with '2' + if self.args.single or ('pa' not in stretch and '2' not in stretch) or '1' in stretch: + return 1 + return 2 + + def get_scale_factors(self, sym_dim, stretch, overlap=None): + """ Get scale in x and y as tuple """ + # It is possible to have empty glyphs, so we need to skip those. + if not sym_dim['width'] or not sym_dim['height']: + return (1.0, 1.0) + + target_width = self.font_dim['width'] * self.get_target_width(stretch) + if overlap: + target_width += self.font_dim['width'] * overlap + scale_ratio_x = target_width / sym_dim['width'] + + # font_dim['height'] represents total line height, keep our symbols sized based upon font's em + # Use the font_dim['height'] only for explicit 'y' scaling (not 'pa') + target_height = self.font_dim['height'] if '^' in stretch else self.font_dim['iconheight'] + target_height *= 1.0 - self.font_dim['ypadding'] + if overlap: + target_height *= 1.0 + min(0.01, overlap) # never aggressive vertical overlap + scale_ratio_y = target_height / sym_dim['height'] + + if 'pa' in stretch: + # We want to preserve x/y aspect ratio, so find biggest scale factor that allows symbol to fit + scale_ratio_x = min(scale_ratio_x, scale_ratio_y) + if not self.args.single and not '!' in stretch and not overlap: + # non monospaced fonts just scale down on 'pa', not up + scale_ratio_x = min(scale_ratio_x, 1.0) + scale_ratio_y = scale_ratio_x + else: + # Keep the not-stretched direction + if not 'x' in stretch: + scale_ratio_x = 1.0 + if not 'y' in stretch: + scale_ratio_y = 1.0 + + return (scale_ratio_x, scale_ratio_y) + + + def copy_glyphs(self, sourceFontStart, symbolFont, symbolFontStart, symbolFontEnd, exactEncoding, scaleRules, setName, attributes): + """ Copies symbol glyphs into self.sourceFont """ + progressText = '' + careful = False + sourceFontCounter = 0 + + if self.args.careful: + careful = True + + # Create glyphs from symbol font + # + # If we are going to copy all Glyphs, then assume we want to be careful + # and only copy those that are not already contained in the source font + if symbolFontStart == 0: + symbolFont.selection.all() + careful = True + else: + symbolFont.selection.select((str("ranges"), str("unicode")), symbolFontStart, symbolFontEnd) + + # Get number of selected non-empty glyphs with codes >=0 (i.e. not -1 == notdef) + symbolFontSelection = [ x for x in symbolFont.selection.byGlyphs if x.unicode >= 0 ] + glyphSetLength = len(symbolFontSelection) + + if not self.args.quiet: + modify = attributes['default']['params'].get('dont_copy') + sys.stdout.write("{} {} Glyphs from {} Set\n".format( + "Adding" if not modify else "Rescaling", glyphSetLength, setName)) + + currentSourceFontGlyph = -1 # initialize for the exactEncoding case + width_warning = False + + for index, sym_glyph in enumerate(symbolFontSelection): + sym_attr = attributes.get(sym_glyph.unicode) + if sym_attr is None: + sym_attr = attributes['default'] + + if self.font_extrawide: + # Do not allow 'xy2' scaling + sym_attr['stretch'] = sym_attr['stretch'].replace('2', '') + + if exactEncoding: + # Use the exact same hex values for the source font as for the symbol font. + # Problem is we do not know the codepoint of the sym_glyph and because it + # came from a selection.byGlyphs there might be skipped over glyphs. + # The iteration is still in the order of the selection by codepoint, + # so we take the next allowed codepoint of the current glyph + possible_codes = [ ] + if sym_glyph.unicode > currentSourceFontGlyph: + possible_codes += [ sym_glyph.unicode ] + if sym_glyph.altuni: + possible_codes += [ v for v, s, r in sym_glyph.altuni if v > currentSourceFontGlyph ] + if len(possible_codes) == 0: + logger.warning("Can not determine codepoint of %X. Skipping...", sym_glyph.unicode) + continue + currentSourceFontGlyph = min(possible_codes) + else: + # use source font defined hex values based on passed in start (fills gaps; symbols are packed) + currentSourceFontGlyph = sourceFontStart + sourceFontCounter + sourceFontCounter += 1 + + # For debugging process only limited glyphs + # if currentSourceFontGlyph != 0xe7bd: + # continue + + ypadding = sym_attr['params'].get('ypadding') + self.font_dim['ypadding'] = ypadding or 0.0 + + if not self.args.quiet: + if self.args.progressbars: + update_progress(round(float(index + 1) / glyphSetLength, 2)) + else: + progressText = "\nUpdating glyph: {} {} putting at: {:X}".format(sym_glyph, sym_glyph.glyphname, currentSourceFontGlyph) + sys.stdout.write(progressText) + sys.stdout.flush() + + # check if a glyph already exists in this location + do_careful = sym_attr['params'].get('careful', careful) # params take precedence + if do_careful or currentSourceFontGlyph in self.essential: + if currentSourceFontGlyph in self.sourceFont: + careful_type = 'essential' if currentSourceFontGlyph in self.essential else 'existing' + logger.debug("Found %s Glyph at %X. Skipping...", careful_type, currentSourceFontGlyph) + # We don't want to touch anything so move to next Glyph + continue + else: + # If we overwrite an existing glyph all subtable entries regarding it will be wrong + # (Probably; at least if we add a symbol and do not substitute a ligature or such) + if currentSourceFontGlyph in self.sourceFont: + self.sourceFont[currentSourceFontGlyph].removePosSub("*") + + stretch = sym_attr['stretch'] + dont_copy = sym_attr['params'].get('dont_copy') + + if dont_copy: + # Just prepare scaling of existing glyphs + glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, stretch, self.sourceFont, currentSourceFontGlyph) if scaleRules is not None else None + else: + # Break apart multiple unicodes linking to one glyph + if currentSourceFontGlyph in self.sourceFont: + altuni = self.sourceFont[currentSourceFontGlyph].altuni + if altuni: + codes = { v for v, s, r in altuni if v >= 0 } + codes.add(self.sourceFont[currentSourceFontGlyph].unicode) + codes.remove(currentSourceFontGlyph) + codes = [ "{:04X}".format(c) for c in sorted(list(codes)) ] + logger.debug("Removing alternate unicode on %X (%s)", currentSourceFontGlyph, ' '.join(codes)); + self.sourceFont[currentSourceFontGlyph].altuni = None + self.sourceFont.encoding = 'UnicodeFull' # Rebuild encoding table (needed after altuni changes) + + # This will destroy any content currently in currentSourceFontGlyph, so do it first + glyph_scale_data = self.get_glyph_scale(sym_glyph.encoding, scaleRules, stretch, symbolFont, currentSourceFontGlyph) if scaleRules is not None else None + + # Select and copy symbol from its encoding point + # We need to do this select after the careful check, this way we don't + # reset our selection before starting the next loop + symbolFont.selection.select(sym_glyph.encoding) + symbolFont.copy() + + # Paste it + self.sourceFont.selection.select(currentSourceFontGlyph) + self.sourceFont.paste() + self.sourceFont[currentSourceFontGlyph].glyphname = \ + self.glyphnames.get(currentSourceFontGlyph, sym_glyph.glyphname) if setName != 'Custom' else sym_glyph.glyphname + self.sourceFont[currentSourceFontGlyph].manualHints = True # No autohints for symbols + + # Prepare symbol glyph dimensions + sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph]) + overlap = sym_attr['params'].get('overlap') + if overlap and ypadding: + logger.critical("Conflicting params: overlap and ypadding") + sys.exit(1) + + if glyph_scale_data is not None: + if glyph_scale_data[1] is not None: + sym_dim = glyph_scale_data[1] # Use combined bounding box + (scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, stretch, overlap) + else: + # This is roughly alike get_scale_factors(glyph_scale_data[1], 'pa') + # Except we do not have glyph_scale_data[1] always... + (scale_ratio_x, scale_ratio_y) = (glyph_scale_data[0], glyph_scale_data[0]) + if overlap: + scale_ratio_x *= 1.0 + (self.font_dim['width'] / (sym_dim['width'] * scale_ratio_x)) * overlap + y_overlap = min(0.01, overlap) # never aggressive vertical overlap + scale_ratio_y *= 1.0 + (self.font_dim['height'] / (sym_dim['height'] * scale_ratio_y)) * y_overlap + else: + (scale_ratio_x, scale_ratio_y) = self.get_scale_factors(sym_dim, stretch, overlap) + + + # Size in x to size in y ratio limit (to prevent over-wide glyphs) + xy_ratio_max = sym_attr['params'].get('xy-ratio') + if (xy_ratio_max): + xy_ratio = sym_dim['width'] * scale_ratio_x / (sym_dim['height'] * scale_ratio_y) + if xy_ratio > xy_ratio_max: + scale_ratio_x = scale_ratio_x * xy_ratio_max / xy_ratio + + if scale_ratio_x != 1.0 or scale_ratio_y != 1.0: + scale_ratio_x *= self.sourceFont.em / (self.sourceFont.em + 1) # scale a tiny bit too small to avoid rounding problems + self.sourceFont[currentSourceFontGlyph].transform(psMat.scale(scale_ratio_x, scale_ratio_y)) + + # Drop nonintegral part of nodes' coordinates; ttf will do it anyhow, otf will be much smaller + self.sourceFont[currentSourceFontGlyph].round() + + if self.args.single: + # Check and correct the scaling after rounding (if all 3 tries fail we will get a warning later on) + destmaxsize = self.font_dim['width'] * max(1, 1 + (overlap or 0)) + for increaser in range(3): + (xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox() + sizeerror = (xmax - xmin) - destmaxsize + if sizeerror <= 0: + break + # Start from scratch with a new unscaled glyph + scale_ratio_x /= 1 + ((sizeerror + increaser) / destmaxsize) + self.sourceFont.paste() + self.sourceFont[currentSourceFontGlyph].transform(psMat.scale(scale_ratio_x, scale_ratio_y)) + self.sourceFont[currentSourceFontGlyph].round() + + # We pasted and scaled now we want to align/move + # Use the dimensions from the newly pasted and stretched glyph to avoid any rounding errors + sym_dim = get_glyph_dimensions(self.sourceFont[currentSourceFontGlyph]) + # Use combined bounding box? + if glyph_scale_data is not None and glyph_scale_data[1] is not None: + scaleglyph_dim = scale_bounding_box(glyph_scale_data[1], scale_ratio_x, scale_ratio_y) + if scaleglyph_dim['advance'] is None: + # On monospaced symbol collections use their advance with, otherwise align horizontally individually + scaleglyph_dim['xmin'] = sym_dim['xmin'] + scaleglyph_dim['xmax'] = sym_dim['xmax'] + scaleglyph_dim['width'] = sym_dim['width'] + sym_dim = scaleglyph_dim + + y_align_distance = 0 + if sym_attr['valign'] == 'c': + # Center the symbol vertically by matching the center of the line height and center of symbol + sym_ycenter = sym_dim['ymax'] - (sym_dim['height'] / 2) + font_ycenter = self.font_dim['ymax'] - (self.font_dim['height'] / 2) + y_align_distance = font_ycenter - sym_ycenter + + # Handle glyph l/r/c alignment + x_align_distance = 0 + simple_nonmono = self.args.nonmono and sym_dim['advance'] is None + if simple_nonmono: + # Remove left side bearing + # (i.e. do not remove left side bearing when combined BB is in use) + x_align_distance = -self.sourceFont[currentSourceFontGlyph].left_side_bearing + elif sym_attr['align']: + # First find the baseline x-alignment (left alignment amount) + x_align_distance = self.font_dim['xmin'] - sym_dim['xmin'] + if self.args.nonmono and 'pa' in stretch: + cell_width = sym_dim['advance'] or sym_dim['width'] + else: + cell_width = self.font_dim['width'] + if sym_attr['align'] == 'c': + # Center align + x_align_distance += (cell_width / 2) - (sym_dim['width'] / 2) + elif sym_attr['align'] == 'r': + # Right align + # (not really supported with pa scaling and 2x stretch in NFP) + x_align_distance += cell_width * self.get_target_width(stretch) - sym_dim['width'] + if not overlap: + # If symbol glyph is wider than target font cell, just left-align + x_align_distance = max(self.font_dim['xmin'] - sym_dim['xmin'], x_align_distance) + + if overlap: + overlap_width = self.font_dim['width'] * overlap + if sym_attr['align'] == 'l': + x_align_distance -= overlap_width + elif sym_attr['align'] == 'c': + # center aligned keeps being center aligned even with overlap + if overlap_width < 0 and simple_nonmono: # Keep positive bearing due to negative overlap (propo) + x_align_distance -= overlap_width / 2 + elif sym_attr['align'] == 'r' and not simple_nonmono: + # Check and correct overlap; it can go wrong if we have a xy-ratio limit + target_xmax = (self.font_dim['xmin'] + self.font_dim['width']) * self.get_target_width(stretch) + target_xmax += overlap_width + glyph_xmax = sym_dim['xmax'] + x_align_distance + correction = target_xmax - glyph_xmax + x_align_distance += correction + + align_matrix = psMat.translate(x_align_distance, y_align_distance) + self.sourceFont[currentSourceFontGlyph].transform(align_matrix) + + # Ensure after horizontal adjustments and centering that the glyph + # does not overlap the bearings (edges) + if not overlap: + self.remove_glyph_neg_bearings(self.sourceFont[currentSourceFontGlyph]) + + # Needed for setting 'advance width' on each glyph so they do not overlap, + # also ensures the font is considered monospaced on Windows by setting the + # same width for all character glyphs. This needs to be done for all glyphs, + # even the ones that are empty and didn't go through the scaling operations. + # It should come after setting the glyph bearings + if not self.args.nonmono: + self.set_glyph_width_mono(self.sourceFont[currentSourceFontGlyph]) + else: + # Target font with variable advance width get the icons with their native widths + # and keeping possible (right and/or negative) bearings in effect + if sym_dim['advance'] is not None: + # 'Width' from monospaced scale group + width = sym_dim['advance'] + else: + width = sym_dim['width'] + # If we have overlap we need to subtract that to keep/get negative bearings + if overlap: + width -= overlap_width + # Fontforge handles the width change like this: + # - Keep existing left_side_bearing + # - Set width + # - Calculate and set new right_side_bearing + self.sourceFont[currentSourceFontGlyph].width = int(width) + + # Check if the inserted glyph is scaled correctly for monospace + if self.args.single: + (xmin, _, xmax, _) = self.sourceFont[currentSourceFontGlyph].boundingBox() + if (xmax - xmin) > self.font_dim['width'] * max(1, 1 + (overlap or 0)): + logger.warning("Scaled glyph %X wider than one monospace width (%d / %d (overlap %s))", + currentSourceFontGlyph, int(xmax - xmin), self.font_dim['width'], repr(overlap)) + + # end for + + if not self.args.quiet: + sys.stdout.write("\n") + + + def set_sourcefont_glyph_widths(self): + """ Makes self.sourceFont monospace compliant """ + + for glyph in self.sourceFont.glyphs(): + if (glyph.width == self.font_dim['width']): + # Don't touch the (negative) bearings if the width is ok + # Ligatures will have these. + continue + + if (glyph.width != 0): + # If the width is zero this glyph is intended to be printed on top of another one. + # In this case we need to keep the negative bearings to shift it 'left'. + # Things like Ä have these: composed of U+0041 'A' and U+0308 'double dot above' + # + # If width is not zero, correct the bearings such that they are within the width: + self.remove_glyph_neg_bearings(glyph) + + self.set_glyph_width_mono(glyph) + + + def remove_glyph_neg_bearings(self, glyph): + """ Sets passed glyph's bearings 0 if they are negative. """ + try: + if glyph.left_side_bearing < 0: + glyph.left_side_bearing = 0 + if glyph.right_side_bearing < 0: + glyph.right_side_bearing = 0 + except: + pass + + + def set_glyph_width_mono(self, glyph): + """ Sets passed glyph.width to self.font_dim.width. + + self.font_dim.width is set with self.get_sourcefont_dimensions(). + """ + try: + # Fontforge handles the width change like this: + # - Keep existing left_side_bearing + # - Set width + # - Calculate and set new right_side_bearing + glyph.width = self.font_dim['width'] + except: + pass + + def prepareScaleRules(self, scaleRules, stretch, symbolFont, destGlyph): + """ Prepare raw ScaleRules data for use """ + # The scaleRules is/will be a dict with these (possible) entries: + # 'ScaleGroups': List of ((lists of glyph codes) or (ranges of glyph codes)) that shall be scaled + # 'scales': List of associated scale factors, one for each entry in 'ScaleGroups' (generated by this function) + # 'bbdims': List of associated sym_dim dicts, one for each entry in 'ScaleGroups' (generated by this function) + # Each dim_dict describes the combined bounding box of all glyphs in one ScaleGroups group + # Example: + # { 'ScaleGroups': [ range(1, 3), [ 7, 10 ], ], + # 'scales': [ 1.23, 1.33, ], + # 'bbdims': [ dim_dict1, dim_dict2, ] } + # + # Each item in 'ScaleGroups' (a range or an explicit list) forms a group of glyphs that shall be + # as rescaled all with the same and maximum possible (for the included glyphs) 'pa' factor. + # If the 'bbdims' is present they all shall be shifted in the same way. + # + # Previously this structure has been used: + # 'ScaleGlyph' Lead glyph, which scaling factor is taken + # 'GlyphsToScale': List of ((glyph code) or (tuple of two glyph codes that form a closed range)) that shall be scaled + # Note that this allows only one group for the whle symbol font, and that the scaling factor is defined by + # a specific character, which needs to be manually selected (on each symbol font update). + # Previous entries are automatically rewritten to the new style. + # + # Note that scaleRules is overwritten with the added data. + if 'scales' in scaleRules: + # Already prepared... must not happen, ignore call + return + + scaleRules['scales'] = [] + scaleRules['bbdims'] = [] + if 'ScaleGroups' not in scaleRules: + scaleRules['ScaleGroups'] = [] + + mode = scaleRules['ShiftMode'] # Mode is only documentary + for group in scaleRules['ScaleGroups']: + sym_dim = get_multiglyph_boundingBox([ symbolFont[g] if g in symbolFont else None for g in group ], destGlyph) + scale = self.get_scale_factors(sym_dim, stretch)[0] + scaleRules['scales'].append(scale) + scaleRules['bbdims'].append(sym_dim) + if (mode): + if ('x' in mode) != (sym_dim['advance'] is not None): + d = '0x{:X} - 0x{:X}'.format(group[0], group[-1]) + if ('x' in mode) : + logger.critical("Scaling in group %s is expected to do horizontal shifts but can not", d) + else: + logger.critical("Scaling in group %s is expected to not do horizontal shifts but will", d) + sys.exit(1) + + if 'ScaleGlyph' in scaleRules: + # Rewrite to equivalent ScaleGroup + group_list = [] + if 'GlyphsToScale+' in scaleRules: + key = 'GlyphsToScale+' + plus = True + else: + key = 'GlyphsToScale' + plus = False + for i in scaleRules[key]: + if isinstance(i, tuple): + group_list.append(range(i[0], i[1] + 1)) + else: + group_list.append(i) + sym_dim = get_glyph_dimensions(symbolFont[scaleRules['ScaleGlyph']]) + scale = self.get_scale_factors(sym_dim, stretch)[0] + scaleRules['ScaleGroups'].append(group_list) + scaleRules['scales'].append(scale) + if plus: + scaleRules['bbdims'].append(sym_dim) + else: + scaleRules['bbdims'].append(None) # The 'old' style keeps just the scale, not the positioning + + def get_glyph_scale(self, symbol_unicode, scaleRules, stretch, symbolFont, dest_unicode): + """ Determines whether or not to use scaled glyphs for glyph in passed symbol_unicode """ + # Potentially destroys the contents of self.sourceFont[dest_unicode] + if not 'scales' in scaleRules: + if not dest_unicode in self.sourceFont: + self.sourceFont.createChar(dest_unicode) + self.prepareScaleRules(scaleRules, stretch, symbolFont, self.sourceFont[dest_unicode]) + for glyph_list, scale, box in zip(scaleRules['ScaleGroups'], scaleRules['scales'], scaleRules['bbdims']): + for e in glyph_list: + if isinstance(e, range): + if symbol_unicode in e: + return (scale, box) + elif symbol_unicode == e: + return (scale, box) + return None + + +def half_gap(gap, top): + """ Divides integer value into two new integers """ + # Line gap add extra space on the bottom of the line which + # doesn't allow the powerline glyphs to fill the entire line. + # Put half of the gap into the 'cell', each top and bottom + if gap <= 0: + return 0 + gap_top = int(gap / 2) + gap_bottom = gap - gap_top + if top: + logger.info("Redistributing line gap of %d (%d top and %d bottom)", gap, gap_top, gap_bottom) + return gap_top + return gap_bottom + +def replace_font_name(font_name, replacement_dict): + """ Replaces all keys with vals from replacement_dict in font_name. """ + for key, val in replacement_dict.items(): + font_name = font_name.replace(key, val) + return font_name + + +def make_sure_path_exists(path): + """ Verifies path passed to it exists. """ + try: + os.makedirs(path) + except OSError as exception: + if exception.errno != errno.EEXIST: + raise + +def sanitize_filename(filename, allow_dirs = False): + """ Enforces to not use forbidden characters in a filename/path. """ + if filename == '.' and not allow_dirs: + return '_' + restore_colon = sys.platform == 'win32' and re.match('[a-z]:', filename, re.I) + trans = filename.maketrans('<>:"|?*', '_______') + for i in range(0x00, 0x20): + trans[i] = ord('_') + if not allow_dirs: + trans[ord('/')] = ord('_') + trans[ord('\\')] = ord('_') + else: + trans[ord('\\')] = ord('/') # We use Posix paths + new_filename = filename.translate(trans) + if restore_colon: + new_filename = new_filename[ :1] + ':' + new_filename[2: ] + return new_filename + +def get_multiglyph_boundingBox(glyphs, destGlyph = None): + """ Returns dict of the dimensions of multiple glyphs combined(, as if they are copied into destGlyph) """ + # If destGlyph is given the glyph(s) are first copied over into that + # glyph and measured in that font (to avoid rounding errors) + # Leaves the destGlyph in unknown state! + bbox = [ None, None, None, None, None ] + for glyph in glyphs: + if glyph is None: + # Glyph has been in defining range but is not in the actual font + continue + if destGlyph and glyph.font != destGlyph.font: + glyph.font.selection.select(glyph) + glyph.font.copy() + destGlyph.font.selection.select(destGlyph) + destGlyph.font.paste() + glyph = destGlyph + gbb = glyph.boundingBox() + gadvance = glyph.width + if len(glyphs) > 1 and gbb[0] == gbb[2] and gbb[1] == gbb[3]: + # Ignore empty glyphs if we examine more than one glyph + continue + bbox[0] = gbb[0] if bbox[0] is None or bbox[0] > gbb[0] else bbox[0] + bbox[1] = gbb[1] if bbox[1] is None or bbox[1] > gbb[1] else bbox[1] + bbox[2] = gbb[2] if bbox[2] is None or bbox[2] < gbb[2] else bbox[2] + bbox[3] = gbb[3] if bbox[3] is None or bbox[3] < gbb[3] else bbox[3] + if not bbox[4]: + bbox[4] = -gadvance # Negative for one/first glyph + else: + if abs(bbox[4]) != gadvance: + bbox[4] = -1 # Marker for not-monospaced + else: + bbox[4] = gadvance # Positive for 2 or more glyphs + if bbox[4] and bbox[4] < 0: + # Not monospaced when only one glyph is used or multiple glyphs with different advance widths + bbox[4] = None + return { + 'xmin' : bbox[0], + 'ymin' : bbox[1], + 'xmax' : bbox[2], + 'ymax' : bbox[3], + 'width' : bbox[2] + (-bbox[0]), + 'height' : bbox[3] + (-bbox[1]), + 'advance': bbox[4], # advance width if monospaced + } + +def get_glyph_dimensions(glyph): + """ Returns dict of the dimensions of the glyph passed to it. """ + return get_multiglyph_boundingBox([ glyph ]) + +def scale_bounding_box(bbox, scale_x, scale_y): + """ Return a scaled version of a glyph dimensions dict """ + # Simulate scaling on combined bounding box, round values for better simulation + new_dim = { + 'xmin' : int(bbox['xmin'] * scale_x), + 'ymin' : int(bbox['ymin'] * scale_y), + 'xmax' : int(bbox['xmax'] * scale_x), + 'ymax' : int(bbox['ymax'] * scale_y), + 'advance': int(bbox['advance'] * scale_x) if bbox['advance'] is not None else None, + } + new_dim['width'] = new_dim['xmax'] + (-new_dim['xmin']) + new_dim['height'] = new_dim['ymax'] + (-new_dim['ymin']) + return new_dim + +def update_progress(progress): + """ Updates progress bar length. + + Accepts a float between 0.0 and 1.0. Any int will be converted to a float. + A value at 1 or bigger represents 100% + modified from: https://stackoverflow.com/questions/3160699/python-progress-bar + """ + barLength = 40 # Modify this to change the length of the progress bar + if isinstance(progress, int): + progress = float(progress) + if progress >= 1: + progress = 1 + status = "Done...\r\n" # NOTE: status initialized and never used + block = int(round(barLength * progress)) + text = "\r╢{0}╟ {1}%".format("█" * block + "░" * (barLength - block), int(progress * 100)) + sys.stdout.write(text) + sys.stdout.flush() + + +def check_fontforge_min_version(): + """ Verifies installed FontForge version meets minimum requirement. """ + minimumVersion = 20141231 + actualVersion = int(fontforge.version()) + + # un-comment following line for testing invalid version error handling + # actualVersion = 20120731 + + # versions tested: 20150612, 20150824 + if actualVersion < minimumVersion: + logger.critical("You seem to be using an unsupported (old) version of fontforge: %d", actualVersion) + logger.critical("Please use at least version: %d", minimumVersion) + sys.exit(1) + +def check_version_with_git(version): + """ Upgraded the version to the current git tag version (starting with 'v') """ + git = subprocess.run("git describe --tags", + cwd=os.path.dirname(__file__), + shell=True, + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + ).stdout.decode('utf-8') + if len(git) == 0: + return False + tag = git.strip() + if len(tag) == 0 or not tag.startswith('v'): + return False + tag = tag[1:] + r = re.search('(.*?)(-[0-9]+)-g[0-9a-fA-F]+$', tag) + if r: + tag = r.group(1) + patchlevel = r.group(2) + else: + patchlevel = "" + # Inspired by Phaxmohdem's versiontuple https://stackoverflow.com/a/28568003 + + versiontuple = lambda v: tuple( p.zfill(8) for p in v.split(".") ) + if versiontuple(tag) > versiontuple(version): + return tag + patchlevel + if versiontuple(tag) == versiontuple(version) and len(patchlevel) > 0: + return tag + patchlevel + return False + +def setup_arguments(): + """ Parse the command line parameters and load the config file if needed """ + parser = argparse.ArgumentParser( + description=( + 'Nerd Fonts Font Patcher: patches a given font with programming and development related glyphs\n\n' + '* Website: https://www.nerdfonts.com\n' + '* Version: ' + version + '\n' + '* Development Website: https://github.com/ryanoasis/nerd-fonts\n' + '* Changelog: https://github.com/ryanoasis/nerd-fonts/blob/-/changelog.md'), + formatter_class=RawTextHelpFormatter, + add_help=False, + ) + + parser.add_argument('font', help='The path to the font to patch (e.g., Inconsolata.otf)') + # optional arguments + parser.add_argument('--careful', dest='careful', default=False, action='store_true', help='Do not overwrite existing glyphs if detected') + parser.add_argument('--debug', dest='debugmode', default=0, type=int, nargs='?', help='Verbose mode (optional: 1=just to file; 2*=just to terminal; 3=display and file)', const=2, choices=range(0, 3 + 1)) + parser.add_argument('--extension', '-ext', dest='extension', default="", type=str, help='Change font file type to create (e.g., ttf, otf)') + parser.add_argument('--help', '-h', action='help', default=argparse.SUPPRESS, help='Show this help message and exit') + parser.add_argument('--makegroups', dest='makegroups', default=1, type=int, nargs='?', help='Use alternative method to name patched fonts (default=1)', const=1, choices=range(-1, 6 + 1)) + parser.add_argument('--mono', '-s', dest='forcemono', default=False, action='count', help='Create monospaced font, existing and added glyphs are single-width (implies --single-width-glyphs)') + parser.add_argument('--outputdir', '-out', dest='outputdir', default=".", type=str, help='The directory to output the patched font file to') + parser.add_argument('--quiet', '-q', dest='quiet', default=False, action='store_true', help='Do not generate verbose output') + parser.add_argument('--single-width-glyphs', dest='single', default=False, action='store_true', help='Whether to generate the glyphs as single-width not double-width (default is double-width) (Nerd Font Mono)') + parser.add_argument('--use-single-width-glyphs', dest='forcemono', default=False, action='count', help=argparse.SUPPRESS) + parser.add_argument('--variable-width-glyphs', dest='nonmono', default=False, action='store_true', help='Do not adjust advance width (no "overhang") (Nerd Font Propo)') + parser.add_argument('--version', '-v', action='version', version=projectName + ': %(prog)s (' + version + ')', help='Show program\'s version number and exit') + # --makegroup has an additional undocumented numeric specifier. '--makegroup' is in fact '--makegroup 1'. + # Original font name: Hugo Sans Mono ExtraCondensed Light Italic + # NF Fam agg. + # -1 no renaming at all (keep old names and versions etc) --- --- --- + # 0 turned off, use old naming scheme [-] [-] [-] + # 1 HugoSansMono Nerd Font ExtraCondensed Light Italic [ ] [ ] [ ] + # 2 HugoSansMono Nerd Font ExtCn Light Italic [ ] [X] [ ] + # 3 HugoSansMono Nerd Font XCn Lt It [ ] [X] [X] + # 4 HugoSansMono NF ExtraCondensed Light Italic [X] [ ] [ ] + # 5 HugoSansMono NF ExtCn Light Italic [X] [X] [ ] + # 6 HugoSansMono NF XCn Lt It [X] [X] [X] + + sym_font_group = parser.add_argument_group('Symbol Fonts') + sym_font_group.add_argument('--complete', '-c', dest='complete', default=False, action='store_true', help='Add all available Glyphs') + sym_font_group.add_argument('--codicons', dest='codicons', default=False, action='store_true', help='Add Codicons Glyphs (https://github.com/microsoft/vscode-codicons)') + sym_font_group.add_argument('--fontawesome', dest='fontawesome', default=False, action='store_true', help='Add Font Awesome Glyphs (http://fontawesome.io/)') + sym_font_group.add_argument('--fontawesomeext', dest='fontawesomeextension', default=False, action='store_true', help='Add Font Awesome Extension Glyphs (https://andrelzgava.github.io/font-awesome-extension/)') + sym_font_group.add_argument('--fontlogos', dest='fontlogos', default=False, action='store_true', help='Add Font Logos Glyphs (https://github.com/Lukas-W/font-logos)') + sym_font_group.add_argument('--material', '--mdi', dest='material', default=False, action='store_true', help='Add Material Design Icons (https://github.com/templarian/MaterialDesign)') + sym_font_group.add_argument('--octicons', dest='octicons', default=False, action='store_true', help='Add Octicons Glyphs (https://octicons.github.com)') + sym_font_group.add_argument('--pomicons', dest='pomicons', default=False, action='store_true', help='Add Pomicon Glyphs (https://github.com/gabrielelana/pomicons)') + sym_font_group.add_argument('--powerline', dest='powerline', default=False, action='store_true', help='Add Powerline Glyphs') + sym_font_group.add_argument('--powerlineextra', dest='powerlineextra', default=False, action='store_true', help='Add Powerline Extra Glyphs (https://github.com/ryanoasis/powerline-extra-symbols)') + sym_font_group.add_argument('--powersymbols', dest='powersymbols', default=False, action='store_true', help='Add IEC Power Symbols (https://unicodepowersymbol.com/)') + sym_font_group.add_argument('--weather', dest='weather', default=False, action='store_true', help='Add Weather Icons (https://github.com/erikflowers/weather-icons)') + + expert_group = parser.add_argument_group('Expert Options') + expert_group.add_argument('--adjust-line-height', '-l', dest='adjustLineHeight', default=False, action='store_true', help='Whether to adjust line heights (attempt to center powerline separators more evenly)') + expert_group.add_argument('--boxdrawing', dest='forcebox', default=False, action='store_true', help='Force patching in (over existing) box drawing glyphs') + expert_group.add_argument('--cell', dest='cellopt', default=None, type=str, help='Adjust or query the cell size, e.g. use "0:1000:-200:800" or "?"') + expert_group.add_argument('--configfile', dest='configfile', default=False, type=str, help='Specify a file path for configuration file (see sample: src/config.sample.cfg)') + expert_group.add_argument('--custom', dest='custom', default=False, type=str, help='Specify a custom symbol font, all glyphs will be copied; absolute path suggested') + expert_group.add_argument('--dry', dest='dry_run', default=False, action='store_true', help='Do neither patch nor store the font, to check naming') + expert_group.add_argument('--glyphdir', dest='glyphdir', default=__dir__ + "/src/glyphs/", type=str, help='Path to glyphs to be used for patching') + expert_group.add_argument('--has-no-italic', dest='noitalic', default=False, action='store_true', help='Font family does not have Italic (but Oblique), to help create correct RIBBI set') + expert_group.add_argument('--metrics', dest='metrics', default=None, choices=get_metrics_names(), help='Select vertical metrics source (for problematic cases)') + expert_group.add_argument('--name', dest='force_name', default=None, type=str, help='Specify naming source (\'full\', \'postscript\', \'filename\', or concrete free name-string)') + expert_group.add_argument('--postprocess', dest='postprocess', default=False, type=str, help='Specify a Script for Post Processing') + progressbars_group_parser = expert_group.add_mutually_exclusive_group(required=False) + expert_group.add_argument('--removeligs', '--removeligatures', dest='removeligatures', default=False, action='store_true', help='Removes ligatures specified in configuration file (needs --configfile)') + expert_group.add_argument('--xavgcharwidth', dest='xavgwidth', default=None, type=int, nargs='?', help='Adjust xAvgCharWidth (optional: concrete value)', const=True) + # --xavgcharwidth for compatibility with old applications like notepad and non-latin fonts + # Possible values with examples: + # - copy from sourcefont (default) + # 0 - calculate from font according to OS/2-version-2 + # 500 - set to 500 + + # progress bar arguments - https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse + progressbars_group_parser.add_argument('--progressbars', dest='progressbars', action='store_true', help='Show percentage completion progress bars per Glyph Set (default)') + progressbars_group_parser.add_argument('--no-progressbars', dest='progressbars', action='store_false', help='Don\'t show percentage completion progress bars per Glyph Set') + expert_group.set_defaults(progressbars=True) + + args = parser.parse_args() + setup_global_logger(args) + + # if we have a config file: fetch commandline arguments from there and process again with all arguments + config = configparser.ConfigParser(empty_lines_in_values=False, allow_no_value=True) + if args.configfile: + if not os.path.isfile(args.configfile): + logger.critical("Configfile does not exist: %s", args.configfile) + sys.exit(1) + if not os.access(args.configfile, os.R_OK): + logger.critical("Can not open configfile for reading: %s", args.configfile) + sys.exit(1) + config.read(args.configfile) + extraflags = config.get("Config", "commandline", fallback='') + if len(extraflags): + logger.info("Adding config commandline options: %s", extraflags) + extraflags += ' ' + args.font # Need to re-add the mandatory argument + args = parser.parse_args(extraflags.split(), args) + + if args.makegroups > 0 and not FontnameParserOK: + logger.critical("FontnameParser module missing (bin/scripts/name_parser/Fontname*), specify --makegroups 0") + sys.exit(1) + + # if you add a new font, set it to True here inside the if condition + if args.complete: + args.fontawesome = True + args.fontawesomeextension = True + args.fontlogos = True + args.octicons = True + args.codicons = True + args.powersymbols = True + args.pomicons = True + args.powerline = True + args.powerlineextra = True + args.material = True + args.weather = True + + if not args.complete: + sym_font_args = [] + # add the list of arguments for each symbol font to the list sym_font_args + for action in sym_font_group._group_actions: + sym_font_args.append(action.__dict__['option_strings']) + + # determine whether or not all symbol fonts are to be used + font_complete = True + for sym_font_arg_aliases in sym_font_args: + found = False + for alias in sym_font_arg_aliases: + if alias in sys.argv: + found = True + if not found: + font_complete = False + args.complete = font_complete + + if args.forcemono: + args.single = True + if args.nonmono and args.single: + logger.warning("Specified contradicting --variable-width-glyphs together with --mono or --single-width-glyphs. Ignoring --variable-width-glyphs.") + args.nonmono = False + + if args.cellopt: + if args.cellopt != '?': + try: + parts = [ int(v) for v in args.cellopt.split(':') ] + if len(parts) != 4: + raise + except: + logger.critical("Parameter for --cell is not 4 colon separated integer numbers: '%s'", args.cellopt) + sys.exit(2) + if parts[0] >= parts[1] or parts[2] >= parts[3]: + logger.critical("Parameter for --cell do not result in positive cell size: %d x %d", + parts[1] - parts[0], parts[3] - parts[2]) + sys.exit(2) + if parts[0] != 0: + logger.warn("First parameter for --cell should be zero, this is probably not working") + args.cellopt = parts + + make_sure_path_exists(args.outputdir) + if not os.path.isfile(args.font): + logger.critical("Font file does not exist: %s", args.font) + sys.exit(1) + if not os.access(args.font, os.R_OK): + logger.critical("Can not open font file for reading: %s", args.font) + sys.exit(1) + is_ttc = len(fontforge.fontsInFile(args.font)) > 1 + try: + source_font_test = TableHEADWriter(args.font) + args.is_variable = source_font_test.find_table([b'avar', b'cvar', b'fvar', b'gvarb', b'HVAR', b'MVAR', b'VVAR'], 0) + if args.is_variable: + logger.warning("Source font is a variable open type font (VF), opening might fail...") + except: + args.is_variable = False + finally: + try: + source_font_test.close() + except: + pass + + if args.extension == "": + args.extension = os.path.splitext(args.font)[1] + else: + args.extension = '.' + args.extension + if re.match(r'\.ttc$', args.extension, re.IGNORECASE): + if not is_ttc: + logger.critical("Can not create True Type Collections from single font files") + sys.exit(1) + else: + if is_ttc: + logger.critical("Can not create single font files from True Type Collections") + sys.exit(1) + + # The if might look ridiculous, but isinstance(False, int) is True! + if isinstance(args.xavgwidth, int) and not isinstance(args.xavgwidth, bool): + if args.xavgwidth < 0: + logger.critical("--xavgcharwidth takes no negative numbers") + sys.exit(2) + if args.xavgwidth > 16384: + logger.critical("--xavgcharwidth takes only numbers up to 16384") + sys.exit(2) + + return (args, config) + +def setup_global_logger(args): + """ Set up the logger and take options into account """ + global logger + logger = logging.getLogger(os.path.basename(args.font)) + logger.setLevel(logging.DEBUG) + log_to_file = (args.debugmode & 1 == 1) + if log_to_file: + try: + f_handler = logging.FileHandler('font-patcher-log.txt') + f_handler.setFormatter(logging.Formatter('%(levelname)s: %(name)s %(message)s')) + logger.addHandler(f_handler) + except: + log_to_file = False + logger.debug(allversions) + logger.debug("Options %s", repr(sys.argv[1:])) + c_handler = logging.StreamHandler(stream=sys.stdout) + c_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) + if not (args.debugmode & 2 == 2): + c_handler.setLevel(logging.INFO) + logger.addHandler(c_handler) + if (args.debugmode & 1 == 1) and not log_to_file: + logger.info("Can not write logfile, disabling") + +def main(): + global logger + logger = logging.getLogger("start") # Use start logger until we can set up something sane + s_handler = logging.StreamHandler(stream=sys.stdout) + s_handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) + logger.addHandler(s_handler) + + global version + git_version = check_version_with_git(version) + global allversions + allversions = "Patcher v{} ({}) (ff {})".format( + git_version if git_version else version, script_version, fontforge.version()) + print("{} {}".format(projectName, allversions)) + if git_version: + version = git_version + check_fontforge_min_version() + (args, conf) = setup_arguments() + logger.debug("Naming mode %d", args.makegroups) + + patcher = font_patcher(args, conf) + + sourceFonts = [] + all_fonts = fontforge.fontsInFile(args.font) + if not all_fonts: + if re.match(".*\\.woff2?", args.font, re.I): + all_fonts=[ "" ] + else: + logger.critical("Can not find any fonts in '%s'", args.font) + sys.exit(1) + for i, subfont in enumerate(all_fonts): + if len(all_fonts) > 1: + print("\n") + logger.info("Processing %s (%d/%d)", subfont, i + 1, len(all_fonts)) + try: + sourceFonts.append(fontforge.open("{}({})".format(args.font, i), 1)) # 1 = ("fstypepermitted",)) + except Exception: + logger.critical("Can not open font '%s', try to open with fontforge interactively to get more information", + subfont) + sys.exit(1) + + patcher.setup_name_backup(sourceFonts[-1]) + patcher.patch(sourceFonts[-1]) + + print("Done with Patch Sets, generating font...") + for f in sourceFonts: + patcher.setup_font_names(f) + patcher.generate(sourceFonts) + + for f in sourceFonts: + f.close() + + +if __name__ == "__main__": + __dir__ = os.path.dirname(os.path.abspath(__file__)) + main()