diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index af31490dd..7c9063c86 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -3,5 +3,9 @@ self-hosted-runner: labels: - blacksmith-8vcpu-ubuntu-2404-arm - blacksmith-8vcpu-windows-2025 - - blacksmith-6vcpu-macos-26 + - blacksmith-6vcpu-macos-27 + # GitHub-hosted Xcode 27 image; newer than the pinned actionlint label list. + - xcode-27 - blacksmith-8vcpu-ubuntu-2404 + - blacksmith-16vcpu-ubuntu-2404 + - blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/agent-ci.yml b/.github/workflows/agent-ci.yml index 753036e1f..1592f5453 100644 --- a/.github/workflows/agent-ci.yml +++ b/.github/workflows/agent-ci.yml @@ -83,7 +83,7 @@ jobs: strategy: fail-fast: false matrix: - os: [blacksmith-8vcpu-ubuntu-2404, macos-26, windows-latest] + os: [blacksmith-8vcpu-ubuntu-2404, xcode-27, windows-latest] steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: @@ -101,6 +101,11 @@ jobs: working-directory: . run: bash scripts/ci/verify-nix-runtime.sh + - name: Select pinned Xcode + if: runner.os == 'macOS' + working-directory: . + run: python3 scripts/ci/select-xcode.py + - name: Install pinned Windows Rust if: runner.os == 'Windows' uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable action, exact toolchain below @@ -264,7 +269,7 @@ jobs: strategy: fail-fast: false matrix: - os: [blacksmith-8vcpu-ubuntu-2404, macos-26, windows-latest] + os: [blacksmith-8vcpu-ubuntu-2404, xcode-27, windows-latest] steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: @@ -282,6 +287,11 @@ jobs: working-directory: . run: bash scripts/ci/verify-nix-runtime.sh + - name: Select pinned Xcode + if: runner.os == 'macOS' + working-directory: . + run: python3 scripts/ci/select-xcode.py + - name: Install pinned Windows Rust if: runner.os == 'Windows' uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable action, exact toolchain below diff --git a/.github/workflows/agent-desktop-build.yml b/.github/workflows/agent-desktop-build.yml index dc5300974..830f734c9 100644 --- a/.github/workflows/agent-desktop-build.yml +++ b/.github/workflows/agent-desktop-build.yml @@ -80,7 +80,10 @@ jobs: name: Build macOS binary (${{ matrix.variant }}) needs: changes if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.agent != 'false') && (github.event_name == 'pull_request' || (github.repository == 'MaplePrivacyLabs/Maple' && github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch'))) }} - runs-on: blacksmith-6vcpu-macos-26 + # gpui compiles Metal shaders, and Blacksmith's macOS 27 image has no Metal + # Toolchain for Xcode 27.1 and cannot download it (the MobileAsset catalog + # fetch fails). GitHub's xcode-27 image ships it, so build there meanwhile. + runs-on: xcode-27 timeout-minutes: 120 strategy: fail-fast: false @@ -172,7 +175,7 @@ jobs: contents: read id-token: write attestations: write - runs-on: macos-26 + runs-on: xcode-27 timeout-minutes: 120 strategy: fail-fast: false @@ -235,7 +238,7 @@ jobs: name: Unsigned macOS packaging (${{ matrix.variant }}) needs: [changes, macos-build] if: ${{ always() && !cancelled() && needs.macos-build.result == 'success' && github.event_name == 'pull_request' && (needs.changes.result != 'success' || needs.changes.outputs.agent != 'false') }} - runs-on: macos-26 + runs-on: xcode-27 timeout-minutes: 120 strategy: fail-fast: false @@ -428,7 +431,7 @@ jobs: permissions: contents: read attestations: read - runs-on: macos-26 + runs-on: xcode-27 timeout-minutes: 30 strategy: fail-fast: false diff --git a/.github/workflows/agent-store-performance.yml b/.github/workflows/agent-store-performance.yml index 20658021c..ba2291395 100644 --- a/.github/workflows/agent-store-performance.yml +++ b/.github/workflows/agent-store-performance.yml @@ -41,7 +41,7 @@ jobs: strategy: fail-fast: false matrix: - include: ${{ fromJSON((github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '[{"os":"blacksmith-8vcpu-ubuntu-2404","profile":"linux-ci","scale":"--ci"},{"os":"macos-26","profile":"macos-full","scale":"--full"}]' || '[{"os":"blacksmith-8vcpu-ubuntu-2404","profile":"linux-ci","scale":"--ci"}]') }} + include: ${{ fromJSON((github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && '[{"os":"blacksmith-8vcpu-ubuntu-2404","profile":"linux-ci","scale":"--ci"},{"os":"xcode-27","profile":"macos-full","scale":"--full"}]' || '[{"os":"blacksmith-8vcpu-ubuntu-2404","profile":"linux-ci","scale":"--ci"}]') }} if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'push' runs-on: ${{ matrix.os }} timeout-minutes: 240 @@ -60,6 +60,11 @@ jobs: working-directory: . run: bash scripts/ci/verify-nix-runtime.sh + - name: Select pinned Xcode + if: runner.os == 'macOS' + working-directory: . + run: python3 scripts/ci/select-xcode.py + - name: Cache benchmark dependencies uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 223dfbc88..ff65aa0e8 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -18,7 +18,7 @@ jobs: environment: desktop-signing needs: changes if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.frontend != 'false' || needs.changes.outputs.macos != 'false') }} - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 with: @@ -204,7 +204,7 @@ jobs: verify-macos-desktop-artifacts: needs: - build-macos - runs-on: macos-26 + runs-on: xcode-27 permissions: contents: read steps: diff --git a/.github/workflows/desktop-pr-build.yml b/.github/workflows/desktop-pr-build.yml index fb65222b4..1abd21f64 100644 --- a/.github/workflows/desktop-pr-build.yml +++ b/.github/workflows/desktop-pr-build.yml @@ -24,7 +24,7 @@ jobs: name: build-macos${{ github.event.action == 'labeled' && format(' (label {0})', github.event.label.name) || '' }} needs: changes if: ${{ always() && !cancelled() && github.event.action != 'labeled' && (needs.changes.result != 'success' || needs.changes.outputs.macos != 'false') }} - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 with: @@ -84,7 +84,7 @@ jobs: concurrency: group: research-dev-macos-pr-${{ github.event.pull_request.number }} cancel-in-progress: true - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 with: diff --git a/.github/workflows/ios-dev-testflight.yml b/.github/workflows/ios-dev-testflight.yml index 56432b4d8..5381fa2c4 100644 --- a/.github/workflows/ios-dev-testflight.yml +++ b/.github/workflows/ios-dev-testflight.yml @@ -22,7 +22,7 @@ jobs: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') environment: apple-signing - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 timeout-minutes: 240 permissions: contents: read @@ -130,7 +130,7 @@ jobs: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') needs: build-ios-dev environment: apple-signing - runs-on: macos-26 + runs-on: xcode-27 timeout-minutes: 45 permissions: contents: read diff --git a/.github/workflows/linux-runner-size-probe.yml b/.github/workflows/linux-runner-size-probe.yml new file mode 100644 index 000000000..411f21e59 --- /dev/null +++ b/.github/workflows/linux-runner-size-probe.yml @@ -0,0 +1,61 @@ +# Experiment only, never merge. Does blacksmith-16vcpu-ubuntu-2404 (and 32) +# provision without a pre-created scale set, and how much faster is a cold Agent +# clippy + test build on 16/32 vCPU than on 8? +name: Linux runner size probe + +on: + pull_request: + paths: + - .github/workflows/linux-runner-size-probe.yml + +permissions: + contents: read + +concurrency: + group: linux-runner-size-probe-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + probe: + name: Agent cold build on ${{ matrix.runner }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + runner: [blacksmith-8vcpu-ubuntu-2404, blacksmith-16vcpu-ubuntu-2404, blacksmith-32vcpu-ubuntu-2404] + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Machine + run: | + echo "::notice::$(nproc) CPUs, $(free -g | awk '/Mem:/{print $2}') GB RAM on ${{ matrix.runner }}" + + - name: Install pinned Nix environment + uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3 + with: + source-tag: v3.23.3 + github-token: "" + + - name: Verify pinned Nix runtime + run: bash scripts/ci/verify-nix-runtime.sh + + - name: Cold Agent clippy and test build + working-directory: apps/maple-agent + env: + RUSTFLAGS: -D warnings + CARGO_INCREMENTAL: "0" + run: | + set -euo pipefail + start=$SECONDS + nix develop --no-update-lock-file . -c true + shell=$((SECONDS - start)) + start=$SECONDS + nix develop --no-update-lock-file . -c cargo clippy --workspace --all-targets --locked -- -D warnings + clippy=$((SECONDS - start)) + start=$SECONDS + nix develop --no-update-lock-file . -c cargo test --workspace --locked --no-run + tests=$((SECONDS - start)) + echo "::notice::${{ matrix.runner }}: shell ${shell}s, cold clippy ${clippy}s, cold test build ${tests}s" diff --git a/.github/workflows/macos27-blacksmith-experiment.yml b/.github/workflows/macos27-blacksmith-experiment.yml new file mode 100644 index 000000000..75a6cc7d4 --- /dev/null +++ b/.github/workflows/macos27-blacksmith-experiment.yml @@ -0,0 +1,179 @@ +# Experiment only, never merge. Can Agent macOS work stay on Blacksmith's macOS 27 +# image with Xcode 27.1? Two arms: +# 1. Export Xcode 27.1's Metal Toolchain on GitHub's xcode-27 image, import it on +# Blacksmith (whose own catalog download fails), and build the Agent with +# compiled Metal shaders. +# 2. Without any Metal Toolchain, run the Agent clippy with gpui's +# runtime_shaders feature (shaders compile at run time, not build time). +name: macOS 27 Blacksmith experiment + +on: + pull_request: + paths: + - .github/workflows/macos27-blacksmith-experiment.yml + +permissions: + contents: read + +concurrency: + group: macos27-blacksmith-experiment-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + export-metal: + name: Export Metal Toolchain (GitHub xcode-27) + runs-on: xcode-27 + timeout-minutes: 30 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Select pinned Xcode + run: python3 scripts/ci/select-xcode.py + + - name: Download and export the Metal Toolchain + run: | + set -euxo pipefail + xcodebuild -version + xcodebuild -showComponent metalToolchain || true + export_dir="$RUNNER_TEMP/metal-export" + mkdir -p "$export_dir" + start=$SECONDS + xcodebuild -downloadComponent metalToolchain -exportPath "$export_dir" + echo "::notice::Metal Toolchain export took $((SECONDS - start))s" + find "$export_dir" -maxdepth 2 -print + du -sh "$export_dir" + find "$export_dir" -name ExportMetadata.plist -exec plutil -p {} \; || true + # Keep symlinks and modes intact; artifacts flatten them. + tar -C "$export_dir" -cf "$RUNNER_TEMP/metal-export.tar" . + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: metal-toolchain-xcode-27.1 + path: ${{ runner.temp }}/metal-export.tar + retention-days: 1 + compression-level: 0 + + blacksmith-imported-metal: + name: Blacksmith macos-27, imported Metal, Agent build + needs: export-metal + runs-on: blacksmith-6vcpu-macos-27 + timeout-minutes: 90 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Select pinned Xcode + run: python3 scripts/ci/select-xcode.py + + - name: Metal before import + run: | + xcodebuild -version + xcodebuild -showComponent metalToolchain || true + if xcrun -sdk macosx metal --version; then + echo "::notice::Metal Toolchain is already present on this Blacksmith image" + else + echo "::notice::Metal Toolchain missing before import, as expected" + fi + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: metal-toolchain-xcode-27.1 + path: ${{ runner.temp }}/metal + + - name: Import the Metal Toolchain + run: | + set -euxo pipefail + export_dir="$RUNNER_TEMP/metal-export" + mkdir -p "$export_dir" + tar -C "$export_dir" -xf "$RUNNER_TEMP/metal/metal-export.tar" + ls -la "$export_dir" + bundle="$(find "$export_dir" -maxdepth 1 \( -name '*.exportedBundle' -o -name '*.dmg' \) -print -quit)" + test -n "$bundle" + start=$SECONDS + if ! xcodebuild -importComponent metalToolchain -importPath "$bundle"; then + echo "::warning::unprivileged import failed; retrying with sudo" + sudo xcodebuild -importComponent metalToolchain -importPath "$bundle" + fi + echo "::notice::Metal Toolchain import took $((SECONDS - start))s" + xcodebuild -showComponent metalToolchain || true + + - name: Compile a Metal shader + run: | + set -euxo pipefail + cat > "$RUNNER_TEMP/probe.metal" <<'EOF' + #include + using namespace metal; + kernel void probe(device float *out [[buffer(0)]]) { out[0] = 1.0; } + EOF + xcrun -sdk macosx metal --version + xcrun -sdk macosx metal -c "$RUNNER_TEMP/probe.metal" -o "$RUNNER_TEMP/probe.air" + xcrun -sdk macosx metallib "$RUNNER_TEMP/probe.air" -o "$RUNNER_TEMP/probe.metallib" + echo "::notice::Metal shader compiled on Blacksmith macos-27" + + - name: Install pinned Nix environment + uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3 + with: + source-tag: v3.23.3 + github-token: "" + + - name: Verify pinned Nix runtime + run: bash scripts/ci/verify-nix-runtime.sh + + - name: Build the Agent workspace (cold, compiled Metal shaders) + working-directory: apps/maple-agent + run: | + set -euo pipefail + start=$SECONDS + nix develop --no-update-lock-file . -c true + shell=$((SECONDS - start)) + start=$SECONDS + nix develop --no-update-lock-file . -c cargo build --workspace --all-targets --locked + build=$((SECONDS - start)) + echo "::notice::Agent dev shell ${shell}s, cold cargo build --workspace --all-targets ${build}s" + printf '| Step | Seconds |\n| --- | ---: |\n| Agent dev shell | %s |\n| Cold workspace build (compiled shaders) | %s |\n' "$shell" "$build" >> "$GITHUB_STEP_SUMMARY" + + blacksmith-runtime-shaders: + name: Blacksmith macos-27, no Metal, runtime_shaders clippy + runs-on: blacksmith-6vcpu-macos-27 + timeout-minutes: 90 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Select pinned Xcode + run: python3 scripts/ci/select-xcode.py + + - name: Confirm no Metal Toolchain + run: | + if xcrun -sdk macosx metal --version; then + echo "::warning::Metal Toolchain present; this arm no longer proves the no-Metal path" + else + echo "::notice::No Metal Toolchain on this runner" + fi + + - name: Install pinned Nix environment + uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3 + with: + source-tag: v3.23.3 + github-token: "" + + - name: Verify pinned Nix runtime + run: bash scripts/ci/verify-nix-runtime.sh + + - name: Clippy the Agent app with runtime shaders (cold) + working-directory: apps/maple-agent + run: | + set -euo pipefail + start=$SECONDS + nix develop --no-update-lock-file . -c true + shell=$((SECONDS - start)) + start=$SECONDS + nix develop --no-update-lock-file . -c cargo clippy -p maple-agent-app --all-targets --locked \ + -F gpui-platform/runtime_shaders -- -D warnings + clippy=$((SECONDS - start)) + echo "::notice::Agent dev shell ${shell}s, cold clippy (runtime_shaders) ${clippy}s" + printf '| Step | Seconds |\n| --- | ---: |\n| Agent dev shell | %s |\n| Cold app clippy, runtime_shaders | %s |\n' "$shell" "$clippy" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/mobile-build.yml b/.github/workflows/mobile-build.yml index de914c085..fef19d248 100644 --- a/.github/workflows/mobile-build.yml +++ b/.github/workflows/mobile-build.yml @@ -34,7 +34,7 @@ jobs: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (needs.changes.result != 'success' || needs.changes.outputs.frontend != 'false' || needs.changes.outputs.ios != 'false') - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 permissions: contents: read id-token: write @@ -118,7 +118,7 @@ jobs: verify-ios-artifacts: needs: build-ios - runs-on: macos-26 + runs-on: xcode-27 permissions: contents: read steps: @@ -153,7 +153,7 @@ jobs: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') needs: verify-ios-artifacts - runs-on: macos-26 + runs-on: xcode-27 permissions: contents: read steps: diff --git a/.github/workflows/mobile-pr-build.yml b/.github/workflows/mobile-pr-build.yml index d76b0c2f6..27767260b 100644 --- a/.github/workflows/mobile-pr-build.yml +++ b/.github/workflows/mobile-pr-build.yml @@ -18,7 +18,7 @@ jobs: build-ios: needs: changes if: ${{ always() && !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.ios != 'false') }} - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 with: diff --git a/.github/workflows/proxy-rust.yml b/.github/workflows/proxy-rust.yml index e55b6dfc5..80de5e47e 100644 --- a/.github/workflows/proxy-rust.yml +++ b/.github/workflows/proxy-rust.yml @@ -95,7 +95,7 @@ jobs: archive: maple-proxy-linux-x86_64.tar.gz - runner: ubuntu-24.04-arm archive: maple-proxy-linux-aarch64.tar.gz - - runner: blacksmith-6vcpu-macos-26 + - runner: blacksmith-6vcpu-macos-27 archive: maple-proxy-macos-aarch64.tar.gz - runner: windows-2025 archive: maple-proxy-windows-x86_64.zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 812c8b2a6..5884fe303 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -137,7 +137,7 @@ jobs: fail-fast: false matrix: include: - - platform: blacksmith-6vcpu-macos-26 + - platform: blacksmith-6vcpu-macos-27 nix_shell: ci artifact_prefix: macos - platform: blacksmith-8vcpu-ubuntu-2404 @@ -484,7 +484,7 @@ jobs: build-ios: environment: apple-signing needs: classify-app-release - runs-on: blacksmith-6vcpu-macos-26 + runs-on: blacksmith-6vcpu-macos-27 permissions: contents: write id-token: write @@ -651,7 +651,7 @@ jobs: archive: maple-proxy-linux-x86_64.tar.gz - runner: ubuntu-24.04-arm archive: maple-proxy-linux-aarch64.tar.gz - - runner: blacksmith-6vcpu-macos-26 + - runner: blacksmith-6vcpu-macos-27 archive: maple-proxy-macos-aarch64.tar.gz - runner: windows-2025 archive: maple-proxy-windows-x86_64.zip @@ -848,7 +848,7 @@ jobs: needs: - classify-app-release - build-tauri - runs-on: macos-26 + runs-on: xcode-27 permissions: contents: read steps: @@ -996,7 +996,7 @@ jobs: - update-latest-json - verify-android-release-artifacts - verify-proxy-release-artifacts - runs-on: macos-26 + runs-on: xcode-27 permissions: contents: read steps: diff --git a/apps/maple-agent/Cargo.toml b/apps/maple-agent/Cargo.toml index 17e22bd43..81f1a1476 100644 --- a/apps/maple-agent/Cargo.toml +++ b/apps/maple-agent/Cargo.toml @@ -35,6 +35,14 @@ lto = "fat" codegen-units = 1 strip = true +# rustc strips with rust-objcopy, which can leave a dylib's LINKEDIT string table +# 4-byte aligned. macOS 27 refuses to load such dylibs when they are linked against +# its SDK, and the build loads its proc-macros as dylibs. Build scripts and +# proc-macros never ship, so leave them unstripped. Remove once the pinned Rust's +# rust-objcopy includes llvm/llvm-project#203680. +[profile.release.build-override] +strip = false + # The embedded proxy requires `maple-sdk` from the registry; resolve it to the same # in-tree source as the workspace dependency above (docs/sdk-publishing.md). [patch.crates-io] diff --git a/apps/maple-agent/crates/store-bench/README.md b/apps/maple-agent/crates/store-bench/README.md index 9a045e579..c9daf8786 100644 --- a/apps/maple-agent/crates/store-bench/README.md +++ b/apps/maple-agent/crates/store-bench/README.md @@ -61,7 +61,7 @@ dataset identity and settings. No synthetic database or key is uploaded. scenarios and their sample counts, effective durability settings, and indexed plans with and without statistics. Missing scenarios cannot silently remove a slow operation. Linux CI compares medians at 10x its actual Linux reference; -the scheduled `macos-26` full run uses 2x its actual macOS reference. Platform +the scheduled `xcode-27` full run uses 2x its actual macOS reference. Platform mismatches are rejected. Native Mac small runs can use `--profile linux-ci --diagnostic` to validate workload evidence without comparing or recording a Linux runner gate. diff --git a/apps/maple-agent/flake.nix b/apps/maple-agent/flake.nix index aa921e936..772e8d836 100644 --- a/apps/maple-agent/flake.nix +++ b/apps/maple-agent/flake.nix @@ -329,6 +329,11 @@ # nixpkgs' generic clang wrapper cannot infer that SDK boundary. export CC="$(/usr/bin/xcrun --find clang)" export CXX="$(/usr/bin/xcrun --find clang++)" + # Rust links through `cc`, which is nixpkgs' clang wrapper and its + # older ld64. That ld64 cannot read the macOS 27 SDK's .tbd stubs + # (they list the arm64e.x1 architecture), so link with Xcode too. + export CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER="$CC" + export CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER="$CC" export PATH="${xcrun}/bin:$PATH" unset maple_nix_developer_dir maple_nix_selected_developer_dir diff --git a/apps/maple-research/frontend/bun.lock b/apps/maple-research/frontend/bun.lock index 5dd9297b2..f99757ebd 100644 --- a/apps/maple-research/frontend/bun.lock +++ b/apps/maple-research/frontend/bun.lock @@ -21,11 +21,11 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.100.1", "@tanstack/react-router": "^1.170.18", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "@tauri-apps/plugin-deep-link": "2.4.8", - "@tauri-apps/plugin-dialog": "^2.7.1", - "@tauri-apps/plugin-opener": "^2.5.4", - "@tauri-apps/plugin-os": "^2.3.2", + "@tauri-apps/plugin-dialog": "^2.8.3", + "@tauri-apps/plugin-opener": "^2.7.2", + "@tauri-apps/plugin-os": "^2.4.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "katex": "^0.18.2", @@ -49,7 +49,7 @@ "@eslint/js": "^9.39.4", "@noble/hashes": "^1.8.0", "@tanstack/router-plugin": "^1.168.23", - "@tauri-apps/cli": "2.11.1", + "@tauri-apps/cli": "2.12.3", "@types/bun": "1.3.13", "@types/node": "^22.19.17", "@types/react": "^18.3.28", @@ -76,7 +76,7 @@ "overrides": { "@babel/core": "^7.29.7", "@humanfs/node": "^0.16.8", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "browserslist": "^4.28.8", "flatted": "^3.4.2", "glob": "^10.5.0", @@ -447,39 +447,39 @@ "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="], - "@tauri-apps/api": ["@tauri-apps/api@2.11.0", "", {}, "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA=="], + "@tauri-apps/api": ["@tauri-apps/api@2.12.4", "", {}, "sha512-pVmfis+4zT8H0Qhp3xPZx9Ju7kpzvA6rr/PSJPyuwfvucTQ8sVt61NYU9KMkK703UFlYadFmjKA4HhybOtN0gg=="], - "@tauri-apps/cli": ["@tauri-apps/cli@2.11.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.11.1", "@tauri-apps/cli-darwin-x64": "2.11.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.1", "@tauri-apps/cli-linux-arm64-gnu": "2.11.1", "@tauri-apps/cli-linux-arm64-musl": "2.11.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.11.1", "@tauri-apps/cli-linux-x64-gnu": "2.11.1", "@tauri-apps/cli-linux-x64-musl": "2.11.1", "@tauri-apps/cli-win32-arm64-msvc": "2.11.1", "@tauri-apps/cli-win32-ia32-msvc": "2.11.1", "@tauri-apps/cli-win32-x64-msvc": "2.11.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-rpEbaJ/HzNb6fwsquwoAbq29/Vt4gADhS423A8fdkwL4edJ0wZmoB8ar7O6JPDL834MUKOCm/rrJ7c9oAaEaYQ=="], + "@tauri-apps/cli": ["@tauri-apps/cli@2.12.3", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.12.3", "@tauri-apps/cli-darwin-x64": "2.12.3", "@tauri-apps/cli-linux-arm-gnueabihf": "2.12.3", "@tauri-apps/cli-linux-arm64-gnu": "2.12.3", "@tauri-apps/cli-linux-arm64-musl": "2.12.3", "@tauri-apps/cli-linux-riscv64-gnu": "2.12.3", "@tauri-apps/cli-linux-x64-gnu": "2.12.3", "@tauri-apps/cli-linux-x64-musl": "2.12.3", "@tauri-apps/cli-win32-arm64-msvc": "2.12.3", "@tauri-apps/cli-win32-ia32-msvc": "2.12.3", "@tauri-apps/cli-win32-x64-msvc": "2.12.3" }, "bin": { "tauri": "tauri.js" } }, "sha512-f7/LsY74SVb1ChmOPw1ajOpxii0GSzDAgmWHRci8JUYuuai04rXsSbFobpmB0sP2DdlvMD/B5ZotEn8GOGMRUg=="], - "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6eEKMBXsQPCuM1EmvrjT2+aBuxWQuFdKdW8pzNuNQtpq45nEEpBlD5gr8pUeAyOU1DQKlkFaEc/MPBxb/Pfjtg=="], + "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.12.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xmVNoJOOKzKLj7GLHXmXLimRby9M42aBIJa0sB80N7o24eKV5JiVmToKTRLVH9yhL05GZ/B6rSSOmbzNFJwBAA=="], - "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-LQUO7exfRWjWALNhetph5guWpMeHphRpokOLk0OIbTTExaNwJNFu3I4vb+CCM/4G/QGoZe/5XikZOJdNEFP1ig=="], + "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.12.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-URd+0usfmUQQyb5kNPiKt2OT8gp7oGU7QR+qOppB6e35mpVBFFVNhWc9oWb3p0u71fDJ09DpF4T6m9xYxoIB8g=="], - "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-5i/awiBCRRhOUG8yjn0fMHXIWD5Ez8eEk5LtvOxyQrKuJkRaZDvnbIjZbE183blAwkoA4xN3aO/prJiqscl02Q=="], + "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.12.3", "", { "os": "linux", "cpu": "arm" }, "sha512-0kwUVx2LbmOoTRlwF1oDww7th4/NSluv4ZBQ6KRZsFKg4g0TlTcIiJ0VehqkQYUeC9xIT1Mk6XtF52mI155VSQ=="], - "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-9LrwDw3S9Fygtw/Q6WDhOP+3svJRGAsejeE+GKrc0eO1ThMVhwi2LL6hw4dlKw93IfS7VY1G19sWGxJ/NcU4nA=="], + "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.12.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-gKSGDx6fx+HBLQmG6guChFdisbrCSdx9Yli+j5teOa64gm1CeM+C6NLru5Pj3ZkkEep05BmCFjxvX+cjqAycZQ=="], - "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mNA5dbbqPqDUdTIwdUYYuhO2GvIe9UnB2r0VU2njxBOS3Opbx4gKNC5yP0Iu4rYmEmqdlwry9VzGZQ3wq9dyFg=="], + "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.12.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Yr59qMy/gcvAfBuFdCGlAmGiLsYWMFDbsP4bAI6iNBzcJTGrYWZGeiQhGDo29MQwfF/NUBqyM2qljXu5tZ6wjQ=="], - "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-fZj3Gwq+6fUs305T5WQiD5iSGJw+j/4w/HGmk4sHDAcy+rp9zU5eaxB7nOyz5/I/nkNAuKPqfp6uIbiUBXkBCw=="], + "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.12.3", "", { "os": "linux", "cpu": "none" }, "sha512-jKr0/REBMnLKC0OwPQZkyge0mKDaNZ0b/AbfGwVyCgFfwS730EVLfVDo3nt7MUu6jJaytRq08+xSj5qfy+U1+Q=="], - "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-XFxGxOvHM7jjeD6ozCKdGfhzJ7lERYDGZl1/Kb4fsvchaJsfLJ981TlyTG8Qy/gFq+f5GitH3bfrX9JAkjPEyw=="], + "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.12.3", "", { "os": "linux", "cpu": "x64" }, "sha512-gTkEqJAGCMROGanxHnzaJzQ+/rXl39HWVg0/hDODS1wDWBGqoceqGC6Qm1+mQbyT11xnsB8mKu/gyVID/YpFIw=="], - "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-d5C2/Zm+68v7R9wTuTCjRQEVrWjcdMkJBZ1+rXse+QdMMlTB9+u9PDNDLw9PQflWxYLaYZ7tjxxL9Nb9II6PbA=="], + "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.12.3", "", { "os": "linux", "cpu": "x64" }, "sha512-T2dTxL1svRym71fZtaW4pZYXbthbfFsEJr1NeOUg/p7laWXZbUt/8DMSZf8recu9f7+1F/Lv00HtzsEu6vDO9w=="], - "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YdeVWFAR1pTXzUU6NLstPq4G6OLxuDrXCXEBdmBH+5EZIDXUx0D2kJlz3+YjpazkKvAzYpgziTsyRagls0OfRQ=="], + "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.12.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-eoiQwpEZWbpsVHcM9BAzqqe4TCHszvr3rsM7pUQ4ofLK0lF1i1d/jX0Qo5DYMqcrUtwQuDdDuOCbcT0Izf1VwQ=="], - "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.11.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-VBGkuH0eB9K9LLSMv361Gzr5Ou72sCS4+ztpmkWEQ+wd/amhcYOsf3X6qn1RJZDzIhiOYHJEOysZUC3baD01rA=="], + "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.12.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-kE+KUg6Dbbkgvcw0m99wJVIToFQzJqpMBAXah74WqxaLzssNwSTUccrHp6+QVzMhjQP330tvekFmnQF+0NbE6Q=="], - "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-b3ORhIAKgp9ZYY+zBt7b7r0kLU2kjvyGF0+MS2SBym3emsweGPybEqocJcmtMuxyBhkOKHP4CiuEJEDuAlTx6A=="], + "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.12.3", "", { "os": "win32", "cpu": "x64" }, "sha512-L7wndWAgX2mLW7alswpL3d1R0P9FN2WYBgHaQkAYEBxHHGkY4vFX9CjcRQ1LAxDAMIJxrY7/aDziFJeChS84NQ=="], "@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.4.8", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-Cd2Cs960MGuGONeIwxOPx9wqwedetAHOGlwK5boJ/SMTfAtAyfErpfVPEn+EJzgXsJun8EKzsEumHjr+64V4fw=="], - "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.7.1", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-OK1UBXYt+ojcmxMktzzuyonYIFta8CmAASpX+CA+DTGK24KlHjhYI6x2iOJ/TjZF4N7/ACK1oFmEOjIY9IhzOQ=="], + "@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.8.3", "", { "dependencies": { "@tauri-apps/api": "^2.12.0" } }, "sha512-Hd/yBfk2R5v2MFrz/0VArSXeh8xKLLXGunqSiTtcHKn1zNZwaKujuA0VSwUvNfwC4hDoF1PveS+gBDNe3nHspw=="], - "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.4", "", { "dependencies": { "@tauri-apps/api": "^2.11.0" } }, "sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ=="], + "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.7.2", "", { "dependencies": { "@tauri-apps/api": "^2.12.0" } }, "sha512-LyybP1VSzTMQiaFYcsbAse47KpivlyXdLZMQzGLEnkgQacGESnpjAfFoQ6h5sqWgAdZDH9szYjPzHMpv0Sz+TQ=="], - "@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A=="], + "@tauri-apps/plugin-os": ["@tauri-apps/plugin-os@2.4.0", "", { "dependencies": { "@tauri-apps/api": "^2.12.0" } }, "sha512-XFQjntNNI3Vlqdy4WUGwiXX8DByzKVvoemAcUVkBm9NJPGmoDtII5i+vfuAzWaJfOE5cUJJLUDqXBnd4reSAIA=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], diff --git a/apps/maple-research/frontend/package.json b/apps/maple-research/frontend/package.json index db1243c09..ab5f1a696 100644 --- a/apps/maple-research/frontend/package.json +++ b/apps/maple-research/frontend/package.json @@ -29,7 +29,7 @@ "resolutions": { "@babel/core": "^7.29.7", "@humanfs/node": "^0.16.8", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "mdast-util-gfm-autolink-literal": "2.0.0", "browserslist": "^4.28.8", "flatted": "^3.4.2", @@ -64,11 +64,11 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-query": "^5.100.1", "@tanstack/react-router": "^1.170.18", - "@tauri-apps/api": "2.11.0", + "@tauri-apps/api": "2.12.4", "@tauri-apps/plugin-deep-link": "2.4.8", - "@tauri-apps/plugin-dialog": "^2.7.1", - "@tauri-apps/plugin-opener": "^2.5.4", - "@tauri-apps/plugin-os": "^2.3.2", + "@tauri-apps/plugin-dialog": "^2.8.3", + "@tauri-apps/plugin-opener": "^2.7.2", + "@tauri-apps/plugin-os": "^2.4.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "katex": "^0.18.2", @@ -92,7 +92,7 @@ "@eslint/js": "^9.39.4", "@noble/hashes": "^1.8.0", "@tanstack/router-plugin": "^1.168.23", - "@tauri-apps/cli": "2.11.1", + "@tauri-apps/cli": "2.12.3", "@types/bun": "1.3.13", "@types/node": "^22.19.17", "@types/react": "^18.3.28", diff --git a/apps/maple-research/frontend/src-tauri/Cargo.lock b/apps/maple-research/frontend/src-tauri/Cargo.lock index 9b89fe512..bd183ab4f 100644 --- a/apps/maple-research/frontend/src-tauri/Cargo.lock +++ b/apps/maple-research/frontend/src-tauri/Cargo.lock @@ -184,13 +184,28 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +[[package]] +name = "alloc-no-stdlib" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" + [[package]] name = "alloc-stdlib" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ - "alloc-no-stdlib", + "alloc-no-stdlib 2.0.4", +] + +[[package]] +name = "alloc-stdlib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" +dependencies = [ + "alloc-no-stdlib 3.0.0", ] [[package]] @@ -890,47 +905,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] -name = "borsh" -version = "1.5.7" +name = "brotli" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ - "borsh-derive", - "cfg_aliases", + "alloc-no-stdlib 2.0.4", + "alloc-stdlib 0.2.2", + "brotli-decompressor 5.0.0", ] [[package]] -name = "borsh-derive" -version = "1.5.7" +name = "brotli" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b" dependencies = [ - "once_cell", - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.108", + "alloc-no-stdlib 3.0.0", + "alloc-stdlib 0.3.0", + "brotli-decompressor 6.0.1", ] [[package]] -name = "brotli" -version = "8.0.2" +name = "brotli-decompressor" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", + "alloc-no-stdlib 2.0.4", + "alloc-stdlib 0.2.2", ] [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", + "alloc-no-stdlib 3.0.0", + "alloc-stdlib 0.3.0", ] [[package]] @@ -965,17 +978,6 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -[[package]] -name = "byte-unit" -version = "5.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174" -dependencies = [ - "rust_decimal", - "serde", - "utf8-width", -] - [[package]] name = "bytecount" version = "0.6.9" @@ -1092,12 +1094,13 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.22.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +checksum = "82f4b26e751e711a5302649417f2da046dce6391b2ea30a4820f37462314f0b9" dependencies = [ + "semver", "serde", - "toml 0.9.8", + "toml 1.1.8+spec-1.1.0", ] [[package]] @@ -1129,9 +1132,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfb" -version = "0.7.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +checksum = "d8a4f8e55be323b378facfcf1f06aa97f6ec17cf4ac84fb17325093aaf62da41" dependencies = [ "byteorder", "fnv", @@ -1140,13 +1143,13 @@ dependencies = [ [[package]] name = "cfb" -version = "0.10.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a4f8e55be323b378facfcf1f06aa97f6ec17cf4ac84fb17325093aaf62da41" +checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3" dependencies = [ - "byteorder", "fnv", "uuid", + "web-time", ] [[package]] @@ -1370,7 +1373,7 @@ version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ - "brotli", + "brotli 8.0.2", "compression-core", "flate2", "memchr", @@ -1683,9 +1686,9 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98" dependencies = [ "cssparser-macros", "dtoa-short", @@ -1696,29 +1699,19 @@ dependencies = [ [[package]] name = "cssparser-macros" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a" dependencies = [ "quote", - "syn 2.0.108", + "syn 3.0.3", ] [[package]] name = "ctor" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" -dependencies = [ - "ctor-proc-macro", - "dtor", -] - -[[package]] -name = "ctor-proc-macro" -version = "0.0.7" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" [[package]] name = "ctr" @@ -2042,6 +2035,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.5.0" @@ -2051,7 +2053,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2120,9 +2122,9 @@ dependencies = [ [[package]] name = "dom_query" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc" dependencies = [ "bit-set", "cssparser", @@ -2163,21 +2165,6 @@ dependencies = [ "dtoa", ] -[[package]] -name = "dtor" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" -dependencies = [ - "dtor-proc-macro", -] - -[[package]] -name = "dtor-proc-macro" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" - [[package]] name = "dunce" version = "1.0.5" @@ -2376,7 +2363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3200,7 +3187,7 @@ dependencies = [ "blake3", "chrono", "clap", - "dirs", + "dirs 6.0.0", "etcetera", "fs-err", "fs2", @@ -3217,7 +3204,7 @@ dependencies = [ "ignore", "image 0.24.9", "include_dir", - "indexmap 2.12.0", + "indexmap 2.14.2", "indoc", "jsonschema", "jsonwebtoken", @@ -3475,7 +3462,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.12.0", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -3619,9 +3606,9 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" dependencies = [ "log", "markup5ever", @@ -3762,7 +3749,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -4045,12 +4032,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -4066,11 +4053,11 @@ dependencies = [ [[package]] name = "infer" -version = "0.19.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4" dependencies = [ - "cfb 0.7.3", + "cfb 0.14.0", ] [[package]] @@ -4322,21 +4309,21 @@ dependencies = [ [[package]] name = "json-patch" -version = "3.0.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" dependencies = [ "jsonptr", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] name = "jsonptr" -version = "0.6.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" dependencies = [ "serde", "serde_json", @@ -4413,13 +4400,12 @@ dependencies = [ [[package]] name = "keyboard-types" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" dependencies = [ "bitflags 2.10.0", "serde", - "unicode-segmentation", ] [[package]] @@ -4596,9 +4582,6 @@ name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -dependencies = [ - "value-bag", -] [[package]] name = "loop9" @@ -4680,7 +4663,7 @@ dependencies = [ "tokio-util", "tower-http 0.6.8", "webpki-roots", - "windows 0.62.2", + "windows", "zip", ] @@ -4746,9 +4729,9 @@ dependencies = [ [[package]] name = "markup5ever" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" dependencies = [ "log", "tendril", @@ -4870,9 +4853,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "minisign-verify" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43" +checksum = "871285dc19d8d0ebe0eef3d0e99a205f2a71363b122632cbbfa4a6c370a960ce" [[package]] name = "miniz_oxide" @@ -4907,9 +4890,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a2e3dff89cd322c66647942668faee0a2b1f88ea6cbb4d374b4a8d7e92528c" +checksum = "cca139e57da4383727f189e43e66e84fa372347027fc996f9f1a007ec0a37996" dependencies = [ "crossbeam-channel", "dpi", @@ -4923,7 +4906,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5070,7 +5053,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5186,7 +5169,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", "syn 2.0.108", @@ -5258,6 +5241,7 @@ dependencies = [ "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", ] [[package]] @@ -5776,7 +5760,7 @@ dependencies = [ "aes 0.9.1", "base64 0.22.1", "bitflags 2.10.0", - "brotli", + "brotli 8.0.2", "byteorder", "bytes", "cbc", @@ -6020,7 +6004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" dependencies = [ "base64 0.22.1", - "indexmap 2.12.0", + "indexmap 2.14.2", "quick-xml 0.41.0", "serde", "time", @@ -6250,11 +6234,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" dependencies = [ "futures", - "indexmap 2.12.0", + "indexmap 2.14.2", "nix 0.31.3", "tokio", "tracing", - "windows 0.62.2", + "windows", ] [[package]] @@ -6937,23 +6921,6 @@ dependencies = [ "ordered-multimap", ] -[[package]] -name = "rust_decimal" -version = "1.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7653272e75dcac41dc199fbea6f5797633994fafd339943c06c9af16bf29cd3a" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "rand 0.8.6", - "rand 0.9.4", - "serde", - "serde_json", - "wasm-bindgen", -] - [[package]] name = "rustc-hash" version = "2.1.2" @@ -7011,7 +6978,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7070,7 +7037,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7258,9 +7225,9 @@ dependencies = [ [[package]] name = "selectors" -version = "0.36.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d" dependencies = [ "bitflags 2.10.0", "cssparser", @@ -7277,9 +7244,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -7344,7 +7311,7 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "itoa", "memchr", "ryu", @@ -7385,9 +7352,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" dependencies = [ "serde_core", ] @@ -7415,7 +7382,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.0", + "indexmap 2.14.2", "schemars 0.9.0", "schemars 1.0.5", "serde_core", @@ -7442,7 +7409,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "itoa", "ryu", "serde", @@ -7555,7 +7522,7 @@ version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ - "dirs", + "dirs 6.0.0", ] [[package]] @@ -7694,7 +7661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7797,7 +7764,7 @@ dependencies = [ "futures-util", "hashbrown 0.16.0", "hashlink", - "indexmap 2.12.0", + "indexmap 2.14.2", "log", "memchr", "percent-encoding", @@ -7964,6 +7931,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "static_vcruntime" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7589a1859b09232522bb9edf076ca7fcf14ded96685d3fa629db73023ffbb7" + [[package]] name = "streaming-iterator" version = "0.1.9" @@ -8085,9 +8058,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "swift-rs" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +checksum = "cefd87076fd385308ee4aff597256c902f1fbb4d031de1558efc12f8ffefadd9" dependencies = [ "base64 0.21.7", "serde", @@ -8219,7 +8192,7 @@ dependencies = [ [[package]] name = "tao" -version = "0.35.2" +version = "0.37.1" dependencies = [ "bitflags 2.10.0", "block2 0.6.2", @@ -8237,29 +8210,29 @@ dependencies = [ "libc", "log", "ndk", + "ndk-context", "ndk-sys", "objc2 0.6.4", "objc2-app-kit", "objc2-foundation 0.3.2", "objc2-ui-kit", - "once_cell", "parking_lot", "percent-encoding", "raw-window-handle", "tao-macros", "unicode-segmentation", "url", - "windows 0.61.3", - "windows-core 0.61.2", + "windows", + "windows-core", "windows-version", "x11-dl", ] [[package]] name = "tao-macros" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" dependencies = [ "proc-macro2", "quote", @@ -8285,14 +8258,14 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.11.2" +version = "2.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437404997acf375d85f1177afa7e11bb971f274ed6a7b83a2a3e339015f4cc28" +checksum = "f316751027fc2644221d1a448564d519efb07067fda584879d9cda966391d212" dependencies = [ "anyhow", "bytes", "cookie", - "dirs", + "dirs 7.0.0", "dunce", "embed_plist", "getrandom 0.3.4", @@ -8331,18 +8304,18 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows 0.61.3", + "windows", ] [[package]] name = "tauri-build" -version = "2.6.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa1f9055fc23919a54e4e125052bed16ed04aef0487086e758fe01a67b451c7" +checksum = "641dd5fd297c99ab92717b0428cca5b9c6f960b2ac96ca131b3eebc9d70b8ddc" dependencies = [ "anyhow", "cargo_toml", - "dirs", + "dirs 7.0.0", "glob", "heck 0.5.0", "json-patch", @@ -8350,6 +8323,8 @@ dependencies = [ "semver", "serde", "serde_json", + "static_vcruntime", + "strsim", "tauri-utils", "tauri-winres", "walkdir", @@ -8357,16 +8332,16 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.6.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a0319528a025a38c4078e7dae2c446f4e63620ddb0659a643ede1cb38f90e9" +checksum = "a6585c5b536c00f039d12ce70a9755172d5423bbcfd3969863baa70314c3e02f" dependencies = [ - "base64 0.22.1", - "brotli", + "base64 0.23.1", + "brotli 9.0.0", "ico", "json-patch", "plist", - "png 0.17.16", + "png 0.18.1", "proc-macro2", "quote", "semver", @@ -8384,9 +8359,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.6.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6cb4e3896c21d2f6da5b31251d2faea0153bba56ed0e970f918115dbee4924" +checksum = "e2e83a0cfa1bae376e8fbbdc0b1692707aed50cd6072ce85cae4789af5d6c19f" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -8398,9 +8373,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.6.2" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e126abc9e84e35cdfd01596140a73a1850cdb0df0a23acf0185776c30b469a6e" +checksum = "67fb21a77e8f94e8beb3325e583102488659365af0a3948b7ea244c5b779c291" dependencies = [ "anyhow", "glob", @@ -8435,9 +8410,9 @@ dependencies = [ [[package]] name = "tauri-plugin-dialog" -version = "2.7.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65981abb771e74e571a38196c3baa11c459379164791eba0e67abc1a5fac9884" +checksum = "b7461aaacf1901915ea7b9f5cd74f8224a351ed96af0a62c07fce934003cb34b" dependencies = [ "log", "raw-window-handle", @@ -8453,9 +8428,9 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.5.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" +checksum = "5d58eb1d4984e535f47a642ffec09a2da6f5c18a12b5902d12126cb7626d628b" dependencies = [ "anyhow", "dunce", @@ -8471,7 +8446,7 @@ dependencies = [ "tauri-plugin", "tauri-utils", "thiserror 2.0.18", - "toml 1.1.0+spec-1.1.0", + "toml 1.1.8+spec-1.1.0", "url", ] @@ -8488,12 +8463,11 @@ dependencies = [ [[package]] name = "tauri-plugin-log" -version = "2.8.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7545bd67f070a4500432c826e2e0682146a1d6712aee22a2786490156b574d93" +checksum = "d83980f6765feb86cf2224959246be28b0488e14a23204a719f7f9deecfc64c9" dependencies = [ "android_logger", - "byte-unit", "fern", "log", "objc2 0.6.4", @@ -8510,9 +8484,9 @@ dependencies = [ [[package]] name = "tauri-plugin-opener" -version = "2.5.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" +checksum = "53810be1b4fed22bb6ac3f4cab5a2b80d9391d1af436bda145607532bc0bb219" dependencies = [ "dunce", "glob", @@ -8526,15 +8500,15 @@ dependencies = [ "tauri-plugin", "thiserror 2.0.18", "url", - "windows 0.61.3", + "windows", "zbus", ] [[package]] name = "tauri-plugin-os" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997" +checksum = "4bd69d272ab772b30462779de12a40f837616831c794c71531bea91474e1061f" dependencies = [ "gethostname", "log", @@ -8551,8 +8525,6 @@ dependencies = [ [[package]] name = "tauri-plugin-sign-in-with-apple" version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9402eb8a70fdff5127fccc1f1666ebb06b5efc10e0eb656d8a70c798e26142e" dependencies = [ "serde", "tauri", @@ -8578,16 +8550,17 @@ dependencies = [ [[package]] name = "tauri-plugin-updater" -version = "2.10.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af" +checksum = "7259989d4fe11b2bf51f7d671642249b1837c4ae2da1430dc713ae38f98ceeec" dependencies = [ "base64 0.22.1", - "dirs", + "dirs 7.0.0", "flate2", "futures-util", "http", "infer", + "libc", "log", "minisign-verify", "osakit", @@ -8605,14 +8578,14 @@ dependencies = [ "time", "tokio", "url", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "tauri-runtime" -version = "2.11.2" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48222d7116c8807eaa6fe2f372e023fae125084e61e6eca6d70b7961cdf129ef" +checksum = "7c55766d99cea69055904d1735b93c2049302b2e09effd462120deb5581d78e9" dependencies = [ "cookie", "dpi", @@ -8630,14 +8603,14 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows 0.61.3", + "windows", ] [[package]] name = "tauri-runtime-wry" -version = "2.11.2" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9" +checksum = "8d61892587aea2a85245bcaec98c6fe46268874c0a0deae5535fb71ca7b93eae" dependencies = [ "gtk", "http", @@ -8655,18 +8628,18 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows 0.61.3", + "windows", "wry", ] [[package]] name = "tauri-utils" -version = "2.9.2" +version = "2.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092379df9a707631978e6c56b1bc2401d387f01e2d4a3c123360d167bbb9aa95" +checksum = "362ab21cd4cc713f2a02b1a9b5688198b1541cfb6907c4281e9190e0f0ef4aa3" dependencies = [ "anyhow", - "brotli", + "brotli 9.0.0", "cargo_metadata", "ctor", "dom_query", @@ -8690,7 +8663,7 @@ dependencies = [ "serde_with", "swift-rs", "thiserror 2.0.18", - "toml 1.1.0+spec-1.1.0", + "toml 0.9.8", "url", "urlpattern", "uuid", @@ -8717,7 +8690,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -9011,9 +8984,9 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "serde_core", - "serde_spanned 1.1.1", + "serde_spanned 1.1.2", "toml_datetime 0.7.3", "toml_parser", "toml_writer", @@ -9022,14 +8995,14 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.0+spec-1.1.0" +version = "1.1.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 1.1.1+spec-1.1.0", + "serde_spanned 1.1.2", + "toml_datetime 1.1.2+spec-1.1.0", "toml_parser", "toml_writer", "winnow 1.0.3", @@ -9055,9 +9028,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" dependencies = [ "serde_core", ] @@ -9068,7 +9041,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -9079,7 +9052,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -9092,7 +9065,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.14.2", "toml_datetime 0.7.3", "toml_parser", "winnow 0.7.13", @@ -9100,18 +9073,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" dependencies = [ "winnow 1.0.3", ] [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" [[package]] name = "tower" @@ -9293,12 +9266,12 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.23.1" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773" +checksum = "b2b9c52859a94554803ccd4a24b98f74148ebc73b90676d783f3490b1bff9d72" dependencies = [ "crossbeam-channel", - "dirs", + "dirs 7.0.0", "libappindicator", "muda", "objc2 0.6.4", @@ -9310,7 +9283,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -9484,47 +9457,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "2.9.0" @@ -9670,13 +9602,13 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "urlpattern" -version = "0.3.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b" dependencies = [ + "icu_properties", "regex", "serde", - "unic-ucd-ident", "url", ] @@ -9686,12 +9618,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf8-width" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -9758,12 +9684,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "value-bag" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" - [[package]] name = "vcpkg" version = "0.2.15" @@ -10035,23 +9955,21 @@ dependencies = [ [[package]] name = "webview2-com" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +checksum = "3f89fca7a704cee10dcb3654c1dbb8941d1783132f1917358af75bec37a7d7e6" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.61.3", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", + "windows", + "windows-core", ] [[package]] name = "webview2-com-macros" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", @@ -10060,13 +9978,13 @@ dependencies = [ [[package]] name = "webview2-com-sys" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +checksum = "b3a07132775117d6065853d9d1178157b8c90e228de47129d6bce2c7edebedfb" dependencies = [ "thiserror 2.0.18", - "windows 0.61.3", - "windows-core 0.61.2", + "windows", + "windows-core", ] [[package]] @@ -10128,7 +10046,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -10139,51 +10057,30 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window-vibrancy" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +checksum = "111e51caca442cafd9bab396628ac4d814880eaea4e63dfd76a12e9f342b5580" dependencies = [ "objc2 0.6.4", "objc2-app-kit", "objc2-core-foundation", "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", "raw-window-handle", - "windows-sys 0.59.0", + "windows-sys 0.60.2", "windows-version", ] -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections 0.2.0", - "windows-core 0.61.2", - "windows-future 0.2.1", - "windows-link 0.1.3", - "windows-numerics 0.2.0", -] - [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections 0.3.2", - "windows-core 0.62.2", - "windows-future 0.3.2", - "windows-numerics 0.3.1", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", ] [[package]] @@ -10192,20 +10089,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -10221,26 +10105,15 @@ dependencies = [ "windows-strings 0.5.1", ] -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading 0.1.0", -] - [[package]] name = "windows-future" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", + "windows-core", "windows-link 0.2.1", - "windows-threading 0.2.1", + "windows-threading", ] [[package]] @@ -10277,23 +10150,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - [[package]] name = "windows-numerics" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", + "windows-core", "windows-link 0.2.1", ] @@ -10437,15 +10300,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-threading" version = "0.2.1" @@ -10661,7 +10515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb731d4c4d93eacc69a1ad2f270f905788a98e4a3438267bcafbe08d3431c8d8" dependencies = [ "font-types", - "indexmap 2.12.0", + "indexmap 2.14.2", "kurbo", "log", "read-fonts", @@ -10675,15 +10529,15 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "wry" -version = "0.55.1" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +checksum = "a819957a01b3119af85e638a38d242af76dbc87d130dca67bfd0441072e21ff0" dependencies = [ "base64 0.22.1", "block2 0.6.2", "cookie", "crossbeam-channel", - "dirs", + "dirs 7.0.0", "dom_query", "dpi", "dunce", @@ -10711,8 +10565,8 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.61.3", - "windows-core 0.61.2", + "windows", + "windows-core", "windows-version", "x11-dl", ] @@ -10993,7 +10847,7 @@ checksum = "6e04976da629d3b04204ba8105b396a193af8ad2583b2c79bfb3ab8af580a11e" dependencies = [ "crc32fast", "flate2", - "indexmap 2.12.0", + "indexmap 2.14.2", "memchr", "nix 0.31.3", "typed-path", diff --git a/apps/maple-research/frontend/src-tauri/Cargo.toml b/apps/maple-research/frontend/src-tauri/Cargo.toml index f58550c53..04c7022f6 100644 --- a/apps/maple-research/frontend/src-tauri/Cargo.toml +++ b/apps/maple-research/frontend/src-tauri/Cargo.toml @@ -15,7 +15,7 @@ name = "app_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { version = "2.6.2", features = [] } +tauri-build = { version = "2.7.3", features = [] } plist = "1" serde_json = "1.0" @@ -23,23 +23,25 @@ serde_json = "1.0" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } log = "0.4" -tauri = { version = "2.11.2", features = [] } -tauri-plugin-log = "2.8.0" +tauri = { version = "2.12.4", features = [] } +tauri-plugin-log = "2.10.1" # Maple publishes raw Windows installers and Linux AppImages, so it does not # need the updater's legacy ZIP / AppImage.tar.gz handlers or their ZIP dependency. -tauri-plugin-updater = { version = "2.10.1", default-features = false, features = ["rustls-tls"] } -tauri-plugin = "2.6.2" +tauri-plugin-updater = { version = "2.14.0", default-features = false, features = ["rustls-tls"] } +tauri-plugin = "2.7.2" # Keep Android deep links on the last known-working plugin behavior. # tauri-plugin-deep-link 2.4.9 added Android-side config filtering that drops # our existing cloud.opensecret.maple:// OAuth callback before Rust receives it. tauri-plugin-deep-link = "=2.4.7" +# single-instance 2.4.1+ needs deep-link >= 2.4.8, so it stays on the newest release +# that resolves with the deep-link pin above. tauri-plugin-single-instance = { version = "=2.4.0", features = ["deep-link"] } -tauri-plugin-opener = "2.5.4" -tauri-plugin-os = "2.3.2" +tauri-plugin-opener = "2.7.2" +tauri-plugin-os = "2.4.0" tauri-plugin-sign-in-with-apple = "1.0.2" tokio = { version = "1.0", features = ["io-std", "io-util", "net", "process", "sync", "rt-multi-thread", "macros", "time"] } once_cell = "1.18.0" -tauri-plugin-fs = "2.5.1" +tauri-plugin-fs = "2.7.1" anyhow = "1.0" pdf_oxide = { version = "=0.3.74", git = "https://github.com/OpenSecretCloud/pdf_oxide.git", rev = "2bc9246782fe874a97e57e348acc7a9eaa27ca4d", features = ["ocr-ort", "rendering"] } # Keep the pre-1.0 Word parser exact-pinned. office_oxide is already in the @@ -73,7 +75,7 @@ axum = "0.8" tower-http = { version = "0.6", features = ["cors"] } async-trait = "0.1" rmcp = { version = "=3.1.2", default-features = false, features = ["client", "transport-streamable-http-client-reqwest"] } -tauri-plugin-dialog = "2.7.1" +tauri-plugin-dialog = "2.8.3" tokio-util = { version = "0.7", features = ["codec", "compat", "io"] } agent-client-protocol = { version = "=2.0.0", default-features = false, features = ["unstable_elicitation", "unstable_end_turn_token_usage"] } chrono = { version = "0.4", default-features = false, features = ["std"] } @@ -117,13 +119,31 @@ ciborium = "0.2" # The embedded proxy requires `maple-sdk` from the registry; resolve it to the same # in-tree source as the dependency above (docs/sdk-publishing.md). maple-sdk = { path = "../../../../sdk/rust" } -# Local patch for tao 0.35.2 Android intent crashes: +# Local patch for tao 0.37.1 Android intent crashes: # https://github.com/tauri-apps/tao/issues/1217 # # Tao's Android intent handler currently unwraps several JNI calls that can # receive null objects from real Android launch/deep-link flows, aborting the # process before tauri-plugin-deep-link sees OAuth/payment redirects. Remove # this once Tauri resolves to a Tao release containing the upstream fix. +# +# It also carries tauri-apps/tao#1376 for iOS: in the scene life cycle a URL that +# cold-launches the app arrives in the scene connection options, which tao 0.37 +# ignores, so OAuth callbacks that relaunch Maple were dropped. # The vendored library uses this application's Cargo.lock; its upstream # standalone lockfile is intentionally omitted. -tao = { path = "patches/tao-0.35.2" } +tao = { path = "patches/tao-0.37.1" } +# tauri-plugin-sign-in-with-apple 1.0.2 (last release 2024-11) declares its Swift +# entry point `@_cdecl("init_plugin_sign_in_with_apple") func initPlugin()` with +# internal visibility. Xcode 27 release builds need these entry points `public` +# to link (Tauri 2.12.3 made its own public: tauri-apps/tauri#16130); the +# vendored copy only adds that keyword. Remove once upstream publishes the fix. +tauri-plugin-sign-in-with-apple = { path = "patches/tauri-plugin-sign-in-with-apple-1.0.2" } + +# Release builds strip with rustc's rust-objcopy, which can leave a dylib's LINKEDIT +# string table 4-byte aligned. macOS 27 refuses to load such dylibs when they are +# linked against its SDK, and the build loads its proc-macros as dylibs. Build +# scripts and proc-macros never ship, so leave them unstripped. Remove once the +# pinned Rust's rust-objcopy includes llvm/llvm-project#203680. +[profile.release.build-override] +strip = false diff --git a/apps/maple-research/frontend/src-tauri/Info.ios.plist b/apps/maple-research/frontend/src-tauri/Info.ios.plist new file mode 100644 index 000000000..da3db42d6 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/Info.ios.plist @@ -0,0 +1,25 @@ + + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + TaoSceneDelegate + + + + + + diff --git a/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties index 0d4bd191c..df3acc569 100644 --- a/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties +++ b/apps/maple-research/frontend/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ #Tue May 10 19:22:52 CST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/apps/maple-research/frontend/src-tauri/gen/apple/maple.xcodeproj/project.pbxproj.backup b/apps/maple-research/frontend/src-tauri/gen/apple/maple.xcodeproj/project.pbxproj.backup deleted file mode 100644 index 41a99a896..000000000 --- a/apps/maple-research/frontend/src-tauri/gen/apple/maple.xcodeproj/project.pbxproj.backup +++ /dev/null @@ -1,626 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 56; - objects = { - -/* Begin PBXBuildFile section */ - 0F4AA8C5111094B8D7B033A4 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E996CAD20B045EF299247BEF /* WebKit.framework */; }; - 23D760C25DBAAA96DF52F98C /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAFD3E024B29F57EA0D88AB5 /* MetalKit.framework */; }; - 27222ACBBD95CA356BDB87AB /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BAF092EB33F2B4FDF867E2ED /* QuartzCore.framework */; }; - 46728F6CD07C626A781543FF /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 938F41BA03AF7FAB80585A60 /* Security.framework */; }; - 4E14944735CA89389849E430 /* libapp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F27A6FC4AB3E06D481B1E137 /* libapp.a */; }; - 621A96F6B965E600E714FB6C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B0EAFEFF59AA7CFEF57FA63C /* LaunchScreen.storyboard */; }; - 8F9EDB5679AB5E12D6F1E071 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DACD0B6B9FB86AFF47366EB2 /* UIKit.framework */; }; - 90DEA2E2EC5F95784C9D8B00 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 898D6E7ABC92C3B9A7970DAE /* CoreGraphics.framework */; }; - 9F3ED7EA97AFC22E0B4A6EAF /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = CE20E8A9C4CF149CA9CA485A /* main.mm */; }; - A120CF155DE2B343CEFFB77C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6BF4C7EB12DBD2E253B2741E /* Assets.xcassets */; }; - B79EC8A2A1A869B9F38906ED /* assets in Resources */ = {isa = PBXBuildFile; fileRef = AF5DA3546C7B12BA141E9508 /* assets */; }; - FB98D58EAB479A32F7CA66CA /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C072EB8E1CCC1094712EA7F7 /* Metal.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - 1094B8CB671809878F533CEC /* lib.rs */ = {isa = PBXFileReference; lastKnownFileType = text; path = lib.rs; sourceTree = ""; }; - 19C98BDFCAE8C0B2FE05F13B /* maple_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = maple_iOS.entitlements; sourceTree = ""; }; - 1DB07799A17353B2E32B65D3 /* Maple.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Maple.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 6BF4C7EB12DBD2E253B2741E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 7BBAB8D5C4E3D05A4F48B6EA /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = ""; }; - 898D6E7ABC92C3B9A7970DAE /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; - 938F41BA03AF7FAB80585A60 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; - AAFD3E024B29F57EA0D88AB5 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; - AF5DA3546C7B12BA141E9508 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; }; - B0EAFEFF59AA7CFEF57FA63C /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; - BAF092EB33F2B4FDF867E2ED /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - C072EB8E1CCC1094712EA7F7 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; - C0A332F21EB65CD4B83229DF /* main.rs */ = {isa = PBXFileReference; lastKnownFileType = text; path = main.rs; sourceTree = ""; }; - CE20E8A9C4CF149CA9CA485A /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; - D6F6085362AF0C062EDB5810 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - DACD0B6B9FB86AFF47366EB2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - E996CAD20B045EF299247BEF /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; - F27A6FC4AB3E06D481B1E137 /* libapp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapp.a; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 04F3BEBB04BD927FDC86B255 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4E14944735CA89389849E430 /* libapp.a in Frameworks */, - 90DEA2E2EC5F95784C9D8B00 /* CoreGraphics.framework in Frameworks */, - FB98D58EAB479A32F7CA66CA /* Metal.framework in Frameworks */, - 23D760C25DBAAA96DF52F98C /* MetalKit.framework in Frameworks */, - 27222ACBBD95CA356BDB87AB /* QuartzCore.framework in Frameworks */, - 46728F6CD07C626A781543FF /* Security.framework in Frameworks */, - 8F9EDB5679AB5E12D6F1E071 /* UIKit.framework in Frameworks */, - 0F4AA8C5111094B8D7B033A4 /* WebKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 31943D18956B6AFD5A82AB02 /* maple_iOS */ = { - isa = PBXGroup; - children = ( - D6F6085362AF0C062EDB5810 /* Info.plist */, - 19C98BDFCAE8C0B2FE05F13B /* maple_iOS.entitlements */, - ); - path = maple_iOS; - sourceTree = ""; - }; - 6AE8986BFC081ADF4EF98889 /* Externals */ = { - isa = PBXGroup; - children = ( - ); - path = Externals; - sourceTree = ""; - }; - A278899D6905367F60E00F01 /* Sources */ = { - isa = PBXGroup; - children = ( - E5992A2C52DE443515252068 /* maple */, - ); - path = Sources; - sourceTree = ""; - }; - B58B2AB62FA5D8F41C3A031C /* Frameworks */ = { - isa = PBXGroup; - children = ( - 898D6E7ABC92C3B9A7970DAE /* CoreGraphics.framework */, - F27A6FC4AB3E06D481B1E137 /* libapp.a */, - C072EB8E1CCC1094712EA7F7 /* Metal.framework */, - AAFD3E024B29F57EA0D88AB5 /* MetalKit.framework */, - BAF092EB33F2B4FDF867E2ED /* QuartzCore.framework */, - 938F41BA03AF7FAB80585A60 /* Security.framework */, - DACD0B6B9FB86AFF47366EB2 /* UIKit.framework */, - E996CAD20B045EF299247BEF /* WebKit.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - C312B5C9A1506139452E194B /* Products */ = { - isa = PBXGroup; - children = ( - 1DB07799A17353B2E32B65D3 /* Maple.app */, - ); - name = Products; - sourceTree = ""; - }; - D3140E092D76240F6A157CCE = { - isa = PBXGroup; - children = ( - AF5DA3546C7B12BA141E9508 /* assets */, - 6BF4C7EB12DBD2E253B2741E /* Assets.xcassets */, - B0EAFEFF59AA7CFEF57FA63C /* LaunchScreen.storyboard */, - 6AE8986BFC081ADF4EF98889 /* Externals */, - 31943D18956B6AFD5A82AB02 /* maple_iOS */, - A278899D6905367F60E00F01 /* Sources */, - FF31B258F4FE6CC5B57B4FB8 /* src */, - B58B2AB62FA5D8F41C3A031C /* Frameworks */, - C312B5C9A1506139452E194B /* Products */, - ); - sourceTree = ""; - }; - E5992A2C52DE443515252068 /* maple */ = { - isa = PBXGroup; - children = ( - CE20E8A9C4CF149CA9CA485A /* main.mm */, - F9E1B0C7AF484C8F6312F2C8 /* bindings */, - ); - path = maple; - sourceTree = ""; - }; - F9E1B0C7AF484C8F6312F2C8 /* bindings */ = { - isa = PBXGroup; - children = ( - 7BBAB8D5C4E3D05A4F48B6EA /* bindings.h */, - ); - path = bindings; - sourceTree = ""; - }; - FF31B258F4FE6CC5B57B4FB8 /* src */ = { - isa = PBXGroup; - children = ( - 1094B8CB671809878F533CEC /* lib.rs */, - C0A332F21EB65CD4B83229DF /* main.rs */, - ); - name = src; - path = ../../src; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 745812EBA5246F9E4CEE1574 /* maple_iOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 27939507295816FC37580B40 /* Build configuration list for PBXNativeTarget "maple_iOS" */; - buildPhases = ( - 88FF8D973FB9516A2A98CD39 /* Build Rust Code */, - 6D9F08407F22BE108C77276C /* Sources */, - 25104604B2C2A81B04543E68 /* Resources */, - 04F3BEBB04BD927FDC86B255 /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = maple_iOS; - productName = maple_iOS; - productReference = 1DB07799A17353B2E32B65D3 /* Maple.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - EAA0B11A781B9CD753D1399F /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1430; - }; - buildConfigurationList = 2FA6FD4DEFE659C96AAB5E5D /* Build configuration list for PBXProject "maple" */; - compatibilityVersion = "Xcode 14.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - Base, - en, - ); - mainGroup = D3140E092D76240F6A157CCE; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 745812EBA5246F9E4CEE1574 /* maple_iOS */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 25104604B2C2A81B04543E68 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - A120CF155DE2B343CEFFB77C /* Assets.xcassets in Resources */, - 621A96F6B965E600E714FB6C /* LaunchScreen.storyboard in Resources */, - B79EC8A2A1A869B9F38906ED /* assets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 88FF8D973FB9516A2A98CD39 /* Build Rust Code */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Build Rust Code"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a", - "$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a", - "$(SRCROOT)/Externals/arm64-sim/${CONFIGURATION}/libapp.a", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "bun tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths \"${FRAMEWORK_SEARCH_PATHS:?}\" --header-search-paths \"${HEADER_SEARCH_PATHS:?}\" --gcc-preprocessor-definitions \"${GCC_PREPROCESSOR_DEFINITIONS:-}\" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 6D9F08407F22BE108C77276C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 9F3ED7EA97AFC22E0B4A6EAF /* main.mm in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 1DE1BEC252AAEF735A07CA8B /* debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - "arm64-sim", - ); - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = maple_iOS/maple_iOS.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = "X773Y823TN"; - ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64"; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\".\"", - ); - INFOPLIST_FILE = maple_iOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - PRODUCT_BUNDLE_IDENTIFIER = cloud.opensecret.maple; - PRODUCT_NAME = Maple; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 arm64-sim"; - }; - name = debug; - }; - 1E8FE8E2E3063C3FDCD3B5D1 /* debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DEBUG=1", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = debug; - }; - 94ECC2E044AA76E166E0866E /* release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - }; - name = release; - }; - B90C9BEF4885680BFA986CD2 /* release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - "arm64-sim", - ); - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = maple_iOS/maple_iOS.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = "X773Y823TN"; - ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64"; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\".\"", - ); - INFOPLIST_FILE = maple_iOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - PRODUCT_BUNDLE_IDENTIFIER = cloud.opensecret.maple; - PRODUCT_NAME = Maple; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 arm64-sim"; - }; - name = release; - }; - EA2669D02DADAA11005A7F4B /* local */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "DEBUG=1", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = local; - }; - EA2669D12DADAA11005A7F4B /* local */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ARCHS = ( - arm64, - "arm64-sim", - ); - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = maple_iOS/maple_iOS.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; - DEVELOPMENT_TEAM = "X773Y823TN"; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = X773Y823TN; - ENABLE_BITCODE = NO; - "EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64"; - "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "\".\"", - ); - INFOPLIST_FILE = maple_iOS/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=arm64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - "LIBRARY_SEARCH_PATHS[arch=x86_64]" = ( - "$(inherited)", - "$(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION)", - "$(SDKROOT)/usr/lib/swift", - "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", - "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", - ); - PRODUCT_BUNDLE_IDENTIFIER = cloud.opensecret.maple; - PRODUCT_NAME = Maple; - PROVISIONING_PROFILE_SPECIFIER = "86059ea7-ae8e-44af-8a58-b2ab7c78d299"; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "86059ea7-ae8e-44af-8a58-b2ab7c78d299"; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALID_ARCHS = "arm64 arm64-sim"; - }; - name = local; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 27939507295816FC37580B40 /* Build configuration list for PBXNativeTarget "maple_iOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1DE1BEC252AAEF735A07CA8B /* debug */, - EA2669D12DADAA11005A7F4B /* local */, - B90C9BEF4885680BFA986CD2 /* release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = debug; - }; - 2FA6FD4DEFE659C96AAB5E5D /* Build configuration list for PBXProject "maple" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1E8FE8E2E3063C3FDCD3B5D1 /* debug */, - EA2669D02DADAA11005A7F4B /* local */, - 94ECC2E044AA76E166E0866E /* release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = debug; - }; -/* End XCConfigurationList section */ - }; - rootObject = EAA0B11A781B9CD753D1399F /* Project object */; -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/scene.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/scene.rs deleted file mode 100644 index 23414f06b..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/scene.rs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2021-2025 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -use objc2::{define_class, rc::Retained, MainThreadMarker, MainThreadOnly}; -use objc2_foundation::{ - NSBundle, NSDictionary, NSError, NSNumber, NSObject, NSObjectProtocol, NSSet, NSString, - NSUserActivity, -}; -use objc2_ui_kit::{ - UIApplication, UIOpenURLContext, UIScene, UISceneConnectionOptions, UISceneDelegate, - UISceneSession, UIWindowScene, -}; - -use crate::{ - event::{Event, WindowEvent}, - platform_impl::platform::{app_state, event_loop::EventWrapper}, - window::WindowId as RootWindowId, -}; - -// true when the system allows the app to display multiple scenes and multiple_scenes_enabled() returns true -// https://developer.apple.com/documentation/uikit/uiapplication/supportsmultiplescenes?language=objc -pub unsafe fn app_supports_multiple_scenes() -> bool { - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - application.supportsMultipleScenes() -} - -// check whether the app's Info.plist enabled multiple scenes -pub unsafe fn multiple_scenes_enabled() -> bool { - let bundle = NSBundle::mainBundle(); - let Some(info) = bundle.infoDictionary() else { - return false; - }; - - let key = NSString::from_str("UIApplicationSceneManifest"); - let Some(manifest) = (*info).objectForKey(&key) else { - return false; - }; - - let manifest_dict = Retained::cast_unchecked::>(manifest); - let supports_key = NSString::from_str("UIApplicationSupportsMultipleScenes"); - let Some(value) = (*manifest_dict).objectForKey(&supports_key) else { - return false; - }; - - let num = Retained::cast_unchecked::(value); - (*num).as_bool() -} - -define_class!( - #[unsafe(super(NSObject))] - #[name = "TaoSceneDelegate"] - #[thread_kind = MainThreadOnly] - pub struct TaoSceneDelegate; - - unsafe impl NSObjectProtocol for TaoSceneDelegate {} - - #[allow(non_snake_case)] - unsafe impl UISceneDelegate for TaoSceneDelegate { - #[unsafe(method(scene:willConnectToSession:options:))] - fn scene_willConnectToSession_options( - &self, - scene: &UIScene, - _session: &UISceneSession, - connection_options: &UISceneConnectionOptions, - ) { - unsafe { - app_state::connect_scene(scene, connection_options); - } - } - - #[unsafe(method(sceneDidDisconnect:))] - fn sceneDidDisconnect(&self, _scene: &UIScene) {} - - #[unsafe(method(sceneDidBecomeActive:))] - fn sceneDidBecomeActive(&self, scene: &UIScene) { - unsafe { - if let Some(window_scene) = scene.downcast_ref::() { - for window in window_scene.windows() { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Focused(true), - })); - } - } - } - } - - #[unsafe(method(sceneWillResignActive:))] - fn sceneWillResignActive(&self, scene: &UIScene) { - unsafe { - if let Some(window_scene) = scene.downcast_ref::() { - for window in window_scene.windows() { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Focused(false), - })); - } - } - } - } - - #[unsafe(method(sceneWillEnterForeground:))] - fn sceneWillEnterForeground(&self, _scene: &UIScene) {} - - #[unsafe(method(sceneDidEnterBackground:))] - fn sceneDidEnterBackground(&self, _scene: &UIScene) {} - - #[unsafe(method(scene:openURLContexts:))] - fn scene_openURLContexts(&self, _scene: &UIScene, url_contexts: &NSSet) { - unsafe { - let urls: Vec = url_contexts - .iter() - .filter_map(|ctx| { - ctx.URL().absoluteString().and_then(|url| { - let url = url.to_string(); - url - .parse() - .map_err(|e| { - log::error!("failed to parse URL {url} from scene:openURLContexts: {e}"); - e - }) - .ok() - }) - }) - .collect(); - if !urls.is_empty() { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls })); - } - } - } - - #[unsafe(method(stateRestorationActivityForScene:))] - fn stateRestorationActivityForScene( - &self, - _scene: &UIScene, - ) -> Option> { - None - } - - #[unsafe(method(scene:restoreInteractionStateWithUserActivity:))] - fn scene_restoreInteractionStateWithUserActivity( - &self, - _scene: &UIScene, - _state_restoration_activity: &NSUserActivity, - ) { - } - - #[unsafe(method(scene:willContinueUserActivityWithType:))] - fn scene_willContinueUserActivityWithType( - &self, - _scene: &UIScene, - _user_activity_type: &NSString, - ) { - } - - #[unsafe(method(scene:continueUserActivity:))] - fn scene_continueUserActivity(&self, _scene: &UIScene, user_activity: &NSUserActivity) { - unsafe { - // universal app links - if let Some(url) = user_activity - .webpageURL() - .and_then(|url| url.absoluteString()) - { - let url = url.to_string().parse::().unwrap(); - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { - urls: vec![url], - })); - } - } - } - - #[unsafe(method(scene:didFailToContinueUserActivityWithType:error:))] - fn scene_didFailToContinueUserActivityWithType_error( - &self, - _scene: &UIScene, - _user_activity_type: &NSString, - _error: &NSError, - ) { - } - - #[unsafe(method(scene:didUpdateUserActivity:))] - fn scene_didUpdateUserActivity(&self, _scene: &UIScene, _user_activity: &NSUserActivity) {} - } -); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/header.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/header.rs deleted file mode 100644 index 480eb4c85..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/header.rs +++ /dev/null @@ -1,36 +0,0 @@ -use gtk::{prelude::*, ApplicationWindow, EventBox, HeaderBar}; - -pub struct WlHeader; - -impl WlHeader { - pub fn setup(window: &ApplicationWindow, title: &str) { - let header = HeaderBar::builder() - .show_close_button(true) - .decoration_layout("menu:minimize,maximize,close") - .title(title) - .build(); - - let event_box = EventBox::new(); - event_box.set_above_child(true); - event_box.set_visible(true); - event_box.set_can_focus(false); - event_box.add(&header); - - window.set_titlebar(Some(&event_box)); - Self::connect_resize_window(&header, window); - } - - fn connect_resize_window(header: &HeaderBar, window: &ApplicationWindow) { - let header_weak = header.downgrade(); - window.connect_resizable_notify(move |window| { - if let Some(header) = header_weak.upgrade() { - let is_resizable = window.is_resizable(); - header.set_decoration_layout(if !is_resizable { - Some("menu:minimize,close") - } else { - Some("menu:minimize,maximize,close") - }); - } - }); - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/mod.rs deleted file mode 100644 index a200d2603..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/wayland/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2014-2021 The winit contributors -// Copyright 2021-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 - -pub mod header; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard.rs deleted file mode 100644 index 3e85d6a2f..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard.rs +++ /dev/null @@ -1,816 +0,0 @@ -use parking_lot::{Mutex, MutexGuard}; -use std::{ - char, - collections::{HashMap, HashSet}, - ffi::OsString, - mem::MaybeUninit, - os::windows::ffi::OsStringExt, -}; - -use windows::Win32::{ - Foundation::{HWND, LPARAM, LRESULT, WPARAM}, - UI::{ - Input::KeyboardAndMouse::{self as win32km, *}, - WindowsAndMessaging::{self as win32wm, *}, - }, -}; - -use once_cell::sync::Lazy; -use unicode_segmentation::UnicodeSegmentation; - -use crate::{ - event::{ElementState, KeyEvent}, - keyboard::{Key, KeyCode, KeyLocation, NativeKeyCode}, - platform_impl::{ - platform::{ - event_loop::ProcResult, - keyboard_layout::{get_or_insert_str, Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE}, - KeyEventExtra, - }, - WindowId, - }, -}; - -pub fn is_msg_keyboard_related(msg: u32) -> bool { - let is_keyboard_msg = (WM_KEYFIRST..=WM_KEYLAST).contains(&msg); - - is_keyboard_msg || msg == WM_SETFOCUS || msg == WM_KILLFOCUS -} - -pub type ExScancode = u16; - -pub struct MessageAsKeyEvent { - pub event: KeyEvent, - pub is_synthetic: bool, -} - -pub(crate) static KEY_EVENT_BUILDERS: Lazy>> = - Lazy::new(|| Mutex::new(HashMap::new())); - -/// Stores information required to make `KeyEvent`s. -/// -/// A single Tao `KeyEvent` contains information which the Windows API passes to the application -/// in multiple window messages. In other words: a Tao `KeyEvent` cannot be built from a single -/// window message. Therefore, this type keeps track of certain information from previous events so -/// that a `KeyEvent` can be constructed when the last event related to a keypress is received. -/// -/// `PeekMessage` is sometimes used to determine whether the next window message still belongs to the -/// current keypress. If it doesn't and the current state represents a key event waiting to be -/// dispatched, then said event is considered complete and is dispatched. -/// -/// The sequence of window messages for a key press event is the following: -/// - Exactly one WM_KEYDOWN / WM_SYSKEYDOWN -/// - Zero or one WM_DEADCHAR / WM_SYSDEADCHAR -/// - Zero or more WM_CHAR / WM_SYSCHAR. These messages each come with a UTF-16 code unit which when -/// put together in the sequence they arrived in, forms the text which is the result of pressing the -/// key. -/// -/// Key release messages are a bit different due to the fact that they don't contribute to -/// text input. The "sequence" only consists of one WM_KEYUP / WM_SYSKEYUP event. -#[derive(Default)] -pub struct KeyEventBuilder { - event_info: Option, -} -impl KeyEventBuilder { - /// Call this function for every window message. - /// Returns Some() if this window message completes a KeyEvent. - /// Returns None otherwise. - pub(crate) fn process_message( - &mut self, - hwnd: HWND, - msg_kind: u32, - wparam: WPARAM, - lparam: LPARAM, - result: &mut ProcResult, - ) -> Vec { - match msg_kind { - win32wm::WM_SETFOCUS => { - // synthesize keydown events - let kbd_state = get_async_kbd_state(); - let key_events = self.synthesize_kbd_state(ElementState::Pressed, &kbd_state); - if !key_events.is_empty() { - return key_events; - } - } - win32wm::WM_KILLFOCUS => { - // sythesize keyup events - let kbd_state = get_kbd_state(); - let key_events = self.synthesize_kbd_state(ElementState::Released, &kbd_state); - if !key_events.is_empty() { - return key_events; - } - } - win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN => { - if msg_kind == WM_SYSKEYDOWN && wparam.0 == usize::from(VK_F4.0) { - // Don't dispatch Alt+F4 to the application. - // This is handled in `event_loop.rs` - return vec![]; - } - - if msg_kind == win32wm::WM_SYSKEYDOWN { - *result = ProcResult::DefSubclassProc; - } else { - *result = ProcResult::Value(LRESULT(0)); - } - - let mut layouts = LAYOUT_CACHE.lock(); - let event_info = - PartialKeyEventInfo::from_message(wparam, lparam, ElementState::Pressed, &mut layouts); - - let mut next_msg = MaybeUninit::uninit(); - let peek_retval = unsafe { - PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ) - }; - let has_next_key_message = peek_retval.as_bool(); - self.event_info = None; - let mut finished_event_info = Some(event_info); - if has_next_key_message { - let next_msg = unsafe { next_msg.assume_init() }; - let next_msg_kind = next_msg.message; - let next_belongs_to_this = !matches!( - next_msg_kind, - win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN | win32wm::WM_KEYUP | win32wm::WM_SYSKEYUP - ); - if next_belongs_to_this { - self.event_info = finished_event_info.take(); - } else { - let (_, layout) = layouts.get_current_layout(); - let is_fake = { - let curr_event = finished_event_info.as_ref().unwrap(); - is_current_fake(curr_event, next_msg, layout) - }; - if is_fake { - finished_event_info = None; - } - } - } - if let Some(event_info) = finished_event_info { - let ev = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event: ev, - is_synthetic: false, - }]; - } - } - win32wm::WM_DEADCHAR | win32wm::WM_SYSDEADCHAR => { - *result = ProcResult::Value(LRESULT(0)); - // At this point, we know that there isn't going to be any more events related to - // this key press - let event_info = self.event_info.take().unwrap(); - let mut layouts = LAYOUT_CACHE.lock(); - let ev = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event: ev, - is_synthetic: false, - }]; - } - win32wm::WM_CHAR | win32wm::WM_SYSCHAR => { - if self.event_info.is_none() { - trace!("Received a CHAR message but no `event_info` was available. The message is probably IME, returning."); - return vec![]; - } - *result = ProcResult::Value(LRESULT(0)); - let is_high_surrogate = (0xD800..=0xDBFF).contains(&wparam.0); - let is_low_surrogate = (0xDC00..=0xDFFF).contains(&wparam.0); - - let is_utf16 = is_high_surrogate || is_low_surrogate; - - let more_char_coming; - unsafe { - let mut next_msg = MaybeUninit::uninit(); - let has_message = PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ); - let has_message = has_message.as_bool(); - if !has_message { - more_char_coming = false; - } else { - let next_msg = next_msg.assume_init().message; - more_char_coming = next_msg == WM_CHAR || next_msg == WM_SYSCHAR; - } - } - - if is_utf16 { - if let Some(ev_info) = self.event_info.as_mut() { - ev_info.utf16parts.push(wparam.0 as u16); - } - } else { - // In this case, wparam holds a UTF-32 character. - // Let's encode it as UTF-16 and append it to the end of `utf16parts` - let utf16parts = match self.event_info.as_mut() { - Some(ev_info) => &mut ev_info.utf16parts, - None => { - warn!("The event_info was None when it was expected to be some"); - return vec![]; - } - }; - let start_offset = utf16parts.len(); - let new_size = utf16parts.len() + 2; - utf16parts.resize(new_size, 0); - if let Some(ch) = char::from_u32(wparam.0 as u32) { - let encode_len = ch.encode_utf16(&mut utf16parts[start_offset..]).len(); - let new_size = start_offset + encode_len; - utf16parts.resize(new_size, 0); - } - } - if !more_char_coming { - let mut event_info = match self.event_info.take() { - Some(ev_info) => ev_info, - None => { - warn!("The event_info was None when it was expected to be some"); - return vec![]; - } - }; - let mut layouts = LAYOUT_CACHE.lock(); - // It's okay to call `ToUnicode` here, because at this point the dead key - // is already consumed by the character. - let kbd_state = get_kbd_state(); - let mod_state = WindowsModifiers::active_modifiers(&kbd_state); - - let (_, layout) = layouts.get_current_layout(); - let ctrl_on = if layout.has_alt_graph { - let alt_on = mod_state.contains(WindowsModifiers::ALT); - !alt_on && mod_state.contains(WindowsModifiers::CONTROL) - } else { - mod_state.contains(WindowsModifiers::CONTROL) - }; - - // If Ctrl is not pressed, just use the text with all - // modifiers because that already consumed the dead key. Otherwise, - // we would interpret the character incorrectly, missing the dead key. - if !ctrl_on { - event_info.text = PartialText::System(event_info.utf16parts.clone()); - } else { - let mod_no_ctrl = mod_state.remove_only_ctrl(); - let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; - let vkey = event_info.vkey; - let scancode = event_info.scancode; - let keycode = event_info.code; - let key = layout.get_key(mod_no_ctrl, num_lock_on, vkey, scancode, keycode); - event_info.text = PartialText::Text(key.to_text()); - } - let ev = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event: ev, - is_synthetic: false, - }]; - } - } - win32wm::WM_KEYUP | win32wm::WM_SYSKEYUP => { - if msg_kind == win32wm::WM_SYSKEYUP { - *result = ProcResult::DefSubclassProc; - } else { - *result = ProcResult::Value(LRESULT(0)); - } - - let mut layouts = LAYOUT_CACHE.lock(); - let event_info = - PartialKeyEventInfo::from_message(wparam, lparam, ElementState::Released, &mut layouts); - let mut next_msg = MaybeUninit::uninit(); - let peek_retval = unsafe { - PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ) - }; - let has_next_key_message = peek_retval.as_bool(); - let mut valid_event_info = Some(event_info); - if has_next_key_message { - let next_msg = unsafe { next_msg.assume_init() }; - let (_, layout) = layouts.get_current_layout(); - let is_fake = { - let event_info = valid_event_info.as_ref().unwrap(); - is_current_fake(event_info, next_msg, layout) - }; - if is_fake { - valid_event_info = None; - } - } - if let Some(event_info) = valid_event_info { - let event = event_info.finalize(&mut layouts.strings); - return vec![MessageAsKeyEvent { - event, - is_synthetic: false, - }]; - } - } - _ => (), - } - - Vec::new() - } - - fn synthesize_kbd_state( - &mut self, - key_state: ElementState, - kbd_state: &[u8; 256], - ) -> Vec { - let mut key_events = Vec::new(); - - let mut layouts = LAYOUT_CACHE.lock(); - let (locale_id, _) = layouts.get_current_layout(); - - let is_key_pressed = |vk: VIRTUAL_KEY| &kbd_state[usize::from(vk.0)] & 0x80 != 0; - - // Is caps-lock active? Note that this is different from caps-lock - // being held down. - let caps_lock_on = kbd_state[usize::from(VK_CAPITAL.0)] & 1 != 0; - let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; - - // We are synthesizing the press event for caps-lock first for the following reasons: - // 1. If caps-lock is *not* held down but *is* active, then we have to - // synthesize all printable keys, respecting the caps-lock state. - // 2. If caps-lock is held down, we could choose to sythesize its - // keypress after every other key, in which case all other keys *must* - // be sythesized as if the caps-lock state was be the opposite - // of what it currently is. - // -- - // For the sake of simplicity we are choosing to always sythesize - // caps-lock first, and always use the current caps-lock state - // to determine the produced text - if is_key_pressed(VK_CAPITAL) { - let event = self.create_synthetic( - VK_CAPITAL, - key_state, - caps_lock_on, - num_lock_on, - locale_id, - &mut layouts, - ); - if let Some(event) = event { - key_events.push(event); - } - } - let do_non_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { - for vk in 0..256 { - let vk = VIRTUAL_KEY(vk); - match vk { - win32km::VK_CONTROL - | win32km::VK_LCONTROL - | win32km::VK_RCONTROL - | win32km::VK_SHIFT - | win32km::VK_LSHIFT - | win32km::VK_RSHIFT - | win32km::VK_MENU - | win32km::VK_LMENU - | win32km::VK_RMENU - | win32km::VK_CAPITAL => continue, - _ => (), - } - if !is_key_pressed(vk) { - continue; - } - let event = self.create_synthetic( - vk, - key_state, - caps_lock_on, - num_lock_on, - locale_id as HKL, - layouts, - ); - if let Some(event) = event { - key_events.push(event); - } - } - }; - let do_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { - const CLEAR_MODIFIER_VKS: [VIRTUAL_KEY; 6] = [ - VK_LCONTROL, - VK_LSHIFT, - VK_LMENU, - VK_RCONTROL, - VK_RSHIFT, - VK_RMENU, - ]; - for vk in CLEAR_MODIFIER_VKS.iter() { - if is_key_pressed(*vk) { - let event = self.create_synthetic( - *vk, - key_state, - caps_lock_on, - num_lock_on, - locale_id as HKL, - layouts, - ); - if let Some(event) = event { - key_events.push(event); - } - } - } - }; - - // Be cheeky and sequence modifier and non-modifier - // key events such that non-modifier keys are not affected - // by modifiers (except for caps-lock) - match key_state { - ElementState::Pressed => { - do_non_modifier(&mut key_events, &mut layouts); - do_modifier(&mut key_events, &mut layouts); - } - ElementState::Released => { - do_modifier(&mut key_events, &mut layouts); - do_non_modifier(&mut key_events, &mut layouts); - } - } - - key_events - } - - fn create_synthetic( - &self, - vk: VIRTUAL_KEY, - key_state: ElementState, - caps_lock_on: bool, - num_lock_on: bool, - locale_id: HKL, - layouts: &mut MutexGuard<'_, LayoutCache>, - ) -> Option { - let scancode = - unsafe { MapVirtualKeyExW(u32::from(vk.0), MAPVK_VK_TO_VSC_EX, Some(locale_id)) }; - if scancode == 0 { - return None; - } - let scancode = scancode as ExScancode; - let code = KeyCode::from_scancode(scancode as u32); - let mods = if caps_lock_on { - WindowsModifiers::CAPS_LOCK - } else { - WindowsModifiers::empty() - }; - let layout = layouts.layouts.get(&(locale_id.0 as _)).unwrap(); - let logical_key = layout.get_key(mods, num_lock_on, vk, scancode, code); - let key_without_modifiers = - layout.get_key(WindowsModifiers::empty(), false, vk, scancode, code); - let text = if key_state == ElementState::Pressed { - logical_key.to_text() - } else { - None - }; - let event_info = PartialKeyEventInfo { - vkey: vk, - logical_key: PartialLogicalKey::This(logical_key.clone()), - key_without_modifiers, - key_state, - scancode, - is_repeat: false, - code, - location: get_location(scancode, locale_id), - utf16parts: Vec::with_capacity(8), - text: PartialText::Text(text), - }; - - let mut event = event_info.finalize(&mut layouts.strings); - event.logical_key = logical_key; - event.platform_specific.text_with_all_modifiers = text; - Some(MessageAsKeyEvent { - event, - is_synthetic: true, - }) - } -} - -enum PartialText { - // Unicode - System(Vec), - Text(Option<&'static str>), -} - -enum PartialLogicalKey { - /// Use the text provided by the WM_CHAR messages and report that as a `Character` variant. If - /// the text consists of multiple grapheme clusters (user-precieved characters) that means that - /// dead key could not be combined with the second input, and in that case we should fall back - /// to using what would have without a dead-key input. - TextOr(Key<'static>), - - /// Use the value directly provided by this variant - This(Key<'static>), -} - -struct PartialKeyEventInfo { - vkey: VIRTUAL_KEY, - scancode: ExScancode, - key_state: ElementState, - is_repeat: bool, - code: KeyCode, - location: KeyLocation, - logical_key: PartialLogicalKey, - - key_without_modifiers: Key<'static>, - - /// The UTF-16 code units of the text that was produced by the keypress event. - /// This take all modifiers into account. Including CTRL - utf16parts: Vec, - - text: PartialText, -} - -impl PartialKeyEventInfo { - fn from_message( - wparam: WPARAM, - lparam: LPARAM, - state: ElementState, - layouts: &mut MutexGuard<'_, LayoutCache>, - ) -> Self { - const NO_MODS: WindowsModifiers = WindowsModifiers::empty(); - - let (_, layout) = layouts.get_current_layout(); - let lparam_struct = destructure_key_lparam(lparam); - let vkey = VIRTUAL_KEY(wparam.0 as u16); - let scancode = if lparam_struct.scancode == 0 { - // In some cases (often with media keys) the device reports a scancode of 0 but a - // valid virtual key. In these cases we obtain the scancode from the virtual key. - unsafe { - MapVirtualKeyExW( - u32::from(vkey.0), - MAPVK_VK_TO_VSC_EX, - Some(HKL(layout.hkl as _)), - ) as u16 - } - } else { - new_ex_scancode(lparam_struct.scancode, lparam_struct.extended) - }; - let code = KeyCode::from_scancode(scancode as u32); - let location = get_location(scancode, HKL(layout.hkl as _)); - - let kbd_state = get_kbd_state(); - let mods = WindowsModifiers::active_modifiers(&kbd_state); - let mods_without_ctrl = mods.remove_only_ctrl(); - let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; - - // On Windows Ctrl+NumLock = Pause (and apparently Ctrl+Pause -> NumLock). In these cases - // the KeyCode still stores the real key, so in the name of consistency across platforms, we - // circumvent this mapping and force the key values to match the keycode. - // For more on this, read the article by Raymond Chen, titled: - // "Why does Ctrl+ScrollLock cancel dialogs?" - // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 - let code_as_key = if mods.contains(WindowsModifiers::CONTROL) { - match code { - KeyCode::NumLock => Some(Key::NumLock), - KeyCode::Pause => Some(Key::Pause), - _ => None, - } - } else { - None - }; - - let preliminary_logical_key = - layout.get_key(mods_without_ctrl, num_lock_on, vkey, scancode, code); - let key_is_char = matches!(preliminary_logical_key, Key::Character(_)); - let is_pressed = state == ElementState::Pressed; - - let logical_key = if let Some(key) = code_as_key.clone() { - PartialLogicalKey::This(key) - } else if is_pressed && key_is_char && !mods.contains(WindowsModifiers::CONTROL) { - // In some cases we want to use the UNICHAR text for logical_key in order to allow - // dead keys to have an effect on the character reported by `logical_key`. - PartialLogicalKey::TextOr(preliminary_logical_key) - } else { - PartialLogicalKey::This(preliminary_logical_key) - }; - let key_without_modifiers = if let Some(key) = code_as_key { - key - } else { - match layout.get_key(NO_MODS, false, vkey, scancode, code) { - // We convert dead keys into their character. - // The reason for this is that `key_without_modifiers` is designed for key-bindings, - // but the US International layout treats `'` (apostrophe) as a dead key and the - // reguar US layout treats it a character. In order for a single binding - // configuration to work with both layouts, we forward each dead key as a character. - Key::Dead(k) => { - if let Some(ch) = k { - // I'm avoiding the heap allocation. I don't want to talk about it :( - let mut utf8 = [0; 4]; - let s = ch.encode_utf8(&mut utf8); - let static_str = get_or_insert_str(&mut layouts.strings, s); - Key::Character(static_str) - } else { - Key::Unidentified(NativeKeyCode::Unidentified) - } - } - key => key, - } - }; - - PartialKeyEventInfo { - vkey, - scancode, - key_state: state, - logical_key, - key_without_modifiers, - is_repeat: lparam_struct.is_repeat, - code, - location, - utf16parts: Vec::with_capacity(8), - text: PartialText::System(Vec::new()), - } - } - - fn finalize(self, strings: &mut HashSet<&'static str>) -> KeyEvent { - let mut char_with_all_modifiers = None; - if !self.utf16parts.is_empty() { - let os_string = OsString::from_wide(&self.utf16parts); - if let Ok(string) = os_string.into_string() { - let static_str = get_or_insert_str(strings, string); - char_with_all_modifiers = Some(static_str); - } - } - - // The text without Ctrl - let mut text = None; - match self.text { - PartialText::System(wide) => { - if !wide.is_empty() { - let os_string = OsString::from_wide(&wide); - if let Ok(string) = os_string.into_string() { - let static_str = get_or_insert_str(strings, string); - text = Some(static_str); - } - } - } - PartialText::Text(s) => { - text = s; - } - } - - let logical_key = match self.logical_key { - PartialLogicalKey::TextOr(fallback) => match text { - Some(s) => { - if s.grapheme_indices(true).count() > 1 { - fallback - } else { - Key::Character(s) - } - } - None => Key::Unidentified(NativeKeyCode::Windows(self.scancode)), - }, - PartialLogicalKey::This(v) => v, - }; - - KeyEvent { - physical_key: self.code, - logical_key, - text, - location: self.location, - state: self.key_state, - repeat: self.is_repeat, - platform_specific: KeyEventExtra { - text_with_all_modifiers: char_with_all_modifiers, - key_without_modifiers: self.key_without_modifiers, - }, - } - } -} - -#[derive(Debug, Copy, Clone)] -struct KeyLParam { - pub scancode: u8, - pub extended: bool, - - /// This is `previous_state XOR transition_state`. See the lParam for WM_KEYDOWN and WM_KEYUP for further details. - pub is_repeat: bool, -} - -fn destructure_key_lparam(lparam: LPARAM) -> KeyLParam { - let previous_state = (lparam.0 >> 30) & 0x01; - let transition_state = (lparam.0 >> 31) & 0x01; - KeyLParam { - scancode: ((lparam.0 >> 16) & 0xFF) as u8, - extended: ((lparam.0 >> 24) & 0x01) != 0, - is_repeat: (previous_state ^ transition_state) != 0, - } -} - -#[inline] -fn new_ex_scancode(scancode: u8, extended: bool) -> ExScancode { - (scancode as u16) | (if extended { 0xE000 } else { 0 }) -} - -#[inline] -fn ex_scancode_from_lparam(lparam: LPARAM) -> ExScancode { - let lparam = destructure_key_lparam(lparam); - new_ex_scancode(lparam.scancode, lparam.extended) -} - -/// Gets the keyboard state as reported by messages that have been removed from the event queue. -/// See also: get_async_kbd_state -fn get_kbd_state() -> [u8; 256] { - unsafe { - let mut kbd_state: MaybeUninit<[u8; 256]> = MaybeUninit::uninit(); - let _ = GetKeyboardState(&mut *kbd_state.as_mut_ptr()); - kbd_state.assume_init() - } -} - -/// Gets the current keyboard state regardless of whether the corresponding keyboard events have -/// been removed from the event queue. See also: get_kbd_state -fn get_async_kbd_state() -> [u8; 256] { - unsafe { - let mut kbd_state: [u8; 256] = [0; 256]; - for (vk, state) in kbd_state.iter_mut().enumerate() { - let vk = VIRTUAL_KEY(vk as u16); - let async_state = GetAsyncKeyState(i32::from(vk.0)); - let is_down = (async_state & (1 << 15)) != 0; - if is_down { - *state = 0x80; - } - - if matches!( - vk, - win32km::VK_CAPITAL | win32km::VK_NUMLOCK | win32km::VK_SCROLL - ) { - // Toggle states aren't reported by `GetAsyncKeyState` - let toggle_state = GetKeyState(i32::from(vk.0)); - let is_active = (toggle_state & 1) != 0; - *state |= if is_active { 1 } else { 0 }; - } - } - kbd_state - } -} - -/// On windows, AltGr == Ctrl + Alt -/// -/// Due to this equivalence, the system generates a fake Ctrl key-press (and key-release) preceeding -/// every AltGr key-press (and key-release). We check if the current event is a Ctrl event and if -/// the next event is a right Alt (AltGr) event. If this is the case, the current event must be the -/// fake Ctrl event. -fn is_current_fake(curr_info: &PartialKeyEventInfo, next_msg: MSG, layout: &Layout) -> bool { - let curr_is_ctrl = matches!(curr_info.logical_key, PartialLogicalKey::This(Key::Control)); - if layout.has_alt_graph { - let next_code = ex_scancode_from_lparam(next_msg.lParam); - let next_is_altgr = next_code == 0xE038; // 0xE038 is right alt - if curr_is_ctrl && next_is_altgr { - return true; - } - } - false -} - -fn get_location(scancode: ExScancode, hkl: HKL) -> KeyLocation { - const VK_ABNT_C2: VIRTUAL_KEY = win32km::VK_ABNT_C2; - - let extension = 0xE000; - let extended = (scancode & extension) == extension; - let vkey = - VIRTUAL_KEY(unsafe { MapVirtualKeyExW(scancode as u32, MAPVK_VSC_TO_VK_EX, Some(hkl)) } as u16); - - // Use the native VKEY and the extended flag to cover most cases - // This is taken from the `druid` GUI library, specifically - // druid-shell/src/platform/windows/keyboard.rs - match vkey { - win32km::VK_LSHIFT | win32km::VK_LCONTROL | win32km::VK_LMENU | win32km::VK_LWIN => { - KeyLocation::Left - } - win32km::VK_RSHIFT | win32km::VK_RCONTROL | win32km::VK_RMENU | win32km::VK_RWIN => { - KeyLocation::Right - } - win32km::VK_RETURN if extended => KeyLocation::Numpad, - win32km::VK_INSERT - | win32km::VK_DELETE - | win32km::VK_END - | win32km::VK_DOWN - | win32km::VK_NEXT - | win32km::VK_LEFT - | win32km::VK_CLEAR - | win32km::VK_RIGHT - | win32km::VK_HOME - | win32km::VK_UP - | win32km::VK_PRIOR => { - if extended { - KeyLocation::Standard - } else { - KeyLocation::Numpad - } - } - win32km::VK_NUMPAD0 - | win32km::VK_NUMPAD1 - | win32km::VK_NUMPAD2 - | win32km::VK_NUMPAD3 - | win32km::VK_NUMPAD4 - | win32km::VK_NUMPAD5 - | win32km::VK_NUMPAD6 - | win32km::VK_NUMPAD7 - | win32km::VK_NUMPAD8 - | win32km::VK_NUMPAD9 - | win32km::VK_DECIMAL - | win32km::VK_DIVIDE - | win32km::VK_MULTIPLY - | win32km::VK_SUBTRACT - | win32km::VK_ADD - | VK_ABNT_C2 => KeyLocation::Numpad, - _ => KeyLocation::Standard, - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/minimal_ime.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/minimal_ime.rs deleted file mode 100644 index 0e0f1d218..000000000 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/minimal_ime.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::mem::MaybeUninit; - -use windows::Win32::{ - Foundation::{HWND, LPARAM, LRESULT, WPARAM}, - UI::WindowsAndMessaging::{self as win32wm, *}, -}; - -use crate::platform_impl::platform::event_loop::ProcResult; - -pub fn is_msg_ime_related(msg_kind: u32) -> bool { - matches!( - msg_kind, - win32wm::WM_IME_COMPOSITION - | win32wm::WM_IME_COMPOSITIONFULL - | win32wm::WM_IME_STARTCOMPOSITION - | win32wm::WM_IME_ENDCOMPOSITION - | win32wm::WM_IME_CHAR - | win32wm::WM_CHAR - | win32wm::WM_SYSCHAR - ) -} - -pub struct MinimalIme { - // True if we're currently receiving messages belonging to a finished IME session. - getting_ime_text: bool, - - utf16parts: Vec, -} -impl Default for MinimalIme { - fn default() -> Self { - MinimalIme { - getting_ime_text: false, - utf16parts: Vec::with_capacity(16), - } - } -} -impl MinimalIme { - pub(crate) fn process_message( - &mut self, - hwnd: HWND, - msg_kind: u32, - wparam: WPARAM, - _lparam: LPARAM, - result: &mut ProcResult, - ) -> Option { - match msg_kind { - win32wm::WM_IME_ENDCOMPOSITION => { - self.getting_ime_text = true; - } - win32wm::WM_CHAR | win32wm::WM_SYSCHAR => { - *result = ProcResult::Value(LRESULT(0)); - if self.getting_ime_text { - self.utf16parts.push(wparam.0 as u16); - - let more_char_coming; - unsafe { - let mut next_msg = MaybeUninit::uninit(); - let has_message = PeekMessageW( - next_msg.as_mut_ptr(), - Some(hwnd), - WM_KEYFIRST, - WM_KEYLAST, - PM_NOREMOVE, - ); - let has_message = has_message.as_bool(); - if !has_message { - more_char_coming = false; - } else { - let next_msg = next_msg.assume_init().message; - more_char_coming = next_msg == WM_CHAR || next_msg == WM_SYSCHAR; - } - } - if !more_char_coming { - let result = String::from_utf16(&self.utf16parts).ok(); - self.utf16parts.clear(); - self.getting_ime_text = false; - return result; - } - } else { - return String::from_utf16(&[wparam.0 as u16]).ok(); - } - } - _ => (), - } - - None - } -} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml similarity index 96% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml index 3f0674d4e..e894d86b8 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml @@ -11,9 +11,9 @@ [package] edition = "2021" -rust-version = "1.74" +rust-version = "1.85" name = "tao" -version = "0.35.2" +version = "0.37.1" authors = [ "Tauri Programme within The Commons Conservancy", "The winit contributors", @@ -79,6 +79,10 @@ x11 = [ name = "tao" path = "src/lib.rs" +[[example]] +name = "background_color" +path = "examples/background_color.rs" + [[example]] name = "control_flow" path = "examples/control_flow.rs" @@ -214,9 +218,6 @@ version = "0.2" [dependencies.log] version = "0.4" -[dependencies.once_cell] -version = "1" - [dependencies.rwh_04] version = "0.4" optional = true @@ -284,6 +285,9 @@ version = "0.21" [target.'cfg(target_os = "android")'.dependencies.ndk] version = "0.9" +[target.'cfg(target_os = "android")'.dependencies.ndk-context] +version = "0.1" + [target.'cfg(target_os = "android")'.dependencies.ndk-sys] version = "0.6" @@ -291,7 +295,7 @@ version = "0.6" version = "2.3" [target.'cfg(target_os = "android")'.dependencies.tao-macros] -version = "0.1.0" +version = "0.1.4" [target.'cfg(target_os = "ios")'.dependencies.objc2-foundation] version = "0.3" @@ -305,6 +309,8 @@ features = [ "UIScene", "UISceneOptions", "UIOpenURLContext", + "UISceneWindowingControlStyle", + "UIWindowScene", ] [target.'cfg(target_os = "macos")'.dependencies.core-foundation] @@ -365,7 +371,7 @@ version = "0.12" version = "1.11" [target.'cfg(target_os = "windows")'.dependencies.windows] -version = "0.61" +version = "0.62" features = [ "Win32_Devices_HumanInterfaceDevice", "Win32_Foundation", @@ -397,7 +403,7 @@ features = [ ] [target.'cfg(target_os = "windows")'.dependencies.windows-core] -version = "0.61" +version = "0.62" [target.'cfg(target_os = "windows")'.dependencies.windows-version] version = "0.1" diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml.orig b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml.orig similarity index 95% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml.orig rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml.orig index 75c8c138b..5aa24dfdb 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/Cargo.toml.orig +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/Cargo.toml.orig @@ -1,13 +1,13 @@ [package] name = "tao" -version = "0.35.2" +version = "0.37.1" description = "Cross-platform window manager library." authors = [ "Tauri Programme within The Commons Conservancy", "The winit contributors", ] edition = "2021" -rust-version = "1.74" +rust-version = "1.85" keywords = ["windowing"] license = "Apache-2.0" readme = "README.md" @@ -42,7 +42,6 @@ members = ["tao-macros"] [dependencies] libc = "0.2" log = "0.4" -once_cell = "1" serde = { version = "1", optional = true, features = ["serde_derive"] } rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true } rwh_05 = { package = "raw-window-handle", version = "0.5", features = [ @@ -67,10 +66,10 @@ softbuffer = "0.4" parking_lot = "0.12" unicode-segmentation = "1.11" windows-version = "0.1" -windows-core = "0.61" +windows-core = "0.62" [target."cfg(target_os = \"windows\")".dependencies.windows] -version = "0.61" +version = "0.62" features = [ "Win32_Devices_HumanInterfaceDevice", "Win32_Foundation", @@ -105,8 +104,9 @@ features = [ jni = "0.21" ndk = "0.9" ndk-sys = "0.6" +ndk-context = "0.1" percent-encoding = "2.3" -tao-macros = { version = "0.1.0", path = "./tao-macros" } +tao-macros = { version = "0.1.4", path = "./tao-macros" } [target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies] objc2 = "0.6" @@ -157,6 +157,8 @@ objc2-ui-kit = { version = "0.3", features = [ "UIScene", "UISceneOptions", "UIOpenURLContext", + "UISceneWindowingControlStyle", + "UIWindowScene", ] } objc2-foundation = { version = "0.3", default-features = false, features = [ "NSProcessInfo", diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE.spdx b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE.spdx similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/LICENSE.spdx rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/LICENSE.spdx diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/README.md b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/README.md similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/README.md rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/README.md diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/background_color.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/background_color.rs new file mode 100644 index 000000000..9dff72f93 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/background_color.rs @@ -0,0 +1,49 @@ +// Copyright 2014-2021 The winit contributors +// Copyright 2021-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use dpi::LogicalSize; +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::KeyCode, + window::WindowBuilder, +}; + +fn main() { + let event_loop = EventLoop::new(); + + let mut background_color = (100, 100, 100, 255); + + let window = WindowBuilder::new() + .with_title("Hit space to change background color!") + .with_inner_size(LogicalSize::new(400.0, 300.0)) + .with_background_color(background_color) + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { + event: + KeyEvent { + physical_key: KeyCode::Space, + state: ElementState::Released, + .. + }, + .. + } => { + background_color.1 = background_color.1.wrapping_add(20); + println!("Setting background color to: {background_color:?}"); + window.set_background_color(Some(background_color)); + } + _ => (), + }, + _ => (), + } + }); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/control_flow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/control_flow.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/control_flow.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/control_flow.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor_grab.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor_grab.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/cursor_grab.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/cursor_grab.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/custom_events.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/custom_events.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/custom_events.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/custom_events.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/decorations.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/decorations.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/decorations.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/decorations.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/drag_window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/drag_window.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/drag_window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/drag_window.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/fullscreen.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/fullscreen.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/fullscreen.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/fullscreen.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/handling_close.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/handling_close.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/handling_close.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/handling_close.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/min_max_size.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/min_max_size.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/min_max_size.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/min_max_size.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/minimize.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/minimize.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/minimize.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/minimize.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/monitor_list.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/monitor_list.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/monitor_list.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/monitor_list.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/mouse_wheel.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/mouse_wheel.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/mouse_wheel.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/mouse_wheel.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multithreaded.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multithreaded.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multithreaded.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multithreaded.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multiwindow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multiwindow.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/multiwindow.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/multiwindow.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/overlay.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/overlay.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/overlay.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/overlay.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/parentwindow.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/parentwindow.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/parentwindow.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/parentwindow.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/progress_bar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/progress_bar.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/progress_bar.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/progress_bar.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/reopen_event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/reopen_event.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/reopen_event.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/reopen_event.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw_threaded.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw_threaded.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/request_redraw_threaded.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/request_redraw_threaded.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/resizable.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/resizable.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/resizable.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/resizable.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/set_ime_position.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/set_ime_position.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/set_ime_position.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/set_ime_position.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/theme.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/theme.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/theme.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/theme.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/timer.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/timer.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/timer.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/timer.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/transparent.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/transparent.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/transparent.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/transparent.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/video_modes.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/video_modes.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/video_modes.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/video_modes.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_debug.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_debug.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_debug.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_debug.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_icon.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_icon.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_icon.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_run_return.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_run_return.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/examples/window_run_return.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/examples/window_run_return.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/error.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/error.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/error.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/error.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event.rs similarity index 97% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event.rs index d72af30ba..14325dc5e 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event.rs @@ -77,24 +77,6 @@ pub enum Event<'a, T: 'static> { /// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event) UserEvent(T), - /// Emitted when the application has been suspended. - /// - /// ## Platform-specific - /// - /// - **Android**: This is triggered by `onPause` method of the Activity. - /// - **iOS**: This is triggered by `applicationWillResignActive` method of the UIApplicationDelegate. - /// - **Linux / macOS / Windows**: Unsupported. - Suspended, - - /// Emitted when the application has been resumed. - /// - /// ## Platform-specific - /// - /// - **Android**: This is triggered by `onResume` method of the Activity. The first onResume() is ignored to match the iOS implementation, since that is called on activity creation. - /// - **iOS**: This is triggered by `applicationWillEnterForeground` method of the UIApplicationDelegate. - /// - **Linux / macOS / Windows**: Unsupported. - Resumed, - /// Emitted when all of the event loop's input events have been processed and redraw processing /// is about to begin. /// @@ -141,7 +123,7 @@ pub enum Event<'a, T: 'static> { /// Emitted when the event loop is being shut down. /// - /// This is irreversable - if this event is emitted, it is guaranteed to be the last event that + /// This is irreversible - if this event is emitted, it is guaranteed to be the last event that /// gets emitted. You generally want to treat this as an "do on quit" event. LoopDestroyed, @@ -189,8 +171,6 @@ impl Clone for Event<'static, T> { RedrawRequested(wid) => RedrawRequested(*wid), RedrawEventsCleared => RedrawEventsCleared, LoopDestroyed => LoopDestroyed, - Suspended => Suspended, - Resumed => Resumed, Opened { urls } => Opened { urls: urls.clone() }, Reopen { has_visible_windows, @@ -218,8 +198,6 @@ impl<'a, T> Event<'a, T> { RedrawRequested(wid) => Ok(RedrawRequested(wid)), RedrawEventsCleared => Ok(RedrawEventsCleared), LoopDestroyed => Ok(LoopDestroyed), - Suspended => Ok(Suspended), - Resumed => Ok(Resumed), Opened { urls } => Ok(Opened { urls }), Reopen { has_visible_windows, @@ -246,8 +224,6 @@ impl<'a, T> Event<'a, T> { RedrawRequested(wid) => Some(RedrawRequested(wid)), RedrawEventsCleared => Some(RedrawEventsCleared), LoopDestroyed => Some(LoopDestroyed), - Suspended => Some(Suspended), - Resumed => Some(Resumed), Opened { urls } => Some(Opened { urls }), Reopen { has_visible_windows, @@ -300,7 +276,7 @@ pub enum WindowEvent<'a> { /// /// ## Platform-specific /// - /// - **Linux(Wayland)**: will always be (0, 0) since Wayland doesn't support a global cordinate system. + /// - **Linux(Wayland)**: will always be (0, 0) since Wayland doesn't support a global coordinate system. Moved(PhysicalPosition), /// The window has been requested to close. @@ -314,6 +290,40 @@ pub enum WindowEvent<'a> { /// - **macOS:** Fired if the [`crate::window::Window`] is dropped or the dock `Quit` item is clicked. Destroyed, + /// The window has been started. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onStart` method of the Activity. + /// - **Linux / macOS / iOS / Windows**: Unsupported. + Started, + + /// The window has been suspended. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onPause` method of the Activity. + /// - **iOS**: This is triggered by `applicationWillResignActive` method of the UIApplicationDelegate. + /// - **Linux / macOS / Windows**: Unsupported. + Suspended, + + /// The window has been resumed. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onResume` method of the Activity. The first onResume() is ignored to match the iOS implementation, since that is called on activity creation. + /// - **iOS**: This is triggered by `applicationWillEnterForeground` method of the UIApplicationDelegate. + /// - **Linux / macOS / Windows**: Unsupported. + Resumed, + + /// The window has been stopped. + /// + /// ## Platform-specific + /// + /// - **Android**: This is triggered by `onStop` method of the Activity. + /// - **Linux / macOS / iOS / Windows**: Unsupported. + Stopped, + /// A file has been dropped into the window. /// /// When the user drops multiple files at once, this event will be emitted for each file @@ -467,6 +477,10 @@ impl Clone for WindowEvent<'static> { Moved(pos) => Moved(*pos), CloseRequested => CloseRequested, Destroyed => Destroyed, + Started => Started, + Suspended => Suspended, + Resumed => Resumed, + Stopped => Stopped, DroppedFile(file) => DroppedFile(file.clone()), HoveredFile(file) => HoveredFile(file.clone()), HoveredFileCancelled => HoveredFileCancelled, @@ -559,6 +573,10 @@ impl<'a> WindowEvent<'a> { Moved(position) => Some(Moved(position)), CloseRequested => Some(CloseRequested), Destroyed => Some(Destroyed), + Started => Some(Started), + Suspended => Some(Suspended), + Resumed => Some(Resumed), + Stopped => Some(Stopped), DroppedFile(file) => Some(DroppedFile(file)), HoveredFile(file) => Some(HoveredFile(file)), HoveredFileCancelled => Some(HoveredFileCancelled), diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event_loop.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event_loop.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event_loop.rs index 6e3406d45..eb73dbbba 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/event_loop.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/event_loop.rs @@ -403,7 +403,7 @@ impl fmt::Display for EventLoopClosed { impl error::Error for EventLoopClosed {} -/// Fiter controlling the propagation of device events. +/// Filter controlling the propagation of device events. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] pub enum DeviceEventFilter { /// Always filter out device events. diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/icon.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/icon.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/icon.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/keyboard.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/keyboard.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/keyboard.rs index ac50d42c5..8628f0f67 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/keyboard.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/keyboard.rs @@ -202,7 +202,7 @@ pub enum NativeKeyCode { /// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and /// "SuperRight" here. /// - The key that the specification calls "Super" is reported as `Unidentified` here. -/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`. +/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`. /// /// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables #[non_exhaustive] @@ -837,8 +837,8 @@ impl fmt::Display for KeyCode { /// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's /// another key which the specification calls `Super`. That does not exist here.) /// - The `Space` variant here, can be identified by the character it generates in the -/// specificaiton. -/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`. +/// specification. +/// - The `Unidentified` variant here, can still identify a key through it's `NativeKeyCode`. /// - The `Dead` variant here, can specify the character which is inserted when pressing the /// dead-key twice. /// diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/lib.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/lib.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/lib.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/lib.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/monitor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/monitor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/monitor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/android.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/android.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/android.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/android.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/ios.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/ios.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/ios.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/ios.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/linux.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/linux.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/linux.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/linux.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/macos.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/macos.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/macos.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/macos.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/mod.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/mod.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/run_return.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/run_return.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/run_return.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/run_return.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/unix.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/unix.rs similarity index 98% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/unix.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/unix.rs index c96fb9361..c25b89fc8 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/unix.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/unix.rs @@ -123,7 +123,7 @@ pub trait WindowBuilderExtUnix { /// Whether to enable or disable the internal draw for transparent window. /// - /// When tranparent attribute is enabled, we will call `connect_draw` and draw a transparent background. + /// When transparent attribute is enabled, we will call `connect_draw` and draw a transparent background. /// For anyone who wants to draw the background themselves, set this to `false`. /// Default is `true`. fn with_transparent_draw(self, draw: bool) -> WindowBuilder; @@ -138,7 +138,7 @@ pub trait WindowBuilderExtUnix { /// Default is `false` but is always `true` if [`WindowAttributes::transparent`](crate::window::WindowAttributes::transparent) is `true` fn with_rgba_visual(self, rgba_visual: bool) -> WindowBuilder; - /// Wether to set this window as app paintable + /// Whether to set this window as app paintable /// /// /// diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/windows.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/windows.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform/windows.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform/windows.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/mod.rs similarity index 91% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/mod.rs index b78113853..5acb4d7ff 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/mod.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/mod.rs @@ -15,12 +15,11 @@ use ndk::{ configuration::Configuration, looper::{ForeignLooper, Poll, ThreadLooper}, }; -use once_cell::sync::Lazy; use std::{ - collections::VecDeque, + collections::{HashSet, VecDeque}, error::Error, fmt, - sync::RwLock, + sync::{LazyLock, RwLock}, time::{Duration, Instant}, }; @@ -123,7 +122,8 @@ macro_rules! jni_call_method { pub mod ndk_glue; use ndk_glue::{ActivityId, Event, Rect, WindowEvent}; -static CONFIG: Lazy> = Lazy::new(|| RwLock::new(Configuration::new())); +static CONFIG: LazyLock> = + LazyLock::new(|| RwLock::new(Configuration::new())); #[derive(Debug)] pub enum OsError { @@ -170,22 +170,12 @@ pub struct EventLoop { first_event: Option, start_cause: event::StartCause, looper: ThreadLooper, - running: bool, + running: HashSet, } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PlatformSpecificEventLoopAttributes {} -macro_rules! call_event_handler { - ( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{ - if let ControlFlow::ExitWithCode(code) = $cf { - $event_handler($event, $window_target, &mut ControlFlow::ExitWithCode(code)); - } else { - $event_handler($event, $window_target, &mut $cf); - } - }}; -} - impl EventLoop { pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self { let (sender, receiver) = crossbeam_channel::unbounded(); @@ -202,7 +192,7 @@ impl EventLoop { first_event: None, start_cause: event::StartCause::Init, looper: ThreadLooper::for_thread().unwrap(), - running: false, + running: HashSet::new(), } } @@ -219,14 +209,14 @@ impl EventLoop { where F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), { - let mut control_flow = ControlFlow::default(); + let event_handler = &mut event_handler; + let control_flow = &mut ControlFlow::default(); - 'event_loop: loop { - call_event_handler!( + loop { + self.call_event_handler( event_handler, - self.window_target(), control_flow, - event::Event::NewEvents(self.start_cause) + event::Event::NewEvents(self.start_cause), ); let mut redraw_window_id = None; @@ -234,12 +224,14 @@ impl EventLoop { match self.first_event.take() { Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() { - Event::Resume => { - call_event_handler!( + Event::Resume { id: window_id } => { + self.call_event_handler( event_handler, - self.window_target(), control_flow, - event::Event::Resumed + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Resumed, + }, ); } Event::WindowEvent { @@ -248,49 +240,65 @@ impl EventLoop { } => match event { WindowEvent::Resized => resized_window_id = Some(window_id), WindowEvent::RedrawNeeded => redraw_window_id = Some(window_id), + WindowEvent::Started => { + self.running.insert(window_id); + self.call_event_handler( + event_handler, + control_flow, + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Started, + }, + ); + } WindowEvent::Focused(focused) => { - call_event_handler!( + self.call_event_handler( event_handler, - self.window_target(), control_flow, event::Event::WindowEvent { window_id, - event: event::WindowEvent::Focused(focused) - } + event: event::WindowEvent::Focused(focused), + }, + ); + } + WindowEvent::Stopped => { + self.running.remove(&window_id); + self.call_event_handler( + event_handler, + control_flow, + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Stopped, + }, ); } WindowEvent::Destroyed => { - call_event_handler!( + self.running.remove(&window_id); + self.call_event_handler( event_handler, - self.window_target(), control_flow, event::Event::WindowEvent { window_id, event: event::WindowEvent::Destroyed, - } + }, ); } _ => {} }, - Event::Pause => { - call_event_handler!( + Event::Pause { id: window_id } => { + self.call_event_handler( event_handler, - self.window_target(), control_flow, - event::Event::Suspended + event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Suspended, + }, ); } - Event::Stop => self.running = false, - Event::Start => self.running = true, Event::Opened => { let urls = ndk_glue::take_intent_urls(); if !urls.is_empty() { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::Opened { urls } - ); + self.call_event_handler(event_handler, control_flow, event::Event::Opened { urls }); } } //Event::ConfigChanged => { @@ -309,7 +317,7 @@ impl EventLoop { // scale_factor, // }, // }; - // call_event_handler!(event_handler, self.window_target(), control_flow, event); + // self.call_event_handler(event_handler, control_flow, event); // } //} _ => {} @@ -365,9 +373,8 @@ impl EventLoop { force: None, }), }; - call_event_handler!( + self.call_event_handler( event_handler, - self.window_target(), control_flow, event ); @@ -403,7 +410,11 @@ impl EventLoop { is_synthetic: false, }, }; - call_event_handler!(event_handler, self.window_target(), control_flow, event); + self.call_event_handler( + event_handler, + control_flow, + event, + ); } _ => {} }; @@ -413,80 +424,58 @@ impl EventLoop { } */ } - Some(EventSource::User) => { - while let Ok(event) = self.receiver.try_recv() { - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::UserEvent(event) - ); - } - } - None => {} + _ => {} } - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::MainEventsCleared - ); + // We need to treat every event as if they were from [`EventLoopProxy::send_event`] + // + // > All return values may also imply ALOOPER_POLL_WAKE. If you call this in a loop, + // > you must treat all return values as if they also indicated ALOOPER_POLL_WAKE. + // > https://developer.android.com/ndk/reference/group/looper#alooper_pollonce + while let Ok(event) = self.receiver.try_recv() { + self.call_event_handler(event_handler, control_flow, event::Event::UserEvent(event)); + } + + self.call_event_handler(event_handler, control_flow, event::Event::MainEventsCleared); if let Some(window_id) = resized_window_id { - if self.running { + if self.running.contains(&window_id) { let size = MonitorHandle.size(); let event = event::Event::WindowEvent { window_id, event: event::WindowEvent::Resized(size), }; - call_event_handler!(event_handler, self.window_target(), control_flow, event); + self.call_event_handler(event_handler, control_flow, event); } } if let Some(window_id) = redraw_window_id { - if self.running { + if self.running.contains(&window_id) { let event = event::Event::RedrawRequested(window_id); - call_event_handler!(event_handler, self.window_target(), control_flow, event); + self.call_event_handler(event_handler, control_flow, event); } } - call_event_handler!( + self.call_event_handler( event_handler, - self.window_target(), control_flow, - event::Event::RedrawEventsCleared + event::Event::RedrawEventsCleared, ); - match control_flow { + match *control_flow { ControlFlow::ExitWithCode(code) => { - self.first_event = poll( - self - .looper - .poll_once_timeout(Duration::from_millis(0)) - .unwrap(), - ); + self.first_event = poll(self.looper.poll_once_timeout(Duration::ZERO).unwrap()); self.start_cause = event::StartCause::WaitCancelled { start: Instant::now(), requested_resume: None, }; - call_event_handler!( - event_handler, - self.window_target(), - control_flow, - event::Event::LoopDestroyed - ); + self.call_event_handler(event_handler, control_flow, event::Event::LoopDestroyed); - break 'event_loop code; + return code; } ControlFlow::Poll => { - self.first_event = poll( - self - .looper - .poll_all_timeout(Duration::from_millis(0)) - .unwrap(), - ); + self.first_event = poll(self.looper.poll_all_timeout(Duration::ZERO).unwrap()); self.start_cause = event::StartCause::Poll; } ControlFlow::Wait => { @@ -520,6 +509,25 @@ impl EventLoop { } } + fn call_event_handler( + &self, + event_handler: &mut F, + control_flow: &mut ControlFlow, + event: event::Event<'_, T>, + ) where + F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget, &mut ControlFlow), + { + if let ControlFlow::ExitWithCode(code) = *control_flow { + event_handler( + event, + self.window_target(), + &mut ControlFlow::ExitWithCode(code), + ); + } else { + event_handler(event, self.window_target(), control_flow); + } + } + pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget { &self.window_target } @@ -539,7 +547,7 @@ pub struct EventLoopProxy { impl EventLoopProxy { pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed> { - _ = self.queue.try_send(event); + let _ = self.queue.send(event); self.looper.wake(); Ok(()) } @@ -883,7 +891,7 @@ impl Window { if let Some(w) = ndk_glue::activity_window_manager(self.activity_id).as_ref() { handle.a_native_window = w.as_obj().as_raw() as *mut _; } else { - panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events."); + panic!("Cannot get the native window, it's null and will always be null before WindowEvent::Resumed and after WindowEvent::Suspended. Make sure you only call this function between those events."); }; rwh_04::RawWindowHandle::AndroidNdk(handle) } @@ -895,7 +903,7 @@ impl Window { if let Some(w) = ndk_glue::activity_window_manager(self.activity_id).as_ref() { handle.a_native_window = w.as_obj().as_raw() as *mut _; } else { - panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events."); + panic!("Cannot get the native window, it's null and will always be null before WindowEvent::Resumed and after WindowEvent::Suspended. Make sure you only call this function between those events."); }; rwh_05::RawWindowHandle::AndroidNdk(handle) } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/ndk_glue.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs similarity index 79% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/ndk_glue.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs index 396936a9e..2ad139400 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/android/ndk_glue.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/android/ndk_glue.rs @@ -17,7 +17,6 @@ use ndk::{ input_queue::InputQueue, looper::{FdEvent, ForeignLooper, ThreadLooper}, }; -use once_cell::sync::{Lazy, OnceCell}; use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; use std::{ collections::{BTreeMap, HashSet}, @@ -25,17 +24,14 @@ use std::{ fs::File, io::{BufRead, BufReader}, os::unix::prelude::*, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Condvar, Mutex, RwLock, RwLockReadGuard, - }, + sync::{Arc, Condvar, LazyLock, Mutex, OnceLock, RwLock, RwLockReadGuard}, thread, time::Duration, }; /// Android pacakge name that could be used to reference classes /// in the android project. -pub static PACKAGE: OnceCell<&str> = OnceCell::new(); +pub static PACKAGE: OnceLock<&str> = OnceLock::new(); /// Character set for encoding text content in data URLs. /// Encodes all control characters and special characters that might cause issues in URLs. @@ -64,22 +60,23 @@ const DATA_URL_ENCODING_SET: &AsciiSet = &CONTROLS /// 1. android app domain name in reverse snake_case as an ident (for ex: com_example) /// 2. android package anme (for ex: wryapp) /// 3. the android activity that has external linking for the following functions and calls them: -/// - `private external fun onActivityCreate(activity: WryActivity)`` -/// - `private external fun start()` -/// - `private external fun resume()` -/// - `private external fun pause()` -/// - `private external fun stop()` -/// - `private external fun onActivitySaveInstanceState()` -/// - `private external fun onActivityDestroy(activity: WryActivity)` -/// - `private external fun onActivityLowMemory()` +/// - `private external fun onFirstActivityCreate()` +/// - `private external fun onCreate(activity: WryActivity)` +/// - `private external fun onStart(activity: WryActivity)` +/// - `private external fun onResume(activity: WryActivity)` +/// - `private external fun onPause(activity: WryActivity)` +/// - `private external fun onStop(activity: WryActivity)` +/// - `private external fun onDestroy(activity: WryActivity)` /// - `private external fun onWindowFocusChanged(activity: WryActivity, focus: Boolean)` -/// 4. a on_activity_create function that will be ran once after the `onActivityCreate` function above. -/// 5. the main entry point of your android application. +/// - `private external fun onLowMemory()` +/// - `private external fun onNewIntent(intent: Intent)` +/// 4. an on_activity_create function that will be ran once after the `onCreate` function above. +/// 5. the main entry point of your android application that runs in `onFirstActivityCreate` after `onCreate` and `on_activity_create`. #[rustfmt::skip] #[macro_export] macro_rules! android_binding { ($domain:ident, $package:ident, $activity:ident, $on_activity_create:path, $main:ident) => { - ::tao::android_binding!($domain, $package, $activity, $setup, $main, ::tao) + ::tao::android_binding!($domain, $package, $activity, $on_activity_create, $main, ::tao) }; ($domain:ident, $package:ident, $activity:ident, $on_activity_create:path, $main:ident, $tao:path) => {{ // NOTE: be careful when changing how this use statement is written @@ -92,8 +89,8 @@ macro_rules! android_binding { $domain, $package, $activity, - create, - [JObject], + onFirstActivityCreate, + [], __VOID__, [$main], ); @@ -101,20 +98,19 @@ macro_rules! android_binding { $domain, $package, $activity, - onActivityCreate, + onCreate, [JObject], __VOID__, [$on_activity_create], _____tao_store_package_name__, ); - android_fn!($domain, $package, $activity, start, [JObject]); - android_fn!($domain, $package, $activity, stop, [JObject]); - android_fn!($domain, $package, $activity, resume, [JObject]); - android_fn!($domain, $package, $activity, pause, [JObject]); - android_fn!($domain, $package, $activity, onActivitySaveInstanceState, [JObject]); - android_fn!($domain, $package, $activity, onActivityDestroy, [JObject]); - android_fn!($domain, $package, $activity, onActivityLowMemory, [JObject]); - android_fn!($domain, $package, $activity, onWindowFocusChanged, [JObject,i32]); + android_fn!($domain, $package, $activity, onStart, [JObject]); + android_fn!($domain, $package, $activity, onStop, [JObject]); + android_fn!($domain, $package, $activity, onResume, [JObject]); + android_fn!($domain, $package, $activity, onPause, [JObject]); + android_fn!($domain, $package, $activity, onDestroy, [JObject]); + android_fn!($domain, $package, $activity, onWindowFocusChanged, [JObject, i32]); + android_fn!($domain, $package, $activity, onLowMemory, []); android_fn!($domain, $package, $activity, onNewIntent, [JObject]); }}; } @@ -212,16 +208,19 @@ unsafe impl Sync for AndroidContext {} pub type ActivityId = i32; -pub(crate) static CONTEXTS: Lazy>> = - Lazy::new(Default::default); -static WINDOW_MANAGER: Lazy>> = Lazy::new(Default::default); -pub(crate) static ACTIVITY_CREATED_SENDERS: Lazy>>> = - Lazy::new(Default::default); -static INTENT_URLS: Lazy>> = Lazy::new(Default::default); -static INPUT_QUEUE: Lazy>> = Lazy::new(Default::default); -static CONTENT_RECT: Lazy> = Lazy::new(Default::default); -static LOOPER: Lazy>> = Lazy::new(Default::default); -static DID_RESUME: AtomicBool = AtomicBool::new(false); +pub(crate) static CONTEXTS: LazyLock>> = + LazyLock::new(Default::default); +// Keeping a reference so we can store it in ndk-context +static APPLICATION_CONTEXT: OnceLock = OnceLock::new(); +static WINDOW_MANAGER: LazyLock>> = + LazyLock::new(Default::default); +pub(crate) static ACTIVITY_CREATED_SENDERS: LazyLock>>> = + LazyLock::new(Default::default); +static INTENT_URLS: LazyLock>> = LazyLock::new(Default::default); +static INPUT_QUEUE: LazyLock>> = LazyLock::new(Default::default); +static CONTENT_RECT: LazyLock> = LazyLock::new(Default::default); +static LOOPER: LazyLock>> = LazyLock::new(Default::default); +static RESUMED_ACTIVITIES: LazyLock>> = LazyLock::new(Default::default); pub fn main_window_manager() -> Option { WINDOW_MANAGER.lock().unwrap().values().next().cloned() @@ -243,6 +242,14 @@ pub fn content_rect() -> Rect { CONTENT_RECT.read().unwrap().clone() } +fn activity_id(env: &mut JNIEnv<'_>, activity: &JObject<'_>) -> ActivityId { + env + .call_method(activity, "getId", "()I", &[]) + .unwrap() + .i() + .unwrap() +} + pub fn main_android_context() -> Option { CONTEXTS.lock().unwrap().values().next().cloned() } @@ -257,7 +264,7 @@ pub fn next_available_activity() -> Option<(ActivityId, AndroidContext)> { .map(|(id, ctx)| (*id, ctx.clone())) } -pub static PIPE: Lazy<[OwnedFd; 2]> = Lazy::new(|| { +pub static PIPE: LazyLock<[OwnedFd; 2]> = LazyLock::new(|| { let mut pipe: [RawFd; 2] = Default::default(); unsafe { libc::pipe(pipe.as_mut_ptr()) }; pipe.map(|fd| unsafe { OwnedFd::from_raw_fd(fd) }) @@ -266,7 +273,7 @@ pub static PIPE: Lazy<[OwnedFd; 2]> = Lazy::new(|| { pub fn poll_events() -> Option { unsafe { let size = std::mem::size_of::(); - let mut event = Event::Start; + let mut event = Event::LowMemory; if libc::read(PIPE[0].as_raw_fd(), &mut event as *mut _ as *mut _, size) == size as libc::ssize_t { @@ -300,10 +307,8 @@ pub struct Rect { #[derive(Clone, Debug, Eq, PartialEq, Copy)] #[repr(u8)] pub enum Event { - Start, - Resume, - Pause, - Stop, + Resume { id: WindowId }, + Pause { id: WindowId }, LowMemory, WindowEvent { id: WindowId, event: WindowEvent }, ContentRectChanged, @@ -315,12 +320,15 @@ pub enum Event { pub enum WindowEvent { Focused(bool), Created, + Started, Resized, RedrawNeeded, + Stopped, Destroyed, } -pub unsafe fn create(_env: JNIEnv, _: JClass, _: JObject, main: fn()) { +#[allow(non_snake_case)] +pub unsafe fn onFirstActivityCreate(_env: JNIEnv, _: JClass, main: fn()) { let logpipe = { let mut logpipe: [RawFd; 2] = Default::default(); libc::pipe(logpipe.as_mut_ptr()); @@ -379,7 +387,7 @@ pub unsafe fn create(_env: JNIEnv, _: JClass, _: JObject, main: fn()) { } #[allow(non_snake_case)] -pub unsafe fn onActivityCreate( +pub unsafe fn onCreate( mut env: JNIEnv, _jclass: JClass, activity: JObject, @@ -388,6 +396,25 @@ pub unsafe fn onActivityCreate( let intent = jni_call_method!(env, &activity, "getIntent", "()Landroid/content/Intent;", l).ok(); + let vm = env.get_java_vm().unwrap(); + + APPLICATION_CONTEXT.get_or_init(|| { + let application = jni_call_method!( + env, + &activity, + "getApplicationContext", + "()Landroid/content/Context;", + l + ) + .unwrap(); + let application = env.new_global_ref(application).unwrap(); + ndk_context::initialize_android_context( + vm.get_java_vm_pointer() as *mut _, + application.as_obj().as_raw() as *mut _, + ); + application + }); + let activity_id = jni_call_method!(env, &activity, "getId", "()I", i).unwrap(); let activity_name: JString = jni_call_method!( @@ -420,7 +447,6 @@ pub unsafe fn onActivityCreate( .unwrap() .insert(activity_id, window_manager); let activity = env.new_global_ref(activity).unwrap(); - let vm = env.get_java_vm().unwrap(); let thread_env = vm.attach_current_thread_as_daemon().unwrap(); CONTEXTS.lock().unwrap().insert( @@ -448,17 +474,25 @@ pub unsafe fn onActivityCreate( } } -pub unsafe fn resume(_: JNIEnv, _: JClass, _: JObject) { - let did_resume = DID_RESUME.swap(true, Ordering::Relaxed); +#[allow(non_snake_case)] +pub unsafe fn onResume(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + let did_resume = !RESUMED_ACTIVITIES.lock().unwrap().insert(activity_id); // first Activity onResume() is called even after onCreate() // to match the iOS implementation, we ignore the first resume event if did_resume { - wake(Event::Resume); + wake(Event::Resume { + id: WindowId(super::WindowId(activity_id)), + }); } } -pub unsafe fn pause(_: JNIEnv, _: JClass, _: JObject) { - wake(Event::Pause); +#[allow(non_snake_case)] +pub unsafe fn onPause(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + wake(Event::Pause { + id: WindowId(super::WindowId(activity_id)), + }); } #[allow(non_snake_case)] @@ -468,7 +502,11 @@ pub unsafe fn onWindowFocusChanged( activity: JObject, has_focus: libc::c_int, ) { - let activity_id = jni_call_method!(env, &activity, "getId", "()I", i).unwrap(); + let activity_id = env + .call_method(&activity, "getId", "()I", &[]) + .unwrap() + .i() + .unwrap(); let event = Event::WindowEvent { id: WindowId(super::WindowId(activity_id)), event: WindowEvent::Focused(has_focus != 0), @@ -485,8 +523,8 @@ pub unsafe fn handle_intent(mut env: JNIEnv, intent: JObject) { // Maple local patch for https://github.com/tauri-apps/tao/issues/1217. // // Android can provide null intent/action/type objects during cold launch, - // task restoration, and OEM deep-link delivery. The upstream tao 0.35.2 - // handler unwraps these JNI calls, which aborts the process across the Rust + // task restoration, and OEM deep-link delivery. Upstream tao (still in 0.37) + // unwraps these JNI calls, which aborts the process across the Rust // JNI boundary. Treat malformed intent pieces as absent and return/continue. if intent.is_null() { return; @@ -519,13 +557,13 @@ pub unsafe fn handle_intent(mut env: JNIEnv, intent: JObject) { // Get intent type (may be null) let intent_type = jni_call_method!(env, &intent, "getType", "()Ljava/lang/String;", l) .ok() - .filter(|intent_type| !intent_type.is_null()) - .and_then(|jstr| { - let intent_type: JString = jstr.into(); + .filter(|jstr| !jstr.is_null()) + .map(|jstr| jstr.into()) + .and_then(|intent_type: JString| { env .get_string(&intent_type) .ok() - .map(|intent_type| intent_type.to_string_lossy().to_string()) + .map(|s| s.to_string_lossy().to_string()) }); // Handle text/plain intents (EXTRA_TEXT) @@ -700,17 +738,31 @@ pub unsafe fn handle_intent(mut env: JNIEnv, intent: JObject) { } } -pub unsafe fn start(_: JNIEnv, _: JClass, _: JObject) { - wake(Event::Start); +#[allow(non_snake_case)] +pub unsafe fn onStart(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + wake(Event::WindowEvent { + id: WindowId(super::WindowId(activity_id)), + event: WindowEvent::Started, + }); } -pub unsafe fn stop(_: JNIEnv, _: JClass, _: JObject) { - wake(Event::Stop); +#[allow(non_snake_case)] +pub unsafe fn onStop(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = activity_id(&mut env, &activity); + wake(Event::WindowEvent { + id: WindowId(super::WindowId(activity_id)), + event: WindowEvent::Stopped, + }); } #[allow(non_snake_case)] -pub unsafe fn onActivityDestroy(mut env: JNIEnv, _: JClass, activity: JObject) { - let activity_id = jni_call_method!(env, &activity, "getId", "()I", i).unwrap(); +pub unsafe fn onDestroy(mut env: JNIEnv, _: JClass, activity: JObject) { + let activity_id = env + .call_method(&activity, "getId", "()I", &[]) + .unwrap() + .i() + .unwrap(); let is_changing_configurations = jni_call_method!(env, &activity, "isChangingConfigurations", "()Z", z).unwrap(); @@ -724,6 +776,7 @@ pub unsafe fn onActivityDestroy(mut env: JNIEnv, _: JClass, activity: JObject) { }); CONTEXTS.lock().unwrap().remove(&activity_id); WINDOW_MANAGER.lock().unwrap().remove(&activity_id); + RESUMED_ACTIVITIES.lock().unwrap().remove(&activity_id); } } @@ -732,10 +785,7 @@ pub unsafe fn onActivityDestroy(mut env: JNIEnv, _: JClass, activity: JObject) { /////////////////////////////////////////////// #[allow(non_snake_case)] -pub unsafe fn onActivitySaveInstanceState(_: JNIEnv, _: JClass, _: JObject) {} - -#[allow(non_snake_case)] -pub unsafe fn onActivityLowMemory(_: JNIEnv, _: JClass, _: JObject) { +pub unsafe fn onLowMemory(_: JNIEnv, _: JClass) { wake(Event::LowMemory); } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/app_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs similarity index 90% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/app_state.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs index e1c5e2667..a0d08996d 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/app_state.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/app_state.rs @@ -14,7 +14,10 @@ use std::{ }; use objc2::{rc::Retained, runtime::AnyObject, MainThreadMarker, Message}; -use objc2_ui_kit::{UIApplication, UIScene, UISceneConnectionOptions, UIWindowScene}; +use objc2_foundation::{NSSet, NSUserActivity}; +use objc2_ui_kit::{ + UIApplication, UIOpenURLContext, UIScene, UISceneConnectionOptions, UIWindowScene, +}; use crate::{ dpi::LogicalSize, @@ -28,7 +31,7 @@ use crate::{ CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CGRect, CGSize, NSInteger, NSOperatingSystemVersion, NSUInteger, }, - scene::multiple_scenes_enabled, + scene::{self, scene_lifecycle_enabled}, }, window::WindowId as RootWindowId, }; @@ -104,7 +107,14 @@ struct AppState { // Window::new might create a window before a scene is ready for it, // requesting a new scene to be activated and deferring the setWindowScene call to the scene delegate windows_for_next_scenes: Vec, + // Stores the UIWindow instances that were created while the app had no connected scene. + // The system can run an app on the scene lifecycle even when its Info.plist does not + // adopt it, in which case the windows are created before the first scene connects + scene_less_windows: Vec, did_first_scene_connect: bool, + // set once the queued windows were flushed and `StartCause::Init` was emitted, + // either from the application delegate or from the first scene connection + is_app_ready: bool, } impl Drop for AppState { @@ -157,7 +167,9 @@ impl AppState { control_flow: ControlFlow::default(), waker, windows_for_next_scenes: Vec::new(), + scene_less_windows: Vec::new(), did_first_scene_connect: false, + is_app_ready: false, }); } init_guard(&mut guard) @@ -485,6 +497,16 @@ pub unsafe fn unitialized_scene() -> Option> { None } +// the first connected scene that is able to display windows +pub unsafe fn first_window_scene() -> Option> { + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + application + .connectedScenes() + .iter() + .find_map(|scene| scene.downcast_ref::().map(|s| s.retain())) +} + pub unsafe fn scene_by_id(id: &str) -> Option> { let mtm = MainThreadMarker::new().unwrap(); let application = UIApplication::sharedApplication(mtm); @@ -529,6 +551,40 @@ pub unsafe fn connect_scene(scene: &UIScene, options: &UISceneConnectionOptions) options: options.retain(), })); } + + // windows that were created before any scene existed are only ever displayed once + // they join one, so the first scene to connect adopts all of them + let scene_less_windows = mem::take(&mut AppState::get_mut().scene_less_windows); + for window in scene_less_windows { + // the system might have attached the window to a scene on its own already + let assigned_scene: id = msg_send![window, windowScene]; + if assigned_scene.is_null() { + let () = msg_send![window, setWindowScene: window_scene]; + } + let () = msg_send![window, release]; + } + } + + // a URL that launches the app (or opens a new scene) is handed over here, in the connection + // options: scene:openURLContexts: is only called for URLs received by a scene that is already + // connected, so without this they would be lost + // + // UIKit returns nil for both sets when the app is launched without a link, although the objc2 + // bindings declare them non-null: read them as optional, the bindings would panic on nil + let url_contexts: Option>> = msg_send![options, URLContexts]; + let user_activities: Option>> = msg_send![options, userActivities]; + let source = "the scene connection options"; + let mut urls = url_contexts + .map(|contexts| scene::urls_from_contexts(&contexts, source)) + .unwrap_or_default(); + urls.extend( + user_activities + .iter() + .flat_map(|activities| activities.iter()) + .filter_map(|activity| scene::universal_link_url(&activity, source)), + ); + if !urls.is_empty() { + handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls })); } } @@ -538,6 +594,17 @@ pub unsafe fn register_window_for_scene(window: id) { .push(msg_send![window, retain]); } +// retains window +pub unsafe fn register_scene_less_window(window: id) { + AppState::get_mut() + .scene_less_windows + .push(msg_send![window, retain]); +} + +pub unsafe fn did_first_scene_connect() -> bool { + AppState::get_mut().did_first_scene_connect +} + // requires main thread and window is a UIWindow // retains window pub unsafe fn set_key_window(window: id) { @@ -608,13 +675,20 @@ pub unsafe fn will_launch(queued_event_handler: Box) { // requires main thread pub unsafe fn did_finish_launching() { // when app is run in scenes lifecycle mode, we defer the did_finish_launching call to the first scene setup - if !multiple_scenes_enabled() { + if !scene_lifecycle_enabled() { on_app_ready(); } } unsafe fn on_app_ready() { let mut this = AppState::get_mut(); + // an app whose Info.plist does not declare a scene manifest is set up on + // `didFinishLaunchingWithOptions:` but can still be given a scene by the system, + // in which case the scene connection must not run this a second time + if this.is_app_ready { + return; + } + this.is_app_ready = true; let windows = match this.state_mut() { AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows), s => bug!("unexpected state {:?}", s), @@ -875,6 +949,11 @@ pub unsafe fn handle_events_cleared() { AppState::get_mut().events_cleared_transition(); } +// requires main thread +pub unsafe fn is_terminated() -> bool { + matches!(AppState::get_mut().state(), AppStateImpl::Terminated) +} + // requires main thread pub unsafe fn terminated() { let mut this = AppState::get_mut(); @@ -1068,8 +1147,8 @@ impl NSOperatingSystemVersion { } pub fn os_capabilities() -> OSCapabilities { - use once_cell::sync::Lazy; - static OS_CAPABILITIES: Lazy = Lazy::new(|| { + use std::sync::LazyLock; + static OS_CAPABILITIES: LazyLock = LazyLock::new(|| { let version: NSOperatingSystemVersion = unsafe { let process_info: id = msg_send![class!(NSProcessInfo), processInfo]; let atleast_ios_8: bool = msg_send![ diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/badge.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/badge.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/badge.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/badge.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/event_loop.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/event_loop.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/event_loop.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/ffi.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/ffi.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/ffi.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/keycode.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/keycode.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/keycode.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/mod.rs similarity index 89% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/mod.rs index ba183d445..08a994426 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/mod.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/mod.rs @@ -50,8 +50,10 @@ //! //! This is how those event are represented in tao: //! -//! - applicationWillEnterForeground is Resumed -//! - applicationWillResignActive is Suspended +//! - sceneWillEnterForeground is WindowEvent::Resumed for each window in the scene +//! - sceneWillResignActive is WindowEvent::Suspended for each window in the scene +//! - applicationWillEnterForeground is WindowEvent::Resumed for each window in non-scene apps +//! - applicationWillResignActive is WindowEvent::Suspended for each window in non-scene apps //! - applicationWillTerminate is LoopDestroyed //! //! Keep in mind that after LoopDestroyed event is received every attempt to draw with diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/monitor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/monitor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/monitor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs new file mode 100644 index 000000000..7495517cc --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/scene.rs @@ -0,0 +1,243 @@ +// Copyright 2021-2025 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 + +use objc2::{define_class, rc::Retained, MainThreadMarker, MainThreadOnly}; +use objc2_foundation::{ + NSBundle, NSError, NSObject, NSObjectProtocol, NSSet, NSString, NSUserActivity, + NSUserActivityTypeBrowsingWeb, +}; +use objc2_ui_kit::{ + UIApplication, UIOpenURLContext, UIScene, UISceneConnectionOptions, UISceneDelegate, + UISceneSession, UISceneWindowingControlStyle, UIWindowScene, UIWindowSceneDelegate, +}; + +use crate::{ + event::{Event, WindowEvent}, + platform_impl::platform::{app_state, event_loop::EventWrapper}, + window::WindowId as RootWindowId, +}; + +// true when the app enabled `UIApplicationSupportsMultipleScenes` in its Info.plist +// and the device allows it to display multiple scenes +// https://developer.apple.com/documentation/uikit/uiapplication/supportsmultiplescenes?language=objc +pub unsafe fn app_supports_multiple_scenes() -> bool { + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + application.supportsMultipleScenes() +} + +// check whether the app adopted the scene lifecycle by declaring a +// `UIApplicationSceneManifest` in its Info.plist +// +// such apps get their windows and their lifecycle callbacks from a `UISceneDelegate` +// instead of the application delegate, so a `UIWindow` is only visible once it is +// attached to a scene +// https://developer.apple.com/documentation/bundleresources/information-property-list/uiapplicationscenemanifest +pub unsafe fn scene_lifecycle_enabled() -> bool { + let bundle = NSBundle::mainBundle(); + let Some(info) = bundle.infoDictionary() else { + return false; + }; + + let key = NSString::from_str("UIApplicationSceneManifest"); + (*info).objectForKey(&key).is_some() +} + +unsafe fn handle_scene_window_events(scene: &UIScene, event: impl Fn() -> WindowEvent<'static>) { + if let Some(window_scene) = scene.downcast_ref::() { + let windows = window_scene.windows(); + + if windows.is_empty() { + log::debug!("scene has no windows; no window events were emitted"); + } + + for window in windows { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: event(), + })); + } + } +} + +// parses the URLs carried by the given contexts, skipping (and logging) the ones that are not +// valid; `source` names the delivery path in the log +pub unsafe fn urls_from_contexts( + url_contexts: &NSSet, + source: &str, +) -> Vec { + url_contexts + .iter() + .filter_map(|ctx| { + ctx + .URL() + .absoluteString() + .and_then(|url| parse_url(&url.to_string(), source)) + }) + .collect() +} + +// the URL of a universal link activity, if `activity` is one: other activities, such as +// Handoff, can carry a web page URL too and are not links that opened the app +pub unsafe fn universal_link_url(activity: &NSUserActivity, source: &str) -> Option { + if !activity + .activityType() + .isEqualToString(NSUserActivityTypeBrowsingWeb) + { + return None; + } + activity + .webpageURL() + .and_then(|url| url.absoluteString()) + .and_then(|url| parse_url(&url.to_string(), source)) +} + +fn parse_url(url: &str, source: &str) -> Option { + url + .parse() + .map_err(|e| log::error!("failed to parse URL {url} from {source}: {e}")) + .ok() +} + +define_class!( + #[unsafe(super(NSObject))] + #[name = "TaoSceneDelegate"] + #[thread_kind = MainThreadOnly] + pub struct TaoSceneDelegate; + + unsafe impl NSObjectProtocol for TaoSceneDelegate {} + + #[allow(non_snake_case)] + unsafe impl UISceneDelegate for TaoSceneDelegate { + #[unsafe(method(scene:willConnectToSession:options:))] + fn scene_willConnectToSession_options( + &self, + scene: &UIScene, + _session: &UISceneSession, + connection_options: &UISceneConnectionOptions, + ) { + unsafe { + app_state::connect_scene(scene, connection_options); + } + } + + // iOS may disconnect a scene because the user closed it, or because + // the system discarded a backgrounded scene to free resources. A + // restored scene goes through `scene:willConnectToSession:` again + // and gets a new `UIWindow` and `WindowId`. + #[unsafe(method(sceneDidDisconnect:))] + fn sceneDidDisconnect(&self, scene: &UIScene) { + unsafe { + if app_state::is_terminated() { + log::debug!("ignoring `sceneDidDisconnect` after application termination"); + return; + } + + handle_scene_window_events(scene, || WindowEvent::Destroyed); + } + } + + #[unsafe(method(sceneDidBecomeActive:))] + fn sceneDidBecomeActive(&self, scene: &UIScene) { + unsafe { + handle_scene_window_events(scene, || WindowEvent::Focused(true)); + } + } + + #[unsafe(method(sceneWillResignActive:))] + fn sceneWillResignActive(&self, scene: &UIScene) { + unsafe { + handle_scene_window_events(scene, || WindowEvent::Focused(false)); + handle_scene_window_events(scene, || WindowEvent::Suspended); + } + } + + #[unsafe(method(sceneWillEnterForeground:))] + fn sceneWillEnterForeground(&self, scene: &UIScene) { + unsafe { + handle_scene_window_events(scene, || WindowEvent::Resumed); + } + } + + #[unsafe(method(sceneDidEnterBackground:))] + fn sceneDidEnterBackground(&self, _scene: &UIScene) {} + + #[unsafe(method(scene:openURLContexts:))] + fn scene_openURLContexts(&self, _scene: &UIScene, url_contexts: &NSSet) { + unsafe { + let urls = urls_from_contexts(url_contexts, "scene:openURLContexts:"); + if !urls.is_empty() { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { urls })); + } + } + } + + #[unsafe(method(stateRestorationActivityForScene:))] + fn stateRestorationActivityForScene( + &self, + _scene: &UIScene, + ) -> Option> { + None + } + + #[unsafe(method(scene:restoreInteractionStateWithUserActivity:))] + fn scene_restoreInteractionStateWithUserActivity( + &self, + _scene: &UIScene, + _state_restoration_activity: &NSUserActivity, + ) { + } + + #[unsafe(method(scene:willContinueUserActivityWithType:))] + fn scene_willContinueUserActivityWithType( + &self, + _scene: &UIScene, + _user_activity_type: &NSString, + ) { + } + + #[unsafe(method(scene:continueUserActivity:))] + fn scene_continueUserActivity(&self, _scene: &UIScene, user_activity: &NSUserActivity) { + unsafe { + // universal app links + if let Some(url) = universal_link_url(user_activity, "scene:continueUserActivity:") { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Opened { + urls: vec![url], + })); + } + } + } + + #[unsafe(method(scene:didFailToContinueUserActivityWithType:error:))] + fn scene_didFailToContinueUserActivityWithType_error( + &self, + _scene: &UIScene, + _user_activity_type: &NSString, + _error: &NSError, + ) { + } + + #[unsafe(method(scene:didUpdateUserActivity:))] + fn scene_didUpdateUserActivity(&self, _scene: &UIScene, _user_activity: &NSUserActivity) {} + } + + #[allow(non_snake_case)] + unsafe impl UIWindowSceneDelegate for TaoSceneDelegate {} + + // Registered from a plain `impl` because objc2 panics in debug builds when a + // protocol-block selector is missing from the runtime protocol, and this one is + // iOS 26+. Both paths register the same method, so iOS 26 still resolves it. + // + // TODO: Move back into the protocol block once objc2 0.7.0 fixes the check, + // ref . + #[allow(non_snake_case)] + impl TaoSceneDelegate { + #[unsafe(method_id(preferredWindowingControlStyleForScene:))] + fn preferredWindowingControlStyleForScene( + &self, + _window_scene: &UIWindowScene, + ) -> Retained { + UISceneWindowingControlStyle::minimalStyle() + } + } +); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/view.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/view.rs similarity index 81% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/view.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/view.rs index 7f37832fc..fb1f83c32 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/view.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/view.rs @@ -12,9 +12,8 @@ use objc2::{ runtime::{AnyClass as Class, AnyObject as Object, ClassBuilder as ClassDecl, Sel}, ClassType, MainThreadMarker, }; -use objc2_foundation::NSString; use objc2_ui_kit::{ - UIApplication, UISceneActivationRequestOptions, UISceneConfiguration, + UIApplication, UISceneActivationRequestOptions, UISceneConfiguration, UISceneSession, UISceneSessionActivationRequest, }; @@ -29,7 +28,7 @@ use crate::{ id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask, UIRectEdge, UITouchPhase, UITouchType, BOOL, NO, YES, }, - scene::{app_supports_multiple_scenes, multiple_scenes_enabled}, + scene::app_supports_multiple_scenes, window::PlatformSpecificWindowBuilderAttributes, DeviceId, }, @@ -136,7 +135,11 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { let () = msg_send![super(object, superclass), layoutSubviews]; let window: id = msg_send![object, window]; - assert!(!window.is_null()); + // UIKit can lay out a view while it is detached during window and view-controller + // transitions. There is no window ID or screen geometry to report until reattachment. + if window.is_null() { + return; + } let window_bounds: CGRect = msg_send![window, bounds]; let screen: id = msg_send![window, screen]; let screen_space: id = msg_send![screen, coordinateSpace]; @@ -537,61 +540,55 @@ pub unsafe fn create_window( "Failed to initialize `UIWindow` instance" ); - if multiple_scenes_enabled() { - // if multiple scenes is enabled in Info.plist, we need to assign it - // regarless of whether the device supports multiple scenes or not - if let Some(scene) = app_state::unitialized_scene() { - let _: () = msg_send![window, setWindowScene: Retained::as_ptr(&scene)]; - } else if !app_supports_multiple_scenes() { - // if there's no unitialized scene and the app does not support multiple scenes - // we need to move this window to the main scene, otherwise it won't be visible - let scene = unsafe { - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - application.connectedScenes().iter().next() - }; - let scene = scene - .as_ref() - .map(|s| Retained::as_ptr(s)) - .unwrap_or(std::ptr::null_mut()); + // on the scene lifecycle a window is only displayed once it belongs to a scene, so we + // must always assign one - the app runs on it whenever the system connected a scene, + // which it may do even when the Info.plist does not declare a scene manifest + if let Some(scene) = app_state::unitialized_scene() { + let _: () = msg_send![window, setWindowScene: Retained::as_ptr(&scene)]; + } else if app_supports_multiple_scenes() { + // only request a new scene if the system actually supports it + // otherwise it is silently ignored + unsafe { + let mtm = MainThreadMarker::new().unwrap(); + let application = UIApplication::sharedApplication(mtm); + app_state::register_window_for_scene(window); - let _: () = msg_send![window, setWindowScene: scene]; - } else { - // only request a new scene if the system actually supports it - // otherwise it is silently ignored - unsafe { - let mtm = MainThreadMarker::new().unwrap(); - let application = UIApplication::sharedApplication(mtm); - app_state::register_window_for_scene(window); - - let options = UISceneActivationRequestOptions::new(mtm); - if let Some(scene) = platform_attributes - .requesting_scene_identifier - .as_ref() - .and_then(|id| app_state::scene_by_id(id)) - { - options.setRequestingScene(Some(&scene)); - } + let options = UISceneActivationRequestOptions::new(mtm); + if let Some(scene) = platform_attributes + .requesting_scene_identifier + .as_ref() + .and_then(|id| app_state::scene_by_id(id)) + { + options.setRequestingScene(Some(&scene)); + } - let error_handler = block2::RcBlock::new(move |error| { - log::error!("error activating scene: {error:?}"); - }); - - if operating_system_version().0 >= 17 { - let request = UISceneSessionActivationRequest::request(); - request.setOptions(Some(&options)); - application.activateSceneSessionForRequest_errorHandler(&request, Some(&error_handler)); - } else { - #[allow(deprecated)] - application.requestSceneSessionActivation_userActivity_options_errorHandler( - None, - None, - Some(&options), - Some(&error_handler), - ); - } + let error_handler = block2::RcBlock::new(move |error| { + log::error!("error activating scene: {error:?}"); + }); + + if operating_system_version().0 >= 17 { + let request = UISceneSessionActivationRequest::request(); + request.setOptions(Some(&options)); + application.activateSceneSessionForRequest_errorHandler(&request, Some(&error_handler)); + } else { + #[allow(deprecated)] + application.requestSceneSessionActivation_userActivity_options_errorHandler( + None, + None, + Some(&options), + Some(&error_handler), + ); } } + } else if let Some(scene) = app_state::first_window_scene() { + // if there's no unitialized scene and the app does not support multiple scenes + // we need to move this window to the main scene, otherwise it won't be visible + let _: () = msg_send![window, setWindowScene: Retained::as_ptr(&scene)]; + } else { + // no scene is connected yet, either because the app runs on the legacy lifecycle + // - where the system attaches the window to a scene on its own - or because it was + // created before the first scene connected, which then adopts it + app_state::register_scene_less_window(window); } let () = msg_send![window, setRootViewController: view_controller]; @@ -630,20 +627,22 @@ pub fn create_delegate_class() { _: &Object, _: Sel, _application: id, - _connecting_scene_session: id, + connecting_scene_session: id, _options: id, ) -> id { unsafe { let mtm = objc2_foundation::MainThreadMarker::new_unchecked(); - let config = UISceneConfiguration::configurationWithName_sessionRole( - Some(&NSString::from_str("TaoScene")), - &NSString::from_str("UIWindowSceneSessionRoleApplication"), - mtm, - ); + // keep the role of the session that is connecting instead of assuming the + // window scene role, so scenes such as external displays still get their own. + // a `None` name picks the app's own configuration for that role when its + // Info.plist declares one, and the default configuration when it doesn't + let session: &UISceneSession = &*connecting_scene_session.cast(); + let config = + UISceneConfiguration::configurationWithName_sessionRole(None, &session.role(), mtm); // Dynamically set the delegate class name config.setDelegateClass(Some(super::scene::TaoSceneDelegate::class())); - Retained::as_ptr(&config) as _ + Retained::autorelease_ptr(config) as _ } } @@ -665,6 +664,27 @@ pub fn create_delegate_class() { } } + unsafe fn handle_tao_window_events(event: impl Fn() -> WindowEvent<'static>) { + let app: id = msg_send![class!(UIApplication), sharedApplication]; + let windows: id = msg_send![app, windows]; + let windows_enum: id = msg_send![windows, objectEnumerator]; + let mut events = Vec::new(); + loop { + let window: id = msg_send![windows_enum, nextObject]; + if window == nil { + break; + } + let is_tao_window: bool = msg_send![window, isKindOfClass: class!(TaoUIWindow)]; + if is_tao_window { + events.push(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: event(), + })); + } + } + app_state::handle_nonuser_events(events); + } + // custom URL schemes // https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app extern "C" fn application_open_url( @@ -701,35 +721,28 @@ pub fn create_delegate_class() { } extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) { - unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) } + unsafe { + if app_state::did_first_scene_connect() { + // the scene delegate already emits this event for the windows of its scene + return; + } + handle_tao_window_events(|| WindowEvent::Suspended) + } } extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) { - unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) } + unsafe { + if app_state::did_first_scene_connect() { + // the scene delegate already emits this event for the windows of its scene + return; + } + handle_tao_window_events(|| WindowEvent::Resumed) + } } - extern "C" fn did_enter_background(_: &Object, _: Sel, _: id) {} - extern "C" fn will_terminate(_: &Object, _: Sel, _: id) { unsafe { - let app: id = msg_send![class!(UIApplication), sharedApplication]; - let windows: id = msg_send![app, windows]; - let windows_enum: id = msg_send![windows, objectEnumerator]; - let mut events = Vec::new(); - loop { - let window: id = msg_send![windows_enum, nextObject]; - if window == nil { - break; - } - let is_tao_window: bool = msg_send![window, isKindOfClass: class!(TaoUIWindow)]; - if is_tao_window { - events.push(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(window.into()), - event: WindowEvent::Destroyed, - })); - } - } - app_state::handle_nonuser_events(events); + handle_tao_window_events(|| WindowEvent::Destroyed); app_state::terminated(); } } @@ -742,17 +755,23 @@ pub fn create_delegate_class() { .expect("Failed to declare class `AppDelegate`"); unsafe { + // register the scene delegate class in the runtime upfront, the system looks it up by + // name when it decodes a scene session that was encoded on a previous launch + let _ = super::scene::TaoSceneDelegate::class(); + decl.add_method( sel!(application:didFinishLaunchingWithOptions:), did_finish_launching as extern "C" fn(_, _, _, _) -> _, ); - if multiple_scenes_enabled() { - decl.add_method( - sel!(application:configurationForConnectingSceneSession:options:), - configuration_for_connecting_scene_session as extern "C" fn(_, _, _, _, _) -> _, - ); - } + // always answer the scene configuration request, even when the Info.plist does not + // declare a scene manifest: UIKit only asks for it when the app runs on the scene + // lifecycle, and answering it is the only way to get our scene delegate installed + // there. Apps that never run on scenes simply never receive this callback. + decl.add_method( + sel!(application:configurationForConnectingSceneSession:options:), + configuration_for_connecting_scene_session as extern "C" fn(_, _, _, _, _) -> _, + ); decl.add_method( sel!(application:openURL:options:), @@ -772,10 +791,6 @@ pub fn create_delegate_class() { sel!(applicationWillEnterForeground:), will_enter_foreground as extern "C" fn(_, _, _), ); - decl.add_method( - sel!(applicationDidEnterBackground:), - did_enter_background as extern "C" fn(_, _, _), - ); decl.add_method( sel!(applicationWillTerminate:), diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/window.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/ios/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/ios/window.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/device.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/device.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/device.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/device.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/event_loop.rs similarity index 94% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/event_loop.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/event_loop.rs index 4e9fe979a..0859023b6 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/event_loop.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/event_loop.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 use std::{ - cell::RefCell, + cell::{Cell, RefCell}, collections::{HashSet, VecDeque}, error::Error, process, @@ -695,8 +695,10 @@ impl EventLoop { }); let tx_clone = event_tx.clone(); + let last_motion_time = Rc::new(Cell::new(0u32)); window.connect_motion_notify_event(move |window, motion| { - if cursor_moved { + let last = last_motion_time.replace(motion.time()); + if cursor_moved && motion.time() != last { if let Some(cursor) = motion.device() { let scale_factor = window.scale_factor(); let (_, x, y) = cursor.window_at_position(); @@ -713,7 +715,7 @@ impl EventLoop { } } } - glib::Propagation::Stop + glib::Propagation::Proceed }); let tx_clone = event_tx.clone(); @@ -730,55 +732,63 @@ impl EventLoop { }); let tx_clone = event_tx.clone(); + let last_press_time = Rc::new(Cell::new(0u32)); window.connect_button_press_event(move |_, event| { - let button = event.button(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseInput { - button: match button { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - _ => MouseButton::Other(button as u16), + let last = last_press_time.replace(event.time()); + if event.time() != last { + let button = event.button(); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseInput { + button: match button { + 1 => MouseButton::Left, + 2 => MouseButton::Middle, + 3 => MouseButton::Right, + _ => MouseButton::Other(button as u16), + }, + state: ElementState::Pressed, + device_id: DEVICE_ID, + // this field is depracted so it is fine to pass empty state + modifiers: ModifiersState::empty(), }, - state: ElementState::Pressed, - device_id: DEVICE_ID, - // this field is depracted so it is fine to pass empty state - modifiers: ModifiersState::empty(), - }, - }) { - log::warn!( - "Failed to send mouse input pressed event to event channel: {}", - e - ); + }) { + log::warn!( + "Failed to send mouse input pressed event to event channel: {}", + e + ); + } } - glib::Propagation::Stop + glib::Propagation::Proceed }); let tx_clone = event_tx.clone(); + let last_release_time = Rc::new(Cell::new(0u32)); window.connect_button_release_event(move |_, event| { - let button = event.button(); - if let Err(e) = tx_clone.send(Event::WindowEvent { - window_id: RootWindowId(id), - event: WindowEvent::MouseInput { - button: match button { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - _ => MouseButton::Other(button as u16), + let last = last_release_time.replace(event.time()); + if event.time() != last { + let button = event.button(); + if let Err(e) = tx_clone.send(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::MouseInput { + button: match button { + 1 => MouseButton::Left, + 2 => MouseButton::Middle, + 3 => MouseButton::Right, + _ => MouseButton::Other(button as u16), + }, + state: ElementState::Released, + device_id: DEVICE_ID, + // this field is depracted so it is fine to pass empty state + modifiers: ModifiersState::empty(), }, - state: ElementState::Released, - device_id: DEVICE_ID, - // this field is depracted so it is fine to pass empty state - modifiers: ModifiersState::empty(), - }, - }) { - log::warn!( - "Failed to send mouse input released event to event channel: {}", - e - ); + }) { + log::warn!( + "Failed to send mouse input released event to event channel: {}", + e + ); + } } - glib::Propagation::Stop + glib::Propagation::Proceed }); let tx_clone = event_tx.clone(); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/icon.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/icon.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/icon.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keyboard.rs similarity index 96% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keyboard.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keyboard.rs index b9d7f781b..aa52b14f2 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keyboard.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keyboard.rs @@ -11,18 +11,18 @@ use gtk::{ gdk::{self, keys::constants::*, EventKey}, glib, }; -use once_cell::sync::Lazy; use std::{ collections::HashSet, ffi::c_void, os::raw::{c_int, c_uint}, ptr, slice, - sync::Mutex, + sync::{LazyLock, Mutex}, }; pub type RawKey = gdk::keys::Key; -static KEY_STRINGS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); +static KEY_STRINGS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); fn insert_or_get_key_str(string: String) -> &'static str { let mut string_set = KEY_STRINGS.lock().unwrap(); @@ -115,6 +115,12 @@ pub(crate) fn raw_key_to_key(gdk_key: RawKey) -> Option> { // KP_Separator? What does it map to? KP_Tab => Some(Key::Tab), KP_Up => Some(Key::ArrowUp), + + // JIS + Zenkaku_Hankaku => Some(Key::ZenkakuHankaku), + Hiragana_Katakana => Some(Key::HiraganaKatakana), + Henkan => Some(Key::Convert), + Muhenkan => Some(Key::NonConvert), // TODO: more mappings (media etc) _ => None, } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keycode.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/keycode.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/keycode.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/mod.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/mod.rs index 0473f67e5..aa317542e 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/mod.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/mod.rs @@ -15,7 +15,6 @@ mod util; mod window; pub mod taskbar; -pub mod wayland; #[cfg(feature = "x11")] pub mod x11; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/monitor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/monitor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/monitor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/portal.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/portal.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/portal.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/portal.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/taskbar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/taskbar.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/taskbar.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/taskbar.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/util.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/util.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/util.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/util.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/window.rs similarity index 92% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/window.rs index fca4c308c..0308f1bf2 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/window.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/window.rs @@ -6,14 +6,17 @@ use std::{ cell::RefCell, collections::VecDeque, rc::Rc, - sync::atomic::{AtomicBool, AtomicI32, Ordering}, + sync::{ + atomic::{AtomicBool, AtomicI32, Ordering}, + RwLock, + }, }; use gtk::{ gdk::WindowState, glib::{self, translate::ToGlibPtr}, prelude::*, - CssProvider, Settings, + CssProvider, EventBox, Settings, }; use crate::{ @@ -21,7 +24,6 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, monitor::MonitorHandle as RootMonitorHandle, - platform_impl::wayland::header::WlHeader, window::{ CursorIcon, Fullscreen, ProgressBarState, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowSizeConstraints, RGBA, @@ -59,11 +61,17 @@ pub struct Window { maximized: Rc, is_always_on_top: Rc, minimized: Rc, - fullscreen: RefCell>, - inner_size_constraints: RefCell, + // `Window` is `Send` and `Sync`, need a `RwLock` not a `RefCell` + // otherwise unsynchronized &RefCell from multiple threads + fullscreen: RwLock>, + // `Window` is `Send` and `Sync`, need a `RwLock` not a `RefCell` + // otherwise unsynchronized &RefCell from multiple threads + inner_size_constraints: RwLock, /// Draw event Sender draw_tx: crossbeam_channel::Sender, - preferred_theme: RefCell>, + // `Window` is `Send` and `Sync`, need a `RwLock` not a `RefCell` + // otherwise unsynchronized &RefCell from multiple threads + preferred_theme: RwLock>, css_provider: CssProvider, } @@ -76,7 +84,6 @@ impl Window { let app = &event_loop_window_target.app; let window_requests_tx = event_loop_window_target.window_requests_tx.clone(); let draw_tx = event_loop_window_target.draw_tx.clone(); - let is_wayland = event_loop_window_target.is_wayland(); let mut window_builder = gtk::ApplicationWindow::builder() .application(app) @@ -87,10 +94,6 @@ impl Window { let window = window_builder.build(); - if is_wayland { - WlHeader::setup(&window, &attributes.title); - } - let window_id = WindowId(window.id()); event_loop_window_target .windows @@ -103,18 +106,25 @@ impl Window { .inner_size .map(|size| size.to_logical::(win_scale_factor as f64).into()) .unwrap_or((800, 600)); - window.set_default_size(1, 1); - window.resize(width, height); - - if attributes.maximized { - let maximize_process = util::WindowMaximizeProcess::new(window.clone(), attributes.resizable); - glib::idle_add_local_full(glib::Priority::HIGH_IDLE, move || { - let mut maximize_process = maximize_process.borrow_mut(); - maximize_process.next_step() - }); - } else { - window.set_resizable(attributes.resizable); - } + + window.set_default_size(width, height); + + // Trying to prevent Wayland Protocol Error about mismatched xdg_surface_buffer sizes + let maximized = attributes.maximized; + let resizable = attributes.resizable; + let configure_handler_id = Rc::new(RefCell::new(None)); + let configure_handler_id_ = configure_handler_id.clone(); + let id = window.connect_configure_event(move |window, _| { + if let Some(id) = configure_handler_id_.take() { + window.disconnect(id); + if maximized { + window.maximize(); + } + window.set_resizable(resizable); + } + false + }); + configure_handler_id.borrow_mut().replace(id); window.set_deletable(attributes.closable); @@ -180,6 +190,11 @@ impl Window { } window.set_visible(attributes.visible); window.set_decorated(attributes.decorations); + if window.display().backend().is_wayland() && !attributes.decorations { + // Enable CSD to prevent compositor from rendering server-side decorations + let event_box = EventBox::new(); + window.set_titlebar(Some(&event_box)); + } if attributes.always_on_bottom { window.set_keep_below(attributes.always_on_bottom); @@ -272,9 +287,9 @@ impl Window { maximized, minimized, is_always_on_top, - fullscreen: RefCell::new(attributes.fullscreen), - inner_size_constraints: RefCell::new(attributes.inner_size_constraints), - preferred_theme: RefCell::new(preferred_theme), + fullscreen: RwLock::new(attributes.fullscreen), + inner_size_constraints: RwLock::new(attributes.inner_size_constraints), + preferred_theme: RwLock::new(preferred_theme), css_provider: CssProvider::new(), }; @@ -415,9 +430,9 @@ impl Window { maximized, minimized, is_always_on_top, - fullscreen: RefCell::new(None), - inner_size_constraints: RefCell::new(WindowSizeConstraints::default()), - preferred_theme: RefCell::new(None), + fullscreen: RwLock::new(None), + inner_size_constraints: RwLock::new(WindowSizeConstraints::default()), + preferred_theme: RwLock::new(None), css_provider: CssProvider::new(), }; @@ -519,7 +534,7 @@ impl Window { pub fn set_min_inner_size(&self, size: Option) { let (width, height) = size.map(crate::extract_width_height).unzip(); - let mut size_constraints = self.inner_size_constraints.borrow_mut(); + let mut size_constraints = self.inner_size_constraints.write().unwrap(); size_constraints.min_width = width; size_constraints.min_height = height; self.set_size_constraints(*size_constraints) @@ -527,14 +542,14 @@ impl Window { pub fn set_max_inner_size(&self, size: Option) { let (width, height) = size.map(crate::extract_width_height).unzip(); - let mut size_constraints = self.inner_size_constraints.borrow_mut(); + let mut size_constraints = self.inner_size_constraints.write().unwrap(); size_constraints.max_width = width; size_constraints.max_height = height; self.set_size_constraints(*size_constraints) } pub fn set_inner_size_constraints(&self, constraints: WindowSizeConstraints) { - *self.inner_size_constraints.borrow_mut() = constraints; + *self.inner_size_constraints.write().unwrap() = constraints; self.set_size_constraints(constraints) } @@ -682,7 +697,7 @@ impl Window { } pub fn set_fullscreen(&self, fullscreen: Option) { - self.fullscreen.replace(fullscreen.clone()); + *self.fullscreen.write().unwrap() = fullscreen.clone(); if let Err(e) = self .window_requests_tx .send((self.window_id, WindowRequest::Fullscreen(fullscreen))) @@ -692,7 +707,7 @@ impl Window { } pub fn fullscreen(&self) -> Option { - self.fullscreen.borrow().clone() + self.fullscreen.read().unwrap().clone() } pub fn set_decorations(&self, decorations: bool) { @@ -1009,7 +1024,7 @@ impl Window { } pub fn theme(&self) -> Theme { - if let Some(theme) = *self.preferred_theme.borrow() { + if let Some(theme) = *self.preferred_theme.read().unwrap() { return theme; } @@ -1022,7 +1037,7 @@ impl Window { } pub fn set_theme(&self, theme: Option) { - *self.preferred_theme.borrow_mut() = theme; + *self.preferred_theme.write().unwrap() = theme; if let Err(e) = self .window_requests_tx .send((WindowId::dummy(), WindowRequest::SetTheme(theme))) diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/ffi.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/ffi.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/ffi.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/mod.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/mod.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/xdisplay.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/xdisplay.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/linux/x11/xdisplay.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/linux/x11/xdisplay.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app.rs similarity index 97% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app.rs index a83363eef..ae5fd266b 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app.rs @@ -6,7 +6,7 @@ use std::{collections::VecDeque, ffi::CStr}; use objc2::runtime::{AnyClass as Class, ClassBuilder as ClassDecl, Sel}; use objc2_app_kit::{self as appkit, NSApplication, NSEvent, NSEventType}; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID}; use crate::event::{DeviceEvent, ElementState, Event}; @@ -15,7 +15,7 @@ pub struct AppClass(pub *const Class); unsafe impl Send for AppClass {} unsafe impl Sync for AppClass {} -pub static APP_CLASS: Lazy = Lazy::new(|| unsafe { +pub static APP_CLASS: LazyLock = LazyLock::new(|| unsafe { let superclass = class!(NSApplication); let mut decl = ClassDecl::new(CStr::from_bytes_with_nul(b"TaoApp\0").unwrap(), superclass).unwrap(); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_delegate.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_delegate.rs similarity index 98% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_delegate.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_delegate.rs index 5107814c9..41b000477 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_delegate.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_delegate.rs @@ -16,12 +16,11 @@ use objc2::runtime::{ use objc2_foundation::{ NSArray, NSError, NSString, NSUserActivity, NSUserActivityTypeBrowsingWeb, NSURL, }; -use once_cell::sync::Lazy; use std::{ cell::{RefCell, RefMut}, ffi::{CStr, CString}, os::raw::c_void, - sync::Mutex, + sync::{LazyLock, Mutex}, time::Instant, }; @@ -44,7 +43,7 @@ pub struct AppDelegateClass(pub *const Class); unsafe impl Send for AppDelegateClass {} unsafe impl Sync for AppDelegateClass {} -pub static APP_DELEGATE_CLASS: Lazy = Lazy::new(|| unsafe { +pub static APP_DELEGATE_CLASS: LazyLock = LazyLock::new(|| unsafe { let superclass = class!(NSResponder); let mut decl = ClassDecl::new( CStr::from_bytes_with_nul(b"TaoAppDelegateParent\0").unwrap(), diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_state.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_state.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_state.rs index 03dff9132..a305d47f9 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/app_state.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/app_state.rs @@ -19,7 +19,7 @@ use std::{ use objc2::{msg_send, rc::Retained, runtime::AnyObject as Object}; use objc2_app_kit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSWindow}; use objc2_foundation::{MainThreadMarker, NSAutoreleasePool, NSSize}; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use crate::{ dpi::LogicalSize, @@ -42,7 +42,7 @@ use crate::{ use super::set_dock_visibility; -static HANDLER: Lazy = Lazy::new(|| Default::default()); +static HANDLER: LazyLock = LazyLock::new(|| Default::default()); impl<'a, Never> Event<'a, Never> { fn userify(self) -> Event<'a, T> { diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/badge.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/badge.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/badge.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/badge.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/dock.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/dock.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/dock.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/dock.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event.rs similarity index 98% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event.rs index 6b1ce97f5..c4bb2a9d2 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event.rs @@ -6,7 +6,7 @@ use std::{collections::HashSet, ffi::c_void, os::raw::c_ushort, sync::Mutex}; use objc2::{msg_send, rc::Retained}; use objc2_app_kit::{NSEvent, NSEventModifierFlags, NSWindow}; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use core_foundation::{base::CFRelease, data::CFDataGetBytePtr}; use objc2_foundation::NSString; @@ -18,7 +18,8 @@ use crate::{ platform_impl::platform::{ffi, util::Never}, }; -static KEY_STRINGS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); +static KEY_STRINGS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashSet::new())); fn insert_or_get_key_str(string: String) -> &'static str { let mut string_set = KEY_STRINGS.lock().unwrap(); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event_loop.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/event_loop.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/event_loop.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/ffi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/ffi.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/ffi.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/ffi.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/icon.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/icon.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/icon.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/keycode.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/keycode.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/keycode.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/mod.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/mod.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/monitor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/monitor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/monitor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/observer.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/observer.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/observer.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/observer.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/progress_bar.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/progress_bar.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/progress_bar.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/progress_bar.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/async.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/async.rs similarity index 95% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/async.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/async.rs index 28363ffac..23dd8ec48 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/async.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/async.rs @@ -17,6 +17,7 @@ use crate::{ dpi::LogicalSize, platform_impl::platform::{ ffi::{self, id, NO, YES}, + view::reapply_traffic_light_inset, window::SharedState, }, }; @@ -219,11 +220,16 @@ pub unsafe fn make_key_and_order_front_sync(ns_window: &NSWindow) { // `setTitle:` isn't thread-safe. Calling it from another thread invalidates the // window drag regions, which throws an exception when not done in the main // thread -pub unsafe fn set_title_async(ns_window: &NSWindow, title: String) { +pub unsafe fn set_title_async(ns_window: &NSWindow, ns_view: &NSView, title: String) { let ns_window = MainThreadSafe(ns_window.retain()); + let ns_view = MainThreadSafe(ns_view.retain()); DispatchQueue::main().exec_async(move || { let title = NSString::from_str(&title); ns_window.setTitle(&title); + // `setTitle:` moves the traffic light buttons back to their default spot. + // Re-apply the configured inset kept on the view so the custom position + // survives a title change (#13044). + reapply_traffic_light_inset(&ns_window, &ns_view); }); } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/cursor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/cursor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/cursor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/cursor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/mod.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/util/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/util/mod.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/view.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/view.rs similarity index 98% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/view.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/view.rs index dfeba9414..4e4b6a231 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/view.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/view.rs @@ -28,7 +28,7 @@ use objc2_foundation::{ ns_string, MainThreadMarker, NSAttributedString, NSInteger, NSMutableAttributedString, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger, }; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use crate::{ dpi::LogicalPosition, @@ -136,7 +136,7 @@ struct ViewClass(&'static Class); unsafe impl Send for ViewClass {} unsafe impl Sync for ViewClass {} -static VIEW_CLASS: Lazy = Lazy::new(|| unsafe { +static VIEW_CLASS: LazyLock = LazyLock::new(|| unsafe { let superclass = class!(NSView); let mut decl = ClassDecl::new(CStr::from_bytes_with_nul(b"TaoView\0").unwrap(), superclass).unwrap(); @@ -1180,3 +1180,18 @@ pub unsafe fn inset_traffic_lights(window: &NSWindow, position: LogicalPosition< button.setFrameOrigin(rect.origin); } } + +// Re-apply the custom traffic light inset stored on the view, if one was set. +// AppKit resets the buttons to their default position on some events (title +// change, leaving fullscreen) without triggering a `drawRect:`, so callers use +// this to restore the configured position afterwards (#13044, #15451). +pub unsafe fn reapply_traffic_light_inset(ns_window: &NSWindow, ns_view: &NSView) { + let state_ptr: *mut c_void = *ns_view.get_ivar("taoState"); + if state_ptr.is_null() { + return; + } + let state = &*(state_ptr as *mut ViewState); + if let Some(position) = state.traffic_light_inset { + inset_traffic_lights(ns_window, position); + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window.rs index 8a19b9d2c..f910c957d 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window.rs @@ -60,7 +60,7 @@ use objc2_foundation::{ ns_string, MainThreadMarker, NSArray, NSAutoreleasePool, NSDictionary, NSInteger, NSPoint, NSRect, NSSize, NSString, NSTimeInterval, NSUInteger, }; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use super::{ ffi::{id, nil, NO}, @@ -402,10 +402,8 @@ pub(super) fn set_ns_theme(theme: Option) { } struct WindowClass(&'static Class); -unsafe impl Send for WindowClass {} -unsafe impl Sync for WindowClass {} -static WINDOW_CLASS: Lazy = Lazy::new(|| unsafe { +static WINDOW_CLASS: LazyLock = LazyLock::new(|| unsafe { let window_superclass = class!(NSWindow); let mut decl = ClassDecl::new( CStr::from_bytes_with_nul(b"TaoWindow\0").unwrap(), @@ -657,7 +655,7 @@ impl UnownedWindow { pub fn set_title(&self, title: &str) { unsafe { - util::set_title_async(&self.ns_window, title.to_string()); + util::set_title_async(&self.ns_window, &self.ns_view, title.to_string()); } } diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window_delegate.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window_delegate.rs similarity index 98% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window_delegate.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window_delegate.rs index fa6e63085..4bd2f5ec1 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/macos/window_delegate.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/macos/window_delegate.rs @@ -18,7 +18,7 @@ use objc2_app_kit::{ self as appkit, NSApplicationPresentationOptions, NSPasteboard, NSView, NSWindow, }; use objc2_foundation::{ns_string, NSArray, NSAutoreleasePool, NSString, NSUInteger}; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use crate::{ dpi::{LogicalPosition, LogicalSize}, @@ -29,7 +29,7 @@ use crate::{ event::{EventProxy, EventWrapper}, ffi::{id, nil, BOOL, NO, YES}, util::{self, IdRef}, - view::ViewState, + view::{reapply_traffic_light_inset, ViewState}, window::{get_ns_theme, get_window_id, UnownedWindow}, }, window::{Fullscreen, WindowId}, @@ -159,7 +159,7 @@ struct WindowDelegateClass(*const Class); unsafe impl Send for WindowDelegateClass {} unsafe impl Sync for WindowDelegateClass {} -static WINDOW_DELEGATE_CLASS: Lazy = Lazy::new(|| unsafe { +static WINDOW_DELEGATE_CLASS: LazyLock = LazyLock::new(|| unsafe { let superclass = class!(NSResponder); let mut decl = ClassDecl::new( CStr::from_bytes_with_nul(b"TaoWindowDelegate\0").unwrap(), @@ -605,6 +605,9 @@ extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { if let Some(target_fullscreen) = target_fullscreen { window.set_fullscreen(target_fullscreen); } + // Leaving fullscreen resets the traffic light buttons to their default + // position without a `drawRect:`, so re-apply the custom inset (#15451). + unsafe { reapply_traffic_light_inset(&window.ns_window, &window.ns_view) }; }); state.emit_resize_event(); state.emit_move_event(); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/mod.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/mod.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dark_mode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dark_mode.rs similarity index 81% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dark_mode.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dark_mode.rs index 2a0189aac..26033112a 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dark_mode.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dark_mode.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #![allow(non_snake_case)] -use once_cell::sync::Lazy; +use std::sync::LazyLock; /// This is a simple implementation of support for Windows Dark Mode, /// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode use windows::{ @@ -25,10 +25,10 @@ use crate::window::Theme; use super::util; -static HUXTHEME: Lazy = - Lazy::new(|| unsafe { LoadLibraryA(s!("uxtheme.dll")).unwrap_or_default().0 as _ }); +static HUXTHEME: LazyLock = + LazyLock::new(|| unsafe { LoadLibraryA(s!("uxtheme.dll")).unwrap_or_default().0 as _ }); -static DARK_MODE_SUPPORTED: Lazy = Lazy::new(|| { +static DARK_MODE_SUPPORTED: LazyLock = LazyLock::new(|| { // We won't try to do anything for windows versions < 17763 // (Windows 10 October 2018 update) let v = *util::WIN_VERSION; @@ -39,17 +39,18 @@ pub fn allow_dark_mode_for_app(is_dark_mode: bool) { if *DARK_MODE_SUPPORTED { const UXTHEME_ALLOWDARKMODEFORAPP_ORDINAL: u16 = 135; type AllowDarkModeForApp = unsafe extern "system" fn(bool) -> bool; - static ALLOW_DARK_MODE_FOR_APP: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } + static ALLOW_DARK_MODE_FOR_APP: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORAPP_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORAPP_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); #[repr(C)] enum PreferredAppMode { @@ -61,17 +62,18 @@ pub fn allow_dark_mode_for_app(is_dark_mode: bool) { } const UXTHEME_SETPREFERREDAPPMODE_ORDINAL: u16 = 135; type SetPreferredAppMode = unsafe extern "system" fn(PreferredAppMode) -> PreferredAppMode; - static SET_PREFERRED_APP_MODE: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } + static SET_PREFERRED_APP_MODE: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_SETPREFERREDAPPMODE_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_SETPREFERREDAPPMODE_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); if util::WIN_VERSION.build < 18362 { if let Some(_allow_dark_mode_for_app) = *ALLOW_DARK_MODE_FOR_APP { @@ -93,8 +95,8 @@ pub fn allow_dark_mode_for_app(is_dark_mode: bool) { fn refresh_immersive_color_policy_state() { const UXTHEME_REFRESHIMMERSIVECOLORPOLICYSTATE_ORDINAL: u16 = 104; type RefreshImmersiveColorPolicyState = unsafe extern "system" fn(); - static REFRESH_IMMERSIVE_COLOR_POLICY_STATE: Lazy> = - Lazy::new(|| unsafe { + static REFRESH_IMMERSIVE_COLOR_POLICY_STATE: LazyLock> = + LazyLock::new(|| unsafe { if HMODULE(*HUXTHEME as _).is_invalid() { return None; } @@ -140,17 +142,18 @@ pub fn try_window_theme( pub fn allow_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) { const UXTHEME_ALLOWDARKMODEFORWINDOW_ORDINAL: u16 = 133; type AllowDarkModeForWindow = unsafe extern "system" fn(HWND, bool) -> bool; - static ALLOW_DARK_MODE_FOR_WINDOW: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } + static ALLOW_DARK_MODE_FOR_WINDOW: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORWINDOW_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_ALLOWDARKMODEFORWINDOW_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); if *DARK_MODE_SUPPORTED { if let Some(_allow_dark_mode_for_window) = *ALLOW_DARK_MODE_FOR_WINDOW { @@ -210,17 +213,18 @@ fn should_apps_use_dark_mode() -> bool { // See https://github.com/tauri-apps/tao/pull/1165 const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: u16 = 132; type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool; - static SHOULD_APPS_USE_DARK_MODE: Lazy> = Lazy::new(|| unsafe { - if HMODULE(*HUXTHEME as _).is_invalid() { - return None; - } + static SHOULD_APPS_USE_DARK_MODE: LazyLock> = + LazyLock::new(|| unsafe { + if HMODULE(*HUXTHEME as _).is_invalid() { + return None; + } - GetProcAddress( - HMODULE(*HUXTHEME as _), - PCSTR::from_raw(UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL as usize as *mut _), - ) - .map(|handle| std::mem::transmute(handle)) - }); + GetProcAddress( + HMODULE(*HUXTHEME as _), + PCSTR::from_raw(UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL as usize as *mut _), + ) + .map(|handle| std::mem::transmute(handle)) + }); SHOULD_APPS_USE_DARK_MODE .map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() }) diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dpi.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dpi.rs similarity index 94% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dpi.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dpi.rs index e4eb427c7..25f1850bd 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/dpi.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/dpi.rs @@ -69,10 +69,6 @@ pub fn dpi_to_scale_factor(dpi: u32) -> f64 { } pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { - let hdc = GetDC(Some(hwnd)); - if hdc.is_invalid() { - panic!("[tao] `GetDC` returned null!"); - } if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW { // We are on Windows 10 Anniversary Update (1607) or later. match GetDpiForWindow(hwnd) { @@ -96,9 +92,15 @@ pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 { } else { // We are on Vista or later. if IsProcessDPIAware().as_bool() { + let hdc = GetDC(Some(hwnd)); + if hdc.is_invalid() { + panic!("[tao] `GetDC` returned null!"); + } // If the process is DPI aware, then scaling must be handled by the application using // this DPI value. - GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32 + let dpi = GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32; + ReleaseDC(Some(hwnd), hdc); + dpi } else { // If the process is DPI unaware, then scaling is performed by the OS; we thus return // 96 (scale factor 1.0) to prevent the window from being re-scaled by both the diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/drop_handler.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/drop_handler.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/drop_handler.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/drop_handler.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop.rs similarity index 82% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop.rs index d580d0d58..2d8859767 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop.rs @@ -7,7 +7,6 @@ mod runner; use crossbeam_channel::{self as channel, Receiver, Sender}; -use once_cell::sync::Lazy; use parking_lot::Mutex; use std::{ cell::Cell, @@ -16,19 +15,16 @@ use std::{ marker::PhantomData, mem, panic, rc::Rc, - sync::Arc, + sync::{Arc, LazyLock}, thread, time::{Duration, Instant}, }; use windows::{ - core::{s, BOOL, PCWSTR}, + core::{s, w, BOOL, PCWSTR}, Win32::{ - Foundation::{ - HANDLE, HINSTANCE, HWND, LPARAM, LRESULT, POINT, RECT, TRUE, WAIT_TIMEOUT, WPARAM, - }, + Foundation::{HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, TRUE, WAIT_TIMEOUT, WPARAM}, Graphics::Gdi::*, System::{ - LibraryLoader::GetModuleHandleW, Ole::{IDropTarget, RevokeDragDrop}, Threading::{GetCurrentThreadId, INFINITE}, }, @@ -36,8 +32,7 @@ use windows::{ Controls::{self as win32c, HOVER_DEFAULT}, Input::{KeyboardAndMouse::*, Pointer::*, Touch::*, *}, Shell::{ - DefSubclassProc, RemoveWindowSubclass, SHAppBarMessage, SetWindowSubclass, ABE_BOTTOM, - ABE_LEFT, ABE_RIGHT, ABE_TOP, ABM_GETAUTOHIDEBAR, APPBARDATA, + SHAppBarMessage, ABE_BOTTOM, ABE_LEFT, ABE_RIGHT, ABE_TOP, ABM_GETAUTOHIDEBAR, APPBARDATA, }, WindowsAndMessaging::{self as win32wm, *}, }, @@ -53,13 +48,13 @@ use crate::{ monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ dark_mode::try_window_theme, - dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, - keyboard::is_msg_keyboard_related, - keyboard_layout::LAYOUT_CACHE, - minimal_ime::is_msg_ime_related, + dpi::{become_dpi_aware, dpi_to_scale_factor}, + ime::ImeContext, + keyboard::KeyEventBuilder, + keyboard_layout::get_agnostic_mods, monitor::{self, MonitorHandle}, raw_input, util, - window::set_skip_taskbar, + window::{set_skip_taskbar, InitData}, window_state::{CursorFlags, WindowFlags, WindowState}, wrap_device_id, WindowId, DEVICE_ID, }, @@ -97,38 +92,39 @@ type GetPointerTouchInfo = type GetPointerPenInfo = unsafe extern "system" fn(pointId: u32, penInfo: *mut POINTER_PEN_INFO) -> BOOL; -static GET_POINTER_FRAME_INFO_HISTORY: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory)); -static SKIP_POINTER_FRAME_MESSAGES: Lazy> = - Lazy::new(|| get_function!("user32.dll", SkipPointerFrameMessages)); -static GET_POINTER_DEVICE_RECTS: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerDeviceRects)); -static GET_POINTER_TOUCH_INFO: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerTouchInfo)); -static GET_POINTER_PEN_INFO: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetPointerPenInfo)); - -pub(crate) struct SubclassInput { +static GET_POINTER_FRAME_INFO_HISTORY: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory)); +static SKIP_POINTER_FRAME_MESSAGES: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SkipPointerFrameMessages)); +static GET_POINTER_DEVICE_RECTS: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerDeviceRects)); +static GET_POINTER_TOUCH_INFO: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerTouchInfo)); +static GET_POINTER_PEN_INFO: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetPointerPenInfo)); + +pub(crate) struct WindowData { pub window_state: Arc>, pub event_loop_runner: EventLoopRunnerShared, + pub key_event_builder: KeyEventBuilder, pub _file_drop_handler: Option, - pub subclass_removed: Cell, + pub userdata_removed: Cell, pub recurse_depth: Cell, pub event_loop_preferred_theme: Arc>>, } -impl SubclassInput { +impl WindowData { unsafe fn send_event(&self, event: Event<'_, T>) { self.event_loop_runner.send_event(event); } } -struct ThreadMsgTargetSubclassInput { +struct ThreadMsgTargetData { event_loop_runner: EventLoopRunnerShared, user_event_receiver: Receiver, } -impl ThreadMsgTargetSubclassInput { +impl ThreadMsgTargetData { unsafe fn send_event(&self, event: Event<'_, T>) { self.event_loop_runner.send_event(event); } @@ -136,8 +132,7 @@ impl ThreadMsgTargetSubclassInput { /// The result of a subclass procedure (the message handling callback) pub(crate) enum ProcResult { - DefSubclassProc, // <- this should be the default value - DefWindowProc, + DefWindowProc, // <- this should be the default value Value(LRESULT), } @@ -190,7 +185,7 @@ impl EventLoop { become_dpi_aware(); } - let thread_msg_target = create_event_target_window(); + let thread_msg_target = create_event_target_window::(); super::dark_mode::allow_dark_mode_for_app(true); @@ -200,7 +195,8 @@ impl EventLoop { let runner_shared = Rc::new(EventLoopRunner::new(thread_msg_target, wait_thread_id)); - let thread_msg_sender = subclass_event_target_window(thread_msg_target, runner_shared.clone()); + let thread_msg_sender = + insert_event_target_window_data::(thread_msg_target, runner_shared.clone()); raw_input::register_all_mice_and_keyboards_for_raw_input(thread_msg_target, Default::default()); EventLoop { @@ -533,16 +529,20 @@ impl EventLoopThreadExecutor { let raw = Box::into_raw(boxed2); - let res = PostMessageW( + let result = PostMessageW( Some(self.target_window), *EXEC_MSG_ID, WPARAM(raw as _), LPARAM(0), ); - assert!( - res.is_ok(), - "PostMessage failed ; is the messages queue full?" - ); + // This can happen when we outlive the parent [`EventLoopWindowTarget`] at which point the `target_window` has already been destroyed + if let Err(error) = result { + log::error!( + "PostMessage failed ; is the messages queue full? Error code {} - {}", + error.code(), + error.message() + ); + } } } } @@ -590,63 +590,63 @@ type WaitUntilInstantBox = Box; /// Message sent by the `EventLoopProxy` when we want to wake up the thread. /// WPARAM and LPARAM are unused. -static USER_EVENT_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WakeupMsg")) }); +static USER_EVENT_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WakeupMsg")) }); /// Message sent when we want to execute a closure in the thread. /// WPARAM contains a Box> that must be retrieved with `Box::from_raw`, /// and LPARAM is unused. -static EXEC_MSG_ID: Lazy = Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ExecMsg")) }); -static PROCESS_NEW_EVENTS_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ProcessNewEvents")) }); +static EXEC_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ExecMsg")) }); +static PROCESS_NEW_EVENTS_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ProcessNewEvents")) }); /// lparam is the wait thread's message id. -static SEND_WAIT_THREAD_ID_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SendWaitThreadId")) }); +static SEND_WAIT_THREAD_ID_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SendWaitThreadId")) }); /// lparam points to a `Box` signifying the time `PROCESS_NEW_EVENTS_MSG_ID` should /// be sent. -static WAIT_UNTIL_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WaitUntil")) }); -static CANCEL_WAIT_UNTIL_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::CancelWaitUntil")) }); +static WAIT_UNTIL_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::WaitUntil")) }); +static CANCEL_WAIT_UNTIL_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::CancelWaitUntil")) }); /// Message sent by a `Window` when it wants to be destroyed by the main thread. /// WPARAM and LPARAM are unused. -pub static DESTROY_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::DestroyMsg")) }); +pub static DESTROY_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::DestroyMsg")) }); /// WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the /// documentation in the `window_state` module for more information. -pub static SET_RETAIN_STATE_ON_SIZE_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SetRetainMaximized")) }); +pub static SET_RETAIN_STATE_ON_SIZE_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::SetRetainMaximized")) }); /// Message sent by event loop when event loop's prefered theme changed. /// WPARAM and LPARAM are unused. -pub static CHANGE_THEME_MSG_ID: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ChangeTheme")) }); +pub static CHANGE_THEME_MSG_ID: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("Tao::ChangeTheme")) }); /// When the taskbar is created, it registers a message with the "TaskbarCreated" string and then broadcasts this message to all top-level windows /// When the application receives this message, it should assume that any taskbar icons it added have been removed and add them again. -pub static S_U_TASKBAR_RESTART: Lazy = - Lazy::new(|| unsafe { RegisterWindowMessageA(s!("TaskbarCreated")) }); -static THREAD_EVENT_TARGET_WINDOW_CLASS: Lazy> = Lazy::new(|| unsafe { - let class_name = util::encode_wide("Tao Thread Event Target"); - - let class = WNDCLASSEXW { - cbSize: mem::size_of::() as u32, - style: Default::default(), - lpfnWndProc: Some(util::call_default_window_proc), - cbClsExtra: 0, - cbWndExtra: 0, - hInstance: HINSTANCE(GetModuleHandleW(PCWSTR::null()).unwrap_or_default().0), - hIcon: HICON::default(), - hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly - hbrBackground: HBRUSH::default(), - lpszMenuName: PCWSTR::null(), - lpszClassName: PCWSTR::from_raw(class_name.as_ptr()), - hIconSm: HICON::default(), - }; +pub static S_U_TASKBAR_RESTART: LazyLock = + LazyLock::new(|| unsafe { RegisterWindowMessageA(s!("TaskbarCreated")) }); - RegisterClassExW(&class); +fn create_event_target_window() -> HWND { + let thread_event_target_window_class = w!("Tao Thread Event Target"); - class_name -}); + unsafe { + let class = WNDCLASSEXW { + cbSize: mem::size_of::() as u32, + style: Default::default(), + lpfnWndProc: Some(thread_event_target_callback::), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: util::get_instance_handle(), + hIcon: HICON::default(), + hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly + hbrBackground: HBRUSH::default(), + lpszMenuName: PCWSTR::null(), + lpszClassName: thread_event_target_window_class, + hIconSm: HICON::default(), + }; + + RegisterClassExW(&class); + } -fn create_event_target_window() -> HWND { let window = unsafe { CreateWindowExW( WS_EX_NOACTIVATE | WS_EX_TRANSPARENT | WS_EX_LAYERED | @@ -658,7 +658,7 @@ fn create_event_target_window() -> HWND { // `explorer.exe` and then starting the process back up. // It is unclear why the bug is triggered by waiting for several hours. WS_EX_TOOLWINDOW, - PCWSTR::from_raw(THREAD_EVENT_TARGET_WINDOW_CLASS.clone().as_ptr()), + thread_event_target_window_class, PCWSTR::null(), WS_OVERLAPPED, 0, @@ -667,7 +667,7 @@ fn create_event_target_window() -> HWND { 0, None, None, - GetModuleHandleW(PCWSTR::null()).map(Into::into).ok(), + Some(util::get_instance_handle()), None, ) }; @@ -688,39 +688,21 @@ fn create_event_target_window() -> HWND { window } -fn subclass_event_target_window( - window: HWND, +fn insert_event_target_window_data( + thread_msg_target: HWND, event_loop_runner: EventLoopRunnerShared, ) -> Sender { - unsafe { - let (tx, rx) = channel::unbounded(); + let (tx, rx) = channel::unbounded(); - let subclass_input = ThreadMsgTargetSubclassInput { - event_loop_runner, - user_event_receiver: rx, - }; - let input_ptr = Box::into_raw(Box::new(subclass_input)); - let subclass_result = SetWindowSubclass( - window, - Some(thread_event_target_callback::), - THREAD_EVENT_TARGET_SUBCLASS_ID, - input_ptr as usize, - ); - assert!(subclass_result.as_bool()); + let userdata = ThreadMsgTargetData { + event_loop_runner, + user_event_receiver: rx, + }; + let input_ptr = Box::into_raw(Box::new(userdata)); - tx - } -} + util::SetWindowLongPtrW(thread_msg_target, GWL_USERDATA, input_ptr as isize); -fn remove_event_target_window_subclass(window: HWND) { - let removal_result = unsafe { - RemoveWindowSubclass( - window, - Some(thread_event_target_callback::), - THREAD_EVENT_TARGET_SUBCLASS_ID, - ) - }; - assert!(removal_result.as_bool()); + tx } /// Capture mouse input, allowing `window` to receive mouse events when the cursor is outside of @@ -741,33 +723,6 @@ unsafe fn release_mouse(mut window_state: parking_lot::MutexGuard<'_, WindowStat } } -const WINDOW_SUBCLASS_ID: usize = 0; -const THREAD_EVENT_TARGET_SUBCLASS_ID: usize = 1; -pub(crate) fn subclass_window(window: HWND, subclass_input: SubclassInput) { - subclass_input.event_loop_runner.register_window(window); - let input_ptr = Box::into_raw(Box::new(subclass_input)); - let subclass_result = unsafe { - SetWindowSubclass( - window, - Some(public_window_callback::), - WINDOW_SUBCLASS_ID, - input_ptr as usize, - ) - }; - assert!(subclass_result.as_bool()); -} - -fn remove_window_subclass(window: HWND) { - let removal_result = unsafe { - RemoveWindowSubclass( - window, - Some(public_window_callback::), - WINDOW_SUBCLASS_ID, - ) - }; - assert!(removal_result.as_bool()); -} - fn normalize_pointer_pressure(pressure: u32) -> Option { match pressure { 1..=1024 => Some(Force::Normalized(pressure as f64 / 1024.0)), @@ -846,11 +801,11 @@ unsafe fn process_control_flow(runner: &EventLoopRunner) { /// Emit a `ModifiersChanged` event whenever modifiers have changed. /// Returns the current modifier state -fn update_modifiers(window: HWND, subclass_input: &SubclassInput) -> ModifiersState { +fn update_modifiers(window: HWND, useredata: &WindowData) -> ModifiersState { use crate::event::WindowEvent::ModifiersChanged; - let modifiers = LAYOUT_CACHE.lock().get_agnostic_mods(); - let mut window_state = subclass_input.window_state.lock(); + let modifiers = get_agnostic_mods(); + let mut window_state = useredata.window_state.lock(); if window_state.modifiers_state != modifiers { window_state.modifiers_state = modifiers; @@ -858,7 +813,7 @@ fn update_modifiers(window: HWND, subclass_input: &SubclassInput) -> Modif drop(window_state); unsafe { - subclass_input.send_event(Event::WindowEvent { + useredata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: ModifiersChanged(modifiers), }); @@ -867,26 +822,26 @@ fn update_modifiers(window: HWND, subclass_input: &SubclassInput) -> Modif modifiers } -unsafe fn gain_active_focus(window: HWND, subclass_input: &SubclassInput) { +unsafe fn gain_active_focus(window: HWND, userdata: &WindowData) { use crate::event::WindowEvent::Focused; - update_modifiers(window, subclass_input); + update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: Focused(true), }); } -unsafe fn lose_active_focus(window: HWND, subclass_input: &SubclassInput) { +unsafe fn lose_active_focus(window: HWND, userdata: &WindowData) { use crate::event::WindowEvent::{Focused, ModifiersChanged}; - subclass_input.window_state.lock().modifiers_state = ModifiersState::empty(); - subclass_input.send_event(Event::WindowEvent { + userdata.window_state.lock().modifiers_state = ModifiersState::empty(); + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: ModifiersChanged(ModifiersState::empty()), }); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: Focused(false), }); @@ -899,36 +854,58 @@ unsafe fn lose_active_focus(window: HWND, subclass_input: &SubclassInput) // // Returning 0 tells the Win32 API that the message has been processed. // FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary -unsafe extern "system" fn public_window_callback( +pub(super) unsafe extern "system" fn public_window_callback( window: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM, - uidsubclass: usize, - subclass_input_ptr: usize, ) -> LRESULT { - let subclass_input_ptr = subclass_input_ptr as *mut SubclassInput; - let (result, subclass_removed, recurse_depth) = { - let subclass_input = &*subclass_input_ptr; - subclass_input - .recurse_depth - .set(subclass_input.recurse_depth.get() + 1); + let userdata = util::GetWindowLongPtrW(window, GWL_USERDATA); + let userdata_ptr = match (userdata, msg) { + (0, WM_NCCREATE) => { + let createstruct = &mut *(lparam.0 as *mut CREATESTRUCTW); + let initdata = createstruct.lpCreateParams; + let initdata = &mut *(initdata as *mut InitData<'_, T>); + + return match unsafe { initdata.on_nccreate(window) } { + Some(userdata) => unsafe { + util::SetWindowLongPtrW(window, GWL_USERDATA, userdata); + DefWindowProcW(window, msg, wparam, lparam) + }, + None => LRESULT(-1), // failed to create the window + }; + } + // Getting here should quite frankly be impossible, + // but we'll make window creation fail here just in case. + (0, WM_CREATE) => return LRESULT(-1), + (_, WM_CREATE) => unsafe { + let createstruct = &mut *(lparam.0 as *mut CREATESTRUCTW); + let initdata = createstruct.lpCreateParams; + let initdata = &mut *(initdata as *mut InitData<'_, T>); - // Clear userdata - util::SetWindowLongPtrW(window, GWL_USERDATA, 0); + initdata.on_create(); + return DefWindowProcW(window, msg, wparam, lparam); + }, + (0, _) => return DefWindowProcW(window, msg, wparam, lparam), + _ => userdata as *mut WindowData, + }; + + let (result, userdata_removed, recurse_depth) = { + let userdata = &*(userdata_ptr); + + userdata.recurse_depth.set(userdata.recurse_depth.get() + 1); - let result = - public_window_callback_inner(window, msg, wparam, lparam, uidsubclass, subclass_input); + let result = public_window_callback_inner(window, msg, wparam, lparam, userdata); - let subclass_removed = subclass_input.subclass_removed.get(); - let recurse_depth = subclass_input.recurse_depth.get() - 1; - subclass_input.recurse_depth.set(recurse_depth); + let userdata_removed = userdata.userdata_removed.get(); + let recurse_depth = userdata.recurse_depth.get() - 1; + userdata.recurse_depth.set(recurse_depth); - (result, subclass_removed, recurse_depth) + (result, userdata_removed, recurse_depth) }; - if subclass_removed && recurse_depth == 0 { - drop(Box::from_raw(subclass_input_ptr)) + if userdata_removed && recurse_depth == 0 { + drop(Box::from_raw(userdata_ptr)) } result @@ -939,50 +916,38 @@ unsafe fn public_window_callback_inner( msg: u32, wparam: WPARAM, lparam: LPARAM, - _: usize, - subclass_input: &SubclassInput, + userdata: &WindowData, ) -> LRESULT { let _ = RedrawWindow( - Some(subclass_input.event_loop_runner.thread_msg_target()), + Some(userdata.event_loop_runner.thread_msg_target()), None, None, RDW_INTERNALPAINT, ); - let mut result = ProcResult::DefSubclassProc; + let mut result = ProcResult::DefWindowProc; // Send new modifiers before sending key events. let mods_changed_callback = || match msg { win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN | win32wm::WM_KEYUP | win32wm::WM_SYSKEYUP => { - update_modifiers(window, subclass_input); + update_modifiers(window, userdata); result = ProcResult::Value(LRESULT(0)); } _ => (), }; - subclass_input + userdata .event_loop_runner .catch_unwind(mods_changed_callback) .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); let keyboard_callback = || { use crate::event::WindowEvent::KeyboardInput; - let is_keyboard_related = is_msg_keyboard_related(msg); - if !is_keyboard_related { - // We return early to avoid a deadlock from locking the window state - // when not appropriate. - return; - } - let events = { - let mut key_event_builders = - crate::platform_impl::platform::keyboard::KEY_EVENT_BUILDERS.lock(); - if let Some(key_event_builder) = key_event_builders.get_mut(&WindowId(window.0 as _)) { - key_event_builder.process_message(window, msg, wparam, lparam, &mut result) - } else { - Vec::new() - } - }; + let events = + userdata + .key_event_builder + .process_message(window, msg, wparam, lparam, &mut result); for event in events { - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: KeyboardInput { device_id: DEVICE_ID, @@ -992,41 +957,17 @@ unsafe fn public_window_callback_inner( }); } }; - subclass_input + userdata .event_loop_runner .catch_unwind(keyboard_callback) .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); - let ime_callback = || { - use crate::event::WindowEvent::ReceivedImeText; - let is_ime_related = is_msg_ime_related(msg); - if !is_ime_related { - return; - } - let text = { - let mut window_state = subclass_input.window_state.lock(); - window_state - .ime_handler - .process_message(window, msg, wparam, lparam, &mut result) - }; - if let Some(str) = text { - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window.0 as _)), - event: ReceivedImeText(str), - }); - } - }; - subclass_input - .event_loop_runner - .catch_unwind(ime_callback) - .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); - // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing // the closure to catch_unwind directly so that the match body indendation wouldn't change and // the git blame and history would be preserved. let callback = || match msg { win32wm::WM_ENTERSIZEMOVE => { - subclass_input + userdata .window_state .lock() .set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE)); @@ -1034,7 +975,7 @@ unsafe fn public_window_callback_inner( } win32wm::WM_EXITSIZEMOVE => { - let mut state = subclass_input.window_state.lock(); + let mut state = userdata.window_state.lock(); if state.dragging { state.dragging = false; let _ = unsafe { PostMessageW(Some(window), WM_LBUTTONUP, WPARAM::default(), lparam) }; @@ -1043,16 +984,13 @@ unsafe fn public_window_callback_inner( result = ProcResult::Value(LRESULT(0)); } - win32wm::WM_NCCREATE => { - enable_non_client_dpi_scaling(window); - } win32wm::WM_NCLBUTTONDOWN => { if wparam.0 == HTCAPTION as _ { let _ = PostMessageW(Some(window), WM_MOUSEMOVE, WPARAM(0), lparam); } use crate::event::WindowEvent::DecorationsClick; - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: DecorationsClick, }); @@ -1060,7 +998,7 @@ unsafe fn public_window_callback_inner( win32wm::WM_CLOSE => { use crate::event::WindowEvent::CloseRequested; - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: CloseRequested, }); @@ -1070,39 +1008,39 @@ unsafe fn public_window_callback_inner( win32wm::WM_DESTROY => { use crate::event::WindowEvent::Destroyed; let _ = RevokeDragDrop(window); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: Destroyed, }); - subclass_input.event_loop_runner.remove_window(window); + userdata.event_loop_runner.remove_window(window); result = ProcResult::Value(LRESULT(0)); } win32wm::WM_NCDESTROY => { - remove_window_subclass::(window); - subclass_input.subclass_removed.set(true); + util::SetWindowLongPtrW(window, GWL_USERDATA, 0); + userdata.userdata_removed.set(true); result = ProcResult::Value(LRESULT(0)); } win32wm::WM_PAINT => { - if subclass_input.event_loop_runner.should_buffer() { + if userdata.event_loop_runner.should_buffer() { // this branch can happen in response to `UpdateWindow`, if win32 decides to // redraw the window outside the normal flow of the event loop. let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); } else { - let managing_redraw = flush_paint_messages(Some(window), &subclass_input.event_loop_runner); - subclass_input.send_event(Event::RedrawRequested(RootWindowId(WindowId( + let managing_redraw = flush_paint_messages(Some(window), &userdata.event_loop_runner); + userdata.send_event(Event::RedrawRequested(RootWindowId(WindowId( window.0 as _, )))); if managing_redraw { - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); + userdata.event_loop_runner.redraw_events_cleared(); + process_control_flow(&userdata.event_loop_runner); } } } win32wm::WM_ERASEBKGND => { - let w = subclass_input.window_state.lock(); + let w = userdata.window_state.lock(); if let Some(color) = w.background_color { let hdc = HDC(wparam.0 as *mut _); let mut rc = RECT::default(); @@ -1113,15 +1051,15 @@ unsafe fn public_window_callback_inner( result = ProcResult::Value(LRESULT(1)); } else { - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } } else { - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } } win32wm::WM_WINDOWPOSCHANGING => { - let mut window_state = subclass_input.window_state.lock(); + let mut window_state = userdata.window_state.lock(); if let Some(ref mut fullscreen) = window_state.fullscreen { let window_pos = &mut *(lparam.0 as *mut WINDOWPOS); @@ -1214,14 +1152,14 @@ unsafe fn public_window_callback_inner( let windowpos = lparam.0 as *const WINDOWPOS; if (*windowpos).flags & SWP_NOMOVE != SWP_NOMOVE { let physical_position = PhysicalPosition::new((*windowpos).x, (*windowpos).y); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: Moved(physical_position), }); } // This is necessary for us to still get sent WM_SIZE. - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } win32wm::WM_SIZE => { @@ -1236,7 +1174,7 @@ unsafe fn public_window_callback_inner( }; { - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); // See WindowFlags::MARKER_RETAIN_STATE_ON_SIZE docs for info on why this `if` check exists. if !w .window_flags() @@ -1247,24 +1185,36 @@ unsafe fn public_window_callback_inner( } } - subclass_input.send_event(event); + userdata.send_event(event); result = ProcResult::Value(LRESULT(0)); } + // TODO: Catch up with winit on the IME events, + // `WindowEvent::ReceivedImeText` currently maps to `WindowEvent::Ime(Ime::Commit(text))` + win32wm::WM_IME_ENDCOMPOSITION => { + let ime_context = unsafe { ImeContext::current(window) }; + if let Some(text) = unsafe { ime_context.get_composed_text() } { + userdata.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window.0 as _)), + event: WindowEvent::ReceivedImeText(text), + }); + } + } + // this is necessary for us to maintain minimize/restore state win32wm::WM_SYSCOMMAND => { if wparam.0 == SC_RESTORE as _ { - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, false)); } if wparam.0 == SC_MINIMIZE as _ { - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, true)); } // Send `WindowEvent::Minimized` here if we decide to implement one if wparam.0 == SC_SCREENSAVE as _ { - let window_state = subclass_input.window_state.lock(); + let window_state = userdata.window_state.lock(); if window_state.fullscreen.is_some() { result = ProcResult::Value(LRESULT(0)); return; @@ -1277,7 +1227,7 @@ unsafe fn public_window_callback_inner( win32wm::WM_MOUSEMOVE => { use crate::event::WindowEvent::{CursorEntered, CursorMoved}; let mouse_was_outside_window = { - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); let was_outside_window = !w.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW); w.mouse @@ -1287,7 +1237,7 @@ unsafe fn public_window_callback_inner( }; if mouse_was_outside_window { - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: CursorEntered { device_id: DEVICE_ID, @@ -1311,13 +1261,13 @@ unsafe fn public_window_callback_inner( // handle spurious WM_MOUSEMOVE messages // see https://devblogs.microsoft.com/oldnewthing/20031001-00/?p=42343 // and http://debugandconquer.blogspot.com/2015/08/the-cause-of-spurious-mouse-move.html - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); cursor_moved = w.mouse.last_position != Some(position); w.mouse.last_position = Some(position); } if cursor_moved { - let modifiers = update_modifiers(window, subclass_input); - subclass_input.send_event(Event::WindowEvent { + let modifiers = update_modifiers(window, userdata); + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: CursorMoved { device_id: DEVICE_ID, @@ -1333,13 +1283,13 @@ unsafe fn public_window_callback_inner( win32c::WM_MOUSELEAVE => { use crate::event::WindowEvent::CursorLeft; { - let mut w = subclass_input.window_state.lock(); + let mut w = userdata.window_state.lock(); w.mouse .set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false)) .ok(); } - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: CursorLeft { device_id: DEVICE_ID, @@ -1355,7 +1305,7 @@ unsafe fn public_window_callback_inner( let value = f32::from(util::GET_WHEEL_DELTA_WPARAM(wparam)); let value = value / WHEEL_DELTA as f32; - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); let mut scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA; @@ -1371,7 +1321,7 @@ unsafe fn public_window_callback_inner( scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA; } - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: WindowEvent::MouseWheel { device_id: DEVICE_ID, @@ -1390,7 +1340,7 @@ unsafe fn public_window_callback_inner( let value = f32::from(util::GET_WHEEL_DELTA_WPARAM(wparam)); let value = value / WHEEL_DELTA as f32; - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); let mut scroll_characters = DEFAULT_SCROLL_CHARACTERS_PER_WHEEL_DELTA; @@ -1401,7 +1351,7 @@ unsafe fn public_window_callback_inner( SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0), ); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: WindowEvent::MouseWheel { device_id: DEVICE_ID, @@ -1416,18 +1366,18 @@ unsafe fn public_window_callback_inner( win32wm::WM_KEYDOWN | win32wm::WM_SYSKEYDOWN => { if msg == WM_SYSKEYDOWN && wparam.0 == usize::from(VK_F4.0) { - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } } win32wm::WM_LBUTTONDOWN => { use crate::event::{ElementState::Pressed, MouseButton::Left, WindowEvent::MouseInput}; - capture_mouse(window, &mut subclass_input.window_state.lock()); + capture_mouse(window, &mut userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1442,11 +1392,11 @@ unsafe fn public_window_callback_inner( win32wm::WM_LBUTTONUP => { use crate::event::{ElementState::Released, MouseButton::Left, WindowEvent::MouseInput}; - release_mouse(subclass_input.window_state.lock()); + release_mouse(userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1461,11 +1411,11 @@ unsafe fn public_window_callback_inner( win32wm::WM_RBUTTONDOWN => { use crate::event::{ElementState::Pressed, MouseButton::Right, WindowEvent::MouseInput}; - capture_mouse(window, &mut subclass_input.window_state.lock()); + capture_mouse(window, &mut userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1480,11 +1430,11 @@ unsafe fn public_window_callback_inner( win32wm::WM_RBUTTONUP => { use crate::event::{ElementState::Released, MouseButton::Right, WindowEvent::MouseInput}; - release_mouse(subclass_input.window_state.lock()); + release_mouse(userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1499,11 +1449,11 @@ unsafe fn public_window_callback_inner( win32wm::WM_MBUTTONDOWN => { use crate::event::{ElementState::Pressed, MouseButton::Middle, WindowEvent::MouseInput}; - capture_mouse(window, &mut subclass_input.window_state.lock()); + capture_mouse(window, &mut userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1518,11 +1468,11 @@ unsafe fn public_window_callback_inner( win32wm::WM_MBUTTONUP => { use crate::event::{ElementState::Released, MouseButton::Middle, WindowEvent::MouseInput}; - release_mouse(subclass_input.window_state.lock()); + release_mouse(userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1538,11 +1488,11 @@ unsafe fn public_window_callback_inner( use crate::event::{ElementState::Pressed, MouseButton::Other, WindowEvent::MouseInput}; let xbutton = util::GET_XBUTTON_WPARAM(wparam); - capture_mouse(window, &mut subclass_input.window_state.lock()); + capture_mouse(window, &mut userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1558,11 +1508,11 @@ unsafe fn public_window_callback_inner( use crate::event::{ElementState::Released, MouseButton::Other, WindowEvent::MouseInput}; let xbutton = util::GET_XBUTTON_WPARAM(wparam); - release_mouse(subclass_input.window_state.lock()); + release_mouse(userdata.window_state.lock()); - let modifiers = update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, userdata); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: MouseInput { device_id: DEVICE_ID, @@ -1580,7 +1530,7 @@ unsafe fn public_window_callback_inner( // can happen if `SetCapture` is called on our window when it already has the mouse // capture. if lparam.0 != window.0 as _ { - subclass_input.window_state.lock().mouse.capture_count = 0; + userdata.window_state.lock().mouse.capture_count = 0; } result = ProcResult::Value(LRESULT(0)); } @@ -1614,7 +1564,7 @@ unsafe fn public_window_callback_inner( let x = location.x as f64 + (input.x % 100) as f64 / 100f64; let y = location.y as f64 + (input.y % 100) as f64 / 100f64; let location = PhysicalPosition::new(x, y); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: WindowEvent::Touch(Touch { phase: if (input.dwFlags & TOUCHEVENTF_DOWN) != Default::default() { @@ -1750,7 +1700,7 @@ unsafe fn public_window_callback_inner( let x = location.x as f64 + x.fract(); let y = location.y as f64 + y.fract(); let location = PhysicalPosition::new(x, y); - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: WindowEvent::Touch(Touch { phase: if (pointer_info.pointerFlags & POINTER_FLAG_DOWN) != Default::default() { @@ -1778,36 +1728,36 @@ unsafe fn public_window_callback_inner( win32wm::WM_NCACTIVATE => { let is_active = wparam != WPARAM(0); - let active_focus_changed = subclass_input.window_state.lock().set_active(is_active); + let active_focus_changed = userdata.window_state.lock().set_active(is_active); if active_focus_changed { if is_active { - gain_active_focus(window, subclass_input); + gain_active_focus(window, userdata); } else { - lose_active_focus(window, subclass_input); + lose_active_focus(window, userdata); } } result = ProcResult::DefWindowProc; } win32wm::WM_SETFOCUS => { - let active_focus_changed = subclass_input.window_state.lock().set_focused(true); + let active_focus_changed = userdata.window_state.lock().set_focused(true); if active_focus_changed { - gain_active_focus(window, subclass_input); + gain_active_focus(window, userdata); } result = ProcResult::Value(LRESULT(0)); } win32wm::WM_KILLFOCUS => { - let active_focus_changed = subclass_input.window_state.lock().set_focused(false); + let active_focus_changed = userdata.window_state.lock().set_focused(false); if active_focus_changed { - lose_active_focus(window, subclass_input); + lose_active_focus(window, userdata); } result = ProcResult::Value(LRESULT(0)); } win32wm::WM_SETCURSOR => { let set_cursor_to = { - let window_state = subclass_input.window_state.lock(); + let window_state = userdata.window_state.lock(); // The return value for the preceding `WM_NCHITTEST` message is conveniently // provided through the low-order word of lParam. We use that here since // `WM_MOUSEMOVE` seems to come after `WM_SETCURSOR` for a given cursor movement. @@ -1833,7 +1783,7 @@ unsafe fn public_window_callback_inner( win32wm::WM_GETMINMAXINFO => { let mmi = lparam.0 as *mut MINMAXINFO; - let window_state = subclass_input.window_state.lock(); + let window_state = userdata.window_state.lock(); let is_decorated = window_state .window_flags() .contains(WindowFlags::MARKER_DECORATIONS); @@ -1896,7 +1846,7 @@ unsafe fn public_window_callback_inner( let old_scale_factor: f64; let (allow_resize, is_decorated) = { - let mut window_state = subclass_input.window_state.lock(); + let mut window_state = userdata.window_state.lock(); old_scale_factor = window_state.scale_factor; window_state.scale_factor = new_scale_factor; @@ -1977,7 +1927,7 @@ unsafe fn public_window_callback_inner( new_physical_inner_size = old_physical_inner_size; } - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: ScaleFactorChanged { scale_factor: new_scale_factor, @@ -1988,7 +1938,7 @@ unsafe fn public_window_callback_inner( let dragging_window: bool; { - let window_state = subclass_input.window_state.lock(); + let window_state = userdata.window_state.lock(); dragging_window = window_state .window_flags() .contains(WindowFlags::MARKER_IN_SIZE_MOVE); @@ -2118,15 +2068,15 @@ unsafe fn public_window_callback_inner( } win32wm::WM_SETTINGCHANGE => { - update_theme(subclass_input, window, true); + update_theme(userdata, window, true); } win32wm::WM_NCCALCSIZE => { - let window_flags = subclass_input.window_state.lock().window_flags(); - let is_fullscreen = subclass_input.window_state.lock().fullscreen.is_some(); + let window_flags = userdata.window_state.lock().window_flags(); + let is_fullscreen = userdata.window_state.lock().fullscreen.is_some(); if wparam == WPARAM(0) || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } else { // adjust the maximized borderless window so it doesn't cover the taskbar if util::is_maximized(window).unwrap_or(false) { @@ -2175,8 +2125,8 @@ unsafe fn public_window_callback_inner( } } - win32wm::WM_NCHITTEST => { - let window_state = subclass_input.window_state.lock(); + WM_NCHITTEST => { + let window_state = userdata.window_state.lock(); let window_flags = window_state.window_flags(); // Allow resizing unmaximized non-fullscreen undecorated window @@ -2222,10 +2172,10 @@ unsafe fn public_window_callback_inner( result = hit_result .map(|r| ProcResult::Value(LRESULT(r as _))) - .unwrap_or(ProcResult::DefSubclassProc); + .unwrap_or(ProcResult::DefWindowProc); } } else { - result = ProcResult::DefSubclassProc; + result = ProcResult::DefWindowProc; } } @@ -2239,42 +2189,39 @@ unsafe fn public_window_callback_inner( let _ = DestroyWindow(window); result = ProcResult::Value(LRESULT(0)); } else if msg == *SET_RETAIN_STATE_ON_SIZE_MSG_ID { - let mut window_state = subclass_input.window_state.lock(); + let mut window_state = userdata.window_state.lock(); window_state.set_window_flags_in_place(|f| { f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam.0 != 0) }); result = ProcResult::Value(LRESULT(0)); } else if msg == *CHANGE_THEME_MSG_ID { - update_theme(subclass_input, window, false); + update_theme(userdata, window, false); result = ProcResult::Value(LRESULT(0)); } else if msg == *S_U_TASKBAR_RESTART { - let window_state = subclass_input.window_state.lock(); - let _ = set_skip_taskbar(window, window_state.skip_taskbar); + let skip_taskbar = userdata.window_state.lock().skip_taskbar; + if skip_taskbar { + let _ = set_skip_taskbar(window, true); + } } } }; - subclass_input + userdata .event_loop_runner .catch_unwind(callback) .unwrap_or_else(|| result = ProcResult::Value(LRESULT(-1))); match result { - ProcResult::DefSubclassProc => DefSubclassProc(window, msg, wparam, lparam), ProcResult::DefWindowProc => DefWindowProcW(window, msg, wparam, lparam), ProcResult::Value(val) => val, } } -fn update_theme( - subclass_input: &SubclassInput, - window: HWND, - from_settings_change_event: bool, -) { - let mut window_state = subclass_input.window_state.lock(); +fn update_theme(userdata: &WindowData, window: HWND, from_settings_change_event: bool) { + let mut window_state = userdata.window_state.lock(); let preferred_theme = window_state .preferred_theme - .or(*subclass_input.event_loop_preferred_theme.lock()); + .or(*userdata.event_loop_preferred_theme.lock()); if from_settings_change_event && preferred_theme.is_some() { return; } @@ -2283,7 +2230,7 @@ fn update_theme( window_state.current_theme = new_theme; mem::drop(window_state); unsafe { - subclass_input.send_event(Event::WindowEvent { + userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window.0 as _)), event: WindowEvent::ThemeChanged(new_theme), }) @@ -2309,20 +2256,24 @@ unsafe extern "system" fn thread_event_target_callback( msg: u32, wparam: WPARAM, lparam: LPARAM, - _: usize, - subclass_input_ptr: usize, ) -> LRESULT { - let subclass_input = Box::from_raw(subclass_input_ptr as *mut ThreadMsgTargetSubclassInput); + let userdata_ptr = util::GetWindowLongPtrW(window, GWL_USERDATA) as *mut ThreadMsgTargetData; + if userdata_ptr.is_null() { + // `userdata_ptr` will always be null for the first `WM_GETMINMAXINFO`, as well as + // `WM_NCCREATE` and `WM_CREATE`. + return DefWindowProcW(window, msg, wparam, lparam); + } + let userdata = Box::from_raw(userdata_ptr); - let mut subclass_removed = false; + let mut userdata_removed = false; // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing // the closure to catch_unwind directly so that the match body indendation wouldn't change and // the git blame and history would be preserved. let callback = || match msg { win32wm::WM_NCDESTROY => { - remove_event_target_window_subclass::(window); - subclass_removed = true; + util::SetWindowLongPtrW(window, GWL_USERDATA, 0); + userdata_removed = true; let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); LRESULT(0) } @@ -2333,25 +2284,22 @@ unsafe extern "system" fn thread_event_target_callback( // If the WM_PAINT handler in `public_window_callback` has already flushed the redraw // events, `handling_events` will return false and we won't emit a second // `RedrawEventsCleared` event. - if subclass_input.event_loop_runner.handling_events() { - if subclass_input.event_loop_runner.should_buffer() { + if userdata.event_loop_runner.handling_events() { + if userdata.event_loop_runner.should_buffer() { // This branch can be triggered when a nested win32 event loop is triggered // inside of the `event_handler` callback. let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); } else { // This WM_PAINT handler will never be re-entrant because `flush_paint_messages` // doesn't call WM_PAINT for the thread event target (i.e. this window). - assert!(flush_paint_messages( - None, - &subclass_input.event_loop_runner - )); - subclass_input.event_loop_runner.redraw_events_cleared(); - process_control_flow(&subclass_input.event_loop_runner); + assert!(flush_paint_messages(None, &userdata.event_loop_runner)); + userdata.event_loop_runner.redraw_events_cleared(); + process_control_flow(&userdata.event_loop_runner); } } // Default WM_PAINT behaviour. This makes sure modals and popups are shown immediatly when opening them. - DefSubclassProc(window, msg, wparam, lparam) + DefWindowProcW(window, msg, wparam, lparam) } win32wm::WM_INPUT_DEVICE_CHANGE => { @@ -2361,7 +2309,7 @@ unsafe extern "system" fn thread_event_target_callback( _ => unreachable!(), }; - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id: wrap_device_id(lparam.0), event, }); @@ -2372,11 +2320,11 @@ unsafe extern "system" fn thread_event_target_callback( win32wm::WM_INPUT => { if let Some(data) = raw_input::get_raw_input_data(HRAWINPUT(lparam.0 as _)) { - handle_raw_input(&subclass_input, data); + handle_raw_input(&userdata, data); let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); } - DefSubclassProc(window, msg, wparam, lparam) + DefWindowProcW(window, msg, wparam, lparam) } // We don't process `WM_QUERYENDSESSION` yet until we introduce the same mechanism as Tauri's `ExitRequested` event @@ -2385,15 +2333,21 @@ unsafe extern "system" fn thread_event_target_callback( // `wParam` is `FALSE` is for if the shutdown gets canceled, // and we don't need to handle that case since we didn't do anything prior in response to `WM_QUERYENDSESSION` if wparam.0 == TRUE.0 as usize { - subclass_input.event_loop_runner.loop_destroyed(); + userdata.event_loop_runner.loop_destroyed(); + // `WM_ENDSESSION` can be sent by Windows during shutdown or by Restart Manager + // + // - From Windows shutdown: Windows will shut us down after we return `0` here + // - From Restart Manager: Restart Manager only sends the message but will wait for us to shutdown ourselves + // + // So, we call `exit` here directly to align this behavior + std::process::exit(0); } - // Note: after we return 0 here, Windows will shut us down LRESULT(0) } _ if msg == *USER_EVENT_MSG_ID => { - if let Ok(event) = subclass_input.user_event_receiver.recv() { - subclass_input.send_event(Event::UserEvent(event)); + if let Ok(event) = userdata.user_event_receiver.recv() { + userdata.send_event(Event::UserEvent(event)); } let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); LRESULT(0) @@ -2406,7 +2360,7 @@ unsafe extern "system" fn thread_event_target_callback( } _ if msg == *PROCESS_NEW_EVENTS_MSG_ID => { let _ = PostThreadMessageW( - subclass_input.event_loop_runner.wait_thread_id(), + userdata.event_loop_runner.wait_thread_id(), *CANCEL_WAIT_UNTIL_MSG_ID, WPARAM(0), LPARAM(0), @@ -2414,7 +2368,7 @@ unsafe extern "system" fn thread_event_target_callback( // if the control_flow is WaitUntil, make sure the given moment has actually passed // before emitting NewEvents - if let ControlFlow::WaitUntil(wait_until) = subclass_input.event_loop_runner.control_flow() { + if let ControlFlow::WaitUntil(wait_until) = userdata.event_loop_runner.control_flow() { let mut msg = MSG::default(); while Instant::now() < wait_until { if PeekMessageW(&mut msg, None, 0, 0, PM_NOREMOVE).as_bool() { @@ -2435,31 +2389,26 @@ unsafe extern "system" fn thread_event_target_callback( } } } - subclass_input.event_loop_runner.poll(); + userdata.event_loop_runner.poll(); let _ = RedrawWindow(Some(window), None, None, RDW_INTERNALPAINT); LRESULT(0) } - _ => DefSubclassProc(window, msg, wparam, lparam), + _ => DefWindowProcW(window, msg, wparam, lparam), }; - let result = subclass_input + let result = userdata .event_loop_runner .catch_unwind(callback) .unwrap_or(LRESULT(-1)); - if subclass_removed { - mem::drop(subclass_input); + if userdata_removed { + drop(userdata); } else { - // FIXME: this seems to leak intentionally? - #[allow(unused_must_use)] - Box::into_raw(subclass_input); + Box::leak(userdata); } result } -unsafe fn handle_raw_input( - subclass_input: &ThreadMsgTargetSubclassInput, - data: RAWINPUT, -) { +unsafe fn handle_raw_input(userdata: &ThreadMsgTargetData, data: RAWINPUT) { use crate::event::{ DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, ElementState::{Pressed, Released}, @@ -2476,21 +2425,21 @@ unsafe fn handle_raw_input( let y = mouse.lLastY as f64; if x != 0.0 { - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: Motion { axis: 0, value: x }, }); } if y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: Motion { axis: 1, value: y }, }); } if x != 0.0 || y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: MouseMotion { delta: (x, y) }, }); @@ -2503,7 +2452,7 @@ unsafe fn handle_raw_input( ) { // We must cast to SHORT first, becaues `usButtonData` must be interpreted as signed. let delta = mouse.Anonymous.Anonymous.usButtonData as i16 as f32 / WHEEL_DELTA as f32; - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: MouseWheel { delta: LineDelta(0.0, delta), @@ -2520,7 +2469,7 @@ unsafe fn handle_raw_input( // seem to be anything else reasonable to do for a mouse // button ID. let button = (index + 1) as _; - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: Button { button, state }, }); @@ -2626,7 +2575,7 @@ unsafe fn handle_raw_input( _ => (), } } - subclass_input.send_event(Event::DeviceEvent { + userdata.send_event(Event::DeviceEvent { device_id, event: Key(RawKeyEvent { physical_key: code, diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop/runner.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop/runner.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/event_loop/runner.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/event_loop/runner.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/icon.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/icon.rs similarity index 97% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/icon.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/icon.rs index b0d0e1bff..871071020 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/icon.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/icon.rs @@ -8,7 +8,6 @@ use windows::{ core::PCWSTR, Win32::{ Foundation::{HWND, LPARAM, WPARAM}, - System::LibraryLoader::*, UI::WindowsAndMessaging::*, }, }; @@ -97,7 +96,7 @@ impl WinIcon { let (width, height) = size.map(Into::into).unwrap_or((0, 0)); let handle = unsafe { LoadImageW( - GetModuleHandleW(PCWSTR::null()).map(Into::into).ok(), + Some(util::get_instance_handle()), PCWSTR::from_raw(resource_id as usize as *const u16), IMAGE_ICON, width, diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/ime.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/ime.rs new file mode 100644 index 000000000..51156aba9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/ime.rs @@ -0,0 +1,163 @@ +use std::{ + ffi::{c_void, OsString}, + os::windows::prelude::OsStringExt, +}; + +use windows::Win32::{ + Foundation::HWND, + UI::Input::Ime::{ + ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext, GCS_RESULTSTR, HIMC, + IME_COMPOSITION_STRING, + }, +}; + +pub struct ImeContext { + hwnd: HWND, + himc: HIMC, +} + +impl ImeContext { + pub unsafe fn current(hwnd: HWND) -> Self { + let himc = unsafe { ImmGetContext(hwnd) }; + ImeContext { hwnd, himc } + } + + // pub unsafe fn get_composing_text_and_cursor( + // &self, + // ) -> Option<(String, Option, Option)> { + // let text = unsafe { self.get_composition_string(GCS_COMPSTR) }?; + // let attrs = unsafe { self.get_composition_data(GCS_COMPATTR) }.unwrap_or_default(); + + // let mut first = None; + // let mut last = None; + // let mut boundary_before_char = 0; + // let mut attr_idx = 0; + + // for chr in text.chars() { + // let Some(attr) = attrs.get(attr_idx).copied() else { + // break; + // }; + + // let char_is_targeted = + // attr as u32 == ATTR_TARGET_CONVERTED || attr as u32 == ATTR_TARGET_NOTCONVERTED; + + // if first.is_none() && char_is_targeted { + // first = Some(boundary_before_char); + // } else if first.is_some() && last.is_none() && !char_is_targeted { + // last = Some(boundary_before_char); + // } + + // boundary_before_char += chr.len_utf8(); + // attr_idx += chr.len_utf16(); + // } + + // if first.is_some() && last.is_none() { + // last = Some(text.len()); + // } else if first.is_none() { + // // IME haven't split words and select any clause yet, so trying to retrieve normal + // // cursor. + // let cursor = unsafe { self.get_composition_cursor(&text) }; + // first = cursor; + // last = cursor; + // } + + // Some((text, first, last)) + // } + + pub unsafe fn get_composed_text(&self) -> Option { + unsafe { self.get_composition_string(GCS_RESULTSTR) } + } + + // unsafe fn get_composition_cursor(&self, text: &str) -> Option { + // let cursor = unsafe { ImmGetCompositionStringW(self.himc, GCS_CURSORPOS, None, 0) }; + // (cursor >= 0).then(|| text.chars().take(cursor as _).map(|c| c.len_utf8()).sum()) + // } + + unsafe fn get_composition_string(&self, gcs_mode: IME_COMPOSITION_STRING) -> Option { + let data = unsafe { self.get_composition_data(gcs_mode) }?; + let (prefix, shorts, suffix) = unsafe { data.align_to::() }; + if prefix.is_empty() && suffix.is_empty() { + OsString::from_wide(shorts).into_string().ok() + } else { + None + } + } + + unsafe fn get_composition_data(&self, gcs_mode: IME_COMPOSITION_STRING) -> Option> { + let size = match unsafe { ImmGetCompositionStringW(self.himc, gcs_mode, None, 0) } { + 0 => return Some(Vec::new()), + size if size < 0 => return None, + size => size, + }; + + let mut buf = Vec::::with_capacity(size as _); + let size = unsafe { + ImmGetCompositionStringW( + self.himc, + gcs_mode, + Some(buf.as_mut_ptr() as *mut c_void), + size as _, + ) + }; + + if size < 0 { + None + } else { + unsafe { buf.set_len(size as _) }; + Some(buf) + } + } + + // pub unsafe fn set_ime_cursor_area(&self, spot: Position, size: Size, scale_factor: f64) { + // if !unsafe { ImeContext::system_has_ime() } { + // return; + // } + + // let (x, y) = spot.to_physical::(scale_factor).into(); + // let (width, height): (i32, i32) = size.to_physical::(scale_factor).into(); + // let rc_area = RECT { + // left: x, + // top: y, + // right: x + width, + // bottom: y + height, + // }; + // let candidate_form = CANDIDATEFORM { + // dwIndex: 0, + // dwStyle: CFS_EXCLUDE, + // ptCurrentPos: POINT { x, y }, + // rcArea: rc_area, + // }; + // let composition_form = COMPOSITIONFORM { + // dwStyle: CFS_POINT, + // ptCurrentPos: POINT { x, y: y + height }, + // rcArea: rc_area, + // }; + + // unsafe { + // ImmSetCompositionWindow(self.himc, &composition_form); + // ImmSetCandidateWindow(self.himc, &candidate_form); + // } + // } + + // pub unsafe fn set_ime_allowed(hwnd: HWND, allowed: bool) { + // if !unsafe { ImeContext::system_has_ime() } { + // return; + // } + + // if allowed { + // unsafe { ImmAssociateContextEx(hwnd, HIMC::default(), IACE_DEFAULT) }; + // } else { + // unsafe { ImmAssociateContextEx(hwnd, HIMC::default(), IACE_CHILDREN) }; + // } + // } + + // pub unsafe fn system_has_ime() -> bool { + // unsafe { GetSystemMetrics(SM_IMMENABLED) != 0 } + // } +} + +impl Drop for ImeContext { + fn drop(&mut self) { + let _ = unsafe { ImmReleaseContext(self.hwnd, self.himc) }; + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard.rs new file mode 100644 index 000000000..aefa9658a --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard.rs @@ -0,0 +1,951 @@ +use std::{ + char, + collections::HashSet, + ffi::OsString, + mem::MaybeUninit, + os::windows::ffi::OsStringExt, + sync::atomic::{AtomicU32, Ordering::Relaxed}, +}; + +use crate::{ + event::{ElementState, KeyEvent}, + platform_impl::KeyEventExtra, +}; +use crate::{ + keyboard::{Key, KeyCode, KeyLocation, NativeKeyCode}, + platform_impl::platform::keyboard_layout::get_or_insert_str, +}; +use parking_lot::{Mutex, MutexGuard}; +use unicode_segmentation::UnicodeSegmentation; +use windows::Win32::{ + Foundation::{HWND, LPARAM, LRESULT, WPARAM}, + UI::{ + Input::KeyboardAndMouse::{ + GetAsyncKeyState, GetKeyState, GetKeyboardState, MapVirtualKeyExW, HKL, MAPVK_VK_TO_VSC_EX, + MAPVK_VSC_TO_VK_EX, VIRTUAL_KEY, VK_ABNT_C2, VK_ADD, VK_CAPITAL, VK_CLEAR, VK_CONTROL, + VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_F4, VK_HOME, VK_INSERT, VK_LCONTROL, + VK_LEFT, VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MENU, VK_MULTIPLY, VK_NEXT, VK_NUMLOCK, VK_NUMPAD0, + VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, + VK_NUMPAD8, VK_NUMPAD9, VK_PRIOR, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT, + VK_RWIN, VK_SCROLL, VK_SHIFT, VK_SUBTRACT, VK_UP, + }, + WindowsAndMessaging::{ + PeekMessageW, MSG, PM_NOREMOVE, WM_CHAR, WM_DEADCHAR, WM_KEYDOWN, WM_KEYFIRST, WM_KEYLAST, + WM_KEYUP, WM_KILLFOCUS, WM_SETFOCUS, WM_SYSCHAR, WM_SYSDEADCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, + }, + }, +}; + +use crate::platform_impl::platform::{ + event_loop::ProcResult, + keyboard_layout::{Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE}, +}; + +pub type ExScancode = u16; + +pub struct MessageAsKeyEvent { + pub event: KeyEvent, + pub is_synthetic: bool, +} + +/// Stores information required to make `KeyEvent`s. +/// +/// A single Tao `KeyEvent` contains information which the Windows API passes to the application +/// in multiple window messages. In other words: a Tao `KeyEvent` cannot be built from a single +/// window message. Therefore, this type keeps track of certain information from previous events so +/// that a `KeyEvent` can be constructed when the last event related to a keypress is received. +/// +/// `PeekMessage` is sometimes used to determine whether the next window message still belongs to the +/// current keypress. If it doesn't and the current state represents a key event waiting to be +/// dispatched, then said event is considered complete and is dispatched. +/// +/// The sequence of window messages for a key press event is the following: +/// - Exactly one WM_KEYDOWN / WM_SYSKEYDOWN +/// - Zero or one WM_DEADCHAR / WM_SYSDEADCHAR +/// - Zero or more WM_CHAR / WM_SYSCHAR. These messages each come with a UTF-16 code unit which when +/// put together in the sequence they arrived in, forms the text which is the result of pressing the +/// key. +/// +/// Key release messages are a bit different due to the fact that they don't contribute to +/// text input. The "sequence" only consists of one WM_KEYUP / WM_SYSKEYUP event. +pub struct KeyEventBuilder { + event_info: Mutex>, + pending: PendingEventQueue, +} +impl Default for KeyEventBuilder { + fn default() -> Self { + KeyEventBuilder { + event_info: Mutex::new(None), + pending: Default::default(), + } + } +} +impl KeyEventBuilder { + /// Call this function for every window message. + /// Returns Some() if this window message completes a KeyEvent. + /// Returns None otherwise. + pub(crate) fn process_message( + &self, + hwnd: HWND, + msg_kind: u32, + wparam: WPARAM, + lparam: LPARAM, + result: &mut ProcResult, + ) -> Vec { + enum MatchResult { + Nothing, + TokenToRemove(PendingMessageToken), + MessagesToDispatch(Vec), + } + + let mut matcher = || -> MatchResult { + match msg_kind { + WM_SETFOCUS => { + // synthesize keydown events + let kbd_state = get_async_kbd_state(); + let key_events = Self::synthesize_kbd_state(ElementState::Pressed, &kbd_state); + MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) + } + WM_KILLFOCUS => { + // synthesize keyup events + let kbd_state = get_kbd_state(); + let key_events = Self::synthesize_kbd_state(ElementState::Released, &kbd_state); + MatchResult::MessagesToDispatch(self.pending.complete_multi(key_events)) + } + WM_KEYDOWN | WM_SYSKEYDOWN => { + if msg_kind == WM_SYSKEYDOWN && wparam.0 == usize::from(VK_F4.0) { + // Don't dispatch Alt+F4 to the application. + // This is handled in `event_loop.rs` + return MatchResult::Nothing; + } + let pending_token = self.pending.add_pending(); + if msg_kind == WM_SYSKEYDOWN { + *result = ProcResult::DefWindowProc; + } else { + *result = ProcResult::Value(LRESULT(0)); + } + + let next_msg = next_kbd_msg(hwnd); + + let mut layouts = LAYOUT_CACHE.lock(); + let mut finished_event_info = Some(PartialKeyEventInfo::from_message( + wparam, + lparam, + ElementState::Pressed, + &mut layouts, + )); + let mut event_info = self.event_info.lock(); + *event_info = None; + if let Some(next_msg) = next_msg { + let next_msg_kind = next_msg.message; + let next_belongs_to_this = !matches!( + next_msg_kind, + WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP + ); + if next_belongs_to_this { + // The next OS event belongs to this Winit event, so let's just + // store the partial information, and add to it in the upcoming events + *event_info = finished_event_info.take(); + } else { + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let curr_event = finished_event_info.as_ref().unwrap(); + is_current_fake(curr_event, next_msg, layout) + }; + if is_fake { + finished_event_info = None; + } + } + } + if let Some(event_info) = finished_event_info { + let ev = event_info.finalize(&mut layouts.strings); + return MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )); + } + MatchResult::TokenToRemove(pending_token) + } + WM_DEADCHAR | WM_SYSDEADCHAR => { + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(LRESULT(0)); + // At this point, we know that there isn't going to be any more events related + // to this key press + let event_info = self.event_info.lock().take().unwrap(); + let mut layouts = LAYOUT_CACHE.lock(); + let ev = event_info.finalize(&mut layouts.strings); + MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )) + } + WM_CHAR | WM_SYSCHAR => { + let mut event_info = self.event_info.lock(); + if event_info.is_none() { + trace!( + "Received a CHAR message but no `event_info` was available. The \ + message is probably IME, returning." + ); + return MatchResult::Nothing; + } + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(LRESULT(0)); + let is_high_surrogate = (0xd800..=0xdbff).contains(&wparam.0); + let is_low_surrogate = (0xdc00..=0xdfff).contains(&wparam.0); + + let is_utf16 = is_high_surrogate || is_low_surrogate; + + if is_utf16 { + if let Some(ev_info) = event_info.as_mut() { + ev_info.utf16parts.push(wparam.0 as u16); + } + } else { + // In this case, wparam holds a UTF-32 character. + // Let's encode it as UTF-16 and append it to the end of `utf16parts` + let utf16parts = match event_info.as_mut() { + Some(ev_info) => &mut ev_info.utf16parts, + None => { + warn!("The event_info was None when it was expected to be some"); + return MatchResult::TokenToRemove(pending_token); + } + }; + let start_offset = utf16parts.len(); + let new_size = utf16parts.len() + 2; + utf16parts.resize(new_size, 0); + if let Some(ch) = char::from_u32(wparam.0 as u32) { + let encode_len = ch.encode_utf16(&mut utf16parts[start_offset..]).len(); + let new_size = start_offset + encode_len; + utf16parts.resize(new_size, 0); + } + } + // It's important that we unlock the mutex, and create the pending event token + // before calling `next_msg` + std::mem::drop(event_info); + let next_msg = next_kbd_msg(hwnd); + let more_char_coming = next_msg + .map(|m| matches!(m.message, WM_CHAR | WM_SYSCHAR)) + .unwrap_or(false); + if more_char_coming { + // No need to produce an event just yet, because there are still more + // characters that need to be appended to this keyboard event + MatchResult::TokenToRemove(pending_token) + } else { + let mut event_info = self.event_info.lock(); + let mut event_info = match event_info.take() { + Some(ev_info) => ev_info, + None => { + warn!("The event_info was None when it was expected to be some"); + return MatchResult::TokenToRemove(pending_token); + } + }; + let mut layouts = LAYOUT_CACHE.lock(); + // It's okay to call `ToUnicode` here, because at this point the dead key + // is already consumed by the character. + let kbd_state = get_kbd_state(); + let mod_state = WindowsModifiers::active_modifiers(&kbd_state); + + let (_, layout) = layouts.get_current_layout(); + let ctrl_on = if layout.has_alt_graph { + let alt_on = mod_state.contains(WindowsModifiers::ALT); + !alt_on && mod_state.contains(WindowsModifiers::CONTROL) + } else { + mod_state.contains(WindowsModifiers::CONTROL) + }; + + // If Ctrl is not pressed, just use the text with all + // modifiers because that already consumed the dead key. Otherwise, + // we would interpret the character incorrectly, missing the dead key. + if !ctrl_on { + event_info.text = PartialText::System(event_info.utf16parts.clone()); + } else { + let mod_no_ctrl = mod_state.remove_only_ctrl(); + let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; + let vkey = event_info.vkey; + let scancode = event_info.scancode; + let keycode = event_info.code; + let key = layout.get_key(mod_no_ctrl, num_lock_on, vkey, scancode, keycode); + event_info.text = PartialText::Text(key.to_text()); + } + let ev = event_info.finalize(&mut layouts.strings); + MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }, + )) + } + } + WM_KEYUP | WM_SYSKEYUP => { + let pending_token = self.pending.add_pending(); + *result = ProcResult::Value(LRESULT(0)); + + let mut layouts = LAYOUT_CACHE.lock(); + let event_info = + PartialKeyEventInfo::from_message(wparam, lparam, ElementState::Released, &mut layouts); + // We MUST release the layout lock before calling `next_kbd_msg`, otherwise it + // may deadlock + drop(layouts); + // It's important that we create the pending token before reading the next + // message. + let next_msg = next_kbd_msg(hwnd); + let mut valid_event_info = Some(event_info); + if let Some(next_msg) = next_msg { + let mut layouts = LAYOUT_CACHE.lock(); + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let event_info = valid_event_info.as_ref().unwrap(); + is_current_fake(event_info, next_msg, layout) + }; + if is_fake { + valid_event_info = None; + } + } + if let Some(event_info) = valid_event_info { + let mut layouts = LAYOUT_CACHE.lock(); + let event = event_info.finalize(&mut layouts.strings); + return MatchResult::MessagesToDispatch(self.pending.complete_pending( + pending_token, + MessageAsKeyEvent { + event, + is_synthetic: false, + }, + )); + } + MatchResult::TokenToRemove(pending_token) + } + _ => MatchResult::Nothing, + } + }; + let matcher_result = matcher(); + match matcher_result { + MatchResult::TokenToRemove(t) => self.pending.remove_pending(t), + MatchResult::MessagesToDispatch(m) => m, + MatchResult::Nothing => Vec::new(), + } + } + + fn synthesize_kbd_state( + key_state: ElementState, + kbd_state: &[u8; 256], + ) -> Vec { + let mut key_events = Vec::new(); + + let mut layouts = LAYOUT_CACHE.lock(); + let (locale_id, _) = layouts.get_current_layout(); + + let is_key_pressed = |vk: VIRTUAL_KEY| &kbd_state[usize::from(vk.0)] & 0x80 != 0; + + // Is caps-lock active? Note that this is different from caps-lock + // being held down. + let caps_lock_on = kbd_state[usize::from(VK_CAPITAL.0)] & 1 != 0; + let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; + + // We are synthesizing the press event for caps-lock first for the following reasons: + // 1. If caps-lock is *not* held down but *is* active, then we have to synthesize all + // printable keys, respecting the caps-lock state. + // 2. If caps-lock is held down, we could choose to synthesize its keypress after every + // other key, in which case all other keys *must* be synthesized as if the caps-lock + // state was be the opposite of what it currently is. + // -- + // For the sake of simplicity we are choosing to always synthesize + // caps-lock first, and always use the current caps-lock state + // to determine the produced text + if is_key_pressed(VK_CAPITAL) { + let event = Self::create_synthetic( + VK_CAPITAL, + key_state, + caps_lock_on, + num_lock_on, + locale_id, + &mut layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + let do_non_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { + for vk in 0..256 { + let vk = VIRTUAL_KEY(vk); + match vk { + VK_CONTROL | VK_LCONTROL | VK_RCONTROL | VK_SHIFT | VK_LSHIFT | VK_RSHIFT | VK_MENU + | VK_LMENU | VK_RMENU | VK_CAPITAL => continue, + _ => (), + } + if !is_key_pressed(vk) { + continue; + } + let event = + Self::create_synthetic(vk, key_state, caps_lock_on, num_lock_on, locale_id, layouts); + if let Some(event) = event { + key_events.push(event); + } + } + }; + let do_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { + const CLEAR_MODIFIER_VKS: [VIRTUAL_KEY; 6] = [ + VK_LCONTROL, + VK_LSHIFT, + VK_LMENU, + VK_RCONTROL, + VK_RSHIFT, + VK_RMENU, + ]; + for vk in CLEAR_MODIFIER_VKS.iter() { + if is_key_pressed(*vk) { + let event = Self::create_synthetic( + *vk, + key_state, + caps_lock_on, + num_lock_on, + locale_id, + layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + } + }; + + // Be cheeky and sequence modifier and non-modifier + // key events such that non-modifier keys are not affected + // by modifiers (except for caps-lock) + match key_state { + ElementState::Pressed => { + do_non_modifier(&mut key_events, &mut layouts); + do_modifier(&mut key_events, &mut layouts); + } + ElementState::Released => { + do_modifier(&mut key_events, &mut layouts); + do_non_modifier(&mut key_events, &mut layouts); + } + } + + key_events + } + + fn create_synthetic( + vk: VIRTUAL_KEY, + key_state: ElementState, + caps_lock_on: bool, + num_lock_on: bool, + locale_id: HKL, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Option { + let scancode = + unsafe { MapVirtualKeyExW(u32::from(vk.0), MAPVK_VK_TO_VSC_EX, Some(locale_id)) }; + if scancode == 0 { + return None; + } + let scancode = scancode as ExScancode; + let code = KeyCode::from_scancode(scancode as u32); + let mods = if caps_lock_on { + WindowsModifiers::CAPS_LOCK + } else { + WindowsModifiers::empty() + }; + let layout = layouts.layouts.get(&(locale_id.0 as _)).unwrap(); + let logical_key = layout.get_key(mods, num_lock_on, vk, scancode, code); + let key_without_modifiers = + layout.get_key(WindowsModifiers::empty(), false, vk, scancode, code); + let text = if key_state == ElementState::Pressed { + logical_key.to_text() + } else { + None + }; + let event_info = PartialKeyEventInfo { + vkey: vk, + logical_key: PartialLogicalKey::This(logical_key.clone()), + key_without_modifiers, + key_state, + scancode, + is_repeat: false, + code, + location: get_location(scancode, locale_id), + utf16parts: Vec::with_capacity(8), + text: PartialText::Text(text), + }; + + let mut event = event_info.finalize(&mut layouts.strings); + event.logical_key = logical_key; + event.platform_specific.text_with_all_modifiers = text; + Some(MessageAsKeyEvent { + event, + is_synthetic: true, + }) + } +} + +enum PartialText { + // Unicode + System(Vec), + Text(Option<&'static str>), +} + +enum PartialLogicalKey { + /// Use the text provided by the WM_CHAR messages and report that as a `Character` variant. If + /// the text consists of multiple grapheme clusters (user-perceived characters) that means that + /// dead key could not be combined with the second input, and in that case we should fall back + /// to using what would have without a dead-key input. + TextOr(Key<'static>), + + /// Use the value directly provided by this variant + This(Key<'static>), +} + +struct PartialKeyEventInfo { + vkey: VIRTUAL_KEY, + scancode: ExScancode, + key_state: ElementState, + is_repeat: bool, + code: KeyCode, + location: KeyLocation, + logical_key: PartialLogicalKey, + + key_without_modifiers: Key<'static>, + + /// The UTF-16 code units of the text that was produced by the keypress event. + /// This take all modifiers into account. Including CTRL + utf16parts: Vec, + + text: PartialText, +} + +impl PartialKeyEventInfo { + fn from_message( + wparam: WPARAM, + lparam: LPARAM, + state: ElementState, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Self { + const NO_MODS: WindowsModifiers = WindowsModifiers::empty(); + + let (_, layout) = layouts.get_current_layout(); + let lparam_struct = destructure_key_lparam(lparam); + let vkey = VIRTUAL_KEY(wparam.0 as u16); + let scancode = if lparam_struct.scancode == 0 { + // In some cases (often with media keys) the device reports a scancode of 0 but a + // valid virtual key. In these cases we obtain the scancode from the virtual key. + unsafe { + MapVirtualKeyExW( + u32::from(vkey.0), + MAPVK_VK_TO_VSC_EX, + Some(HKL(layout.hkl as _)), + ) as u16 + } + } else { + new_ex_scancode(lparam_struct.scancode, lparam_struct.extended) + }; + let code = KeyCode::from_scancode(scancode as u32); + let location = get_location(scancode, HKL(layout.hkl as _)); + + let kbd_state = get_kbd_state(); + let mods = WindowsModifiers::active_modifiers(&kbd_state); + let mods_without_ctrl = mods.remove_only_ctrl(); + let num_lock_on = kbd_state[usize::from(VK_NUMLOCK.0)] & 1 != 0; + + // On Windows Ctrl+NumLock = Pause (and apparently Ctrl+Pause -> NumLock). In these cases + // the KeyCode still stores the real key, so in the name of consistency across platforms, we + // circumvent this mapping and force the key values to match the keycode. + // For more on this, read the article by Raymond Chen, titled: + // "Why does Ctrl+ScrollLock cancel dialogs?" + // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 + let code_as_key = if mods.contains(WindowsModifiers::CONTROL) { + match code { + KeyCode::NumLock => Some(Key::NumLock), + KeyCode::Pause => Some(Key::Pause), + _ => None, + } + } else { + None + }; + + let preliminary_logical_key = + layout.get_key(mods_without_ctrl, num_lock_on, vkey, scancode, code); + let key_is_char = matches!(preliminary_logical_key, Key::Character(_)); + let is_pressed = state == ElementState::Pressed; + + let logical_key = if let Some(key) = code_as_key.clone() { + PartialLogicalKey::This(key) + } else if is_pressed && key_is_char && !mods.contains(WindowsModifiers::CONTROL) { + // In some cases we want to use the UNICHAR text for logical_key in order to allow + // dead keys to have an effect on the character reported by `logical_key`. + PartialLogicalKey::TextOr(preliminary_logical_key) + } else { + PartialLogicalKey::This(preliminary_logical_key) + }; + let key_without_modifiers = if let Some(key) = code_as_key { + key + } else { + match layout.get_key(NO_MODS, false, vkey, scancode, code) { + // We convert dead keys into their character. + // The reason for this is that `key_without_modifiers` is designed for key-bindings, + // but the US International layout treats `'` (apostrophe) as a dead key and the + // regular US layout treats it a character. In order for a single binding + // configuration to work with both layouts, we forward each dead key as a character. + Key::Dead(k) => { + if let Some(ch) = k { + // I'm avoiding the heap allocation. I don't want to talk about it :( + let mut utf8 = [0; 4]; + let s = ch.encode_utf8(&mut utf8); + let static_str = get_or_insert_str(&mut layouts.strings, s); + Key::Character(static_str) + } else { + Key::Unidentified(NativeKeyCode::Unidentified) + } + } + key => key, + } + }; + + PartialKeyEventInfo { + vkey, + scancode, + key_state: state, + logical_key, + key_without_modifiers, + is_repeat: lparam_struct.is_repeat, + code, + location, + utf16parts: Vec::with_capacity(8), + text: PartialText::System(Vec::new()), + } + } + + fn finalize(self, strings: &mut HashSet<&'static str>) -> KeyEvent { + let mut char_with_all_modifiers = None; + if !self.utf16parts.is_empty() { + let os_string = OsString::from_wide(&self.utf16parts); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + char_with_all_modifiers = Some(static_str); + } + } + + // The text without Ctrl + let mut text = None; + match self.text { + PartialText::System(wide) => { + if !wide.is_empty() { + let os_string = OsString::from_wide(&wide); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + text = Some(static_str); + } + } + } + PartialText::Text(s) => { + text = s; + } + } + + let logical_key = match self.logical_key { + PartialLogicalKey::TextOr(fallback) => match text { + Some(s) => { + if s.grapheme_indices(true).count() > 1 { + fallback + } else { + Key::Character(s) + } + } + None => Key::Unidentified(NativeKeyCode::Windows(self.scancode)), + }, + PartialLogicalKey::This(v) => v, + }; + + KeyEvent { + physical_key: self.code, + logical_key, + text, + location: self.location, + state: self.key_state, + repeat: self.is_repeat, + platform_specific: KeyEventExtra { + text_with_all_modifiers: char_with_all_modifiers, + key_without_modifiers: self.key_without_modifiers, + }, + } + } +} + +#[derive(Debug, Copy, Clone)] +struct KeyLParam { + pub scancode: u8, + pub extended: bool, + + /// This is `previous_state XOR transition_state`. See the lParam for WM_KEYDOWN and WM_KEYUP for further details. + pub is_repeat: bool, +} + +fn destructure_key_lparam(lparam: LPARAM) -> KeyLParam { + let previous_state = (lparam.0 >> 30) & 0x01; + let transition_state = (lparam.0 >> 31) & 0x01; + KeyLParam { + scancode: ((lparam.0 >> 16) & 0xFF) as u8, + extended: ((lparam.0 >> 24) & 0x01) != 0, + is_repeat: (previous_state ^ transition_state) != 0, + } +} + +#[inline] +fn new_ex_scancode(scancode: u8, extended: bool) -> ExScancode { + (scancode as u16) | (if extended { 0xE000 } else { 0 }) +} + +#[inline] +fn ex_scancode_from_lparam(lparam: LPARAM) -> ExScancode { + let lparam = destructure_key_lparam(lparam); + new_ex_scancode(lparam.scancode, lparam.extended) +} + +/// Gets the keyboard state as reported by messages that have been removed from the event queue. +/// See also: get_async_kbd_state +fn get_kbd_state() -> [u8; 256] { + unsafe { + let mut kbd_state: MaybeUninit<[u8; 256]> = MaybeUninit::uninit(); + let _ = GetKeyboardState(&mut *kbd_state.as_mut_ptr()); + kbd_state.assume_init() + } +} + +/// Gets the current keyboard state regardless of whether the corresponding keyboard events have +/// been removed from the event queue. See also: get_kbd_state +fn get_async_kbd_state() -> [u8; 256] { + unsafe { + let mut kbd_state: [u8; 256] = [0; 256]; + for (vk, state) in kbd_state.iter_mut().enumerate() { + let vk = VIRTUAL_KEY(vk as u16); + let async_state = GetAsyncKeyState(i32::from(vk.0)); + let is_down = (async_state & (1 << 15)) != 0; + *state = if is_down { 0x80 } else { 0 }; + + if matches!(vk, VK_CAPITAL | VK_NUMLOCK | VK_SCROLL) { + // Toggle states aren't reported by `GetAsyncKeyState` + let toggle_state = GetKeyState(i32::from(vk.0)); + let is_active = (toggle_state & 1) != 0; + *state |= u8::from(is_active); + } + } + kbd_state + } +} + +/// On windows, AltGr == Ctrl + Alt +/// +/// Due to this equivalence, the system generates a fake Ctrl key-press (and key-release) preceding +/// every AltGr key-press (and key-release). We check if the current event is a Ctrl event and if +/// the next event is a right Alt (AltGr) event. If this is the case, the current event must be the +/// fake Ctrl event. +fn is_current_fake(curr_info: &PartialKeyEventInfo, next_msg: MSG, layout: &Layout) -> bool { + let curr_is_ctrl = matches!(curr_info.logical_key, PartialLogicalKey::This(Key::Control)); + if layout.has_alt_graph { + let next_code = ex_scancode_from_lparam(next_msg.lParam); + let next_is_altgr = next_code == 0xE038; // 0xE038 is right alt + if curr_is_ctrl && next_is_altgr { + return true; + } + } + false +} + +enum PendingMessage { + Incomplete, + Complete(T), +} +struct IdentifiedPendingMessage { + token: PendingMessageToken, + msg: PendingMessage, +} +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PendingMessageToken(u32); + +/// While processing keyboard events, we sometimes need +/// to call `PeekMessageW` (`next_msg`). But `PeekMessageW` +/// can also call the event handler, which means that the new event +/// gets processed before finishing to process the one that came before. +/// +/// This would mean that the application receives events in the wrong order. +/// To avoid this, we keep track whether we are in the middle of processing +/// an event. Such an event is an "incomplete pending event". A +/// "complete pending event" is one that has already finished processing, but +/// hasn't been dispatched to the application because there still are incomplete +/// pending events that came before it. +/// +/// When we finish processing an event, we call `complete_pending`, +/// which returns an empty array if there are incomplete pending events, but +/// if all pending events are complete, then it returns all pending events in +/// the order they were encountered. These can then be dispatched to the application +pub struct PendingEventQueue { + pending: Mutex>>, + next_id: AtomicU32, +} +impl PendingEventQueue { + /// Add a new pending event to the "pending queue" + pub fn add_pending(&self) -> PendingMessageToken { + let token = self.next_token(); + let mut pending = self.pending.lock(); + pending.push(IdentifiedPendingMessage { + token, + msg: PendingMessage::Incomplete, + }); + token + } + + /// Returns all finished pending events + /// + /// If the return value is non empty, it's guaranteed to contain `msg` + /// + /// See also: `add_pending` + pub fn complete_pending(&self, token: PendingMessageToken, msg: T) -> Vec { + let mut pending = self.pending.lock(); + let mut target_is_first = false; + for (i, pending_msg) in pending.iter_mut().enumerate() { + if pending_msg.token == token { + pending_msg.msg = PendingMessage::Complete(msg); + if i == 0 { + target_is_first = true; + } + break; + } + } + if target_is_first { + // If the message that we just finished was the first one in the pending queue, + // then we can empty the queue, and dispatch all of the messages. + Self::drain_pending(&mut *pending) + } else { + Vec::new() + } + } + + pub fn complete_multi(&self, msgs: Vec) -> Vec { + let mut pending = self.pending.lock(); + if pending.is_empty() { + return msgs; + } + pending.reserve(msgs.len()); + for msg in msgs { + pending.push(IdentifiedPendingMessage { + token: self.next_token(), + msg: PendingMessage::Complete(msg), + }); + } + Vec::new() + } + + /// Returns all finished pending events + /// + /// It's safe to call this even if the element isn't in the list anymore + /// + /// See also: `add_pending` + pub fn remove_pending(&self, token: PendingMessageToken) -> Vec { + let mut pending = self.pending.lock(); + let mut was_first = false; + if let Some(m) = pending.first() { + if m.token == token { + was_first = true; + } + } + pending.retain(|m| m.token != token); + if was_first { + Self::drain_pending(&mut *pending) + } else { + Vec::new() + } + } + + fn drain_pending(pending: &mut Vec>) -> Vec { + pending + .drain(..) + .map(|m| match m.msg { + PendingMessage::Complete(msg) => msg, + PendingMessage::Incomplete => { + panic!( + "Found an incomplete pending message when collecting messages. This \ + indicates a bug in tao." + ) + } + }) + .collect() + } + + fn next_token(&self) -> PendingMessageToken { + // It's okay for the u32 to overflow here. Yes, that could mean + // that two different messages have the same token, + // but that would only happen after having about 4 billion + // messages sitting in the pending queue. + // + // In that case, having two identical tokens is the least of your concerns. + let id = self.next_id.fetch_add(1, Relaxed); + PendingMessageToken(id) + } +} +impl Default for PendingEventQueue { + fn default() -> Self { + PendingEventQueue { + pending: Mutex::new(Vec::new()), + next_id: AtomicU32::new(0), + } + } +} + +/// WARNING: Due to using PeekMessage, the event handler +/// function may get called during this function. +/// (Re-entrance to the event handler) +/// +/// This can cause a deadlock if calling this function +/// while having a mutex locked. +/// +/// It can also cause code to get executed in a surprising order. +pub(crate) fn next_kbd_msg(window: HWND) -> Option { + unsafe { + let mut next_msg = MaybeUninit::uninit(); + if PeekMessageW( + next_msg.as_mut_ptr(), + Some(window), + WM_KEYFIRST, + WM_KEYLAST, + PM_NOREMOVE, + ) + .as_bool() + { + Some(next_msg.assume_init()) + } else { + None + } + } +} + +fn get_location(scancode: ExScancode, hkl: HKL) -> KeyLocation { + let extension = 0xE000; + let extended = (scancode & extension) == extension; + let vkey = + VIRTUAL_KEY(unsafe { MapVirtualKeyExW(scancode as u32, MAPVK_VSC_TO_VK_EX, Some(hkl)) } as u16); + + // Use the native VKEY and the extended flag to cover most cases + // This is taken from the `druid` GUI library, specifically + // druid-shell/src/platform/windows/keyboard.rs + match vkey { + VK_LSHIFT | VK_LCONTROL | VK_LMENU | VK_LWIN => KeyLocation::Left, + VK_RSHIFT | VK_RCONTROL | VK_RMENU | VK_RWIN => KeyLocation::Right, + VK_RETURN if extended => KeyLocation::Numpad, + VK_INSERT | VK_DELETE | VK_END | VK_DOWN | VK_NEXT | VK_LEFT | VK_CLEAR | VK_RIGHT + | VK_HOME | VK_UP | VK_PRIOR => { + if extended { + KeyLocation::Standard + } else { + KeyLocation::Numpad + } + } + VK_NUMPAD0 | VK_NUMPAD1 | VK_NUMPAD2 | VK_NUMPAD3 | VK_NUMPAD4 | VK_NUMPAD5 | VK_NUMPAD6 + | VK_NUMPAD7 | VK_NUMPAD8 | VK_NUMPAD9 | VK_DECIMAL | VK_DIVIDE | VK_MULTIPLY | VK_SUBTRACT + | VK_ADD | VK_ABNT_C2 => KeyLocation::Numpad, + _ => KeyLocation::Standard, + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard_layout.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard_layout.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard_layout.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard_layout.rs index 9862c5532..e4105a3de 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keyboard_layout.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keyboard_layout.rs @@ -1,9 +1,9 @@ -use once_cell::sync::Lazy; use parking_lot::Mutex; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, ffi::OsString, os::windows::ffi::OsStringExt, + sync::LazyLock, }; use windows::Win32::{ @@ -17,8 +17,12 @@ use crate::{ platform_impl::platform::util, }; -pub(crate) static LAYOUT_CACHE: Lazy> = - Lazy::new(|| Mutex::new(LayoutCache::default())); +pub(crate) static LAYOUT_CACHE: LazyLock> = + LazyLock::new(|| Mutex::new(LayoutCache::default())); + +pub(crate) fn get_agnostic_mods() -> ModifiersState { + LAYOUT_CACHE.lock().get_agnostic_mods() +} fn key_pressed(vkey: VIRTUAL_KEY) -> bool { unsafe { (GetKeyState(u32::from(vkey.0) as i32) & (1 << 15)) == (1 << 15) } @@ -43,7 +47,7 @@ const NUMPAD_VKEYS: [VIRTUAL_KEY; 16] = [ VK_DIVIDE, ]; -static NUMPAD_KEYCODES: Lazy> = Lazy::new(|| { +static NUMPAD_KEYCODES: LazyLock> = LazyLock::new(|| { let mut keycodes = HashSet::new(); keycodes.insert(KeyCode::Numpad0); keycodes.insert(KeyCode::Numpad1); @@ -245,7 +249,7 @@ impl LayoutCache { } } - pub fn get_agnostic_mods(&mut self) -> ModifiersState { + fn get_agnostic_mods(&mut self) -> ModifiersState { let (_, layout) = self.get_current_layout(); let filter_out_altgr = layout.has_alt_graph && key_pressed(VK_RMENU); let mut mods = ModifiersState::empty(); diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keycode.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keycode.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/keycode.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/keycode.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/mod.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/mod.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/mod.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/mod.rs index 8cd4d97a1..169d35e03 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/mod.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/mod.rs @@ -148,9 +148,9 @@ mod dpi; mod drop_handler; mod event_loop; mod icon; +mod ime; mod keyboard; mod keyboard_layout; -mod minimal_ime; mod monitor; mod raw_input; mod window; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/monitor.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/monitor.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/monitor.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/monitor.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/raw_input.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/raw_input.rs similarity index 100% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/raw_input.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/raw_input.rs diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/util.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/util.rs similarity index 89% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/util.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/util.rs index b4bc6e066..3535b00ab 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/util.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/util.rs @@ -17,11 +17,11 @@ use crate::{ window::CursorIcon, }; -use once_cell::sync::Lazy; +use std::sync::LazyLock; use windows::{ core::{BOOL, HRESULT, PCSTR, PCWSTR}, Win32::{ - Foundation::{COLORREF, FARPROC, HWND, LPARAM, LRESULT, POINT, RECT, WPARAM}, + Foundation::{COLORREF, FARPROC, HWND, LPARAM, POINT, RECT, WPARAM}, Globalization::lstrlenW, Graphics::Gdi::{ClientToScreen, InvalidateRgn, HMONITOR}, System::LibraryLoader::*, @@ -292,22 +292,22 @@ pub type AdjustWindowRectExForDpi = unsafe extern "system" fn( dpi: u32, ) -> BOOL; -pub static GET_DPI_FOR_WINDOW: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetDpiForWindow)); -pub static ADJUST_WINDOW_RECT_EX_FOR_DPI: Lazy> = - Lazy::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi)); -pub static GET_DPI_FOR_MONITOR: Lazy> = - Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor)); -pub static GET_SYSTEM_METRICS_FOR_DPI: Lazy> = - Lazy::new(|| get_function!("user32.dll", GetSystemMetricsForDpi)); -pub static ENABLE_NON_CLIENT_DPI_SCALING: Lazy> = - Lazy::new(|| get_function!("user32.dll", EnableNonClientDpiScaling)); -pub static SET_PROCESS_DPI_AWARENESS_CONTEXT: Lazy> = - Lazy::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext)); -pub static SET_PROCESS_DPI_AWARENESS: Lazy> = - Lazy::new(|| get_function!("shcore.dll", SetProcessDpiAwareness)); -pub static SET_PROCESS_DPI_AWARE: Lazy> = - Lazy::new(|| get_function!("user32.dll", SetProcessDPIAware)); +pub static GET_DPI_FOR_WINDOW: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetDpiForWindow)); +pub static ADJUST_WINDOW_RECT_EX_FOR_DPI: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi)); +pub static GET_DPI_FOR_MONITOR: LazyLock> = + LazyLock::new(|| get_function!("shcore.dll", GetDpiForMonitor)); +pub static GET_SYSTEM_METRICS_FOR_DPI: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", GetSystemMetricsForDpi)); +pub static ENABLE_NON_CLIENT_DPI_SCALING: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", EnableNonClientDpiScaling)); +pub static SET_PROCESS_DPI_AWARENESS_CONTEXT: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext)); +pub static SET_PROCESS_DPI_AWARENESS: LazyLock> = + LazyLock::new(|| get_function!("shcore.dll", SetProcessDpiAwareness)); +pub static SET_PROCESS_DPI_AWARE: LazyLock> = + LazyLock::new(|| get_function!("user32.dll", SetProcessDPIAware)); #[allow(non_snake_case)] #[cfg(target_pointer_width = "32")] @@ -398,16 +398,7 @@ pub fn RGB>(r: T, g: T, b: T) -> COLORREF { COLORREF(r.into() | (g.into() << 8) | (b.into() << 16)) } -pub unsafe extern "system" fn call_default_window_proc( - hwnd: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - DefWindowProcW(hwnd, msg, wparam, lparam) -} - -pub fn get_instance_handle() -> windows::Win32::Foundation::HMODULE { +pub fn get_instance_handle() -> windows::Win32::Foundation::HINSTANCE { // Gets the instance handle by taking the address of the // pseudo-variable created by the microsoft linker: // https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483 @@ -419,11 +410,11 @@ pub fn get_instance_handle() -> windows::Win32::Foundation::HMODULE { static __ImageBase: windows::Win32::System::SystemServices::IMAGE_DOS_HEADER; } - windows::Win32::Foundation::HMODULE(unsafe { &__ImageBase as *const _ as _ }) + windows::Win32::Foundation::HINSTANCE(unsafe { &__ImageBase as *const _ as _ }) } -pub static WIN_VERSION: Lazy = - Lazy::new(windows_version::OsVersion::current); +pub static WIN_VERSION: LazyLock = + LazyLock::new(windows_version::OsVersion::current); pub fn get_frame_thickness(dpi: u32) -> i32 { let resize_frame_thickness = unsafe { get_system_metrics_for_dpi(SM_CXSIZEFRAME, dpi) }; diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window.rs similarity index 72% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window.rs index 170de3668..c92bcfad0 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window.rs @@ -11,6 +11,7 @@ use std::{ ffi::OsStr, io, mem, os::windows::ffi::OsStrExt, + panic, sync::Arc, }; @@ -18,18 +19,16 @@ use crossbeam_channel as channel; use windows::{ core::PCWSTR, Win32::{ - Foundation::{ - self as win32f, HINSTANCE, HMODULE, HWND, LPARAM, LRESULT, POINT, POINTS, RECT, WPARAM, - }, + Foundation::{self as win32f, HINSTANCE, HWND, LPARAM, POINT, POINTS, RECT, WPARAM}, Graphics::{ Dwm::{DwmEnableBlurBehindWindow, DWM_BB_BLURREGION, DWM_BB_ENABLE, DWM_BLURBEHIND}, Gdi::*, }, - System::{Com::*, LibraryLoader::*, Ole::*}, + System::{Com::*, Ole::*}, UI::{ Input::{Ime::*, KeyboardAndMouse::*, Touch::*}, Shell::{ITaskbarList4 as ITaskbarList, TaskbarList, *}, - WindowsAndMessaging::{self as win32wm, *}, + WindowsAndMessaging::*, }, }, }; @@ -41,7 +40,7 @@ use crate::{ monitor::MonitorHandle as RootMonitorHandle, platform_impl::platform::{ dark_mode::try_window_theme, - dpi::{dpi_to_scale_factor, hwnd_dpi}, + dpi::{dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_dpi}, drop_handler::FileDropHandler, event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID}, icon::{self, IconType}, @@ -57,11 +56,11 @@ use crate::{ }; use super::{ - event_loop::CHANGE_THEME_MSG_ID, - keyboard::{KeyEventBuilder, KEY_EVENT_BUILDERS}, - util::calculate_insets_for_dpi, + event_loop::CHANGE_THEME_MSG_ID, keyboard::KeyEventBuilder, util::calculate_insets_for_dpi, }; +use super::event_loop::WindowData; + /// A simple non-owning wrapper around a window. #[derive(Clone, Copy)] pub struct WindowWrapper(pub HWND); @@ -73,6 +72,20 @@ pub struct WindowWrapper(pub HWND); unsafe impl Sync for WindowWrapper {} unsafe impl Send for WindowWrapper {} +impl WindowWrapper { + /// This is used for moving `WindowWrapper` instead of `window.0` + /// when using closures + /// + /// ```ignore + /// let window = self.window; + /// let not_send_sync = move || { window.0 }; + /// let send_sync = move || { window.hwnd() }; + /// ``` + fn hwnd(&self) -> HWND { + self.0 + } +} + /// The Win32 implementation of the main `Window` object. pub struct Window { /// Main handle for the window. @@ -95,77 +108,28 @@ impl Window { // First person to remove the need for cloning here gets a cookie! // // done. you owe me -- ossi - unsafe { - let drag_and_drop = pl_attr.drag_and_drop; - init(w_attr, pl_attr, event_loop).map(|win| { - let file_drop_handler = if drag_and_drop { - // It is ok if the initialize result is `S_FALSE` because it might happen that - // multiple windows are created on the same thread. - if let Err(error) = OleInitialize(None) { - match error.code() { - win32f::OLE_E_WRONGCOMPOBJ => { - panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`") - } - win32f::RPC_E_CHANGED_MODE => panic!( - "OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`. \ - Make sure other crates are not using multithreaded COM library \ - on the same thread or disable drag and drop support." - ), - _ => (), - }; - } - - let file_drop_runner = event_loop.runner_shared.clone(); - let file_drop_handler: IDropTarget = FileDropHandler::new( - win.window.0, - Box::new(move |event| { - if let Ok(e) = event.map_nonuser_event() { - file_drop_runner.send_event(e) - } - }), - ) - .into(); - - assert!(RegisterDragDrop(win.window.0, &file_drop_handler).is_ok()); - Some(file_drop_handler) - } else { - None - }; - - let subclass_input = event_loop::SubclassInput { - window_state: win.window_state.clone(), - event_loop_runner: event_loop.runner_shared.clone(), - _file_drop_handler: file_drop_handler, - subclass_removed: Cell::new(false), - recurse_depth: Cell::new(0), - event_loop_preferred_theme: event_loop.preferred_theme.clone(), - }; - - event_loop::subclass_window(win.window.0, subclass_input); - win - }) - } + unsafe { init::(w_attr, pl_attr, event_loop) } } pub fn set_title(&self, text: &str) { let text = util::encode_wide(text); unsafe { - let _ = SetWindowTextW(self.window.0, PCWSTR::from_raw(text.as_ptr())); + let _ = SetWindowTextW(self.hwnd(), PCWSTR::from_raw(text.as_ptr())); } } pub fn title(&self) -> String { - let len = unsafe { GetWindowTextLengthW(self.window.0) }; + let len = unsafe { GetWindowTextLengthW(self.hwnd()) }; let mut buf = vec![0; (len + 1) as usize]; - unsafe { GetWindowTextW(self.window.0, &mut buf) }; + unsafe { GetWindowTextW(self.hwnd(), &mut buf) }; String::from_utf16_lossy(&buf[..len as _]) } #[inline] pub fn set_visible(&self, visible: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::VISIBLE, visible) }); }); @@ -187,10 +151,10 @@ impl Window { #[inline] pub fn set_focusable(&self, focusable: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::FOCUSABLE, focusable) }); }); @@ -205,13 +169,13 @@ impl Window { #[inline] pub fn request_redraw(&self) { unsafe { - let _ = RedrawWindow(Some(self.window.0), None, None, RDW_INTERNALPAINT); + let _ = RedrawWindow(Some(self.hwnd()), None, None, RDW_INTERNALPAINT); } } #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { - unsafe { util::get_window_rect(self.window.0) } + unsafe { util::get_window_rect(self.hwnd()) } .map(|rect| Ok(PhysicalPosition::new(rect.left, rect.top))) .expect("Unexpected GetWindowRect failure") } @@ -219,7 +183,7 @@ impl Window { #[inline] pub fn inner_position(&self) -> Result, NotSupportedError> { let mut position = POINT::default(); - if !unsafe { ClientToScreen(self.window.0, &mut position) }.as_bool() { + if !unsafe { ClientToScreen(self.hwnd(), &mut position) }.as_bool() { panic!("Unexpected ClientToScreen failure") } Ok(PhysicalPosition::new(position.x, position.y)) @@ -230,16 +194,16 @@ impl Window { let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); let window_state = Arc::clone(&self.window_state); - let window = self.window.0 .0 as isize; + let window = self.window; self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MAXIMIZED, false) }); }); unsafe { let _ = SetWindowPos( - self.window.0, + self.hwnd(), None, x, y, @@ -247,7 +211,7 @@ impl Window { 0, SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE, ); - let _ = InvalidateRgn(self.window.0, None, false); + let _ = InvalidateRgn(self.hwnd(), None, false); } } @@ -298,15 +262,15 @@ impl Window { } let window_state = Arc::clone(&self.window_state); - let window = self.window.0 .0 as isize; + let window = self.window; self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MAXIMIZED, false) }); }); util::set_inner_size_physical( - self.window.0, + self.hwnd(), desired_width, desired_height, window_flags.contains(WindowFlags::MARKER_DECORATIONS), @@ -353,11 +317,11 @@ impl Window { #[inline] pub fn set_resizable(&self, resizable: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::RESIZABLE, resizable) }); }); @@ -365,11 +329,11 @@ impl Window { #[inline] pub fn set_minimizable(&self, minimizable: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MINIMIZABLE, minimizable) }); }); @@ -377,11 +341,11 @@ impl Window { #[inline] pub fn set_maximizable(&self, maximizable: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MAXIMIZABLE, maximizable) }); }); @@ -389,10 +353,10 @@ impl Window { #[inline] pub fn set_closable(&self, closable: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::CLOSABLE, closable) }); }); @@ -401,11 +365,11 @@ impl Window { /// Returns the `hwnd` of this window. #[inline] pub fn hwnd(&self) -> HWND { - self.window.0 + self.window.hwnd() } #[inline] - pub fn hinstance(&self) -> HMODULE { + pub fn hinstance(&self) -> HINSTANCE { util::get_instance_handle() } @@ -413,7 +377,7 @@ impl Window { #[inline] pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { let mut window_handle = rwh_04::Win32Handle::empty(); - window_handle.hwnd = self.window.0 .0 as *mut _; + window_handle.hwnd = self.hwnd().0; let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); window_handle.hinstance = hinstance as *mut _; rwh_04::RawWindowHandle::Win32(window_handle) @@ -423,7 +387,7 @@ impl Window { #[inline] pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { let mut window_handle = rwh_05::Win32WindowHandle::empty(); - window_handle.hwnd = self.window.0 .0 as *mut _; + window_handle.hwnd = self.hwnd().0; let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); window_handle.hinstance = hinstance as *mut _; rwh_05::RawWindowHandle::Win32(window_handle) @@ -440,7 +404,7 @@ impl Window { pub fn raw_window_handle_rwh_06(&self) -> Result { let mut window_handle = rwh_06::Win32WindowHandle::new(unsafe { // SAFETY: Handle will never be zero. - let window = self.window.0 .0; + let window = self.hwnd().0; std::num::NonZeroIsize::new_unchecked(window as _) }); let hinstance = util::GetWindowLongPtrW(self.hwnd(), GWLP_HINSTANCE); @@ -467,7 +431,7 @@ impl Window { #[inline] pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel::unbounded(); @@ -475,7 +439,7 @@ impl Window { let result = window_state .lock() .mouse - .set_cursor_flags(HWND(window as _), |f| f.set(CursorFlags::GRABBED, grab)) + .set_cursor_flags(window.hwnd(), |f| f.set(CursorFlags::GRABBED, grab)) .map_err(|e| ExternalError::Os(os_error!(OsError::IoError(e)))); let _ = tx.send(result); }); @@ -484,7 +448,7 @@ impl Window { #[inline] pub fn set_cursor_visible(&self, visible: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel::unbounded(); @@ -492,7 +456,7 @@ impl Window { let result = window_state .lock() .mouse - .set_cursor_flags(HWND(window as _), |f| f.set(CursorFlags::HIDDEN, !visible)) + .set_cursor_flags(window.hwnd(), |f| f.set(CursorFlags::HIDDEN, !visible)) .map_err(|e| e.to_string()); let _ = tx.send(result); }); @@ -516,7 +480,7 @@ impl Window { let mut point = POINT { x, y }; unsafe { - if !ClientToScreen(self.window.0, &mut point).as_bool() { + if !ClientToScreen(self.hwnd(), &mut point).as_bool() { return Err(ExternalError::Os(os_error!(OsError::IoError( io::Error::last_os_error() )))); @@ -564,10 +528,10 @@ impl Window { #[inline] pub fn set_ignore_cursor_events(&self, ignore: bool) -> Result<(), ExternalError> { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::IGNORE_CURSOR_EVENT, ignore) }); }); @@ -577,12 +541,12 @@ impl Window { #[inline] pub fn id(&self) -> WindowId { - WindowId(self.window.0 .0 as _) + WindowId(self.hwnd().0 as _) } #[inline] pub fn set_minimized(&self, minimized: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); let is_minimized = self.is_minimized(); @@ -591,7 +555,7 @@ impl Window { WindowState::set_window_flags_in_place(&mut window_state.lock(), |f| { f.set(WindowFlags::MINIMIZED, is_minimized) }); - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MINIMIZED, minimized) }); }); @@ -599,11 +563,11 @@ impl Window { #[inline] pub fn set_maximized(&self, maximized: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MAXIMIZED, maximized) }); }); @@ -662,7 +626,7 @@ impl Window { #[inline] pub fn is_visible(&self) -> bool { - util::is_visible(self.window.0) + util::is_visible(self.hwnd()) } #[inline] @@ -826,11 +790,11 @@ impl Window { #[inline] pub fn set_decorations(&self, decorations: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MARKER_DECORATIONS, decorations) }); }); @@ -838,11 +802,11 @@ impl Window { #[inline] pub fn set_always_on_bottom(&self, always_on_bottom: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::ALWAYS_ON_BOTTOM, always_on_bottom) }); }); @@ -850,22 +814,22 @@ impl Window { #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top) }); }); } pub fn set_rtl(&self, rtl: bool) { - let window = self.window.0 .0 as isize; + let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - WindowState::set_window_flags(window_state.lock(), HWND(window as _), |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::RIGHT_TO_LEFT_LAYOUT, rtl) }); }); @@ -874,7 +838,7 @@ impl Window { #[inline] pub fn current_monitor(&self) -> Option { Some(RootMonitorHandle { - inner: monitor::current_monitor(self.window.0), + inner: monitor::current_monitor(self.hwnd()), }) } @@ -883,9 +847,9 @@ impl Window { if let Some(ref window_icon) = window_icon { window_icon .inner - .set_for_window(self.window.0, IconType::Small); + .set_for_window(self.hwnd(), IconType::Small); } else { - icon::unset_for_window(self.window.0, IconType::Small); + icon::unset_for_window(self.hwnd(), IconType::Small); } self.window_state.lock().window_icon = window_icon; } @@ -895,9 +859,9 @@ impl Window { if let Some(ref taskbar_icon) = taskbar_icon { taskbar_icon .inner - .set_for_window(self.window.0, IconType::Big); + .set_for_window(self.hwnd(), IconType::Big); } else { - icon::unset_for_window(self.window.0, IconType::Big); + icon::unset_for_window(self.hwnd(), IconType::Big); } self.window_state.lock().taskbar_icon = taskbar_icon; } @@ -910,9 +874,9 @@ impl Window { rcArea: RECT::default(), }; unsafe { - let himc = ImmGetContext(self.window.0); + let himc = ImmGetContext(self.hwnd()); let _ = ImmSetCompositionWindow(himc, &composition_form); - let _ = ImmReleaseContext(self.window.0, himc); + let _ = ImmReleaseContext(self.hwnd(), himc); } } } @@ -1024,7 +988,7 @@ impl Window { pub fn set_progress_bar(&self, progress: ProgressBarState) { unsafe { let taskbar_list: ITaskbarList = CoCreateInstance(&TaskbarList, None, CLSCTX_SERVER).unwrap(); - let handle = self.window.0; + let handle = self.hwnd(); if let Some(state) = progress.state { let taskbar_state = { @@ -1060,7 +1024,7 @@ impl Window { unsafe { taskbar - .SetOverlayIcon(self.window.0, icon, None) + .SetOverlayIcon(self.hwnd(), icon, None) .unwrap_or(()); } } @@ -1071,8 +1035,7 @@ impl Window { let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { - let _ = &window; - WindowState::set_window_flags(window_state.lock(), window.0, |f| { + WindowState::set_window_flags(window_state.lock(), window.hwnd(), |f| { f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow) }); }); @@ -1104,12 +1067,167 @@ impl Window { impl Drop for Window { #[inline] fn drop(&mut self) { - KEY_EVENT_BUILDERS.lock().remove(&self.id()); unsafe { // The window must be destroyed from the same thread that created it, so we send a // custom message to be handled by our callback to do the actual work. - let _ = PostMessageW(Some(self.window.0), *DESTROY_MSG_ID, WPARAM(0), LPARAM(0)); + let _ = PostMessageW(Some(self.hwnd()), *DESTROY_MSG_ID, WPARAM(0), LPARAM(0)); + } + } +} + +pub(super) struct InitData<'a, T: 'static> { + // inputs + pub event_loop: &'a EventLoopWindowTarget, + pub attributes: WindowAttributes, + pub pl_attribs: PlatformSpecificWindowBuilderAttributes, + pub window_flags: WindowFlags, + // outputs + pub window: Option, +} + +impl InitData<'_, T> { + unsafe fn create_window(&self, window: HWND) -> Window { + // Register for touch events if applicable + { + let digitizer = GetSystemMetrics(SM_DIGITIZER) as u32; + if digitizer & NID_READY != 0 { + let _ = RegisterTouchWindow(window, TWF_WANTPALM); + } + } + + let dpi = hwnd_dpi(window); + let scale_factor = dpi_to_scale_factor(dpi); + + // If the system theme is dark, we need to set the window theme now + // before we update the window flags (and possibly show the + // window for the first time). + let current_theme = try_window_theme( + window, + self + .attributes + .preferred_theme + .or(*self.event_loop.preferred_theme.lock()), + false, + ); + + let window_state = { + let window_state = WindowState::new( + &self.attributes, + None, + scale_factor, + current_theme, + self.attributes.preferred_theme, + self.attributes.background_color, + ); + let window_state = Arc::new(Mutex::new(window_state)); + WindowState::set_window_flags(window_state.lock(), window, |f| *f = self.window_flags); + window_state + }; + + enable_non_client_dpi_scaling(window); + + Window { + window: WindowWrapper(window), + window_state, + thread_executor: self.event_loop.create_thread_executor(), + } + } + + unsafe fn create_window_data(&self, win: &Window) -> event_loop::WindowData { + let file_drop_handler = if self.pl_attribs.drag_and_drop { + // It is ok if the initialize result is `S_FALSE` because it might happen that + // multiple windows are created on the same thread. + if let Err(error) = OleInitialize(None) { + match error.code() { + win32f::OLE_E_WRONGCOMPOBJ => { + panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`") + } + win32f::RPC_E_CHANGED_MODE => panic!( + "OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`. \ + Make sure other crates are not using multithreaded COM library \ + on the same thread or disable drag and drop support." + ), + _ => (), + }; + } + + let file_drop_runner = self.event_loop.runner_shared.clone(); + let file_drop_handler: IDropTarget = FileDropHandler::new( + win.hwnd(), + Box::new(move |event| { + if let Ok(e) = event.map_nonuser_event() { + file_drop_runner.send_event(e) + } + }), + ) + .into(); + + assert!(RegisterDragDrop(win.hwnd(), &file_drop_handler).is_ok()); + Some(file_drop_handler) + } else { + None + }; + + self.event_loop.runner_shared.register_window(win.hwnd()); + + WindowData { + window_state: win.window_state.clone(), + event_loop_runner: self.event_loop.runner_shared.clone(), + key_event_builder: KeyEventBuilder::default(), + _file_drop_handler: file_drop_handler, + userdata_removed: Cell::new(false), + recurse_depth: Cell::new(0), + event_loop_preferred_theme: self.event_loop.preferred_theme.clone(), + } + } + + // Returns a pointer to window user data on success. + // The user data will be registered for the window and can be accessed within the window event + // callback. + pub unsafe fn on_nccreate(&mut self, window: HWND) -> Option { + let runner = self.event_loop.runner_shared.clone(); + let (win, userdata) = runner.catch_unwind(|| { + let window = unsafe { self.create_window(window) }; + let window_data = unsafe { self.create_window_data(&window) }; + (window, window_data) + })?; + + self.window = Some(win); + let userdata = Box::into_raw(Box::new(userdata)); + Some(userdata as _) + } + + pub unsafe fn on_create(&mut self) { + let win = self.window.as_mut().expect("failed window creation"); + + // making the window transparent + if self.attributes.transparent && !self.pl_attribs.no_redirection_bitmap { + // Empty region for the blur effect, so the window is fully transparent + let region = CreateRectRgn(0, 0, -1, -1); + + let bb = DWM_BLURBEHIND { + dwFlags: DWM_BB_ENABLE | DWM_BB_BLURREGION, + fEnable: true.into(), + hRgnBlur: region, + fTransitionOnMaximized: false.into(), + }; + + let _ = DwmEnableBlurBehindWindow(win.hwnd(), &bb); + let _ = DeleteObject(region.into()); + } + + if self.pl_attribs.skip_taskbar { + let _ = win.set_skip_taskbar(true); + } + win.set_window_icon(self.attributes.window_icon.clone()); + win.set_taskbar_icon(self.pl_attribs.taskbar_icon.clone()); + + if self.attributes.content_protection { + win.set_content_protection(true); } + + win.set_visible(self.attributes.visible); + win.set_closable(self.attributes.closable); } } @@ -1119,7 +1237,7 @@ unsafe fn init( event_loop: &EventLoopWindowTarget, ) -> Result { // registering the window class - let class_name = register_window_class(&pl_attribs.window_classname); + let class_name = register_window_class::(&pl_attribs.window_classname); let mut window_flags = WindowFlags::empty(); window_flags.set(WindowFlags::MARKER_DECORATIONS, attributes.decorations); @@ -1166,202 +1284,154 @@ unsafe fn init( } }; - // creating the real window this time, by using the functions in `extra_functions` - let real_window = { - let (style, ex_style) = window_flags.to_window_styles(); - let title = util::encode_wide(&attributes.title); - - let (target_monitor, position) = attributes - .position - .and_then(|p| { - monitor::available_monitors() - .into_iter() - .find_map(|monitor| { - let dpi = monitor.dpi(); - let scale_factor = dpi_to_scale_factor(dpi); - let position = p.to_physical::(scale_factor); - let (x, y): (i32, i32) = monitor.position().into(); - let (width, height): (i32, i32) = monitor.size().into(); - - let frame_thickness = if window_flags.contains_shadow() { - util::get_frame_thickness(dpi) - } else { - 0 - }; - - // Only the starting position x needs to be accounted - if x <= position.x + frame_thickness - && position.x <= x + width - && y <= position.y - && position.y <= y + height - { - Some((monitor, position.into())) - } else { - None - } - }) - }) - .unwrap_or_else(|| (monitor::primary_monitor(), (CW_USEDEFAULT, CW_USEDEFAULT))); - - let desired_size = attributes - .inner_size - .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); - let clamped_size = attributes - .inner_size_constraints - .clamp(desired_size, target_monitor.scale_factor()); - - // Best effort: try to create the window with the requested inner size - let adjusted_size = { - let (mut w, mut h): (i32, i32) = clamped_size - .to_physical::(target_monitor.scale_factor()) - .into(); - - if window_flags.contains(WindowFlags::MARKER_DECORATIONS) { - let mut rect = RECT { - left: 0, - top: 0, - right: w, - bottom: h, - }; + let (style, ex_style) = window_flags.to_window_styles(); + let title = util::encode_wide(&attributes.title); + + let (target_monitor, position) = attributes + .position + .and_then(|p| { + monitor::available_monitors() + .into_iter() + .find_map(|monitor| { + let dpi = monitor.dpi(); + let scale_factor = dpi_to_scale_factor(dpi); + let position = p.to_physical::(scale_factor); + let (x, y): (i32, i32) = monitor.position().into(); + let (width, height): (i32, i32) = monitor.size().into(); + + let frame_thickness = if window_flags.contains_shadow() { + util::get_frame_thickness(dpi) + } else { + 0 + }; - unsafe { - AdjustWindowRectEx( - &mut rect, - window_flags.to_adjusted_window_styles().0, - pl_attribs.menu.is_some(), - ex_style, - )?; - } + // Only the starting position x needs to be accounted + if x <= position.x + frame_thickness + && position.x <= x + width + && y <= position.y + && position.y <= y + height + { + Some((monitor, position.into())) + } else { + None + } + }) + }) + .unwrap_or_else(|| (monitor::primary_monitor(), (CW_USEDEFAULT, CW_USEDEFAULT))); + + let desired_size = attributes + .inner_size + .unwrap_or_else(|| PhysicalSize::new(800, 600).into()); + let clamped_size = attributes + .inner_size_constraints + .clamp(desired_size, target_monitor.scale_factor()); + + // Best effort: try to create the window with the requested inner size + let adjusted_size = { + let (mut w, mut h): (i32, i32) = clamped_size + .to_physical::(target_monitor.scale_factor()) + .into(); + + if window_flags.contains(WindowFlags::MARKER_DECORATIONS) { + let mut rect = RECT { + left: 0, + top: 0, + right: w, + bottom: h, + }; - w = rect.right - rect.left; - h = rect.bottom - rect.top; - } else if window_flags.undecorated_with_shadows() { - let dpi = target_monitor.dpi(); - let insets = calculate_insets_for_dpi(dpi); - w += insets.left + insets.right; - h += insets.top + insets.bottom; + unsafe { + AdjustWindowRectEx( + &mut rect, + window_flags.to_adjusted_window_styles().0, + pl_attribs.menu.is_some(), + ex_style, + )?; } - (w, h) - }; - - let handle = CreateWindowExW( - ex_style, - PCWSTR::from_raw(class_name.as_ptr()), - PCWSTR::from_raw(title.as_ptr()), - style, - position.0, - position.1, - adjusted_size.0, - adjusted_size.1, - parent, - pl_attribs.menu, - GetModuleHandleW(PCWSTR::null()).map(Into::into).ok(), - Some(Box::into_raw(Box::new(window_flags)) as _), - )?; - - if !IsWindow(Some(handle)).as_bool() { - return Err(os_error!(OsError::IoError(io::Error::last_os_error()))); + w = rect.right - rect.left; + h = rect.bottom - rect.top; + } else if window_flags.undecorated_with_shadows() { + let dpi = target_monitor.dpi(); + let insets = calculate_insets_for_dpi(dpi); + w += insets.left + insets.right; + h += insets.top + insets.bottom; } - super::dark_mode::allow_dark_mode_for_window(handle, true); - - WindowWrapper(handle) + (w, h) }; - // Register for touch events if applicable - { - let digitizer = GetSystemMetrics(SM_DIGITIZER) as u32; - if digitizer & NID_READY != 0 { - RegisterTouchWindow(real_window.0, TWF_WANTPALM)?; - } - } - - let dpi = hwnd_dpi(real_window.0); - let scale_factor = dpi_to_scale_factor(dpi); - - // making the window transparent - if attributes.transparent && !pl_attribs.no_redirection_bitmap { - // Empty region for the blur effect, so the window is fully transparent - let region = CreateRectRgn(0, 0, -1, -1); - - let bb = DWM_BLURBEHIND { - dwFlags: DWM_BB_ENABLE | DWM_BB_BLURREGION, - fEnable: true.into(), - hRgnBlur: region, - fTransitionOnMaximized: false.into(), - }; - - let _ = DwmEnableBlurBehindWindow(real_window.0, &bb); - let _ = DeleteObject(region.into()); - } + let fullscreen = attributes.fullscreen.clone(); + let maximized = attributes.maximized; + let focused = attributes.focused; + let menu = pl_attribs.menu; + + let mut initdata = InitData { + event_loop, + attributes, + pl_attribs, + window_flags, + window: None, + }; - // If the system theme is dark, we need to set the window theme now - // before we update the window flags (and possibly show the - // window for the first time). - let current_theme = try_window_theme( - real_window.0, - attributes - .preferred_theme - .or(*event_loop.preferred_theme.lock()), - false, + let result = CreateWindowExW( + ex_style, + PCWSTR::from_raw(class_name.as_ptr()), + PCWSTR::from_raw(title.as_ptr()), + style, + position.0, + position.1, + adjusted_size.0, + adjusted_size.1, + parent, + menu, + Some(util::get_instance_handle()), + Some(&mut initdata as *mut _ as *mut _), ); - let window_state = { - let window_state = WindowState::new( - &attributes, - None, - scale_factor, - current_theme, - attributes.preferred_theme, - attributes.background_color, - ); - let window_state = Arc::new(Mutex::new(window_state)); - WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags); - window_state - }; + // If the window creation in `InitData` panicked, then should resume panicking here + if let Err(panic_error) = event_loop.runner_shared.take_panic_error() { + panic::resume_unwind(panic_error) + } - let win = Window { - window: real_window, - window_state, - thread_executor: event_loop.create_thread_executor(), - }; + let handle = result?; - KEY_EVENT_BUILDERS - .lock() - .insert(win.id(), KeyEventBuilder::default()); + super::dark_mode::allow_dark_mode_for_window(handle, true); - let _ = win.set_skip_taskbar(pl_attribs.skip_taskbar); - win.set_window_icon(attributes.window_icon); - win.set_taskbar_icon(pl_attribs.taskbar_icon); + // If the handle is non-null, then window creation must have succeeded, which means + // that we *must* have populated the `InitData.window` field. + let window = initdata.window.unwrap(); - if attributes.fullscreen.is_some() { - win.set_fullscreen(attributes.fullscreen); - force_window_active(win.window.0); - } else if attributes.maximized { - win.set_maximized(true); + // Need to set FULLSCREEN or MAXIMIZED after CreateWindowEx + // This is because if the size is changed in WM_CREATE, the restored size will be stored in that + // size. + if fullscreen.is_some() { + window.set_fullscreen(fullscreen); + force_window_active(window.hwnd()); + } else if maximized { + window.set_maximized(true); } - if attributes.content_protection { - win.set_content_protection(true); + if !focused { + window + .window_state + .lock() + .set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_DONT_FOCUS)); } - win.set_visible(attributes.visible); - win.set_closable(attributes.closable); - - Ok(win) + Ok(window) } -unsafe fn register_window_class(window_classname: &str) -> Vec { +unsafe fn register_window_class(window_classname: &str) -> Vec { let class_name = util::encode_wide(window_classname); let class = WNDCLASSEXW { cbSize: mem::size_of::() as u32, style: CS_HREDRAW | CS_VREDRAW | CS_OWNDC, - lpfnWndProc: Some(window_proc), + lpfnWndProc: Some(super::event_loop::public_window_callback::), cbClsExtra: 0, cbWndExtra: 0, - hInstance: HINSTANCE(GetModuleHandleW(PCWSTR::null()).unwrap_or_default().0), + hInstance: util::get_instance_handle(), hIcon: HICON::default(), hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly hbrBackground: HBRUSH::default(), @@ -1379,62 +1449,6 @@ unsafe fn register_window_class(window_classname: &str) -> Vec { class_name } -unsafe extern "system" fn window_proc( - window: HWND, - msg: u32, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - // This window procedure is only needed until the subclass procedure is attached. - // we need this because we need to respond to WM_NCCALCSIZE as soon as possible - // in order to make the window borderless if needed. - match msg { - win32wm::WM_NCCALCSIZE => { - let userdata = util::GetWindowLongPtrW(window, GWL_USERDATA); - if userdata != 0 { - let window_flags = WindowFlags::from_bits_truncate(userdata as _); - - if wparam == WPARAM(0) || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { - return DefWindowProcW(window, msg, wparam, lparam); - } - - // adjust the maximized borderless window so it doesn't cover the taskbar - if util::is_maximized(window).unwrap_or(false) { - let params = &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS); - if let Ok(monitor_info) = - monitor::get_monitor_info(MonitorFromRect(¶ms.rgrc[0], MONITOR_DEFAULTTONULL)) - { - params.rgrc[0] = monitor_info.monitorInfo.rcWork; - } - } else if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) { - let params = &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS); - - let insets = util::calculate_window_insets(window); - - params.rgrc[0].left += insets.left; - params.rgrc[0].top += insets.top; - params.rgrc[0].right -= insets.right; - params.rgrc[0].bottom -= insets.bottom; - } - return LRESULT(0); // return 0 here to make the window borderless - } - - DefWindowProcW(window, msg, wparam, lparam) - } - win32wm::WM_NCCREATE => { - let userdata = util::GetWindowLongPtrW(window, GWL_USERDATA); - if userdata == 0 { - let createstruct = &*(lparam.0 as *const CREATESTRUCTW); - let userdata = createstruct.lpCreateParams; - let window_flags = Box::from_raw(userdata as *mut WindowFlags); - util::SetWindowLongPtrW(window, GWL_USERDATA, window_flags.bits() as _); - } - DefWindowProcW(window, msg, wparam, lparam) - } - _ => DefWindowProcW(window, msg, wparam, lparam), - } -} - struct ComInitialized(Option<()>); impl Drop for ComInitialized { fn drop(&mut self) { diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window_state.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window_state.rs similarity index 94% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window_state.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window_state.rs index 6437c80df..4595b14f9 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/platform_impl/windows/window_state.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/platform_impl/windows/window_state.rs @@ -6,7 +6,7 @@ use crate::{ dpi::PhysicalPosition, icon::Icon, keyboard::ModifiersState, - platform_impl::platform::{event_loop, minimal_ime::MinimalIme, util}, + platform_impl::platform::{event_loop, util}, window::{CursorIcon, Fullscreen, Theme, WindowAttributes, WindowSizeConstraints, RGBA}, }; use parking_lot::MutexGuard; @@ -39,8 +39,6 @@ pub struct WindowState { pub current_theme: Theme, pub preferred_theme: Option, - pub ime_handler: MinimalIme, - pub window_flags: WindowFlags, // Used by WM_NCACTIVATE, WM_SETFOCUS and WM_KILLFOCUS @@ -156,7 +154,6 @@ impl WindowState { fullscreen: None, current_theme, preferred_theme, - ime_handler: MinimalIme::default(), window_flags: WindowFlags::empty(), is_active: false, is_focused: false, @@ -312,30 +309,16 @@ impl WindowFlags { } /// Adjust the window client rectangle to the return value, if present. - fn apply_diff(mut self, window: HWND, mut new: WindowFlags) { - self = self.mask(); - new = new.mask(); + fn apply_diff(self, window: HWND, new: WindowFlags) { + let old = self.mask(); + let new = new.mask(); - let mut diff = self ^ new; + let mut diff = old ^ new; if diff == WindowFlags::empty() { return; } - if new.contains(WindowFlags::VISIBLE) { - unsafe { - let _ = ShowWindow( - window, - if self.contains(WindowFlags::MARKER_DONT_FOCUS) { - self.set(WindowFlags::MARKER_DONT_FOCUS, false); - SW_SHOWNOACTIVATE - } else { - SW_SHOW - }, - ); - } - } - if diff.contains(WindowFlags::ALWAYS_ON_TOP) { unsafe { let _ = SetWindowPos( @@ -374,7 +357,10 @@ impl WindowFlags { } } - if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) { + if (diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED)) + // This is to avoid the window from flashing + && !(new.contains(WindowFlags::MAXIMIZED) && !new.contains(WindowFlags::VISIBLE)) + { unsafe { let _ = ShowWindow( window, @@ -424,7 +410,11 @@ impl WindowFlags { } if diff != WindowFlags::empty() { - let (style, style_ex) = new.to_window_styles(); + let (mut style, style_ex) = new.to_window_styles(); + // Remove `WS_VISIBLE` if we were hidden, this is required for the `ShowWindow` below to work + if !old.contains(WindowFlags::VISIBLE) { + style &= !WS_VISIBLE; + } unsafe { SendMessageW( @@ -461,6 +451,21 @@ impl WindowFlags { ); } } + + // This needs to be after the `SetWindowPos` above or there will be + // a title bar flicker on undecorated windows's creation + if new.contains(WindowFlags::VISIBLE) { + unsafe { + let _ = ShowWindow( + window, + if old.contains(WindowFlags::MARKER_DONT_FOCUS) { + SW_SHOWNOACTIVATE + } else { + SW_SHOW + }, + ); + } + } } pub fn undecorated_with_shadows(&self) -> bool { diff --git a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/window.rs b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/window.rs similarity index 99% rename from apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/window.rs rename to apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/window.rs index 9ec5ccc49..e43c72eff 100644 --- a/apps/maple-research/frontend/src-tauri/patches/tao-0.35.2/src/window.rs +++ b/apps/maple-research/frontend/src-tauri/patches/tao-0.37.1/src/window.rs @@ -718,7 +718,7 @@ impl Window { /// - **iOS:** Can only be called on the main thread. Returns the top left coordinates of the /// window in the screen space coordinate system. /// - **Android:** Always returns [`NotSupportedError`]. - /// - **Linux(Wayland)**: Has no effect, since Wayland doesn't support a global cordinate system + /// - **Linux(Wayland)**: Has no effect, since Wayland doesn't support a global coordinate system #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { self.window.outer_position() @@ -828,7 +828,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android:** Unsupported. Returns ane empty string. + /// - **iOS / Android:** Unsupported. Returns an empty string. #[inline] pub fn title(&self) -> String { self.window.title() @@ -1662,6 +1662,7 @@ pub enum ResizeDirection { West, } +#[cfg(any(target_os = "windows", target_os = "linux"))] pub(crate) fn hit_test( (left, top, right, bottom): (i32, i32, i32, i32), cx: i32, diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.cargo-ok b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.cargo-ok new file mode 100644 index 000000000..5f8b79583 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.gitignore b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.gitignore new file mode 100644 index 000000000..d6789ca54 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.gitignore @@ -0,0 +1,8 @@ +/.tauri/ +/ios/.build/ +/ios/Package.resolved +/target/ +/Cargo.lock +/permissions/schemas/ +/permissions/autogenerated/ +/android/.gradle/ diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/.gitignore b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/.gitignore new file mode 100644 index 000000000..5922fdaa5 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Package.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Package.swift new file mode 100644 index 000000000..43417f18c --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Package.swift @@ -0,0 +1,40 @@ +// swift-tools-version:5.3 +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "Tauri", + platforms: [ + .macOS(.v10_13), + .iOS(.v11), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "Tauri", + type: .static, + targets: ["Tauri"]) + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(name: "SwiftRs", url: "https://github.com/Brendonovich/swift-rs", from: "1.0.9") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "Tauri", + dependencies: [ + .byName(name: "SwiftRs") + ], + path: "Sources" + ), + .testTarget( + name: "TauriTests", + dependencies: ["Tauri"] + ), + ] +) diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/README.md b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/README.md new file mode 100644 index 000000000..52c3f1c7f --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/README.md @@ -0,0 +1,3 @@ +# Tauri + +Tauri iOS API. diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Channel.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Channel.swift new file mode 100644 index 000000000..add065c74 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Channel.swift @@ -0,0 +1,65 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +let CHANNEL_PREFIX = "__CHANNEL__:" +let channelDataKey = CodingUserInfoKey(rawValue: "sendChannelData")! + +public class Channel: Decodable { + public let id: UInt64 + let handler: (UInt64, String) -> Void + + public required init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let channelDef = try container.decode(String.self) + + let components = channelDef.components(separatedBy: CHANNEL_PREFIX) + if components.count < 2 { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Invalid channel definition from \(channelDef)" + ) + + } + guard let channelId = UInt64(components[1]) else { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Invalid channel ID from \(channelDef)" + ) + } + + guard let handler = decoder.userInfo[channelDataKey] as? (UInt64, String) -> Void else { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "missing userInfo for Channel handler. This is a Tauri issue" + ) + } + + self.id = channelId + self.handler = handler + } + + func serialize(_ data: JsonValue) -> String { + do { + return try data.jsonRepresentation() ?? "\"Failed to serialize payload\"" + } catch { + return "\"\(error)\"" + } + } + + public func send(_ data: JsonObject) { + send(.dictionary(data)) + } + + public func send(_ data: JsonValue) { + handler(id, serialize(data)) + } + + public func send(_ data: T) throws { + let json = try JSONEncoder().encode(data) + handler(id, String(decoding: json, as: UTF8.self)) + } + +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Invoke.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Invoke.swift new file mode 100644 index 000000000..6f810868f --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Invoke.swift @@ -0,0 +1,118 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation +import UIKit + +@objc public class Invoke: NSObject { + public let command: String + let callback: UInt64 + let error: UInt64 + let data: String + let sendResponse: (UInt64, String?) -> Void + let sendChannelData: (UInt64, String) -> Void + + public init( + command: String, callback: UInt64, error: UInt64, + sendResponse: @escaping (UInt64, String?) -> Void, + sendChannelData: @escaping (UInt64, String) -> Void, data: String + ) { + self.command = command + self.callback = callback + self.error = error + self.data = data + self.sendResponse = sendResponse + self.sendChannelData = sendChannelData + } + + public func getRawArgs() -> String { + return self.data + } + + public func getArgs() throws -> JSObject { + let jsonData = self.data.data(using: .utf8)! + let data = try JSONSerialization.jsonObject(with: jsonData, options: []) + return JSTypes.coerceDictionaryToJSObject( + (data as! NSDictionary), formattingDatesAsStrings: true)! + } + + public func parseArgs(_ type: T.Type) throws -> T { + let jsonData = self.data.data(using: .utf8)! + let decoder = JSONDecoder() + decoder.userInfo[channelDataKey] = sendChannelData + return try decoder.decode(type, from: jsonData) + } + + func serialize(_ data: JsonValue) -> String { + do { + return try data.jsonRepresentation() ?? "\"Failed to serialize payload\"" + } catch { + return "\"\(error)\"" + } + } + + public func resolve() { + sendResponse(callback, nil) + } + + public func resolve(_ data: JsonObject) { + resolve(.dictionary(data)) + } + + public func resolve(_ data: JsonValue) { + sendResponse(callback, serialize(data)) + } + + public func resolve(_ data: T) { + do { + let json = try JSONEncoder().encode(data) + sendResponse(callback, String(decoding: json, as: UTF8.self)) + } catch { + sendResponse(self.error, "\"\(error)\"") + } + } + + public func reject( + _ message: String, code: String? = nil, error: Error? = nil, data: JsonValue? = nil + ) { + let payload: NSMutableDictionary = [ + "message": message + ] + + if let code = code { + payload["code"] = code + } + + if let error = error { + payload["error"] = error + } + + if let data = data { + switch data { + case .dictionary(let dict): + for entry in dict { + payload[entry.key] = entry.value + } + } + } + + sendResponse(self.error, serialize(.dictionary(payload as! JsonObject))) + } + + public func unimplemented() { + unimplemented("not implemented") + } + + public func unimplemented(_ message: String) { + reject(message) + } + + public func unavailable() { + unavailable("not available") + } + + public func unavailable(_ message: String) { + reject(message) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JSTypes.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JSTypes.swift new file mode 100644 index 000000000..0ac9f2c79 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JSTypes.swift @@ -0,0 +1,123 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +// declare our empty protocol, and conformance, for typing +public protocol JSValue {} +extension String: JSValue {} +extension Bool: JSValue {} +extension Int: JSValue {} +extension Float: JSValue {} +extension Double: JSValue {} +extension NSNumber: JSValue {} +extension NSNull: JSValue {} +extension Array: JSValue {} +extension Date: JSValue {} +extension Dictionary: JSValue where Key == String, Value == JSValue {} + +// convenience aliases +public typealias JSObject = [String: JSValue] +public typealias JSArray = [JSValue] + +extension Dictionary where Key == String, Value == JSValue { + public func getValue(_ key: String) -> JSValue? { + return self[key] + } + + public func getString(_ key: String) -> String? { + return self[key] as? String + } + + public func getBool(_ key: String) -> Bool? { + return self[key] as? Bool + } + + public func getInt(_ key: String) -> Int? { + return self[key] as? Int + } + + public func getFloat(_ key: String) -> Float? { + if let floatValue = self[key] as? Float { + return floatValue + } else if let doubleValue = self[key] as? Double { + return Float(doubleValue) + } + return nil + } + + public func getDouble(_ key: String) -> Double? { + return self[key] as? Double + } + + public func getArray(_ key: String) -> JSArray? { + return self[key] as? JSArray + } + + public func getObject(_ key: String) -> JSObject? { + return self[key] as? JSObject + } +} + +/* + Simply casting objects from foundation class clusters (such as __NSArrayM) + doesn't work with the JSValue protocol and will always fail. So we need to + recursively and explicitly convert each value in the dictionary. + */ +public enum JSTypes {} +extension JSTypes { + public static func coerceDictionaryToJSObject( + _ dictionary: NSDictionary?, formattingDatesAsStrings: Bool = false + ) -> JSObject? { + return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject + } + + public static func coerceDictionaryToJSObject( + _ dictionary: [AnyHashable: Any]?, formattingDatesAsStrings: Bool = false + ) -> JSObject? { + return coerceToJSValue(dictionary, formattingDates: formattingDatesAsStrings) as? JSObject + } +} + +private let dateStringFormatter = ISO8601DateFormatter() + +// We need a large switch statement because we have a lot of types. +// swiftlint:disable:next cyclomatic_complexity +private func coerceToJSValue(_ value: Any?, formattingDates: Bool) -> JSValue? { + guard let value = value else { + return nil + } + switch value { + case let stringValue as String: + return stringValue + case let numberValue as NSNumber: + return numberValue + case let boolValue as Bool: + return boolValue + case let intValue as Int: + return intValue + case let floatValue as Float: + return floatValue + case let doubleValue as Double: + return doubleValue + case let dateValue as Date: + if formattingDates { + return dateStringFormatter.string(from: dateValue) + } + return dateValue + case let nullValue as NSNull: + return nullValue + case let arrayValue as NSArray: + return arrayValue.compactMap { coerceToJSValue($0, formattingDates: formattingDates) } + case let dictionaryValue as NSDictionary: + let keys = dictionaryValue.allKeys.compactMap { $0 as? String } + var result: JSObject = [:] + for key in keys { + result[key] = coerceToJSValue(dictionaryValue[key], formattingDates: formattingDates) + } + return result + default: + return nil + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JsonValue.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JsonValue.swift new file mode 100644 index 000000000..ae6d96f77 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/JsonValue.swift @@ -0,0 +1,58 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +public typealias JsonObject = [String: Any?] + +public enum JsonValue { + case dictionary(JsonObject) + + enum SerializationError: Error { + case invalidObject + } + + public func jsonRepresentation(includingFields: JsonObject? = nil) throws -> String? { + switch self { + case .dictionary(var dictionary): + if let fields = includingFields { + dictionary.merge(fields) { (current, _) in current } + } + dictionary = prepare(dictionary: dictionary) + guard JSONSerialization.isValidJSONObject(dictionary) else { + throw SerializationError.invalidObject + } + let data = try JSONSerialization.data(withJSONObject: dictionary, options: []) + return String(data: data, encoding: .utf8) + } + } + + private static let formatter = ISO8601DateFormatter() + + private func prepare(dictionary: JsonObject) -> JsonObject { + return dictionary.mapValues { (value) -> Any in + if let date = value as? Date { + return JsonValue.formatter.string(from: date) + } else if let aDictionary = value as? JsonObject { + return prepare(dictionary: aDictionary) + } else if let anArray = value as? [Any] { + return prepare(array: anArray) + } + return value + } + } + + private func prepare(array: [Any]) -> [Any] { + return array.map { (value) -> Any in + if let date = value as? Date { + return JsonValue.formatter.string(from: date) + } else if let aDictionary = value as? JsonObject { + return prepare(dictionary: aDictionary) + } else if let anArray = value as? [Any] { + return prepare(array: anArray) + } + return value + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Logger.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Logger.swift new file mode 100644 index 000000000..d055090dc --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Logger.swift @@ -0,0 +1,139 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import os.log +import UIKit +import Foundation + +class StdoutRedirector { + private var originalStdout: Int32 = -1 + private var originalStderr: Int32 = -1 + private var stdoutPipe: [Int32] = [-1, -1] + private var stderrPipe: [Int32] = [-1, -1] + private var stdoutReadSource: DispatchSourceRead? + private var stderrReadSource: DispatchSourceRead? + + func start() { + originalStdout = dup(STDOUT_FILENO) + originalStderr = dup(STDERR_FILENO) + + guard Darwin.pipe(&stdoutPipe) == 0, + Darwin.pipe(&stderrPipe) == 0 else { + Logger.error("Failed to create stdout/stderr pipes") + return + } + + dup2(stdoutPipe[1], STDOUT_FILENO) + dup2(stderrPipe[1], STDERR_FILENO) + close(stdoutPipe[1]) + close(stderrPipe[1]) + + stdoutReadSource = createReader( + readPipe: stdoutPipe[0], + writeToOriginal: originalStdout, + label: "stdout" + ) + + stderrReadSource = createReader( + readPipe: stderrPipe[0], + writeToOriginal: originalStderr, + label: "stderr" + ) + } + + private func createReader( + readPipe: Int32, + writeToOriginal: Int32, + label: String + ) -> DispatchSourceRead { + let source = DispatchSource.makeReadSource( + fileDescriptor: readPipe, + queue: .global(qos: .utility) + ) + + source.setEventHandler { + let bufferSize = 4096 + var buffer = [UInt8](repeating: 0, count: bufferSize) + let bytesRead = read(readPipe, &buffer, bufferSize) + + if bytesRead > 0 { + let output = String( + bytes: buffer[0..(_ type: T.Type) throws -> T { + let jsonData = self.config.data(using: .utf8)! + let decoder = JSONDecoder() + return try decoder.decode(type, from: jsonData) + } + + @objc open func load(webview: WKWebView) {} + + @objc open func checkPermissions(_ invoke: Invoke) { + invoke.resolve() + } + + @objc open func requestPermissions(_ invoke: Invoke) { + invoke.resolve() + } + + public func trigger(_ event: String, data: JSObject) { + if let eventListeners = listeners[event] { + for channel in eventListeners { + channel.send(data) + } + } + } + + public func trigger(_ event: String, data: T) throws { + if let eventListeners = listeners[event] { + for channel in eventListeners { + try channel.send(data) + } + } + } + + @objc func registerListener(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(RegisterListenerArgs.self) + + if var eventListeners = listeners[args.event] { + eventListeners.append(args.handler) + listeners[args.event] = eventListeners + } else { + listeners[args.event] = [args.handler] + } + + invoke.resolve() + } + + @objc func removeListener(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(RemoveListenerArgs.self) + + if let eventListeners = listeners[args.event] { + + listeners[args.event] = eventListeners.filter { $0.id != args.channelId } + } + + invoke.resolve() + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Tauri.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Tauri.swift new file mode 100644 index 000000000..3404dacfa --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/Tauri.swift @@ -0,0 +1,157 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation +import SwiftRs +import UIKit +import WebKit +import os.log + +class PluginHandle { + var instance: Plugin + var loaded = false + + init(plugin: Plugin) { + instance = plugin + } +} + +public class PluginManager { + static let shared: PluginManager = PluginManager() + public var viewController: UIViewController? + var plugins: [String: PluginHandle] = [:] + var ipcDispatchQueue = DispatchQueue(label: "ipc") + public var isSimEnvironment: Bool { + #if targetEnvironment(simulator) + return true + #else + return false + #endif + } + + public func assetUrl(fromLocalURL url: URL?) -> URL? { + guard let inputURL = url else { + return nil + } + + return URL(string: "asset://localhost")!.appendingPathComponent(inputURL.path) + } + + func onWebviewCreated(_ webview: WKWebView) { + for (_, handle) in plugins { + if !handle.loaded { + handle.instance.load(webview: webview) + } + } + } + + func load(name: String, plugin: P, config: String, webview: WKWebView?) { + plugin.setConfig(config) + let handle = PluginHandle(plugin: plugin) + if let webview = webview { + handle.instance.load(webview: webview) + handle.loaded = true + } + plugins[name] = handle + } + + func invoke(name: String, invoke: Invoke) { + if let plugin = plugins[name] { + ipcDispatchQueue.async { + let selectorWithCompletionHandler = Selector(("\(invoke.command):completionHandler:")) + let selectorWithThrows = Selector(("\(invoke.command):error:")) + + if plugin.instance.responds(to: selectorWithCompletionHandler) { + let completion: @convention(block) (NSError?) -> Void = { error in + if let error = error { + invoke.reject("\(error)") + } + } + + let blockObj: AnyObject = unsafeBitCast(completion, to: AnyObject.self) + let imp = plugin.instance.method(for: selectorWithCompletionHandler) + + typealias Fn = @convention(c) (AnyObject, Selector, Invoke, AnyObject) -> Void + let fn = unsafeBitCast(imp, to: Fn.self) + fn(plugin.instance, selectorWithCompletionHandler, invoke, blockObj) + } else if plugin.instance.responds(to: selectorWithThrows) { + var error: NSError? = nil + withUnsafeMutablePointer(to: &error) { + let methodIMP: IMP! = plugin.instance.method(for: selectorWithThrows) + unsafeBitCast( + methodIMP, to: (@convention(c) (Any?, Selector, Invoke, OpaquePointer) -> Void).self)( + plugin.instance, selectorWithThrows, invoke, OpaquePointer($0)) + } + if let error = error { + invoke.reject("\(error)") + // TODO: app crashes without this leak + let _ = Unmanaged.passRetained(error) + } + } else { + let selector = Selector(("\(invoke.command):")) + if plugin.instance.responds(to: selector) { + plugin.instance.perform(selector, with: invoke) + } else { + invoke.reject("No command \(invoke.command) found for plugin \(name)") + } + } + } + } else { + invoke.reject("Plugin \(name) not initialized") + } + } +} + +extension PluginManager: NSCopying { + public func copy(with zone: NSZone? = nil) -> Any { + return self + } +} + +private var stdoutRedirector: StdoutRedirector? + +@_cdecl("log_stdout") +public func logStdout() { + stdoutRedirector = StdoutRedirector() + stdoutRedirector!.start() +} + +@_cdecl("register_plugin") +public func registerPlugin(name: SRString, plugin: NSObject, config: SRString, webview: WKWebView?) { + PluginManager.shared.load( + name: name.toString(), + plugin: plugin as! Plugin, + config: config.toString(), + webview: webview + ) +} + +@_cdecl("on_webview_created") +public func onWebviewCreated(webview: WKWebView, viewController: UIViewController) { + PluginManager.shared.viewController = viewController + PluginManager.shared.onWebviewCreated(webview) +} + +@_cdecl("run_plugin_command") +public func runCommand( + id: Int, + name: SRString, + command: SRString, + data: SRString, + callback: @escaping @convention(c) (Int, Bool, UnsafePointer) -> Void, + sendChannelData: @escaping @convention(c) (UInt64, UnsafePointer) -> Void +) { + let callbackId: UInt64 = 0 + let errorId: UInt64 = 1 + let invoke = Invoke( + command: command.toString(), callback: callbackId, error: errorId, + sendResponse: { (fn: UInt64, payload: String?) -> Void in + let success = fn == callbackId + callback(id, success, payload ?? "null") + }, + sendChannelData: { (id: UInt64, payload: String) -> Void in + sendChannelData(id, payload) + }, data: data.toString()) + PluginManager.shared.invoke(name: name.toString(), invoke: invoke) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/UiUtils.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/UiUtils.swift new file mode 100644 index 000000000..4097596e2 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/.tauri/tauri-api/Sources/Tauri/UiUtils.swift @@ -0,0 +1,15 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import UIKit + +public class UIUtils { + public static func centerPopover(rootViewController: UIViewController?, popoverController: UIViewController) { + if let viewController = rootViewController { + popoverController.popoverPresentationController?.sourceRect = CGRect(x: viewController.view.center.x, y: viewController.view.center.y, width: 0, height: 0) + popoverController.popoverPresentationController?.sourceView = viewController.view + popoverController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up + } + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml new file mode 100644 index 000000000..df1bb1bed --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml @@ -0,0 +1,49 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.77.2" +name = "tauri-plugin-sign-in-with-apple" +version = "1.0.2" +authors = ["Manaf941"] +build = "build.rs" +links = "tauri-plugin-sign-in-with-apple" +exclude = [ + "/examples", + "/webview-dist", + "/webview-src", + "/node_modules", +] +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Add Sign In With Apple capabilities to your Tauri iOS App." +readme = "README.md" +license = "MIT" + +[lib] +name = "tauri_plugin_sign_in_with_apple" +path = "src/lib.rs" + +[dependencies.serde] +version = "1.0" + +[dependencies.tauri] +version = "2.1.0" + +[dependencies.thiserror] +version = "2" + +[build-dependencies.tauri-plugin] +version = "2.0.2" +features = ["build"] diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml.orig b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml.orig new file mode 100644 index 000000000..991605794 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/Cargo.toml.orig @@ -0,0 +1,18 @@ +[package] +name = "tauri-plugin-sign-in-with-apple" +version = "1.0.2" +authors = [ "Manaf941" ] +description = "Add Sign In With Apple capabilities to your Tauri iOS App." +license = "MIT" +edition = "2021" +rust-version = "1.77.2" +exclude = ["/examples", "/webview-dist", "/webview-src", "/node_modules"] +links = "tauri-plugin-sign-in-with-apple" + +[dependencies] +tauri = { version = "2.1.0" } +serde = "1.0" +thiserror = "2" + +[build-dependencies] +tauri-plugin = { version = "2.0.2", features = ["build"] } diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/LICENSE b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/LICENSE new file mode 100644 index 000000000..8cb345be2 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/LICENSE @@ -0,0 +1,7 @@ +Copyright 2024 Manaf Mhamdi Alaoui + +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/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/README.md b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/README.md new file mode 100644 index 000000000..792e168d1 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/README.md @@ -0,0 +1,13 @@ +# Tauri Plugin sign_in_with_apple + +probably unstable, use with caution ! + +For now, only iOS works. + +For other platform, you should probably use the Sign In with Apple Web SDK. + +https://developer.apple.com/help/account/configure-app-capabilities/configure-sign-in-with-apple-for-the-web/ + +in case of any question, contact me: +Discord: @manaf941 +Twitter: @manaaaaaaaf \ No newline at end of file diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/.gitignore b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/.gitignore new file mode 100644 index 000000000..c0f21ec2f --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/build.gradle.kts b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/build.gradle.kts new file mode 100644 index 000000000..0ed421e5a --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "ch.manaf.tauri_plugins.sign_in_with_apple" + compileSdk = 34 + + defaultConfig { + minSdk = 21 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/proguard-rules.pro b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/settings.gradle b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/settings.gradle new file mode 100644 index 000000000..d7782a40d --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/settings.gradle @@ -0,0 +1,31 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + google() + } + resolutionStrategy { + eachPlugin { + switch (requested.id.id) { + case "com.android.library": + useVersion("8.0.2") + break + case "org.jetbrains.kotlin.android": + useVersion("1.8.20") + break + } + } + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenCentral() + google() + + } +} + +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/androidTest/java/ExampleInstrumentedTest.kt b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..7f1e75be0 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package ch.manaf.tauri_plugins.sign_in_with_apple + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("ch.manaf.tauri_plugins.sign_in_with_apple", appContext.packageName) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/AndroidManifest.xml b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..9a40236b9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/Example.kt b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/Example.kt new file mode 100644 index 000000000..28c6203ea --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/Example.kt @@ -0,0 +1,10 @@ +package ch.manaf.tauri_plugins.sign_in_with_apple + +import android.util.Log + +class Example { + fun pong(value: String): String { + Log.i("Pong", value) + return value + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/ExamplePlugin.kt b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/ExamplePlugin.kt new file mode 100644 index 000000000..045f0c6ff --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/main/java/ExamplePlugin.kt @@ -0,0 +1,28 @@ +package ch.manaf.tauri_plugins.sign_in_with_apple + +import android.app.Activity +import app.tauri.annotation.Command +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin +import app.tauri.plugin.Invoke + +@InvokeArg +class PingArgs { + var value: String? = null +} + +@TauriPlugin +class ExamplePlugin(private val activity: Activity): Plugin(activity) { + private val implementation = Example() + + @Command + fun ping(invoke: Invoke) { + val args = invoke.parseArgs(PingArgs::class.java) + + val ret = JSObject() + ret.put("value", implementation.pong(args.value ?: "default value :(")) + invoke.resolve(ret) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/test/java/ExampleUnitTest.kt b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 000000000..d1c3ad2b5 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package ch.manaf.tauri_plugins.sign_in_with_apple + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/build.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/build.rs new file mode 100644 index 000000000..0ca719650 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/build.rs @@ -0,0 +1,8 @@ +const COMMANDS: &[&str] = &["get_apple_id_credential"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .android_path("android") + .ios_path("ios") + .build(); +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/guest-js/index.ts b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/guest-js/index.ts new file mode 100644 index 000000000..99c1234f9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/guest-js/index.ts @@ -0,0 +1,31 @@ +import { invoke } from '@tauri-apps/api/core' + +export type Scope = "fullName" | "email" +export interface AppleIDAuthorizationRequest { + scope: Scope[], + nonce?: string, + state?: string +} + +export interface AppleIDAuthorizationResponse { + // usually not null + userIdentifier: string | null, + + givenName: string | null, + familyName: string | null, + email: string | null, + + authorizationCode: string, + identityToken: string | null, + state: string | null +} + +export async function get_apple_id_credential(request: AppleIDAuthorizationRequest): Promise { + const result = await invoke('plugin:sign-in-with-apple|get_apple_id_credential', { + payload: request, + }) + + return result +} + + diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/.gitignore b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/.gitignore new file mode 100644 index 000000000..5922fdaa5 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.resolved b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.resolved new file mode 100644 index 000000000..7b82ebddb --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "SwiftRs", + "repositoryURL": "https://github.com/Brendonovich/swift-rs", + "state": { + "branch": null, + "revision": "207e4bfe5d36fdd0cc08f28d1849ff94ac7e24b8", + "version": "1.0.9" + } + } + ] + }, + "version": 1 +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.swift new file mode 100644 index 000000000..c95f5c0b6 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Package.swift @@ -0,0 +1,32 @@ +// swift-tools-version:5.3 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "tauri-plugin-sign-in-with-apple", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-sign-in-with-apple", + type: .static, + targets: ["tauri-plugin-sign-in-with-apple"]), + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-sign-in-with-apple", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/README.md b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/README.md new file mode 100644 index 000000000..9d6980b0f --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin sign_in_with_apple + +A description of this package. diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithAppleAuthorizationController.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithAppleAuthorizationController.swift new file mode 100644 index 000000000..9ed7f9c36 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithAppleAuthorizationController.swift @@ -0,0 +1,82 @@ +// +// SignInWithAppleAuthorizationController.swift +// tauri-plugin-sign-in-with-apple +// +// Created by Manaf Mhamdi Alaoui on 10.11.2024. +// + +import AuthenticationServices +import Tauri + +class SignInWithAppleAuthorizationController: NSObject, ASAuthorizationControllerDelegate { + var invoke: Invoke + init(_ invoke: Invoke) { + self.invoke = invoke + } + + public func authorize(req: AppleIDAuthorizationRequest) { + let provider = ASAuthorizationAppleIDProvider() + let request = provider.createRequest() + + if let nonce = req.nonce { + request.nonce = nonce + } + if let state = req.state { + request.state = state + } + + request.requestedScopes = [] + for scope in req.scope { + switch scope { + case "email": + request.requestedScopes?.append(.email) + case "fullName": + request.requestedScopes?.append(.fullName) + default: + NSLog("[tauri-plugin-sign-in-with-apple] Unsupported scope: \(scope)") + continue; + } + } + + let controller = ASAuthorizationController( + authorizationRequests: [ + request + ] + ) + + controller.delegate = self + controller.performRequests() + } + + public func authorizationController( + controller: ASAuthorizationController, + didCompleteWithAuthorization authorization: ASAuthorization + ) { + if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential { + invoke.resolve( + AppleIDAuthorizationResponse( + userIdentifier: !appleIDCredential.user.isEmpty ? + appleIDCredential.user : + nil, + givenName: appleIDCredential.fullName?.givenName, + familyName: appleIDCredential.fullName?.familyName, + email: appleIDCredential.email, + authorizationCode: String(decoding: appleIDCredential.authorizationCode!, as: UTF8.self), + identityToken: appleIDCredential.identityToken != nil ? + String(decoding: appleIDCredential.identityToken!, as: UTF8.self) : + nil, + state: appleIDCredential.state + ) + ) + } else { + invoke.reject("Returned credential is not of type ASAuthorizationAppleIDCredential") + } + } + + public func authorizationController( + controller: ASAuthorizationController, + didCompleteWithError error: Error + ) { + invoke.reject(error.localizedDescription) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithApplePlugin.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithApplePlugin.swift new file mode 100644 index 000000000..df9acc495 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Sources/SignInWithApplePlugin.swift @@ -0,0 +1,58 @@ +// +// SignInWithApplePlugin.swift +// tauri-plugin-sign-in-with-apple +// +// Created by Manaf Mhamdi Alaoui on 10.11.2024. +// + +import SwiftRs +import Tauri +import UIKit +import WebKit + +class AppleIDAuthorizationRequest: Decodable { + let scope: [String] + let nonce: String? + let state: String? +} + +class AppleIDAuthorizationResponse: Encodable { + let userIdentifier: String? + + let givenName: String? + let familyName: String? + let email: String? + + let authorizationCode: String + let identityToken: String? + let state: String? + + init(userIdentifier: String?, givenName: String?, familyName: String?, email: String?, authorizationCode: String, identityToken: String?, state: String?) { + self.userIdentifier = userIdentifier + self.givenName = givenName + self.familyName = familyName + self.email = email + self.authorizationCode = authorizationCode + self.identityToken = identityToken + self.state = state + } +} + +class SignInWithApplePlugin: Plugin { + var lastController: SignInWithAppleAuthorizationController? + + @objc public func get_apple_id_credential(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(AppleIDAuthorizationRequest.self) + + // store to keep alive + let controller = SignInWithAppleAuthorizationController(invoke) + lastController = controller + + controller.authorize(req: args) + } +} + +@_cdecl("init_plugin_sign_in_with_apple") +public func initPlugin() -> Plugin { + return SignInWithApplePlugin() +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Tests/PluginTests/PluginTests.swift b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 000000000..2f50e3087 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,8 @@ +import XCTest +@testable import SignInWithApplePlugin + +final class SignInWithApplePluginTests: XCTestCase { + func testExample() throws { + let plugin = SignInWithApplePlugin() + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/package.json b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/package.json new file mode 100644 index 000000000..7ad4261c5 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/package.json @@ -0,0 +1,34 @@ +{ + "name": "tauri-plugin-sign-in-with-apple-api", + "version": "1.0.2", + "author": "Manaf941", + "description": "", + "type": "module", + "license": "MIT", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "files": [ + "dist-js", + "README.md" + ], + "scripts": { + "build": "rollup -c", + "prepublishOnly": "pnpm build", + "pretest": "pnpm build" + }, + "dependencies": { + "@tauri-apps/api": ">=2.0.0-beta.6" + }, + "devDependencies": { + "@rollup/plugin-typescript": "^11.1.6", + "rollup": "^4.9.6", + "typescript": "^5.3.3", + "tslib": "^2.6.2" + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/default.toml b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/default.toml new file mode 100644 index 000000000..02f8eb766 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/default.toml @@ -0,0 +1,3 @@ +[default] +description = "Default permissions for the plugin" +permissions = ["allow-get-apple-id-credential"] diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/schemas/schema.json b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/schemas/schema.json new file mode 100644 index 000000000..149d8c00d --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/permissions/schemas/schema.json @@ -0,0 +1,318 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the get_apple_id_credential command without any pre-configured scope.", + "type": "string", + "const": "allow-get-apple-id-credential", + "markdownDescription": "Enables the get_apple_id_credential command without any pre-configured scope." + }, + { + "description": "Denies the get_apple_id_credential command without any pre-configured scope.", + "type": "string", + "const": "deny-get-apple-id-credential", + "markdownDescription": "Denies the get_apple_id_credential command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-apple-id-credential`", + "type": "string", + "const": "default", + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-apple-id-credential`" + } + ] + } + } +} \ No newline at end of file diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/rollup.config.js b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/rollup.config.js new file mode 100644 index 000000000..d83436352 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/rollup.config.js @@ -0,0 +1,31 @@ +import { readFileSync } from 'fs' +import { join } from 'path' +import { cwd } from 'process' +import typescript from '@rollup/plugin-typescript' + +const pkg = JSON.parse(readFileSync(join(cwd(), 'package.json'), 'utf8')) + +export default { + input: 'guest-js/index.ts', + output: [ + { + file: pkg.exports.import, + format: 'esm' + }, + { + file: pkg.exports.require, + format: 'cjs' + } + ], + plugins: [ + typescript({ + declaration: true, + declarationDir: `./${pkg.exports.import.split('/')[0]}` + }) + ], + external: [ + /^@tauri-apps\/api/, + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}) + ] +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/commands.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/commands.rs new file mode 100644 index 000000000..5f1e9b3d9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/commands.rs @@ -0,0 +1,13 @@ +use tauri::{AppHandle, command, Runtime}; + +use crate::models::*; +use crate::Result; +use crate::SignInWithAppleExt; + +#[command] +pub(crate) async fn get_apple_id_credential( + app: AppHandle, + payload: AppleIDAuthorizationRequest, +) -> Result { + app.sign_in_with_apple().get_apple_id_credential(payload) +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/desktop.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/desktop.rs new file mode 100644 index 000000000..833307bf6 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/desktop.rs @@ -0,0 +1,20 @@ +use serde::de::DeserializeOwned; +use tauri::{plugin::PluginApi, AppHandle, Runtime}; + +use crate::models::*; + +pub fn init( + app: &AppHandle, + _api: PluginApi, +) -> crate::Result> { + Ok(SignInWithApple(app.clone())) +} + +/// Access to the sign-in-with-apple APIs. +pub struct SignInWithApple(AppHandle); + +impl SignInWithApple { + pub fn get_apple_id_credential(&self, _payload: AppleIDAuthorizationRequest) -> crate::Result { + Err(crate::Error::UnsupportedPlatformError) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/error.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/error.rs new file mode 100644 index 000000000..0e76603ed --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/error.rs @@ -0,0 +1,23 @@ +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Unsupported platform for sign_in_with_apple")] + UnsupportedPlatformError, + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/lib.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/lib.rs new file mode 100644 index 000000000..4f4dec6a9 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/lib.rs @@ -0,0 +1,48 @@ +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +pub use models::*; + +#[cfg(desktop)] +mod desktop; +#[cfg(mobile)] +mod mobile; + +mod commands; +mod error; +mod models; + +pub use error::{Error, Result}; + +#[cfg(desktop)] +use desktop::SignInWithApple; +#[cfg(mobile)] +use mobile::SignInWithApple; + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the sign-in-with-apple APIs. +pub trait SignInWithAppleExt { + fn sign_in_with_apple(&self) -> &SignInWithApple; +} + +impl> crate::SignInWithAppleExt for T { + fn sign_in_with_apple(&self) -> &SignInWithApple { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("sign-in-with-apple") + .invoke_handler(tauri::generate_handler![commands::get_apple_id_credential]) + .setup(|app, api| { + #[cfg(mobile)] + let sign_in_with_apple = mobile::init(app, api)?; + #[cfg(desktop)] + let sign_in_with_apple = desktop::init(app, api)?; + app.manage(sign_in_with_apple); + Ok(()) + }) + .build() +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/mobile.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/mobile.rs new file mode 100644 index 000000000..c28ac9039 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/mobile.rs @@ -0,0 +1,34 @@ +use serde::de::DeserializeOwned; +use tauri::{ + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use crate::models::*; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_sign_in_with_apple); + +// initializes the Kotlin or Swift plugin classes +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin("ch.manaf.tauri_plugins.sign_in_with_apple", "ExamplePlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_sign_in_with_apple)?; + Ok(SignInWithApple(handle)) +} + +/// Access to the sign-in-with-apple APIs. +pub struct SignInWithApple(PluginHandle); + +impl SignInWithApple { + pub fn get_apple_id_credential(&self, payload: AppleIDAuthorizationRequest) -> crate::Result { + self + .0 + .run_mobile_plugin("get_apple_id_credential", payload) + .map_err(Into::into) + } +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/models.rs b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/models.rs new file mode 100644 index 000000000..980c7ec4c --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/src/models.rs @@ -0,0 +1,36 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppleIDAuthorizationRequest { + pub scope: Vec, + pub nonce: Option, + pub state: Option, +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppleIDAuthorizationResponse { + pub user_identifier: Option, + + pub given_name: Option, + pub family_name: Option, + pub email: Option, + + pub authorization_code: String, + pub identity_token: Option, + pub state: Option, +} + + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PingRequest { + pub value: Option, +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PingResponse { + pub value: Option, +} diff --git a/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/tsconfig.json b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/tsconfig.json new file mode 100644 index 000000000..059112270 --- /dev/null +++ b/apps/maple-research/frontend/src-tauri/patches/tauri-plugin-sign-in-with-apple-1.0.2/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2021", + "module": "esnext", + "moduleResolution": "bundler", + "skipLibCheck": true, + "strict": true, + "noUnusedLocals": true, + "noImplicitAny": true, + "noEmit": true + }, + "include": ["guest-js/*.ts"], + "exclude": ["dist-js", "node_modules"] +} diff --git a/apps/maple-research/frontend/src-tauri/plugins/tauri-plugin-iap/ios/Sources/IapPlugin.swift b/apps/maple-research/frontend/src-tauri/plugins/tauri-plugin-iap/ios/Sources/IapPlugin.swift index 6841fabf1..2fb10e151 100644 --- a/apps/maple-research/frontend/src-tauri/plugins/tauri-plugin-iap/ios/Sources/IapPlugin.swift +++ b/apps/maple-research/frontend/src-tauri/plugins/tauri-plugin-iap/ios/Sources/IapPlugin.swift @@ -311,7 +311,7 @@ class IapPlugin: Plugin { } @_cdecl("init_plugin_iap") -func initPlugin() -> Plugin { +public func initPlugin() -> Plugin { if #available(iOS 16.0, *) { return IapPlugin() } return Plugin() } diff --git a/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh b/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh index 21b6e43c8..0baf7fc9d 100755 --- a/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh +++ b/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh @@ -23,7 +23,7 @@ BUILD_DIR="${TAURI_DIR}/onnxruntime-build" OUTPUT_DIR="${TAURI_DIR}/onnxruntime-ios" XCFRAMEWORK_DIR="${OUTPUT_DIR}/onnxruntime.xcframework" -IOS_DEPLOYMENT_TARGET="13.0" +IOS_DEPLOYMENT_TARGET="16.0" echo "========================================" echo "Building ONNX Runtime ${ORT_VERSION} for iOS" diff --git a/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime.sh b/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime.sh index de3e38d0e..649e1df11 100755 --- a/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime.sh +++ b/apps/maple-research/frontend/src-tauri/scripts/build-ios-onnxruntime.sh @@ -24,7 +24,7 @@ OUTPUT_DIR="${TAURI_DIR}/onnxruntime-ios" XCFRAMEWORK_DIR="${OUTPUT_DIR}/onnxruntime.xcframework" # Minimum iOS version to support -IOS_DEPLOYMENT_TARGET="13.0" +IOS_DEPLOYMENT_TARGET="16.0" echo "========================================" echo "Building ONNX Runtime ${ORT_VERSION} for iOS" diff --git a/apps/maple-research/frontend/src-tauri/scripts/onnxruntime-pins.sh b/apps/maple-research/frontend/src-tauri/scripts/onnxruntime-pins.sh index 51cb644df..ce6b55e44 100644 --- a/apps/maple-research/frontend/src-tauri/scripts/onnxruntime-pins.sh +++ b/apps/maple-research/frontend/src-tauri/scripts/onnxruntime-pins.sh @@ -165,11 +165,11 @@ onnxruntime_ios_commit_for_version() { } onnxruntime_ios_device_lib_sha256_for_version() { - local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-17F42}}" + local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-27A9275}}" case "$1:${xcode_build}" in - 1.23.2:17F42) - printf '%s\n' "917cf5f30aa65f435371e323e347e094c99804fd6a268d3ae946cd8e5e58945f" + 1.23.2:27A9275) + printf '%s\n' "e4d288188f0b520d6676e87ad4a694326a8844618f471b5c8cfcdb69daf66657" ;; *) echo "No pinned ONNX Runtime iOS device library SHA-256 for version '$1' and Xcode build '${xcode_build}'." >&2 @@ -179,11 +179,11 @@ onnxruntime_ios_device_lib_sha256_for_version() { } onnxruntime_ios_simulator_lib_sha256_for_version() { - local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-17F42}}" + local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-27A9275}}" case "$1:${xcode_build}" in - 1.23.2:17F42) - printf '%s\n' "87c87225026db5ee2b5a1293468b9cc3410ad22eac182899056a05f2f2862071" + 1.23.2:27A9275) + printf '%s\n' "0b670a0fce021844551d7bd9fc0c3c4275208375155e0c17254866215ae28736" ;; *) echo "No pinned ONNX Runtime iOS simulator library SHA-256 for version '$1' and Xcode build '${xcode_build}'." >&2 @@ -193,11 +193,11 @@ onnxruntime_ios_simulator_lib_sha256_for_version() { } onnxruntime_ios_xcframework_sha256_for_version() { - local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-17F42}}" + local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-27A9275}}" case "$1:${xcode_build}" in - 1.23.2:17F42) - printf '%s\n' "86871fe82fb5b043540d5bbc67de5ba73f2a52105ec05c66e835b7add8972af5" + 1.23.2:27A9275) + printf '%s\n' "93cbb699abebba06963ccf1b5c283ea0bbf7b8e9e4664f9b2df726d2808bf18a" ;; *) echo "No pinned ONNX Runtime iOS xcframework SHA-256 for version '$1' and Xcode build '${xcode_build}'." >&2 diff --git a/apps/maple-research/frontend/src-tauri/tests/storekit/README.md b/apps/maple-research/frontend/src-tauri/tests/storekit/README.md index 96943c890..e570b3064 100644 --- a/apps/maple-research/frontend/src-tauri/tests/storekit/README.md +++ b/apps/maple-research/frontend/src-tauri/tests/storekit/README.md @@ -26,8 +26,9 @@ re-signs/verifies them, and generates the local experiment scheme. Source plist and entitlements are restored. Preserve the existing frozen artifact when comparing toolchains; rebuilding Maple is a separate action. -These test commands explicitly select installed Xcode 26.6 **after entering** the -pinned Apple shell, without changing repository pins or global Xcode selection. +These test commands use the Xcode selected by the pinned Apple shell +(`scripts/ci/apple-toolchain.json`). The dated notes below record earlier runs on +Xcode 26.5 and 26.6. Use `run.sh`, which prepares native linker variables; do not invoke `run.py` directly from an unprepared Nix shell. Substitute the exact frozen app path and intended simulator UDID when different from these examples. @@ -35,9 +36,7 @@ intended simulator UDID when different from these examples. First build the small runner and run the catalog canary, which needs no fixture: ```sh -nix develop --no-update-lock-file .#apple -c env \ - MAPLE_NIX_XCODE_VERSION=26.6 MAPLE_NIX_XCODE_BUILD_VERSION=17F113 \ - DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \ +nix develop --no-update-lock-file .#apple -c \ bash scripts/testing/storekit/run.sh \ --app apps/maple-research/frontend/src-tauri/gen/apple/build/arm64-sim/Maple.app \ --udid "" \ @@ -47,9 +46,7 @@ nix develop --no-update-lock-file .#apple -c env \ Then run the certificate canary with a fresh runner-owned bootstrap fixture: ```sh -nix develop --no-update-lock-file .#apple -c env \ - MAPLE_NIX_XCODE_VERSION=26.6 MAPLE_NIX_XCODE_BUILD_VERSION=17F113 \ - DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \ +nix develop --no-update-lock-file .#apple -c \ bash scripts/testing/storekit/run.sh --skip-build --fixture-bootstrap \ --app apps/maple-research/frontend/src-tauri/gen/apple/build/arm64-sim/Maple.app \ --udid "" \ @@ -69,9 +66,7 @@ Pass that certificate to the remaining cases. They reuse the runner build but each gets a separate XCTest invocation, fixture process and journal: ```sh -nix develop --no-update-lock-file .#apple -c env \ - MAPLE_NIX_XCODE_VERSION=26.6 MAPLE_NIX_XCODE_BUILD_VERSION=17F113 \ - DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \ +nix develop --no-update-lock-file .#apple -c \ bash scripts/testing/storekit/run.sh --skip-build \ --fixture-certificate /absolute/path/to/MapleStoreKitSigningCertificate.cer \ --app apps/maple-research/frontend/src-tauri/gen/apple/build/arm64-sim/Maple.app \ @@ -175,9 +170,7 @@ To export metadata from an already running purchased diagnostic screen without reinstalling or restarting Maple, pass the exact GUI-built DerivedData app: ```sh -nix develop --no-update-lock-file .#apple -c env \ - MAPLE_NIX_XCODE_VERSION=26.6 MAPLE_NIX_XCODE_BUILD_VERSION=17F113 \ - DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \ +nix develop --no-update-lock-file .#apple -c \ bash scripts/testing/storekit/run.sh --app /absolute/path/to/Maple.app \ --udid "" \ --only MapleStoreKitCaptureUITests/testExportPublicSigningCertificate @@ -202,7 +195,7 @@ The exact Foundation-only parser can also be checked on the host, without building an app or operating a simulator: ```sh -DEVELOPER_DIR=/Applications/Xcode_26.6.app/Contents/Developer \ +DEVELOPER_DIR=/Applications/Xcode_27.1.app/Contents/Developer \ python3 scripts/testing/storekit/check-snapshot-parser.py ``` diff --git a/docs/repository-workflows.md b/docs/repository-workflows.md index 338230424..09305f270 100644 --- a/docs/repository-workflows.md +++ b/docs/repository-workflows.md @@ -164,22 +164,28 @@ for the baseline, tradeoffs and cold/warm validation procedure. ## macOS runners and Apple toolchain -Research desktop/iOS builds, Agent release-binary jobs -and native proxy macOS archives use ARM64 `blacksmith-6vcpu-macos-26` runners. -Artifact verifiers, TestFlight uploads, and Agent preview packaging/protected -signing use standard GitHub `macos-26` runners. Research signed builders retain +Research desktop/iOS builds and native proxy macOS archives use ARM64 +`blacksmith-6vcpu-macos-27` runners. Artifact verifiers, TestFlight uploads, +and the Agent's release binaries, preview packaging and protected signing use +GitHub's `xcode-27` image runners. The Agent binary build moves back to +Blacksmith once that image can install the Metal Toolchain gpui's shaders need. +Research signed builders retain their existing `desktop-signing` or `apple-signing` environment, so their provider receives that environment's secret context. Existing master pushes can build and upload independent Dev/Prod TestFlight artifacts after verification. The Agent's final signed-package provenance identifies its GitHub -packaging/signing job; Blacksmith is trusted for prebuilt binary integrity. -Research desktop and iOS builds select stable Xcode 26.5, build `17F42`, from +packaging/signing job. +Research desktop and iOS builds and the Agent's macOS jobs select stable Xcode 27.1, +build `27A9275`, from [`scripts/ci/apple-toolchain.json`](../scripts/ci/apple-toolchain.json) through the shared selector, which aligns the runner's active `xcode-select` directory and `DEVELOPER_DIR`; the root Nix shell exports the same version and build for its Apple recipes. Tauri mobile tooling discovers the active Xcode directly. Do not rely on the runner's default Xcode or beta app -names. The independent Agent shell selects a full installed Xcode, and the +names; hosted images can keep a release under a suffixed name such as +`Xcode_27.1_Release_Candidate.app`, which the selector accepts only when its +build matches the pin. Xcode 27 builds iOS code for deployment targets 15.0 and +later, so the iOS ONNX Runtime build targets 16.0 like the app. The independent Agent shell selects a full installed Xcode, and the SDK/proxy flakes own their Nix compiler and SDK inputs. The [iOS ONNX pins](../apps/maple-research/frontend/src-tauri/scripts/onnxruntime-pins.sh) diff --git a/scripts/ci/_common.sh b/scripts/ci/_common.sh index f9886112a..d8a00955a 100755 --- a/scripts/ci/_common.sh +++ b/scripts/ci/_common.sh @@ -1257,6 +1257,16 @@ linuxdeploy_tools_arch() { esac } +# tauri-cli runs target/.tauri/linuxdeploy--.AppImage and downloads +# an unpinned linuxdeploy when that file is missing, so the pinned wrapper is +# installed under that name. The hash is LINUXDEPLOY_COMMIT_HASH in the +# tauri-bundler that @tauri-apps/cli ships; update it with the CLI. +tauri_linuxdeploy_wrapper() { + local arch + arch="$(linuxdeploy_tools_arch)" || return 1 + printf '%s\n' "${TAURI_DIR}/target/.tauri/linuxdeploy-07333c6-${arch}.AppImage" +} + write_chmod_patchelf_wrapper() { local wrapper="$1" local real_patchelf="$2" @@ -1441,7 +1451,7 @@ prepare_tauri_linuxdeploy_tools_cache() { tools="${MAPLE_NIX_TAURI_LINUXDEPLOY_TOOLS}" cache="${TAURI_DIR}/target/.tauri" internal="${cache}/maple-linuxdeploy-tools" - linuxdeploy_wrapper="${cache}/linuxdeploy-${linuxdeploy_arch}.AppImage" + linuxdeploy_wrapper="$(tauri_linuxdeploy_wrapper)" appimage_wrapper="${cache}/linuxdeploy-plugin-appimage.AppImage" linuxdeploy_appdir_bin="${cache}/linuxdeploy-${linuxdeploy_arch}.AppDir/usr/bin" plugin_bin="${internal}/plugins" @@ -1470,8 +1480,9 @@ prepare_tauri_linuxdeploy_tools_cache() { wrap_linuxdeploy_appdir_patchelf "${linuxdeploy_appdir_bin}" rm -f "${linuxdeploy_appdir_bin}"/linuxdeploy-plugin-* - write_linuxdeploy_input_plugin_wrapper "${cache}/linuxdeploy-plugin-gtk.sh" "maple-linuxdeploy-tools/linuxdeploy-plugin-gtk.real.sh" - write_linuxdeploy_input_plugin_wrapper "${cache}/linuxdeploy-plugin-gstreamer.sh" "maple-linuxdeploy-tools/linuxdeploy-plugin-gstreamer.real.sh" + # tauri-cli also writes its own embedded linuxdeploy-plugin-*.sh into the + # cache. The wrapper replaces PATH and unsets APPIMAGE, so linuxdeploy only + # finds the pinned plugins below. write_linuxdeploy_input_plugin_wrapper "${plugin_bin}/linuxdeploy-plugin-gtk" "../linuxdeploy-plugin-gtk.real.sh" write_linuxdeploy_input_plugin_wrapper "${plugin_bin}/linuxdeploy-plugin-gstreamer" "../linuxdeploy-plugin-gstreamer.real.sh" @@ -1809,7 +1820,7 @@ EOF print_file_hashes \ "${cache}/AppRun-${arch}" \ "${cache}/linuxdeploy-${linuxdeploy_arch}.real.AppImage" \ - "${cache}/linuxdeploy-${linuxdeploy_arch}.AppImage" \ + "${linuxdeploy_wrapper}" \ "${cache}/linuxdeploy-${linuxdeploy_arch}.AppDir/AppRun" \ "${plugin_bin}/linuxdeploy-plugin-appimage" \ "${plugin_bin}/linuxdeploy-plugin-gtk" \ @@ -1817,9 +1828,7 @@ EOF "${appimage_real_appimage}" \ "${cache}/linuxdeploy-plugin-appimage.AppImage" \ "${appimage_appdir}/AppRun" \ - "${runtime_file}" \ - "${cache}/linuxdeploy-plugin-gtk.sh" \ - "${cache}/linuxdeploy-plugin-gstreamer.sh" + "${runtime_file}" } verify_linuxdeploy_plugin_metadata() { @@ -2803,7 +2812,7 @@ verify_ios_onnxruntime_pinned_hashes() { local version xcode_build xcframework manifest file expected actual status version="$(ios_onnxruntime_version)" - xcode_build="${MAPLE_XCODE_BUILD_VERSION:-17F42}" + xcode_build="${MAPLE_XCODE_BUILD_VERSION:-27A9275}" xcframework="$(ios_onnxruntime_xcframework_dir)" manifest="$(ios_onnxruntime_manifest_file)" status=0 diff --git a/scripts/ci/apple-toolchain.json b/scripts/ci/apple-toolchain.json index 8f2cd86e1..9aa97b351 100644 --- a/scripts/ci/apple-toolchain.json +++ b/scripts/ci/apple-toolchain.json @@ -1,4 +1,4 @@ { - "version": "26.5", - "build": "17F42" + "version": "27.1", + "build": "27A9275" } diff --git a/scripts/ci/desktop-pr.sh b/scripts/ci/desktop-pr.sh index 9b10f1da1..b37959349 100755 --- a/scripts/ci/desktop-pr.sh +++ b/scripts/ci/desktop-pr.sh @@ -23,7 +23,7 @@ case "$(host_os)" in prepend_linux_runtime_library_path prepare_tauri_linuxdeploy_tools_cache verify_linuxdeploy_plugin_metadata - run_with_nix_usr_bin "${TAURI_DIR}/target/.tauri/linuxdeploy-$(linuxdeploy_tools_arch).AppImage" --appimage-extract-and-run --list-plugins + run_with_nix_usr_bin "$(tauri_linuxdeploy_wrapper)" --appimage-extract-and-run --list-plugins run_with_nix_usr_bin pkg-config --modversion glib-2.0 remove_build_tree "${TAURI_DIR}/target/release/bundle/appimage" remove_build_tree "${TAURI_DIR}/target/release/bundle/deb" diff --git a/scripts/ci/desktop-release.sh b/scripts/ci/desktop-release.sh index b20a0d97c..fa82c404a 100755 --- a/scripts/ci/desktop-release.sh +++ b/scripts/ci/desktop-release.sh @@ -41,7 +41,7 @@ case "$(host_os)" in prepend_linux_runtime_library_path prepare_tauri_linuxdeploy_tools_cache verify_linuxdeploy_plugin_metadata - run_with_nix_usr_bin "${TAURI_DIR}/target/.tauri/linuxdeploy-$(linuxdeploy_tools_arch).AppImage" --appimage-extract-and-run --list-plugins + run_with_nix_usr_bin "$(tauri_linuxdeploy_wrapper)" --appimage-extract-and-run --list-plugins remove_build_tree "${TAURI_DIR}/target/release/bundle/appimage" remove_build_tree "${TAURI_DIR}/target/release/bundle/deb" diff --git a/scripts/ci/select-xcode.py b/scripts/ci/select-xcode.py index 2e29aadb5..8a85f59cb 100644 --- a/scripts/ci/select-xcode.py +++ b/scripts/ci/select-xcode.py @@ -29,7 +29,10 @@ def load_pin(path=PIN): def select_xcode(pin, applications=Path("/Applications")): version = pin["version"] - for name in (f"Xcode_{version}.app", f"Xcode_{version}.0.app", "Xcode.app"): + # Hosted images may keep a release under a suffixed name (for example + # Xcode_27.1_Release_Candidate.app); the exact build check below decides. + suffixed = sorted(path.name for path in applications.glob(f"Xcode_{version}_*.app")) + for name in (f"Xcode_{version}.app", f"Xcode_{version}.0.app", *suffixed, "Xcode.app"): developer = applications / name / "Contents/Developer" if not (developer / "usr/bin/xcodebuild").is_file(): continue diff --git a/scripts/ci/test-canonical-macho.py b/scripts/ci/test-canonical-macho.py index 99e344fe0..94790a3af 100644 --- a/scripts/ci/test-canonical-macho.py +++ b/scripts/ci/test-canonical-macho.py @@ -71,6 +71,35 @@ def local_absolute_symbols(path): return symbols +def swap_local_absolute_symbols(path): + """Swap the first two local absolute nlist entries in place.""" + data = bytearray(Path(path).read_bytes()) + _, _, _, _, ncmds, _, _, _ = struct.unpack_from(" MAPLE_NIX_XCODE_BUILD_VERSION= \ +# DEVELOPER_DIR=/Applications/Xcode_.app/Contents/Developer \ # bash scripts/testing/storekit/run.sh ... source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../../ci" && pwd)/_common.sh" use_xcode_toolchain